feat: add option to respect present interval 0 as unlocked FPS

When enabled, this feature allows games using present interval 0 to run with
truly unlocked FPS, matching actual hardware behavior more accurately.

Previously, Citron would cap present interval 0 at 120FPS to conserve battery,
but this prevented proper functionality of dynamic framerate mods like UltraCam
by MaxLastBreath (https://www.nxoptimizer.com/).

The setting is disabled by default to maintain the current behavior for most users.

Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron 2025-04-16 19:28:15 +10:00
parent a1f3414bde
commit bbd3253169
3 changed files with 18 additions and 0 deletions

View file

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "citron/configuration/shared_translation.h"
@ -146,6 +147,11 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
INSERT(
Settings, 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:"),
tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for "
"decoding, or perform no decoding at all (black screen on videos).\n"

View file

@ -284,6 +284,8 @@ struct Values {
Category::Renderer};
SwitchableSetting<bool> use_asynchronous_gpu_emulation{
linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer};
SwitchableSetting<bool> respect_present_interval_zero{
linkage, false, "respect_present_interval_zero", Category::Renderer};
SwitchableSetting<AstcDecodeMode, true> accelerate_astc{linkage,
#ifdef ANDROID
AstcDecodeMode::Cpu,

View file

@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <boost/container/small_vector.hpp>
#include "common/microprofile.h"
#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"
@ -17,6 +19,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<f32>(1 - swap_interval);