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 88d31ec7f2..e2c7010eeb 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1033,6 +1033,7 @@ 30 FPS 60 FPS 120 FPS + 240 FPS CPU diff --git a/src/common/settings.h b/src/common/settings.h index 1b696615d7..8a0f3dcdc6 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -442,7 +442,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();