From b90d8164c6c09782b6dc0b2952cdace157284be9 Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Tue, 20 Oct 2026 08:54:41 +0200 Subject: [PATCH] fix(render): Preserve frame limiter deadlines so a 60 fps cap runs at 60 --- .../Include/Common/FrameRateLimit.h | 2 ++ .../Source/Common/FrameRateLimit.cpp | 21 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Core/GameEngine/Include/Common/FrameRateLimit.h b/Core/GameEngine/Include/Common/FrameRateLimit.h index a729313d005..c9f9949f782 100644 --- a/Core/GameEngine/Include/Common/FrameRateLimit.h +++ b/Core/GameEngine/Include/Common/FrameRateLimit.h @@ -32,6 +32,8 @@ class FrameRateLimit private: Int64 m_freq; Int64 m_start; + Int64 m_lastActual; + UnsignedInt m_previousFps; }; diff --git a/Core/GameEngine/Source/Common/FrameRateLimit.cpp b/Core/GameEngine/Source/Common/FrameRateLimit.cpp index a7c6776b1c2..609f1bde678 100644 --- a/Core/GameEngine/Source/Common/FrameRateLimit.cpp +++ b/Core/GameEngine/Source/Common/FrameRateLimit.cpp @@ -28,6 +28,8 @@ FrameRateLimit::FrameRateLimit() QueryPerformanceCounter(&start); m_freq = freq.QuadPart; m_start = start.QuadPart; + m_lastActual = m_start; + m_previousFps = 0; } Real FrameRateLimit::wait(UnsignedInt maxFps) @@ -35,6 +37,11 @@ Real FrameRateLimit::wait(UnsignedInt maxFps) PROFILER_SECTION; LARGE_INTEGER tick; QueryPerformanceCounter(&tick); + if (m_previousFps != maxFps) + { + m_start = m_lastActual; + } + m_previousFps = maxFps; double elapsedSeconds = static_cast(tick.QuadPart - m_start) / m_freq; const double targetSeconds = 1.0 / maxFps; const double sleepSeconds = targetSeconds - elapsedSeconds - 0.002; // leave ~2ms for spin wait @@ -54,8 +61,16 @@ Real FrameRateLimit::wait(UnsignedInt maxFps) } while (elapsedSeconds < targetSeconds); - m_start = tick.QuadPart; - return (Real)elapsedSeconds; + const Int64 period = static_cast(static_cast(m_freq) / maxFps + 0.5); + const Int64 deadline = m_start + period; + const double actualSeconds = static_cast(tick.QuadPart - m_lastActual) / m_freq; + m_lastActual = tick.QuadPart; + // Preserve small overshoot, but discard long pauses instead of banking catch-up. + if (tick.QuadPart - deadline < period) + m_start = deadline; + else + m_start = tick.QuadPart; + return static_cast(actualSeconds); } void FrameRateLimit::reset() @@ -63,6 +78,8 @@ void FrameRateLimit::reset() LARGE_INTEGER tick; QueryPerformanceCounter(&tick); m_start = tick.QuadPart; + m_lastActual = m_start; + m_previousFps = 0; }