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