diff --git a/src/d_main.c b/src/d_main.c
index f29fdd22497eeed4954beaf3af57f3dbee43342d..0931944fe3297a3d78c50b5307870c1c5845d7e2 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -702,8 +702,8 @@ void D_SRB2Loop(void)
 	boolean interp = false;
 	boolean doDisplay = false;
 
-	precise_t frameTime = 0;
-	int frameElapsed = 0;
+	double frameTime = 0.0;
+	double frameElapsed = 0.0;
 
 	if (dedicated)
 		server = true;
@@ -755,8 +755,8 @@ void D_SRB2Loop(void)
 
 	for (;;)
 	{
-		frameTime = I_GetPreciseTime();
-		frameElapsed = 0;
+		frameTime = I_GetFrameTime();
+		frameElapsed = 0.0;
 
 		if (lastwipetic)
 		{
@@ -903,7 +903,7 @@ void D_SRB2Loop(void)
 		SCR_CalculateFPS();
 
 		// Fully completed frame made, handle frame cap delay.
-		frameElapsed = I_PreciseToMicros(I_GetPreciseTime() - frameTime);
+		frameElapsed = I_GetFrameTime() - frameTime;
 
 		if (!singletics)
 		{
diff --git a/src/i_system.h b/src/i_system.h
index f813135cefaab9b488ddc85a279160b732f60d34..a6f4ea70feb521d88a6df24d71a698fafdb7d285 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -42,11 +42,11 @@ extern UINT8 keyboard_started;
 */
 UINT32 I_GetFreeMem(UINT32 *total);
 
-/**	\brief  Called by D_SRB2Loop, returns current time in tics.
+/**	\brief  Called by D_SRB2Loop, returns current time in game tics.
 */
 tic_t I_GetTime(void);
 
-/** \brief  Get the current time in tics including fractions.
+/** \brief  Get the current time in game tics, including fractions.
 */
 float I_GetTimeFrac(void);
 
@@ -58,6 +58,10 @@ precise_t I_GetPreciseTime(void);
   */
 int I_PreciseToMicros(precise_t d);
 
+/** \brief  Get the current time in rendering tics, including fractions.
+*/
+double I_GetFrameTime(void);
+
 /**	\brief	Sleeps by the value of cv_sleep
 
 	\return	void
@@ -68,7 +72,7 @@ void I_Sleep(void);
 
 	\return	void
 */
-boolean I_FrameCapSleep(const int elapsed);
+boolean I_FrameCapSleep(const double elapsed);
 
 /**	\brief Get events
 
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 428186f307dd81fbf88969fb4538177c6859ed24..21b262207ab093a204e745850141762899a00d38 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -2187,6 +2187,58 @@ int I_PreciseToMicros(precise_t d)
 	return (int)(UINT64)(d / (timer_frequency / 1000000.0));
 }
 
+//
+// I_GetFrameTime
+// returns time in 1/fpscap second tics
+//
+
+static UINT32 frame_rate;
+
+static double frame_frequency;
+static UINT64 frame_epoch;
+static double elapsed_frames;
+
+static void I_InitFrameTime(const UINT64 now, const UINT32 cap)
+{
+	frame_rate = cap;
+	frame_epoch = now;
+
+	//elapsed_frames = 0.0;
+
+	if (frame_rate == 0)
+	{
+		// Shouldn't be used, but just in case...?
+		frame_frequency = 1.0;
+		return;
+	}
+
+	frame_frequency = timer_frequency / (double)frame_rate;
+}
+
+double I_GetFrameTime(void)
+{
+	const UINT64 now = SDL_GetPerformanceCounter();
+	const UINT32 cap = R_GetFramerateCap();
+
+	if (cap != frame_rate)
+	{
+		I_InitFrameTime(now, cap);
+	}
+
+	if (frame_rate == 0)
+	{
+		// Always advance a frame.
+		elapsed_frames += 1.0;
+	}
+	else
+	{
+		elapsed_frames += (now - frame_epoch) / frame_frequency;
+	}
+
+	frame_epoch = now; // moving epoch
+	return elapsed_frames;
+}
+
 //
 // I_StartupTimer
 //
@@ -2197,6 +2249,9 @@ void I_StartupTimer(void)
 
 	tic_frequency   = timer_frequency / (double)NEWTICRATE;
 	elapsed_tics    = 0.0;
+
+	I_InitFrameTime(tic_epoch, R_GetFramerateCap());
+	elapsed_frames  = 0.0;
 }
 
 //
@@ -2213,69 +2268,66 @@ void I_Sleep(void)
 // I_FrameCapSleep
 // Sleeps for a variable amount of time, depending on how much time the last frame took.
 //
-boolean I_FrameCapSleep(const int elapsed)
+boolean I_FrameCapSleep(const double elapsed)
 {
-	const INT64 delayGranularity = 2000;
-	// I picked 2ms as it's what GZDoom uses before it stops trying to sleep,
-	// but maybe other values might work better.
+	// 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;
 
-	const UINT32 capFrames = R_GetFramerateCap();
-	int capMicros = 0;
+	double capMS = 0.0;
+	double elapsedMS = 0.0;
+	double waitMS = 0.0;
 
-	if (capFrames == 0)
+	if (frame_rate == 0)
 	{
 		// We don't want to cap.
 		return false;
 	}
 
-	capMicros = 1000000 / capFrames;
+	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.
 
-	if (elapsed < capMicros)
+	if (waitMS <= 0.0)
 	{
-		const INT64 error = capMicros / 40;
-		// 2.5% ... How much we might expect the framerate to flucuate.
-		// No exact logic behind this number, simply tried stuff until the framerate
-		// reached the cap 300 more often and only overshot it occasionally.
+		// Too small of a wait, don't delay.
+		return false;
+	}
 
-		INT64 wait = (capMicros - elapsed) - error;
+	while (waitMS > 0.0)
+	{
+		double sleepStart = I_GetFrameTime();
+		double sleepEnd = sleepStart;
+		double sleepElaspedMS = 0.0;
 
-		while (wait > 0)
+		if (waitMS > delayGranularity && cv_sleep.value != -1)
 		{
-			precise_t sleepStart = I_GetPreciseTime();
-			precise_t sleepEnd = sleepStart;
-			int sleepElasped = 0;
+			// Wait 1ms at a time (on default settings)
+			// until we're close enough.
+			SDL_Delay(cv_sleep.value);
 
-			if (wait > delayGranularity && cv_sleep.value != -1)
-			{
-				// 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.
 
-				sleepEnd = I_GetPreciseTime();
-				sleepElasped = I_PreciseToMicros(sleepEnd - sleepStart);
-			}
-			else
+			while (sleepElaspedMS < waitMS)
 			{
-				// 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);
-				}
-
-				break;
+				sleepEnd = I_GetFrameTime();
+				sleepElaspedMS = (sleepEnd - sleepStart) * capMS;
 			}
 
-			wait -= sleepElasped;
+			break;
 		}
 
-		// We took our nap.
-		return true;
+		waitMS -= sleepElaspedMS;
 	}
 
-	// We're lagging behind.
-	return false;
+	// We took our nap.
+	return true;
 }
 
 #ifdef NEWSIGNALHANDLER