diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index e85bc3592a..b0487302b3 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -534,8 +534,8 @@
- @string/frame_pacing_mode_target_Auto
- @string/frame_pacing_mode_target_30
- @string/frame_pacing_mode_target_60
+ - @string/frame_pacing_mode_target_90
- @string/frame_pacing_mode_target_120
- - @string/frame_pacing_mode_target_240
- 0
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index b703575cc5..b553402628 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -1038,8 +1038,8 @@
Auto
30 FPS
60 FPS
+ 90 FPS
120 FPS
- 240 FPS
CPU
diff --git a/src/common/settings.h b/src/common/settings.h
index a7cf800142..a03c6e1a2c 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -462,9 +462,12 @@ struct Values {
SwitchableSetting frame_pacing_mode{linkage,
FramePacingMode::Target_Auto,
FramePacingMode::Target_Auto,
- FramePacingMode::Target_240,
+ FramePacingMode::Target_120,
"frame_pacing_mode",
- Category::RendererAdvanced};
+ Category::RendererAdvanced,
+ Specialization::Default,
+ true,
+ true};
SwitchableSetting astc_recompression{linkage,
AstcRecompression::Uncompressed,
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index 62bf17c51a..d4935d9b6d 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, Target_240);
+ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
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 7d53eb609e..f4355197b0 100644
--- a/src/qt_common/config/shared_translation.cpp
+++ b/src/qt_common/config/shared_translation.cpp
@@ -517,8 +517,8 @@ std::unique_ptr ComboboxEnumeration(QObject* parent)
PAIR(FramePacingMode, Target_Auto, tr("Auto")),
PAIR(FramePacingMode, Target_30, tr("30 FPS")),
PAIR(FramePacingMode, Target_60, tr("60 FPS")),
+ PAIR(FramePacingMode, Target_90, tr("90 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 2a69d6d244..aafcfdf65b 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -347,7 +347,7 @@ void Scheduler::EndRenderPass()
Record([num_images = num_renderpass_images,
images = renderpass_images,
ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
- std::vector barriers(num_images);
+ std::array barriers;
VkPipelineStageFlags src_stages = 0;
for (size_t i = 0; i < num_images; ++i) {
const VkImageSubresourceRange& range = ranges[i];
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index fb0ac9b008..2913211480 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -115,28 +115,27 @@ public:
/// 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) {
- auto frame_duration = std::chrono::duration_cast(std::chrono::duration(1.0 / target_fps));
- auto now = std::chrono::steady_clock::now();
- if (now < next_frame_time) {
- std::this_thread::sleep_until(next_frame_time);
- next_frame_time += frame_duration;
+ const auto now = std::chrono::steady_clock::now();
+ if (start_time == std::chrono::steady_clock::time_point{} || current_target_fps != target_fps) {
+ start_time = now;
+ frame_counter = 0;
+ current_target_fps = target_fps;
+ }
+ frame_counter++;
+ std::chrono::duration frame_interval(1.0 / current_target_fps);
+ auto target_time = start_time + frame_interval * frame_counter;
+ if (target_time > now) {
+ std::this_thread::sleep_until(target_time);
} else {
- next_frame_time = now + frame_duration;
+ start_time = now;
+ frame_counter = 0;
}
}
- if (tick > master_semaphore->CurrentTick() && !chunk->Empty()) {
- Flush();
- }
- master_semaphore->Wait(tick);
- }
-
- /// Resets the frame pacing state by setting the next frame time.
- void ResetFramePacing(double target_fps = 0.0) {
- if (target_fps > 0.0) {
- auto frame_duration = std::chrono::duration_cast(std::chrono::duration(1.0 / target_fps));
- next_frame_time = std::chrono::steady_clock::now() + frame_duration;
- } else {
- next_frame_time = std::chrono::steady_clock::time_point{};
+ if (tick > 0) {
+ if (tick >= master_semaphore->CurrentTick()) {
+ Flush();
+ }
+ master_semaphore->Wait(tick);
}
}
@@ -282,7 +281,9 @@ private:
std::condition_variable_any event_cv;
std::jthread worker_thread;
- std::chrono::steady_clock::time_point next_frame_time{};
+ std::chrono::steady_clock::time_point start_time{};
+ u64 frame_counter{};
+ double current_target_fps{};
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 7e7e67639d..89aa6c4628 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -146,25 +146,6 @@ void Swapchain::Create(
{
is_outdated = false;
is_suboptimal = false;
-
- switch (Settings::values.frame_pacing_mode.GetValue()) {
- case Settings::FramePacingMode::Target_Auto:
- scheduler.ResetFramePacing();
- break;
- case Settings::FramePacingMode::Target_30:
- scheduler.ResetFramePacing(30.0);
- break;
- case Settings::FramePacingMode::Target_60:
- scheduler.ResetFramePacing(60.0);
- break;
- case Settings::FramePacingMode::Target_120:
- scheduler.ResetFramePacing(120.0);
- break;
- case Settings::FramePacingMode::Target_240:
- scheduler.ResetFramePacing(240.0);
- break;
- }
-
width = width_;
height = height_;
#ifdef ANDROID
@@ -213,24 +194,22 @@ bool Swapchain::AcquireNextImage() {
break;
}
- if (resource_ticks[image_index] != 0 && !scheduler.IsFree(resource_ticks[image_index])) {
- 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.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;
- }
+ 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.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_90:
+ scheduler.Wait(resource_ticks[image_index], 90.0);
+ break;
+ case Settings::FramePacingMode::Target_120:
+ scheduler.Wait(resource_ticks[image_index], 120.0);
+ break;
}
resource_ticks[image_index] = scheduler.CurrentTick();