diff --git a/src/android/i_system.c b/src/android/i_system.c index e6e840ac0..1fba4d825 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 ac0ccc054..3068f2314 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 b2f10988a..3f539128e 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; + + { + // Casting the return value of a function is bad practice (apparently) + double budget = round((1.0 / R_GetFramerateCap()) * I_GetPrecisePrecision()); + capbudget = (precise_t) budget; + } I_UpdateTime(cv_timescale.value); - // Can't guarantee that I_UpdateTime won't be called inside TryRunTics - // so capture the realtics for later use - realtics = g_time.realtics; - 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 c2a86481f..a7e1eb16d 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 d11e8e576..34fb587ab 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 a26600101..1fb078b64 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 612d2ab83..ab869ca60 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 dfc5fadd5..1ea75844d 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 bf4627d54..7153aa735 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 +/** \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 */ -void I_Sleep(void); - -/** \brief Sleeps for a variable amount of time, depending on how much time the last frame took. - - \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 93f9b3410..c86973f6e 100644 --- a/src/i_time.c +++ b/src/i_time.c @@ -13,8 +13,11 @@ #include "i_time.h" +#include + #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 077174756..cab36133b 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 d06badc12..c53c83d0c 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 b3a1d0fe2..41765e6e1 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 0c0f90386..ecbebe5f4 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 f58959f1d..9f65a7616 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 267bcc175..7730a099f 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 62b1fe05c..9cac11263 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 de7369236..343de61c8 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) +UINT64 I_GetPrecisePrecision(void) { - // 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)); + return SDL_GetPerformanceFrequency(); } -double I_PreciseElapsedSeconds(precise_t before, precise_t after) -{ - return (after - before) / (double)timer_frequency; -} - -// -// 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) +void I_Sleep(UINT32 ms) { - 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) -{ - // 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 7e6f82a29..d325b2234 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