mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-15 13:16:43 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec985475c7 | |||
| c059b0e5e1 | |||
| f0a0f12767 | |||
| aae469118e | |||
| 96fe8e0382 | |||
| e05869de89 | |||
| 122dcaf913 | |||
| 378dd69dec | |||
| 1a02b91cdc | |||
| 447c288023 | |||
| 0f763b6de7 |
+21
-5
@@ -81,6 +81,9 @@ cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSV
|
||||
option(ENABLE_DEBUG_TOOLS "Enable debugging tools (maxwell disassembler, SPIRV translator, etc)" OFF)
|
||||
option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
|
||||
|
||||
# Lossless Scaling frame generation. Only Android.
|
||||
cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
|
||||
|
||||
# non-linux bundled qt are static
|
||||
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
|
||||
set(YUZU_STATIC_BUILD ON)
|
||||
@@ -339,13 +342,26 @@ if (CXX_GCC OR CXX_CLANG)
|
||||
endif()
|
||||
elseif(ARCHITECTURE_arm64)
|
||||
# See https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html
|
||||
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, armv9, native")
|
||||
set(mtune generic)
|
||||
set(armv8_2_target armv8.2-a+fp16+dotprod)
|
||||
|
||||
if (${YUZU_BUILD_PRESET} STREQUAL "generic")
|
||||
set(march armv8-a)
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9")
|
||||
set(march armv9-a)
|
||||
if (ANDROID)
|
||||
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, armv8.2, armv9, native")
|
||||
set(march ${armv8_2_target})
|
||||
|
||||
if (${YUZU_BUILD_PRESET} STREQUAL "armv9")
|
||||
set(march armv9-a)
|
||||
endif()
|
||||
else()
|
||||
set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, armv8.2, armv9, native")
|
||||
|
||||
if (${YUZU_BUILD_PRESET} STREQUAL "generic")
|
||||
set(march armv8-a)
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv8.2")
|
||||
set(march ${armv8_2_target})
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9")
|
||||
set(march armv9-a)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -539,6 +539,40 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun installKeys(path: String, ext: String): Int
|
||||
|
||||
/**
|
||||
* @return Whether this GPU can run the Lossless Scaling frame generation shaders,
|
||||
* which are built against the Vulkan memory model.
|
||||
*/
|
||||
external fun supportsFrameGeneration(): Boolean
|
||||
|
||||
/**
|
||||
* @return Path the user-supplied Lossless Scaling library is expected at.
|
||||
*/
|
||||
external fun getLosslessDllPath(): String
|
||||
|
||||
/**
|
||||
* Parses the installed Lossless Scaling library and checks that every shader the
|
||||
* frame generation chain needs is present.
|
||||
*
|
||||
* @return The result code, matching the losslessDllResults array.
|
||||
*/
|
||||
external fun validateLosslessDll(): Int
|
||||
|
||||
/**
|
||||
* Translates the frame generation shaders out of the installed Lossless Scaling library
|
||||
* and writes them to the SPIR-V cache. Slow, so call it off the main thread.
|
||||
*
|
||||
* @return The result code, matching the losslessDllResults array.
|
||||
*/
|
||||
external fun prepareLosslessDll(): Int
|
||||
|
||||
/**
|
||||
* Deletes the installed Lossless Scaling library.
|
||||
*
|
||||
* @return Whether the library is gone after the call.
|
||||
*/
|
||||
external fun removeLosslessDll(): Boolean
|
||||
|
||||
/**
|
||||
* Checks the PatchManager for any addons that are available
|
||||
*
|
||||
|
||||
+4
@@ -37,6 +37,10 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||
RENDERER_FRAME_GEN("frame_gen"),
|
||||
RENDERER_FRAME_GEN_FP16("frame_gen_fp16"),
|
||||
RENDERER_FRAME_GEN_FLOW_SCALE_AUTO("frame_gen_flow_scale_auto"),
|
||||
RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"),
|
||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
||||
|
||||
@@ -19,6 +19,10 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
RENDERER_ASTC_DECODE_METHOD("accelerate_astc"),
|
||||
RENDERER_ACCURACY("gpu_accuracy"),
|
||||
RENDERER_RESOLUTION("resolution_setup"),
|
||||
RENDERER_FRAME_GEN_MULTIPLIER("frame_gen_multiplier"),
|
||||
RENDERER_FRAME_GEN_TARGET_RATE("frame_gen_target_rate"),
|
||||
RENDERER_FRAME_GEN_QUEUE_TARGET("frame_gen_queue_target"),
|
||||
RENDERER_FRAME_GEN_FLOW_SCALE("frame_gen_flow_scale"),
|
||||
RENDERER_VSYNC("use_vsync"),
|
||||
RENDERER_SCALING_FILTER("scaling_filter"),
|
||||
RENDERER_ANTI_ALIASING("anti_aliasing"),
|
||||
|
||||
@@ -11,6 +11,7 @@ object Settings {
|
||||
SECTION_ROOT(R.string.advanced_settings),
|
||||
SECTION_SYSTEM(R.string.preferences_system),
|
||||
SECTION_RENDERER(R.string.preferences_graphics),
|
||||
SECTION_FRAME_GEN(R.string.frame_gen),
|
||||
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
||||
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
|
||||
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
|
||||
|
||||
+103
@@ -21,6 +21,7 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.network.NetDataValidators
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
/**
|
||||
@@ -65,6 +66,19 @@ abstract class SettingsItem(
|
||||
return NativeLibrary.isFirmwareAvailable()
|
||||
}
|
||||
|
||||
if (setting.key in frameGenKeys &&
|
||||
!(LosslessScalingHelper.isInstalled() && LosslessScalingHelper.isSupportedByGpu())
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// A frame rate target moves the multiplier on its own
|
||||
if (setting.key == IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key &&
|
||||
frameGenTargetRate != 0
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
||||
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||
return false
|
||||
@@ -88,7 +102,31 @@ abstract class SettingsItem(
|
||||
val clearable: Boolean
|
||||
get() = !setting.global && NativeConfig.isPerGameConfigLoaded()
|
||||
|
||||
private val frameGenTargetRate: Int
|
||||
get() {
|
||||
val key = IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key
|
||||
val needsGlobal = if (NativeLibrary.isRunning() &&
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
!NativeConfig.usingGlobal(key)
|
||||
} else {
|
||||
NativeConfig.usingGlobal(key)
|
||||
}
|
||||
return IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(needsGlobal)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val frameGenKeys = setOf(
|
||||
BooleanSetting.RENDERER_FRAME_GEN.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key,
|
||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FP16.key,
|
||||
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key
|
||||
)
|
||||
|
||||
const val TYPE_HEADER = 0
|
||||
const val TYPE_SWITCH = 1
|
||||
const val TYPE_SINGLE_CHOICE = 2
|
||||
@@ -607,6 +645,71 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.rendererAntiAliasingValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FRAME_GEN,
|
||||
titleId = R.string.frame_gen,
|
||||
descriptionId = R.string.frame_gen_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER,
|
||||
titleId = R.string.frame_gen_multiplier,
|
||||
descriptionId = R.string.frame_gen_multiplier_description,
|
||||
choicesId = R.array.frameGenMultiplierNames,
|
||||
valuesId = R.array.frameGenMultiplierValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE,
|
||||
titleId = R.string.frame_gen_target_rate,
|
||||
descriptionId = R.string.frame_gen_target_rate_description,
|
||||
choicesId = R.array.frameGenTargetRateNames,
|
||||
valuesId = R.array.frameGenTargetRateValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET,
|
||||
titleId = R.string.frame_gen_queue_target,
|
||||
descriptionId = R.string.frame_gen_queue_target_description,
|
||||
choicesId = R.array.frameGenQueueTargetNames,
|
||||
valuesId = R.array.frameGenQueueTargetValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO,
|
||||
titleId = R.string.frame_gen_flow_scale_auto,
|
||||
descriptionId = R.string.frame_gen_flow_scale_auto_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE,
|
||||
titleId = R.string.frame_gen_flow_scale,
|
||||
descriptionId = R.string.frame_gen_flow_scale_description,
|
||||
min = 25,
|
||||
max = 100,
|
||||
units = "%"
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FRAME_GEN_FP16,
|
||||
titleId = R.string.frame_gen_fp16,
|
||||
descriptionId = R.string.frame_gen_fp16_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW,
|
||||
titleId = R.string.frame_gen_dump_flow,
|
||||
descriptionId = R.string.frame_gen_dump_flow_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||
|
||||
+3
-1
@@ -382,7 +382,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
}
|
||||
scSetting.setSelectedValue(value)
|
||||
|
||||
if (scSetting.setting.key == IntSetting.RENDERER_SCALING_FILTER.key) {
|
||||
if (scSetting.setting.key == IntSetting.RENDERER_SCALING_FILTER.key ||
|
||||
scSetting.setting.key == IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key
|
||||
) {
|
||||
settingsViewModel.setShouldReloadSettingsList(true)
|
||||
}
|
||||
|
||||
|
||||
+38
-1
@@ -27,6 +27,7 @@ import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
||||
@@ -76,6 +77,41 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun addFrameGenSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
if (!LosslessScalingHelper.isSupportedByGpu()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.frame_gen_unsupported,
|
||||
descriptionId = R.string.frame_gen_unsupported_description,
|
||||
isRunnable = false
|
||||
) {}
|
||||
)
|
||||
} else if (!LosslessScalingHelper.isInstalled()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.lossless_scaling_missing,
|
||||
descriptionId = R.string.lossless_scaling_missing_description,
|
||||
isRunnable = false
|
||||
) {}
|
||||
)
|
||||
}
|
||||
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
|
||||
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
||||
if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(
|
||||
getNeedsGlobalForKey(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
||||
)
|
||||
) {
|
||||
add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key)
|
||||
}
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN_FP16.key)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSharpnessScalingFilterSelected(): Boolean {
|
||||
val needsGlobal = getNeedsGlobalForKey(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||
val selectedFilter = IntSetting.RENDERER_SCALING_FILTER.getInt(needsGlobal)
|
||||
@@ -120,6 +156,7 @@ class SettingsFragmentPresenter(
|
||||
MenuTag.SECTION_ROOT -> addConfigSettings(sl)
|
||||
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
|
||||
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
||||
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
||||
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
||||
@@ -302,7 +339,6 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
|
||||
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
|
||||
add(BooleanSetting.EMULATE_BGR565.key)
|
||||
add(BooleanSetting.RESCALE_HACK.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
|
||||
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
|
||||
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
|
||||
@@ -1296,6 +1332,7 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.DUMP_GUEST_SHADERS.key)
|
||||
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
|
||||
add(BooleanSetting.DUMP_MACROS.key)
|
||||
add(BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key)
|
||||
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
|
||||
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
|
||||
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
|
||||
|
||||
+2
@@ -29,6 +29,7 @@ enum class SettingsSubscreen {
|
||||
DRIVER_MANAGER,
|
||||
DRIVER_FETCHER,
|
||||
FREEDRENO_SETTINGS,
|
||||
LOSSLESS_MANAGER,
|
||||
APPLET_LAUNCHER,
|
||||
INSTALLABLE,
|
||||
GAME_FOLDERS,
|
||||
@@ -126,6 +127,7 @@ class SettingsSubscreenActivity : AppCompatActivity() {
|
||||
SettingsSubscreen.DRIVER_MANAGER -> R.id.driverManagerFragment
|
||||
SettingsSubscreen.DRIVER_FETCHER -> R.id.driverFetcherFragment
|
||||
SettingsSubscreen.FREEDRENO_SETTINGS -> R.id.freedrenoSettingsFragment
|
||||
SettingsSubscreen.LOSSLESS_MANAGER -> R.id.losslessManagerFragment
|
||||
SettingsSubscreen.APPLET_LAUNCHER -> R.id.appletLauncherFragment
|
||||
SettingsSubscreen.INSTALLABLE -> R.id.installableFragment
|
||||
SettingsSubscreen.GAME_FOLDERS -> R.id.gameFoldersFragment
|
||||
|
||||
@@ -369,6 +369,21 @@ class GamePropertiesFragment : Fragment() {
|
||||
)
|
||||
)
|
||||
}
|
||||
add(
|
||||
SubmenuProperty(
|
||||
R.string.frame_gen,
|
||||
R.string.frame_gen_per_game_description,
|
||||
R.drawable.ic_duck,
|
||||
action = {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
args.game,
|
||||
Settings.MenuTag.SECTION_FRAME_GEN
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||
add(
|
||||
SubmenuProperty(
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
|
||||
class HomeSettingsFragment : Fragment() {
|
||||
@@ -170,6 +171,24 @@ class HomeSettingsFragment : Fragment() {
|
||||
)
|
||||
)
|
||||
}
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.lossless_scaling,
|
||||
R.string.lossless_scaling_description,
|
||||
R.drawable.ic_duck,
|
||||
{
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.LOSSLESS_MANAGER,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
},
|
||||
{ true },
|
||||
0,
|
||||
0,
|
||||
LosslessScalingHelper.statusText
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.multiplayer,
|
||||
@@ -337,6 +356,7 @@ class HomeSettingsFragment : Fragment() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
driverViewModel.updateDriverNameForGame(null)
|
||||
LosslessScalingHelper.refreshStatus()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentLosslessManagerBinding
|
||||
import org.yuzu.yuzu_emu.features.fetcher.SpacingItemDecoration
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class LosslessManagerFragment : Fragment() {
|
||||
private var _binding: FragmentLosslessManagerBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentLosslessManagerBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.toolbarLossless.setNavigationOnClickListener {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.losslessOptionsList.apply {
|
||||
layoutManager =
|
||||
GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns))
|
||||
addItemDecoration(
|
||||
SpacingItemDecoration(resources.getDimensionPixelSize(R.dimen.spacing_small))
|
||||
)
|
||||
}
|
||||
|
||||
LosslessScalingHelper.statusText.collect(viewLifecycleOwner) { refreshOptions() }
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
private fun refreshOptions() {
|
||||
binding.losslessOptionsList.adapter = HomeSettingAdapter(
|
||||
requireActivity() as AppCompatActivity,
|
||||
viewLifecycleOwner,
|
||||
buildOptions()
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildOptions(): List<HomeSetting> {
|
||||
val installed = LosslessScalingHelper.isInstalled()
|
||||
return listOf(
|
||||
HomeSetting(
|
||||
if (installed) R.string.lossless_scaling_replace else R.string.lossless_scaling_install,
|
||||
if (installed) {
|
||||
R.string.lossless_scaling_replace_description
|
||||
} else {
|
||||
R.string.lossless_scaling_install_description
|
||||
},
|
||||
R.drawable.ic_install,
|
||||
{ dllPickerLauncher.launch(arrayOf("*/*")) },
|
||||
{ !NativeLibrary.isRunning() },
|
||||
R.string.lossless_scaling_locked,
|
||||
R.string.lossless_scaling_locked_description,
|
||||
LosslessScalingHelper.statusText
|
||||
),
|
||||
HomeSetting(
|
||||
R.string.lossless_scaling_remove,
|
||||
R.string.lossless_scaling_remove_description,
|
||||
R.drawable.ic_delete,
|
||||
{ confirmRemoval() },
|
||||
{ installed && !NativeLibrary.isRunning() },
|
||||
if (installed) {
|
||||
R.string.lossless_scaling_locked
|
||||
} else {
|
||||
R.string.lossless_scaling_remove_unavailable
|
||||
},
|
||||
if (installed) {
|
||||
R.string.lossless_scaling_locked_description
|
||||
} else {
|
||||
R.string.lossless_scaling_remove_unavailable_description
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun confirmRemoval() {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.lossless_scaling_remove,
|
||||
descriptionId = R.string.lossless_scaling_remove_confirmation,
|
||||
positiveButtonTitleId = R.string.lossless_scaling_remove,
|
||||
positiveAction = { LosslessScalingHelper.remove() },
|
||||
showNegativeButton = true,
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
private val dllPickerLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.lossless_scaling_installing,
|
||||
false
|
||||
) { _, _ ->
|
||||
val installResult = LosslessScalingHelper.install(result)
|
||||
if (installResult == LosslessScalingHelper.RESULT_OK) {
|
||||
getString(R.string.lossless_scaling_install_success)
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.lossless_scaling_install_failed,
|
||||
descriptionString = resultStrings[installResult]
|
||||
)
|
||||
}
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
binding.appbarLossless.updateMargins(
|
||||
left = barInsets.left + cutoutInsets.left,
|
||||
right = barInsets.right + cutoutInsets.right
|
||||
)
|
||||
|
||||
binding.scrollViewLossless.updatePadding(bottom = barInsets.bottom)
|
||||
|
||||
binding.losslessOptionsList.updatePadding(
|
||||
left = barInsets.left + cutoutInsets.left,
|
||||
right = barInsets.right + cutoutInsets.right
|
||||
)
|
||||
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -42,6 +42,7 @@ import org.yuzu.yuzu_emu.model.SetupPage
|
||||
import org.yuzu.yuzu_emu.model.PageState
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
@@ -202,6 +203,24 @@ class SetupFragment : Fragment() {
|
||||
R.string.install_firmware_warning_help,
|
||||
)
|
||||
)
|
||||
add(
|
||||
PageButton(
|
||||
R.drawable.ic_duck,
|
||||
R.string.lossless_scaling,
|
||||
R.string.lossless_scaling_setup_description,
|
||||
{
|
||||
pageButtonCallback = it
|
||||
getLosslessDll.launch(arrayOf("*/*"))
|
||||
},
|
||||
{
|
||||
if (LosslessScalingHelper.isInstalled()) {
|
||||
ButtonState.BUTTON_ACTION_COMPLETE
|
||||
} else {
|
||||
ButtonState.BUTTON_ACTION_INCOMPLETE
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
add(
|
||||
PageButton(
|
||||
R.drawable.ic_controller,
|
||||
@@ -446,6 +465,32 @@ class SetupFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
val getLosslessDll =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.lossless_scaling_installing,
|
||||
false
|
||||
) { _, _ ->
|
||||
val installResult = LosslessScalingHelper.install(result)
|
||||
if (installResult == LosslessScalingHelper.RESULT_OK) {
|
||||
getString(R.string.lossless_scaling_install_success)
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.lossless_scaling_install_failed,
|
||||
descriptionString = resultStrings[installResult]
|
||||
)
|
||||
}
|
||||
}.apply {
|
||||
onDialogComplete = { checkForButtonState.invoke() }
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
val getGamesDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||
if (result != null) {
|
||||
|
||||
+13
-1
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -72,6 +72,18 @@ class SystemInfoDialogFragment : DialogFragment() {
|
||||
|
||||
val vulkanDriver = NativeLibrary.getVulkanDriverVersion()
|
||||
appendLine("${getString(R.string.vulkan_driver_version)}: $vulkanDriver")
|
||||
|
||||
val frameGen = NativeLibrary.supportsFrameGeneration()
|
||||
appendLine(
|
||||
"${getString(R.string.frame_generation_support)}: " +
|
||||
getString(
|
||||
if (frameGen) {
|
||||
R.string.frame_generation_supported
|
||||
} else {
|
||||
R.string.frame_generation_unsupported
|
||||
}
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
appendLine("${getString(R.string.error_getting_emulator_info)}: ${e.message}")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.net.Uri
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import java.io.File
|
||||
|
||||
object LosslessScalingHelper {
|
||||
const val RESULT_OK = 0
|
||||
const val RESULT_NOT_INSTALLED = 1
|
||||
|
||||
private val _statusText = MutableStateFlow("")
|
||||
val statusText: StateFlow<String> = _statusText.asStateFlow()
|
||||
|
||||
private var installed: Boolean? = null
|
||||
private var gpuSupported: Boolean? = null
|
||||
|
||||
fun isInstalled(): Boolean = installed ?: refreshStatus()
|
||||
|
||||
fun isSupportedByGpu(): Boolean {
|
||||
val cached = gpuSupported
|
||||
if (cached != null) {
|
||||
return cached
|
||||
}
|
||||
val result = NativeLibrary.supportsFrameGeneration()
|
||||
gpuSupported = result
|
||||
return result
|
||||
}
|
||||
|
||||
fun refreshStatus(): Boolean {
|
||||
val result = NativeLibrary.validateLosslessDll() == RESULT_OK
|
||||
installed = result
|
||||
|
||||
val context = YuzuApplication.appContext
|
||||
_statusText.value = if (result) {
|
||||
context.getString(R.string.lossless_scaling_installed)
|
||||
} else {
|
||||
context.getString(R.string.lossless_scaling_not_installed)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun install(source: Uri): Int {
|
||||
val destination = File(NativeLibrary.getLosslessDllPath())
|
||||
destination.parentFile?.mkdirs()
|
||||
|
||||
val copied = FileUtil.copyUriToInternalStorage(
|
||||
source,
|
||||
destination.parent!!,
|
||||
destination.name
|
||||
)
|
||||
if (copied == null) {
|
||||
refreshStatus()
|
||||
return RESULT_NOT_INSTALLED
|
||||
}
|
||||
|
||||
val result = NativeLibrary.prepareLosslessDll()
|
||||
if (result != RESULT_OK) {
|
||||
NativeLibrary.removeLosslessDll()
|
||||
}
|
||||
refreshStatus()
|
||||
return result
|
||||
}
|
||||
|
||||
fun remove(): Boolean {
|
||||
val removed = NativeLibrary.removeLosslessDll()
|
||||
refreshStatus()
|
||||
return removed
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ void AndroidConfig::SaveAllValues() {
|
||||
}
|
||||
|
||||
void AndroidConfig::ReadAndroidValues() {
|
||||
ReadAndroidUIValues();
|
||||
if (global) {
|
||||
ReadAndroidUIValues();
|
||||
ReadUIValues();
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
|
||||
Settings::values.ext_content_from_game_dirs = ReadBooleanSetting(
|
||||
@@ -223,8 +223,8 @@ void AndroidConfig::ReadAndroidControlValues() {
|
||||
}
|
||||
|
||||
void AndroidConfig::SaveAndroidValues() {
|
||||
SaveAndroidUIValues();
|
||||
if (global) {
|
||||
SaveAndroidUIValues();
|
||||
SaveUIValues();
|
||||
SaveOverlayValues();
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace AndroidSettings {
|
||||
&show_performance_overlay};
|
||||
|
||||
|
||||
Settings::Setting<s32> pipeline_worker_count{linkage, 4, "pipeline_worker_count",
|
||||
Settings::SwitchableSetting<s32> pipeline_worker_count{linkage, 2, "pipeline_worker_count",
|
||||
Settings::Category::Android,
|
||||
Settings::Specialization::Default,
|
||||
true,
|
||||
|
||||
@@ -124,9 +124,18 @@ float EmuWindow_Android::GetFrameTimeVerifiedHint() const {
|
||||
return QuantizeFrameRateHint(verified_rate);
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetPresentedFrameMultiplier() {
|
||||
if (!Settings::values.frame_gen.GetValue()) {
|
||||
return 1.0f;
|
||||
}
|
||||
return static_cast<float>(std::clamp<u32>(Settings::values.frame_gen_multiplier.GetValue(), 2, 4));
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetFrameRateHint() const {
|
||||
const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f);
|
||||
const float frame_time_verified_hint = GetFrameTimeVerifiedHint();
|
||||
const float presented_multiplier = GetPresentedFrameMultiplier();
|
||||
const float observed_rate =
|
||||
std::clamp(m_smoothed_present_rate * presented_multiplier, 0.0f, 240.0f);
|
||||
const float frame_time_verified_hint = GetFrameTimeVerifiedHint() * presented_multiplier;
|
||||
|
||||
if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) {
|
||||
const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f);
|
||||
@@ -150,9 +159,9 @@ float EmuWindow_Android::GetFrameRateHint() const {
|
||||
return frame_time_verified_hint;
|
||||
}
|
||||
|
||||
constexpr float NominalFrameRate = 60.0f;
|
||||
const float nominal_rate = 60.0f * presented_multiplier;
|
||||
if (!Settings::values.use_speed_limit.GetValue()) {
|
||||
return NominalFrameRate;
|
||||
return QuantizeFrameRateHint(nominal_rate);
|
||||
}
|
||||
|
||||
const u16 speed_limit = Settings::SpeedLimit();
|
||||
@@ -161,7 +170,7 @@ float EmuWindow_Android::GetFrameRateHint() const {
|
||||
}
|
||||
|
||||
const float speed_limited_rate =
|
||||
NominalFrameRate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
|
||||
nominal_rate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
|
||||
return QuantizeFrameRateHint(speed_limited_rate);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ private:
|
||||
void UpdateObservedFrameRate();
|
||||
[[nodiscard]] float GetFrameRateHint() const;
|
||||
[[nodiscard]] float GetFrameTimeVerifiedHint() const;
|
||||
[[nodiscard]] static float GetPresentedFrameMultiplier();
|
||||
[[nodiscard]] static float QuantizeFrameRateHint(float frame_rate);
|
||||
|
||||
float m_window_width{};
|
||||
|
||||
@@ -44,6 +44,7 @@ extern "C" {
|
||||
#include "common/android/android_common.h"
|
||||
#include "common/android/id_cache.h"
|
||||
#include "common/dynamic_library.h"
|
||||
#include "common/fs/fs_util.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scm_rev.h"
|
||||
@@ -90,6 +91,7 @@ extern "C" {
|
||||
#include "hid_core/hid_types.h"
|
||||
#include "input_common/drivers/virtual_amiibo.h"
|
||||
#include "jni/native.h"
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/capture.h"
|
||||
@@ -1090,6 +1092,34 @@ VkPhysicalDeviceProperties GetVulkanDeviceProperties() {
|
||||
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
|
||||
return physical_device.GetProperties();
|
||||
}
|
||||
|
||||
bool GetVulkanMemoryModelSupport() {
|
||||
Common::DynamicLibrary library;
|
||||
if (!library.Open("libvulkan.so")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Vulkan::vk::InstanceDispatch dld;
|
||||
const auto instance = Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_1);
|
||||
const auto physical_devices = instance.EnumeratePhysicalDevices();
|
||||
if (physical_devices.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
|
||||
|
||||
VkPhysicalDeviceVulkanMemoryModelFeatures memory_model{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES,
|
||||
.pNext = nullptr,
|
||||
};
|
||||
VkPhysicalDeviceFeatures2 features{
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||
.pNext = &memory_model,
|
||||
};
|
||||
physical_device.GetFeatures2(features);
|
||||
|
||||
return memory_model.vulkanMemoryModel == VK_TRUE;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanDriverVersion(JNIEnv* env, jobject jobj) {
|
||||
@@ -1165,6 +1195,14 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanApiVersion(JNIEnv* env, j
|
||||
}
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_supportsFrameGeneration(JNIEnv* env, jobject jobj) {
|
||||
try {
|
||||
return static_cast<jboolean>(GetVulkanMemoryModelSupport());
|
||||
} catch (...) {
|
||||
return static_cast<jboolean>(false);
|
||||
}
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuModel(JNIEnv* env, jobject jobj) {
|
||||
const auto props = GetVulkanDeviceProperties();
|
||||
if (props.deviceID == 0) {
|
||||
@@ -1390,6 +1428,39 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_installKeys(JNIEnv* env, jclass clazz
|
||||
return static_cast<int>(FirmwareManager::InstallKeys(path, ext));
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getLosslessDllPath(JNIEnv* env, jclass clazz) {
|
||||
#ifdef HAS_LSFG
|
||||
const auto path = VideoCore::FrameGen::GetLosslessDllPath();
|
||||
return Common::Android::ToJString(env, Common::FS::PathToUTF8String(path));
|
||||
#else
|
||||
return Common::Android::ToJString(env, "");
|
||||
#endif
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_validateLosslessDll(JNIEnv* env, jclass clazz) {
|
||||
#ifdef HAS_LSFG
|
||||
return static_cast<jint>(VideoCore::FrameGen::GetInstalledLosslessStatus());
|
||||
#else
|
||||
return static_cast<jint>(VideoCore::FrameGen::LosslessStatus::NotInstalled);
|
||||
#endif
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_prepareLosslessDll(JNIEnv* env, jclass clazz) {
|
||||
#ifdef HAS_LSFG
|
||||
return static_cast<jint>(VideoCore::FrameGen::BuildShaderCache());
|
||||
#else
|
||||
return static_cast<jint>(VideoCore::FrameGen::LosslessStatus::NotInstalled);
|
||||
#endif
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_removeLosslessDll(JNIEnv* env, jclass clazz) {
|
||||
#ifdef HAS_LSFG
|
||||
return static_cast<jboolean>(VideoCore::FrameGen::RemoveInstalledLosslessDll());
|
||||
#else
|
||||
return static_cast<jboolean>(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
|
||||
jstring jpath,
|
||||
jstring jprogramId) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M11,10.6a8,5.2 0 1,0 0,10.4a8,5.2 0 1,0 0,-10.4z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M4.6,12.6L0.8,10.2L3.2,15.5z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M15.3,3.5a4,4 0 1,0 0,8a4,4 0 1,0 0,-8zM16.6,4.9a1,1 0 1,0 0,2a1,1 0 1,0 0,-2z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M18.6,6.5L23.2,7.8L18.6,9.3z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_lossless"
|
||||
style="@style/Widget.Eden.TransparentTopAppBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_lossless"
|
||||
style="@style/Widget.Eden.TransparentTopToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
app:title="@string/lossless_scaling" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scroll_view_lossless"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:clipToPadding="false"
|
||||
android:defaultFocusHighlightEnabled="false"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar_lossless">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="16dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/lossless_options_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -47,6 +47,12 @@
|
||||
android:defaultValue="@null" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/losslessManagerFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.LosslessManagerFragment"
|
||||
android:label="@string/lossless_scaling"
|
||||
tools:layout="@layout/fragment_lossless_manager" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/appletLauncherFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment"
|
||||
|
||||
@@ -162,6 +162,48 @@
|
||||
<item>@string/resolution_four</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="frameGenMultiplierNames">
|
||||
<item>@string/frame_gen_multiplier_2x</item>
|
||||
<item>@string/frame_gen_multiplier_3x</item>
|
||||
<item>@string/frame_gen_multiplier_4x</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenMultiplierValues">
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="frameGenTargetRateNames">
|
||||
<item>@string/frame_gen_target_rate_off</item>
|
||||
<item>@string/frame_gen_target_rate_60</item>
|
||||
<item>@string/frame_gen_target_rate_90</item>
|
||||
<item>@string/frame_gen_target_rate_120</item>
|
||||
<item>@string/frame_gen_target_rate_144</item>
|
||||
<item>@string/frame_gen_target_rate_165</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenTargetRateValues">
|
||||
<item>0</item>
|
||||
<item>60</item>
|
||||
<item>90</item>
|
||||
<item>120</item>
|
||||
<item>144</item>
|
||||
<item>165</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="frameGenQueueTargetNames">
|
||||
<item>@string/frame_gen_queue_target_0</item>
|
||||
<item>@string/frame_gen_queue_target_1</item>
|
||||
<item>@string/frame_gen_queue_target_2</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="frameGenQueueTargetValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="rendererVSyncNames">
|
||||
<item>@string/renderer_vsync_immediate</item>
|
||||
<item>@string/renderer_vsync_mailbox</item>
|
||||
@@ -638,6 +680,16 @@
|
||||
<item>@string/error_keys_failed_init</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="losslessDllResults">
|
||||
<item>""</item>
|
||||
<item>@string/error_lossless_copy_failed</item>
|
||||
<item>@string/error_lossless_unreadable</item>
|
||||
<item>@string/error_lossless_not_pe</item>
|
||||
<item>@string/error_lossless_missing_shaders</item>
|
||||
<item>@string/error_lossless_translation_failed</item>
|
||||
<item>@string/error_lossless_cache_failed</item>
|
||||
</string-array>
|
||||
|
||||
<!-- GPU Logging Arrays -->
|
||||
<string-array name="gpuLogLevelEntries">
|
||||
<item>Off</item>
|
||||
|
||||
@@ -298,6 +298,67 @@
|
||||
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
||||
<string name="gpu_driver_manager">GPU driver manager</string>
|
||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||
<string name="frame_gen">Frame generation</string>
|
||||
<string name="frame_gen_per_game_description">Configure frame generation for this game</string>
|
||||
<string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
|
||||
<string name="frame_gen_multiplier">Frame multiplier</string>
|
||||
<string name="frame_gen_multiplier_description">How many frames to display for each rendered frame. Higher values cost proportionally more GPU time. Asking for more than your display can present will slow emulation down.</string>
|
||||
<string name="frame_gen_multiplier_2x">2x</string>
|
||||
<string name="frame_gen_multiplier_3x">3x</string>
|
||||
<string name="frame_gen_multiplier_4x">4x</string>
|
||||
<string name="frame_gen_target_rate">Target frame rate</string>
|
||||
<string name="frame_gen_target_rate_description">Pick the rate your display can actually show. The multiplier then rises or falls on its own to hold it, and rolls back any step that makes the game itself run slower.</string>
|
||||
<string name="frame_gen_target_rate_off">Use a fixed multiplier</string>
|
||||
<string name="frame_gen_target_rate_60">60 FPS</string>
|
||||
<string name="frame_gen_target_rate_90">90 FPS</string>
|
||||
<string name="frame_gen_target_rate_120">120 FPS</string>
|
||||
<string name="frame_gen_target_rate_144">144 FPS</string>
|
||||
<string name="frame_gen_target_rate_165">165 FPS</string>
|
||||
<string name="frame_gen_queue_target">Frame queue target</string>
|
||||
<string name="frame_gen_queue_target_description">How many finished frames may wait ahead of the display. Larger queues absorb GPU spikes at the cost of input latency.</string>
|
||||
<string name="frame_gen_queue_target_0">Lowest latency (Unbuffered)</string>
|
||||
<string name="frame_gen_queue_target_1">Balanced (1 frame)</string>
|
||||
<string name="frame_gen_queue_target_2">Smoothest (2 frames)</string>
|
||||
<string name="frame_gen_flow_scale_auto">Match motion estimation to the game</string>
|
||||
<string name="frame_gen_flow_scale_auto_description">Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail.</string>
|
||||
<string name="frame_gen_flow_scale">Motion estimation resolution</string>
|
||||
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.</string>
|
||||
<string name="frame_gen_fp16">Half precision shaders</string>
|
||||
<string name="frame_gen_fp16_description">Use the 16-bit shader variant. Falls back automatically if the driver or the file lacks it.</string>
|
||||
<string name="frame_gen_dump_flow">Dump generated frame</string>
|
||||
<string name="frame_gen_dump_flow_description">Write the optical flow mip levels and the interpolated frame to the lossless/debug folder once, for troubleshooting</string>
|
||||
<string name="frame_gen_unsupported">Frame generation unavailable</string>
|
||||
<string name="frame_gen_unsupported_description">This GPU driver does not support the Vulkan memory model, which the Lossless Scaling shaders require.</string>
|
||||
<string name="lossless_scaling_setup_description">Optional. Provide your own Lossless.dll to enable frame generation later</string>
|
||||
<string name="lossless_scaling_install">Install Lossless.dll</string>
|
||||
<string name="lossless_scaling_install_description">Frame generation needs your own legal copy of Lossless.dll from Lossless Scaling</string>
|
||||
<string name="lossless_scaling_replace_description">Select a different copy of Lossless.dll</string>
|
||||
<string name="frame_generation_support">Frame generation</string>
|
||||
<string name="frame_generation_supported">Supported</string>
|
||||
<string name="frame_generation_unsupported">Unsupported (no Vulkan memory model)</string>
|
||||
<string name="lossless_scaling">Lossless Scaling</string>
|
||||
<string name="lossless_scaling_description">Provide your own copy of Lossless.dll to enable frame generation</string>
|
||||
<string name="lossless_scaling_installed">Installed</string>
|
||||
<string name="lossless_scaling_not_installed">Not installed</string>
|
||||
<string name="lossless_scaling_replace">Replace</string>
|
||||
<string name="lossless_scaling_remove">Remove</string>
|
||||
<string name="lossless_scaling_remove_description">Delete the installed Lossless.dll and its prepared shaders</string>
|
||||
<string name="lossless_scaling_remove_confirmation">Frame generation will stop working until you install Lossless.dll again. Your original file is not affected.</string>
|
||||
<string name="lossless_scaling_missing">Lossless.dll not installed</string>
|
||||
<string name="lossless_scaling_missing_description">Install it from Settings › Lossless Scaling to use frame generation.</string>
|
||||
<string name="lossless_scaling_locked">Close the game first</string>
|
||||
<string name="lossless_scaling_locked_description">Lossless.dll cannot be changed while a game is running.</string>
|
||||
<string name="lossless_scaling_remove_unavailable">Nothing to remove</string>
|
||||
<string name="lossless_scaling_remove_unavailable_description">Lossless.dll is not installed yet.</string>
|
||||
<string name="lossless_scaling_installing">Preparing frame generation shaders…</string>
|
||||
<string name="lossless_scaling_install_success">Lossless.dll installed successfully</string>
|
||||
<string name="lossless_scaling_install_failed">Could not install Lossless.dll</string>
|
||||
<string name="error_lossless_copy_failed">The selected file could not be copied.</string>
|
||||
<string name="error_lossless_unreadable">The selected file could not be read.</string>
|
||||
<string name="error_lossless_not_pe">The selected file is not a Windows library. Select Lossless.dll from your Lossless Scaling installation.</string>
|
||||
<string name="error_lossless_missing_shaders">This copy of Lossless.dll does not contain the frame generation shaders. Update Lossless Scaling and try again.</string>
|
||||
<string name="error_lossless_translation_failed">The frame generation shaders could not be translated. This version of Lossless Scaling is not supported yet.</string>
|
||||
<string name="error_lossless_cache_failed">The translated shaders could not be written to storage. Check that there is free space available.</string>
|
||||
<string name="advanced_settings">Advanced settings</string>
|
||||
<string name="settings_description">Configure emulator settings</string>
|
||||
<string name="search_recently_played">Recently played</string>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -20,6 +20,7 @@
|
||||
#define KEYS_DIR "keys"
|
||||
#define LOAD_DIR "load"
|
||||
#define LOG_DIR "log"
|
||||
#define LOSSLESS_DIR "lossless"
|
||||
#define NAND_DIR "nand"
|
||||
#define PLAY_TIME_DIR "play_time"
|
||||
#define SCREENSHOTS_DIR "screenshots"
|
||||
@@ -37,3 +38,5 @@
|
||||
|
||||
// yuzu-specific files
|
||||
#define LOG_FILE "eden_log.txt"
|
||||
#define LOSSLESS_DLL_FILE "Lossless.dll"
|
||||
#define LOSSLESS_CACHE_FILE "lsfg_spirv.cache"
|
||||
|
||||
@@ -157,6 +157,7 @@ public:
|
||||
GenerateEdenPath(EdenPath::KeysDir, eden_path / KEYS_DIR);
|
||||
GenerateEdenPath(EdenPath::LoadDir, eden_path / LOAD_DIR);
|
||||
GenerateEdenPath(EdenPath::LogDir, eden_path / LOG_DIR);
|
||||
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
||||
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
||||
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
||||
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -23,6 +23,7 @@ enum class EdenPath {
|
||||
KeysDir, // Where key files are stored.
|
||||
LoadDir, // Where cheat/mod files are stored.
|
||||
LogDir, // Where log files are stored.
|
||||
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
||||
NANDDir, // Where the emulated NAND is stored.
|
||||
PlayTimeDir, // Where play time data is stored.
|
||||
SaveDir, // Where save data is stored.
|
||||
|
||||
@@ -380,6 +380,28 @@ void UpdateRescalingInfo() {
|
||||
TranslateResolutionInfo(setup, info);
|
||||
}
|
||||
|
||||
u32 FrameGenMultiplier() {
|
||||
return std::clamp(values.frame_gen_multiplier.GetValue(), MIN_FRAME_GEN_MULTIPLIER,
|
||||
MAX_FRAME_GEN_MULTIPLIER);
|
||||
}
|
||||
|
||||
size_t FrameGenGenerations() {
|
||||
if (!values.frame_gen.GetValue()) {
|
||||
return 0;
|
||||
}
|
||||
return FrameGenMultiplier() - 1;
|
||||
}
|
||||
|
||||
size_t FrameGenMaxGenerations() {
|
||||
if (!values.frame_gen.GetValue()) {
|
||||
return 0;
|
||||
}
|
||||
if (values.frame_gen_target_rate.GetValue() != 0) {
|
||||
return MAX_FRAME_GEN_MULTIPLIER - 1;
|
||||
}
|
||||
return FrameGenMultiplier() - 1;
|
||||
}
|
||||
|
||||
void RestoreGlobalState(bool is_powered_on) {
|
||||
// If a game is running, DO NOT restore the global settings state
|
||||
if (is_powered_on) {
|
||||
|
||||
+76
-8
@@ -352,7 +352,7 @@ struct Values {
|
||||
true};
|
||||
|
||||
SwitchableSetting<ScalingFilter> scaling_filter{linkage,
|
||||
ScalingFilter::Bilinear,
|
||||
ScalingFilter::NearestNeighbor,
|
||||
"scaling_filter",
|
||||
Category::Renderer,
|
||||
Specialization::Default,
|
||||
@@ -388,6 +388,69 @@ struct Values {
|
||||
true,
|
||||
true};
|
||||
|
||||
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
||||
Specialization::Default, true, false};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_multiplier{linkage,
|
||||
2,
|
||||
2,
|
||||
4,
|
||||
"frame_gen_multiplier",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_target_rate{linkage,
|
||||
0,
|
||||
0,
|
||||
240,
|
||||
"frame_gen_target_rate",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
true,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<bool> frame_gen_flow_scale_auto{linkage,
|
||||
true,
|
||||
"frame_gen_flow_scale_auto",
|
||||
Category::Renderer,
|
||||
Specialization::Default,
|
||||
true,
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_flow_scale{linkage,
|
||||
75,
|
||||
25,
|
||||
100,
|
||||
"frame_gen_flow_scale",
|
||||
Category::Renderer,
|
||||
Specialization::Countable |
|
||||
Specialization::Percentage,
|
||||
true,
|
||||
true,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<u32, true> frame_gen_queue_target{linkage,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
"frame_gen_queue_target",
|
||||
Category::Renderer,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
false,
|
||||
&frame_gen};
|
||||
|
||||
SwitchableSetting<bool> frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer,
|
||||
Specialization::Default, true, false, &frame_gen};
|
||||
|
||||
SwitchableSetting<bool> frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow",
|
||||
Category::Renderer};
|
||||
|
||||
SwitchableSetting<bool> use_asynchronous_gpu_emulation{linkage,
|
||||
#ifdef __ANDROID__
|
||||
false,
|
||||
@@ -569,13 +632,8 @@ struct Values {
|
||||
SwitchableSetting<bool> emulate_bgr565{linkage, false, "emulate_bgr565",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> rescale_hack{linkage,
|
||||
#ifdef __ANDROID__
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
#endif
|
||||
"rescale_hack", Category::RendererHacks};
|
||||
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
|
||||
Category::RendererHacks};
|
||||
SwitchableSetting<bool> enable_gpu_buffer_readback{linkage,
|
||||
false,
|
||||
"enable_gpu_buffer_readback",
|
||||
@@ -875,10 +933,20 @@ struct Values {
|
||||
|
||||
// Per-game overrides
|
||||
bool use_squashed_iterated_blend;
|
||||
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
constexpr u32 MIN_FRAME_GEN_MULTIPLIER = 2;
|
||||
constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
|
||||
|
||||
[[nodiscard]] u32 FrameGenMultiplier();
|
||||
|
||||
[[nodiscard]] size_t FrameGenGenerations();
|
||||
|
||||
[[nodiscard]] size_t FrameGenMaxGenerations();
|
||||
|
||||
bool getDebugKnobAt(u8 i);
|
||||
|
||||
void UpdateGPUAccuracy();
|
||||
|
||||
+10
-2
@@ -388,6 +388,14 @@ add_library(core STATIC
|
||||
hle/result.h
|
||||
hle/service/acc/acc.cpp
|
||||
hle/service/acc/acc.h
|
||||
hle/service/acc/acc_aa.cpp
|
||||
hle/service/acc/acc_aa.h
|
||||
hle/service/acc/acc_su.cpp
|
||||
hle/service/acc/acc_su.h
|
||||
hle/service/acc/acc_u0.cpp
|
||||
hle/service/acc/acc_u0.h
|
||||
hle/service/acc/acc_u1.cpp
|
||||
hle/service/acc/acc_u1.h
|
||||
hle/service/acc/async_context.cpp
|
||||
hle/service/acc/async_context.h
|
||||
hle/service/acc/errors.h
|
||||
@@ -760,8 +768,8 @@ add_library(core STATIC
|
||||
hle/service/mii/types/ver3_store_data.h
|
||||
hle/service/mm/mm_u.cpp
|
||||
hle/service/mm/mm_u.h
|
||||
hle/service/mnpp/mnpp.cpp
|
||||
hle/service/mnpp/mnpp.h
|
||||
hle/service/mnpp/mnpp_app.cpp
|
||||
hle/service/mnpp/mnpp_app.h
|
||||
hle/service/ncm/ncm.cpp
|
||||
hle/service/ncm/ncm.h
|
||||
hle/service/nfc/common/amiibo_crypto.cpp
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
#include "core/hle/service/acc/acc_aa.h"
|
||||
#include "core/hle/service/acc/acc_su.h"
|
||||
#include "core/hle/service/acc/acc_u0.h"
|
||||
#include "core/hle/service/acc/acc_u1.h"
|
||||
#include "core/hle/service/acc/async_context.h"
|
||||
#include "core/hle/service/acc/errors.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
@@ -1240,416 +1244,19 @@ Module::Interface::Interface(std::shared_ptr<Module> module_,
|
||||
|
||||
Module::Interface::~Interface() = default;
|
||||
|
||||
class ACC_AA final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_AA(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:aa") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "EnsureCacheAsync"},
|
||||
{1, nullptr, "LoadCache"},
|
||||
{2, nullptr, "GetDeviceAccountId"},
|
||||
{50, nullptr, "RegisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
|
||||
{51, nullptr, "UnregisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
~ACC_AA() override = default;
|
||||
};
|
||||
|
||||
class ACC_SU final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:su") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ACC_SU::GetUserCount, "GetUserCount"},
|
||||
{1, &ACC_SU::GetUserExistence, "GetUserExistence"},
|
||||
{2, &ACC_SU::ListAllUsers, "ListAllUsers"},
|
||||
{3, &ACC_SU::ListOpenUsers, "ListOpenUsers"},
|
||||
{4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"},
|
||||
{5, &ACC_SU::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, &ACC_SU::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"},
|
||||
{52, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, // 19.0.0+
|
||||
{60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"},
|
||||
{99, nullptr, "DebugActivateOpenContextRetention"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
{101, nullptr, "GetUserStateChangeNotifier"},
|
||||
{102, &ACC_SU::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"},
|
||||
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
|
||||
{104, nullptr, "GetProfileUpdateNotifier"},
|
||||
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
|
||||
{106, nullptr, "GetProfileSyncNotifier"},
|
||||
{110, &ACC_SU::StoreSaveDataThumbnailSystem, "StoreSaveDataThumbnail"},
|
||||
{111, nullptr, "ClearSaveDataThumbnail"},
|
||||
{112, nullptr, "LoadSaveDataThumbnail"},
|
||||
{113, nullptr, "GetSaveDataThumbnailExistence"},
|
||||
{120, nullptr, "ListOpenUsersInApplication"},
|
||||
{130, nullptr, "ActivateOpenContextRetention"},
|
||||
{140, &ACC_SU::ListQualifiedUsers, "ListQualifiedUsers"},
|
||||
{150, nullptr, "AuthenticateApplicationAsync"},
|
||||
{151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"},
|
||||
{152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
|
||||
{190, nullptr, "GetUserLastOpenedApplication"},
|
||||
{191, nullptr, "ActivateOpenContextHolder"},
|
||||
{200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"},
|
||||
{201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"},
|
||||
{202, nullptr, "CancelUserRegistration"},
|
||||
{203, &ACC_SU::DeleteUser, "DeleteUser"},
|
||||
{204, &ACC_SU::SetUserPosition, "SetUserPosition"},
|
||||
{205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
|
||||
{206, nullptr, "CompleteUserRegistrationForcibly"},
|
||||
{210, nullptr, "CreateFloatingRegistrationRequest"},
|
||||
{211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"},
|
||||
{212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"},
|
||||
{230, nullptr, "AuthenticateServiceAsync"},
|
||||
{250, &ACC_SU::GetBaasAccountAdministrator, "GetBaasAccountAdministrator"},
|
||||
{290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"},
|
||||
{291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"},
|
||||
{299, nullptr, "SuspendBackgroundDaemon"},
|
||||
{400, nullptr, "SetPinCode"}, // 18.0.0+
|
||||
{401, &ACC_SU::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+
|
||||
{402, nullptr, "GetPinCode"}, // 18.0.0+
|
||||
{403, nullptr, "GetPinCodeParity"},
|
||||
{404, nullptr, "VerifyPinCode"},
|
||||
{405, nullptr, "IsPinCodeVerificationForbidden"},
|
||||
{410, nullptr, "GetPinCodeErrorCount"}, // 18.0.0+
|
||||
{411, nullptr, "ResetPinCodeErrorCount"}, // 18.0.0+
|
||||
{412, nullptr, "IncrementPinCodeErrorCount"}, // 18.0.0+
|
||||
{900, nullptr, "SetUserUnqualifiedForDebug"},
|
||||
{901, nullptr, "UnsetUserUnqualifiedForDebug"},
|
||||
{902, nullptr, "ListUsersUnqualifiedForDebug"},
|
||||
{910, nullptr, "RefreshFirmwareSettingsForDebug"},
|
||||
{997, nullptr, "DebugInvalidateTokenCacheForUser"},
|
||||
{998, nullptr, "DebugSetUserStateClose"},
|
||||
{999, nullptr, "DebugSetUserStateOpen"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
~ACC_SU() override = default;
|
||||
};
|
||||
|
||||
class ACC_U0 final : public Module::Interface {
|
||||
public:
|
||||
ACC_U0(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:u0") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ACC_U0::GetUserCount, "GetUserCount"},
|
||||
{1, &ACC_U0::GetUserExistence, "GetUserExistence"},
|
||||
{2, &ACC_U0::ListAllUsers, "ListAllUsers"},
|
||||
{3, &ACC_U0::ListOpenUsers, "ListOpenUsers"},
|
||||
{4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"},
|
||||
{5, &ACC_U0::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"}, // 3.0.0+
|
||||
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, &ACC_U0::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"},
|
||||
{52, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
|
||||
{60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
|
||||
{99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
|
||||
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
|
||||
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
|
||||
{102, nullptr, "AuthenticateApplicationAsync"},
|
||||
{103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
|
||||
{110, &ACC_U0::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"},
|
||||
{111, nullptr, "ClearSaveDataThumbnail"},
|
||||
{120, nullptr, "CreateGuestLoginRequest"},
|
||||
{130, nullptr, "LoadOpenContext"}, // 5.0.0+
|
||||
{131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
|
||||
{140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
|
||||
{141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
|
||||
{150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+
|
||||
{160, &ACC_U0::InitializeApplicationInfoV2, "InitializeApplicationInfoV2"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
~ACC_U0() override = default;
|
||||
};
|
||||
|
||||
class ACC_U1 final : public Module::Interface {
|
||||
public:
|
||||
ACC_U1(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:u1") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ACC_U1::GetUserCount, "GetUserCount"},
|
||||
{1, &ACC_U1::GetUserExistence, "GetUserExistence"},
|
||||
{2, &ACC_U1::ListAllUsers, "ListAllUsers"},
|
||||
{3, &ACC_U1::ListOpenUsers, "ListOpenUsers"},
|
||||
{4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"},
|
||||
{5, &ACC_U1::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
|
||||
{60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"},
|
||||
{99, nullptr, "DebugActivateOpenContextRetention"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
{101, nullptr, "GetUserStateChangeNotifier"},
|
||||
{102, &ACC_U1::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"},
|
||||
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
|
||||
{104, nullptr, "GetProfileUpdateNotifier"},
|
||||
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
|
||||
{106, nullptr, "GetProfileSyncNotifier"},
|
||||
{110, &ACC_U1::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"},
|
||||
{111, nullptr, "ClearSaveDataThumbnail"},
|
||||
{112, nullptr, "LoadSaveDataThumbnail"},
|
||||
{113, nullptr, "GetSaveDataThumbnailExistence"},
|
||||
{120, nullptr, "ListOpenUsersInApplication"},
|
||||
{130, nullptr, "ActivateOpenContextRetention"},
|
||||
{140, &ACC_U1::ListQualifiedUsers, "ListQualifiedUsers"},
|
||||
{150, nullptr, "AuthenticateApplicationAsync"},
|
||||
{151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"},
|
||||
{152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
|
||||
{190, nullptr, "GetUserLastOpenedApplication"},
|
||||
{191, nullptr, "ActivateOpenContextHolder"},
|
||||
{401, &ACC_U1::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+
|
||||
{402, nullptr, "GetPinCode"}, // 18.0.0+
|
||||
{997, nullptr, "DebugInvalidateTokenCacheForUser"},
|
||||
{998, nullptr, "DebugSetUserStateClose"},
|
||||
{999, nullptr, "DebugSetUserStateOpen"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
~ACC_U1() override = default;
|
||||
};
|
||||
|
||||
class DAUTH_0 final : public ServiceFramework<DAUTH_0> {
|
||||
public:
|
||||
explicit DAUTH_0(Core::System& system_) : ServiceFramework{system_, "dauth:0"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "EnsureAuthenticationTokenCacheAsync"},
|
||||
{1, nullptr, "LoadAuthenticationTokenCache"},
|
||||
{2, nullptr, "InvalidateAuthenticationTokenCache"},
|
||||
{3, nullptr, "IsDeviceAuthenticationTokenCacheAvailable"},
|
||||
{10, nullptr, "EnsureEdgeTokenCacheAsync"},
|
||||
{11, nullptr, "LoadEdgeTokenCache"},
|
||||
{12, nullptr, "InvalidateEdgeTokenCache"},
|
||||
{13, nullptr, "IsEdgeTokenCacheAvailable"},
|
||||
{20, nullptr, "EnsureApplicationAuthenticationCacheAsync"},
|
||||
{21, nullptr, "LoadApplicationAuthenticationTokenCache"},
|
||||
{22, nullptr, "LoadApplicationNetworkServiceClientConfigCache"},
|
||||
{23, nullptr, "IsApplicationAuthenticationCacheAvailable"},
|
||||
{24, nullptr, "InvalidateApplicationAuthenticationCache"},
|
||||
{30, nullptr, "EnsureGameCardAuthenticationCacheAsync"},
|
||||
{31, nullptr, "LoadGameCardAuthenticationTokenCache"},
|
||||
{32, nullptr, "IsGameCardAuthenticationCacheAvailable"},
|
||||
{33, nullptr, "InvalidateGameCardAuthenticationCache"},
|
||||
{1000, nullptr, "GetInactiveElicenseUsedEvent"},
|
||||
{9000, nullptr, "ImportVirtualClientCertificate"},
|
||||
{9010, nullptr, "DeleteVirtualClientCertificate"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class ACC_E final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_E(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:e") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{ 0, nullptr, "GetUserCount"},
|
||||
{ 1, nullptr, "GetUserExistence"},
|
||||
{ 2, nullptr, "ListAllUsers"},
|
||||
{ 3, nullptr, "ListOpenUsers"},
|
||||
{ 4, nullptr, "GetLastOpenedUser"},
|
||||
{ 5, nullptr, "GetProfile"},
|
||||
{ 6, nullptr, "GetProfileDigest"},
|
||||
{ 50, nullptr, "IsUserRegistrationRequestPermitted"},
|
||||
{ 51, nullptr, "TrySelectUserWithoutInteractionDeprecated"},
|
||||
{ 52, nullptr, "TrySelectUserWithoutInteraction"},
|
||||
{ 99, nullptr, "DebugActivateOpenContextRetention"},
|
||||
{ 100, nullptr, "GetUserRegistrationNotifier"},
|
||||
{ 101, nullptr, "GetUserStateChangeNotifier"},
|
||||
{ 102, nullptr, "GetBaasAccountManagerForSystemService"},
|
||||
{ 103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
|
||||
{ 104, nullptr, "GetProfileUpdateNotifier"},
|
||||
{ 105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
|
||||
{ 106, nullptr, "GetProfileSyncNotifier"},
|
||||
{ 110, nullptr, "StoreSaveDataThumbnail"},
|
||||
{ 111, nullptr, "ClearSaveDataThumbnail"},
|
||||
{ 112, nullptr, "LoadSaveDataThumbnail"},
|
||||
{ 113, nullptr, "GetSaveDataThumbnailExistence"},
|
||||
{ 120, nullptr, "ListOpenUsersInApplication"},
|
||||
{ 130, nullptr, "ActivateOpenContextRetention"},
|
||||
{ 140, nullptr, "ListQualifiedUsers"},
|
||||
{ 151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"},
|
||||
{ 152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
|
||||
{ 170, nullptr, "GetNasOp2MembershipStateChangeNotifier"},
|
||||
{ 191, nullptr, "UpdateNotificationReceiverInfo"},
|
||||
{ 200, nullptr, "BeginUserRegistration"},
|
||||
{ 201, nullptr, "CompleteUserRegistration"},
|
||||
{ 202, nullptr, "CancelUserRegistration"},
|
||||
{ 203, nullptr, "DeleteUser"},
|
||||
{ 204, nullptr, "SetUserPosition"},
|
||||
{ 205, nullptr, "GetProfileEditor"},
|
||||
{ 206, nullptr, "CompleteUserRegistrationForcibly"},
|
||||
{ 210, nullptr, "CreateFloatingRegistrationRequest"},
|
||||
{ 211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"},
|
||||
{ 212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"},
|
||||
{ 213, nullptr, "CreateProcedureToCreateUserWithNintendoAccount"},
|
||||
{ 214, nullptr, "ResumeProcedureToCreateUserWithNintendoAccount"},
|
||||
{ 215, nullptr, "ResumeProcedureToCreateUserWithNintendoAccountAfterApplyResponse"},
|
||||
{ 230, nullptr, "AuthenticateServiceAsync"},
|
||||
{ 250, nullptr, "GetBaasAccountAdministrator"},
|
||||
{ 251, nullptr, "SynchronizeNetworkServiceAccountsSnapshotAsync"},
|
||||
{ 290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"},
|
||||
{ 291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"},
|
||||
{ 292, nullptr, "ProxyProcedureForDeviceMigrationAuthenticatingOperatingUser"},
|
||||
{ 293, nullptr, "ProxyProcedureForDeviceMigrationDownload"},
|
||||
{ 299, nullptr, "SuspendBackgroundDaemon"},
|
||||
{ 350, nullptr, "CreateDeviceMigrationUserExportRequest"},
|
||||
{ 351, nullptr, "UploadNasCredential"},
|
||||
{ 352, nullptr, "CreateDeviceMigrationUserImportRequest"},
|
||||
{ 353, nullptr, "DeleteUserMigrationSaveData"},
|
||||
{ 400, nullptr, "SetPinCode"},
|
||||
{ 401, nullptr, "GetPinCodeLength"},
|
||||
{ 402, nullptr, "GetPinCode"},
|
||||
{ 403, nullptr, "GetPinCodeParity"},
|
||||
{ 404, nullptr, "VerifyPinCode"},
|
||||
{ 405, nullptr, "IsPinCodeVerificationForbidden"},
|
||||
{ 410, nullptr, "GetPinCodeErrorCount"},
|
||||
{ 411, nullptr, "ResetPinCodeErrorCount"},
|
||||
{ 412, nullptr, "IncrementPinCodeErrorCount"},
|
||||
{ 413, nullptr, "SetPinCodeErrorCount"},
|
||||
{ 420, nullptr, "SetStartPenaltyTime"},
|
||||
{ 421, nullptr, "GetStartPenaltyTime"},
|
||||
{ 900, nullptr, "SetUserUnqualifiedForDebug"},
|
||||
{ 901, nullptr, "UnsetUserUnqualifiedForDebug"},
|
||||
{ 902, nullptr, "ListUsersUnqualifiedForDebug"},
|
||||
{ 910, nullptr, "RefreshFirmwareSettingsForDebug"},
|
||||
{ 997, nullptr, "DebugInvalidateTokenCacheForUser"},
|
||||
{ 998, nullptr, "DebugSetUserStateClose"},
|
||||
{ 999, nullptr, "DebugSetUserStateOpen"},
|
||||
{ 1000, nullptr, "CreateIAccountEntityServiceForApplication"},
|
||||
{ 1100, nullptr, "CreateIUserStateManager"},
|
||||
{ 10050, nullptr, "IsUserRegistrationRequestPermittedForAccountPolicy"},
|
||||
{ 10105, nullptr, "CheckNetworkServiceAvailabilityAsyncForAccountPolicy"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class ACC_E_U1 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_E_U1(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:e:u1") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetUserCount"},
|
||||
{1, nullptr, "GetUserExistence"},
|
||||
{2, nullptr, "ListAllUsers"},
|
||||
{3, nullptr, "ListOpenUsers"},
|
||||
{4, nullptr, "GetLastOpenedUser"},
|
||||
{5, nullptr, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, nullptr, "IsUserRegistrationRequestPermitted"},
|
||||
{51, nullptr, "TrySelectUserWithoutInteractionDeprecated"},
|
||||
{99, nullptr, "DebugActivateOpenContextRetention"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
{101, nullptr, "GetUserStateChangeNotifier"},
|
||||
{102, nullptr, "GetBaasAccountManagerForSystemService"},
|
||||
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
|
||||
{104, nullptr, "GetProfileUpdateNotifier"},
|
||||
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
|
||||
{106, nullptr, "GetProfileSyncNotifier"},
|
||||
{110, nullptr, "StoreSaveDataThumbnail"},
|
||||
{111, nullptr, "ClearSaveDataThumbnail"},
|
||||
{112, nullptr, "LoadSaveDataThumbnail"},
|
||||
{113, nullptr, "GetSaveDataThumbnailExistence"},
|
||||
{120, nullptr, "ListOpenUsersInApplication"},
|
||||
{130, nullptr, "ActivateOpenContextRetention"},
|
||||
{140, nullptr, "ListQualifiedUsers"},
|
||||
{151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"},
|
||||
{152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
|
||||
{170, nullptr, "GetNasOp2MembershipStateChangeNotifier"},
|
||||
{191, nullptr, "UpdateNotificationReceiverInfo"},
|
||||
{997, nullptr, "DebugInvalidateTokenCacheForUser"},
|
||||
{998, nullptr, "DebugSetUserStateClose"},
|
||||
{999, nullptr, "DebugSetUserStateOpen"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class ACC_E_U2 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_E_U2(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:e:u2") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetUserCount"},
|
||||
{1, nullptr, "GetUserExistence"},
|
||||
{2, nullptr, "ListAllUsers"},
|
||||
{3, nullptr, "ListOpenUsers"},
|
||||
{4, nullptr, "GetLastOpenedUser"},
|
||||
{5, nullptr, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, nullptr, "#IsUserRegistrationRequestPermitted"},
|
||||
{51, nullptr, "TrySelectUserWithoutInteractionDeprecated"},
|
||||
{52, nullptr, "TrySelectUserWithoutInteraction"},
|
||||
{99, nullptr, "DebugActivateOpenContextRetention"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
{101, nullptr, "GetUserStateChangeNotifier"},
|
||||
{102, nullptr, "GetBaasAccountManagerForSystemService"},
|
||||
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
|
||||
{104, nullptr, "GetProfileUpdateNotifier"},
|
||||
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
|
||||
{106, nullptr, "GetProfileSyncNotifier"},
|
||||
{110, nullptr, "StoreSaveDataThumbnail"},
|
||||
{111, nullptr, "ClearSaveDataThumbnail"},
|
||||
{112, nullptr, "LoadSaveDataThumbnail"},
|
||||
{113, nullptr, "GetSaveDataThumbnailExistence"},
|
||||
{120, nullptr, "ListOpenUsersInApplication"},
|
||||
{130, nullptr, "ActivateOpenContextRetention"},
|
||||
{140, nullptr, "ListQualifiedUsers"},
|
||||
{151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"},
|
||||
{152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
|
||||
{170, nullptr, "GetNasOp2MembershipStateChangeNotifier"},
|
||||
{191, nullptr, "UpdateNotificationReceiverInfo"},
|
||||
{205, nullptr, "GetProfileEditor"},
|
||||
{401, nullptr, "GetPinCodeLength"},
|
||||
{402, nullptr, "GetPinCode"},
|
||||
{403, nullptr, "GetPinCodeParity"},
|
||||
{404, nullptr, "VerifyPinCode"},
|
||||
{405, nullptr, "IsPinCodeVerificationForbidden"},
|
||||
{997, nullptr, "DebugInvalidateTokenCacheForUser"},
|
||||
{998, nullptr, "DebugSetUserStateClose"},
|
||||
{999, nullptr, "DebugSetUserStateOpen"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto module = std::make_shared<Module>();
|
||||
auto profile_manager = std::make_shared<ProfileManager>();
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("acc:aa", std::make_shared<ACC_AA>(module, profile_manager, system));
|
||||
server_manager->RegisterNamedService("acc:su", std::make_shared<ACC_SU>(module, profile_manager, system));
|
||||
server_manager->RegisterNamedService("acc:u0", std::make_shared<ACC_U0>(module, profile_manager, system));
|
||||
server_manager->RegisterNamedService("acc:u1", std::make_shared<ACC_U1>(module, profile_manager, system));
|
||||
|
||||
server_manager->RegisterNamedService("acc:e", std::make_shared<ACC_E>(module, profile_manager, system));
|
||||
server_manager->RegisterNamedService("acc:e:u1", std::make_shared<ACC_E_U1>(module, profile_manager, system));
|
||||
server_manager->RegisterNamedService("acc:e:u2", std::make_shared<ACC_E_U2>(module, profile_manager, system));
|
||||
|
||||
server_manager->RegisterNamedService("dauth:0", std::make_shared<DAUTH_0>(system));
|
||||
server_manager->RegisterNamedService("acc:aa",
|
||||
std::make_shared<ACC_AA>(module, profile_manager, system));
|
||||
server_manager->RegisterNamedService("acc:su",
|
||||
std::make_shared<ACC_SU>(module, profile_manager, system));
|
||||
server_manager->RegisterNamedService("acc:u0",
|
||||
std::make_shared<ACC_U0>(module, profile_manager, system));
|
||||
server_manager->RegisterNamedService("acc:u1",
|
||||
std::make_shared<ACC_U1>(module, profile_manager, system));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/acc/acc_aa.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_AA::ACC_AA(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_,
|
||||
Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:aa") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "EnsureCacheAsync"},
|
||||
{1, nullptr, "LoadCache"},
|
||||
{2, nullptr, "GetDeviceAccountId"},
|
||||
{50, nullptr, "RegisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
|
||||
{51, nullptr, "UnregisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ACC_AA::~ACC_AA() = default;
|
||||
|
||||
} // namespace Service::Account
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
class ACC_AA final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_AA(std::shared_ptr<Module> module_,
|
||||
std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_);
|
||||
~ACC_AA() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/acc/acc_su.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_,
|
||||
Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:su") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ACC_SU::GetUserCount, "GetUserCount"},
|
||||
{1, &ACC_SU::GetUserExistence, "GetUserExistence"},
|
||||
{2, &ACC_SU::ListAllUsers, "ListAllUsers"},
|
||||
{3, &ACC_SU::ListOpenUsers, "ListOpenUsers"},
|
||||
{4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"},
|
||||
{5, &ACC_SU::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, &ACC_SU::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"},
|
||||
{52, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, // 19.0.0+
|
||||
{60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"},
|
||||
{99, nullptr, "DebugActivateOpenContextRetention"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
{101, nullptr, "GetUserStateChangeNotifier"},
|
||||
{102, &ACC_SU::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"},
|
||||
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
|
||||
{104, nullptr, "GetProfileUpdateNotifier"},
|
||||
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
|
||||
{106, nullptr, "GetProfileSyncNotifier"},
|
||||
{110, &ACC_SU::StoreSaveDataThumbnailSystem, "StoreSaveDataThumbnail"},
|
||||
{111, nullptr, "ClearSaveDataThumbnail"},
|
||||
{112, nullptr, "LoadSaveDataThumbnail"},
|
||||
{113, nullptr, "GetSaveDataThumbnailExistence"},
|
||||
{120, nullptr, "ListOpenUsersInApplication"},
|
||||
{130, nullptr, "ActivateOpenContextRetention"},
|
||||
{140, &ACC_SU::ListQualifiedUsers, "ListQualifiedUsers"},
|
||||
{150, nullptr, "AuthenticateApplicationAsync"},
|
||||
{151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"},
|
||||
{152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
|
||||
{190, nullptr, "GetUserLastOpenedApplication"},
|
||||
{191, nullptr, "ActivateOpenContextHolder"},
|
||||
{200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"},
|
||||
{201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"},
|
||||
{202, nullptr, "CancelUserRegistration"},
|
||||
{203, &ACC_SU::DeleteUser, "DeleteUser"},
|
||||
{204, &ACC_SU::SetUserPosition, "SetUserPosition"},
|
||||
{205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
|
||||
{206, nullptr, "CompleteUserRegistrationForcibly"},
|
||||
{210, nullptr, "CreateFloatingRegistrationRequest"},
|
||||
{211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"},
|
||||
{212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"},
|
||||
{230, nullptr, "AuthenticateServiceAsync"},
|
||||
{250, &ACC_SU::GetBaasAccountAdministrator, "GetBaasAccountAdministrator"},
|
||||
{290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"},
|
||||
{291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"},
|
||||
{299, nullptr, "SuspendBackgroundDaemon"},
|
||||
{400, nullptr, "SetPinCode"}, // 18.0.0+
|
||||
{401, &ACC_SU::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+
|
||||
{402, nullptr, "GetPinCode"}, // 18.0.0+
|
||||
{410, nullptr, "GetPinCodeErrorCount"}, // 18.0.0+
|
||||
{411, nullptr, "ResetPinCodeErrorCount"}, // 18.0.0+
|
||||
{412, nullptr, "IncrementPinCodeErrorCount"}, // 18.0.0+
|
||||
{900, nullptr, "SetUserUnqualifiedForDebug"},
|
||||
{901, nullptr, "UnsetUserUnqualifiedForDebug"},
|
||||
{902, nullptr, "ListUsersUnqualifiedForDebug"},
|
||||
{910, nullptr, "RefreshFirmwareSettingsForDebug"},
|
||||
{997, nullptr, "DebugInvalidateTokenCacheForUser"},
|
||||
{998, nullptr, "DebugSetUserStateClose"},
|
||||
{999, nullptr, "DebugSetUserStateOpen"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ACC_SU::~ACC_SU() = default;
|
||||
|
||||
} // namespace Service::Account
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
class ACC_SU final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_SU(std::shared_ptr<Module> module_,
|
||||
std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_);
|
||||
~ACC_SU() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/acc/acc_u0.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_U0::ACC_U0(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_,
|
||||
Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:u0") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ACC_U0::GetUserCount, "GetUserCount"},
|
||||
{1, &ACC_U0::GetUserExistence, "GetUserExistence"},
|
||||
{2, &ACC_U0::ListAllUsers, "ListAllUsers"},
|
||||
{3, &ACC_U0::ListOpenUsers, "ListOpenUsers"},
|
||||
{4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"},
|
||||
{5, &ACC_U0::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"}, // 3.0.0+
|
||||
{50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, &ACC_U0::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"},
|
||||
{52, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
|
||||
{60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0
|
||||
{99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+
|
||||
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
|
||||
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
|
||||
{102, nullptr, "AuthenticateApplicationAsync"},
|
||||
{103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
|
||||
{110, &ACC_U0::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"},
|
||||
{111, nullptr, "ClearSaveDataThumbnail"},
|
||||
{120, nullptr, "CreateGuestLoginRequest"},
|
||||
{130, nullptr, "LoadOpenContext"}, // 5.0.0+
|
||||
{131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
|
||||
{140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
|
||||
{141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
|
||||
{150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+
|
||||
{160, &ACC_U0::InitializeApplicationInfoV2, "InitializeApplicationInfoV2"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ACC_U0::~ACC_U0() = default;
|
||||
|
||||
} // namespace Service::Account
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
class ACC_U0 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_U0(std::shared_ptr<Module> module_,
|
||||
std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_);
|
||||
~ACC_U0() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/acc/acc_u1.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
ACC_U1::ACC_U1(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager> profile_manager_,
|
||||
Core::System& system_)
|
||||
: Interface(std::move(module_), std::move(profile_manager_), system_, "acc:u1") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ACC_U1::GetUserCount, "GetUserCount"},
|
||||
{1, &ACC_U1::GetUserExistence, "GetUserExistence"},
|
||||
{2, &ACC_U1::ListAllUsers, "ListAllUsers"},
|
||||
{3, &ACC_U1::ListOpenUsers, "ListOpenUsers"},
|
||||
{4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"},
|
||||
{5, &ACC_U1::GetProfile, "GetProfile"},
|
||||
{6, nullptr, "GetProfileDigest"},
|
||||
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
|
||||
{51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
|
||||
{60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"},
|
||||
{99, nullptr, "DebugActivateOpenContextRetention"},
|
||||
{100, nullptr, "GetUserRegistrationNotifier"},
|
||||
{101, nullptr, "GetUserStateChangeNotifier"},
|
||||
{102, &ACC_U1::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"},
|
||||
{103, nullptr, "GetBaasUserAvailabilityChangeNotifier"},
|
||||
{104, nullptr, "GetProfileUpdateNotifier"},
|
||||
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"},
|
||||
{106, nullptr, "GetProfileSyncNotifier"},
|
||||
{110, &ACC_U1::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"},
|
||||
{111, nullptr, "ClearSaveDataThumbnail"},
|
||||
{112, nullptr, "LoadSaveDataThumbnail"},
|
||||
{113, nullptr, "GetSaveDataThumbnailExistence"},
|
||||
{120, nullptr, "ListOpenUsersInApplication"},
|
||||
{130, nullptr, "ActivateOpenContextRetention"},
|
||||
{140, &ACC_U1::ListQualifiedUsers, "ListQualifiedUsers"},
|
||||
{150, nullptr, "AuthenticateApplicationAsync"},
|
||||
{151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"},
|
||||
{152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"},
|
||||
{190, nullptr, "GetUserLastOpenedApplication"},
|
||||
{191, nullptr, "ActivateOpenContextHolder"},
|
||||
{401, &ACC_U1::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+
|
||||
{402, nullptr, "GetPinCode"}, // 18.0.0+
|
||||
{997, nullptr, "DebugInvalidateTokenCacheForUser"},
|
||||
{998, nullptr, "DebugSetUserStateClose"},
|
||||
{999, nullptr, "DebugSetUserStateOpen"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ACC_U1::~ACC_U1() = default;
|
||||
|
||||
} // namespace Service::Account
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
class ACC_U1 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_U1(std::shared_ptr<Module> module_,
|
||||
std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_);
|
||||
~ACC_U1() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -57,58 +54,11 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class BPC_C final : public ServiceFramework<BPC_C> {
|
||||
public:
|
||||
explicit BPC_C(Core::System& system_) : ServiceFramework{system_, "bpc:c"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "ShutdownSystem"},
|
||||
{1, nullptr, "RebootSystem"},
|
||||
{2, nullptr, "GetWakeupReason"},
|
||||
{3, nullptr, "GetShutdownReason"},
|
||||
{4, nullptr, "GetAcOk"},
|
||||
{5, nullptr, "GetPowerEvent"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class BPC_B final : public ServiceFramework<BPC_B> {
|
||||
public:
|
||||
explicit BPC_B(Core::System& system_) : ServiceFramework{system_, "bpc:b"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetSleepButtonState"},
|
||||
{1, nullptr, "GetPowerButtonEvent"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class BPC_W final : public ServiceFramework<BPC_W> {
|
||||
public:
|
||||
explicit BPC_W(Core::System& system_) : ServiceFramework{system_, "bpc:w"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "CreateWakeupTimer"},
|
||||
{1, nullptr, "CancelWakeupTimer"},
|
||||
{2, nullptr, "EnableWakeupTimerOnDevice"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("bpc", std::make_shared<BPC>(system));
|
||||
server_manager->RegisterNamedService("bpc:r", std::make_shared<BPC_R>(system));
|
||||
server_manager->RegisterNamedService("bpc:c", std::make_shared<BPC_C>(system));
|
||||
server_manager->RegisterNamedService("bpc:b", std::make_shared<BPC_B>(system));
|
||||
server_manager->RegisterNamedService("bpc:w", std::make_shared<BPC_W>(system));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,20 @@ void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
auto album_manager = std::make_shared<AlbumManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
|
||||
server_manager->RegisterNamedService("caps:c", std::make_shared<IAlbumControlService>(system, album_manager));
|
||||
server_manager->RegisterNamedService("caps:u", std::make_shared<IAlbumApplicationService>(system, album_manager));
|
||||
server_manager->RegisterNamedService("caps:ss", std::make_shared<IScreenShotService>(system, album_manager));
|
||||
server_manager->RegisterNamedService("caps:sc", std::make_shared<IScreenShotControlService>(system));
|
||||
server_manager->RegisterNamedService("caps:su", std::make_shared<IScreenShotApplicationService>(system, album_manager));
|
||||
server_manager->RegisterNamedService(
|
||||
"caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
|
||||
server_manager->RegisterNamedService(
|
||||
"caps:c", std::make_shared<IAlbumControlService>(system, album_manager));
|
||||
server_manager->RegisterNamedService(
|
||||
"caps:u", std::make_shared<IAlbumApplicationService>(system, album_manager));
|
||||
|
||||
server_manager->RegisterNamedService(
|
||||
"caps:ss", std::make_shared<IScreenShotService>(system, album_manager));
|
||||
server_manager->RegisterNamedService("caps:sc",
|
||||
std::make_shared<IScreenShotControlService>(system));
|
||||
server_manager->RegisterNamedService(
|
||||
"caps:su", std::make_shared<IScreenShotApplicationService>(system, album_manager));
|
||||
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
|
||||
@@ -263,9 +263,6 @@ Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: ???
|
||||
R_UNLESS(!image_data.empty(), ResultUnknown);
|
||||
|
||||
const auto date = ConvertToAlbumDateTime(posix_time);
|
||||
|
||||
return SaveImage(out_entry, image_data, title_id, date);
|
||||
|
||||
@@ -43,30 +43,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
ECTX_W::ECTX_W(Core::System& system_) : ServiceFramework{system_, "ectx:w"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "CreateContextRegistrar"},
|
||||
{1, nullptr, "CommitContext"},
|
||||
{2, nullptr, "RemoveContext"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
ECTX_W::~ECTX_W() = default;
|
||||
|
||||
ECTX_R::ECTX_R(Core::System& system_) : ServiceFramework{system_, "ectx:r"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetContextInfo"},
|
||||
{1, nullptr, "PullContext"},
|
||||
{2, nullptr, "ListContextDescriptorWithResultForDebug"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
ECTX_R::~ECTX_R() = default;
|
||||
|
||||
ECTX_AW::ECTX_AW(Core::System& system_) : ServiceFramework{system_, "ectx:aw"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
|
||||
@@ -11,18 +11,6 @@ class System;
|
||||
|
||||
namespace Service::Glue {
|
||||
|
||||
class ECTX_W final : public ServiceFramework<ECTX_W> {
|
||||
public:
|
||||
explicit ECTX_W(Core::System& system_);
|
||||
~ECTX_W() override;
|
||||
};
|
||||
|
||||
class ECTX_R final : public ServiceFramework<ECTX_R> {
|
||||
public:
|
||||
explicit ECTX_R(Core::System& system_);
|
||||
~ECTX_R() override;
|
||||
};
|
||||
|
||||
class ECTX_AW final : public ServiceFramework<ECTX_AW> {
|
||||
public:
|
||||
explicit ECTX_AW(Core::System& system_);
|
||||
|
||||
@@ -19,27 +19,40 @@ void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
// ARP
|
||||
server_manager->RegisterNamedService("arp:r", std::make_shared<ARP_R>(system, system.GetARPManager()));
|
||||
server_manager->RegisterNamedService("arp:w", std::make_shared<ARP_W>(system, system.GetARPManager()));
|
||||
server_manager->RegisterNamedService("arp:r",
|
||||
std::make_shared<ARP_R>(system, system.GetARPManager()));
|
||||
server_manager->RegisterNamedService("arp:w",
|
||||
std::make_shared<ARP_W>(system, system.GetARPManager()));
|
||||
|
||||
// BackGround Task Controller
|
||||
server_manager->RegisterNamedService("bgtc:t", std::make_shared<BGTC_T>(system));
|
||||
server_manager->RegisterNamedService("bgtc:sc", std::make_shared<BGTC_SC>(system));
|
||||
|
||||
// Error Context
|
||||
server_manager->RegisterNamedService("ectx:w", std::make_shared<ECTX_W>(system));
|
||||
server_manager->RegisterNamedService("ectx:r", std::make_shared<ECTX_R>(system));
|
||||
server_manager->RegisterNamedService("ectx:aw", std::make_shared<ECTX_AW>(system));
|
||||
|
||||
// Notification Services
|
||||
server_manager->RegisterNamedService("notif:a", std::make_shared<INotificationServicesForApplication>(system));
|
||||
server_manager->RegisterNamedService("notif:s", std::make_shared<INotificationServices>(system));
|
||||
server_manager->RegisterNamedService(
|
||||
"notif:a", std::make_shared<INotificationServicesForApplication>(system));
|
||||
server_manager->RegisterNamedService("notif:s",
|
||||
std::make_shared<INotificationServices>(system));
|
||||
|
||||
// Time
|
||||
auto time = std::make_shared<Time::TimeManager>(system);
|
||||
server_manager->RegisterNamedService("time:u", std::make_shared<Time::StaticService>(system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 0, 0}, time, "time:u"));
|
||||
server_manager->RegisterNamedService("time:a", std::make_shared<Time::StaticService>(system, Service::PSC::Time::StaticServiceSetupInfo{1, 1, 0, 1, 0, 0}, time, "time:a"));
|
||||
server_manager->RegisterNamedService("time:r", std::make_shared<Time::StaticService>(system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 1, 0}, time, "time:r"));
|
||||
|
||||
server_manager->RegisterNamedService(
|
||||
"time:u",
|
||||
std::make_shared<Time::StaticService>(
|
||||
system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 0, 0}, time, "time:u"));
|
||||
server_manager->RegisterNamedService(
|
||||
"time:a",
|
||||
std::make_shared<Time::StaticService>(
|
||||
system, Service::PSC::Time::StaticServiceSetupInfo{1, 1, 0, 1, 0, 0}, time, "time:a"));
|
||||
server_manager->RegisterNamedService(
|
||||
"time:r",
|
||||
std::make_shared<Time::StaticService>(
|
||||
system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 1, 0}, time, "time:r"));
|
||||
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -19,77 +16,27 @@
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
class IHidTemporaryServer final : public ServiceFramework<IHidTemporaryServer> {
|
||||
public:
|
||||
explicit IHidTemporaryServer(Core::System& system_)
|
||||
: ServiceFramework{system_, "hid:tmp"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetConsoleSixAxisSensorCalibrationValues"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
~IHidTemporaryServer() override = default;
|
||||
};
|
||||
|
||||
class AHID_CD final : public ServiceFramework<AHID_CD> {
|
||||
public:
|
||||
explicit AHID_CD(Core::System& system_)
|
||||
: ServiceFramework{system_, "ahid:cd"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "AcquireDevice"},
|
||||
{1, nullptr, "ReleaseDevice"},
|
||||
{2, nullptr, "GetCtrlSession"},
|
||||
{3, nullptr, "GetReadSession"},
|
||||
{4, nullptr, "GetWriteSession"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
~AHID_CD() override = default;
|
||||
};
|
||||
|
||||
class AHID_HDR final : public ServiceFramework<AHID_HDR> {
|
||||
public:
|
||||
explicit AHID_HDR(Core::System& system_)
|
||||
: ServiceFramework{system_, "ahid:hdr"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetDeviceEntries"},
|
||||
{1, nullptr, "GetDeviceList"},
|
||||
{2, nullptr, "GetDeviceParameters"},
|
||||
{3, nullptr, "AttachDevice"},
|
||||
{4, nullptr, "DetachDevice"},
|
||||
{5, nullptr, "SetDeviceFilter"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
~AHID_HDR() override = default;
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
auto firmware_settings = std::make_shared<HidFirmwareSettings>(system);
|
||||
auto resource_manager = std::make_shared<ResourceManager>(system, firmware_settings);
|
||||
std::shared_ptr<HidFirmwareSettings> firmware_settings =
|
||||
std::make_shared<HidFirmwareSettings>(system);
|
||||
std::shared_ptr<ResourceManager> resource_manager =
|
||||
std::make_shared<ResourceManager>(system, firmware_settings);
|
||||
|
||||
resource_manager->Initialize();
|
||||
|
||||
server_manager->RegisterNamedService("hid", std::make_shared<IHidServer>(system, resource_manager, firmware_settings));
|
||||
server_manager->RegisterNamedService("hid:dbg", std::make_shared<IHidDebugServer>(system, resource_manager, firmware_settings));
|
||||
server_manager->RegisterNamedService("hid:sys", std::make_shared<IHidSystemServer>(system, resource_manager, firmware_settings));
|
||||
server_manager->RegisterNamedService("hid:tmp", std::make_shared<IHidTemporaryServer>(system));
|
||||
server_manager->RegisterNamedService(
|
||||
"hid", std::make_shared<IHidServer>(system, resource_manager, firmware_settings));
|
||||
server_manager->RegisterNamedService(
|
||||
"hid:dbg", std::make_shared<IHidDebugServer>(system, resource_manager, firmware_settings));
|
||||
server_manager->RegisterNamedService(
|
||||
"hid:sys", std::make_shared<IHidSystemServer>(system, resource_manager, firmware_settings));
|
||||
|
||||
server_manager->RegisterNamedService("hidbus", std::make_shared<Hidbus>(system));
|
||||
|
||||
server_manager->RegisterNamedService("irs", std::make_shared<IRS::IRS>(system));
|
||||
server_manager->RegisterNamedService("irs:sys", std::make_shared<IRS::IRS_SYS>(system));
|
||||
|
||||
server_manager->RegisterNamedService("ahid:cd", std::make_shared<AHID_CD>(system));
|
||||
server_manager->RegisterNamedService("ahid:hdr", std::make_shared<AHID_HDR>(system));
|
||||
|
||||
server_manager->RegisterNamedService("xcd:sys", std::make_shared<XCD_SYS>(system));
|
||||
|
||||
system.RunServer(std::move(server_manager));
|
||||
|
||||
@@ -38,30 +38,10 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
|
||||
{213, nullptr, "ActivateNfc"},
|
||||
{214, nullptr, "GetXcdHandleForNpadWithNfc"},
|
||||
{215, nullptr, "IsNfcActivated"},
|
||||
{216, nullptr, "GetAbstractedPadIdForNpadWithNfc"},
|
||||
{217, nullptr, "SetNfcEvent"},
|
||||
{218, nullptr, "GetNfcInfo"},
|
||||
{219, nullptr, "StartNfcDiscovery"},
|
||||
{220, nullptr, "StopNfcDiscovery"},
|
||||
{221, nullptr, "StartNtagRead"},
|
||||
{222, nullptr, "StartNtagWrite"},
|
||||
{223, nullptr, "SendNfcRawData"},
|
||||
{224, nullptr, "RegisterMifareKey"},
|
||||
{225, nullptr, "ClearMifareKey"},
|
||||
{226, nullptr, "StartMifareRead"},
|
||||
{227, nullptr, "StartMifareWrite"},
|
||||
{230, nullptr, "AcquireIrSensorEventHndle" },
|
||||
{230, nullptr, "AcquireIrSensorEventHandle"},
|
||||
{231, nullptr, "ActivateIrSensor"},
|
||||
{232, nullptr, "GetIrSensorState"},
|
||||
{233, nullptr, "GetXcdHandleForNpadWihIrSensor" },
|
||||
{234, nullptr, "GetNpadJoyHoldType"},
|
||||
{241, nullptr, "GetDataFormat"},
|
||||
{242, nullptr, "SetDataFormat"},
|
||||
{243, nullptr, "GetMcuState"},
|
||||
{244, nullptr, "SetMcuState"},
|
||||
{245, nullptr, "GetMcuVersionForNfc"},
|
||||
{246, nullptr, "CheckNfcDevicePower"},
|
||||
{247, nullptr, "SetMcuStateImmediate"},
|
||||
{233, nullptr, "GetXcdHandleForNpadWithIrSensor"},
|
||||
{301, nullptr, "ActivateNpadSystem"},
|
||||
{303, &IHidSystemServer::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"},
|
||||
{304, &IHidSystemServer::EnableAssigningSingleOnSlSrPress, "EnableAssigningSingleOnSlSrPress"},
|
||||
|
||||
@@ -330,12 +330,12 @@ public:
|
||||
/// Helper function to test whether the output buffer at buffer_index can be written
|
||||
[[nodiscard]] bool CanWriteBuffer(std::size_t buffer_index = 0) const;
|
||||
|
||||
[[nodiscard]] Handle GetCopyHandle(std::size_t index) const noexcept {
|
||||
return index >= incoming_copy_handles.size() ? 0 : incoming_copy_handles[index];
|
||||
[[nodiscard]] Handle GetCopyHandle(std::size_t index) const {
|
||||
return incoming_copy_handles.at(index);
|
||||
}
|
||||
|
||||
[[nodiscard]] Handle GetMoveHandle(std::size_t index) const noexcept {
|
||||
return index >= incoming_move_handles.size() ? 0 : incoming_move_handles[index];
|
||||
[[nodiscard]] Handle GetMoveHandle(std::size_t index) const {
|
||||
return incoming_move_handles.at(index);
|
||||
}
|
||||
|
||||
void AddMoveObject(Kernel::KAutoObject* object) {
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/mnpp/mnpp.h"
|
||||
#include "core/hle/service/server_manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::MNPP {
|
||||
|
||||
class MNPP_APP final : public ServiceFramework<MNPP_APP> {
|
||||
public:
|
||||
explicit MNPP_APP(Core::System& system_) : ServiceFramework{system_, "mnpp:app"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &MNPP_APP::Cmd0, "Cmd0"},
|
||||
{1, &MNPP_APP::Cmd1, "Cmd1"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void Cmd0(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MNPP, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void Cmd1(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MNPP, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
class MNPP_SYS final : public ServiceFramework<MNPP_SYS> {
|
||||
public:
|
||||
explicit MNPP_SYS(Core::System& system_) : ServiceFramework{system_, "mnpp:sys"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Cmd0"},
|
||||
{10, nullptr, "Cmd10"},
|
||||
{100, nullptr, "Cmd100"},
|
||||
{200, nullptr, "Cmd200"},
|
||||
{300, nullptr, "Cmd300"},
|
||||
{400, nullptr, "Cmd400"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class MNPP_WEB final : public ServiceFramework<MNPP_WEB> {
|
||||
public:
|
||||
explicit MNPP_WEB(Core::System& system_) : ServiceFramework{system_, "mnpp:web"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Cmd0"},
|
||||
{1, nullptr, "Cmd1"},
|
||||
{10, nullptr, "Cmd10"},
|
||||
{20, nullptr, "Cmd20"},
|
||||
{100, nullptr, "Cmd100"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("mnpp:app", std::make_shared<MNPP_APP>(system));
|
||||
server_manager->RegisterNamedService("mnpp:sys", std::make_shared<MNPP_SYS>(system));
|
||||
server_manager->RegisterNamedService("mnpp:web", std::make_shared<MNPP_WEB>(system));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
} // namespace Service::MNPP
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/mnpp/mnpp_app.h"
|
||||
#include "core/hle/service/server_manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::MNPP {
|
||||
|
||||
class MNPP_APP final : public ServiceFramework<MNPP_APP> {
|
||||
public:
|
||||
explicit MNPP_APP(Core::System& system_) : ServiceFramework{system_, "mnpp:app"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &MNPP_APP::Unknown0, "unknown0"},
|
||||
{1, &MNPP_APP::Unknown1, "unknown1"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void Unknown0(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MNPP, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void Unknown1(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MNPP, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("mnpp:app", std::make_shared<MNPP_APP>(system));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
} // namespace Service::MNPP
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -55,30 +55,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class PCV_ARB final : public ServiceFramework<PCV_ARB> {
|
||||
public:
|
||||
explicit PCV_ARB(Core::System& system_) : ServiceFramework{system_, "pcv:arb"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "ReleaseControl"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class PCV_IMM final : public ServiceFramework<PCV_IMM> {
|
||||
public:
|
||||
explicit PCV_IMM(Core::System& system_) : ServiceFramework{system_, "pcv:imm"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "SetClockRate"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class IClkrstSession final : public ServiceFramework<IClkrstSession> {
|
||||
public:
|
||||
explicit IClkrstSession(Core::System& system_, DeviceCode device_code_)
|
||||
@@ -172,8 +148,6 @@ void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("pcv", std::make_shared<PCV>(system));
|
||||
server_manager->RegisterNamedService("pcv:arb", std::make_shared<PCV_ARB>(system));
|
||||
server_manager->RegisterNamedService("pcv:imm", std::make_shared<PCV_IMM>(system));
|
||||
server_manager->RegisterNamedService("clkrst", std::make_shared<CLKRST>(system, "clkrst"));
|
||||
server_manager->RegisterNamedService("clkrst:i", std::make_shared<CLKRST>(system, "clkrst:i"));
|
||||
server_manager->RegisterNamedService("clkrst:a", std::make_shared<CLKRST_A>(system));
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#include "core/hle/service/mig/mig.h"
|
||||
#include "core/hle/service/mii/mii.h"
|
||||
#include "core/hle/service/mm/mm_u.h"
|
||||
#include "core/hle/service/mnpp/mnpp.h"
|
||||
#include "core/hle/service/mnpp/mnpp_app.h"
|
||||
#include "core/hle/service/ncm/ncm.h"
|
||||
#include "core/hle/service/nfc/nfc.h"
|
||||
#include "core/hle/service/nfp/nfp.h"
|
||||
|
||||
@@ -173,26 +173,8 @@ void BSD::Socket(HLERequestContext& ctx) {
|
||||
|
||||
LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
|
||||
|
||||
const auto [fd, bsd_errno] = SocketImpl(Domain(domain), Type(type), Protocol(protocol));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<s32>(fd);
|
||||
rb.PushEnum(bsd_errno);
|
||||
}
|
||||
|
||||
void BSD::SocketExempt(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 domain = rp.Pop<u32>();
|
||||
const u32 type = rp.Pop<u32>();
|
||||
const u32 protocol = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
|
||||
|
||||
auto [fd, bsd_errno] = SocketImpl(Domain(domain), Type(type), Protocol(protocol));
|
||||
if (bsd_errno == Errno::SUCCESS) {
|
||||
bsd_errno = ShutdownImpl(fd, 0);
|
||||
}
|
||||
const auto [fd, bsd_errno] = SocketImpl(static_cast<Domain>(domain), static_cast<Type>(type),
|
||||
static_cast<Protocol>(protocol));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
@@ -463,7 +445,6 @@ void BSD::Close(HLERequestContext& ctx) {
|
||||
BuildErrnoResponse(ctx, CloseImpl(fd));
|
||||
}
|
||||
|
||||
/// @brief Only bsd:s is able to dup()
|
||||
void BSD::DuplicateSocket(HLERequestContext& ctx) {
|
||||
struct InputParameters {
|
||||
s32 fd;
|
||||
@@ -482,13 +463,6 @@ void BSD::DuplicateSocket(HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
if (is_user) {
|
||||
rb.PushRaw(OutputParameters{
|
||||
.ret = 0,
|
||||
.bsd_errno = Errno::INVAL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
auto const res_v = DuplicateSocketImpl(input.fd);
|
||||
if (auto* res = std::get_if<s32>(&res_v)) {
|
||||
@@ -522,13 +496,11 @@ void BSD::ExecuteWork(HLERequestContext& ctx, Work work) {
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protocol) {
|
||||
// user bsd:u has restrictions on SOCK_SEQPACKET and SOCK_RAW
|
||||
if (is_user && (type == Type::SEQPACKET || type == Type::RAW)) {
|
||||
if (type == Type::RAW && domain == Domain::INET && protocol == Protocol::ICMP) {
|
||||
// fine, can use on bsd:s and bsd:u
|
||||
} else {
|
||||
return {-1, Errno::INVAL};
|
||||
}
|
||||
|
||||
if (type == Type::SEQPACKET) {
|
||||
UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management");
|
||||
} else if (type == Type::RAW && (domain != Domain::INET || protocol != Protocol::ICMP)) {
|
||||
UNIMPLEMENTED_MSG("SOCK_RAW errno management");
|
||||
}
|
||||
|
||||
[[maybe_unused]] const bool unk_flag = (static_cast<u32>(type) & 0x20000000) != 0;
|
||||
@@ -1080,15 +1052,14 @@ void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) {
|
||||
}
|
||||
}
|
||||
|
||||
BSD::BSD(Core::System& system_, const char* name, bool is_user_)
|
||||
: ServiceFramework{system_, name}
|
||||
, is_user{is_user_} {
|
||||
BSD::BSD(Core::System& system_, const char* name)
|
||||
: ServiceFramework{system_, name} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &BSD::RegisterClient, "RegisterClient"},
|
||||
{1, &BSD::StartMonitoring, "StartMonitoring"},
|
||||
{2, &BSD::Socket, "Socket"},
|
||||
{3, &BSD::SocketExempt, "SocketExempt"},
|
||||
{3, nullptr, "SocketExempt"},
|
||||
{4, nullptr, "Open"},
|
||||
{5, &BSD::Select, "Select"},
|
||||
{6, &BSD::Poll, "Poll"},
|
||||
@@ -1154,8 +1125,7 @@ std::unique_lock<std::mutex> BSD::LockService() noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
BSDCFG::BSDCFG(Core::System& system_, const char *name)
|
||||
: ServiceFramework{system_, name} {
|
||||
BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "SetIfUp"},
|
||||
@@ -1182,16 +1152,4 @@ BSDCFG::BSDCFG(Core::System& system_, const char *name)
|
||||
|
||||
BSDCFG::~BSDCFG() = default;
|
||||
|
||||
BSD_NU::BSD_NU(Core::System& system_)
|
||||
: ServiceFramework{system_, "bsd:nu"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "CreateUserService"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
BSD_NU::~BSD_NU() = default;
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Service::Sockets {
|
||||
|
||||
class BSD final : public ServiceFramework<BSD> {
|
||||
public:
|
||||
explicit BSD(Core::System& system_, const char* name, bool is_user);
|
||||
explicit BSD(Core::System& system_, const char* name);
|
||||
~BSD() override;
|
||||
|
||||
// These methods are called from SSL; the first two are also called from
|
||||
@@ -129,7 +129,6 @@ private:
|
||||
void RegisterClient(HLERequestContext& ctx);
|
||||
void StartMonitoring(HLERequestContext& ctx);
|
||||
void Socket(HLERequestContext& ctx);
|
||||
void SocketExempt(HLERequestContext& ctx);
|
||||
void Select(HLERequestContext& ctx);
|
||||
void Poll(HLERequestContext& ctx);
|
||||
void Accept(HLERequestContext& ctx);
|
||||
@@ -156,7 +155,8 @@ private:
|
||||
void ExecuteWork(HLERequestContext& ctx, Work work);
|
||||
|
||||
std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol);
|
||||
std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer, s32 nfds, s32 timeout);
|
||||
std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer,
|
||||
s32 nfds, s32 timeout);
|
||||
std::pair<s32, Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer);
|
||||
Errno BindImpl(s32 fd, std::span<const u8> addr);
|
||||
Errno ConnectImpl(s32 fd, std::span<const u8> addr);
|
||||
@@ -189,19 +189,12 @@ private:
|
||||
|
||||
protected:
|
||||
std::unique_lock<std::mutex> LockService() noexcept override;
|
||||
bool is_user = false;
|
||||
};
|
||||
|
||||
class BSDCFG final : public ServiceFramework<BSDCFG> {
|
||||
public:
|
||||
explicit BSDCFG(Core::System& system_, const char *name);
|
||||
explicit BSDCFG(Core::System& system_);
|
||||
~BSDCFG() override;
|
||||
};
|
||||
|
||||
class BSD_NU final : public ServiceFramework<BSD_NU> {
|
||||
public:
|
||||
explicit BSD_NU(Core::System& system_);
|
||||
~BSD_NU() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -406,21 +406,4 @@ void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<s32>(0); // bsd errno
|
||||
}
|
||||
|
||||
|
||||
DNS_PRIV::DNS_PRIV(Core::System& system_)
|
||||
: ServiceFramework{system_, "dns:priv"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Cmd0"},
|
||||
{1, nullptr, "Cmd1"},
|
||||
{2, nullptr, "Cmd2"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
DNS_PRIV::~DNS_PRIV() = default;
|
||||
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -28,10 +25,4 @@ private:
|
||||
void ResolverSetOptionRequest(HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
class DNS_PRIV final : public ServiceFramework<DNS_PRIV> {
|
||||
public:
|
||||
explicit DNS_PRIV(Core::System& system_);
|
||||
~DNS_PRIV() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -15,16 +12,12 @@ namespace Service::Sockets {
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("bsd:s", std::make_shared<BSD>(system, "bsd:s", false));
|
||||
server_manager->RegisterNamedService("bsd:u", std::make_shared<BSD>(system, "bsd:u", true));
|
||||
server_manager->RegisterNamedService("bsd:a", std::make_shared<BSD>(system, "bsd:a", true));
|
||||
server_manager->RegisterNamedService("bsd:nu", std::make_shared<BSD_NU>(system));
|
||||
server_manager->RegisterNamedService("bsdcfg", std::make_shared<BSDCFG>(system, "bsdcfg"));
|
||||
server_manager->RegisterNamedService("ifcfg", std::make_shared<BSDCFG>(system, "ifcfg"));
|
||||
server_manager->RegisterNamedService("bsd:s", std::make_shared<BSD>(system, "bsd:s"));
|
||||
server_manager->RegisterNamedService("bsd:u", std::make_shared<BSD>(system, "bsd:u"));
|
||||
server_manager->RegisterNamedService("bsdcfg", std::make_shared<BSDCFG>(system));
|
||||
server_manager->RegisterNamedService("nsd:a", std::make_shared<NSD>(system, "nsd:a"));
|
||||
server_manager->RegisterNamedService("nsd:u", std::make_shared<NSD>(system, "nsd:u"));
|
||||
server_manager->RegisterNamedService("sfdnsres", std::make_shared<SFDNSRES>(system));
|
||||
server_manager->RegisterNamedService("dns:priv", std::make_shared<DNS_PRIV>(system));
|
||||
server_manager->StartAdditionalHostThreads("bsdsocket", 2);
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
@@ -53,11 +53,12 @@ Result NpadAbstractBatteryHandler::DecrementRefCounter() {
|
||||
Result NpadAbstractBatteryHandler::UpdateBatteryState(u64 aruid) {
|
||||
const auto npad_index = NpadIdTypeToIndex(properties_handler->GetNpadId());
|
||||
AruidData* aruid_data = applet_resource_holder->applet_resource->GetAruidData(aruid);
|
||||
if (aruid_data == nullptr || aruid_data->shared_memory_format == nullptr) {
|
||||
if (aruid_data == nullptr) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
auto& npad_internal_state = aruid_data->shared_memory_format->npad.npad_entry[npad_index].internal_state;
|
||||
auto& npad_internal_state =
|
||||
aruid_data->shared_memory_format->npad.npad_entry[npad_index].internal_state;
|
||||
auto& system_properties = npad_internal_state.system_properties;
|
||||
|
||||
system_properties.is_charging_joy_dual.Assign(dual_battery.is_charging);
|
||||
|
||||
@@ -80,11 +80,7 @@ Result NPad::Activate(u64 aruid) {
|
||||
std::scoped_lock shared_lock{*applet_resource_holder.shared_mutex};
|
||||
|
||||
auto* data = applet_resource_holder.applet_resource->GetAruidData(aruid);
|
||||
auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid);
|
||||
if (aruid_index >= AruidIndexMax) {
|
||||
LOG_ERROR(Service_HID, "Invalid aruid:{:016X}", aruid);
|
||||
aruid_index = 0;
|
||||
}
|
||||
const auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid);
|
||||
|
||||
if (data == nullptr || !data->flag.is_assigned) {
|
||||
return ResultSuccess;
|
||||
@@ -1103,16 +1099,13 @@ Result NPad::RegisterAppletResourceUserId(u64 aruid) {
|
||||
|
||||
void NPad::UnregisterAppletResourceUserId(u64 aruid) {
|
||||
// TODO: Remove this once abstract pad is emulated properly
|
||||
auto aruid_index = npad_resource.GetIndexFromAruid(aruid);
|
||||
if (aruid_index >= AruidIndexMax) {
|
||||
LOG_ERROR(Service_HID, "Invalid aruid:{:016X}", aruid);
|
||||
aruid_index = 0;
|
||||
}
|
||||
const auto aruid_index = npad_resource.GetIndexFromAruid(aruid);
|
||||
for (auto& controller : controller_data[aruid_index]) {
|
||||
controller.is_active = false;
|
||||
controller.is_connected = false;
|
||||
controller.shared_memory = nullptr;
|
||||
}
|
||||
|
||||
npad_resource.UnregisterAppletResourceUserId(aruid);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,9 +71,6 @@ Result NPadResource::RegisterAppletResourceUserId(u64 aruid) {
|
||||
|
||||
void NPadResource::UnregisterAppletResourceUserId(u64 aruid) {
|
||||
const u64 aruid_index = GetIndexFromAruid(aruid);
|
||||
if (aruid_index >= AruidIndexMax) {
|
||||
return;
|
||||
}
|
||||
|
||||
FreeAppletResourceId(aruid);
|
||||
if (aruid_index < AruidIndexMax) {
|
||||
|
||||
@@ -267,6 +267,38 @@ add_library(video_core STATIC
|
||||
video_core.h
|
||||
)
|
||||
|
||||
if (ENABLE_LSFG)
|
||||
target_sources(video_core PRIVATE
|
||||
frame_gen/lossless_dll.cpp
|
||||
frame_gen/lossless_dll.h
|
||||
frame_gen/lsfg_translate.cpp
|
||||
frame_gen/lsfg_translate.h
|
||||
renderer_vulkan/present/frame_gen.cpp
|
||||
renderer_vulkan/present/frame_gen.h
|
||||
renderer_vulkan/present/frame_gen_pacer.cpp
|
||||
renderer_vulkan/present/frame_gen_pacer.h
|
||||
renderer_vulkan/present/lsfg_alpha.cpp
|
||||
renderer_vulkan/present/lsfg_alpha.h
|
||||
renderer_vulkan/present/lsfg_beta.cpp
|
||||
renderer_vulkan/present/lsfg_beta.h
|
||||
renderer_vulkan/present/lsfg_chain.cpp
|
||||
renderer_vulkan/present/lsfg_chain.h
|
||||
renderer_vulkan/present/lsfg_common.cpp
|
||||
renderer_vulkan/present/lsfg_common.h
|
||||
renderer_vulkan/present/lsfg_delta.cpp
|
||||
renderer_vulkan/present/lsfg_delta.h
|
||||
renderer_vulkan/present/lsfg_gamma.cpp
|
||||
renderer_vulkan/present/lsfg_gamma.h
|
||||
renderer_vulkan/present/lsfg_generate.cpp
|
||||
renderer_vulkan/present/lsfg_generate.h
|
||||
renderer_vulkan/present/lsfg_mipmaps.cpp
|
||||
renderer_vulkan/present/lsfg_mipmaps.h
|
||||
renderer_vulkan/present/lsfg_shaders.cpp
|
||||
renderer_vulkan/present/lsfg_shaders.h
|
||||
)
|
||||
target_compile_definitions(video_core PUBLIC HAS_LSFG)
|
||||
endif()
|
||||
|
||||
if (ENABLE_OPENGL)
|
||||
target_sources(video_core PRIVATE
|
||||
renderer_opengl/present/filters.cpp
|
||||
|
||||
@@ -0,0 +1,553 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
#include "common/cityhash.h"
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_paths.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/frame_gen/lsfg_translate.h"
|
||||
|
||||
namespace VideoCore::FrameGen {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u16 DOS_MAGIC = 0x5A4D;
|
||||
constexpr u32 PE_SIGNATURE = 0x00004550;
|
||||
constexpr u16 PE32_MAGIC = 0x010B;
|
||||
constexpr u16 PE32_PLUS_MAGIC = 0x020B;
|
||||
|
||||
constexpr size_t DOS_LFANEW_OFFSET = 0x3C;
|
||||
constexpr size_t COFF_HEADER_SIZE = 20;
|
||||
constexpr size_t OPTIONAL_HEADER_SIZE_OFFSET = 16;
|
||||
constexpr size_t SECTION_HEADER_SIZE = 40;
|
||||
constexpr size_t DATA_DIRECTORY_ENTRY_SIZE = 8;
|
||||
constexpr size_t DATA_DIRECTORY_OFFSET_PE32 = 96;
|
||||
constexpr size_t DATA_DIRECTORY_OFFSET_PE32_PLUS = 112;
|
||||
constexpr size_t RESOURCE_DATA_DIRECTORY_INDEX = 2;
|
||||
|
||||
constexpr size_t RESOURCE_DIRECTORY_SIZE = 16;
|
||||
constexpr size_t RESOURCE_NAMED_COUNT_OFFSET = 12;
|
||||
constexpr size_t RESOURCE_ID_COUNT_OFFSET = 14;
|
||||
constexpr size_t RESOURCE_ENTRY_SIZE = 8;
|
||||
constexpr u32 RESOURCE_SUBDIRECTORY_FLAG = 0x80000000;
|
||||
constexpr u32 RESOURCE_TYPE_RCDATA = 10;
|
||||
|
||||
constexpr u32 MIPMAPS_SHADER_ID = 255;
|
||||
constexpr u32 GENERATE_SHADER_ID = 256;
|
||||
constexpr u32 PERFORMANCE_SHADER_ID_FIRST = 280;
|
||||
constexpr u32 PERFORMANCE_SHADER_ID_LAST = 302;
|
||||
|
||||
constexpr u32 CACHE_MAGIC = 0x4746534C;
|
||||
constexpr u32 CACHE_VERSION = 2;
|
||||
|
||||
struct CacheHeader {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u64 source_size;
|
||||
u64 source_hash;
|
||||
u32 module_count;
|
||||
u32 variant;
|
||||
};
|
||||
|
||||
struct Section {
|
||||
u32 virtual_address;
|
||||
u32 virtual_size;
|
||||
u32 raw_address;
|
||||
u32 raw_size;
|
||||
};
|
||||
|
||||
struct ResourceEntry {
|
||||
u32 id;
|
||||
u32 offset;
|
||||
bool is_directory;
|
||||
bool is_named;
|
||||
};
|
||||
|
||||
class ImageReader {
|
||||
public:
|
||||
explicit ImageReader(std::span<const u8> image_) : image{image_} {}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] bool Read(size_t offset, T& out_value) const {
|
||||
if (offset > image.size() || image.size() - offset < sizeof(T)) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(&out_value, image.data() + offset, sizeof(T));
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Slice(size_t offset, size_t size, std::span<const u8>& out_slice) const {
|
||||
if (offset > image.size() || image.size() - offset < size) {
|
||||
return false;
|
||||
}
|
||||
out_slice = image.subspan(offset, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::span<const u8> image;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<size_t> FindPeHeader(const ImageReader& reader) {
|
||||
u16 dos_magic{};
|
||||
if (!reader.Read(0, dos_magic) || dos_magic != DOS_MAGIC) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
u32 pe_offset{};
|
||||
if (!reader.Read(DOS_LFANEW_OFFSET, pe_offset)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
u32 pe_signature{};
|
||||
if (!reader.Read(pe_offset, pe_signature) || pe_signature != PE_SIGNATURE) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<size_t>(pe_offset);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<size_t> FindDataDirectory(const ImageReader& reader,
|
||||
size_t optional_header_offset) {
|
||||
u16 optional_magic{};
|
||||
if (!reader.Read(optional_header_offset, optional_magic)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
switch (optional_magic) {
|
||||
case PE32_MAGIC:
|
||||
return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32;
|
||||
case PE32_PLUS_MAGIC:
|
||||
return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32_PLUS;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ReadSections(const ImageReader& reader, size_t pe_offset,
|
||||
std::vector<Section>& out_sections) {
|
||||
u16 section_count{};
|
||||
u16 optional_header_size{};
|
||||
if (!reader.Read(pe_offset + 4 + 2, section_count) ||
|
||||
!reader.Read(pe_offset + 4 + OPTIONAL_HEADER_SIZE_OFFSET, optional_header_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t table_offset = pe_offset + 4 + COFF_HEADER_SIZE + optional_header_size;
|
||||
out_sections.reserve(section_count);
|
||||
for (size_t i = 0; i < section_count; ++i) {
|
||||
const size_t offset = table_offset + i * SECTION_HEADER_SIZE;
|
||||
Section section{};
|
||||
if (!reader.Read(offset + 8, section.virtual_size) ||
|
||||
!reader.Read(offset + 12, section.virtual_address) ||
|
||||
!reader.Read(offset + 16, section.raw_size) ||
|
||||
!reader.Read(offset + 20, section.raw_address)) {
|
||||
return false;
|
||||
}
|
||||
out_sections.push_back(section);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<size_t> RvaToFileOffset(std::span<const Section> sections, u32 rva) {
|
||||
for (const Section& section : sections) {
|
||||
const u32 span = std::max(section.virtual_size, section.raw_size);
|
||||
if (span == 0 || rva < section.virtual_address) {
|
||||
continue;
|
||||
}
|
||||
const u32 relative = rva - section.virtual_address;
|
||||
if (relative < span) {
|
||||
return static_cast<size_t>(section.raw_address) + relative;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ReadResourceEntries(const ImageReader& reader, size_t directory_offset,
|
||||
std::vector<ResourceEntry>& out_entries) {
|
||||
u16 named_count{};
|
||||
u16 id_count{};
|
||||
if (!reader.Read(directory_offset + RESOURCE_NAMED_COUNT_OFFSET, named_count) ||
|
||||
!reader.Read(directory_offset + RESOURCE_ID_COUNT_OFFSET, id_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t total = size_t{named_count} + size_t{id_count};
|
||||
out_entries.clear();
|
||||
out_entries.reserve(total);
|
||||
for (size_t i = 0; i < total; ++i) {
|
||||
const size_t offset = directory_offset + RESOURCE_DIRECTORY_SIZE + i * RESOURCE_ENTRY_SIZE;
|
||||
u32 name{};
|
||||
u32 data{};
|
||||
if (!reader.Read(offset, name) || !reader.Read(offset + 4, data)) {
|
||||
return false;
|
||||
}
|
||||
out_entries.push_back(ResourceEntry{
|
||||
.id = name & ~RESOURCE_SUBDIRECTORY_FLAG,
|
||||
.offset = data & ~RESOURCE_SUBDIRECTORY_FLAG,
|
||||
.is_directory = (data & RESOURCE_SUBDIRECTORY_FLAG) != 0,
|
||||
.is_named = (name & RESOURCE_SUBDIRECTORY_FLAG) != 0,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ReadResourceLeaf(const ImageReader& reader, std::span<const Section> sections,
|
||||
size_t leaf_offset, std::span<const u8>& out_data) {
|
||||
u32 data_rva{};
|
||||
u32 data_size{};
|
||||
if (!reader.Read(leaf_offset, data_rva) || !reader.Read(leaf_offset + 4, data_size) ||
|
||||
data_size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::optional<size_t> data_offset = RvaToFileOffset(sections, data_rva);
|
||||
if (!data_offset) {
|
||||
return false;
|
||||
}
|
||||
return reader.Slice(*data_offset, data_size, out_data);
|
||||
}
|
||||
|
||||
using ResourceSpans = std::map<u32, std::span<const u8>>;
|
||||
|
||||
[[nodiscard]] bool CollectRcData(const ImageReader& reader, std::span<const Section> sections,
|
||||
size_t resource_base, ResourceSpans& out_resources) {
|
||||
std::vector<ResourceEntry> type_entries;
|
||||
if (!ReadResourceEntries(reader, resource_base, type_entries)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const ResourceEntry& type_entry : type_entries) {
|
||||
if (type_entry.is_named || type_entry.id != RESOURCE_TYPE_RCDATA ||
|
||||
!type_entry.is_directory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<ResourceEntry> name_entries;
|
||||
if (!ReadResourceEntries(reader, resource_base + type_entry.offset, name_entries)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const ResourceEntry& name_entry : name_entries) {
|
||||
if (name_entry.is_named || !name_entry.is_directory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<ResourceEntry> language_entries;
|
||||
if (!ReadResourceEntries(reader, resource_base + name_entry.offset, language_entries)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const ResourceEntry& language_entry : language_entries) {
|
||||
if (language_entry.is_directory) {
|
||||
continue;
|
||||
}
|
||||
std::span<const u8> data;
|
||||
if (!ReadResourceLeaf(reader, sections, resource_base + language_entry.offset,
|
||||
data)) {
|
||||
continue;
|
||||
}
|
||||
out_resources.insert_or_assign(name_entry.id, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<u32> PerformanceShaderIds() {
|
||||
std::vector<u32> ids{MIPMAPS_SHADER_ID, GENERATE_SHADER_ID};
|
||||
for (u32 id = PERFORMANCE_SHADER_ID_FIRST; id <= PERFORMANCE_SHADER_ID_LAST; ++id) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
template <typename Map>
|
||||
[[nodiscard]] bool HasPerformanceShaders(const Map& resources) {
|
||||
const std::vector<u32> ids = PerformanceShaderIds();
|
||||
return std::ranges::all_of(ids, [&](u32 id) { return resources.contains(id); });
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 VariantOffset(ShaderVariant variant) {
|
||||
return variant == ShaderVariant::NativeFp16 ? PerformanceShader::NATIVE_FP16_OFFSET
|
||||
: PerformanceShader::NATIVE_FP32_OFFSET;
|
||||
}
|
||||
|
||||
template <typename Map>
|
||||
[[nodiscard]] bool HasNativeVariant(const Map& resources, ShaderVariant variant) {
|
||||
const u32 offset = VariantOffset(variant);
|
||||
return std::ranges::all_of(PerformanceShaderIds(), [&](u32 id) {
|
||||
const auto hit = resources.find(id + offset);
|
||||
return hit != resources.end() && IsSpirvModule(hit->second);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<ShaderVariant> SelectVariant(const ResourceSpans& resources,
|
||||
bool allow_fp16, bool prefer_fp16) {
|
||||
if (prefer_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) {
|
||||
return ShaderVariant::NativeFp16;
|
||||
}
|
||||
if (HasNativeVariant(resources, ShaderVariant::NativeFp32)) {
|
||||
return ShaderVariant::NativeFp32;
|
||||
}
|
||||
if (allow_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) {
|
||||
return ShaderVariant::NativeFp16;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] LosslessStatus TranslateAll(const ResourceSpans& resources,
|
||||
ShaderModules& out_modules,
|
||||
ShaderVariant variant) {
|
||||
const u32 offset = VariantOffset(variant);
|
||||
out_modules.clear();
|
||||
for (const u32 id : PerformanceShaderIds()) {
|
||||
const auto hit = resources.find(id + offset);
|
||||
if (hit == resources.end()) {
|
||||
return LosslessStatus::MissingShaders;
|
||||
}
|
||||
std::vector<u32> adopted = AdoptSpirvModule(hit->second);
|
||||
if (adopted.empty()) {
|
||||
return LosslessStatus::TranslationFailed;
|
||||
}
|
||||
out_modules.emplace(id, std::move(adopted));
|
||||
}
|
||||
return LosslessStatus::Ok;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool WriteShaderCache(const std::filesystem::path& path, const CacheHeader& header,
|
||||
const ShaderModules& modules) {
|
||||
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
if (!file.IsOpen() || file.Write(header) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& [id, words] : modules) {
|
||||
const u32 word_count = static_cast<u32>(words.size());
|
||||
if (file.Write(id) != 1 || file.Write(word_count) != 1 ||
|
||||
file.Write(words) != words.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return file.Flush();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size,
|
||||
u64 source_hash, u32 variant, ShaderModules& out_modules) {
|
||||
if (!Common::FS::Exists(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
CacheHeader header{};
|
||||
if (!file.IsOpen() || file.Read(header) != 1) {
|
||||
return false;
|
||||
}
|
||||
if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION ||
|
||||
header.source_size != source_size || header.source_hash != source_hash ||
|
||||
header.variant != variant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out_modules.clear();
|
||||
for (u32 i = 0; i < header.module_count; ++i) {
|
||||
u32 id{};
|
||||
u32 word_count{};
|
||||
if (file.Read(id) != 1 || file.Read(word_count) != 1 || word_count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u32> words(word_count);
|
||||
if (file.Read(words) != words.size()) {
|
||||
return false;
|
||||
}
|
||||
out_modules.emplace(id, std::move(words));
|
||||
}
|
||||
|
||||
return HasPerformanceShaders(out_modules);
|
||||
}
|
||||
|
||||
[[nodiscard]] LosslessStatus ReadImageFile(const std::filesystem::path& path,
|
||||
std::vector<u8>& out_image) {
|
||||
if (!Common::FS::Exists(path)) {
|
||||
return LosslessStatus::NotInstalled;
|
||||
}
|
||||
|
||||
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
if (!file.IsOpen()) {
|
||||
return LosslessStatus::UnreadableFile;
|
||||
}
|
||||
|
||||
out_image.resize(static_cast<size_t>(file.GetSize()));
|
||||
if (out_image.empty() || file.Read(out_image) != out_image.size()) {
|
||||
return LosslessStatus::UnreadableFile;
|
||||
}
|
||||
return LosslessStatus::Ok;
|
||||
}
|
||||
|
||||
[[nodiscard]] LosslessStatus ParseShaderSpans(std::span<const u8> image,
|
||||
ResourceSpans& out_resources) {
|
||||
const ImageReader reader{image};
|
||||
const std::optional<size_t> pe_offset = FindPeHeader(reader);
|
||||
if (!pe_offset) {
|
||||
return LosslessStatus::NotPortableExecutable;
|
||||
}
|
||||
|
||||
const std::optional<size_t> data_directory =
|
||||
FindDataDirectory(reader, *pe_offset + 4 + COFF_HEADER_SIZE);
|
||||
if (!data_directory) {
|
||||
return LosslessStatus::NotPortableExecutable;
|
||||
}
|
||||
|
||||
std::vector<Section> sections;
|
||||
if (!ReadSections(reader, *pe_offset, sections)) {
|
||||
return LosslessStatus::NotPortableExecutable;
|
||||
}
|
||||
|
||||
u32 resource_rva{};
|
||||
if (!reader.Read(*data_directory + RESOURCE_DATA_DIRECTORY_INDEX * DATA_DIRECTORY_ENTRY_SIZE,
|
||||
resource_rva) ||
|
||||
resource_rva == 0) {
|
||||
return LosslessStatus::MissingShaders;
|
||||
}
|
||||
|
||||
const std::optional<size_t> resource_base = RvaToFileOffset(sections, resource_rva);
|
||||
if (!resource_base) {
|
||||
return LosslessStatus::NotPortableExecutable;
|
||||
}
|
||||
|
||||
out_resources.clear();
|
||||
if (!CollectRcData(reader, sections, *resource_base, out_resources)) {
|
||||
return LosslessStatus::MissingShaders;
|
||||
}
|
||||
|
||||
return HasPerformanceShaders(out_resources) ? LosslessStatus::Ok
|
||||
: LosslessStatus::MissingShaders;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
std::filesystem::path GetLosslessDllPath() {
|
||||
return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_DLL_FILE;
|
||||
}
|
||||
|
||||
std::filesystem::path GetShaderCachePath() {
|
||||
return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_CACHE_FILE;
|
||||
}
|
||||
|
||||
LosslessStatus ReadShaderResources(const std::filesystem::path& path,
|
||||
ShaderResources& out_resources) {
|
||||
std::vector<u8> image;
|
||||
const LosslessStatus read_status = ReadImageFile(path, image);
|
||||
if (read_status != LosslessStatus::Ok) {
|
||||
return read_status;
|
||||
}
|
||||
|
||||
ResourceSpans spans;
|
||||
const LosslessStatus parse_status = ParseShaderSpans(image, spans);
|
||||
if (parse_status != LosslessStatus::Ok) {
|
||||
return parse_status;
|
||||
}
|
||||
|
||||
out_resources.clear();
|
||||
for (const auto& [id, data] : spans) {
|
||||
out_resources.emplace(id, std::vector<u8>{data.begin(), data.end()});
|
||||
}
|
||||
return LosslessStatus::Ok;
|
||||
}
|
||||
|
||||
LosslessStatus ValidateLosslessDll(const std::filesystem::path& path) {
|
||||
std::vector<u8> image;
|
||||
const LosslessStatus read_status = ReadImageFile(path, image);
|
||||
if (read_status != LosslessStatus::Ok) {
|
||||
return read_status;
|
||||
}
|
||||
|
||||
ResourceSpans spans;
|
||||
return ParseShaderSpans(image, spans);
|
||||
}
|
||||
|
||||
LosslessStatus GetInstalledLosslessStatus() {
|
||||
return ValidateLosslessDll(GetLosslessDllPath());
|
||||
}
|
||||
|
||||
LosslessStatus LoadShaderModules(ShaderModules& out_modules, bool allow_fp16, bool prefer_fp16) {
|
||||
std::vector<u8> image;
|
||||
const LosslessStatus read_status = ReadImageFile(GetLosslessDllPath(), image);
|
||||
if (read_status != LosslessStatus::Ok) {
|
||||
return read_status;
|
||||
}
|
||||
|
||||
const u64 source_size = image.size();
|
||||
const u64 source_hash =
|
||||
Common::CityHash64(reinterpret_cast<const char*>(image.data()), image.size());
|
||||
const std::filesystem::path cache_path = GetShaderCachePath();
|
||||
|
||||
ResourceSpans spans;
|
||||
const LosslessStatus parse_status = ParseShaderSpans(image, spans);
|
||||
if (parse_status != LosslessStatus::Ok) {
|
||||
return parse_status;
|
||||
}
|
||||
|
||||
const std::optional<ShaderVariant> variant = SelectVariant(spans, allow_fp16, prefer_fp16);
|
||||
if (!variant) {
|
||||
return LosslessStatus::MissingShaders;
|
||||
}
|
||||
|
||||
if (ReadShaderCache(cache_path, source_size, source_hash, static_cast<u32>(*variant),
|
||||
out_modules)) {
|
||||
return LosslessStatus::Ok;
|
||||
}
|
||||
|
||||
const LosslessStatus translate_status = TranslateAll(spans, out_modules, *variant);
|
||||
if (translate_status != LosslessStatus::Ok) {
|
||||
return translate_status;
|
||||
}
|
||||
|
||||
const CacheHeader header{
|
||||
.magic = CACHE_MAGIC,
|
||||
.version = CACHE_VERSION,
|
||||
.source_size = source_size,
|
||||
.source_hash = source_hash,
|
||||
.module_count = static_cast<u32>(out_modules.size()),
|
||||
.variant = static_cast<u32>(*variant),
|
||||
};
|
||||
if (!WriteShaderCache(cache_path, header, out_modules)) {
|
||||
void(Common::FS::RemoveFile(cache_path));
|
||||
return LosslessStatus::CacheUnusable;
|
||||
}
|
||||
|
||||
return LosslessStatus::Ok;
|
||||
}
|
||||
|
||||
LosslessStatus BuildShaderCache() {
|
||||
ShaderModules modules;
|
||||
return LoadShaderModules(modules, true);
|
||||
}
|
||||
|
||||
bool RemoveInstalledLosslessDll() {
|
||||
const std::filesystem::path cache_path = GetShaderCachePath();
|
||||
if (Common::FS::Exists(cache_path)) {
|
||||
void(Common::FS::RemoveFile(cache_path));
|
||||
}
|
||||
|
||||
const std::filesystem::path path = GetLosslessDllPath();
|
||||
if (!Common::FS::Exists(path)) {
|
||||
return true;
|
||||
}
|
||||
return Common::FS::RemoveFile(path);
|
||||
}
|
||||
|
||||
} // namespace VideoCore::FrameGen
|
||||
@@ -0,0 +1,67 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace VideoCore::FrameGen {
|
||||
|
||||
enum class LosslessStatus : u32 {
|
||||
Ok,
|
||||
NotInstalled,
|
||||
UnreadableFile,
|
||||
NotPortableExecutable,
|
||||
MissingShaders,
|
||||
TranslationFailed,
|
||||
CacheUnusable,
|
||||
};
|
||||
|
||||
using ShaderResources = std::map<u32, std::vector<u8>>;
|
||||
using ShaderModules = std::map<u32, std::vector<u32>>;
|
||||
|
||||
enum class ShaderVariant : u32 {
|
||||
NativeFp32 = 1,
|
||||
NativeFp16 = 2,
|
||||
};
|
||||
|
||||
namespace PerformanceShader {
|
||||
constexpr u32 MIPMAPS = 255;
|
||||
constexpr u32 GENERATE = 256;
|
||||
constexpr std::array<u32, 4> ALPHA{290, 291, 292, 293};
|
||||
constexpr std::array<u32, 5> BETA{298, 299, 300, 301, 302};
|
||||
constexpr std::array<u32, 5> GAMMA{280, 282, 283, 284, 285};
|
||||
constexpr std::array<u32, 10> DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296, 297};
|
||||
|
||||
constexpr u32 NATIVE_FP16_OFFSET = 49;
|
||||
constexpr u32 NATIVE_FP32_OFFSET = 98;
|
||||
} // namespace PerformanceShader
|
||||
|
||||
[[nodiscard]] std::filesystem::path GetLosslessDllPath();
|
||||
|
||||
[[nodiscard]] std::filesystem::path GetShaderCachePath();
|
||||
|
||||
[[nodiscard]] LosslessStatus ReadShaderResources(const std::filesystem::path& path,
|
||||
ShaderResources& out_resources);
|
||||
|
||||
[[nodiscard]] LosslessStatus ValidateLosslessDll(const std::filesystem::path& path);
|
||||
|
||||
[[nodiscard]] LosslessStatus GetInstalledLosslessStatus();
|
||||
|
||||
[[nodiscard]] LosslessStatus BuildShaderCache();
|
||||
|
||||
[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules,
|
||||
bool allow_fp16 = false,
|
||||
bool prefer_fp16 = false);
|
||||
|
||||
bool RemoveInstalledLosslessDll();
|
||||
|
||||
} // namespace VideoCore::FrameGen
|
||||
@@ -0,0 +1,93 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
|
||||
#include "video_core/frame_gen/lsfg_translate.h"
|
||||
|
||||
namespace VideoCore::FrameGen {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 SPIRV_MAGIC = 0x07230203;
|
||||
constexpr u32 SPIRV_WORD_COUNT_SHIFT = 16;
|
||||
constexpr u32 SPIRV_OPCODE_MASK = 0xffff;
|
||||
constexpr u32 SPIRV_OP_FUNCTION = 54;
|
||||
constexpr u32 SPIRV_OP_DECORATE = 71;
|
||||
constexpr u32 SPIRV_DECORATION_BINDING = 33;
|
||||
constexpr u32 SPIRV_DECORATION_DESCRIPTOR_SET = 34;
|
||||
|
||||
constexpr u32 DECORATION_LITERAL_WORD = 3;
|
||||
constexpr size_t SPIRV_HEADER_WORDS = 5;
|
||||
|
||||
void RenumberBindingsInOrder(std::vector<u32>& words) {
|
||||
struct Slot {
|
||||
u32 set;
|
||||
u32 binding;
|
||||
size_t literal_offset;
|
||||
};
|
||||
|
||||
std::map<u32, u32> sets;
|
||||
std::vector<Slot> slots;
|
||||
|
||||
size_t offset = SPIRV_HEADER_WORDS;
|
||||
while (offset + 1 <= words.size()) {
|
||||
const u32 length = words[offset] >> SPIRV_WORD_COUNT_SHIFT;
|
||||
const u32 opcode = words[offset] & SPIRV_OPCODE_MASK;
|
||||
if (length == 0 || offset + length > words.size()) {
|
||||
return;
|
||||
}
|
||||
if (opcode == SPIRV_OP_FUNCTION) {
|
||||
break;
|
||||
}
|
||||
if (opcode == SPIRV_OP_DECORATE && length >= 4) {
|
||||
if (words[offset + 2] == SPIRV_DECORATION_DESCRIPTOR_SET) {
|
||||
sets[words[offset + 1]] = words[offset + 3];
|
||||
} else if (words[offset + 2] == SPIRV_DECORATION_BINDING) {
|
||||
slots.push_back(Slot{0, words[offset + 3], offset + DECORATION_LITERAL_WORD});
|
||||
}
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
|
||||
for (Slot& slot : slots) {
|
||||
const auto hit = sets.find(words[slot.literal_offset - 2]);
|
||||
slot.set = hit == sets.end() ? 0 : hit->second;
|
||||
}
|
||||
|
||||
std::ranges::stable_sort(slots, [](const Slot& lhs, const Slot& rhs) {
|
||||
return std::tie(lhs.set, lhs.binding) < std::tie(rhs.set, rhs.binding);
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < slots.size(); ++i) {
|
||||
words[slots[i].literal_offset] = static_cast<u32>(i);
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
bool IsSpirvModule(std::span<const u8> blob) {
|
||||
if (blob.size() < SPIRV_HEADER_WORDS * sizeof(u32) || blob.size() % sizeof(u32) != 0) {
|
||||
return false;
|
||||
}
|
||||
u32 magic{};
|
||||
std::memcpy(&magic, blob.data(), sizeof(magic));
|
||||
return magic == SPIRV_MAGIC;
|
||||
}
|
||||
|
||||
std::vector<u32> AdoptSpirvModule(std::span<const u8> blob) {
|
||||
if (!IsSpirvModule(blob)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<u32> words(blob.size() / sizeof(u32));
|
||||
std::memcpy(words.data(), blob.data(), blob.size());
|
||||
|
||||
RenumberBindingsInOrder(words);
|
||||
return words;
|
||||
}
|
||||
|
||||
} // namespace VideoCore::FrameGen
|
||||
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace VideoCore::FrameGen {
|
||||
|
||||
[[nodiscard]] bool IsSpirvModule(std::span<const u8> blob);
|
||||
|
||||
[[nodiscard]] std::vector<u32> AdoptSpirvModule(std::span<const u8> blob);
|
||||
|
||||
} // namespace VideoCore::FrameGen
|
||||
@@ -0,0 +1,361 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_vulkan/present/frame_gen.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/renderer_vulkan/vk_present_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t COLOR_CHANNELS = 4;
|
||||
constexpr u64 LSFG_REQUIRED_FRAMES = 2;
|
||||
constexpr u32 LSFG_RECURRENCE_FRAMES = 2;
|
||||
|
||||
[[nodiscard]] f32 ManualFlowScale() {
|
||||
return static_cast<f32>(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f;
|
||||
}
|
||||
|
||||
[[nodiscard]] f32 ConfiguredFlowScale(VkExtent2D guest_extent, VkExtent2D presented_extent) {
|
||||
if (!Settings::values.frame_gen_flow_scale_auto.GetValue()) {
|
||||
return ManualFlowScale();
|
||||
}
|
||||
if (guest_extent.width == 0 || presented_extent.width == 0) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const f32 rendered_width = static_cast<f32>(guest_extent.width) *
|
||||
Settings::values.resolution_info.up_factor;
|
||||
const f32 ratio = rendered_width / static_cast<f32>(presented_extent.width);
|
||||
|
||||
constexpr f32 FLOW_SCALE_STEPS = 20.0f;
|
||||
const f32 stepped = std::ceil(ratio * FLOW_SCALE_STEPS) / FLOW_SCALE_STEPS;
|
||||
return std::clamp(stepped, 0.25f, 1.0f);
|
||||
}
|
||||
|
||||
bool IsBlueFirst(VkFormat format) {
|
||||
return format == VK_FORMAT_B8G8R8A8_UNORM || format == VK_FORMAT_B8G8R8A8_SRGB;
|
||||
}
|
||||
|
||||
VkDeviceSize BytesPerTexel(VkFormat format) {
|
||||
switch (format) {
|
||||
case VK_FORMAT_R8_UNORM:
|
||||
return 1;
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT:
|
||||
return 8;
|
||||
default:
|
||||
return COLOR_CHANNELS;
|
||||
}
|
||||
}
|
||||
|
||||
void WritePortablePixmap(const std::filesystem::path& path, const std::string& magic,
|
||||
VkExtent2D extent, std::span<const u8> pixels) {
|
||||
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
if (!file.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string header = magic + "\n" + std::to_string(extent.width) + " " +
|
||||
std::to_string(extent.height) + "\n255\n";
|
||||
if (file.Write(header) != header.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void(file.Write(pixels));
|
||||
void(file.Flush());
|
||||
}
|
||||
|
||||
void WriteGrayscalePgm(const std::filesystem::path& path, VkExtent2D extent,
|
||||
std::span<const u8> pixels) {
|
||||
const size_t expected = static_cast<size_t>(extent.width) * extent.height;
|
||||
WritePortablePixmap(path, "P5", extent, pixels.subspan(0, std::min(expected, pixels.size())));
|
||||
}
|
||||
|
||||
void WriteRaw(const std::filesystem::path& path, std::span<const u8> pixels) {
|
||||
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
if (!file.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
void(file.Write(pixels));
|
||||
void(file.Flush());
|
||||
}
|
||||
|
||||
void WriteColorPpm(const std::filesystem::path& path, VkExtent2D extent,
|
||||
std::span<const u8> pixels, bool blue_first) {
|
||||
const size_t pixel_count = static_cast<size_t>(extent.width) * extent.height;
|
||||
if (pixels.size() < pixel_count * COLOR_CHANNELS) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u8> rgb(pixel_count * 3);
|
||||
for (size_t i = 0; i < pixel_count; ++i) {
|
||||
const u8 first = pixels[i * COLOR_CHANNELS];
|
||||
const u8 green = pixels[i * COLOR_CHANNELS + 1];
|
||||
const u8 third = pixels[i * COLOR_CHANNELS + 2];
|
||||
rgb[i * 3] = blue_first ? third : first;
|
||||
rgb[i * 3 + 1] = green;
|
||||
rgb[i * 3 + 2] = blue_first ? first : third;
|
||||
}
|
||||
|
||||
WritePortablePixmap(path, "P6", extent, rgb);
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier MakeTransitionBarrier(VkImage image, VkAccessFlags src_access,
|
||||
VkAccessFlags dst_access, VkImageLayout old_layout,
|
||||
VkImageLayout new_layout) {
|
||||
return VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = src_access,
|
||||
.dstAccessMask = dst_access,
|
||||
.oldLayout = old_layout,
|
||||
.newLayout = new_layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
VkImageCopy MakeCopyRegion(VkExtent2D extent) {
|
||||
return VkImageCopy{
|
||||
.srcSubresource{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.srcOffset = {},
|
||||
.dstSubresource{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.mipLevel = 0,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
.dstOffset = {},
|
||||
.extent = {.width = extent.width, .height = extent.height, .depth = 1},
|
||||
};
|
||||
}
|
||||
|
||||
void CopyPresentedFrame(vk::CommandBuffer cmdbuf, VkImage source, LsfgImage& destination,
|
||||
VkExtent2D extent) {
|
||||
const auto make_barrier = MakeTransitionBarrier;
|
||||
|
||||
const std::array before{
|
||||
make_barrier(source, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL),
|
||||
make_barrier(destination.Handle(), VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
destination.Layout(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL),
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, before);
|
||||
|
||||
cmdbuf.CopyImage(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.Handle(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeCopyRegion(extent));
|
||||
|
||||
const std::array after{
|
||||
make_barrier(source, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
|
||||
make_barrier(destination.Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL),
|
||||
};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, {}, {}, after);
|
||||
|
||||
destination.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
|
||||
: memory_allocator{memory_allocator_}, scheduler{scheduler_} {}
|
||||
|
||||
FrameGen::~FrameGen() = default;
|
||||
|
||||
void FrameGen::Process(const Device& device, Frame* frame, VkFormat format,
|
||||
VkExtent2D guest_extent) {
|
||||
generated = false;
|
||||
|
||||
if (unavailable || !Settings::values.frame_gen.GetValue()) {
|
||||
if (chain) {
|
||||
scheduler.Finish();
|
||||
chain.reset();
|
||||
}
|
||||
warm_streak = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frame->storage_view) {
|
||||
unavailable = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shaders) {
|
||||
shaders.emplace(device);
|
||||
if (!shaders->IsValid()) {
|
||||
unavailable = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
peak_guest_extent.width = std::max(peak_guest_extent.width, guest_extent.width);
|
||||
peak_guest_extent.height = std::max(peak_guest_extent.height, guest_extent.height);
|
||||
|
||||
const VkExtent2D extent{.width = frame->width, .height = frame->height};
|
||||
const f32 flow_scale = ConfiguredFlowScale(peak_guest_extent, extent);
|
||||
if (!chain || built_extent.width != extent.width || built_extent.height != extent.height ||
|
||||
built_format != format || built_flow_scale != flow_scale) {
|
||||
Rebuild(device, extent, format, flow_scale);
|
||||
}
|
||||
|
||||
const u64 count = frame_count++;
|
||||
last_count = count;
|
||||
last_generations = plan.generations;
|
||||
|
||||
const bool warm = plan.warm && count + 1 >= LSFG_REQUIRED_FRAMES;
|
||||
warm_streak = warm ? warm_streak + 1 : 0;
|
||||
generated = warm && warm_streak >= LSFG_RECURRENCE_FRAMES && plan.generations > 0;
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, source = *frame->image, extent, count,
|
||||
dispatch = warm](vk::CommandBuffer cmdbuf) {
|
||||
CopyPresentedFrame(cmdbuf, source, chain->Input(count), extent);
|
||||
if (dispatch) {
|
||||
chain->DispatchShared(cmdbuf, count);
|
||||
}
|
||||
});
|
||||
|
||||
const bool dump_requested = generated && Settings::values.frame_gen_dump_flow.GetValue();
|
||||
if (!dump_requested) {
|
||||
dumped = false;
|
||||
} else if (!dumped) {
|
||||
DumpDebugImages(count);
|
||||
dumped = true;
|
||||
}
|
||||
}
|
||||
|
||||
size_t FrameGen::WantedGenerations(size_t capacity) {
|
||||
if (unavailable) {
|
||||
plan = {};
|
||||
return 0;
|
||||
}
|
||||
plan = pacer.Plan(capacity);
|
||||
return plan.generations;
|
||||
}
|
||||
|
||||
size_t FrameGen::GeneratedFrameCount() const {
|
||||
return generated ? last_generations : 0;
|
||||
}
|
||||
|
||||
void FrameGen::GenerateInto(const Device& device, Frame* destination, size_t generation) {
|
||||
chain->SetTarget(device, last_generations, generation, destination->index,
|
||||
*destination->storage_view);
|
||||
|
||||
const VkExtent2D extent{.width = destination->width, .height = destination->height};
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, count = last_count, generation_count = last_generations, generation,
|
||||
target = destination->index, image = *destination->image,
|
||||
extent](vk::CommandBuffer cmdbuf) {
|
||||
chain->DispatchGeneration(cmdbuf, count, generation_count, generation, target, image,
|
||||
extent);
|
||||
});
|
||||
}
|
||||
|
||||
void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale) {
|
||||
scheduler.Finish();
|
||||
chain.reset();
|
||||
|
||||
built_flow_scale = flow_scale;
|
||||
|
||||
chain.emplace(device, memory_allocator, *shaders, extent, format, built_flow_scale);
|
||||
built_extent = extent;
|
||||
built_format = format;
|
||||
frame_count = 0;
|
||||
warm_streak = 0;
|
||||
generated = false;
|
||||
}
|
||||
|
||||
void FrameGen::DumpDebugImages(u64 count) {
|
||||
const std::filesystem::path directory =
|
||||
Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / "debug";
|
||||
if (!Common::FS::CreateDirs(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dump = [&](const std::string& name, LsfgImage& image) {
|
||||
const VkExtent2D extent = image.Extent();
|
||||
const VkFormat format = image.Format();
|
||||
const VkDeviceSize texel_size = BytesPerTexel(format);
|
||||
const VkDeviceSize size =
|
||||
static_cast<VkDeviceSize>(extent.width) * extent.height * texel_size;
|
||||
|
||||
vk::Buffer buffer = CreateWrappedBuffer(memory_allocator, size, MemoryUsage::Download);
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record(
|
||||
[handle = image.Handle(), dst = *buffer, extent](vk::CommandBuffer cmdbuf) {
|
||||
DownloadColorImage(
|
||||
cmdbuf, handle, dst,
|
||||
VkExtent3D{.width = extent.width, .height = extent.height, .depth = 1});
|
||||
});
|
||||
scheduler.Finish();
|
||||
|
||||
buffer.Invalidate();
|
||||
const std::span<u8> mapped = buffer.Mapped();
|
||||
|
||||
if (format == LSFG_FLOW_FORMAT) {
|
||||
WriteGrayscalePgm(directory / (name + ".pgm"), extent, mapped);
|
||||
} else if (texel_size == COLOR_CHANNELS) {
|
||||
WriteColorPpm(directory / (name + ".ppm"), extent, mapped, IsBlueFirst(format));
|
||||
} else {
|
||||
WriteRaw(directory / (name + "_" + std::to_string(extent.width) + "x" +
|
||||
std::to_string(extent.height) + ".f16"),
|
||||
mapped.subspan(0, std::min<size_t>(size, mapped.size())));
|
||||
}
|
||||
};
|
||||
|
||||
dump("in0", chain->Input(0));
|
||||
dump("in1", chain->Input(1));
|
||||
|
||||
for (size_t level = 0; level < LSFG_MIP_LEVELS; ++level) {
|
||||
dump("flow_mip" + std::to_string(level), chain->FlowLevel(level));
|
||||
}
|
||||
for (size_t index = 0; index < 2; ++index) {
|
||||
dump("alpha0_" + std::to_string(index), chain->AlphaOutput(0, count, index));
|
||||
dump("alpha6_" + std::to_string(index), chain->AlphaOutput(LSFG_MIP_LEVELS - 1, count,
|
||||
index));
|
||||
}
|
||||
for (size_t level = 0; level < LSFG_BETA_OUTPUTS; ++level) {
|
||||
dump("beta_" + std::to_string(level), chain->BetaOutput(level));
|
||||
}
|
||||
|
||||
dump("gamma0", chain->GammaOutput(0));
|
||||
dump("gamma6", chain->GammaOutput(LSFG_MIP_LEVELS - 1));
|
||||
dump("delta2_out1", chain->DeltaOutput1(LSFG_DELTA_INSTANCES - 1));
|
||||
dump("delta2_out2", chain->DeltaOutput2(LSFG_DELTA_INSTANCES - 1));
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,57 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/frame_gen_pacer.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_chain.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class Scheduler;
|
||||
struct Frame;
|
||||
|
||||
class FrameGen {
|
||||
public:
|
||||
explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler);
|
||||
~FrameGen();
|
||||
|
||||
void Process(const Device& device, Frame* frame, VkFormat format, VkExtent2D guest_extent);
|
||||
|
||||
[[nodiscard]] size_t WantedGenerations(size_t capacity);
|
||||
|
||||
[[nodiscard]] size_t GeneratedFrameCount() const;
|
||||
|
||||
void GenerateInto(const Device& device, Frame* destination, size_t generation);
|
||||
|
||||
private:
|
||||
void Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale);
|
||||
void DumpDebugImages(u64 count);
|
||||
|
||||
MemoryAllocator& memory_allocator;
|
||||
Scheduler& scheduler;
|
||||
|
||||
std::optional<LsfgShaders> shaders;
|
||||
std::optional<LsfgChain> chain;
|
||||
FrameGenPacer pacer;
|
||||
FrameGenPlan plan{};
|
||||
VkExtent2D peak_guest_extent{};
|
||||
VkExtent2D built_extent{};
|
||||
VkFormat built_format{VK_FORMAT_UNDEFINED};
|
||||
f32 built_flow_scale{};
|
||||
u64 frame_count{};
|
||||
u64 last_count{};
|
||||
size_t last_generations{};
|
||||
u32 warm_streak{};
|
||||
bool generated{};
|
||||
bool unavailable{};
|
||||
bool dumped{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,240 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_vulkan/present/frame_gen_pacer.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
constexpr f32 INTERVAL_SMOOTHING = 0.25f;
|
||||
constexpr f32 MINIMUM_BASE_RATE = 10.0f;
|
||||
constexpr f32 BURST_CADENCE_RATIO = 3.0f;
|
||||
constexpr f32 BURST_TARGET_RATIO = 2.0f;
|
||||
constexpr f32 PROBE_THROUGHPUT_TOLERANCE = 0.95f;
|
||||
constexpr f32 PROBE_BASE_COLLAPSE_RATIO = 0.70f;
|
||||
constexpr f32 PROBE_MARGINAL_GAIN = 1.15f;
|
||||
constexpr f32 TARGET_SATISFIED_RATIO = 0.95f;
|
||||
constexpr f32 UNLOADED_BASE_RETENTION = 0.75f;
|
||||
constexpr f32 CREDIT_EPSILON = 1.0e-4f;
|
||||
constexpr u32 MAX_PROBE_FAILURES = 4;
|
||||
|
||||
constexpr auto STABILIZATION_DURATION = std::chrono::seconds(1);
|
||||
constexpr auto PROBE_DURATION = std::chrono::seconds(1);
|
||||
constexpr auto DEFICIT_DURATION = std::chrono::seconds(1);
|
||||
constexpr auto PROBE_STEP_DELAY = std::chrono::milliseconds(250);
|
||||
|
||||
[[nodiscard]] Clock::duration ProbeBackoff(u32 failures) {
|
||||
switch (failures) {
|
||||
case 1:
|
||||
return std::chrono::seconds(5);
|
||||
case 2:
|
||||
return std::chrono::seconds(15);
|
||||
case 3:
|
||||
return std::chrono::seconds(30);
|
||||
default:
|
||||
return std::chrono::seconds(60);
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
FrameGenPlan FrameGenPacer::Plan(size_t capacity) {
|
||||
const size_t ceiling = std::min(capacity, Settings::FrameGenMaxGenerations());
|
||||
if (ceiling == 0) {
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
const Clock::time_point now = Clock::now();
|
||||
const size_t previous_generations = std::exchange(issued_generations, 0);
|
||||
if (!last_frame) {
|
||||
last_frame = now;
|
||||
return {};
|
||||
}
|
||||
|
||||
const Clock::duration interval = now - *last_frame;
|
||||
const f32 interval_seconds = std::chrono::duration<f32>(interval).count();
|
||||
last_frame = now;
|
||||
|
||||
if (interval_seconds <= 0.0f) {
|
||||
Stabilize(now);
|
||||
return {};
|
||||
}
|
||||
|
||||
const f32 target_rate = static_cast<f32>(Settings::values.frame_gen_target_rate.GetValue());
|
||||
|
||||
if (smoothed_interval > 0.0f) {
|
||||
f32 burst_threshold = BURST_CADENCE_RATIO / smoothed_interval;
|
||||
if (target_rate > 0.0f) {
|
||||
burst_threshold = std::max(burst_threshold, target_rate * BURST_TARGET_RATIO);
|
||||
}
|
||||
if (1.0f / interval_seconds > burst_threshold) {
|
||||
DeferEvaluations(interval);
|
||||
output_credit = 0.0f;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (interval_seconds > 1.0f / MINIMUM_BASE_RATE) {
|
||||
Stabilize(now);
|
||||
return {};
|
||||
}
|
||||
|
||||
smoothed_interval = smoothed_interval > 0.0f
|
||||
? smoothed_interval +
|
||||
(interval_seconds - smoothed_interval) * INTERVAL_SMOOTHING
|
||||
: interval_seconds;
|
||||
|
||||
if (previous_generations == 0) {
|
||||
const f32 measured = 1.0f / smoothed_interval;
|
||||
unloaded_base_rate =
|
||||
unloaded_base_rate > 0.0f
|
||||
? unloaded_base_rate + (measured - unloaded_base_rate) * INTERVAL_SMOOTHING
|
||||
: measured;
|
||||
}
|
||||
|
||||
if (stable_until) {
|
||||
if (now < *stable_until) {
|
||||
return {};
|
||||
}
|
||||
stable_until.reset();
|
||||
}
|
||||
|
||||
if (target_rate == 0.0f) {
|
||||
limit = std::min(Settings::FrameGenGenerations(), ceiling);
|
||||
output_credit = 0.0f;
|
||||
issued_generations = limit;
|
||||
return {.generations = limit, .warm = limit > 0};
|
||||
}
|
||||
|
||||
UpdateLimit(now, 1.0f / smoothed_interval, target_rate, ceiling);
|
||||
|
||||
const size_t allowed = std::min(limit, ceiling);
|
||||
const f32 desired_outputs = smoothed_interval * target_rate;
|
||||
if (allowed == 0 || desired_outputs <= 1.0f) {
|
||||
output_credit = 0.0f;
|
||||
return {};
|
||||
}
|
||||
|
||||
output_credit += desired_outputs;
|
||||
const size_t outputs =
|
||||
std::max<size_t>(1, static_cast<size_t>(std::floor(output_credit + CREDIT_EPSILON)));
|
||||
const size_t generations = std::min(outputs - 1, allowed);
|
||||
|
||||
output_credit -= static_cast<f32>(generations + 1);
|
||||
if (output_credit < 0.0f) {
|
||||
output_credit = 0.0f;
|
||||
} else if (generations == allowed && output_credit >= 1.0f) {
|
||||
output_credit = std::fmod(output_credit, 1.0f);
|
||||
}
|
||||
|
||||
issued_generations = generations;
|
||||
return {.generations = generations, .warm = true};
|
||||
}
|
||||
|
||||
void FrameGenPacer::UpdateLimit(Clock::time_point now, f32 base_rate, f32 target_rate,
|
||||
size_t ceiling) {
|
||||
limit = std::min(limit, ceiling);
|
||||
|
||||
if (probe_until) {
|
||||
if (now < *probe_until) {
|
||||
return;
|
||||
}
|
||||
probe_until.reset();
|
||||
output_credit = 0.0f;
|
||||
|
||||
const f32 previous_output =
|
||||
std::min(target_rate, probe_base_rate * static_cast<f32>(probe_previous_limit + 1));
|
||||
const f32 current_output =
|
||||
std::min(target_rate, base_rate * static_cast<f32>(limit + 1));
|
||||
|
||||
const bool throughput_regressed =
|
||||
current_output < previous_output * PROBE_THROUGHPUT_TOLERANCE;
|
||||
const bool collapsed_for_marginal_gain =
|
||||
base_rate < probe_base_rate * PROBE_BASE_COLLAPSE_RATIO &&
|
||||
current_output < previous_output * PROBE_MARGINAL_GAIN;
|
||||
const bool emulation_slowed = unloaded_base_rate > 0.0f &&
|
||||
base_rate < unloaded_base_rate * UNLOADED_BASE_RETENTION;
|
||||
|
||||
if (throughput_regressed || collapsed_for_marginal_gain || emulation_slowed) {
|
||||
limit = probe_previous_limit;
|
||||
probe_failures = std::min(probe_failures + 1, MAX_PROBE_FAILURES);
|
||||
next_probe = now + ProbeBackoff(probe_failures);
|
||||
deficit_since.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
probe_failures = 0;
|
||||
next_probe = now + PROBE_STEP_DELAY;
|
||||
}
|
||||
|
||||
if (base_rate * static_cast<f32>(limit + 1) >= target_rate * TARGET_SATISFIED_RATIO ||
|
||||
limit >= ceiling) {
|
||||
deficit_since.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!deficit_since) {
|
||||
deficit_since = now;
|
||||
return;
|
||||
}
|
||||
if (now - *deficit_since < DEFICIT_DURATION) {
|
||||
return;
|
||||
}
|
||||
if (next_probe && now < *next_probe) {
|
||||
return;
|
||||
}
|
||||
|
||||
probe_previous_limit = limit;
|
||||
probe_base_rate = base_rate;
|
||||
++limit;
|
||||
probe_until = now + PROBE_DURATION;
|
||||
deficit_since.reset();
|
||||
output_credit = 0.0f;
|
||||
}
|
||||
|
||||
void FrameGenPacer::DeferEvaluations(Clock::duration amount) {
|
||||
const auto defer = [amount](std::optional<Clock::time_point>& deadline) {
|
||||
if (deadline) {
|
||||
*deadline += amount;
|
||||
}
|
||||
};
|
||||
defer(stable_until);
|
||||
defer(probe_until);
|
||||
defer(next_probe);
|
||||
deficit_since.reset();
|
||||
}
|
||||
|
||||
void FrameGenPacer::Stabilize(Clock::time_point now) {
|
||||
stable_until = now + STABILIZATION_DURATION;
|
||||
probe_until.reset();
|
||||
deficit_since.reset();
|
||||
smoothed_interval = 0.0f;
|
||||
output_credit = 0.0f;
|
||||
}
|
||||
|
||||
void FrameGenPacer::Reset() {
|
||||
last_frame.reset();
|
||||
stable_until.reset();
|
||||
probe_until.reset();
|
||||
next_probe.reset();
|
||||
deficit_since.reset();
|
||||
smoothed_interval = 0.0f;
|
||||
output_credit = 0.0f;
|
||||
probe_base_rate = 0.0f;
|
||||
unloaded_base_rate = 0.0f;
|
||||
issued_generations = 0;
|
||||
probe_previous_limit = 0;
|
||||
limit = 0;
|
||||
probe_failures = 0;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
struct FrameGenPlan {
|
||||
size_t generations{};
|
||||
bool warm{};
|
||||
};
|
||||
|
||||
class FrameGenPacer {
|
||||
public:
|
||||
[[nodiscard]] FrameGenPlan Plan(size_t capacity);
|
||||
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
void Stabilize(Clock::time_point now);
|
||||
void DeferEvaluations(Clock::duration amount);
|
||||
void UpdateLimit(Clock::time_point now, f32 base_rate, f32 target_rate, size_t ceiling);
|
||||
|
||||
std::optional<Clock::time_point> last_frame;
|
||||
std::optional<Clock::time_point> stable_until;
|
||||
std::optional<Clock::time_point> probe_until;
|
||||
std::optional<Clock::time_point> next_probe;
|
||||
std::optional<Clock::time_point> deficit_since;
|
||||
f32 smoothed_interval{};
|
||||
f32 output_credit{};
|
||||
f32 probe_base_rate{};
|
||||
f32 unloaded_base_rate{};
|
||||
size_t issued_generations{};
|
||||
size_t probe_previous_limit{};
|
||||
size_t limit{};
|
||||
u32 probe_failures{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,140 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_alpha.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 3;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkExtent2D HalveExtent(VkExtent2D extent) {
|
||||
return VkExtent2D{
|
||||
.width = (extent.width + 1) >> 1,
|
||||
.height = (extent.height + 1) >> 1,
|
||||
};
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgAlphaPasses::LsfgAlphaPasses(const Device& device, const LsfgShaders& shaders) {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
passes[0] = LsfgPass(device, shaders, ALPHA[0],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[1] = LsfgPass(device, shaders, ALPHA[1],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[2] = LsfgPass(device, shaders, ALPHA[2],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[3] = LsfgPass(device, shaders, ALPHA[3],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
}
|
||||
|
||||
LsfgAlpha::LsfgAlpha(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgAlphaPasses& passes_, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImage& input_)
|
||||
: passes{&passes_}, input{&input_} {
|
||||
const VkExtent2D half_extent = HalveExtent(input->Extent());
|
||||
const VkExtent2D quarter_extent = HalveExtent(half_extent);
|
||||
|
||||
temp1 = LsfgImage(device, memory_allocator, half_extent);
|
||||
temp2 = LsfgImage(device, memory_allocator, half_extent);
|
||||
for (size_t i = 0; i < temp3.size(); ++i) {
|
||||
temp3[i] = LsfgImage(device, memory_allocator, quarter_extent);
|
||||
for (size_t j = 0; j < LSFG_HISTORY_SLOTS; ++j) {
|
||||
out_images[j][i] = LsfgImage(device, memory_allocator, quarter_extent);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) {
|
||||
layouts.push_back(passes->Get(i).SetLayout());
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes->Get(3).SetLayout());
|
||||
}
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) {
|
||||
descriptor_sets[i] = owned_sets[i];
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
last_descriptor_sets[i] = owned_sets[LSFG_ALPHA_STAGES - 1 + i];
|
||||
}
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
|
||||
LsfgDescriptorWriter(descriptor_sets[0])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(*input)
|
||||
.AddStorageImage(temp1)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[1])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp1)
|
||||
.AddStorageImage(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[2])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp2)
|
||||
.AddStorageImages(temp3)
|
||||
.Build(device);
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
LsfgDescriptorWriter(last_descriptor_sets[i])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp3)
|
||||
.AddStorageImages(out_images[i])
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgAlpha::PushBarriers(LsfgBarriers& barriers, u64 frame_count, size_t stage) {
|
||||
switch (stage) {
|
||||
case 0:
|
||||
barriers.WriteToRead(*input).ReadToWrite(temp1);
|
||||
break;
|
||||
case 1:
|
||||
barriers.WriteToRead(temp1).ReadToWrite(temp2);
|
||||
break;
|
||||
case 2:
|
||||
barriers.WriteToRead(temp2).ReadToWriteAll(temp3);
|
||||
break;
|
||||
default:
|
||||
barriers.WriteToReadAll(temp3).ReadToWriteAll(out_images[frame_count % LSFG_HISTORY_SLOTS]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgAlpha::DispatchStage(vk::CommandBuffer cmdbuf, u64 frame_count, size_t stage) {
|
||||
const VkExtent2D extent = stage < 2 ? temp1.Extent() : temp3[0].Extent();
|
||||
const VkDescriptorSet set = stage < LSFG_ALPHA_STAGES - 1
|
||||
? descriptor_sets[stage]
|
||||
: last_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS];
|
||||
|
||||
passes->Get(stage).BindSet(cmdbuf, set);
|
||||
cmdbuf.Dispatch(GroupCount(extent.width), GroupCount(extent.height), 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,62 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_ALPHA_STAGES = 4;
|
||||
|
||||
class LsfgAlphaPasses {
|
||||
public:
|
||||
LsfgAlphaPasses() = default;
|
||||
LsfgAlphaPasses(const Device& device, const LsfgShaders& shaders);
|
||||
|
||||
[[nodiscard]] const LsfgPass& Get(size_t stage) const {
|
||||
return passes[stage];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<LsfgPass, LSFG_ALPHA_STAGES> passes;
|
||||
};
|
||||
|
||||
class LsfgAlpha {
|
||||
public:
|
||||
LsfgAlpha() = default;
|
||||
LsfgAlpha(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgAlphaPasses& passes_, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImage& input);
|
||||
|
||||
void PushBarriers(LsfgBarriers& barriers, u64 frame_count, size_t stage);
|
||||
void DispatchStage(vk::CommandBuffer cmdbuf, u64 frame_count, size_t stage);
|
||||
|
||||
[[nodiscard]] LsfgImageHistory& Outputs() {
|
||||
return out_images;
|
||||
}
|
||||
|
||||
private:
|
||||
const LsfgAlphaPasses* passes{};
|
||||
LsfgImage* input{};
|
||||
|
||||
std::array<VkDescriptorSet, LSFG_ALPHA_STAGES - 1> descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> last_descriptor_sets{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
LsfgImage temp1;
|
||||
LsfgImage temp2;
|
||||
LsfgImagePair temp3;
|
||||
LsfgImageHistory out_images;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,147 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_beta.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 3;
|
||||
constexpr u32 OUTPUT_TILE_SHIFT = 5;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size, u32 shift) {
|
||||
return (size + (1u << shift) - 1) >> shift;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgBeta::LsfgBeta(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_)
|
||||
: inputs{&inputs_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
passes[0] = LsfgPass(device, shaders, BETA[0],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
for (size_t i = 1; i < LSFG_BETA_STAGES - 1; ++i) {
|
||||
passes[i] = LsfgPass(device, shaders, BETA[i],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
}
|
||||
passes[4] = LsfgPass(device, shaders, BETA[4],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
const VkExtent2D extent = (*inputs)[0][0].Extent();
|
||||
for (size_t i = 0; i < temp1.size(); ++i) {
|
||||
temp1[i] = LsfgImage(device, memory_allocator, extent);
|
||||
temp2[i] = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_BETA_OUTPUTS; ++i) {
|
||||
const VkExtent2D level_extent{
|
||||
.width = extent.width >> i,
|
||||
.height = extent.height >> i,
|
||||
};
|
||||
out_images[i] = LsfgImage(device, memory_allocator, level_extent, LSFG_FLOW_FORMAT);
|
||||
}
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[0].SetLayout());
|
||||
}
|
||||
for (size_t i = 1; i < LSFG_BETA_STAGES; ++i) {
|
||||
layouts.push_back(passes[i].SetLayout());
|
||||
}
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
first_descriptor_sets[i] = owned_sets[i];
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_BETA_STAGES - 1; ++i) {
|
||||
descriptor_sets[i] = owned_sets[LSFG_HISTORY_SLOTS + i];
|
||||
}
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
const VkSampler border_sampler = resources.GetSampler(
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
LsfgDescriptorWriter(first_descriptor_sets[i])
|
||||
.AddSampler(border_sampler)
|
||||
.AddSampledImages((*inputs)[(i + 1) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
|
||||
.AddStorageImages(temp1)
|
||||
.Build(device);
|
||||
}
|
||||
LsfgDescriptorWriter(descriptor_sets[0])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp1)
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[1])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddStorageImages(temp1)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[2])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp1)
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(descriptor_sets[3])
|
||||
.AddUniformBuffer(resources.GetBuffer(0.5f), LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddStorageImages(out_images)
|
||||
.Build(device);
|
||||
}
|
||||
|
||||
void LsfgBeta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
|
||||
const VkExtent2D extent = temp1[0].Extent();
|
||||
const u32 groups_x = GroupCount(extent.width, DISPATCH_TILE_SHIFT);
|
||||
const u32 groups_y = GroupCount(extent.height, DISPATCH_TILE_SHIFT);
|
||||
|
||||
LsfgBarriers barriers(cmdbuf);
|
||||
for (auto& slot : *inputs) {
|
||||
barriers.WriteToReadAll(slot);
|
||||
}
|
||||
barriers.ReadToWriteAll(temp1).Build();
|
||||
|
||||
passes[0].Bind(cmdbuf, first_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[1].Bind(cmdbuf, descriptor_sets[0]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build();
|
||||
passes[2].Bind(cmdbuf, descriptor_sets[1]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[3].Bind(cmdbuf, descriptor_sets[2]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(out_images).Build();
|
||||
passes[4].Bind(cmdbuf, descriptor_sets[3]);
|
||||
cmdbuf.Dispatch(GroupCount(extent.width, OUTPUT_TILE_SHIFT),
|
||||
GroupCount(extent.height, OUTPUT_TILE_SHIFT), 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_BETA_STAGES = 5;
|
||||
constexpr size_t LSFG_BETA_OUTPUTS = 6;
|
||||
|
||||
class LsfgBeta {
|
||||
public:
|
||||
LsfgBeta() = default;
|
||||
LsfgBeta(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImageHistory& inputs);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output(size_t level) {
|
||||
return out_images[level];
|
||||
}
|
||||
|
||||
private:
|
||||
LsfgImageHistory* inputs{};
|
||||
|
||||
std::array<LsfgPass, LSFG_BETA_STAGES> passes;
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> first_descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_BETA_STAGES - 1> descriptor_sets{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
LsfgImagePair temp1;
|
||||
LsfgImagePair temp2;
|
||||
std::array<LsfgImage, LSFG_BETA_OUTPUTS> out_images;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,103 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "video_core/renderer_vulkan/present/lsfg_chain.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 FIXED_DESCRIPTOR_SETS = 64;
|
||||
constexpr u32 DESCRIPTOR_SETS_PER_SLOT = 112;
|
||||
constexpr size_t FIRST_DELTA_LEVEL = 4;
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, VkExtent2D extent, VkFormat format,
|
||||
f32 flow_scale)
|
||||
: resources{device, memory_allocator, flow_scale},
|
||||
descriptor_pool{CreateLsfgDescriptorPool(
|
||||
device, FIXED_DESCRIPTOR_SETS +
|
||||
DESCRIPTOR_SETS_PER_SLOT * static_cast<u32>(LSFG_GENERATION_SLOTS))} {
|
||||
for (auto& image : frames) {
|
||||
image = LsfgImage(device, memory_allocator, extent, format);
|
||||
}
|
||||
|
||||
mipmaps = LsfgMipmaps(device, memory_allocator, shaders, resources, descriptor_pool, frames,
|
||||
flow_scale);
|
||||
|
||||
alpha_passes = LsfgAlphaPasses(device, shaders);
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
alpha[i] = LsfgAlpha(device, memory_allocator, alpha_passes, resources, descriptor_pool,
|
||||
mipmaps.Output(i));
|
||||
}
|
||||
|
||||
beta = LsfgBeta(device, memory_allocator, shaders, resources, descriptor_pool,
|
||||
alpha[0].Outputs());
|
||||
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
const size_t level = LSFG_MIP_LEVELS - 1 - i;
|
||||
gamma[i] = LsfgGamma(device, memory_allocator, shaders, resources, descriptor_pool,
|
||||
alpha[level].Outputs(),
|
||||
beta.Output(std::min(level, LSFG_BETA_OUTPUTS - 1)),
|
||||
i == 0 ? nullptr : &gamma[i - 1].Output());
|
||||
|
||||
if (i < FIRST_DELTA_LEVEL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t index = i - FIRST_DELTA_LEVEL;
|
||||
delta[index] = LsfgDelta(
|
||||
device, memory_allocator, shaders, resources, descriptor_pool, alpha[level].Outputs(),
|
||||
beta.Output(level), i == FIRST_DELTA_LEVEL ? nullptr : &gamma[i - 1].Output(),
|
||||
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output1(),
|
||||
i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output2());
|
||||
}
|
||||
|
||||
generate = LsfgGenerate(device, shaders, resources, descriptor_pool, frames,
|
||||
gamma[LSFG_MIP_LEVELS - 1].Output(),
|
||||
delta[LSFG_DELTA_INSTANCES - 1].Output1(),
|
||||
delta[LSFG_DELTA_INSTANCES - 1].Output2());
|
||||
}
|
||||
|
||||
void LsfgChain::DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count) {
|
||||
mipmaps.Dispatch(cmdbuf, frame_count);
|
||||
|
||||
for (size_t stage = 0; stage < LSFG_ALPHA_STAGES; ++stage) {
|
||||
LsfgBarriers barriers(cmdbuf);
|
||||
for (auto& level : alpha) {
|
||||
level.PushBarriers(barriers, frame_count, stage);
|
||||
}
|
||||
barriers.Build();
|
||||
|
||||
alpha_passes.Get(stage).BindPipeline(cmdbuf);
|
||||
for (auto& level : alpha) {
|
||||
level.DispatchStage(cmdbuf, frame_count, stage);
|
||||
}
|
||||
}
|
||||
|
||||
beta.Dispatch(cmdbuf, frame_count);
|
||||
}
|
||||
|
||||
void LsfgChain::DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count,
|
||||
size_t generation_count, size_t generation, u32 target,
|
||||
VkImage image, VkExtent2D extent) {
|
||||
const size_t slot = LsfgGenerationSlot(generation_count, generation);
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
gamma[i].Dispatch(cmdbuf, frame_count, slot);
|
||||
if (i >= FIRST_DELTA_LEVEL) {
|
||||
delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, slot);
|
||||
}
|
||||
}
|
||||
generate.Dispatch(cmdbuf, frame_count, slot, target, image, extent);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,87 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_alpha.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_beta.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_delta.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_gamma.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_generate.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_DELTA_INSTANCES = 3;
|
||||
|
||||
class LsfgChain {
|
||||
public:
|
||||
LsfgChain(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
VkExtent2D extent, VkFormat format, f32 flow_scale);
|
||||
|
||||
LsfgChain(const LsfgChain&) = delete;
|
||||
LsfgChain& operator=(const LsfgChain&) = delete;
|
||||
|
||||
void DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count);
|
||||
|
||||
void DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation_count,
|
||||
size_t generation, u32 target, VkImage image, VkExtent2D extent);
|
||||
|
||||
void SetTarget(const Device& device, size_t generation_count, size_t generation, u32 target,
|
||||
VkImageView view) {
|
||||
generate.SetTarget(device, LsfgGenerationSlot(generation_count, generation), target, view);
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& Input(u64 frame_count) {
|
||||
return frames[frame_count % frames.size()];
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& FlowLevel(size_t level) {
|
||||
return mipmaps.Output(level);
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& AlphaOutput(size_t level, u64 frame_count, size_t index) {
|
||||
return alpha[level].Outputs()[frame_count % LSFG_HISTORY_SLOTS][index];
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& BetaOutput(size_t level) {
|
||||
return beta.Output(level);
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& GammaOutput(size_t index) {
|
||||
return gamma[index].Output();
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& DeltaOutput1(size_t index) {
|
||||
return delta[index].Output1();
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& DeltaOutput2(size_t index) {
|
||||
return delta[index].Output2();
|
||||
}
|
||||
|
||||
private:
|
||||
LsfgResources resources;
|
||||
vk::DescriptorPool descriptor_pool;
|
||||
|
||||
LsfgImagePair frames;
|
||||
LsfgMipmaps mipmaps;
|
||||
LsfgAlphaPasses alpha_passes;
|
||||
std::array<LsfgAlpha, LSFG_MIP_LEVELS> alpha;
|
||||
LsfgBeta beta;
|
||||
std::array<LsfgGamma, LSFG_MIP_LEVELS> gamma;
|
||||
std::array<LsfgDelta, LSFG_DELTA_INSTANCES> delta;
|
||||
LsfgGenerate generate;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,339 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DESCRIPTORS_PER_TYPE = 4096;
|
||||
|
||||
struct LsfgConstants {
|
||||
std::array<u32, 2> input_offset;
|
||||
u32 first_iter;
|
||||
u32 first_iter_s;
|
||||
u32 advanced_color_kind;
|
||||
u32 hdr_support;
|
||||
f32 resolution_inv_scale;
|
||||
f32 timestamp;
|
||||
f32 ui_threshold;
|
||||
std::array<u32, 3> padding;
|
||||
};
|
||||
static_assert(sizeof(LsfgConstants) == 48);
|
||||
|
||||
vk::Image CreateChainImage(MemoryAllocator& memory_allocator, VkExtent2D extent, VkFormat format) {
|
||||
const VkImageCreateInfo image_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = format,
|
||||
.extent = {.width = extent.width, .height = extent.height, .depth = 1},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
};
|
||||
return memory_allocator.CreateImage(image_ci);
|
||||
}
|
||||
|
||||
vk::Buffer CreateUniformBuffer(MemoryAllocator& memory_allocator, VkDeviceSize size) {
|
||||
const VkBufferCreateInfo buffer_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = size,
|
||||
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
};
|
||||
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::Upload);
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier MakeBarrier(const LsfgImage& image, VkAccessFlags src_access,
|
||||
VkAccessFlags dst_access) {
|
||||
return VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = src_access,
|
||||
.dstAccessMask = dst_access,
|
||||
.oldLayout = image.Layout(),
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image.Handle(),
|
||||
.subresourceRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgImage::LsfgImage(const Device& device, MemoryAllocator& memory_allocator, VkExtent2D extent_,
|
||||
VkFormat format_)
|
||||
: extent{std::max(1u, extent_.width), std::max(1u, extent_.height)}, format{format_} {
|
||||
image = CreateChainImage(memory_allocator, extent, format);
|
||||
view = CreateWrappedImageView(device, image, format);
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::Push(LsfgImage& image, VkAccessFlags src_access,
|
||||
VkAccessFlags dst_access) {
|
||||
barriers.push_back(MakeBarrier(image, src_access, dst_access));
|
||||
image.SetLayout(VK_IMAGE_LAYOUT_GENERAL);
|
||||
return *this;
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage& image) {
|
||||
return Push(image, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage& image) {
|
||||
return Push(image, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT);
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage* image) {
|
||||
return image == nullptr ? *this : WriteToRead(*image);
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage* image) {
|
||||
return image == nullptr ? *this : ReadToWrite(*image);
|
||||
}
|
||||
|
||||
LsfgBarriers& LsfgBarriers::DiscardToWrite(VkImage image) {
|
||||
barriers.push_back(VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = 0,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
VkDeviceSize LsfgResources::BufferSize() {
|
||||
return sizeof(LsfgConstants);
|
||||
}
|
||||
|
||||
VkSampler LsfgResources::GetSampler(VkSamplerAddressMode address_mode, VkCompareOp compare_op,
|
||||
bool white_border) {
|
||||
const u64 key = static_cast<u64>(address_mode) | (static_cast<u64>(compare_op) << 8) |
|
||||
(static_cast<u64>(white_border) << 16);
|
||||
|
||||
const auto it = samplers.find(key);
|
||||
if (it != samplers.end()) {
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
const auto [entry, inserted] =
|
||||
samplers.emplace(key, CreateLsfgSampler(*device, address_mode, compare_op, white_border));
|
||||
return *entry->second;
|
||||
}
|
||||
|
||||
VkBuffer LsfgResources::GetBuffer(f32 timestamp, bool first_iter, bool first_iter_s) {
|
||||
u32 timestamp_bits{};
|
||||
std::memcpy(×tamp_bits, ×tamp, sizeof(timestamp_bits));
|
||||
const u64 key = static_cast<u64>(timestamp_bits) | (static_cast<u64>(first_iter) << 32) |
|
||||
(static_cast<u64>(first_iter_s) << 33);
|
||||
|
||||
const auto it = buffers.find(key);
|
||||
if (it != buffers.end()) {
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
vk::Buffer buffer = CreateUniformBuffer(*memory_allocator, sizeof(LsfgConstants));
|
||||
|
||||
const LsfgConstants constants{
|
||||
.input_offset = {0, 0},
|
||||
.first_iter = first_iter ? 1u : 0u,
|
||||
.first_iter_s = first_iter_s ? 1u : 0u,
|
||||
.advanced_color_kind = 0,
|
||||
.hdr_support = 0,
|
||||
.resolution_inv_scale = 1.0f / flow_scale,
|
||||
.timestamp = timestamp,
|
||||
.ui_threshold = 0.5f,
|
||||
.padding = {0, 0, 0},
|
||||
};
|
||||
|
||||
const std::span<u8> mapped = buffer.Mapped();
|
||||
std::memcpy(mapped.data(), &constants, sizeof(constants));
|
||||
buffer.Flush();
|
||||
|
||||
const auto [entry, inserted] = buffers.emplace(key, std::move(buffer));
|
||||
return *entry->second;
|
||||
}
|
||||
|
||||
void LsfgBarriers::Build() {
|
||||
if (barriers.empty()) {
|
||||
return;
|
||||
}
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, {}, {}, barriers);
|
||||
barriers.clear();
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::PushImage(VkDescriptorType type, VkSampler sampler,
|
||||
VkImageView view) {
|
||||
image_infos.push_back(VkDescriptorImageInfo{
|
||||
.sampler = sampler,
|
||||
.imageView = view,
|
||||
.imageLayout = view == VK_NULL_HANDLE ? VK_IMAGE_LAYOUT_UNDEFINED
|
||||
: VK_IMAGE_LAYOUT_GENERAL,
|
||||
});
|
||||
writes.push_back(VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set,
|
||||
.dstBinding = binding++,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = type,
|
||||
.pImageInfo = &image_infos.back(),
|
||||
.pBufferInfo = nullptr,
|
||||
.pTexelBufferView = nullptr,
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampler(VkSampler sampler) {
|
||||
return PushImage(VK_DESCRIPTOR_TYPE_SAMPLER, sampler, VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage& image) {
|
||||
return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE, image.View());
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage* image) {
|
||||
return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE,
|
||||
image == nullptr ? VK_NULL_HANDLE : image->View());
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddStorageImage(const LsfgImage& image) {
|
||||
return PushImage(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_NULL_HANDLE, image.View());
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddStorageView(VkImageView view) {
|
||||
return PushImage(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_NULL_HANDLE, view);
|
||||
}
|
||||
|
||||
LsfgDescriptorWriter& LsfgDescriptorWriter::AddUniformBuffer(VkBuffer buffer, VkDeviceSize size) {
|
||||
buffer_infos.push_back(VkDescriptorBufferInfo{
|
||||
.buffer = buffer,
|
||||
.offset = 0,
|
||||
.range = size,
|
||||
});
|
||||
writes.push_back(VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set,
|
||||
.dstBinding = binding++,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.pImageInfo = nullptr,
|
||||
.pBufferInfo = &buffer_infos.back(),
|
||||
.pTexelBufferView = nullptr,
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
void LsfgDescriptorWriter::Build(const Device& device) {
|
||||
if (writes.empty()) {
|
||||
return;
|
||||
}
|
||||
device.GetLogical().UpdateDescriptorSets(writes, {});
|
||||
writes.clear();
|
||||
}
|
||||
|
||||
LsfgPass::LsfgPass(const Device& device, const LsfgShaders& shaders, u32 shader_id,
|
||||
LsfgBindings bindings) {
|
||||
std::vector<VkDescriptorType> types;
|
||||
for (const auto& [count, type] : bindings) {
|
||||
types.insert(types.end(), count, type);
|
||||
}
|
||||
descriptor_count = static_cast<u32>(types.size());
|
||||
|
||||
descriptor_set_layout = CreateWrappedDescriptorSetLayout(
|
||||
device, std::span<const VkDescriptorType>{types}, VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
pipeline_layout = CreateWrappedPipelineLayout(device, descriptor_set_layout);
|
||||
pipeline = CreateWrappedComputePipeline(device, pipeline_layout, shaders.Get(shader_id));
|
||||
}
|
||||
|
||||
void LsfgPass::Bind(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const {
|
||||
BindPipeline(cmdbuf);
|
||||
BindSet(cmdbuf, set);
|
||||
}
|
||||
|
||||
void LsfgPass::BindPipeline(vk::CommandBuffer cmdbuf) const {
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
||||
}
|
||||
|
||||
void LsfgPass::BindSet(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const {
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0, set, {});
|
||||
}
|
||||
|
||||
vk::DescriptorPool CreateLsfgDescriptorPool(const Device& device, u32 max_sets) {
|
||||
return CreateWrappedDescriptorPool(
|
||||
device, DESCRIPTORS_PER_TYPE, max_sets,
|
||||
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER,
|
||||
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE});
|
||||
}
|
||||
|
||||
vk::Sampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode,
|
||||
VkCompareOp compare_op, bool white_border) {
|
||||
return device.GetLogical().CreateSampler(VkSamplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.addressModeU = address_mode,
|
||||
.addressModeV = address_mode,
|
||||
.addressModeW = address_mode,
|
||||
.mipLodBias = 0.0f,
|
||||
.anisotropyEnable = VK_FALSE,
|
||||
.maxAnisotropy = 0.0f,
|
||||
.compareEnable = VK_FALSE,
|
||||
.compareOp = compare_op,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = VK_LOD_CLAMP_NONE,
|
||||
.borderColor = white_border ? VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE
|
||||
: VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,227 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr VkFormat LSFG_DEFAULT_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
constexpr VkFormat LSFG_FLOW_FORMAT = VK_FORMAT_R8_UNORM;
|
||||
constexpr VkFormat LSFG_MOTION_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
|
||||
constexpr size_t LSFG_HISTORY_SLOTS = 3;
|
||||
constexpr size_t LSFG_MAX_TARGETS = 7;
|
||||
constexpr size_t LSFG_MAX_GENERATIONS = 3;
|
||||
|
||||
constexpr size_t LSFG_GENERATION_SLOTS = LSFG_MAX_GENERATIONS * (LSFG_MAX_GENERATIONS + 1) / 2;
|
||||
|
||||
[[nodiscard]] constexpr size_t LsfgGenerationSlot(size_t generation_count, size_t generation) {
|
||||
return (generation_count - 1) * generation_count / 2 + generation;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr f32 LsfgTimestamp(size_t generation, size_t generation_count) {
|
||||
return static_cast<f32>(generation + 1) / static_cast<f32>(generation_count + 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr size_t LsfgSlotCount(size_t slot) {
|
||||
size_t count = 1;
|
||||
while (LsfgGenerationSlot(count + 1, 0) <= slot) {
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr f32 LsfgSlotTimestamp(size_t slot) {
|
||||
const size_t count = LsfgSlotCount(slot);
|
||||
return LsfgTimestamp(slot - LsfgGenerationSlot(count, 0), count);
|
||||
}
|
||||
|
||||
class LsfgImage {
|
||||
public:
|
||||
LsfgImage() = default;
|
||||
LsfgImage(const Device& device, MemoryAllocator& memory_allocator, VkExtent2D extent_,
|
||||
VkFormat format = LSFG_DEFAULT_FORMAT);
|
||||
|
||||
[[nodiscard]] VkImage Handle() const {
|
||||
return *image;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImageView View() const {
|
||||
return *view;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkExtent2D Extent() const {
|
||||
return extent;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkFormat Format() const {
|
||||
return format;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImageLayout Layout() const {
|
||||
return layout;
|
||||
}
|
||||
|
||||
void SetLayout(VkImageLayout new_layout) {
|
||||
layout = new_layout;
|
||||
}
|
||||
|
||||
private:
|
||||
vk::Image image;
|
||||
vk::ImageView view;
|
||||
VkExtent2D extent{};
|
||||
VkFormat format{VK_FORMAT_UNDEFINED};
|
||||
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
};
|
||||
|
||||
using LsfgImagePair = std::array<LsfgImage, 2>;
|
||||
using LsfgImageHistory = std::array<LsfgImagePair, LSFG_HISTORY_SLOTS>;
|
||||
|
||||
class LsfgResources {
|
||||
public:
|
||||
LsfgResources() = default;
|
||||
LsfgResources(const Device& device_, MemoryAllocator& memory_allocator_, f32 flow_scale_)
|
||||
: device{&device_}, memory_allocator{&memory_allocator_}, flow_scale{flow_scale_} {}
|
||||
|
||||
[[nodiscard]] VkSampler GetSampler(
|
||||
VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
VkCompareOp compare_op = VK_COMPARE_OP_NEVER, bool white_border = false);
|
||||
|
||||
[[nodiscard]] VkBuffer GetBuffer(f32 timestamp = 0.0f, bool first_iter = false,
|
||||
bool first_iter_s = false);
|
||||
|
||||
[[nodiscard]] static VkDeviceSize BufferSize();
|
||||
|
||||
private:
|
||||
const Device* device{};
|
||||
MemoryAllocator* memory_allocator{};
|
||||
f32 flow_scale{1.0f};
|
||||
|
||||
std::map<u64, vk::Sampler> samplers;
|
||||
std::map<u64, vk::Buffer> buffers;
|
||||
};
|
||||
|
||||
class LsfgBarriers {
|
||||
public:
|
||||
explicit LsfgBarriers(vk::CommandBuffer cmdbuf_) : cmdbuf{cmdbuf_} {}
|
||||
|
||||
LsfgBarriers& WriteToRead(LsfgImage& image);
|
||||
LsfgBarriers& ReadToWrite(LsfgImage& image);
|
||||
LsfgBarriers& WriteToRead(LsfgImage* image);
|
||||
LsfgBarriers& ReadToWrite(LsfgImage* image);
|
||||
LsfgBarriers& DiscardToWrite(VkImage image);
|
||||
|
||||
template <typename Range>
|
||||
LsfgBarriers& WriteToReadAll(Range& images) {
|
||||
for (auto& image : images) {
|
||||
WriteToRead(image);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
LsfgBarriers& ReadToWriteAll(Range& images) {
|
||||
for (auto& image : images) {
|
||||
ReadToWrite(image);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Build();
|
||||
|
||||
private:
|
||||
LsfgBarriers& Push(LsfgImage& image, VkAccessFlags src_access, VkAccessFlags dst_access);
|
||||
|
||||
vk::CommandBuffer cmdbuf;
|
||||
std::vector<VkImageMemoryBarrier> barriers;
|
||||
};
|
||||
|
||||
class LsfgDescriptorWriter {
|
||||
public:
|
||||
explicit LsfgDescriptorWriter(VkDescriptorSet set_) : set{set_} {}
|
||||
|
||||
LsfgDescriptorWriter& AddSampler(VkSampler sampler);
|
||||
LsfgDescriptorWriter& AddSampledImage(const LsfgImage& image);
|
||||
LsfgDescriptorWriter& AddSampledImage(const LsfgImage* image);
|
||||
LsfgDescriptorWriter& AddStorageImage(const LsfgImage& image);
|
||||
LsfgDescriptorWriter& AddStorageView(VkImageView view);
|
||||
LsfgDescriptorWriter& AddUniformBuffer(VkBuffer buffer, VkDeviceSize size);
|
||||
|
||||
template <typename Range>
|
||||
LsfgDescriptorWriter& AddSampledImages(const Range& images) {
|
||||
for (const auto& image : images) {
|
||||
AddSampledImage(image);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
LsfgDescriptorWriter& AddStorageImages(const Range& images) {
|
||||
for (const auto& image : images) {
|
||||
AddStorageImage(image);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Build(const Device& device);
|
||||
|
||||
private:
|
||||
LsfgDescriptorWriter& PushImage(VkDescriptorType type, VkSampler sampler, VkImageView view);
|
||||
|
||||
VkDescriptorSet set;
|
||||
u32 binding{};
|
||||
std::deque<VkDescriptorImageInfo> image_infos;
|
||||
std::deque<VkDescriptorBufferInfo> buffer_infos;
|
||||
std::vector<VkWriteDescriptorSet> writes;
|
||||
};
|
||||
|
||||
using LsfgBindings = std::initializer_list<std::pair<u32, VkDescriptorType>>;
|
||||
|
||||
class LsfgPass {
|
||||
public:
|
||||
LsfgPass() = default;
|
||||
LsfgPass(const Device& device, const LsfgShaders& shaders, u32 shader_id,
|
||||
LsfgBindings bindings);
|
||||
|
||||
[[nodiscard]] VkDescriptorSetLayout SetLayout() const {
|
||||
return *descriptor_set_layout;
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 DescriptorCount() const {
|
||||
return descriptor_count;
|
||||
}
|
||||
|
||||
void Bind(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const;
|
||||
void BindPipeline(vk::CommandBuffer cmdbuf) const;
|
||||
void BindSet(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const;
|
||||
|
||||
private:
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::Pipeline pipeline;
|
||||
u32 descriptor_count{};
|
||||
};
|
||||
|
||||
[[nodiscard]] vk::DescriptorPool CreateLsfgDescriptorPool(const Device& device, u32 max_sets);
|
||||
|
||||
[[nodiscard]] vk::Sampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode,
|
||||
VkCompareOp compare_op, bool white_border);
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,283 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_delta.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 3;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgDelta::LsfgDelta(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_,
|
||||
LsfgImage& flow_input_, LsfgImage* previous_gamma_, LsfgImage* previous1_,
|
||||
LsfgImage* previous2_)
|
||||
: inputs{&inputs_}, flow_input{&flow_input_}, previous_gamma{previous_gamma_},
|
||||
previous1{previous1_}, previous2{previous2_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
passes[0] = LsfgPass(device, shaders, DELTA[0],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[1] = LsfgPass(device, shaders, DELTA[1],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[2] = LsfgPass(device, shaders, DELTA[2],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[3] = LsfgPass(device, shaders, DELTA[3],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[4] = LsfgPass(device, shaders, DELTA[4],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[5] = LsfgPass(device, shaders, DELTA[5],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
for (size_t i = 6; i < LSFG_DELTA_STAGES - 1; ++i) {
|
||||
passes[i] = LsfgPass(device, shaders, DELTA[i],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
}
|
||||
passes[9] = LsfgPass(device, shaders, DELTA[9],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
const VkExtent2D extent = (*inputs)[0][0].Extent();
|
||||
for (auto& image : temp1) {
|
||||
image = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
for (auto& image : temp2) {
|
||||
image = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
out_image1 = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
|
||||
out_image2 = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[0].SetLayout());
|
||||
}
|
||||
for (size_t i = 1; i <= 4; ++i) {
|
||||
layouts.push_back(passes[i].SetLayout());
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[5].SetLayout());
|
||||
}
|
||||
for (size_t i = 6; i < LSFG_DELTA_STAGES; ++i) {
|
||||
layouts.push_back(passes[i].SetLayout());
|
||||
}
|
||||
}
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
const VkSampler border_sampler = resources.GetSampler(
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
|
||||
const VkSampler edge_sampler =
|
||||
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
|
||||
size_t next = 0;
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
Generation& pass = generations[slot];
|
||||
const VkBuffer buffer =
|
||||
resources.GetBuffer(LsfgSlotTimestamp(slot), false, previous_gamma == nullptr);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
pass.first_descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
pass.descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
pass.sixth_descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
for (size_t i = 4; i < LSFG_DELTA_STAGES - 2; ++i) {
|
||||
pass.descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
LsfgDescriptorWriter(pass.first_descriptor_sets[i])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(border_sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImage(previous_gamma)
|
||||
.AddStorageImages(temp1)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.sixth_descriptor_sets[i])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(border_sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImage(previous_gamma)
|
||||
.AddSampledImage(previous1)
|
||||
.AddStorageImage(temp2[0])
|
||||
.Build(device);
|
||||
}
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[0])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp1)
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[1])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddStorageImage(temp1[0])
|
||||
.AddStorageImage(temp1[1])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[2])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp1[0])
|
||||
.AddSampledImage(temp1[1])
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[3])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddSampledImage(previous_gamma)
|
||||
.AddSampledImage(*flow_input)
|
||||
.AddStorageImage(out_image1)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[4])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp2[0])
|
||||
.AddStorageImage(temp1[0])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[5])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp1[0])
|
||||
.AddStorageImage(temp2[0])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[6])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp2[0])
|
||||
.AddStorageImage(temp1[0])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[7])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImage(temp1[0])
|
||||
.AddSampledImage(previous2)
|
||||
.AddStorageImage(out_image2)
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot) {
|
||||
const Generation& pass = generations[slot];
|
||||
|
||||
const VkExtent2D extent = temp1[0].Extent();
|
||||
const u32 groups_x = GroupCount(extent.width);
|
||||
const u32 groups_y = GroupCount(extent.height);
|
||||
|
||||
const size_t history = frame_count % LSFG_HISTORY_SLOTS;
|
||||
const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS;
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll((*inputs)[previous_history])
|
||||
.WriteToReadAll((*inputs)[history])
|
||||
.WriteToRead(previous_gamma)
|
||||
.ReadToWriteAll(temp1)
|
||||
.Build();
|
||||
passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[1].Bind(cmdbuf, pass.descriptor_sets[0]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build();
|
||||
passes[2].Bind(cmdbuf, pass.descriptor_sets[1]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[3].Bind(cmdbuf, pass.descriptor_sets[2]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.WriteToRead(previous_gamma)
|
||||
.WriteToRead(*flow_input)
|
||||
.ReadToWrite(out_image1)
|
||||
.Build();
|
||||
passes[4].Bind(cmdbuf, pass.descriptor_sets[3]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll((*inputs)[previous_history])
|
||||
.WriteToReadAll((*inputs)[history])
|
||||
.WriteToRead(previous_gamma)
|
||||
.WriteToRead(previous1)
|
||||
.ReadToWriteAll(temp2)
|
||||
.Build();
|
||||
passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[history]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.ReadToWrite(temp1[0])
|
||||
.ReadToWrite(temp1[1])
|
||||
.Build();
|
||||
passes[6].Bind(cmdbuf, pass.descriptor_sets[4]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToRead(temp1[0])
|
||||
.WriteToRead(temp1[1])
|
||||
.ReadToWriteAll(temp2)
|
||||
.Build();
|
||||
passes[7].Bind(cmdbuf, pass.descriptor_sets[5]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.ReadToWrite(temp1[0])
|
||||
.ReadToWrite(temp1[1])
|
||||
.Build();
|
||||
passes[8].Bind(cmdbuf, pass.descriptor_sets[6]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToRead(temp1[0])
|
||||
.WriteToRead(temp1[1])
|
||||
.WriteToRead(previous2)
|
||||
.ReadToWrite(out_image2)
|
||||
.Build();
|
||||
passes[9].Bind(cmdbuf, pass.descriptor_sets[7]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_DELTA_STAGES = 10;
|
||||
constexpr size_t LSFG_DELTA_TEMPS = 3;
|
||||
|
||||
class LsfgDelta {
|
||||
public:
|
||||
LsfgDelta() = default;
|
||||
LsfgDelta(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous_gamma,
|
||||
LsfgImage* previous1, LsfgImage* previous2);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output1() {
|
||||
return out_image1;
|
||||
}
|
||||
|
||||
[[nodiscard]] LsfgImage& Output2() {
|
||||
return out_image2;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Generation {
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> first_descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> sixth_descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_DELTA_STAGES - 2> descriptor_sets{};
|
||||
};
|
||||
|
||||
LsfgImageHistory* inputs{};
|
||||
LsfgImage* flow_input{};
|
||||
LsfgImage* previous_gamma{};
|
||||
LsfgImage* previous1{};
|
||||
LsfgImage* previous2{};
|
||||
|
||||
std::array<LsfgPass, LSFG_DELTA_STAGES> passes;
|
||||
std::array<Generation, LSFG_GENERATION_SLOTS> generations{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
std::array<LsfgImage, LSFG_DELTA_TEMPS> temp1;
|
||||
LsfgImagePair temp2;
|
||||
LsfgImage out_image1;
|
||||
LsfgImage out_image2;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,185 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_gamma.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 3;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgGamma::LsfgGamma(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_,
|
||||
LsfgImage& flow_input_, LsfgImage* previous_)
|
||||
: inputs{&inputs_}, flow_input{&flow_input_}, previous{previous_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
passes[0] = LsfgPass(device, shaders, GAMMA[0],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[1] = LsfgPass(device, shaders, GAMMA[1],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[2] = LsfgPass(device, shaders, GAMMA[2],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[3] = LsfgPass(device, shaders, GAMMA[3],
|
||||
{{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
passes[4] = LsfgPass(device, shaders, GAMMA[4],
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
const VkExtent2D extent = (*inputs)[0][0].Extent();
|
||||
for (auto& image : temp1) {
|
||||
image = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
for (auto& image : temp2) {
|
||||
image = LsfgImage(device, memory_allocator, extent);
|
||||
}
|
||||
out_image = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT);
|
||||
|
||||
std::vector<VkDescriptorSetLayout> layouts;
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
layouts.push_back(passes[0].SetLayout());
|
||||
}
|
||||
for (size_t i = 1; i < LSFG_GAMMA_STAGES; ++i) {
|
||||
layouts.push_back(passes[i].SetLayout());
|
||||
}
|
||||
}
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
const VkSampler border_sampler = resources.GetSampler(
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true);
|
||||
const VkSampler edge_sampler =
|
||||
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
|
||||
|
||||
size_t next = 0;
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
Generation& pass = generations[slot];
|
||||
const VkBuffer buffer =
|
||||
resources.GetBuffer(LsfgSlotTimestamp(slot), previous == nullptr);
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
pass.first_descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
for (size_t i = 0; i < LSFG_GAMMA_STAGES - 1; ++i) {
|
||||
pass.descriptor_sets[i] = owned_sets[next++];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) {
|
||||
LsfgDescriptorWriter(pass.first_descriptor_sets[i])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(border_sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS])
|
||||
.AddSampledImage(previous)
|
||||
.AddStorageImages(temp1)
|
||||
.Build(device);
|
||||
}
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[0])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp1)
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[1])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddStorageImage(temp1[0])
|
||||
.AddStorageImage(temp1[1])
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[2])
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage(temp1[0])
|
||||
.AddSampledImage(temp1[1])
|
||||
.AddStorageImages(temp2)
|
||||
.Build(device);
|
||||
LsfgDescriptorWriter(pass.descriptor_sets[3])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImages(temp2)
|
||||
.AddSampledImage(previous)
|
||||
.AddSampledImage(*flow_input)
|
||||
.AddStorageImage(out_image)
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgGamma::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot) {
|
||||
const Generation& pass = generations[slot];
|
||||
|
||||
const VkExtent2D extent = temp1[0].Extent();
|
||||
const u32 groups_x = GroupCount(extent.width);
|
||||
const u32 groups_y = GroupCount(extent.height);
|
||||
|
||||
const size_t history = frame_count % LSFG_HISTORY_SLOTS;
|
||||
const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS;
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll((*inputs)[previous_history])
|
||||
.WriteToReadAll((*inputs)[history])
|
||||
.WriteToRead(previous)
|
||||
.ReadToWriteAll(temp1)
|
||||
.Build();
|
||||
passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build();
|
||||
passes[1].Bind(cmdbuf, pass.descriptor_sets[0]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.ReadToWrite(temp1[0])
|
||||
.ReadToWrite(temp1[1])
|
||||
.Build();
|
||||
passes[2].Bind(cmdbuf, pass.descriptor_sets[1]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToRead(temp1[0])
|
||||
.WriteToRead(temp1[1])
|
||||
.ReadToWriteAll(temp2)
|
||||
.Build();
|
||||
passes[3].Bind(cmdbuf, pass.descriptor_sets[2]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(temp2)
|
||||
.WriteToRead(previous)
|
||||
.WriteToRead(*flow_input)
|
||||
.ReadToWrite(out_image)
|
||||
.Build();
|
||||
passes[4].Bind(cmdbuf, pass.descriptor_sets[3]);
|
||||
cmdbuf.Dispatch(groups_x, groups_y, 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_GAMMA_STAGES = 5;
|
||||
constexpr size_t LSFG_GAMMA_TEMPS = 3;
|
||||
|
||||
class LsfgGamma {
|
||||
public:
|
||||
LsfgGamma() = default;
|
||||
LsfgGamma(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output() {
|
||||
return out_image;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Generation {
|
||||
std::array<VkDescriptorSet, LSFG_HISTORY_SLOTS> first_descriptor_sets{};
|
||||
std::array<VkDescriptorSet, LSFG_GAMMA_STAGES - 1> descriptor_sets{};
|
||||
};
|
||||
|
||||
LsfgImageHistory* inputs{};
|
||||
LsfgImage* flow_input{};
|
||||
LsfgImage* previous{};
|
||||
|
||||
std::array<LsfgPass, LSFG_GAMMA_STAGES> passes;
|
||||
std::array<Generation, LSFG_GENERATION_SLOTS> generations{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
std::array<LsfgImage, LSFG_GAMMA_TEMPS> temp1;
|
||||
LsfgImagePair temp2;
|
||||
LsfgImage out_image;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,130 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_generate.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 4;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier MakeTargetBarrier(VkImage image, VkAccessFlags src_access,
|
||||
VkAccessFlags dst_access, VkImageLayout old_layout) {
|
||||
return VkImageMemoryBarrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = src_access,
|
||||
.dstAccessMask = dst_access,
|
||||
.oldLayout = old_layout,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgGenerate::LsfgGenerate(const Device& device, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImagePair& frames_, LsfgImage& motion_, LsfgImage& detail1_,
|
||||
LsfgImage& detail2_)
|
||||
: frames{&frames_}, motion{&motion_}, detail1{&detail1_}, detail2{&detail2_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
pass = LsfgPass(device, shaders, GENERATE,
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{2, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
sampler = resources.GetSampler();
|
||||
edge_sampler =
|
||||
resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false);
|
||||
|
||||
const std::vector<VkDescriptorSetLayout> layouts(
|
||||
LSFG_GENERATION_SLOTS * LSFG_MAX_TARGETS * 2, pass.SetLayout());
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
size_t next = 0;
|
||||
for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) {
|
||||
Generation& target = generations[slot];
|
||||
target.buffer = resources.GetBuffer(LsfgSlotTimestamp(slot));
|
||||
|
||||
for (auto& entry : target.targets) {
|
||||
for (auto& set : entry.descriptor_sets) {
|
||||
set = owned_sets[next++];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgGenerate::SetTarget(const Device& device, size_t slot, u32 target, VkImageView view) {
|
||||
Target& entry = generations[slot].targets[target];
|
||||
if (entry.view == view) {
|
||||
return;
|
||||
}
|
||||
entry.view = view;
|
||||
|
||||
for (size_t i = 0; i < entry.descriptor_sets.size(); ++i) {
|
||||
LsfgDescriptorWriter(entry.descriptor_sets[i])
|
||||
.AddUniformBuffer(generations[slot].buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampler(edge_sampler)
|
||||
.AddSampledImage((*frames)[1 - i])
|
||||
.AddSampledImage((*frames)[i])
|
||||
.AddSampledImage(*motion)
|
||||
.AddSampledImage(*detail1)
|
||||
.AddSampledImage(*detail2)
|
||||
.AddStorageView(view)
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgGenerate::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot, u32 target,
|
||||
VkImage image, VkExtent2D extent) {
|
||||
const Target& entry = generations[slot].targets[target];
|
||||
|
||||
LsfgBarriers(cmdbuf)
|
||||
.WriteToReadAll(*frames)
|
||||
.WriteToRead(*motion)
|
||||
.WriteToRead(*detail1)
|
||||
.WriteToRead(*detail2)
|
||||
.DiscardToWrite(image)
|
||||
.Build();
|
||||
|
||||
pass.Bind(cmdbuf, entry.descriptor_sets[frame_count % entry.descriptor_sets.size()]);
|
||||
cmdbuf.Dispatch(GroupCount(extent.width), GroupCount(extent.height), 1);
|
||||
|
||||
const std::array after{MakeTargetBarrier(
|
||||
image, VK_ACCESS_SHADER_WRITE_BIT,
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_IMAGE_LAYOUT_GENERAL)};
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, {}, {}, after);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
class LsfgGenerate {
|
||||
public:
|
||||
LsfgGenerate() = default;
|
||||
LsfgGenerate(const Device& device, const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames, LsfgImage& motion,
|
||||
LsfgImage& detail1, LsfgImage& detail2);
|
||||
|
||||
void SetTarget(const Device& device, size_t slot, u32 target, VkImageView view);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot, u32 target,
|
||||
VkImage image, VkExtent2D extent);
|
||||
|
||||
private:
|
||||
struct Target {
|
||||
std::array<VkDescriptorSet, 2> descriptor_sets{};
|
||||
VkImageView view{};
|
||||
};
|
||||
|
||||
struct Generation {
|
||||
std::array<Target, LSFG_MAX_TARGETS> targets{};
|
||||
VkBuffer buffer{};
|
||||
};
|
||||
|
||||
LsfgImagePair* frames{};
|
||||
LsfgImage* motion{};
|
||||
LsfgImage* detail1{};
|
||||
LsfgImage* detail2{};
|
||||
VkSampler sampler{};
|
||||
VkSampler edge_sampler{};
|
||||
|
||||
LsfgPass pass;
|
||||
std::array<Generation, LSFG_GENERATION_SLOTS> generations{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,81 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DISPATCH_TILE_SHIFT = 6;
|
||||
|
||||
[[nodiscard]] u32 GroupCount(u32 size) {
|
||||
return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
LsfgMipmaps::LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator,
|
||||
const LsfgShaders& shaders, LsfgResources& resources,
|
||||
vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames_,
|
||||
f32 flow_scale)
|
||||
: frames{&frames_} {
|
||||
using namespace VideoCore::FrameGen::PerformanceShader;
|
||||
|
||||
pass = LsfgPass(device, shaders, MIPMAPS,
|
||||
{{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLER},
|
||||
{1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
|
||||
{LSFG_MIP_LEVELS, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}});
|
||||
|
||||
const VkExtent2D input_extent = (*frames)[0].Extent();
|
||||
flow_extent = VkExtent2D{
|
||||
.width = std::max(1u, static_cast<u32>(static_cast<f32>(input_extent.width) * flow_scale)),
|
||||
.height = std::max(1u, static_cast<u32>(static_cast<f32>(input_extent.height) * flow_scale)),
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) {
|
||||
const VkExtent2D level_extent{
|
||||
.width = flow_extent.width >> i,
|
||||
.height = flow_extent.height >> i,
|
||||
};
|
||||
out_images[i] = LsfgImage(device, memory_allocator, level_extent, LSFG_FLOW_FORMAT);
|
||||
}
|
||||
|
||||
const std::vector<VkDescriptorSetLayout> layouts(descriptor_sets.size(), pass.SetLayout());
|
||||
owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts);
|
||||
|
||||
const VkSampler sampler = resources.GetSampler();
|
||||
const VkBuffer buffer = resources.GetBuffer();
|
||||
|
||||
for (size_t i = 0; i < descriptor_sets.size(); ++i) {
|
||||
descriptor_sets[i] = owned_sets[i];
|
||||
LsfgDescriptorWriter(descriptor_sets[i])
|
||||
.AddUniformBuffer(buffer, LsfgResources::BufferSize())
|
||||
.AddSampler(sampler)
|
||||
.AddSampledImage((*frames)[i])
|
||||
.AddStorageImages(out_images)
|
||||
.Build(device);
|
||||
}
|
||||
}
|
||||
|
||||
void LsfgMipmaps::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) {
|
||||
const size_t slot = frame_count % descriptor_sets.size();
|
||||
|
||||
LsfgBarriers(cmdbuf).WriteToRead((*frames)[slot]).ReadToWriteAll(out_images).Build();
|
||||
|
||||
pass.Bind(cmdbuf, descriptor_sets[slot]);
|
||||
cmdbuf.Dispatch(GroupCount(flow_extent.width), GroupCount(flow_extent.height), 1);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class LsfgShaders;
|
||||
|
||||
constexpr size_t LSFG_MIP_LEVELS = 7;
|
||||
|
||||
class LsfgMipmaps {
|
||||
public:
|
||||
LsfgMipmaps() = default;
|
||||
LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders,
|
||||
LsfgResources& resources, vk::DescriptorPool& descriptor_pool,
|
||||
LsfgImagePair& frames, f32 flow_scale);
|
||||
|
||||
void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count);
|
||||
|
||||
[[nodiscard]] LsfgImage& Output(size_t level) {
|
||||
return out_images[level];
|
||||
}
|
||||
|
||||
private:
|
||||
LsfgImagePair* frames{};
|
||||
|
||||
LsfgPass pass;
|
||||
std::array<VkDescriptorSet, 2> descriptor_sets{};
|
||||
vk::DescriptorSets owned_sets;
|
||||
|
||||
VkExtent2D flow_extent{};
|
||||
std::array<LsfgImage, LSFG_MIP_LEVELS> out_images;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "video_core/frame_gen/lossless_dll.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
LsfgShaders::LsfgShaders(const Device& device) {
|
||||
if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool allow_fp16 = device.IsFloat16Supported();
|
||||
const bool prefer_fp16 = allow_fp16 && Settings::values.frame_gen_fp16.GetValue();
|
||||
|
||||
VideoCore::FrameGen::ShaderModules code;
|
||||
if (VideoCore::FrameGen::LoadShaderModules(code, allow_fp16, prefer_fp16) !=
|
||||
VideoCore::FrameGen::LosslessStatus::Ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& [id, words] : code) {
|
||||
modules.emplace(id, CreateWrappedShaderModule(device, words));
|
||||
}
|
||||
valid = true;
|
||||
}
|
||||
|
||||
VkShaderModule LsfgShaders::Get(u32 shader_id) const {
|
||||
const auto hit = modules.find(shader_id);
|
||||
return hit == modules.end() ? VK_NULL_HANDLE : *hit->second;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
|
||||
class LsfgShaders {
|
||||
public:
|
||||
explicit LsfgShaders(const Device& device);
|
||||
|
||||
[[nodiscard]] bool IsValid() const {
|
||||
return valid;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkShaderModule Get(u32 shader_id) const;
|
||||
|
||||
private:
|
||||
std::map<u32, vk::ShaderModule> modules;
|
||||
bool valid{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -320,15 +320,16 @@ vk::DescriptorPool CreateWrappedDescriptorPool(const Device& device, size_t max_
|
||||
});
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(
|
||||
const Device& device, std::initializer_list<VkDescriptorType> types) {
|
||||
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(const Device& device,
|
||||
std::span<const VkDescriptorType> types,
|
||||
VkShaderStageFlags stages) {
|
||||
std::vector<VkDescriptorSetLayoutBinding> bindings(types.size());
|
||||
for (size_t i = 0; i < types.size(); i++) {
|
||||
bindings[i] = {
|
||||
.binding = static_cast<u32>(i),
|
||||
.descriptorType = std::data(types)[i],
|
||||
.descriptorType = types[i],
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.stageFlags = stages,
|
||||
.pImmutableSamplers = nullptr,
|
||||
};
|
||||
}
|
||||
@@ -342,6 +343,13 @@ vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(
|
||||
});
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(
|
||||
const Device& device, std::initializer_list<VkDescriptorType> types,
|
||||
VkShaderStageFlags stages) {
|
||||
return CreateWrappedDescriptorSetLayout(
|
||||
device, std::span<const VkDescriptorType>{std::data(types), types.size()}, stages);
|
||||
}
|
||||
|
||||
vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool,
|
||||
vk::Span<VkDescriptorSetLayout> layouts) {
|
||||
return pool.Allocate(VkDescriptorSetAllocateInfo{
|
||||
@@ -353,6 +361,28 @@ vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool,
|
||||
});
|
||||
}
|
||||
|
||||
vk::Pipeline CreateWrappedComputePipeline(const Device& device, vk::PipelineLayout& layout,
|
||||
VkShaderModule shader) {
|
||||
return device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.stage =
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.module = shader,
|
||||
.pName = "main",
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *layout,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
}
|
||||
|
||||
vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
|
||||
vk::DescriptorSetLayout& layout) {
|
||||
return device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
@@ -38,7 +38,11 @@ vk::DescriptorPool CreateWrappedDescriptorPool(const Device& device, size_t max_
|
||||
std::initializer_list<VkDescriptorType> types = {
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
|
||||
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(
|
||||
const Device& device, std::initializer_list<VkDescriptorType> types);
|
||||
const Device& device, std::initializer_list<VkDescriptorType> types,
|
||||
VkShaderStageFlags stages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(const Device& device,
|
||||
std::span<const VkDescriptorType> types,
|
||||
VkShaderStageFlags stages);
|
||||
vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool,
|
||||
vk::Span<VkDescriptorSetLayout> layouts);
|
||||
vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
|
||||
@@ -46,6 +50,8 @@ vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
|
||||
vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
|
||||
vk::PipelineLayout& layout,
|
||||
std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
|
||||
vk::Pipeline CreateWrappedComputePipeline(const Device& device, vk::PipelineLayout& layout,
|
||||
VkShaderModule shader);
|
||||
vk::Pipeline CreateWrappedPremultipliedBlendingPipeline(
|
||||
const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
|
||||
std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
|
||||
|
||||
@@ -49,6 +49,23 @@ constexpr VkExtent2D CaptureImageSize{
|
||||
.height = VideoCore::Capture::LinearHeight,
|
||||
};
|
||||
|
||||
#ifdef HAS_LSFG
|
||||
[[nodiscard]] VkExtent2D GuestExtent(std::span<const Tegra::FramebufferConfig> framebuffers) {
|
||||
if (framebuffers.empty()) {
|
||||
return VkExtent2D{};
|
||||
}
|
||||
|
||||
const auto& framebuffer = framebuffers.front();
|
||||
if (framebuffer.crop_rect.IsEmpty()) {
|
||||
return VkExtent2D{.width = framebuffer.width, .height = framebuffer.height};
|
||||
}
|
||||
return VkExtent2D{
|
||||
.width = static_cast<u32>(framebuffer.crop_rect.GetWidth()),
|
||||
.height = static_cast<u32>(framebuffer.crop_rect.GetHeight()),
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr VkExtent3D CaptureImageExtent{
|
||||
.width = VideoCore::Capture::LinearWidth,
|
||||
.height = VideoCore::Capture::LinearHeight,
|
||||
@@ -155,7 +172,11 @@ try
|
||||
present_manager,
|
||||
scheduler,
|
||||
PresentFiltersForAppletCapture)
|
||||
, rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker, scheduler) {
|
||||
, rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker, scheduler)
|
||||
#ifdef HAS_LSFG
|
||||
, frame_gen(memory_allocator, scheduler)
|
||||
#endif
|
||||
{
|
||||
|
||||
if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
|
||||
turbo_mode.emplace(instance, dld);
|
||||
@@ -191,9 +212,28 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
||||
blit_swapchain.DrawToFrame(device, rasterizer, frame, framebuffers,
|
||||
render_window.GetFramebufferLayout(), swapchain.GetImageCount(),
|
||||
swapchain.GetImageViewFormat());
|
||||
|
||||
#ifdef HAS_LSFG
|
||||
void(frame_gen.WantedGenerations(present_manager.MaxExtraFrames()));
|
||||
|
||||
frame_gen.Process(device, frame, swapchain.GetImageFormat(), GuestExtent(framebuffers));
|
||||
|
||||
const size_t generated_frames = frame_gen.GeneratedFrameCount();
|
||||
for (size_t generation = 0; generation < generated_frames; ++generation) {
|
||||
Frame* generated = present_manager.GetRenderFrame();
|
||||
blit_swapchain.PrepareFrame(device, generated, render_window.GetFramebufferLayout());
|
||||
frame_gen.GenerateInto(device, generated, generation);
|
||||
scheduler.Flush(*generated->render_ready);
|
||||
present_manager.Present(generated);
|
||||
}
|
||||
#endif
|
||||
|
||||
scheduler.Flush(*frame->render_ready);
|
||||
|
||||
present_manager.Present(frame);
|
||||
#ifdef HAS_LSFG
|
||||
scheduler.DispatchWork();
|
||||
#endif
|
||||
|
||||
gpu.RendererFrameEndNotify();
|
||||
rasterizer.TickFrame();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -13,6 +13,9 @@
|
||||
#include "common/dynamic_library.h"
|
||||
#include "video_core/host1x/gpu_device_memory_manager.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#ifdef HAS_LSFG
|
||||
#include "video_core/renderer_vulkan/present/frame_gen.h"
|
||||
#endif
|
||||
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
||||
#include "video_core/renderer_vulkan/vk_present_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||
@@ -95,6 +98,9 @@ private:
|
||||
BlitScreen blit_capture;
|
||||
BlitScreen blit_applet;
|
||||
RasterizerVulkan rasterizer;
|
||||
#ifdef HAS_LSFG
|
||||
FrameGen frame_gen;
|
||||
#endif
|
||||
std::optional<TurboMode> turbo_mode;
|
||||
|
||||
Frame applet_frame;
|
||||
|
||||
@@ -85,6 +85,17 @@ void BlitScreen::SetWindowAdaptPass(const Device& device) {
|
||||
}
|
||||
}
|
||||
|
||||
void BlitScreen::PrepareFrame(const Device& device, Frame* frame,
|
||||
const Layout::FramebufferLayout& layout) {
|
||||
if (!window_adapt || (frame->width == layout.width && frame->height == layout.height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
WaitIdle(device);
|
||||
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
|
||||
window_adapt->GetRenderPass());
|
||||
}
|
||||
|
||||
void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer, Frame* frame,
|
||||
std::span<const Tegra::FramebufferConfig> framebuffers,
|
||||
const Layout::FramebufferLayout& layout,
|
||||
|
||||
@@ -60,6 +60,8 @@ public:
|
||||
const Layout::FramebufferLayout& layout, size_t current_swapchain_image_count,
|
||||
VkFormat current_swapchain_view_format);
|
||||
|
||||
void PrepareFrame(const Device& device, Frame* frame, const Layout::FramebufferLayout& layout);
|
||||
|
||||
[[nodiscard]] vk::Framebuffer CreateFramebuffer(const Device& device, const Layout::FramebufferLayout& layout,
|
||||
VkImageView image_view,
|
||||
VkFormat current_view_format);
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#ifdef HAS_LSFG
|
||||
#include "video_core/renderer_vulkan/present/lsfg_common.h"
|
||||
#endif
|
||||
#include "video_core/renderer_vulkan/vk_present_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_swapchain.h"
|
||||
@@ -19,6 +22,23 @@ namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t MAX_FRAMES_IN_FLIGHT = 7;
|
||||
#ifdef HAS_LSFG
|
||||
static_assert(MAX_FRAMES_IN_FLIGHT <= LSFG_MAX_TARGETS);
|
||||
#endif
|
||||
|
||||
bool CanStoreToFrame(const vk::PhysicalDevice& physical_device, VkFormat format) {
|
||||
#ifdef HAS_LSFG
|
||||
if (!Settings::values.frame_gen.GetValue()) {
|
||||
return false;
|
||||
}
|
||||
const VkFormatProperties props{physical_device.GetFormatProperties(format)};
|
||||
return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat format) {
|
||||
const VkFormatProperties props{physical_device.GetFormatProperties(format)};
|
||||
return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT);
|
||||
@@ -110,6 +130,7 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
||||
, swapchain{swapchain_}
|
||||
, surface{surface_}
|
||||
, blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}
|
||||
, storage_supported{CanStoreToFrame(device.GetPhysical(), swapchain.GetImageFormat())}
|
||||
, use_present_thread{Settings::values.async_presentation.GetValue()}
|
||||
{
|
||||
SetImageCount();
|
||||
@@ -127,6 +148,7 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
||||
frames.resize(image_count);
|
||||
for (u32 i = 0; i < frames.size(); i++) {
|
||||
Frame& frame = frames[i];
|
||||
frame.index = i;
|
||||
frame.cmdbuf = vk::CommandBuffer{cmdbuffers[i], device.GetDispatchLoader()};
|
||||
frame.render_ready = dld.CreateSemaphore({
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
@@ -179,6 +201,10 @@ void PresentManager::Present(Frame* frame) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t PresentManager::MaxExtraFrames() const {
|
||||
return image_count - 1;
|
||||
}
|
||||
|
||||
void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
|
||||
VkRenderPass rd) {
|
||||
auto& dld = device.GetLogical();
|
||||
@@ -186,6 +212,9 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat
|
||||
frame->width = width;
|
||||
frame->height = height;
|
||||
|
||||
const VkImageUsageFlags storage_usage =
|
||||
storage_supported ? static_cast<VkImageUsageFlags>(VK_IMAGE_USAGE_STORAGE_BIT) : 0;
|
||||
|
||||
frame->image = memory_allocator.CreateImage({
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -202,7 +231,8 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | storage_usage,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
@@ -233,6 +263,33 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat
|
||||
},
|
||||
});
|
||||
|
||||
frame->storage_view = vk::ImageView{};
|
||||
if (storage_supported) {
|
||||
frame->storage_view = dld.CreateImageView({
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.image = *frame->image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = swapchain.GetImageFormat(),
|
||||
.components =
|
||||
{
|
||||
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.g = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.b = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
},
|
||||
.subresourceRange =
|
||||
{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const VkImageView image_view{*frame->image_view};
|
||||
frame->framebuffer = dld.CreateFramebuffer({
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
@@ -301,7 +358,15 @@ void PresentManager::SetImageCount() {
|
||||
// We cannot have more than 7 images in flight at any given time.
|
||||
// FRAMES_IN_FLIGHT is 8, and the cache TICKS_TO_DESTROY is 8.
|
||||
// Mali drivers will give us 6.
|
||||
image_count = std::min<size_t>(swapchain.GetImageCount(), 7);
|
||||
#ifdef HAS_LSFG
|
||||
const size_t generations = Settings::FrameGenMaxGenerations();
|
||||
const size_t queued_composites = Settings::values.frame_gen_queue_target.GetValue() + 1;
|
||||
image_count =
|
||||
std::clamp<size_t>((generations + 1) * queued_composites, swapchain.GetImageCount(),
|
||||
MAX_FRAMES_IN_FLIGHT);
|
||||
#else
|
||||
image_count = std::min<size_t>(swapchain.GetImageCount(), MAX_FRAMES_IN_FLIGHT);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
|
||||
@@ -28,8 +28,10 @@ class Swapchain;
|
||||
struct Frame {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 index;
|
||||
vk::Image image;
|
||||
vk::ImageView image_view;
|
||||
vk::ImageView storage_view;
|
||||
vk::Framebuffer framebuffer;
|
||||
vk::CommandBuffer cmdbuf;
|
||||
vk::Semaphore render_ready;
|
||||
@@ -60,6 +62,9 @@ public:
|
||||
/// Waits for the present thread to finish presenting all queued frames.
|
||||
void WaitPresent();
|
||||
|
||||
/// How many additional frames can be queued without stalling the render thread
|
||||
[[nodiscard]] size_t MaxExtraFrames() const;
|
||||
|
||||
private:
|
||||
void PresentThread(std::stop_token token);
|
||||
|
||||
@@ -90,6 +95,7 @@ private:
|
||||
std::mutex free_mutex;
|
||||
std::jthread present_thread;
|
||||
bool blit_supported;
|
||||
bool storage_supported;
|
||||
bool use_present_thread;
|
||||
std::size_t image_count{};
|
||||
};
|
||||
|
||||
@@ -146,9 +146,10 @@ public:
|
||||
frame_counter++;
|
||||
auto target_time = start_time + frame_interval * frame_counter;
|
||||
if (target_time >= now) {
|
||||
constexpr auto spin_tail = std::chrono::milliseconds(1);
|
||||
auto sleep_time = target_time - now;
|
||||
if (sleep_time > std::chrono::milliseconds(15)) {
|
||||
std::this_thread::sleep_for(sleep_time - std::chrono::milliseconds(1));
|
||||
if (sleep_time > spin_tail * 2) {
|
||||
std::this_thread::sleep_for(sleep_time - spin_tail);
|
||||
}
|
||||
while (std::chrono::steady_clock::now() < target_time) {
|
||||
std::this_thread::yield();
|
||||
|
||||
@@ -48,6 +48,9 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox,
|
||||
Settings::VSyncMode setting = [has_imm, has_mailbox]() {
|
||||
// Choose Mailbox or Immediate if unlocked and those modes are supported
|
||||
const auto mode = Settings::values.vsync_mode.GetValue();
|
||||
if (Settings::values.frame_gen.GetValue()) {
|
||||
return mode == Settings::VSyncMode::FifoRelaxed ? mode : Settings::VSyncMode::Fifo;
|
||||
}
|
||||
if (Settings::values.use_speed_limit.GetValue() &&
|
||||
Settings::values.current_speed_mode.GetValue() != Settings::SpeedMode::Turbo) {
|
||||
return mode;
|
||||
|
||||
@@ -37,7 +37,8 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
FEATURE(EXT, HostQueryReset, HOST_QUERY_RESET, host_query_reset) \
|
||||
FEATURE(KHR, 8BitStorage, 8BIT_STORAGE, bit8_storage) \
|
||||
FEATURE(KHR, BufferDeviceAddress, BUFFER_DEVICE_ADDRESS, buffer_device_address) \
|
||||
FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore)
|
||||
FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore) \
|
||||
FEATURE(KHR, VulkanMemoryModel, VULKAN_MEMORY_MODEL, vulkan_memory_model)
|
||||
|
||||
#define FOR_EACH_VK_FEATURE_1_3(FEATURE) \
|
||||
FEATURE(EXT, ImageRobustness, IMAGE_ROBUSTNESS, robust_image_access) \
|
||||
@@ -416,6 +417,11 @@ FN_MAX_LIMIT_LIST
|
||||
return features.shader_float16_int8.shaderFloat16;
|
||||
}
|
||||
|
||||
/// Returns true if the device can run shaders built against the Vulkan memory model.
|
||||
bool IsVulkanMemoryModelSupported() const {
|
||||
return features.vulkan_memory_model.vulkanMemoryModel;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports int8 natively.
|
||||
bool IsInt8Supported() const {
|
||||
return features.shader_float16_int8.shaderInt8;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user