From 1bff73aea0b9fab06a2d4dca0c69e69f1737a87e Mon Sep 17 00:00:00 2001
From: Sally Coolatta <tehrealsalt@gmail.com>
Date: Mon, 18 Apr 2022 17:18:31 -0400
Subject: [PATCH] Improve the framerate limiter's timing for extreme stable FPS

---
 src/d_main.c       | 32 ++++++++++----------------
 src/sdl/i_system.c | 57 +++++++++++++++++++++++++++-------------------
 2 files changed, 46 insertions(+), 43 deletions(-)

diff --git a/src/d_main.c b/src/d_main.c
index 731710a9c..470ec4fb7 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -804,25 +804,17 @@ void D_SRB2Loop(void)
 		doDisplay = false;
 		ticked = false;
 
-		frameCap = false;
-		if (interp)
-		{
-			frameCap = D_CheckFrameCap();
-		}
+		frameCap = D_CheckFrameCap();
+
+		// Moved to here from I_FinishUpdate.
+		// It doesn't track fades properly anymore by being here (might be easy fix),
+		// but it's a little more accurate for actual rendering when its here.
+		SCR_CalculateFPS();
 
 		if (!realtics && !singletics)
 		{
-			if (interp)
-			{
-				if (frameCap)
-					continue;
-			}
-			else
-			{
-				// Non-interp sleep
-				I_Sleep();
+			if (frameCap)
 				continue;
-			}
 		}
 
 #ifdef HW3SOUND
@@ -920,6 +912,11 @@ void D_SRB2Loop(void)
 		}
 		else
 		{
+			if (frameCap)
+			{
+				continue;
+			}
+
 			renderdeltatics = realtics * FRACUNIT;
 			rendertimefrac = FRACUNIT;
 		}
@@ -943,11 +940,6 @@ void D_SRB2Loop(void)
 #endif
 
 		LUA_Step();
-
-		// Moved to here from I_FinishUpdate.
-		// It doesn't track fades properly anymore by being here (might be easy fix),
-		// but it's a little more accurate for actual game logic when its here.
-		SCR_CalculateFPS();
 	}
 }
 
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 6b9b11327..b3db8b455 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2203,7 +2203,7 @@ void I_Sleep(void)
 
 boolean I_CheckFrameCap(precise_t start, precise_t end)
 {
-	UINT32 capFrames = R_GetFramerateCap();
+	const UINT32 capFrames = R_GetFramerateCap();
 	int capMicros = 0;
 
 	int elapsed;
@@ -2219,31 +2219,42 @@ boolean I_CheckFrameCap(precise_t start, precise_t end)
 
 	if (elapsed < capMicros)
 	{
-		// Wait to draw the next frame.
-		UINT32 wait = ((capMicros - elapsed) / 1000);
-
-		if (cv_sleep.value > 1)
+		// Experimental variable delay code.
+		if (cv_sleep.value > 0)
 		{
-			// 1 is the default, and in non-interpolated mode is just the bare minimum wait.
-			// Since we're already adding some wait with an FPS cap, only apply when it's above 1.
-			wait += cv_sleep.value - 1;
-		}
+			const INT64 delayGranularity = 2000; // 2ms, I picked this as it's what GZDoom uses before it stops trying to sleep.
+			INT64 wait = (capMicros - elapsed);
 
-		// If the wait's greater than our granularity value,
-		// we'll just burn the couple extra cycles in the main loop
-		// in order to get to the next frame.
-		// This makes us get to the exact FPS cap more often.
-
-		// Higher values have more wasted CPU cycles, but the in-game frame performance is better.
-		// 10ms is the average clock tick of most OS scheduling.
-		// 15ms is a little more than that, for leniency on slow machines. (This helps mine reach a stable 60, at least!)
-		// (https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdldelay.html)
-#define DELAY_GRANULARITY 15
-		if (wait >= DELAY_GRANULARITY)
-		{
-			SDL_Delay(wait);
+			while (wait > 0)
+			{
+				precise_t sleepStart = I_GetPreciseTime();
+				precise_t sleepEnd = sleepStart;
+				int sleepElasped = 0;
+
+				if (wait > delayGranularity)
+				{
+					// Wait 1ms at a time (on default settings)
+					// until we're close enough.
+					SDL_Delay(cv_sleep.value);
+
+					sleepEnd = I_GetPreciseTime();
+					sleepElasped = I_PreciseToMicros(sleepEnd - sleepStart);
+				}
+				else
+				{
+					// When we have an extremely fine wait,
+					// we do this to spin-lock the remaining time.
+
+					while (sleepElasped < wait)
+					{
+						sleepEnd = I_GetPreciseTime();
+						sleepElasped = I_PreciseToMicros(sleepEnd - sleepStart);
+					}
+				}
+
+				wait -= sleepElasped;
+			}
 		}
-#undef DELAY_GRANULARITY
 
 		return true;
 	}
-- 
GitLab