From 2c6e274b3978ce4ee1af98e21f101e7037ec9d24 Mon Sep 17 00:00:00 2001
From: ameerj <52414509+ameerj@users.noreply.github.com>
Date: Thu, 22 Jul 2021 21:03:50 -0400
Subject: [PATCH] config, nvflinger: Add FPS cap setting

Allows finer tuning of the FPS limit.
---
 src/common/settings.h                        |  1 +
 src/core/hle/service/nvflinger/nvflinger.cpp | 11 +++----
 src/yuzu/configuration/config.cpp            |  2 ++
 src/yuzu/configuration/configure_general.cpp |  4 +++
 src/yuzu/configuration/configure_general.ui  | 30 ++++++++++++++++++++
 src/yuzu/main.cpp                            |  2 +-
 src/yuzu_cmd/config.cpp                      |  1 +
 src/yuzu_cmd/default_ini.h                   |  4 +++
 8 files changed, 49 insertions(+), 6 deletions(-)

diff --git a/src/common/settings.h b/src/common/settings.h
index ce1bc647d9..8ad320aed1 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -330,6 +330,7 @@ struct Values {
     Setting<bool> use_nvdec_emulation{true, "use_nvdec_emulation"};
     Setting<bool> accelerate_astc{true, "accelerate_astc"};
     Setting<bool> use_vsync{true, "use_vsync"};
+    BasicSetting<u16> fps_cap{1000, "fps_cap"};
     BasicSetting<bool> disable_fps_limit{false, "disable_fps_limit"};
     Setting<bool> use_assembly_shaders{false, "use_assembly_shaders"};
     Setting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 1d810562ff..9417489705 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -307,11 +307,12 @@ void NVFlinger::Compose() {
 }
 
 s64 NVFlinger::GetNextTicks() const {
-    if (Settings::values.disable_fps_limit.GetValue()) {
-        return 0;
-    }
-    constexpr s64 max_hertz = 120LL;
-    return (1000000000 * (1LL << swap_interval)) / max_hertz;
+    static constexpr s64 max_hertz = 120LL;
+
+    const auto& settings = Settings::values;
+    const bool unlocked_fps = settings.disable_fps_limit.GetValue();
+    const s64 fps_cap = unlocked_fps ? static_cast<s64>(settings.fps_cap.GetValue()) : 1;
+    return (1000000000 * (1LL << swap_interval)) / (max_hertz * fps_cap);
 }
 
 } // namespace Service::NVFlinger
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index a5e032959a..52bb07d160 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -823,6 +823,7 @@ void Config::ReadRendererValues() {
     ReadGlobalSetting(Settings::values.bg_blue);
 
     if (global) {
+        ReadBasicSetting(Settings::values.fps_cap);
         ReadBasicSetting(Settings::values.renderer_debug);
     }
 
@@ -1352,6 +1353,7 @@ void Config::SaveRendererValues() {
     WriteGlobalSetting(Settings::values.bg_blue);
 
     if (global) {
+        WriteBasicSetting(Settings::values.fps_cap);
         WriteBasicSetting(Settings::values.renderer_debug);
     }
 
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 18f25def62..d79d2e23e3 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -48,6 +48,8 @@ void ConfigureGeneral::SetConfiguration() {
     ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue());
     ui->frame_limit->setValue(Settings::values.frame_limit.GetValue());
 
+    ui->fps_cap->setValue(Settings::values.fps_cap.GetValue());
+
     ui->button_reset_defaults->setEnabled(runtime_lock);
 
     if (Settings::IsConfiguringGlobal()) {
@@ -87,6 +89,8 @@ void ConfigureGeneral::ApplyConfiguration() {
         UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
         UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
 
+        Settings::values.fps_cap.SetValue(ui->fps_cap->value());
+
         // Guard if during game and set to game-specific value
         if (Settings::values.use_frame_limit.UsingGlobal()) {
             Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() ==
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index bc70410900..bc3c4b481c 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -51,6 +51,36 @@
             </item>
            </layout>
           </item>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout_2">
+            <item>
+             <widget class="QLabel" name="fps_cap_label">
+              <property name="text">
+               <string>Framerate Cap</string>
+              </property>
+              <property name="toolTip">
+                <string>Requires the use of the FPS Limiter Toggle hotkey to take effect.</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="fps_cap">
+              <property name="suffix">
+               <string>x</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>1000</number>
+              </property>
+              <property name="value">
+               <number>500</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
           <item>
            <widget class="QCheckBox" name="use_multi_core">
             <property name="text">
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 03a909d17d..ce74155927 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -2921,7 +2921,7 @@ void GMainWindow::UpdateStatusBar() {
     }
     if (Settings::values.disable_fps_limit) {
         game_fps_label->setText(
-            tr("Game: %1 FPS (Limit off)").arg(results.average_game_fps, 0, 'f', 0));
+            tr("Game: %1 FPS (Unlocked)").arg(results.average_game_fps, 0, 'f', 0));
     } else {
         game_fps_label->setText(tr("Game: %1 FPS").arg(results.average_game_fps, 0, 'f', 0));
     }
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 3e22fee379..12a61fc43d 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -455,6 +455,7 @@ void Config::ReadValues() {
     ReadSetting("Renderer", Settings::values.gpu_accuracy);
     ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation);
     ReadSetting("Renderer", Settings::values.use_vsync);
+    ReadSetting("Renderer", Settings::values.fps_cap);
     ReadSetting("Renderer", Settings::values.disable_fps_limit);
     ReadSetting("Renderer", Settings::values.use_assembly_shaders);
     ReadSetting("Renderer", Settings::values.use_asynchronous_shaders);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 88d33ecabd..833c346bca 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -290,6 +290,10 @@ bg_red =
 bg_blue =
 bg_green =
 
+# Caps the unlocked framerate to a multiple of the title's target FPS.
+# 1 - 1000: Target FPS multiple cap. 1000 (default)
+fps_cap =
+
 [Audio]
 # Which audio output engine to use.
 # auto (default): Auto-select