Improve frame pacing precision and stability

This commit is contained in:
MaranBr 2026-02-10 08:35:58 -04:00 committed by crueter
parent 7f1a36fb52
commit 6e859f0c1a
8 changed files with 64 additions and 47 deletions

View File

@ -535,12 +535,14 @@
<item>@string/frame_pacing_mode_target_30</item>
<item>@string/frame_pacing_mode_target_60</item>
<item>@string/frame_pacing_mode_target_120</item>
<item>@string/frame_pacing_mode_target_240</item>
</string-array>
<integer-array name="framePacingModeValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<string-array name="appletEntries">

View File

@ -1030,6 +1030,7 @@
<string name="frame_pacing_mode_target_30">30 FPS</string>
<string name="frame_pacing_mode_target_60">60 FPS</string>
<string name="frame_pacing_mode_target_120">120 FPS</string>
<string name="frame_pacing_mode_target_240">240 FPS</string>
<!-- ASTC Decoding Method Choices -->
<string name="accelerate_astc_cpu" translatable="false">CPU</string>

View File

@ -437,7 +437,7 @@ struct Values {
SwitchableSetting<FramePacingMode, true> frame_pacing_mode{linkage,
FramePacingMode::Target_Auto,
FramePacingMode::Target_Auto,
FramePacingMode::Target_120,
FramePacingMode::Target_240,
"frame_pacing_mode",
Category::RendererAdvanced};

View File

@ -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);

View File

@ -505,6 +505,7 @@ std::unique_ptr<ComboboxTranslationMap> 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<Settings::VramUsageMode>::Index(),
{

View File

@ -48,14 +48,9 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)
master_semaphore{std::make_unique<MasterSemaphore>(device)},
command_pool{std::make_unique<CommandPool>(*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<CommandChunk>());
}
}*/
#ifdef _WIN32
high_res_timer = std::make_unique<HighResolutionTimer>();
#endif
AcquireNewChunk();
AllocateWorkerCommandBuffer();

View File

@ -6,6 +6,11 @@
#pragma once
#ifdef _WIN32
#include <windows.h>
#include <mmsystem.h>
#endif
#include <condition_variable>
#include <cstddef>
#include <functional>
@ -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<double>(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<std::chrono::nanoseconds>(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<std::chrono::nanoseconds>(frame_duration);
if (now > next_frame_time) {
next_frame_time = now + std::chrono::duration_cast<std::chrono::nanoseconds>(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::steady_clock::duration>(std::chrono::duration<double>(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<HighResolutionTimer> high_res_timer;
#endif
};
} // namespace Vulkan

View File

@ -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();