From 9d9504aa020a0ccb2633744c12b584fe7f2cb3f6 Mon Sep 17 00:00:00 2001 From: mchughalex <25671999+mchughalex@users.noreply.github.com> Date: Sat, 11 Jul 2026 08:33:49 +0100 Subject: [PATCH] Settings: route Framerate Cap to the guest-side pacer when native render is on --- src/ui/overlay/simple_settings_overlay.cpp | 62 ++++++++++++++++------ 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/src/ui/overlay/simple_settings_overlay.cpp b/src/ui/overlay/simple_settings_overlay.cpp index 1a638d1..3f10756 100644 --- a/src/ui/overlay/simple_settings_overlay.cpp +++ b/src/ui/overlay/simple_settings_overlay.cpp @@ -22,10 +22,11 @@ constexpr std::array kResolutionScales = {1, 2, 3}; constexpr std::array kResolutionLabels = {"720p (1x)", "1440p (2x)", "2160p (3x)"}; constexpr std::array kAspectRatioLabels = {"16:9", "21:9 (Experimental)"}; -constexpr std::array kFrameCapRates = {60.0, 90.0, 120.0, 144.0, 165.0, 240.0}; -constexpr std::array kFrameCapLabels = {"Unlimited", "60 FPS", "90 FPS", - "120 FPS", "144 FPS", "165 FPS", - "240 FPS"}; +constexpr std::array kFrameCapRates = {60.0, 90.0, 120.0, 140.0, + 144.0, 165.0, 240.0}; +constexpr std::array kFrameCapLabels = {"Unlimited", "60 FPS", "90 FPS", + "120 FPS", "140 FPS", "144 FPS", + "165 FPS", "240 FPS"}; constexpr std::array kCoreSimpleSettingsCvars = { "resolution_scale", "draw_resolution_scale_x", @@ -57,6 +58,9 @@ std::vector GetSimpleSettingsCvars() { if (HasCvar("skate3_field_of_view")) { cvars.push_back("skate3_field_of_view"); } + if (HasCvar("skate3_guest_fps_cap")) { + cvars.push_back("skate3_guest_fps_cap"); + } if (HasCvar("d3d12_present_frame_limiter")) { cvars.push_back("d3d12_present_frame_limiter"); } @@ -105,18 +109,35 @@ int ResolutionIndexFromCvar() { return best; } -bool HasFrameCapCvars() { +bool HasHostFrameCapCvars() { return HasCvar("d3d12_present_frame_limiter") && HasCvar("d3d12_present_frame_limiter_fps"); } +// The guest-side cap paces the game's render loop at the swap boundary, so +// both content production and present cadence land on an even beat (the VRR +// case the host present limiter can't fix: it only delays presents of frames +// whose content was already produced on an irregular schedule). It works on +// any backend, but only when the native-render hook layer that implements it +// is active; otherwise fall back to the host present limiter. +bool UseGuestFrameCap() { + return HasCvar("skate3_guest_fps_cap") && HasCvar("skate3_native_render") && + rex::cvar::Query("skate3_native_render"); +} + +bool HasFrameCapControl() { + return UseGuestFrameCap() || HasHostFrameCapCvars(); +} + int FrameCapIndexFromCvar() { - if (!HasFrameCapCvars()) { + double current = 0.0; + if (UseGuestFrameCap()) { + current = rex::cvar::Query("skate3_guest_fps_cap"); + } else if (HasHostFrameCapCvars() && rex::cvar::Query("d3d12_present_frame_limiter")) { + current = rex::cvar::Query("d3d12_present_frame_limiter_fps"); + } + if (current < 1.0) { return 0; } - if (!rex::cvar::Query("d3d12_present_frame_limiter")) { - return 0; - } - double current = rex::cvar::Query("d3d12_present_frame_limiter_fps"); int best = 1; double best_delta = 1000.0; for (int i = 0; i < static_cast(kFrameCapRates.size()); ++i) { @@ -330,12 +351,21 @@ void SimpleSettingsDialog::SaveVideo() { rex::cvar::SetFlagByName("resolution_scale", scale); rex::cvar::SetFlagByName("draw_resolution_scale_x", scale); rex::cvar::SetFlagByName("draw_resolution_scale_y", scale); - if (HasFrameCapCvars()) { + if (UseGuestFrameCap()) { + const double cap_fps = frame_cap_index_ != 0 ? kFrameCapRates[frame_cap_index_ - 1] : 0.0; + rex::cvar::SetFlagByName("skate3_guest_fps_cap", std::to_string(cap_fps)); + // Never run both pacers: the host limiter waits on the paint thread + // without backpressuring the guest, so presents drop frames on an + // irregular beat: the judder the guest cap exists to remove. + if (HasHostFrameCapCvars()) { + SetBoolCvar("d3d12_present_frame_limiter", false); + } + } else if (HasHostFrameCapCvars()) { SetBoolCvar("d3d12_present_frame_limiter", frame_cap_index_ != 0); - } - if (HasFrameCapCvars() && frame_cap_index_ != 0) { - rex::cvar::SetFlagByName("d3d12_present_frame_limiter_fps", - std::to_string(kFrameCapRates[frame_cap_index_ - 1])); + if (frame_cap_index_ != 0) { + rex::cvar::SetFlagByName("d3d12_present_frame_limiter_fps", + std::to_string(kFrameCapRates[frame_cap_index_ - 1])); + } } SetBoolCvar("fullscreen", fullscreen_); if (HasCvar("skate3_ultrawide")) { @@ -485,7 +515,7 @@ void SimpleSettingsDialog::OnDraw(ImGuiIO& io) { static_cast(kResolutionLabels.size())); EndFieldRow(); - if (HasFrameCapCvars()) { + if (HasFrameCapControl()) { BeginFieldRow("Framerate Cap"); ImGui::Combo("##frame_cap", &frame_cap_index_, kFrameCapLabels.data(), static_cast(kFrameCapLabels.size()));