diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 398e2ee418..fea9c10315 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -53,6 +53,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { SOC_OVERLAY_BACKGROUND("soc_overlay_background"), ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"), + RESPECT_PRESENT_INTERVAL_ZERO("respect_present_interval_zero"), PERF_OVERLAY_BACKGROUND("perf_overlay_background"), SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index cc1ce41e4b..ee27058ab2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -237,6 +237,13 @@ abstract class SettingsItem( override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset() } + put( + SwitchSetting( + BooleanSetting.RESPECT_PRESENT_INTERVAL_ZERO, + titleId = R.string.respect_present_interval_zero, + descriptionId = R.string.respect_present_interval_zero_description + ) + ) put( SwitchSetting( dockedModeSetting, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 2a6929301c..35f561a1e0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -460,6 +460,7 @@ class SettingsFragmentPresenter( add(IntSetting.RENDERER_SAMPLE_SHADING_FRACTION.key) add(HeaderSetting(R.string.veil_renderer)) + add(BooleanSetting.RESPECT_PRESENT_INTERVAL_ZERO.key) add(BooleanSetting.RENDERER_EARLY_RELEASE_FENCES.key) add(IntSetting.DMA_ACCURACY.key) add(BooleanSetting.BUFFER_REORDER_DISABLE.key) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index d0d4e48fd9..459ab922eb 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -105,6 +105,8 @@ The intensity of the sample shading pass. Higher values improve quality more but also reduce performance to a greater extent. Renderer + Respect present interval 0 for unlocked FPS + When enabled, present interval 0 will be used for games requesting unlocked FPS. This matches console behavior more closely, but may cause higher battery usage and frame pacing issues. When disabled (default), present interval 0 is capped at 120FPS to conserve battery. Release Fences Early Helps fix 0 FPS in games like DKCR:HD, Subnautica Below Zero and Ori 2, but may break loading or performance in Unreal Engine games. Sync Memory Operations diff --git a/src/common/settings.h b/src/common/settings.h index 6690411d2d..7ca0e675fa 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -342,6 +342,9 @@ struct Values { Category::Renderer}; SwitchableSetting use_asynchronous_gpu_emulation{ linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer}; + SwitchableSetting respect_present_interval_zero{ + linkage, false, "respect_present_interval_zero", Category::Renderer}; + SwitchableSetting accelerate_astc{linkage, #ifdef ANDROID AstcDecodeMode::Cpu, diff --git a/src/core/hle/service/nvnflinger/hardware_composer.cpp b/src/core/hle/service/nvnflinger/hardware_composer.cpp index 4f8f5da69a..fdbfd3ccd5 100644 --- a/src/core/hle/service/nvnflinger/hardware_composer.cpp +++ b/src/core/hle/service/nvnflinger/hardware_composer.cpp @@ -8,6 +8,8 @@ #include +#include "common/settings.h" + #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" #include "core/hle/service/nvnflinger/buffer_item.h" #include "core/hle/service/nvnflinger/buffer_item_consumer.h" @@ -21,6 +23,14 @@ namespace { s32 NormalizeSwapInterval(f32* out_speed_scale, s32 swap_interval) { if (swap_interval <= 0) { + // If swap_interval is 0 and setting enabled, respect it as unlocked FPS + if (swap_interval == 0 && Settings::values.respect_present_interval_zero.GetValue()) { + if (out_speed_scale) { + *out_speed_scale = 1.0f; + } + return 0; + } + // As an extension, treat nonpositive swap interval as speed multiplier. if (out_speed_scale) { *out_speed_scale = 2.f * static_cast(1 - swap_interval); diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 48190eda79..1c86019d0d 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -213,6 +213,11 @@ std::unique_ptr InitializeTranslations(QObject* parent) use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"), tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled.")); + INSERT( + Settings, respect_present_interval_zero, tr("Respect present interval 0 for unlocked FPS"), + tr("When enabled, present interval 0 will be used for games requesting unlocked FPS.\n" + "This matches console behavior more closely, but may cause higher battery usage and frame pacing issues.\n" + "When disabled (default), present interval 0 is capped at 120FPS to conserve battery.")); INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"),