diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 8b6ae5405c..e85bc3592a 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -535,12 +535,14 @@
- @string/frame_pacing_mode_target_30
- @string/frame_pacing_mode_target_60
- @string/frame_pacing_mode_target_120
+ - @string/frame_pacing_mode_target_240
- 0
- 1
- 2
- 3
+ - 4
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 1c8f009132..79282c18e3 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -1030,6 +1030,7 @@
30 FPS
60 FPS
120 FPS
+ 240 FPS
CPU
diff --git a/src/common/settings.h b/src/common/settings.h
index 5dc2f30cf8..669d38b17c 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -437,7 +437,7 @@ struct Values {
SwitchableSetting frame_pacing_mode{linkage,
FramePacingMode::Target_Auto,
FramePacingMode::Target_Auto,
- FramePacingMode::Target_120,
+ FramePacingMode::Target_240,
"frame_pacing_mode",
Category::RendererAdvanced};
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index def99db21b..7e3bef9bea 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -129,7 +129,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None);
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
-ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_120);
+ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_120, Target_240);
ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed);
ENUM(VramUsageMode, Conservative, Aggressive);
ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV);
diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp
index b5ad941cc5..40fd42720f 100644
--- a/src/qt_common/config/shared_translation.cpp
+++ b/src/qt_common/config/shared_translation.cpp
@@ -505,6 +505,7 @@ std::unique_ptr ComboboxEnumeration(QObject* parent)
PAIR(FramePacingMode, Target_30, tr("30 FPS")),
PAIR(FramePacingMode, Target_60, tr("60 FPS")),
PAIR(FramePacingMode, Target_120, tr("120 FPS")),
+ PAIR(FramePacingMode, Target_240, tr("240 FPS")),
}});
translations->insert({Settings::EnumMetadata::Index(),
{
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index aafcfdf65b..8830c5a506 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -48,14 +48,9 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)
master_semaphore{std::make_unique(device)},
command_pool{std::make_unique(*master_semaphore, device)} {
- /*// PRE-OPTIMIZATION: Warm up the pool to prevent mid-frame spikes
- {
- std::scoped_lock rl{reserve_mutex};
- chunk_reserve.reserve(2048); // Prevent vector resizing
- for (int i = 0; i < 1024; ++i) {
- chunk_reserve.push_back(std::make_unique());
- }
- }*/
+#ifdef _WIN32
+ high_res_timer = std::make_unique();
+#endif
AcquireNewChunk();
AllocateWorkerCommandBuffer();
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 094607775c..a2adb795e9 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -6,6 +6,11 @@
#pragma once
+#ifdef _WIN32
+#include
+#include
+#endif
+
#include
#include
#include
@@ -16,6 +21,7 @@
#include "common/alignment.h"
#include "common/common_types.h"
+#include "common/settings.h"
#include "common/polyfill_thread.h"
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -111,33 +117,30 @@ public:
return master_semaphore->IsFree(tick);
}
- /// Waits for the given tick to trigger on the GPU.
- void Wait(u64 tick) {
+ /// Waits for the given GPU tick, optionally pacing frames.
+ void Wait(u64 tick, double target_fps = 0.0) {
+ if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) {
+ const auto frame_duration = std::chrono::duration(1.0 / target_fps);
+ auto now = std::chrono::steady_clock::now();
+ if (next_frame_time == std::chrono::steady_clock::time_point{} || now > next_frame_time + frame_duration) {
+ next_frame_time = now + std::chrono::duration_cast(frame_duration);
+ }
+ if (now < next_frame_time) {
+ std::this_thread::sleep_until(next_frame_time);
+ now = std::chrono::steady_clock::now();
+ }
+ next_frame_time += std::chrono::duration_cast(frame_duration);
+ if (now > next_frame_time) {
+ next_frame_time = now + std::chrono::duration_cast(frame_duration);
+ }
+ }
if (tick >= master_semaphore->CurrentTick()) {
- // Make sure we are not waiting for the current tick without signalling
Flush();
}
master_semaphore->Wait(tick);
}
- /// Waits until the next game frame based on the current game FPS.
- void WaitFPS(u64 tick, double target_fps) {
- if (master_semaphore->CurrentTick() >= tick) {
- return;
- }
- const auto frame_duration = std::chrono::duration_cast(std::chrono::duration(1.0 / target_fps));
- const auto now = std::chrono::steady_clock::now();
- if (next_frame_time == std::chrono::steady_clock::time_point{}) {
- next_frame_time = now;
- }
- next_frame_time += frame_duration;
- if (next_frame_time > now) {
- std::this_thread::sleep_until(next_frame_time);
- } else {
- next_frame_time = now;
- }
- }
-
+ /// Resets the frame pacing state by updating the next frame time to now.
void ResetFramePacing() {
next_frame_time = std::chrono::steady_clock::now();
}
@@ -150,6 +153,18 @@ public:
std::mutex submit_mutex;
private:
+#ifdef _WIN32
+ class HighResolutionTimer {
+ public:
+ HighResolutionTimer() {
+ timeBeginPeriod(1);
+ }
+ ~HighResolutionTimer() {
+ timeEndPeriod(1);
+ }
+ };
+#endif
+
class Command {
public:
virtual ~Command() = default;
@@ -285,6 +300,10 @@ private:
std::jthread worker_thread;
std::chrono::steady_clock::time_point next_frame_time{};
+
+#ifdef _WIN32
+ std::unique_ptr high_res_timer;
+#endif
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 3f56a950a3..1f31c8d5a1 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -197,23 +197,22 @@ bool Swapchain::AcquireNextImage() {
break;
}
- if (!Settings::values.use_speed_limit.GetValue()) {
+ switch (Settings::values.frame_pacing_mode.GetValue()) {
+ case Settings::FramePacingMode::Target_Auto:
scheduler.Wait(resource_ticks[image_index]);
- } else {
- switch (Settings::values.frame_pacing_mode.GetValue()) {
- case Settings::FramePacingMode::Target_Auto:
- scheduler.Wait(resource_ticks[image_index]);
- break;
- case Settings::FramePacingMode::Target_30:
- scheduler.WaitFPS(resource_ticks[image_index], 30.0);
- break;
- case Settings::FramePacingMode::Target_60:
- scheduler.WaitFPS(resource_ticks[image_index], 60.0);
- break;
- case Settings::FramePacingMode::Target_120:
- scheduler.WaitFPS(resource_ticks[image_index], 120.0);
- break;
- }
+ break;
+ case Settings::FramePacingMode::Target_30:
+ scheduler.Wait(resource_ticks[image_index], 30.0);
+ break;
+ case Settings::FramePacingMode::Target_60:
+ scheduler.Wait(resource_ticks[image_index], 60.0);
+ break;
+ case Settings::FramePacingMode::Target_120:
+ scheduler.Wait(resource_ticks[image_index], 120.0);
+ break;
+ case Settings::FramePacingMode::Target_240:
+ scheduler.Wait(resource_ticks[image_index], 240.0);
+ break;
}
resource_ticks[image_index] = scheduler.CurrentTick();