From f0d7d8467f2b7a3e7169c8522dccddca07f60f9d Mon Sep 17 00:00:00 2001
From: Eidolon <furyhunter600@gmail.com>
Date: Sun, 1 May 2022 00:32:46 -0500
Subject: [PATCH] Refactor timing code even more

System layer is greatly simplified and framecap
logic has been moved internally. I_Sleep now
takes a sleep duration and I_SleepDuration
generically implements a precise sleep with spin
loop.
---
 src/android/i_system.c | 12 +++++-
 src/d_clisrv.c         |  5 ++-
 src/d_main.c           | 58 +++++++++++++++++++---------
 src/d_net.c            |  5 ++-
 src/dummy/i_system.c   | 10 ++++-
 src/f_finale.c         |  5 ++-
 src/f_wipe.c           |  5 ++-
 src/g_game.c           |  5 ++-
 src/i_system.h         | 29 +++++++-------
 src/i_time.c           | 47 +++++++++++++++++++----
 src/i_time.h           | 11 ++++--
 src/lua_baselib.c      |  4 +-
 src/m_anigif.c         |  4 +-
 src/m_menu.c           |  3 +-
 src/m_perfstats.c      |  8 ++--
 src/p_setup.c          |  5 ++-
 src/screen.c           |  4 +-
 src/sdl/i_system.c     | 87 ++----------------------------------------
 src/win32/win_sys.c    | 21 ++++++++--
 19 files changed, 176 insertions(+), 152 deletions(-)

diff --git a/src/android/i_system.c b/src/android/i_system.c
index e6e840ac06..1fba4d8253 100644
--- a/src/android/i_system.c
+++ b/src/android/i_system.c
@@ -82,7 +82,17 @@ INT64 current_time_in_ps() {
   return (t.tv_sec * (INT64)1000000) + t.tv_usec;
 }
 
-void I_Sleep(void){}
+void I_Sleep(UINT32 ms){}
+
+precise_t I_GetPreciseTime(void)
+{
+	return 0;
+}
+
+UINT64 I_GetPrecisePrecision(void) 
+{
+	return 1000000;
+}
 
 void I_GetEvent(void){}
 
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index ac0ccc0548..3068f2314b 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2442,7 +2442,10 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
 #endif
 	}
 	else
-		I_Sleep();
+	{
+		I_Sleep(cv_sleep.value);
+		I_UpdateTime(cv_timescale.value);
+	}
 
 	return true;
 }
diff --git a/src/d_main.c b/src/d_main.c
index b2f10988a3..3f539128e4 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -696,15 +696,15 @@ tic_t rendergametic;
 
 void D_SRB2Loop(void)
 {
-	tic_t realtics = 0, rendertimeout = INFTICS;
+	tic_t entertic = 0, oldentertics = 0, realtics = 0, rendertimeout = INFTICS;
+	double deltatics = 0.0;
+	double deltasecs = 0.0;
 	static lumpnum_t gstartuplumpnum;
 
 	boolean interp = false;
 	boolean doDisplay = false;
 	boolean screenUpdate = false;
 
-	double frameEnd = 0.0;
-
 	if (dedicated)
 		server = true;
 
@@ -716,6 +716,7 @@ void D_SRB2Loop(void)
 #endif
 
 	I_UpdateTime(cv_timescale.value);
+	oldentertics = I_GetTime();
 
 	// end of loading screen: CONS_Printf() will no more call FinishUpdate()
 	con_refresh = false;
@@ -755,20 +756,30 @@ void D_SRB2Loop(void)
 
 	for (;;)
 	{
-		frameEnd = I_GetFrameTime();
+		// capbudget is the minimum precise_t duration of a single loop iteration
+		precise_t capbudget;
+		precise_t enterprecise = I_GetPreciseTime();
+		precise_t finishprecise = enterprecise;
 
-		I_UpdateTime(cv_timescale.value);
+		{
+			// Casting the return value of a function is bad practice (apparently)
+			double budget = round((1.0 / R_GetFramerateCap()) * I_GetPrecisePrecision());
+			capbudget = (precise_t) budget;
+		}
 
-		// Can't guarantee that I_UpdateTime won't be called inside TryRunTics
-		// so capture the realtics for later use
-		realtics = g_time.realtics;
+		I_UpdateTime(cv_timescale.value);
 
 		if (lastwipetic)
 		{
-			// oldentertics = lastwipetic;
+			oldentertics = lastwipetic;
 			lastwipetic = 0;
 		}
 
+		// get real tics
+		entertic = I_GetTime();
+		realtics = entertic - oldentertics;
+		oldentertics = entertic;
+
 		if (demoplayback && gamestate == GS_LEVEL)
 		{
 			// Nicer place to put this.
@@ -803,11 +814,11 @@ void D_SRB2Loop(void)
 			if (lastdraw || singletics || gametic > rendergametic)
 			{
 				rendergametic = gametic;
-				rendertimeout = g_time.time + TICRATE/17;
+				rendertimeout = entertic + TICRATE/17;
 
 				doDisplay = true;
 			}
-			else if (rendertimeout < g_time.time) // in case the server hang or netsplit
+			else if (rendertimeout < entertic) // in case the server hang or netsplit
 			{
 				// Lagless camera! Yay!
 				if (gamestate == GS_LEVEL && netgame)
@@ -839,9 +850,9 @@ void D_SRB2Loop(void)
 			// I looked at the possibility of putting in a float drawer for
 			// perfstats and it's very complicated, so we'll just do this instead...
 			ps_interp_frac.value.p = (precise_t)((FIXED_TO_FLOAT(g_time.timefrac)) * 1000.0f);
-			ps_interp_lag.value.p = (precise_t)((FIXED_TO_FLOAT(g_time.deltaseconds)) * 1000.0f);
+			ps_interp_lag.value.p = (precise_t)((deltasecs) * 1000.0f);
 
-			renderdeltatics = g_time.deltatics;
+			renderdeltatics = FLOAT_TO_FIXED(deltatics);
 
 			if (!(paused || P_AutoPause()))
 			{
@@ -873,12 +884,6 @@ void D_SRB2Loop(void)
 
 		LUA_Step();
 
-		// Fully completed frame made.
-		if (!singletics)
-		{
-			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)
@@ -888,6 +893,21 @@ void D_SRB2Loop(void)
 			PS_STOP_TIMING(ps_swaptime);
 		}
 
+		// Fully completed frame made.
+		finishprecise = I_GetPreciseTime();
+		if (!singletics)
+		{
+			INT64 elapsed = (INT64)(finishprecise - enterprecise);
+			if (elapsed > 0 && (INT64)capbudget > elapsed)
+			{
+				I_SleepDuration(capbudget - (finishprecise - enterprecise));
+			}
+		}
+		// Capture the time once more to get the real delta time.
+		finishprecise = I_GetPreciseTime();
+		deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
+		deltatics = deltasecs * NEWTICRATE;
+
 		// Only take screenshots after drawing.
 		if (moviemode)
 			M_SaveFrame();
diff --git a/src/d_net.c b/src/d_net.c
index c2a86481f5..a7e1eb16d6 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -615,7 +615,10 @@ void Net_WaitAllAckReceived(UINT32 timeout)
 	while (timeout > I_GetTime() && !Net_AllAcksReceived())
 	{
 		while (tictac == I_GetTime())
-			I_Sleep();
+		{
+			I_Sleep(cv_sleep.value);
+			I_UpdateTime(cv_timescale.value);
+		}
 		tictac = I_GetTime();
 		HGetPacket();
 		Net_AckTicker();
diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c
index d11e8e576f..34fb587ab4 100644
--- a/src/dummy/i_system.c
+++ b/src/dummy/i_system.c
@@ -11,7 +11,15 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	return 0;
 }
 
-void I_Sleep(void){}
+void I_Sleep(UINT32 ms){}
+
+precise_t I_GetPreciseTime(void) {
+	return 0;
+}
+
+UINT64 I_GetPrecisePrecision(void) {
+	return 1000000;
+}
 
 void I_GetEvent(void){}
 
diff --git a/src/f_finale.c b/src/f_finale.c
index a26600101a..1fb078b64f 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -915,7 +915,10 @@ void F_IntroTicker(void)
 				while (quittime > nowtime)
 				{
 					while (!((nowtime = I_GetTime()) - lasttime))
-						I_Sleep();
+					{
+						I_Sleep(cv_sleep.value);
+						I_UpdateTime(cv_timescale.value);
+					}
 					lasttime = nowtime;
 
 					I_OsPolling();
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 612d2ab830..ab869ca605 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -556,7 +556,10 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 
 		// wait loop
 		while (!((nowtime = I_GetTime()) - lastwipetic))
-			I_Sleep();
+		{
+			I_Sleep(cv_sleep.value);
+			I_UpdateTime(cv_timescale.value);
+		}
 		lastwipetic = nowtime;
 
 		// Wipe styles
diff --git a/src/g_game.c b/src/g_game.c
index dfc5fadd5e..1ea75844dc 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1905,7 +1905,10 @@ void G_PreLevelTitleCard(void)
 	{
 		// draw loop
 		while (!((nowtime = I_GetTime()) - lasttime))
-			I_Sleep();
+		{
+			I_Sleep(cv_sleep.value);
+			I_UpdateTime(cv_timescale.value);
+		}
 		lasttime = nowtime;
 
 		ST_runTitleCard();
diff --git a/src/i_system.h b/src/i_system.h
index bf4627d543..7153aa7357 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -42,33 +42,32 @@ extern UINT8 keyboard_started;
 */
 UINT32 I_GetFreeMem(UINT32 *total);
 
-/**	\brief	Returns precise time value for performance measurement.
+/**	\brief	Returns precise time value for performance measurement. The precise
+            time should be a monotonically increasing counter, and will wrap.
+			precise_t is internally represented as an unsigned integer and
+			integer arithmetic may be used directly between values of precise_t.
   */
 precise_t I_GetPreciseTime(void);
 
-/**	\brief	Converts a precise_t to microseconds and casts it to a 32 bit integer.
+/** \brief  Get the precision of precise_t in units per second. Invocations of
+            this function for the program's duration MUST return the same value.
   */
-int I_PreciseToMicros(precise_t d);
-
-/** \brief  Calculates the elapsed microseconds between two precise_t.
-  */
-double I_PreciseElapsedSeconds(precise_t before, precise_t after);
+UINT64 I_GetPrecisePrecision(void);
 
 /** \brief  Get the current time in rendering tics, including fractions.
 */
 double I_GetFrameTime(void);
 
-/**	\brief	Sleeps by the value of cv_sleep
-
-	\return	void
-*/
-void I_Sleep(void);
-
-/**	\brief	Sleeps for a variable amount of time, depending on how much time the last frame took.
+/**	\brief	Sleeps for the given duration in milliseconds. Depending on the
+            operating system's scheduler, the calling thread may give up its
+			time slice for a longer duration. The implementation should give a
+			best effort to sleep for the given duration, without spin-locking.
+			Calling code should check the current precise time after sleeping
+			and not assume the thread has slept for the expected duration.
 
 	\return	void
 */
-boolean I_FrameCapSleep(const double frameStart);
+void I_Sleep(UINT32 ms);
 
 /**	\brief Get events
 
diff --git a/src/i_time.c b/src/i_time.c
index 93f9b3410d..c86973f6e9 100644
--- a/src/i_time.c
+++ b/src/i_time.c
@@ -13,8 +13,11 @@
 
 #include "i_time.h"
 
+#include <math.h>
+
 #include "command.h"
 #include "doomtype.h"
+#include "d_netcmd.h"
 #include "m_fixed.h"
 #include "i_system.h"
 
@@ -36,9 +39,6 @@ void I_InitializeTime(void)
 {
 	g_time.time = 0;
 	g_time.timefrac = 0;
-	g_time.realtics = 0;
-	g_time.deltaseconds = 0;
-	g_time.ticrate = FLOAT_TO_FIXED(TICRATE);
 
 	enterprecise = 0;
 	oldenterprecise = 0;
@@ -61,7 +61,7 @@ void I_UpdateTime(fixed_t timescale)
 	ticratescaled = (double)TICRATE * FIXED_TO_FLOAT(timescale);
 
 	enterprecise = I_GetPreciseTime();
-	elapsedseconds = I_PreciseElapsedSeconds(oldenterprecise, enterprecise);
+	elapsedseconds = (double)(enterprecise - oldenterprecise) / I_GetPrecisePrecision();
 	tictimer += elapsedseconds;
 	while (tictimer > 1.0/ticratescaled)
 	{
@@ -79,8 +79,39 @@ void I_UpdateTime(fixed_t timescale)
 		fractional = modf(tictimer * ticratescaled, &integral);
 		g_time.timefrac = FLOAT_TO_FIXED(fractional);
 	}
-	g_time.realtics = realtics;
-	g_time.deltatics = FLOAT_TO_FIXED(elapsedseconds * ticratescaled);
-	g_time.deltaseconds = FLOAT_TO_FIXED(elapsedseconds);
-	g_time.ticrate = FLOAT_TO_FIXED(ticratescaled);
+}
+
+void I_SleepDuration(precise_t duration)
+{
+	UINT64 precision = I_GetPrecisePrecision();
+	INT32 sleepvalue = cv_sleep.value;
+	UINT64 delaygranularity;
+	precise_t cur;
+	precise_t dest;
+
+	{
+		double gran = round(((double)(precision / 1000) * sleepvalue * 2.1));
+		delaygranularity = (UINT64)gran;
+	}
+
+	cur = I_GetPreciseTime();
+	dest = cur + duration;
+
+	// the reason this is not dest > cur is because the precise counter may wrap
+	// two's complement arithmetic is our friend here, though!
+	// e.g. cur 0xFFFFFFFFFFFFFFFE = -2, dest 0x0000000000000001 = 1
+	// 0x0000000000000001 - 0xFFFFFFFFFFFFFFFE = 3
+	while ((INT64)(dest - cur) > 0)
+	{
+		// If our cv_sleep value exceeds the remaining sleep duration, use the
+		// hard sleep function.
+		if (sleepvalue > 0 && (dest - cur) > delaygranularity)
+		{
+			I_Sleep(sleepvalue);
+		}
+
+		// Otherwise, this is a spinloop.
+
+		cur = I_GetPreciseTime();
+	}
 }
diff --git a/src/i_time.h b/src/i_time.h
index 0771747566..cab36133b6 100644
--- a/src/i_time.h
+++ b/src/i_time.h
@@ -25,10 +25,6 @@ extern "C" {
 typedef struct timestate_s {
 	tic_t time;
 	fixed_t timefrac;
-	fixed_t realtics;
-	fixed_t deltatics;
-	fixed_t deltaseconds;
-	fixed_t ticrate;
 } timestate_t;
 
 extern timestate_t g_time;
@@ -44,6 +40,13 @@ void I_InitializeTime(void);
 
 void I_UpdateTime(fixed_t timescale);
 
+/** \brief  Block for at minimum the duration specified. This function makes a
+            best effort not to oversleep, and will spinloop if sleeping would
+			take too long. However, callers should still check the current time
+			after this returns.
+*/
+void I_SleepDuration(precise_t duration);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index d06badc12b..c53c83d0c8 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -31,7 +31,7 @@
 #include "m_misc.h" // M_MapNumber
 #include "b_bot.h" // B_UpdateBotleader
 #include "d_clisrv.h" // CL_RemovePlayer
-#include "i_system.h" // I_GetPreciseTime, I_PreciseToMicros
+#include "i_system.h" // I_GetPreciseTime, I_GetPrecisePrecision
 
 #include "lua_script.h"
 #include "lua_libs.h"
@@ -3917,7 +3917,7 @@ static int lib_gTicsToMilliseconds(lua_State *L)
 
 static int lib_getTimeMicros(lua_State *L)
 {
-	lua_pushinteger(L, I_PreciseToMicros(I_GetPreciseTime()));
+	lua_pushinteger(L, I_GetPreciseTime() / (I_GetPrecisePrecision() / 1000000));
 	return 1;
 }
 
diff --git a/src/m_anigif.c b/src/m_anigif.c
index b3a1d0fe22..41765e6e1c 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -608,7 +608,7 @@ static void GIF_framewrite(void)
 		{
 			// golden's attempt at creating a "dynamic delay"
 			UINT16 mingifdelay = 10; // minimum gif delay in milliseconds (keep at 10 because gifs can't get more precise).
-			gif_delayus += I_PreciseToMicros(I_GetPreciseTime() - gif_prevframetime); // increase delay by how much time was spent between last measurement
+			gif_delayus += (I_GetPreciseTime() - gif_prevframetime) / (I_GetPrecisePrecision() / 1000000); // increase delay by how much time was spent between last measurement
 
 			if (gif_delayus/1000 >= mingifdelay) // delay is big enough to be able to effect gif frame delay?
 			{
@@ -621,7 +621,7 @@ static void GIF_framewrite(void)
 		{
 			float delayf = ceil(100.0f/NEWTICRATE);
 
-			delay = (UINT16)I_PreciseToMicros((I_GetPreciseTime() - gif_prevframetime))/10/1000;
+			delay = (UINT16)((I_GetPreciseTime() - gif_prevframetime)) / (I_GetPrecisePrecision() / 1000000) /10/1000;
 
 			if (delay < (UINT16)(delayf))
 				delay = (UINT16)(delayf);
diff --git a/src/m_menu.c b/src/m_menu.c
index 0c0f903866..ecbebe5f4a 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -13569,7 +13569,8 @@ void M_QuitResponse(INT32 ch)
 		{
 			V_DrawScaledPatch(0, 0, 0, W_CachePatchName("GAMEQUIT", PU_PATCH)); // Demo 3 Quit Screen Tails 06-16-2001
 			I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001
-			I_Sleep();
+			I_Sleep(cv_sleep.value);
+			I_UpdateTime(cv_timescale.value);
 		}
 	}
 	I_Quit();
diff --git a/src/m_perfstats.c b/src/m_perfstats.c
index f58959f1dc..9f65a7616a 100644
--- a/src/m_perfstats.c
+++ b/src/m_perfstats.c
@@ -268,7 +268,7 @@ static INT32 PS_GetMetricAverage(ps_metric_t *metric, boolean time_metric)
 	for (i = 0; i < cv_ps_samplesize.value; i++)
 	{
 		if (time_metric)
-			sum += I_PreciseToMicros(*((precise_t*)history_read_pos));
+			sum += (*((precise_t*)history_read_pos)) / (I_GetPrecisePrecision() / 1000000);
 		else
 			sum += *((INT32*)history_read_pos);
 		history_read_pos += value_size;
@@ -288,7 +288,7 @@ static INT32 PS_GetMetricMinOrMax(ps_metric_t *metric, boolean time_metric, bool
 	{
 		INT32 value;
 		if (time_metric)
-			value = I_PreciseToMicros(*((precise_t*)history_read_pos));
+			value = (*((precise_t*)history_read_pos)) / (I_GetPrecisePrecision() / 1000000);
 		else
 			value = *((INT32*)history_read_pos);
 
@@ -316,7 +316,7 @@ static INT32 PS_GetMetricSD(ps_metric_t *metric, boolean time_metric)
 	{
 		INT64 value;
 		if (time_metric)
-			value = I_PreciseToMicros(*((precise_t*)history_read_pos));
+			value = (*((precise_t*)history_read_pos)) / (I_GetPrecisePrecision() / 1000000);
 		else
 			value = *((INT32*)history_read_pos);
 
@@ -346,7 +346,7 @@ static INT32 PS_GetMetricScreenValue(ps_metric_t *metric, boolean time_metric)
 	else
 	{
 		if (time_metric)
-			return I_PreciseToMicros(metric->value.p);
+			return (metric->value.p) / (I_GetPrecisePrecision() / 1000000);
 		else
 			return metric->value.i;
 	}
diff --git a/src/p_setup.c b/src/p_setup.c
index 267bcc1753..7730a099f2 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -4072,7 +4072,10 @@ static void P_RunSpecialStageWipe(void)
 	{
 		// wait loop
 		while (!((nowtime = I_GetTime()) - lastwipetic))
-			I_Sleep();
+		{
+			I_Sleep(cv_sleep.value);
+			I_UpdateTime(cv_timescale.value);
+		}
 		lastwipetic = nowtime;
 		if (moviemode) // make sure we save frames for the white hold too
 			M_SaveFrame();
diff --git a/src/screen.c b/src/screen.c
index 62b1fe05cd..9cac112634 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -479,12 +479,12 @@ void SCR_CalculateFPS(void)
 		return;
 	}
 
-	updateElapsed = I_PreciseToMicros(endTime - updateTime);
+	updateElapsed = (endTime - updateTime) / (I_GetPrecisePrecision() / 1000000);
 
 	if (updateElapsed >= FPS_SAMPLE_RATE)
 	{
 		static int sampleIndex = 0;
-		int frameElapsed = I_PreciseToMicros(endTime - startTime);
+		int frameElapsed = (endTime - startTime) / (I_GetPrecisePrecision() / 1000000);
 
 		fps_samples[sampleIndex] = frameElapsed / 1000.0f;
 
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index de73692363..343de61c8f 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2144,36 +2144,16 @@ ticcmd_t *I_BaseTiccmd2(void)
 
 static Uint64 timer_frequency;
 
-//
-// I_GetPreciseTime
-// returns time in precise_t
-//
 precise_t I_GetPreciseTime(void)
 {
 	return SDL_GetPerformanceCounter();
 }
 
-int I_PreciseToMicros(precise_t d)
-{
-	// d is going to be converted into a double. So remove the highest bits
-	// to avoid loss of precision in the lower bits, for the (probably rare) case
-	// that the higher bits are actually used.
-	d &= ((precise_t)1 << 53) - 1; // The mantissa of a double can handle 53 bits at most.
-	// The resulting double from the calculation is converted first to UINT64 to avoid overflow,
-	// which is undefined behaviour when converting floating point values to integers.
-	return (int)(UINT64)(d / (timer_frequency / 1000000.0));
-}
-
-double I_PreciseElapsedSeconds(precise_t before, precise_t after)
+UINT64 I_GetPrecisePrecision(void)
 {
-	return (after - before) / (double)timer_frequency;
+	return SDL_GetPerformanceFrequency();
 }
 
-//
-// I_GetFrameTime
-// returns time in 1/fpscap second tics
-//
-
 static UINT32 frame_rate;
 
 static double frame_frequency;
@@ -2233,68 +2213,9 @@ void I_StartupTimer(void)
 	elapsed_frames  = 0.0;
 }
 
-//
-// I_Sleep
-// Sleeps by the value of cv_sleep
-//
-void I_Sleep(void)
-{
-	if (cv_sleep.value > 0)
-		SDL_Delay(cv_sleep.value);
-
-	// I_Sleep is still called in a number of places
-	// we need to update the internal time state to make this work
-	I_UpdateTime(cv_timescale.value);
-}
-
-//
-// I_FrameCapSleep
-// Sleeps for a variable amount of time, depending on how much time the frame took.
-//
-boolean I_FrameCapSleep(const double t)
+void I_Sleep(UINT32 ms)
 {
-	// 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 curTime = 0.0;
-	double destTime = 0.0;
-	double sleepTime = 0.0;
-
-	if (frame_rate == 0)
-	{
-		// We don't want to cap.
-		return false;
-	}
-
-	curTime = I_GetFrameTime();
-	destTime = floor(t) + 1.0;
-
-	if (curTime >= destTime)
-	{
-		// We're already behind schedule.
-		return false;
-	}
-
-	frameMS = frame_rate * 0.001; // 1ms as frame time
-	sleepTime = destTime - (delayGranularity * frameMS);
-
-	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);
-		}
-
-		// This part will spin-lock the rest.
-		curTime = I_GetFrameTime();
-	}
-
-	// We took our nap.
-	return true;
+	SDL_Delay(ms);
 }
 
 #ifdef NEWSIGNALHANDLER
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index 7e6f82a29b..d325b22348 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -263,12 +263,25 @@ tic_t I_GetTime(void)
 	return newtics;
 }
 
-void I_Sleep(void)
+precise_t I_GetPreciseTime(void)
 {
-	if (cv_sleep.value != -1)
-		Sleep(cv_sleep.value);
+	LARGE_INTEGER time;
+	BOOL res = QueryPerformanceCounter(&time);
+	if (!res) I_Error("QueryPerformanceCounter error"); // if this happens, you've gone back to the 90s
+	return (precise_t) time.QuadPart;
+}
 
-	I_UpdateTime(cv_timescale.value);
+UINT64 I_GetPrecisePrecision(void)
+{
+	LARGE_INTEGER time;
+	BOOL res = QueryPerformanceFrequency(&time);
+	if (!res) I_Error("QueryPerformanceFrequency error"); // if this happens, you've gone back to the 90s
+	return (precise_t) time.QuadPart;
+}
+
+void I_Sleep(UINT32 ms)
+{
+	Sleep(ms);
 }
 
 // should move to i_video
-- 
GitLab