diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 05:55:09 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 05:55:09 -0400 |
| commit | 3b3d243c0d0ace12db1085f0112e9719b868b0f4 (patch) | |
| tree | 370d296d4ae3c3e3b934c55c27072da27ac13b42 | |
| parent | cff4add442d78e088d13005c4c6fdd436b47eecd (diff) | |
nx2: pace Switch 2 presents to a 60 Hz vblank
The console flips one frame per vblank and holds the rest in the swapchain,
which caps a game at 60 fps. vi2 229 reports every buffer slot free at once,
so nothing paced title 4: it presented at 90-160 fps and its frame-stepped
timing, audio included, ran ahead. Hold each present until the next vblank,
scaled by the speed limit, without banking time lost to slow frames.
| -rw-r--r-- | src/core/hle/service/nx2_gsv.h | 22 |
1 files changed, 22 insertions, 0 deletions
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index b8156b1921..6532fedd25 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -18,6 +18,7 @@ #include <vector> #include "common/alignment.h" +#include "common/settings.h" #include "core/core.h" #include "video_core/renderer_base.h" #include "core/frontend/framebuffer_layout.h" @@ -610,6 +611,7 @@ private: } WaitForFrame(~w[22], w[24]); Composite(w[5], ~w[22], w[24]); + PaceToVblank(); constexpr u64 AvailableSlots = 0x7fff; TipcReplyWords(ctx, {static_cast<u32>(AvailableSlots), static_cast<u32>(AvailableSlots >> 32), 0, 0, 0, 0}); @@ -622,6 +624,26 @@ private: // rewrites command memory it hasn't read yet. u32 m_wait_timeouts{}; + // The console flips one frame per 60 Hz vblank and keeps the rest queued, so a game can't + // present faster than that. Every slot is reported free here: hold each present until the + // next vblank instead (title 4 ran at up to 160 fps, its timing and audio with it). + std::chrono::steady_clock::time_point m_next_vblank{}; + + void PaceToVblank() { + if (!Settings::values.use_speed_limit.GetValue() || Settings::SpeedLimit() == 0) { + return; + } + const auto interval = std::chrono::nanoseconds{100'000'000'000LL / 60 / Settings::SpeedLimit()}; + const auto now = std::chrono::steady_clock::now(); + if (now > m_next_vblank + interval) { + // Running slower than 60 fps: show at once and don't bank the lost time. + m_next_vblank = now; + } else if (now < m_next_vblank) { + std::this_thread::sleep_until(m_next_vblank); + } + m_next_vblank += interval; + } + void WaitForFrame(u32 fence_id, u32 fence_value) { if (fence_id >= 192) { return; |
