From c4e4fa53d9200bbac34a498d55cde381e3112764 Mon Sep 17 00:00:00 2001
From: emmauss <emmausssss@gmail.com>
Date: Wed, 7 Dec 2016 07:33:19 +1200
Subject: [PATCH] Implement Frame rate limiter (#2223)

* implement frame limiter

* fixes
---
 src/citra/config.cpp                |  2 ++
 src/citra/default_ini.h             |  4 ++++
 src/citra_qt/config.cpp             |  2 ++
 src/citra_qt/configure_graphics.cpp |  2 ++
 src/citra_qt/configure_graphics.ui  |  7 ++++++
 src/core/hw/gpu.cpp                 | 33 +++++++++++++++++++++++++++++
 src/core/settings.cpp               |  1 +
 src/core/settings.h                 |  1 +
 src/video_core/video_core.cpp       |  1 +
 src/video_core/video_core.h         |  1 +
 10 files changed, 54 insertions(+)

diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index fe74fcd723..29462c982d 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -66,6 +66,8 @@ void Config::ReadValues() {
     Settings::values.use_scaled_resolution =
         sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false);
     Settings::values.use_vsync = sdl2_config->GetBoolean("Renderer", "use_vsync", false);
+    Settings::values.toggle_framelimit =
+        sdl2_config->GetBoolean("Renderer", "toggle_framelimit", true);
 
     Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0);
     Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0);
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index b98dc4d83f..001b18ac2e 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -64,6 +64,10 @@ use_vsync =
 # 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen
 layout_option =
 
+#Whether to toggle frame limiter on or off.
+# 0: Off , 1  (default): On
+toggle_framelimit =
+
 # Swaps the prominent screen with the other screen.
 # For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen.
 # 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent
diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp
index 3cdfe64436..06a4e9d25c 100644
--- a/src/citra_qt/config.cpp
+++ b/src/citra_qt/config.cpp
@@ -47,6 +47,7 @@ void Config::ReadValues() {
     Settings::values.use_scaled_resolution =
         qt_config->value("use_scaled_resolution", false).toBool();
     Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool();
+    Settings::values.toggle_framelimit = qt_config->value("toggle_framelimit", true).toBool();
 
     Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat();
     Settings::values.bg_green = qt_config->value("bg_green", 1.0).toFloat();
@@ -152,6 +153,7 @@ void Config::SaveValues() {
     qt_config->setValue("use_shader_jit", Settings::values.use_shader_jit);
     qt_config->setValue("use_scaled_resolution", Settings::values.use_scaled_resolution);
     qt_config->setValue("use_vsync", Settings::values.use_vsync);
+    qt_config->setValue("toggle_framelimit", Settings::values.toggle_framelimit);
 
     // Cast to double because Qt's written float values are not human-readable
     qt_config->setValue("bg_red", (double)Settings::values.bg_red);
diff --git a/src/citra_qt/configure_graphics.cpp b/src/citra_qt/configure_graphics.cpp
index 29834e11ba..36f10c8d76 100644
--- a/src/citra_qt/configure_graphics.cpp
+++ b/src/citra_qt/configure_graphics.cpp
@@ -23,6 +23,7 @@ void ConfigureGraphics::setConfiguration() {
     ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit);
     ui->toggle_scaled_resolution->setChecked(Settings::values.use_scaled_resolution);
     ui->toggle_vsync->setChecked(Settings::values.use_vsync);
+    ui->toggle_framelimit->setChecked(Settings::values.toggle_framelimit);
     ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option));
     ui->swap_screen->setChecked(Settings::values.swap_screen);
 }
@@ -32,6 +33,7 @@ void ConfigureGraphics::applyConfiguration() {
     Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
     Settings::values.use_scaled_resolution = ui->toggle_scaled_resolution->isChecked();
     Settings::values.use_vsync = ui->toggle_vsync->isChecked();
+    Settings::values.toggle_framelimit = ui->toggle_framelimit->isChecked();
     Settings::values.layout_option =
         static_cast<Settings::LayoutOption>(ui->layout_combobox->currentIndex());
     Settings::values.swap_screen = ui->swap_screen->isChecked();
diff --git a/src/citra_qt/configure_graphics.ui b/src/citra_qt/configure_graphics.ui
index af16a4292e..964aa0bbd0 100644
--- a/src/citra_qt/configure_graphics.ui
+++ b/src/citra_qt/configure_graphics.ui
@@ -50,6 +50,13 @@
           </property>
          </widget>
         </item>
+        <item>
+         <widget class="QCheckBox" name="toggle_framelimit">
+          <property name="text">
+           <string>Limit framerate</string>
+          </property>
+         </widget>
+        </item>
        </layout>
       </widget>
      </item>
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index 45dedea688..cfba82e51c 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -8,7 +8,10 @@
 #include "common/color.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
+#include "common/math_util.h"
 #include "common/microprofile.h"
+#include "common/thread.h"
+#include "common/timer.h"
 #include "common/vector_math.h"
 #include "core/core_timing.h"
 #include "core/hle/service/gsp_gpu.h"
@@ -35,6 +38,14 @@ const u64 frame_ticks = 268123480ull / 60;
 static int vblank_event;
 /// Total number of frames drawn
 static u64 frame_count;
+/// Start clock for frame limiter
+static u32 time_point;
+/// Total delay caused by slow frames
+static float time_delay;
+constexpr float FIXED_FRAME_TIME = 1000.0f / 60;
+// Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher
+// values increases time needed to limit frame rate after spikes
+constexpr float MAX_LAG_TIME = 18;
 
 template <typename T>
 inline void Read(T& var, const u32 raw_addr) {
@@ -512,6 +523,21 @@ template void Write<u32>(u32 addr, const u32 data);
 template void Write<u16>(u32 addr, const u16 data);
 template void Write<u8>(u32 addr, const u8 data);
 
+static void FrameLimiter() {
+    time_delay += FIXED_FRAME_TIME;
+    time_delay = MathUtil::Clamp(time_delay, -MAX_LAG_TIME, MAX_LAG_TIME);
+    s32 desired_time = static_cast<s32>(time_delay);
+    s32 elapsed_time = static_cast<s32>(Common::Timer::GetTimeMs() - time_point);
+
+    if (elapsed_time < desired_time) {
+        Common::SleepCurrentThread(desired_time - elapsed_time);
+    }
+
+    u32 frame_time = Common::Timer::GetTimeMs() - time_point;
+
+    time_delay -= frame_time;
+}
+
 /// Update hardware
 static void VBlankCallback(u64 userdata, int cycles_late) {
     frame_count++;
@@ -528,6 +554,12 @@ static void VBlankCallback(u64 userdata, int cycles_late) {
     // Check for user input updates
     Service::HID::Update();
 
+    if (!Settings::values.use_vsync && Settings::values.toggle_framelimit) {
+        FrameLimiter();
+    }
+
+    time_point = Common::Timer::GetTimeMs();
+
     // Reschedule recurrent event
     CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event);
 }
@@ -563,6 +595,7 @@ void Init() {
     framebuffer_sub.active_fb = 0;
 
     frame_count = 0;
+    time_point = Common::Timer::GetTimeMs();
 
     vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback);
     CoreTiming::ScheduleEvent(frame_ticks, vblank_event);
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 05f41f7987..626e06cd92 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -21,6 +21,7 @@ void Apply() {
     VideoCore::g_hw_renderer_enabled = values.use_hw_renderer;
     VideoCore::g_shader_jit_enabled = values.use_shader_jit;
     VideoCore::g_scaled_resolution_enabled = values.use_scaled_resolution;
+    VideoCore::g_toggle_framelimit_enabled = values.toggle_framelimit;
 
     if (VideoCore::g_emu_window) {
         auto layout = VideoCore::g_emu_window->GetFramebufferLayout();
diff --git a/src/core/settings.h b/src/core/settings.h
index 7470fdbeb2..db4c8fadaa 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -90,6 +90,7 @@ struct Values {
     bool use_shader_jit;
     bool use_scaled_resolution;
     bool use_vsync;
+    bool toggle_framelimit;
 
     LayoutOption layout_option;
     bool swap_screen;
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 83e33dfc2b..8db882f59a 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -21,6 +21,7 @@ std::atomic<bool> g_hw_renderer_enabled;
 std::atomic<bool> g_shader_jit_enabled;
 std::atomic<bool> g_scaled_resolution_enabled;
 std::atomic<bool> g_vsync_enabled;
+std::atomic<bool> g_toggle_framelimit_enabled;
 
 /// Initialize the video core
 bool Init(EmuWindow* emu_window) {
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index e2d725ab18..c397c1974f 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -38,6 +38,7 @@ extern EmuWindow* g_emu_window;                  ///< Emu window
 extern std::atomic<bool> g_hw_renderer_enabled;
 extern std::atomic<bool> g_shader_jit_enabled;
 extern std::atomic<bool> g_scaled_resolution_enabled;
+extern std::atomic<bool> g_toggle_framelimit_enabled;
 
 /// Start the video core
 void Start();
-- 
GitLab