mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-18 06:10:35 +00:00
Compare commits
4 Commits
4f4f4a3586
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 265c27f391 | |||
| fb2202d69c | |||
| 2000fdfb7b | |||
| dc95cd09ee |
+6
-4
@@ -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)
|
||||
@@ -169,8 +172,6 @@ if (CXX_CLANG_CL)
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++98-compat-pedantic>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c++98-compat>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c99-compat>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c98-compat>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-c99-extensions>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/EHsc>)
|
||||
# REQUIRED CPU features IN Windows-amd64
|
||||
if (ARCHITECTURE_x86_64)
|
||||
@@ -339,11 +340,12 @@ 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(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, optimized, armv9, native")
|
||||
set(mtune generic)
|
||||
|
||||
if (${YUZU_BUILD_PRESET} STREQUAL "generic")
|
||||
set(march armv8-a)
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "optimized")
|
||||
set(march armv8.2-a+lse+rcpc)
|
||||
elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9")
|
||||
set(march armv9-a)
|
||||
endif()
|
||||
|
||||
@@ -65,12 +65,6 @@
|
||||
"repo": "eden-emulator/discord-rpc",
|
||||
"version": "0d8b2d6a37"
|
||||
},
|
||||
"dxbc": {
|
||||
"bundled": true,
|
||||
"hash": "196d26c07747d7aa2ced6fb1a5ae6f665e9b917de223a540038690b037c70a1eeca51c21d42aa5bec9fff7df0a6656a75ee8b4e2cb4008469f18a6812280f831",
|
||||
"repo": "PancakeTAS/dxbc",
|
||||
"version": "78ab59a8aaeb43cd1b0a5e91ba86722433a10b78"
|
||||
},
|
||||
"enet": {
|
||||
"find_args": "MODULE",
|
||||
"hash": "a0d2fa8c957704dd49e00a726284ac5ca034b50b00d2b20a94fa1bbfbb80841467834bfdc84aa0ed0d6aab894608fd6c86c3b94eee46343f0e6d9c22e391dbf9",
|
||||
|
||||
Vendored
+540
-531
File diff suppressed because it is too large
Load Diff
Vendored
+538
-529
File diff suppressed because it is too large
Load Diff
Vendored
+534
-525
File diff suppressed because it is too large
Load Diff
Vendored
+534
-525
File diff suppressed because it is too large
Load Diff
Vendored
+539
-532
File diff suppressed because it is too large
Load Diff
Vendored
+534
-525
File diff suppressed because it is too large
Load Diff
Vendored
+539
-532
File diff suppressed because it is too large
Load Diff
Vendored
+534
-525
File diff suppressed because it is too large
Load Diff
Vendored
+537
-529
File diff suppressed because it is too large
Load Diff
Vendored
+534
-525
File diff suppressed because it is too large
Load Diff
Vendored
+533
-525
File diff suppressed because it is too large
Load Diff
Vendored
+539
-532
File diff suppressed because it is too large
Load Diff
Vendored
+536
-527
File diff suppressed because it is too large
Load Diff
Vendored
+599
-591
File diff suppressed because it is too large
Load Diff
Vendored
+541
-532
File diff suppressed because it is too large
Load Diff
Vendored
+535
-528
File diff suppressed because it is too large
Load Diff
Vendored
+539
-532
File diff suppressed because it is too large
Load Diff
Vendored
+539
-532
File diff suppressed because it is too large
Load Diff
Vendored
+534
-525
File diff suppressed because it is too large
Load Diff
Vendored
+569
-553
File diff suppressed because it is too large
Load Diff
Vendored
+539
-532
File diff suppressed because it is too large
Load Diff
Vendored
+539
-531
File diff suppressed because it is too large
Load Diff
Vendored
+539
-532
File diff suppressed because it is too large
Load Diff
Vendored
+534
-525
File diff suppressed because it is too large
Load Diff
Vendored
+534
-525
File diff suppressed because it is too large
Load Diff
Vendored
+556
-541
File diff suppressed because it is too large
Load Diff
Vendored
+535
-527
File diff suppressed because it is too large
Load Diff
Vendored
-4
@@ -92,10 +92,6 @@ AddDependentPackages(vulkan-headers vulkan-utility-libraries)
|
||||
# frozen
|
||||
AddJsonPackage(frozen)
|
||||
|
||||
# DXVK's DXBC compiler, used to translate the frame generation shaders
|
||||
# out of a user-supplied Lossless.dll into SPIR-V
|
||||
AddJsonPackage(dxbc)
|
||||
|
||||
# DiscordRPC
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
if (ARCHITECTURE_arm64)
|
||||
|
||||
@@ -559,6 +559,15 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
private fun enableFullscreenImmersive() {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
window.decorView.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
|
||||
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||
controller.systemBarsBehavior =
|
||||
|
||||
+2
@@ -38,6 +38,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
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"),
|
||||
|
||||
@@ -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),
|
||||
|
||||
+89
@@ -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
|
||||
@@ -614,6 +652,57 @@ abstract class SettingsItem(
|
||||
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,
|
||||
|
||||
+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)
|
||||
}
|
||||
|
||||
|
||||
-32
@@ -33,7 +33,6 @@ import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.PathSetting
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.fragments.ProgressDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.PathUtil
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
@@ -115,10 +114,6 @@ class SettingsFragment : Fragment() {
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
|
||||
) { if (it) presenter.loadSettingsList() }
|
||||
settingsViewModel.shouldShowLosslessInstaller.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldShowLosslessInstaller(false) }
|
||||
) { if (it) losslessDllPickerLauncher.launch(arrayOf("*/*")) }
|
||||
settingsViewModel.adapterItemChanged.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setAdapterItemChanged(-1) }
|
||||
@@ -272,33 +267,6 @@ private fun getPlayerIndex(): Int =
|
||||
directoryPickerLauncher.launch(null)
|
||||
}
|
||||
|
||||
private val losslessDllPickerLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
if (uri == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val resultStrings = resources.getStringArray(R.array.losslessDllResults)
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.lossless_scaling_installing,
|
||||
false
|
||||
) { _, _ ->
|
||||
val result = LosslessScalingHelper.install(uri)
|
||||
if (result == LosslessScalingHelper.RESULT_OK) {
|
||||
getString(R.string.lossless_scaling_install_success)
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.lossless_scaling_install_failed,
|
||||
descriptionString = resultStrings[result]
|
||||
)
|
||||
}
|
||||
}.apply {
|
||||
onDialogComplete = { settingsViewModel.setShouldReloadSettingsList(true) }
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
private val directoryPickerLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocumentTree()
|
||||
) { uri ->
|
||||
|
||||
+21
-30
@@ -79,27 +79,6 @@ class SettingsFragmentPresenter(
|
||||
|
||||
private fun addFrameGenSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.frame_gen))
|
||||
|
||||
val installed = LosslessScalingHelper.isInstalled()
|
||||
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = if (installed) {
|
||||
R.string.lossless_scaling_replace
|
||||
} else {
|
||||
R.string.lossless_scaling_install
|
||||
},
|
||||
descriptionId = if (installed) {
|
||||
R.string.lossless_scaling_replace_description
|
||||
} else {
|
||||
R.string.lossless_scaling_install_description
|
||||
},
|
||||
isRunnable = !NativeLibrary.isRunning(),
|
||||
iconId = R.drawable.ic_install
|
||||
) { settingsViewModel.setShouldShowLosslessInstaller(true) }
|
||||
)
|
||||
|
||||
if (!LosslessScalingHelper.isSupportedByGpu()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
@@ -108,15 +87,28 @@ class SettingsFragmentPresenter(
|
||||
isRunnable = false
|
||||
) {}
|
||||
)
|
||||
return@apply
|
||||
}
|
||||
|
||||
if (!installed) {
|
||||
return@apply
|
||||
} 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(BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,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)
|
||||
@@ -324,8 +317,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
||||
|
||||
addFrameGenSettings(this)
|
||||
|
||||
add(HeaderSetting(R.string.advanced))
|
||||
|
||||
add(IntSetting.RENDERER_ACCURACY.key)
|
||||
@@ -348,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)
|
||||
@@ -1342,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
|
||||
|
||||
-7
@@ -36,9 +36,6 @@ class SettingsViewModel : ViewModel() {
|
||||
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
|
||||
private val _shouldReloadSettingsList = MutableStateFlow(false)
|
||||
|
||||
val shouldShowLosslessInstaller: StateFlow<Boolean> get() = _shouldShowLosslessInstaller
|
||||
private val _shouldShowLosslessInstaller = MutableStateFlow(false)
|
||||
|
||||
val sliderProgress: StateFlow<Int> get() = _sliderProgress
|
||||
private val _sliderProgress = MutableStateFlow(-1)
|
||||
|
||||
@@ -88,10 +85,6 @@ class SettingsViewModel : ViewModel() {
|
||||
_shouldReloadSettingsList.value = value
|
||||
}
|
||||
|
||||
fun setShouldShowLosslessInstaller(value: Boolean) {
|
||||
_shouldShowLosslessInstaller.value = value
|
||||
}
|
||||
|
||||
fun setSliderTextValue(value: Float, units: String) {
|
||||
_sliderProgress.value = value.toInt()
|
||||
_sliderTextValue.value = String.format(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
@@ -176,8 +175,14 @@ class HomeSettingsFragment : Fragment() {
|
||||
HomeSetting(
|
||||
R.string.lossless_scaling,
|
||||
R.string.lossless_scaling_description,
|
||||
R.drawable.ic_frames,
|
||||
{ onLosslessScalingClicked() },
|
||||
R.drawable.ic_duck,
|
||||
{
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.LOSSLESS_MANAGER,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
},
|
||||
{ true },
|
||||
0,
|
||||
0,
|
||||
@@ -354,48 +359,6 @@ class HomeSettingsFragment : Fragment() {
|
||||
LosslessScalingHelper.refreshStatus()
|
||||
}
|
||||
|
||||
private fun onLosslessScalingClicked() {
|
||||
if (!LosslessScalingHelper.isInstalled()) {
|
||||
getLosslessDllLauncher.launch(arrayOf("*/*"))
|
||||
return
|
||||
}
|
||||
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.lossless_scaling,
|
||||
descriptionId = R.string.lossless_scaling_installed_description,
|
||||
positiveButtonTitleId = R.string.lossless_scaling_replace,
|
||||
positiveAction = { getLosslessDllLauncher.launch(arrayOf("*/*")) },
|
||||
showNegativeButton = true,
|
||||
negativeButtonTitleId = R.string.lossless_scaling_remove,
|
||||
negativeAction = { LosslessScalingHelper.remove() }
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
private val getLosslessDllLauncher =
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -205,7 +205,7 @@ class SetupFragment : Fragment() {
|
||||
)
|
||||
add(
|
||||
PageButton(
|
||||
R.drawable.ic_frames,
|
||||
R.drawable.ic_duck,
|
||||
R.string.lossless_scaling,
|
||||
R.string.lossless_scaling_setup_description,
|
||||
{
|
||||
|
||||
+1
-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
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -1000,7 +1000,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject
|
||||
FILE* f = std::fopen(CPUINFO_PATH, "r");
|
||||
if (!f) return Common::Android::ToJString(env, result);
|
||||
|
||||
char buf[512];
|
||||
char buf[4096];
|
||||
|
||||
if (f) {
|
||||
std::set<std::string> feature_set;
|
||||
@@ -1031,7 +1031,13 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject
|
||||
bool has_dotprod = feature_set.count("asimddp") || feature_set.count("dotprod");
|
||||
bool has_i8mm = feature_set.count("i8mm");
|
||||
bool has_bf16 = feature_set.count("bf16");
|
||||
bool has_fp16 = feature_set.count("fphp") || feature_set.count("asimdhp");
|
||||
bool has_atomics = feature_set.count("atomics") || feature_set.count("lse");
|
||||
bool has_lse2 = feature_set.count("uscat");
|
||||
bool has_rcpc = feature_set.count("lrcpc");
|
||||
bool has_rcpc2 = feature_set.count("ilrcpc");
|
||||
bool has_flagm = feature_set.count("flagm");
|
||||
bool has_flagm2 = feature_set.count("flagm2");
|
||||
|
||||
std::string features;
|
||||
if (has_neon || has_fp) {
|
||||
@@ -1039,6 +1045,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject
|
||||
if (has_dotprod) features += "+DP";
|
||||
if (has_i8mm) features += "+I8MM";
|
||||
if (has_bf16) features += "+BF16";
|
||||
if (has_fp16) features += "+FP16";
|
||||
}
|
||||
|
||||
if (has_sve) {
|
||||
@@ -1055,6 +1062,19 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject
|
||||
if (has_atomics) {
|
||||
if (!features.empty()) features += " | ";
|
||||
features += "LSE";
|
||||
if (has_lse2) features += "2";
|
||||
}
|
||||
|
||||
if (has_rcpc) {
|
||||
if (!features.empty()) features += " | ";
|
||||
features += "RCpc";
|
||||
if (has_rcpc2) features += "2";
|
||||
}
|
||||
|
||||
if (has_flagm) {
|
||||
if (!features.empty()) features += " | ";
|
||||
features += "FlagM";
|
||||
if (has_flagm2) features += "2";
|
||||
}
|
||||
|
||||
if (!features.empty()) {
|
||||
@@ -1429,20 +1449,36 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_installKeys(JNIEnv* env, jclass clazz
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -291,6 +291,22 @@
|
||||
<string name="gpu_driver_fetcher">أداة جلب برامج تشغيل وحدة المعالجة الرسومية</string>
|
||||
<string name="gpu_driver_manager">إدارة برامج تشغيل وحدة معالجة الرسومات</string>
|
||||
<string name="install_gpu_driver_description">تثبيت برامج تشغيل بديلة لأداء أو دقة أفضل</string>
|
||||
<string name="frame_gen">توليد الإطار</string>
|
||||
<string name="frame_gen_per_game_description">تكوين إعدادات إنشاء الإطارات لهذه اللعبة</string>
|
||||
<string name="frame_gen_description">قم بإدراج الإطارات المُستكملة بين الإطارات المُعالجة باستخدام تقنية التحجيم بدون فقدان الجودة. يُفرض هذا الخيار عرض الإطارات وفقًا لترتيب FIFO عند تفعيله.</string>
|
||||
<string name="frame_gen_multiplier">مضاعف الإطارات</string>
|
||||
<string name="frame_gen_multiplier_description">عدد الإطارات المطلوب عرضها لكل إطار مُعالَج. تتطلب القيم الأعلى وقت معالجة رسوميات أكبر. طلب عدد إطارات يفوق قدرة الشاشة على عرضه سيؤدي إلى إبطاء المحاكاة.</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">معدل الإطارات المستهدف</string>
|
||||
<string name="frame_gen_target_rate_off">استخدم مضاعفًا ثابتًا</string>
|
||||
<string name="frame_gen_target_rate_60">60 إطارًا في الثانية</string>
|
||||
<string name="frame_gen_target_rate_90">90 إطارًا في الثانية</string>
|
||||
<string name="frame_gen_target_rate_120">120 إطارًا في الثانية</string>
|
||||
<string name="frame_gen_target_rate_144">144 إطارًا في الثانية</string>
|
||||
<string name="frame_gen_target_rate_165">165 إطارًا في الثانية</string>
|
||||
<string name="frame_gen_queue_target">هدف قائمة انتظار الإطارات</string>
|
||||
<string name="advanced_settings">الإعدادات المتقدمة</string>
|
||||
<string name="settings_description">ضبط إعدادات المحاكي</string>
|
||||
<string name="search_recently_played">تم تشغيلها مؤخرًا</string>
|
||||
@@ -444,8 +460,8 @@
|
||||
<string name="set_custom_rtc">تعيين ساعة مخصصة في الوقت الحقيقي</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">رفع تردد المعالج</string>
|
||||
<string name="fast_cpu_time_description">يجبر وحدة المعالجة المركزية المحاكاة على العمل بتردد أعلى، مما يقلل بعض محددات معدل الإطارات. استخدم وضع التعزيز (1700 ميجاهرتز) لتشغيلها عند أعلى تردد أصلي لجهاز Switch، أو وضع السرعة (2000 ميجاهرتز) لتشغيلها بتردد مضاعف.</string>
|
||||
<string name="fast_cpu_time">ترددات وحدة المعالجة المركزية</string>
|
||||
<string name="fast_cpu_time_description">يرفع هذا الإعداد تردد المعالج المُحاكى، مما يُزيل بعض مُحددات معدل الإطارات. قد تُلاحظ المعالجات الأضعف انخفاضًا في الأداء، وقد تعمل بعض الألعاب بشكل غير صحيح.</string>
|
||||
<string name="custom_cpu_ticks">تخصيص دورات المعالج</string>
|
||||
<string name="custom_cpu_ticks_description">قم بتعيين قيمة مخصصة لدورات المعالج. القيم الأعلى يمكن أن تزيد من الأداء، ولكنها قد تتسبب أيضًا في تجميد اللعبة. يوصى باستخدام نطاق 77-21000.</string>
|
||||
<string name="cpu_ticks">دورات</string>
|
||||
@@ -506,8 +522,8 @@
|
||||
|
||||
<string name="hacks">اختراقات</string>
|
||||
|
||||
<string name="fast_gpu_time">وقت معالجة الرسومات السريع</string>
|
||||
<string name="fast_gpu_time_description">يُجبر هذا الخيار معظم الألعاب على العمل بأعلى دقة عرض أصلية. استخدم 256 للحصول على أقصى أداء و512 للحصول على أعلى جودة رسومات.</string>
|
||||
<string name="fast_gpu_time">سرعات معالج الرسوميات</string>
|
||||
<string name="fast_gpu_time_description">يجعل اللعبة تعتقد أن عمل وحدة معالجة الرسومات ينتهي بشكل أسرع مما هو عليه في الواقع، لذلك تتوقف عن خفض الدقة ومسافة العرض لتتناسب مع سرعات جهاز Switch.</string>
|
||||
<string name="skip_cpu_inner_invalidation">تخطي إبطال صلاحية وحدة المعالجة المركزية الداخلية</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">يتخطى بعض عمليات إبطال ذاكرة التخزين المؤقتة من جانب وحدة المعالجة المركزية أثناء تحديثات الذاكرة، مما يقلل من استخدام وحدة المعالجة المركزية ويحسن أداءها. قد يتسبب ذلك في حدوث أعطال أو تعطل في بعض الألعاب.</string>
|
||||
<string name="fix_bloom_effects">إصلاح تأثيرات التوهج</string>
|
||||
@@ -554,6 +570,7 @@
|
||||
|
||||
<!-- Debug settings strings -->
|
||||
<string name="cpu">وحدة المعالج المركزية</string>
|
||||
<string name="clocks">ترددات</string>
|
||||
<string name="use_auto_stub">استخدم الملحق التلقائي</string>
|
||||
<string name="use_auto_stub_description">استبدال الخدمات والوظائف المفقودة تلقائيًا. قد يؤدي ذلك إلى تحسين التوافق، ولكنه قد يتسبب في حدوث أعطال ومشكلات في الاستقرار.</string>
|
||||
|
||||
@@ -953,13 +970,15 @@
|
||||
<string name="memory_6gb">6 جيجابايت (غير آمن)</string>
|
||||
<string name="memory_8gb">8 جيجابايت (غير آمن)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">تعزيز (1700MHz)</string>
|
||||
<string name="clock_fast">سريع (2000MHz)</string>
|
||||
<!-- CPU clock levels -->
|
||||
<string name="clock_normal">طبيعي</string>
|
||||
<string name="clock_boost">طبيعي</string>
|
||||
<string name="clock_fast">كسر السرعة</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="fast_gpu_medium">متوسط (256)</string>
|
||||
<string name="fast_gpu_high">مرتفع (512)</string>
|
||||
<!-- GPU clock levels -->
|
||||
<string name="fast_gpu_normal">طبيعي</string>
|
||||
<string name="fast_gpu_medium">تعزيز</string>
|
||||
<string name="fast_gpu_high">كسر السرعة</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">صغير جدًا (16 ميغابايت)</string>
|
||||
|
||||
@@ -553,10 +553,6 @@
|
||||
<string name="memory_6gb">6GB (نائاسایش)</string>
|
||||
<string name="memory_8gb">8GB (نائاسایش)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">زۆرکردن (1700MHz)</string>
|
||||
<string name="clock_fast">خێرا (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">سیلیزی</string>
|
||||
<string name="temperature_fahrenheit">فارینهایت</string>
|
||||
|
||||
@@ -417,9 +417,6 @@
|
||||
<string name="use_custom_rtc_description">Umožňuje nastavit vlastní hodiny reálného času nezávisle na systému.</string>
|
||||
<string name="set_custom_rtc">Nastavit vlastní RTC</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">Přetaktování CPU</string>
|
||||
<string name="fast_cpu_time_description">Vynutí běh CPU na vyšší frekvenci, což může pomoci obejít některé omezovače FPS. Režim Boost (1700 MHz) odpovídá maximální taktu konzole Switch, režim Fast (2000 MHz) je dvojnásobek základního taktu konzole.</string>
|
||||
<string name="custom_cpu_ticks">Vlastní hodnota CPU cyků</string>
|
||||
<string name="custom_cpu_ticks_description">Nastavte vlastní hodnotu cyklů CPU. Vyšší hodnoty mohou zvýšit výkon, ale mohou také způsobit zamrznutí hry. Doporučený rozsah hodnot je 77–21000.</string>
|
||||
<string name="cpu_ticks">Cykly</string>
|
||||
@@ -466,8 +463,6 @@
|
||||
<string name="enable_buffer_history_description">Umožňuje přístup k předchozím stavům bufferu. Tato volba může u některých her zvýšit kvalitu vykreslování a zlepšit stabilitu výkonu.</string>
|
||||
<string name="hacks">Hacky</string>
|
||||
|
||||
<string name="fast_gpu_time">Rychlé časování GPU</string>
|
||||
<string name="fast_gpu_time_description">Nutí většinu her běžet v nejvyšším nativním rozlišení. Použijte hodnotu 256 pro maximální výkon a 512 pro nejlepší kvalitu.</string>
|
||||
<string name="skip_cpu_inner_invalidation">Přeskočit vnitřní invalidaci CPU</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Přeskočí určité invalidace mezipaměti procesoru při aktualizacích paměti, čímž snižuje vytížení CPU a zvyšuje jeho výkon. Může způsobit chyby nebo pády některých hrách.</string>
|
||||
<string name="renderer_asynchronous_shaders">Asynchronní kompilace shaderů</string>
|
||||
@@ -694,10 +689,6 @@
|
||||
<string name="memory_6gb">6GB (Nebezpečné)</string>
|
||||
<string name="memory_8gb">8GB (Nebezpečné)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Rychlé (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Celsia</string>
|
||||
<string name="temperature_fahrenheit">Fahrenheita</string>
|
||||
|
||||
@@ -429,9 +429,6 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die
|
||||
<string name="use_custom_rtc_description">Ermöglicht Ihnen, eine benutzerdefinierte Echtzeituhr unabhängig von Ihrer aktuellen Systemzeit einzustellen.</string>
|
||||
<string name="set_custom_rtc">Legen Sie eine benutzerdefinierte Echtzeituhr fest</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">CPU-Übertaktung</string>
|
||||
<string name="fast_cpu_time_description">Zwingt die emulierte CPU, mit höherer Taktrate zu laufen, wodurch bestimmte FPS-Begrenzungen reduziert werden. Verwende Boost (1700MHz), um mit der höchsten nativen Taktrate der Switch zu laufen, oder Fast (2000MHz), um mit der doppelten Taktrate zu laufen.</string>
|
||||
<string name="custom_cpu_ticks">Benutzerdefinierte CPU-Ticks</string>
|
||||
<string name="custom_cpu_ticks_description">Legen Sie einen benutzerdefinierten Wert für CPU-Ticks fest. Höhere Werte können die Leistung steigern, aber auch zum Einfrieren des Spiels führen. Ein Bereich von 77–21000 wird empfohlen.</string>
|
||||
<string name="cpu_ticks">Ticks</string>
|
||||
@@ -476,8 +473,6 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die
|
||||
<string name="renderer_reactive_flushing_description">Verbessert die Genauigkeit in einigen Spielen.</string>
|
||||
<string name="hacks">Hacks</string>
|
||||
|
||||
<string name="fast_gpu_time">Schnelle GPU-Zeit</string>
|
||||
<string name="fast_gpu_time_description">Erzwingt bei den meisten Spielen die höchste native Auflösung. Verwende 256 für maximale Leistung und 512 für maximale Grafikqualität</string>
|
||||
<string name="skip_cpu_inner_invalidation">CPU-interne Invalidierung überspringen</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Überspringt bestimmte Cache-Invalidierungen auf CPU-Seite während Speicherupdates, reduziert die CPU-Auslastung und verbessert die Leistung. Kann in einigen Spielen zu Fehlern oder Abstürzen führen.</string>
|
||||
<string name="renderer_asynchronous_shaders">Asynchrone Shader</string>
|
||||
@@ -879,14 +874,6 @@ Wirklich fortfahren?</string>
|
||||
<string name="memory_6gb">6 GB (Unsicher)</string>
|
||||
<string name="memory_8gb">8 GB (Unsicher)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Schnell (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="fast_gpu_medium">Mittel (256)</string>
|
||||
<string name="fast_gpu_high">Hoch (512)</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">Sehr klein (16 MB)</string>
|
||||
<string name="gpu_texturesizeswizzle_small">Klein (32 MB)</string>
|
||||
|
||||
@@ -291,6 +291,23 @@
|
||||
<string name="gpu_driver_fetcher">Obtenedor de controladores de la GPU</string>
|
||||
<string name="gpu_driver_manager">Gestor de controladores de la GPU</string>
|
||||
<string name="install_gpu_driver_description">Instale los controladores alternativos para obtener un posible mejor rendimiento o precisión</string>
|
||||
<string name="frame_gen">Generación de fotograma</string>
|
||||
<string name="frame_gen_multiplier">Multiplicador de fotograma</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_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_1">Equilibrado (1 fotograma)</string>
|
||||
<string name="frame_generation_support">Generación de fotograma</string>
|
||||
<string name="frame_generation_supported">Soportado</string>
|
||||
<string name="lossless_scaling">Escalado sin pérdidas</string>
|
||||
<string name="lossless_scaling_installed">Instalado</string>
|
||||
<string name="lossless_scaling_not_installed">No instalado</string>
|
||||
<string name="lossless_scaling_missing">Lossless.dll no instalado</string>
|
||||
<string name="advanced_settings">Ajustes avanzados</string>
|
||||
<string name="settings_description">Configurar los ajustes del emulador</string>
|
||||
<string name="search_recently_played">Jugado recientemente</string>
|
||||
@@ -437,9 +454,6 @@
|
||||
<string name="use_custom_rtc_description">Le permite tener un reloj personalizado en tiempo real diferente de la hora de su sistema.</string>
|
||||
<string name="set_custom_rtc">Configurar RTC personalizado</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">Overclock de la CPU</string>
|
||||
<string name="fast_cpu_time_description">Fuerza a la CPU emulada a ejecutarser a una velocidad de reloj más alta, lo cual reduce ciertos limitadores de fotogramas por segundo. Usa Boost (1700 MHz) para ejecutar a la velocidad de reloj nativa más alta de la Switch, o Fast (2000 MHz) para ejecutar a una velocidad doble de reloj.</string>
|
||||
<string name="custom_cpu_ticks">Ticks de CPU personalizados</string>
|
||||
<string name="custom_cpu_ticks_description">Establezca un valor personalizado de los ciclos de la CPU. Los valores más altos pueden aumentar el rendimiento, pero también pueden hacer que el juego se congele. Se recomienda un rango de 77–21000.</string>
|
||||
<string name="cpu_ticks">Ciclos</string>
|
||||
@@ -498,8 +512,6 @@
|
||||
|
||||
<string name="hacks">Hacks</string>
|
||||
|
||||
<string name="fast_gpu_time">Tiempo rápido de la GPU</string>
|
||||
<string name="fast_gpu_time_description">Fuerza a la mayoría de los juegos a ejecutarse a su resolución nativa más alta. Usa 256 para un máximo rendimiento y 512 para una fidelidad gráfica óptima.</string>
|
||||
<string name="skip_cpu_inner_invalidation">Omitir invalidación interna de la CPU</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Omite ciertas invalidaciones de caché de la CPU durante las actualizaciones de memoria, lo que reduce el uso de la CPU y mejora su rendimiento. Esto puede causar fallos o bloqueos en algunos juegos.</string>
|
||||
<string name="fix_bloom_effects">Arreglar los efectos de resplandor</string>
|
||||
@@ -945,13 +957,13 @@
|
||||
<string name="memory_6gb">6GB (Inseguro)</string>
|
||||
<string name="memory_8gb">8GB (Inseguro)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Impulsado (1700MHz)</string>
|
||||
<string name="clock_fast">Rápido (2000MHz)</string>
|
||||
<!-- CPU clock levels -->
|
||||
<string name="clock_normal">Normal</string>
|
||||
<string name="clock_fast">Overclock</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="fast_gpu_medium">Medio (256)</string>
|
||||
<string name="fast_gpu_high">Alto (512)</string>
|
||||
<!-- GPU clock levels -->
|
||||
<string name="fast_gpu_normal">Normal</string>
|
||||
<string name="fast_gpu_high">Overclock</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">Muy pequeño (16 MB)</string>
|
||||
|
||||
@@ -432,9 +432,6 @@
|
||||
<string name="use_custom_rtc_description">Vous permet de définir une horloge en temps réel personnalisée distincte de l\'heure actuelle de votre système.</string>
|
||||
<string name="set_custom_rtc">Définir l\'horloge RTC personnalisée</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">Overclock du CPU</string>
|
||||
<string name="fast_cpu_time_description">Force le CPU émulé à fonctionner à une fréquence plus élevée, en contournant certaines limitations de FPS. Utilisez Boost (1700 MHz) pour atteindre la fréquence native maximale de la Nintendo Switch, ou Fast (2000 MHz) pour doubler la fréquence. C’est un “hack”. Certains chipsets moins puissants peuvent voir leurs performances réduites et les jeux peuvent se comporter différemment.</string>
|
||||
<string name="custom_cpu_ticks">Ticks CPU personnalisés</string>
|
||||
<string name="custom_cpu_ticks_description">Définissez une valeur personnalisée de ticks CPU. Des valeurs plus élevées peuvent améliorer les performances, mais peuvent aussi provoquer des gels du jeu. Une plage de 77 à 21000 est recommandée.</string>
|
||||
<string name="cpu_ticks">Ticks</string>
|
||||
@@ -482,7 +479,6 @@
|
||||
<string name="enable_buffer_history">Activer l\'historique du tampon</string>
|
||||
<string name="hacks">Contournements</string>
|
||||
|
||||
<string name="fast_gpu_time">Temps GPU rapide</string>
|
||||
<string name="skip_cpu_inner_invalidation">Ignorer l\'invalidation interne du CPU</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Ignore certaines invalidations de cache côté CPU lors des mises à jour mémoire, réduisant l\'utilisation du CPU et améliorant ses performances. Peut causer des bugs ou plantages sur certains jeux.</string>
|
||||
<string name="emulate_bgr565">Emuler BGR565</string>
|
||||
@@ -892,14 +888,6 @@
|
||||
<string name="memory_6gb">6 Go (Dangereux)</string>
|
||||
<string name="memory_8gb">8 Go (Dangereux)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Rapide (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="fast_gpu_medium">Moyen (256)</string>
|
||||
<string name="fast_gpu_high">Élevé (512)</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">Très petit (16 Mo)</string>
|
||||
<string name="gpu_texturesizeswizzle_small">Petit (32 Mo)</string>
|
||||
|
||||
@@ -592,10 +592,6 @@
|
||||
<string name="memory_6gb">6GB (לא בטוח)</string>
|
||||
<string name="memory_8gb">8GB (לא בטוח)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">מהיר (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">צלזיוס</string>
|
||||
<string name="temperature_fahrenheit">פרנהייט</string>
|
||||
|
||||
@@ -681,10 +681,6 @@
|
||||
<string name="memory_6gb">6GB (Nem biztonságos)</string>
|
||||
<string name="memory_8gb">8GB (Nem biztonságos)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Gyors (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Celsius</string>
|
||||
<string name="temperature_fahrenheit">Fahrenheit</string>
|
||||
|
||||
@@ -727,10 +727,6 @@
|
||||
<string name="memory_6gb">6GB (Tidak Aman)</string>
|
||||
<string name="memory_8gb">8GB (Tidak Aman)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Cepat (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Celsius</string>
|
||||
<string name="temperature_fahrenheit">Fahrenheit</string>
|
||||
|
||||
@@ -764,10 +764,6 @@
|
||||
<string name="memory_6gb">6GB (Non sicuro)</string>
|
||||
<string name="memory_8gb">8GB (Non sicuro)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Veloce (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Celsius</string>
|
||||
<string name="temperature_fahrenheit">Fahrenheit</string>
|
||||
|
||||
@@ -590,10 +590,6 @@
|
||||
<string name="memory_6gb">6GB (安全でない)</string>
|
||||
<string name="memory_8gb">8GB (安全でない)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">ブースト (1700MHz)</string>
|
||||
<string name="clock_fast">高速 (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">摂氏</string>
|
||||
<string name="temperature_fahrenheit">華氏</string>
|
||||
|
||||
@@ -642,10 +642,6 @@
|
||||
<string name="memory_6gb">6GB (안전하지 않음)</string>
|
||||
<string name="memory_8gb">8GB (안전하지 않음)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">부스트 (1700MHz)</string>
|
||||
<string name="clock_fast">빠름 (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">섭씨</string>
|
||||
<string name="temperature_fahrenheit">화씨</string>
|
||||
|
||||
@@ -566,10 +566,6 @@
|
||||
<string name="memory_6gb">6GB (Usikkert)</string>
|
||||
<string name="memory_8gb">8GB (Usikkert)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Rask (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Celsius</string>
|
||||
<string name="temperature_fahrenheit">Fahrenheit</string>
|
||||
|
||||
@@ -417,9 +417,6 @@
|
||||
<string name="use_custom_rtc_description">Ta opcja pozwala na wybranie własnych ustawień czasu używanych w czasie emulacji, innych niż czas systemu Android.</string>
|
||||
<string name="set_custom_rtc">Ustaw niestandardowy czas RTC</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">Podkręcanie CPU</string>
|
||||
<string name="fast_cpu_time_description">Wymusza działanie emulowanego CPU z wyższym taktowaniem, ograniczając niektóre blokady FPS. Użyj Boost (1700 MHz), aby działać z najwyższym natywnym taktowaniem Switcha, lub Fast (2000 MHz), aby działać z taktowaniem 2×.</string>
|
||||
<string name="custom_cpu_ticks">Niestandardowe takty CPU</string>
|
||||
<string name="custom_cpu_ticks_description">Ustaw niestandardową wartość taktów CPU. Wyższe wartości mogą zwiększyć wydajność, ale mogą też powodować zawieszanie się gry. Zalecany zakres to 77–21000.</string>
|
||||
<string name="cpu_ticks">Takty</string>
|
||||
@@ -464,8 +461,6 @@
|
||||
<string name="renderer_reactive_flushing_description">Poprawia jakość renderowania w kilku grach, kosztem wydajności.</string>
|
||||
<string name="hacks">Hacki</string>
|
||||
|
||||
<string name="fast_gpu_time">Szybki czas GPU</string>
|
||||
<string name="fast_gpu_time_description">Wymusza działanie większości gier w ich najwyższej natywnej rozdzielczości. Ustaw 256 dla maksymalnej wydajności, a 512 dla maksymalnej jakości grafiki.</string>
|
||||
<string name="skip_cpu_inner_invalidation">Pomiń wewnętrzne unieważnienie CPU</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Pomija niektóre unieważnienia pamięci podręcznej po stronie CPU podczas aktualizacji pamięci, zmniejszając użycie CPU i poprawiając jego wydajność. Może powodować błędy lub awarie w niektórych grach.</string>
|
||||
<string name="renderer_asynchronous_shaders">Wyłącz synchronizację shaderów</string>
|
||||
@@ -866,14 +861,6 @@
|
||||
<string name="memory_6gb">6GB (Niebezpieczne)</string>
|
||||
<string name="memory_8gb">8GB (Niebezpieczne)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Szybkie (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="fast_gpu_medium">Średnie (256)</string>
|
||||
<string name="fast_gpu_high">Wysokie (512)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Celsjusza</string>
|
||||
<string name="temperature_fahrenheit">Fahrenheita</string>
|
||||
|
||||
@@ -409,8 +409,6 @@
|
||||
<string name="use_custom_rtc_description">Permite a você configurar um relógio em tempo real separado do relógio do seu dispositivo.</string>
|
||||
<string name="set_custom_rtc">Definir um relógio em tempo real personalizado</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">Overclock da CPU</string>
|
||||
<string name="custom_cpu_ticks">Ciclos da CPU Personalizados</string>
|
||||
<string name="custom_cpu_ticks_description">Defina um valor personalizado de ciclos da CPU. Valores mais altos podem aumentar o desempenho, mas também podem fazer o jogo travar. Recomenda-se usar valores entre 77 e 21000.</string>
|
||||
<string name="cpu_ticks">Ciclos</string>
|
||||
@@ -821,10 +819,6 @@
|
||||
<string name="memory_6gb">6GB (Inseguro)</string>
|
||||
<string name="memory_8gb">8GB (Inseguro)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Rápido (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Celsius</string>
|
||||
<string name="temperature_fahrenheit">Fahrenheit</string>
|
||||
|
||||
@@ -693,10 +693,6 @@ uma tentativa de mapeamento automático</string>
|
||||
<string name="memory_6gb">6GB (Inseguro)</string>
|
||||
<string name="memory_8gb">8GB (Inseguro)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Rápido (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Celsius</string>
|
||||
<string name="temperature_fahrenheit">Fahrenheit</string>
|
||||
|
||||
@@ -439,9 +439,6 @@
|
||||
<string name="use_custom_rtc_description">Позволяет установить пользовательские часы реального времени отдельно от текущего системного времени.</string>
|
||||
<string name="set_custom_rtc">Установить пользовательский RTC</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">Разгон ЦП</string>
|
||||
<string name="fast_cpu_time_description">Принудительно повышает тактовую частоту эмулируемого ЦП, снижая влияние некоторых ограничителей FPS. Используйте Разгон (1700 МГц) для работы на максимальной штатной частоте Switch или Быстрая (2000 МГц) для удвоенной частоты.</string>
|
||||
<string name="custom_cpu_ticks">Пользовательские такты ЦП</string>
|
||||
<string name="custom_cpu_ticks_description">Установите пользовательское значение тактов ЦП. Более высокие значения могут увеличить производительность, но также могут вызвать зависание игры. Рекомендуется диапазон 77–21000.</string>
|
||||
<string name="cpu_ticks">Такты</string>
|
||||
@@ -502,8 +499,6 @@
|
||||
|
||||
<string name="hacks">Хаки</string>
|
||||
|
||||
<string name="fast_gpu_time">Быстрое время ГПУ</string>
|
||||
<string name="fast_gpu_time_description">Принудительно запускает большинство игр в их максимальном нативном разрешении. Используйте значение 256 для максимальной производительности и 512 для максимального качества графики.</string>
|
||||
<string name="skip_cpu_inner_invalidation">Пропустить внутреннюю инвалидацию ЦП</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Пропускает некоторые инвалидации кэша на стороне ЦП при обновлениях памяти, уменьшая нагрузку на процессор и повышая производительность. Может вызывать сбои в некоторых играх.</string>
|
||||
<string name="fix_bloom_effects">Исправить эффекты размытия</string>
|
||||
@@ -949,14 +944,6 @@
|
||||
<string name="memory_6gb">6 ГБ (Небезопасно)</string>
|
||||
<string name="memory_8gb">8 ГБ (Небезопасно)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Разгон (1700MHz)</string>
|
||||
<string name="clock_fast">Быстрая (2000MHz)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="fast_gpu_medium">Среднее (256)</string>
|
||||
<string name="fast_gpu_high">Высокое (512)</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">Очень малый (16 МБ)</string>
|
||||
<string name="gpu_texturesizeswizzle_small">Малый (32 МБ)</string>
|
||||
|
||||
@@ -690,10 +690,6 @@
|
||||
<string name="memory_6gb">6ГБ (несигуран)</string>
|
||||
<string name="memory_8gb">8ГБ (несигурно)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Боост (1700МХз)</string>
|
||||
<string name="clock_fast">Брзи (2000МХз)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Целзијус</string>
|
||||
<string name="temperature_fahrenheit">Фаренхајт</string>
|
||||
|
||||
@@ -439,9 +439,6 @@
|
||||
<string name="use_custom_rtc_description">Дозволяє встановити власний час (Real-time clock, або RTC), відмінний від системного.</string>
|
||||
<string name="set_custom_rtc">Встановити RTC</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">Розгін ЦП</string>
|
||||
<string name="fast_cpu_time_description">Примушує емульований ЦП працювати з вищою тактовою частотою, послаблюючи деякі обмежувачі частоти кадрів. Використовуйте «Підвищення (1700 МГц)», щоб емулювати максимальну тактову частоту справжнього Switch, або «Швидко (2000 МГц)», щоб подвоїти частоту.</string>
|
||||
<string name="custom_cpu_ticks">Користувацькі такти CPU</string>
|
||||
<string name="custom_cpu_ticks_description">Встановіть користувацьке значення тактів CPU. Вищі значення можуть покращити продуктивність, але також можуть спричинити зависання гри. Рекомендується діапазон 77–21000.</string>
|
||||
<string name="cpu_ticks">Такти</string>
|
||||
@@ -497,8 +494,6 @@
|
||||
|
||||
<string name="hacks">Обхідні рішення</string>
|
||||
|
||||
<string name="fast_gpu_time">Швидкий час роботи ГП</string>
|
||||
<string name="fast_gpu_time_description">Примушує більшість ігор працювати на їхній максимальній нативній роздільності. Використовуйте 256 для максимальної продуктивності та 512 для найкращої якості.</string>
|
||||
<string name="skip_cpu_inner_invalidation">Пропустити внутрішнє інвалідування CPU</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">Пропускає деякі інвалідації кешу на стороні CPU під час оновлення пам\'яті, зменшуючи навантаження на процесор і покращуючи продуктивність. Може спричинити збої в деяких іграх.</string>
|
||||
<string name="fix_bloom_effects">Виправити ефекти світіння</string>
|
||||
@@ -938,14 +933,6 @@
|
||||
<string name="memory_6gb">6 ГБ (Небезпечно)</string>
|
||||
<string name="memory_8gb">8 ГБ (Небезпечно)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Підвищення (1700 МГц)</string>
|
||||
<string name="clock_fast">Швидко (2000 МГц)</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="fast_gpu_medium">Середньо (256)</string>
|
||||
<string name="fast_gpu_high">Високо (512)</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">Дуже малий (16 МБ)</string>
|
||||
<string name="gpu_texturesizeswizzle_small">Малий (32 МБ)</string>
|
||||
|
||||
@@ -562,10 +562,6 @@
|
||||
<string name="memory_6gb">6GB (Không an toàn)</string>
|
||||
<string name="memory_8gb">8GB (Không an toàn)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">Boost (1700MHz)</string>
|
||||
<string name="clock_fast">Nhanh (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">Celsius</string>
|
||||
<string name="temperature_fahrenheit">Fahrenheit</string>
|
||||
|
||||
@@ -434,8 +434,8 @@
|
||||
<string name="set_custom_rtc">设置自定义系统时间</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">CPU 超频</string>
|
||||
<string name="fast_cpu_time_description">强制模拟的 CPU 以更高的时钟频率运行,从而消除某些帧率限制。使用“升频”(1700MHz)以在 Switch 的最高原生时钟频率运行,或使用“快速”(2000MHz)以 2 倍时钟频率运行。</string>
|
||||
<string name="fast_cpu_time">CPU 时钟频率</string>
|
||||
<string name="fast_cpu_time_description">提高模拟 CPU 报告的时钟频率,从而消除部分帧率限制。性能较弱的 CPU 可能会出现可见性能下降,甚至会导致某些游戏运行异常。</string>
|
||||
<string name="custom_cpu_ticks">自定义 CPU 时钟周期</string>
|
||||
<string name="custom_cpu_ticks_description">设定自定义的 CPU 时钟周期值。更高的值可以提升性能,但也可能导致游戏冻结卡死。建议的赋值范围为77-21000。</string>
|
||||
<string name="cpu_ticks">时钟周期</string>
|
||||
@@ -496,8 +496,8 @@
|
||||
|
||||
<string name="hacks">Hacks</string>
|
||||
|
||||
<string name="fast_gpu_time">快速 GPU 时间</string>
|
||||
<string name="fast_gpu_time_description">强制大多数游戏以其最高原生分辨率运行。设置为 256 可获得最佳性能,设置为 512 可获得最佳画面保真度。</string>
|
||||
<string name="fast_gpu_time">GPU 时钟频率</string>
|
||||
<string name="fast_gpu_time_description">通过让游戏误以为 GPU 的处理速度比实际更快,以避免游戏为了适应 Switch 的时钟频率而降低分辨率和渲染距离。</string>
|
||||
<string name="skip_cpu_inner_invalidation">跳过CPU内部无效化</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">在更新内存时跳过某些 CPU 端的缓存失效操作,从而降低 CPU 占用率并提升性能。可能会在某些游戏中引发故障点或崩溃。</string>
|
||||
<string name="fix_bloom_effects">修复 Bloom 效果</string>
|
||||
@@ -544,6 +544,7 @@
|
||||
|
||||
<!-- Debug settings strings -->
|
||||
<string name="cpu">CPU</string>
|
||||
<string name="clocks">时钟频率</string>
|
||||
<string name="use_auto_stub">使用自动占位处理</string>
|
||||
<string name="use_auto_stub_description">自动为缺失的服务和功能生成占位代码。这可能会提高兼容性,但也可能导致崩溃和稳定性问题</string>
|
||||
|
||||
@@ -943,13 +944,15 @@
|
||||
<string name="memory_6gb">6GB (不安全)</string>
|
||||
<string name="memory_8gb">8GB (不安全)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">加速 (1700MHz)</string>
|
||||
<string name="clock_fast">快速 (2000MHz)</string>
|
||||
<!-- CPU clock levels -->
|
||||
<string name="clock_normal">正常</string>
|
||||
<string name="clock_boost">升频</string>
|
||||
<string name="clock_fast">超频</string>
|
||||
|
||||
<!-- GPU overclock factors -->
|
||||
<string name="fast_gpu_medium">中 (256)</string>
|
||||
<string name="fast_gpu_high">高 (512)</string>
|
||||
<!-- GPU clock levels -->
|
||||
<string name="fast_gpu_normal">正常</string>
|
||||
<string name="fast_gpu_medium">升频</string>
|
||||
<string name="fast_gpu_high">超频</string>
|
||||
|
||||
<!-- GPU swizzle texture size -->
|
||||
<string name="gpu_texturesizeswizzle_verysmall">极小 (16 MB)</string>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<string name="use_sync_core">同步核心速度</string>
|
||||
<string name="use_sync_core_description">將核心速度與最大速度百分比同步,在不改變遊戲實際速度的情況下提高效能</string>
|
||||
<string name="cpuopt_unsafe_host_mmu">啟用主機 MMU 模擬</string>
|
||||
<string name="cpuopt_unsafe_host_mmu_description">此最佳化可加速來賓程式的記憶體存取。啟用後,來賓記憶體讀取/寫入將直接在記憶體中執行並利用主機的 MMU。停用此功能將強制所有記憶體存取使用軟體 MMU 模擬。</string>
|
||||
<string name="cpuopt_unsafe_host_mmu_description">此最佳化可加速客體程式的記憶體存取。啟用後,客體記憶體讀取/寫入將直接在記憶體中執行並利用主機的 MMU。停用此功能將強制所有記憶體存取使用軟體 MMU 模擬。</string>
|
||||
<string name="debug_knobs">偵錯開關</string>
|
||||
<string name="debug_knobs_description">僅限開發用途</string>
|
||||
<string name="debug_knobs_hint">0到65535</string>
|
||||
@@ -192,7 +192,7 @@
|
||||
<string name="multiplayer_nickname_invalid">使用者名稱無效,請在系統→網路中檢查設定</string>
|
||||
<string name="multiplayer_token_error">必須為48個字元,且僅包含小寫字母a-z</string>
|
||||
<string name="multiplayer_port_error">埠號需為1-65535</string>
|
||||
<string name="cancel">略過</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="ok">確定</string>
|
||||
<string name="refresh">重新整理</string>
|
||||
<string name="room_list">房間列表</string>
|
||||
@@ -302,7 +302,7 @@
|
||||
<string name="notification_no_directory_link">無法開啟 Eden 目錄</string>
|
||||
<string name="notification_no_directory_link_description">請使用檔案管理員的側邊面板手動定位到使用者資料夾</string>
|
||||
<string name="manage_save_data">管理儲存資料</string>
|
||||
<string name="manage_save_data_description">導入/導出遊戲的儲存資料\n\n壓縮檔中首個子資料夾名稱必須為遊戲 ID</string>
|
||||
<string name="manage_save_data_description">導入/導出遊戲的儲存資料\n壓縮檔必須為.zip格式\n壓縮檔中首個子資料夾名稱必須為遊戲 ID</string>
|
||||
<string name="import_save_warning">導入儲存資料</string>
|
||||
<string name="import_save_warning_description">這將會以提供的檔案覆寫現有的該遊戲儲存資料,您確定要繼續嗎?</string>
|
||||
<string name="save_files_importing">正在導入儲存資料…</string>
|
||||
@@ -434,8 +434,8 @@
|
||||
<string name="set_custom_rtc">設定自訂 RTC</string>
|
||||
|
||||
<!-- CPU -->
|
||||
<string name="fast_cpu_time">CPU 超頻</string>
|
||||
<string name="fast_cpu_time_description">強制模擬 CPU 以更高的時脈運作,減少某些 FPS 限制。使用 加速 (1700MHz) 以 Switch 的最高原生時脈執行,或 高速 (2000MHz) 以雙倍時脈執行</string>
|
||||
<string name="fast_cpu_time">CPU 時脈</string>
|
||||
<string name="fast_cpu_time_description">提高模擬 CPU 回報的時脈,可以減少某些 FPS 限制。較弱的 CPU 可能會出現效能下降的情況,部分遊戲可能會出現異常</string>
|
||||
<string name="custom_cpu_ticks">自訂CPU時脈</string>
|
||||
<string name="custom_cpu_ticks_description">自訂CPU時脈。更高的值可能提高效能,但也可能導致遊戲卡死。建議範圍為77-21000。</string>
|
||||
<string name="cpu_ticks">時脈</string>
|
||||
@@ -481,15 +481,57 @@
|
||||
<string name="use_disk_shader_cache_description">將產生的著色器快取儲存至硬碟以減少中斷</string>
|
||||
<string name="renderer_force_max_clock">強制使用最大時脈 (僅限Adreno)</string>
|
||||
<string name="renderer_force_max_clock_description">強制 GPU 以可能的最大時脈執行 (熱溫限制仍會被套用)</string>
|
||||
<string name="renderer_asynchronous_gpu_emulation">GPU 非同步模擬</string>
|
||||
<string name="renderer_asynchronous_gpu_emulation_description">此修改可透過使用 GPU 非同步模擬來提升性能,不過可能會導致圖形問題且會提高在進行時序相關操作時當機的機率</string>
|
||||
<string name="renderer_async_presentation">非同步呈現</string>
|
||||
<string name="renderer_async_presentation_description">此修改可以透過將渲染移至單獨的 CPU 執行緒來提升性能,但可能會導致圖形問題</string>
|
||||
<string name="renderer_reactive_flushing">使用重新啟用排清</string>
|
||||
<string name="renderer_reactive_flushing_description">犧牲效能,以改善部分遊戲的轉譯準確度。</string>
|
||||
<string name="enable_buffer_history">啟用緩衝區歷史</string>
|
||||
<string name="enable_buffer_history_description">允許存取先前的緩衝區狀態。此選項可能會改善部分遊戲的渲染品質與效能穩定性</string>
|
||||
<string name="enable_gpu_buffer_readback">啟用 GPU 緩衝區讀回</string>
|
||||
<string name="enable_gpu_buffer_readback_description">透過在上傳之前先將 GPU 修改過的緩衝區資料讀回來保存資料,部分遊戲需要啟用此功能才能正常渲染遊戲特效。如果硬體無法負荷可能會導致錯誤</string>
|
||||
<string name="use_optimized_vertex_buffers">最佳化頂點緩衝區</string>
|
||||
<string name="use_optimized_vertex_buffers_description">啟用最佳化的頂點緩衝區綁定。需要安裝 Mesa 26.0+ Turnip drivers/Qualcomm drivers。使用舊版 Turnip drivers 會導致當機 (25.3版和更低的版本)</string>
|
||||
|
||||
<string name="hacks">修改</string>
|
||||
|
||||
<string name="fast_gpu_time">GPU 時脈</string>
|
||||
<string name="fast_gpu_time_description">讓遊戲誤以為 GPU 工作能更快完成,避免遊戲為了配合 Switch 的時脈而降低解析度與渲染距離</string>
|
||||
<string name="skip_cpu_inner_invalidation">跳過CPU內部失效處理</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">在記憶體更新期間跳過某些CPU端快取的失效處理,減少CPU使用率並提高其性能。可能會導致某些遊戲出現故障或崩潰。</string>
|
||||
<string name="skip_cpu_inner_invalidation_description">在記憶體更新期間跳過某些CPU端快取的失效處理,減少CPU使用率並提高其性能。可能會導致某些遊戲出現故障或當機。</string>
|
||||
<string name="fix_bloom_effects">修正光暈特效</string>
|
||||
<string name="fix_bloom_effects_description">在 薩爾達傳說:織夢島 和 薩爾達傳說:智慧的再現 中 減少光暈模糊的問題並移除 橫衝直撞:狂飆樂園重製版 中的光暈效果 (Adreno A6XX - A7XX/ Turnip)。警告:可能會在其它遊戲中導致圖形異常</string>
|
||||
<string name="emulate_bgr565">模擬 BGR565</string>
|
||||
<string name="emulate_bgr565_description">修正遊戲中出現的顏色反轉、圖形異常或陰影異常</string>
|
||||
<string name="rescale_hack">啟用舊版重新縮放處理</string>
|
||||
<string name="rescale_hack_description">透過快速重新縮放處理來啟用遊戲重新縮放設定階段的舊版處理方式</string>
|
||||
<string name="renderer_asynchronous_shaders">使用非同步著色器</string>
|
||||
<string name="renderer_asynchronous_shaders_description">非同步編譯著色器。這可能會減少卡頓,但也可能導致圖形錯誤。</string>
|
||||
<string name="renderer_asynchronous_shaders_description">使用非同步編譯著色器。這可能會減少卡頓,但也可能導致圖形錯誤。</string>
|
||||
<string name="gpu_unswizzle_settings">GPU Unswizzle 設定</string>
|
||||
<string name="gpu_unswizzle_settings_description">設定基於 GPU 的 Unswizzling 參數或完全停用此功能。調整此設定以在性能與紋理載入品質中取得平衡</string>
|
||||
<string name="gpu_unswizzle_enable">啟用 GPU Unswizzle</string>
|
||||
<string name="gpu_unswizzle_disabled">停用</string>
|
||||
<string name="gpu_unswizzle_texture_size">GPU Unswizzle 最大紋理尺寸</string>
|
||||
<string name="gpu_unswizzle_texture_size_description">設定 GPU 紋理 Unswizzling 的最大值 (MB)。雖然 GPU 處理中型和大型紋理的速度較快,但 CPU 處理非常小的紋理可能更有效率。整此設定以在 GPU 加速與 CPU 負載之間取得平衡</string>
|
||||
<string name="gpu_unswizzle_stream_size">GPU Unswizzle 流大小</string>
|
||||
<string name="gpu_unswizzle_stream_size_description">設定每個影格處理大型紋理 Unswizzling 的資料上限。數值越高載入紋理的速度越快,但會增加畫面延遲;數值較低則能降低 GPU 負載,不過有可能導致至紋理載入不完全甚至突然出現</string>
|
||||
<string name="gpu_unswizzle_chunk_size">GPU Unswizzle 塊大小</string>
|
||||
<string name="gpu_unswizzle_chunk_size_description">設定 3D 紋理每批次處理的深度切片數量。增加此數值可提升規格較佳 GPU 的處理效率,但在較低規格的硬體上可能導致卡頓或驅動程式逾時</string>
|
||||
<string name="gpu_unswizzle_default_button">預設</string>
|
||||
|
||||
|
||||
<string name="extensions">擴充功能</string>
|
||||
|
||||
<string name="dyna_state">擴展動態狀態</string>
|
||||
<string name="disabled">已停用</string>
|
||||
<string name="dyna_state_description">控制擴展動態狀態中可使用的功能數量。較高的數值可根據驅動程式支援的動態狀態減少管線編譯的次數。</string>
|
||||
<string name="disabled">停用</string>
|
||||
<string name="vertex_input_dynamic_state">頂點輸入動態狀態</string>
|
||||
<string name="vertex_input_dynamic_state_description">啟用此功能可提供更彈性的頂點輸入處理方式,並可能減少頂點/緩衝區的管線編譯時間</string>
|
||||
<string name="sample_shading_fraction">取樣著色</string>
|
||||
<string name="sample_shading_fraction_description">讓片段著色器在多重取樣片段中針對每個取樣執行,而不是每個片段只執行一次。可提升圖形品質但會降低部分效能</string>
|
||||
|
||||
|
||||
<string name="display">顯示</string>
|
||||
|
||||
<string name="renderer_screen_layout">方向</string>
|
||||
@@ -498,32 +540,55 @@
|
||||
<string name="picture_in_picture">子母畫面</string>
|
||||
<string name="picture_in_picture_description">位於背景時最小化視窗</string>
|
||||
|
||||
<string name="warning_resolution">解析度縮放到2倍或更高可能會導致問題,並可能顯著降低設備速度。</string>
|
||||
<string name="warning_resolution">解析度縮放到2倍或更高可能會導致問題,並可能顯著降低設備速度</string>
|
||||
|
||||
<!-- Debug settings strings -->
|
||||
<string name="cpu">CPU</string>
|
||||
<string name="clocks">時脈</string>
|
||||
<string name="use_auto_stub">使用自動存根</string>
|
||||
<string name="use_auto_stub_description">自動補上缺少的服務和功能。可提高相容性但可能導致崩潰和穩定性問題。</string>
|
||||
<string name="use_auto_stub_description">自動補上缺少的服務和功能。可提高相容性但可能導致當機和穩定性問題。</string>
|
||||
|
||||
<string name="gpu">GPU</string>
|
||||
<string name="renderer_api">API</string>
|
||||
<string name="renderer_debug">圖形偵錯</string>
|
||||
<string name="renderer_debug_description">將圖形 API 設為慢速偵錯模式。</string>
|
||||
<string name="renderer_debug_description">將圖形 API 設為慢速偵錯模式</string>
|
||||
<string name="patch_old_qcom_drivers">BCn 區塊壓縮紋理修補</string>
|
||||
<string name="patch_old_qcom_drivers_description">覆寫 Adreno GPU 的 BCn 紋理格式自動偵測。通常會根據 Android 版本自動偵測(會在API 28 以上自動啟用)</string>
|
||||
<string name="fastmem">Fastmem</string>
|
||||
|
||||
<string name="log">日誌記錄</string>
|
||||
<string name="log">日誌</string>
|
||||
<string name="flush_by_line">按行寫入偵錯日誌</string>
|
||||
<string name="flush_by_line_description">在每行寫入時重新整理偵錯日誌,讓程式在崩潰或當機時更容易偵錯。</string>
|
||||
<string name="flush_by_line_description">在每行寫入時重新整理偵錯日誌,讓程式在當機或閃退時更容易偵錯。</string>
|
||||
|
||||
<!-- GPU Logging strings -->
|
||||
<string name="gpu_logging_header">GPU 日誌</string>
|
||||
<string name="gpu_log_level">記錄層級</string>
|
||||
<string name="gpu_log_level_description">GPU 日誌的詳細層級 (越高日誌越詳細,不過會更容易過熱)</string>
|
||||
<string name="gpu_log_vulkan_calls">記錄 Vulkan API 呼叫</string>
|
||||
<string name="gpu_log_vulkan_calls_description">在環形緩衝區中記錄所有 Vulkan API 呼叫</string>
|
||||
<string name="gpu_log_shader_dumps">傾印 SPIR-V 著色器</string>
|
||||
<string name="gpu_log_shader_dumps_description">將重新編譯後的 SPIR-V 二進位檔案 (.spv) 儲存至 Eden 資料夾裡的 dump 資料夾中。可使用 spirv-dis/spirv-cross/spirv-val 進行檢查</string>
|
||||
<string name="dump_guest_shaders">傾印客體(Maxwell)著色器</string>
|
||||
<string name="dump_guest_shaders_description">將 Maxwell 客體著色器位元組碼檔案 (.ash) 儲存至 Eden 資料夾裡的 dump 資料夾中。可使用 nvdisasm 進行檢查</string>
|
||||
<string name="dump_macros">傾印 Maxwell 巨集</string>
|
||||
<string name="dump_macros_description">將 Maxwell 巨集程式檔案 (.macro) 儲存至 Eden 資料夾裡的 dump 資料夾中。可使用 envydis 進行檢查</string>
|
||||
<string name="gpu_log_memory_tracking">追蹤 GPU 記憶體</string>
|
||||
<string name="gpu_log_memory_tracking_description">監控 GPU 記憶體的分配和釋放</string>
|
||||
<string name="gpu_log_driver_debug">驅動程式偵錯資料</string>
|
||||
<string name="gpu_log_driver_debug_description">擷取驅動程式的特定偵錯資料(例如 Turnip breadcrumbs)</string>
|
||||
<string name="gpu_log_ring_buffer_size">環形緩衝區大小</string>
|
||||
<string name="gpu_log_ring_buffer_size_description">追蹤的近期 Vulkan 呼叫數量(預設為 512 )</string>
|
||||
<string name="gpu_log_ring_buffer_size_hint">64 至 4096 個項目</string>
|
||||
|
||||
<string name="general">一般</string>
|
||||
|
||||
<!-- Audio settings strings -->
|
||||
<string name="audio_output_engine">輸出引擎</string>
|
||||
<string name="audio_volume">音量</string>
|
||||
<string name="audio_volume_description">指定音訊輸出音量。</string>
|
||||
<string name="audio_volume_description">指定音訊輸出音量</string>
|
||||
|
||||
<!-- Input strings -->
|
||||
<string name="buttons">按键</string>
|
||||
<string name="buttons">按鍵</string>
|
||||
<string name="button_plus">+</string>
|
||||
<string name="button_minus">-</string>
|
||||
<string name="button_home">HOME</string>
|
||||
@@ -592,19 +657,20 @@
|
||||
<string name="button_map_description">按下一個按鈕或肩部按鍵/移動搖桿</string>
|
||||
<string name="map_dpad_direction">將方向鍵配對到 %1$s</string>
|
||||
<string name="map_control">配對到 %1$s</string>
|
||||
<string name="failed_to_load_profile">匯入設定檔失敗</string>
|
||||
<string name="failed_to_load_profile">導入設定檔失敗</string>
|
||||
<string name="failed_to_save_profile">儲存設定檔失敗</string>
|
||||
<string name="reset_mapping">重設控制器配對</string>
|
||||
<string name="reset_mapping_description">您確定要將此控制器的所有配對重設為預設值?此動作無法復原</string>
|
||||
|
||||
<!-- Miscellaneous -->
|
||||
<string name="slider_default">預設</string>
|
||||
<string name="default_string">預設</string>
|
||||
<string name="loading">正在載入…</string>
|
||||
<string name="shutting_down">正在關閉…</string>
|
||||
<string name="reset_setting_confirmation">要將此設定重設回預設值嗎?</string>
|
||||
<string name="reset_to_default">重設為預設值</string>
|
||||
<string name="reset_to_default_description">重設所有進階設定</string>
|
||||
<string name="reset_all_settings">重設所有設定?</string>
|
||||
<string name="reset_all_settings">確定要重設所有設定嗎?</string>
|
||||
<string name="reset_all_settings_description">所有進階設定將被重設,此動作無法復原</string>
|
||||
<string name="settings_reset">設定已重設</string>
|
||||
<string name="close">關閉</string>
|
||||
@@ -624,8 +690,8 @@
|
||||
<string name="import_success">已成功導入</string>
|
||||
<string name="export_success">已成功導出</string>
|
||||
<string name="start">開始</string>
|
||||
<string name="global">全域</string>
|
||||
<string name="custom">自定義</string>
|
||||
<string name="global">全域設定</string>
|
||||
<string name="custom">個別遊戲設定</string>
|
||||
<string name="import_complete">導入完成</string>
|
||||
<string name="use_global_setting">使用全域設定</string>
|
||||
<string name="operation_completed_successfully">操作已成功完成</string>
|
||||
@@ -637,6 +703,7 @@
|
||||
<string name="select_gpu_driver_default">預設</string>
|
||||
<string name="select_gpu_driver_error">選擇的驅動程式無效</string>
|
||||
<string name="driver_already_installed">驅動程式已安裝</string>
|
||||
<string name="installed_label">%1$s(已安裝)</string>
|
||||
<string name="system_gpu_driver">系統 GPU 驅動程式</string>
|
||||
<string name="installing_driver">正在安裝驅動程式…</string>
|
||||
|
||||
@@ -666,6 +733,9 @@
|
||||
<string name="preferences_system_description">底座模式、區域及語言</string>
|
||||
<string name="preferences_graphics">圖形</string>
|
||||
<string name="preferences_graphics_description">準確度層級、解析度及著色器快取</string>
|
||||
<string name="quick_settings">快捷設定</string>
|
||||
<string name="enable_quick_settings">啟用快捷設定</string>
|
||||
<string name="enable_quick_settings_description">允許透過滑動和選單按鈕存取快捷設定選單</string>
|
||||
<string name="preferences_audio">音訊</string>
|
||||
<string name="preferences_audio_description">輸出引擎及音量</string>
|
||||
<string name="preferences_controls">控制</string>
|
||||
@@ -673,12 +743,26 @@
|
||||
<string name="preferences_player">玩家 %d</string>
|
||||
<string name="preferences_debug">偵錯</string>
|
||||
<string name="preferences_debug_description">CPU/GPU 偵錯、圖形 API 及 fastmem</string>
|
||||
<string name="preferences_custom_paths">自訂路徑</string>
|
||||
<string name="preferences_custom_paths_description">自訂檔案的存放路徑</string>
|
||||
|
||||
<!-- Custom Paths settings -->
|
||||
<string name="custom_save_directory">遊戲儲存資料路徑</string>
|
||||
<string name="custom_save_directory_description">設定遊戲儲存資料的自訂路徑</string>
|
||||
<string name="reset_to_nand">重設為預設值</string>
|
||||
<string name="migrate_save_data">遷移儲存資料</string>
|
||||
<string name="migrate_save_data_question">您要將現有的儲存資料移至新位置嗎?</string>
|
||||
<string name="save_migration_complete">成功遷移儲存資料</string>
|
||||
<string name="save_migration_failed">儲存資料遷移失敗</string>
|
||||
<string name="save_directory_set">存檔目錄設定完成</string>
|
||||
<string name="skip_migration">跳過</string>
|
||||
|
||||
<!-- Game properties -->
|
||||
<string name="info">資訊</string>
|
||||
<string name="info_description">程式 ID、開發人員及版本資訊</string>
|
||||
<string name="per_game_settings">個別遊戲設定</string>
|
||||
<string name="per_game_settings_description">編輯此遊戲的特定設定</string>
|
||||
<string name="launch_options">啟動組態</string>
|
||||
<string name="launch_options">啟動設定</string>
|
||||
<string name="path">路徑</string>
|
||||
<string name="program_id">程式 ID</string>
|
||||
<string name="developer">開發商</string>
|
||||
@@ -784,7 +868,7 @@
|
||||
<string name="driver_missing_title">需要GPU驅動程式</string>
|
||||
<string name="driver_missing_message">這個遊戲的設定需要 \"%s\"驅動程式,而它並沒有安裝在您的裝置上\n\n要下載並安裝此驅動程式嗎?</string>
|
||||
<string name="driver_download_cancelled">驅動程式下載已取消。沒有所需的驅動程式無法啟動遊戲。</string>
|
||||
<string name="download">下載</string>
|
||||
<string name="download">遷移</string>
|
||||
|
||||
<!-- Emulation Menu -->
|
||||
<string name="emulation_exit">結束模擬</string>
|
||||
@@ -827,11 +911,11 @@
|
||||
<string name="abort_button">中止</string>
|
||||
<string name="continue_button">繼續</string>
|
||||
<string name="system_archive_not_found">找不到系統存檔</string>
|
||||
<string name="system_archive_not_found_message">缺少%s,請轉儲您的系統存檔\n繼續模擬可能會導致崩潰</string>
|
||||
<string name="system_archive_not_found_message">缺少%s,請轉儲您的系統存檔\n繼續模擬可能會導致當機</string>
|
||||
<string name="system_archive_general">系統存檔</string>
|
||||
<string name="save_load_error">儲存/載入時發生錯誤</string>
|
||||
<string name="fatal_error">嚴重錯誤</string>
|
||||
<string name="fatal_error_message">發生嚴重錯誤。請查看日誌以取得詳細資料\n繼續模擬可能會導致崩潰</string>
|
||||
<string name="fatal_error_message">發生嚴重錯誤。請查看日誌以取得詳細資料\n繼續模擬可能會導致當機</string>
|
||||
<string name="device_memory_inadequate">裝置記憶體大小:%1$s\n推薦記憶體大小:%2$s</string>
|
||||
<string name="memory_formatted">%1$s%2$s</string>
|
||||
<string name="no_game_present">目前沒有可啟動的遊戲!</string>
|
||||
@@ -850,10 +934,6 @@
|
||||
<string name="memory_6gb">6GB (不穩定)</string>
|
||||
<string name="memory_8gb">8GB (不穩定)</string>
|
||||
|
||||
<!--CPU clock speeds-->
|
||||
<string name="clock_boost">加速 (1700MHz)</string>
|
||||
<string name="clock_fast">快速 (2000MHz)</string>
|
||||
|
||||
<!-- Temperature Units -->
|
||||
<string name="temperature_celsius">攝氏度</string>
|
||||
<string name="temperature_fahrenheit">華氏度</string>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -299,14 +299,39 @@
|
||||
<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_description">Insert an interpolated frame between every pair of real frames using Lossless Scaling. Adds about one frame of input latency.</string>
|
||||
<string name="frame_gen_dump_flow">Dump flow pyramid</string>
|
||||
<string name="frame_gen_dump_flow_description">Write the optical flow mip levels to the lossless/debug folder once, for troubleshooting</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 copy of Lossless.dll from Lossless Scaling</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>
|
||||
@@ -315,9 +340,16 @@
|
||||
<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_installed_description">Lossless.dll is installed and contains every shader frame generation needs.</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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+72
-9
@@ -352,7 +352,7 @@ struct Values {
|
||||
true};
|
||||
|
||||
SwitchableSetting<ScalingFilter> scaling_filter{linkage,
|
||||
ScalingFilter::Bilinear,
|
||||
ScalingFilter::NearestNeighbor,
|
||||
"scaling_filter",
|
||||
Category::Renderer,
|
||||
Specialization::Default,
|
||||
@@ -389,7 +389,65 @@ struct Values {
|
||||
true};
|
||||
|
||||
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
||||
Specialization::Default, true, true};
|
||||
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};
|
||||
|
||||
@@ -574,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",
|
||||
@@ -880,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();
|
||||
|
||||
@@ -59,10 +59,6 @@ add_library(video_core STATIC
|
||||
engines/maxwell_dma.h
|
||||
engines/puller.cpp
|
||||
engines/puller.h
|
||||
frame_gen/lossless_dll.cpp
|
||||
frame_gen/lossless_dll.h
|
||||
frame_gen/lsfg_translate.cpp
|
||||
frame_gen/lsfg_translate.h
|
||||
framebuffer_config.cpp
|
||||
framebuffer_config.h
|
||||
fsr.cpp
|
||||
@@ -125,12 +121,6 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/present/anti_alias_pass.h
|
||||
renderer_vulkan/present/filters.cpp
|
||||
renderer_vulkan/present/filters.h
|
||||
renderer_vulkan/present/frame_gen.cpp
|
||||
renderer_vulkan/present/frame_gen.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
|
||||
renderer_vulkan/present/fsr.cpp
|
||||
renderer_vulkan/present/fsr.h
|
||||
renderer_vulkan/present/fxaa.cpp
|
||||
@@ -277,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
|
||||
@@ -355,7 +377,6 @@ add_dependencies(video_core host_shaders)
|
||||
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
|
||||
|
||||
target_link_libraries(video_core PRIVATE sirit::sirit)
|
||||
target_link_libraries(video_core PRIVATE dxbc)
|
||||
|
||||
# Header-only stuff needed by all dependent targets
|
||||
target_link_libraries(video_core PUBLIC Vulkan::Headers Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator)
|
||||
|
||||
@@ -45,7 +45,7 @@ constexpr u32 PERFORMANCE_SHADER_ID_FIRST = 280;
|
||||
constexpr u32 PERFORMANCE_SHADER_ID_LAST = 302;
|
||||
|
||||
constexpr u32 CACHE_MAGIC = 0x4746534C;
|
||||
constexpr u32 CACHE_VERSION = 1;
|
||||
constexpr u32 CACHE_VERSION = 2;
|
||||
|
||||
struct CacheHeader {
|
||||
u32 magic;
|
||||
@@ -53,6 +53,7 @@ struct CacheHeader {
|
||||
u64 source_size;
|
||||
u64 source_hash;
|
||||
u32 module_count;
|
||||
u32 variant;
|
||||
};
|
||||
|
||||
struct Section {
|
||||
@@ -276,20 +277,49 @@ template <typename Map>
|
||||
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) {
|
||||
ShaderModules& out_modules,
|
||||
ShaderVariant variant) {
|
||||
const u32 offset = VariantOffset(variant);
|
||||
out_modules.clear();
|
||||
for (const u32 id : PerformanceShaderIds()) {
|
||||
const auto hit = resources.find(id);
|
||||
const auto hit = resources.find(id + offset);
|
||||
if (hit == resources.end()) {
|
||||
return LosslessStatus::MissingShaders;
|
||||
}
|
||||
|
||||
std::vector<u32> words = TranslateComputeShader(hit->second);
|
||||
if (words.empty()) {
|
||||
std::vector<u32> adopted = AdoptSpirvModule(hit->second);
|
||||
if (adopted.empty()) {
|
||||
return LosslessStatus::TranslationFailed;
|
||||
}
|
||||
out_modules.emplace(id, std::move(words));
|
||||
out_modules.emplace(id, std::move(adopted));
|
||||
}
|
||||
return LosslessStatus::Ok;
|
||||
}
|
||||
@@ -313,7 +343,7 @@ template <typename Map>
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size,
|
||||
u64 source_hash, ShaderModules& out_modules) {
|
||||
u64 source_hash, u32 variant, ShaderModules& out_modules) {
|
||||
if (!Common::FS::Exists(path)) {
|
||||
return false;
|
||||
}
|
||||
@@ -325,7 +355,8 @@ template <typename Map>
|
||||
return false;
|
||||
}
|
||||
if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION ||
|
||||
header.source_size != source_size || header.source_hash != source_hash) {
|
||||
header.source_size != source_size || header.source_hash != source_hash ||
|
||||
header.variant != variant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -452,7 +483,7 @@ LosslessStatus GetInstalledLosslessStatus() {
|
||||
return ValidateLosslessDll(GetLosslessDllPath());
|
||||
}
|
||||
|
||||
LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
|
||||
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) {
|
||||
@@ -464,17 +495,23 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
|
||||
Common::CityHash64(reinterpret_cast<const char*>(image.data()), image.size());
|
||||
const std::filesystem::path cache_path = GetShaderCachePath();
|
||||
|
||||
if (ReadShaderCache(cache_path, source_size, source_hash, out_modules)) {
|
||||
return LosslessStatus::Ok;
|
||||
}
|
||||
|
||||
ResourceSpans spans;
|
||||
const LosslessStatus parse_status = ParseShaderSpans(image, spans);
|
||||
if (parse_status != LosslessStatus::Ok) {
|
||||
return parse_status;
|
||||
}
|
||||
|
||||
const LosslessStatus translate_status = TranslateAll(spans, out_modules);
|
||||
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;
|
||||
}
|
||||
@@ -485,6 +522,7 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
|
||||
.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));
|
||||
@@ -496,7 +534,7 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
|
||||
|
||||
LosslessStatus BuildShaderCache() {
|
||||
ShaderModules modules;
|
||||
return LoadShaderModules(modules);
|
||||
return LoadShaderModules(modules, true);
|
||||
}
|
||||
|
||||
bool RemoveInstalledLosslessDll() {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// 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>
|
||||
@@ -25,6 +28,11 @@ enum class LosslessStatus : u32 {
|
||||
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;
|
||||
@@ -32,6 +40,9 @@ 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();
|
||||
@@ -47,7 +58,9 @@ constexpr std::array<u32, 10> DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296,
|
||||
|
||||
[[nodiscard]] LosslessStatus BuildShaderCache();
|
||||
|
||||
[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules);
|
||||
[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules,
|
||||
bool allow_fp16 = false,
|
||||
bool prefer_fp16 = false);
|
||||
|
||||
bool RemoveInstalledLosslessDll();
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <dxbc_modinfo.h>
|
||||
#include <dxbc_module.h>
|
||||
#include <dxbc_reader.h>
|
||||
#include <thirdparty/spirv.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
|
||||
#include "video_core/frame_gen/lsfg_translate.h"
|
||||
|
||||
@@ -12,47 +12,82 @@ namespace VideoCore::FrameGen {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 DECORATION_LITERAL_WORD = 3;
|
||||
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;
|
||||
|
||||
void RenumberBindings(dxvk::SpirvCodeBuffer& code) {
|
||||
std::vector<u32> literal_offsets;
|
||||
for (const auto instruction : code) {
|
||||
if (instruction.opCode() == spv::OpFunction) {
|
||||
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 (instruction.opCode() == spv::OpDecorate &&
|
||||
instruction.arg(2) == spv::DecorationBinding) {
|
||||
literal_offsets.push_back(instruction.offset() + DECORATION_LITERAL_WORD);
|
||||
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 (size_t i = 0; i < literal_offsets.size(); ++i) {
|
||||
code.data()[literal_offsets[i]] = static_cast<u32>(i);
|
||||
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
|
||||
|
||||
std::vector<u32> TranslateComputeShader(std::span<const u8> dxbc) {
|
||||
if (dxbc.empty()) {
|
||||
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 {};
|
||||
}
|
||||
|
||||
try {
|
||||
dxvk::DxbcReader reader{reinterpret_cast<const char*>(dxbc.data()), dxbc.size()};
|
||||
dxvk::DxbcModule module{reader};
|
||||
std::vector<u32> words(blob.size() / sizeof(u32));
|
||||
std::memcpy(words.data(), blob.data(), blob.size());
|
||||
|
||||
const dxvk::DxbcModuleInfo module_info{};
|
||||
dxvk::SpirvCodeBuffer code = module.compile(module_info, "CS");
|
||||
if (code.dwords() == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
RenumberBindings(code);
|
||||
return std::vector<u32>{code.data(), code.data() + code.dwords()};
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
RenumberBindingsInOrder(words);
|
||||
return words;
|
||||
}
|
||||
|
||||
} // namespace VideoCore::FrameGen
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
namespace VideoCore::FrameGen {
|
||||
|
||||
[[nodiscard]] std::vector<u32> TranslateComputeShader(std::span<const u8> dxbc);
|
||||
[[nodiscard]] bool IsSpirvModule(std::span<const u8> blob);
|
||||
|
||||
[[nodiscard]] std::vector<u32> AdoptSpirvModule(std::span<const u8> blob);
|
||||
|
||||
} // namespace VideoCore::FrameGen
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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"
|
||||
@@ -17,27 +19,174 @@ namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr f32 LSFG_FLOW_SCALE = 1.0f;
|
||||
constexpr size_t COLOR_CHANNELS = 4;
|
||||
constexpr u64 LSFG_REQUIRED_FRAMES = 2;
|
||||
constexpr u32 LSFG_RECURRENCE_FRAMES = 2;
|
||||
|
||||
void WriteGrayscalePgm(const std::filesystem::path& path, VkExtent2D extent,
|
||||
std::span<const u8> pixels) {
|
||||
[[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 =
|
||||
"P5\n" + std::to_string(extent.width) + " " + std::to_string(extent.height) + "\n255\n";
|
||||
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;
|
||||
}
|
||||
|
||||
const size_t expected = static_cast<size_t>(extent.width) * extent.height;
|
||||
void(file.Write(pixels.subspan(0, std::min(expected, pixels.size()))));
|
||||
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_)
|
||||
@@ -45,8 +194,21 @@ FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
|
||||
|
||||
FrameGen::~FrameGen() = default;
|
||||
|
||||
void FrameGen::Process(const Device& device, Frame* frame) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -58,55 +220,142 @@ void FrameGen::Process(const Device& device, Frame* frame) {
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
if (!mipmaps || built_extent.width != extent.width || built_extent.height != extent.height) {
|
||||
Rebuild(device, extent);
|
||||
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);
|
||||
}
|
||||
|
||||
mipmaps->Dispatch(device, scheduler, *frame->image_view, frame_count);
|
||||
++frame_count;
|
||||
const u64 count = frame_count++;
|
||||
last_count = count;
|
||||
last_generations = plan.generations;
|
||||
|
||||
const bool dump_requested = Settings::values.frame_gen_dump_flow.GetValue();
|
||||
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) {
|
||||
DumpFlowPyramid(device);
|
||||
DumpDebugImages(count);
|
||||
dumped = true;
|
||||
}
|
||||
}
|
||||
|
||||
void FrameGen::Rebuild(const Device& device, VkExtent2D extent) {
|
||||
scheduler.Finish();
|
||||
mipmaps.emplace(device, memory_allocator, *shaders, extent, LSFG_FLOW_SCALE);
|
||||
built_extent = extent;
|
||||
frame_count = 0;
|
||||
size_t FrameGen::WantedGenerations(size_t capacity) {
|
||||
if (unavailable) {
|
||||
plan = {};
|
||||
return 0;
|
||||
}
|
||||
plan = pacer.Plan(capacity);
|
||||
return plan.generations;
|
||||
}
|
||||
|
||||
void FrameGen::DumpFlowPyramid(const Device& device) {
|
||||
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;
|
||||
}
|
||||
|
||||
for (size_t level = 0; level < LSFG_MIP_LEVELS; ++level) {
|
||||
const VkExtent2D extent = mipmaps->GetLevelExtent(level);
|
||||
const VkDeviceSize size = static_cast<VkDeviceSize>(extent.width) * extent.height;
|
||||
vk::Buffer readback = CreateWrappedBuffer(memory_allocator, size, MemoryUsage::Download);
|
||||
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([image = mipmaps->GetLevelImage(level), dst = *readback,
|
||||
extent](vk::CommandBuffer cmdbuf) {
|
||||
DownloadColorImage(cmdbuf, image, dst,
|
||||
VkExtent3D{.width = extent.width, .height = extent.height,
|
||||
.depth = 1});
|
||||
});
|
||||
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();
|
||||
|
||||
readback.Invalidate();
|
||||
WriteGrayscalePgm(directory / ("flow_mip" + std::to_string(level) + ".pgm"), extent,
|
||||
readback.Mapped());
|
||||
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
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/present/lsfg_mipmaps.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"
|
||||
|
||||
@@ -21,19 +22,34 @@ public:
|
||||
explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler);
|
||||
~FrameGen();
|
||||
|
||||
void Process(const Device& device, Frame* frame);
|
||||
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);
|
||||
void DumpFlowPyramid(const Device& device);
|
||||
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<LsfgMipmaps> mipmaps;
|
||||
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{};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user