From 80cb9994d52147c8da25d44fe3f1040b1046f787 Mon Sep 17 00:00:00 2001
From: Sally Coolatta <tehrealsalt@gmail.com>
Date: Mon, 25 Apr 2022 02:12:27 -0400
Subject: [PATCH] Move I_FinishUpdate to D_SRB2Loop to sync screen updates with
 FPS cap, use timestamps in I_FrameCapSleep to simplify the code

---
 src/d_main.c       | 59 +++++++++++++++++++++++-----------------------
 src/i_system.h     |  2 +-
 src/sdl/i_system.c | 53 +++++++++++++++--------------------------
 src/sdl/i_video.c  |  2 +-
 4 files changed, 51 insertions(+), 65 deletions(-)

diff --git a/src/d_main.c b/src/d_main.c
index 0931944fe3..9cfd694e72 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -297,17 +297,17 @@ gamestate_t wipegamestate = GS_LEVEL;
 INT16 wipetypepre = -1;
 INT16 wipetypepost = -1;
 
-static void D_Display(void)
+static boolean D_Display(void)
 {
 	boolean forcerefresh = false;
 	static boolean wipe = false;
 	INT32 wipedefindex = 0;
 
 	if (dedicated)
-		return;
+		return false;
 
 	if (nodrawers)
-		return; // for comparative timing/profiling
+		return false; // for comparative timing/profiling
 
 	// Lactozilla: Switching renderers works by checking
 	// if the game has to do it right when the frame
@@ -681,10 +681,10 @@ static void D_Display(void)
 			M_DrawPerfStats();
 		}
 
-		PS_START_TIMING(ps_swaptime);
-		I_FinishUpdate(); // page flip or blit buffer
-		PS_STOP_TIMING(ps_swaptime);
+		return true; // Do I_FinishUpdate in the main loop
 	}
+
+	return false;
 }
 
 // =========================================================================
@@ -701,9 +701,9 @@ void D_SRB2Loop(void)
 	boolean ticked = false;
 	boolean interp = false;
 	boolean doDisplay = false;
+	boolean screenUpdate = false;
 
-	double frameTime = 0.0;
-	double frameElapsed = 0.0;
+	double frameEnd = 0.0;
 
 	if (dedicated)
 		server = true;
@@ -755,9 +755,6 @@ void D_SRB2Loop(void)
 
 	for (;;)
 	{
-		frameTime = I_GetFrameTime();
-		frameElapsed = 0.0;
-
 		if (lastwipetic)
 		{
 			oldentertics = lastwipetic;
@@ -769,8 +766,6 @@ void D_SRB2Loop(void)
 		realtics = entertic - oldentertics;
 		oldentertics = entertic;
 
-		refreshdirmenu = 0; // not sure where to put this, here as good as any?
-
 		if (demoplayback && gamestate == GS_LEVEL)
 		{
 			// Nicer place to put this.
@@ -784,13 +779,15 @@ void D_SRB2Loop(void)
 #endif
 
 		interp = R_UsingFrameInterpolation();
-		doDisplay = false;
+		doDisplay = screenUpdate = false;
 		ticked = false;
 
 #ifdef HW3SOUND
 		HW3S_BeginFrameUpdate();
 #endif
 
+		refreshdirmenu = 0; // not sure where to put this, here as good as any?
+
 		if (realtics > 0 || singletics)
 		{
 			// don't skip more than 10 frames at a time
@@ -879,14 +876,9 @@ void D_SRB2Loop(void)
 
 		if (interp || doDisplay)
 		{
-			D_Display();
+			screenUpdate = D_Display();
 		}
 
-		if (moviemode)
-			M_SaveFrame();
-		if (takescreenshot) // Only take screenshots after drawing.
-			M_DoScreenShot();
-
 		// consoleplayer -> displayplayer (hear sounds from viewpoint)
 		S_UpdateSounds(); // move positional sounds
 		S_UpdateClosedCaptions();
@@ -897,18 +889,27 @@ void D_SRB2Loop(void)
 
 		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 rendering when its here.
-		SCR_CalculateFPS();
-
-		// Fully completed frame made, handle frame cap delay.
-		frameElapsed = I_GetFrameTime() - frameTime;
-
+		// Fully completed frame made.
+		frameEnd = I_GetFrameTime();
 		if (!singletics)
 		{
-			I_FrameCapSleep(frameElapsed);
+			I_FrameCapSleep(frameEnd);
+		}
+
+		// I_FinishUpdate is now here instead of D_Display,
+		// because it synchronizes it more closely with the frame counter.
+		if (screenUpdate == true)
+		{
+			PS_START_TIMING(ps_swaptime);
+			I_FinishUpdate(); // page flip or blit buffer
+			PS_STOP_TIMING(ps_swaptime);
 		}
+
+		// Only take screenshots after drawing.
+		if (moviemode)
+			M_SaveFrame();
+		if (takescreenshot)
+			M_DoScreenShot();
 	}
 }
 
diff --git a/src/i_system.h b/src/i_system.h
index a6f4ea70fe..f607ec79cc 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -72,7 +72,7 @@ void I_Sleep(void);
 
 	\return	void
 */
-boolean I_FrameCapSleep(const double elapsed);
+boolean I_FrameCapSleep(const double frameStart);
 
 /**	\brief Get events
 
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 21b262207a..2e1bf0c174 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2222,6 +2222,7 @@ double I_GetFrameTime(void)
 
 	if (cap != frame_rate)
 	{
+		// Maybe do this in a OnChange function for cv_fpscap?
 		I_InitFrameTime(now, cap);
 	}
 
@@ -2260,23 +2261,24 @@ void I_StartupTimer(void)
 //
 void I_Sleep(void)
 {
-	if (cv_sleep.value != -1)
+	if (cv_sleep.value > 0)
 		SDL_Delay(cv_sleep.value);
 }
 
 //
 // I_FrameCapSleep
-// Sleeps for a variable amount of time, depending on how much time the last frame took.
+// Sleeps for a variable amount of time, depending on how much time the frame took.
 //
-boolean I_FrameCapSleep(const double elapsed)
+boolean I_FrameCapSleep(const double t)
 {
 	// SDL_Delay(1) gives me a range of around 1.95ms to 2.05ms.
 	// Has a bit extra to be totally safe.
 	const double delayGranularity = 2.1;
+	double frameMS = 0.0;
 
-	double capMS = 0.0;
-	double elapsedMS = 0.0;
-	double waitMS = 0.0;
+	double curTime = 0.0;
+	double destTime = 0.0;
+	double sleepTime = 0.0;
 
 	if (frame_rate == 0)
 	{
@@ -2284,46 +2286,29 @@ boolean I_FrameCapSleep(const double elapsed)
 		return false;
 	}
 
-	capMS = 1000.0 / frame_rate; // Time of 1 frame, in milliseconds
-	elapsedMS = elapsed * capMS; // Convert elapsed from frame time to milliseconds.
-	waitMS = (capMS - elapsedMS); // How many MS to delay by.
+	curTime = I_GetFrameTime();
+	destTime = floor(t) + 1.0;
 
-	if (waitMS <= 0.0)
+	if (curTime >= destTime)
 	{
-		// Too small of a wait, don't delay.
+		// We're already behind schedule.
 		return false;
 	}
 
-	while (waitMS > 0.0)
-	{
-		double sleepStart = I_GetFrameTime();
-		double sleepEnd = sleepStart;
-		double sleepElaspedMS = 0.0;
+	frameMS = frame_rate * 0.001; // 1ms as frame time
+	sleepTime = destTime - (delayGranularity * frameMS);
 
-		if (waitMS > delayGranularity && cv_sleep.value != -1)
+	while (curTime < destTime)
+	{
+		if (curTime < sleepTime && cv_sleep.value <= 0)
 		{
 			// Wait 1ms at a time (on default settings)
 			// until we're close enough.
 			SDL_Delay(cv_sleep.value);
-
-			sleepEnd = I_GetFrameTime();
-			sleepElaspedMS = (sleepEnd - sleepStart) * capMS;
-		}
-		else
-		{
-			// When we have an extremely fine wait,
-			// we do this to spin-lock the remaining time.
-
-			while (sleepElaspedMS < waitMS)
-			{
-				sleepEnd = I_GetFrameTime();
-				sleepElaspedMS = (sleepEnd - sleepStart) * capMS;
-			}
-
-			break;
 		}
 
-		waitMS -= sleepElaspedMS;
+		// This part will spin-lock the rest.
+		curTime = I_GetFrameTime();
 	}
 
 	// We took our nap.
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index eedb60e091..8d1ea62d43 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1214,7 +1214,7 @@ void I_FinishUpdate(void)
 	if (rendermode == render_none)
 		return; //Alam: No software or OpenGl surface
 
-	//SCR_CalculateFPS(); // Moved to main loop
+	SCR_CalculateFPS();
 
 	if (I_SkipFrame())
 		return;
-- 
GitLab