aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 05:55:09 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-28 05:55:09 -0400
commit3b3d243c0d0ace12db1085f0112e9719b868b0f4 (patch)
tree370d296d4ae3c3e3b934c55c27072da27ac13b42
parentcff4add442d78e088d13005c4c6fdd436b47eecd (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.h22
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;