Completely refactor timing system

Time is now tracked internally in the game using I_GetPreciseTime
and I_UpdateTime. I_Time now pulls from this internal timer. The
system code no longer needs to keep track of time itself.

This significantly improves frame and tic timing in interp mode,
resulting in a much smoother image with essentially no judder at
any framerate.
This commit is contained in:
Eidolon 2022-04-30 16:33:23 -05:00
parent dcbe929baa
commit e79654a33a
22 changed files with 193 additions and 124 deletions

View file

@ -18,6 +18,7 @@ am_map.c
command.c command.c
console.c console.c
hu_stuff.c hu_stuff.c
i_time.c
y_inter.c y_inter.c
st_stuff.c st_stuff.c
m_aatree.c m_aatree.c

View file

@ -82,18 +82,6 @@ INT64 current_time_in_ps() {
return (t.tv_sec * (INT64)1000000) + t.tv_usec; return (t.tv_sec * (INT64)1000000) + t.tv_usec;
} }
tic_t I_GetTime(void)
{
INT64 since_start = current_time_in_ps() - start_time;
return (since_start*TICRATE)/1000000;
}
fixed_t I_GetTimeFrac(void)
{
//stub
return 0;
}
void I_Sleep(void){} void I_Sleep(void){}
void I_GetEvent(void){} void I_GetEvent(void){}

View file

@ -15,6 +15,7 @@
#include <unistd.h> //for unlink #include <unistd.h> //for unlink
#endif #endif
#include "i_time.h"
#include "i_net.h" #include "i_net.h"
#include "i_system.h" #include "i_system.h"
#include "i_video.h" #include "i_video.h"

View file

@ -40,6 +40,7 @@
#include "hu_stuff.h" #include "hu_stuff.h"
#include "i_sound.h" #include "i_sound.h"
#include "i_system.h" #include "i_system.h"
#include "i_time.h"
#include "i_threads.h" #include "i_threads.h"
#include "i_video.h" #include "i_video.h"
#include "m_argv.h" #include "m_argv.h"
@ -695,10 +696,9 @@ tic_t rendergametic;
void D_SRB2Loop(void) void D_SRB2Loop(void)
{ {
tic_t oldentertics = 0, entertic = 0, realtics = 0, rendertimeout = INFTICS; tic_t realtics = 0, rendertimeout = INFTICS;
static lumpnum_t gstartuplumpnum; static lumpnum_t gstartuplumpnum;
boolean ticked = false;
boolean interp = false; boolean interp = false;
boolean doDisplay = false; boolean doDisplay = false;
boolean screenUpdate = false; boolean screenUpdate = false;
@ -715,7 +715,7 @@ void D_SRB2Loop(void)
I_DoStartupMouse(); I_DoStartupMouse();
#endif #endif
oldentertics = I_GetTime(); I_UpdateTime(cv_timescale.value);
// end of loading screen: CONS_Printf() will no more call FinishUpdate() // end of loading screen: CONS_Printf() will no more call FinishUpdate()
con_refresh = false; con_refresh = false;
@ -757,17 +757,18 @@ void D_SRB2Loop(void)
{ {
frameEnd = I_GetFrameTime(); frameEnd = I_GetFrameTime();
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) if (lastwipetic)
{ {
oldentertics = lastwipetic; // oldentertics = lastwipetic;
lastwipetic = 0; lastwipetic = 0;
} }
// get real tics
entertic = I_GetTime();
realtics = entertic - oldentertics;
oldentertics = entertic;
if (demoplayback && gamestate == GS_LEVEL) if (demoplayback && gamestate == GS_LEVEL)
{ {
// Nicer place to put this. // Nicer place to put this.
@ -782,7 +783,6 @@ void D_SRB2Loop(void)
interp = R_UsingFrameInterpolation() && !dedicated; interp = R_UsingFrameInterpolation() && !dedicated;
doDisplay = screenUpdate = false; doDisplay = screenUpdate = false;
ticked = false;
#ifdef HW3SOUND #ifdef HW3SOUND
HW3S_BeginFrameUpdate(); HW3S_BeginFrameUpdate();
@ -798,16 +798,16 @@ void D_SRB2Loop(void)
realtics = 1; realtics = 1;
// process tics (but maybe not if realtic == 0) // process tics (but maybe not if realtic == 0)
ticked = TryRunTics(realtics); TryRunTics(realtics);
if (lastdraw || singletics || gametic > rendergametic) if (lastdraw || singletics || gametic > rendergametic)
{ {
rendergametic = gametic; rendergametic = gametic;
rendertimeout = entertic+TICRATE/17; rendertimeout = g_time.time + TICRATE/17;
doDisplay = true; doDisplay = true;
} }
else if (rendertimeout < entertic) // in case the server hang or netsplit else if (rendertimeout < g_time.time) // in case the server hang or netsplit
{ {
// Lagless camera! Yay! // Lagless camera! Yay!
if (gamestate == GS_LEVEL && netgame) if (gamestate == GS_LEVEL && netgame)
@ -836,41 +836,21 @@ void D_SRB2Loop(void)
if (interp) if (interp)
{ {
static float tictime = 0.0f;
static float prevtime = 0.0f;
float entertime = I_GetTimeFrac();
fixed_t entertimefrac = FRACUNIT;
if (ticked)
{
tictime = entertime;
}
// I looked at the possibility of putting in a float drawer for // 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... // perfstats and it's very complicated, so we'll just do this instead...
ps_interp_frac.value.p = (precise_t)((entertime - tictime) * 1000.0f); ps_interp_frac.value.p = (precise_t)((FIXED_TO_FLOAT(g_time.timefrac)) * 1000.0f);
ps_interp_lag.value.p = (precise_t)((entertime - prevtime) * 1000.0f); ps_interp_lag.value.p = (precise_t)((FIXED_TO_FLOAT(g_time.deltaseconds)) * 1000.0f);
renderdeltatics = g_time.deltatics;
if (!(paused || P_AutoPause())) if (!(paused || P_AutoPause()))
{ {
if (entertime - prevtime >= 1.0f) rendertimefrac = g_time.timefrac;
{
// Lagged for more frames than a gametic...
// No need for interpolation.
entertimefrac = FRACUNIT;
} }
else else
{ {
entertimefrac = min(FRACUNIT, FLOAT_TO_FIXED(entertime - tictime)); rendertimefrac = FRACUNIT;
} }
// renderdeltatics is a bit awkard to evaluate, since the system time interface is whole tic-based
renderdeltatics = FloatToFixed(entertime - prevtime);
rendertimefrac = entertimefrac;
}
prevtime = entertime;
} }
else else
{ {
@ -1398,8 +1378,8 @@ void D_SRB2Main(void)
//---------------------------------------------------- READY TIME //---------------------------------------------------- READY TIME
// we need to check for dedicated before initialization of some subsystems // we need to check for dedicated before initialization of some subsystems
CONS_Printf("I_StartupTimer()...\n"); CONS_Printf("I_InitializeTime()...\n");
I_StartupTimer(); I_InitializeTime();
// Make backups of some SOCcable tables. // Make backups of some SOCcable tables.
P_BackupTables(); P_BackupTables();

View file

@ -18,6 +18,7 @@
#include "doomdef.h" #include "doomdef.h"
#include "g_game.h" #include "g_game.h"
#include "i_time.h"
#include "i_net.h" #include "i_net.h"
#include "i_system.h" #include "i_system.h"
#include "m_argv.h" #include "m_argv.h"

View file

@ -16,6 +16,7 @@
#include "console.h" #include "console.h"
#include "command.h" #include "command.h"
#include "i_time.h"
#include "i_system.h" #include "i_system.h"
#include "g_game.h" #include "g_game.h"
#include "hu_stuff.h" #include "hu_stuff.h"

View file

@ -35,6 +35,7 @@
#include "doomstat.h" #include "doomstat.h"
#include "d_main.h" #include "d_main.h"
#include "g_game.h" #include "g_game.h"
#include "i_time.h"
#include "i_net.h" #include "i_net.h"
#include "i_system.h" #include "i_system.h"
#include "m_argv.h" #include "m_argv.h"

View file

@ -11,16 +11,6 @@ UINT32 I_GetFreeMem(UINT32 *total)
return 0; return 0;
} }
tic_t I_GetTime(void)
{
return 0;
}
fixed_t I_GetTimeFrac(void)
{
return 0;
}
void I_Sleep(void){} void I_Sleep(void){}
void I_GetEvent(void){} void I_GetEvent(void){}

View file

@ -20,6 +20,7 @@
#include "hu_stuff.h" #include "hu_stuff.h"
#include "r_local.h" #include "r_local.h"
#include "s_sound.h" #include "s_sound.h"
#include "i_time.h"
#include "i_video.h" #include "i_video.h"
#include "v_video.h" #include "v_video.h"
#include "w_wad.h" #include "w_wad.h"

View file

@ -24,6 +24,7 @@
#include "w_wad.h" #include "w_wad.h"
#include "z_zone.h" #include "z_zone.h"
#include "i_time.h"
#include "i_system.h" #include "i_system.h"
#include "i_threads.h" #include "i_threads.h"
#include "m_menu.h" #include "m_menu.h"

View file

@ -17,6 +17,7 @@
#include "d_player.h" #include "d_player.h"
#include "d_clisrv.h" #include "d_clisrv.h"
#include "p_setup.h" #include "p_setup.h"
#include "i_time.h"
#include "i_system.h" #include "i_system.h"
#include "m_random.h" #include "m_random.h"
#include "p_local.h" #include "p_local.h"

View file

@ -19,6 +19,7 @@
#include "f_finale.h" #include "f_finale.h"
#include "p_setup.h" #include "p_setup.h"
#include "p_saveg.h" #include "p_saveg.h"
#include "i_time.h"
#include "i_system.h" #include "i_system.h"
#include "am_map.h" #include "am_map.h"
#include "m_random.h" #include "m_random.h"

View file

@ -42,14 +42,6 @@ extern UINT8 keyboard_started;
*/ */
UINT32 I_GetFreeMem(UINT32 *total); UINT32 I_GetFreeMem(UINT32 *total);
/** \brief Called by D_SRB2Loop, returns current time in game tics.
*/
tic_t I_GetTime(void);
/** \brief Get the current time in game tics, including fractions.
*/
float I_GetTimeFrac(void);
/** \brief Returns precise time value for performance measurement. /** \brief Returns precise time value for performance measurement.
*/ */
precise_t I_GetPreciseTime(void); precise_t I_GetPreciseTime(void);
@ -58,6 +50,10 @@ precise_t I_GetPreciseTime(void);
*/ */
int I_PreciseToMicros(precise_t d); int I_PreciseToMicros(precise_t d);
/** \brief Calculates the elapsed microseconds between two precise_t.
*/
double I_PreciseElapsedSeconds(precise_t before, precise_t after);
/** \brief Get the current time in rendering tics, including fractions. /** \brief Get the current time in rendering tics, including fractions.
*/ */
double I_GetFrameTime(void); double I_GetFrameTime(void);

86
src/i_time.c Normal file
View file

@ -0,0 +1,86 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2022 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file i_time.c
/// \brief Timing for the system layer.
#include "i_time.h"
#include "command.h"
#include "doomtype.h"
#include "m_fixed.h"
#include "i_system.h"
timestate_t g_time;
static CV_PossibleValue_t timescale_cons_t[] = {{FRACUNIT/20, "MIN"}, {20*FRACUNIT, "MAX"}, {0, NULL}};
consvar_t cv_timescale = CVAR_INIT ("timescale", "1.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, timescale_cons_t, NULL);
static precise_t enterprecise, oldenterprecise;
static fixed_t entertic, oldentertics;
static double tictimer;
tic_t I_GetTime(void)
{
return g_time.time;
}
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;
tictimer = 0.0;
CV_RegisterVar(&cv_timescale);
// I_StartupTimer is preserved for potential subsystems that need to setup
// timing information for I_GetPreciseTime and sleeping
I_StartupTimer();
}
void I_UpdateTime(fixed_t timescale)
{
double ticratescaled;
double elapsedseconds;
tic_t realtics;
// get real tics
ticratescaled = (double)TICRATE * FIXED_TO_FLOAT(timescale);
enterprecise = I_GetPreciseTime();
elapsedseconds = I_PreciseElapsedSeconds(oldenterprecise, enterprecise);
tictimer += elapsedseconds;
while (tictimer > 1.0/ticratescaled)
{
entertic += 1;
tictimer -= 1.0/ticratescaled;
}
realtics = entertic - oldentertics;
oldentertics = entertic;
oldenterprecise = enterprecise;
// Update global time state
g_time.time += realtics;
{
double fractional, integral;
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);
}

51
src/i_time.h Normal file
View file

@ -0,0 +1,51 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2022 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file i_time.h
/// \brief Timing for the system layer.
#ifndef __I_TIME_H__
#define __I_TIME_H__
#include "command.h"
#include "doomtype.h"
#include "m_fixed.h"
#ifdef __cplusplus
extern "C" {
#endif
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;
extern consvar_t cv_timescale;
/** \brief Called by D_SRB2Loop, returns current time in game tics.
*/
tic_t I_GetTime(void);
/** \brief Initializes timing system.
*/
void I_InitializeTime(void);
void I_UpdateTime(fixed_t timescale);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __I_TIME_H__

View file

@ -32,6 +32,7 @@
// Data. // Data.
#include "sounds.h" #include "sounds.h"
#include "s_sound.h" #include "s_sound.h"
#include "i_time.h"
#include "i_system.h" #include "i_system.h"
#include "i_threads.h" #include "i_threads.h"

View file

@ -36,6 +36,7 @@
#include "v_video.h" #include "v_video.h"
#include "z_zone.h" #include "z_zone.h"
#include "g_input.h" #include "g_input.h"
#include "i_time.h"
#include "i_video.h" #include "i_video.h"
#include "d_main.h" #include "d_main.h"
#include "m_argv.h" #include "m_argv.h"

View file

@ -21,6 +21,7 @@
#include "p_spec.h" #include "p_spec.h"
#include "p_saveg.h" #include "p_saveg.h"
#include "i_time.h"
#include "i_sound.h" // for I_PlayCD().. #include "i_sound.h" // for I_PlayCD()..
#include "i_video.h" // for I_FinishUpdate().. #include "i_video.h" // for I_FinishUpdate()..
#include "r_sky.h" #include "r_sky.h"

View file

@ -15,6 +15,7 @@
#include "screen.h" #include "screen.h"
#include "console.h" #include "console.h"
#include "am_map.h" #include "am_map.h"
#include "i_time.h"
#include "i_system.h" #include "i_system.h"
#include "i_video.h" #include "i_video.h"
#include "r_local.h" #include "r_local.h"

View file

@ -170,6 +170,7 @@ static char returnWadPath[256];
#include "../doomdef.h" #include "../doomdef.h"
#include "../m_misc.h" #include "../m_misc.h"
#include "../i_time.h"
#include "../i_video.h" #include "../i_video.h"
#include "../i_sound.h" #include "../i_sound.h"
#include "../i_system.h" #include "../i_system.h"
@ -2143,44 +2144,6 @@ ticcmd_t *I_BaseTiccmd2(void)
static Uint64 timer_frequency; static Uint64 timer_frequency;
static double tic_frequency;
static Uint64 tic_epoch;
static double elapsed_tics;
static void UpdateTicFreq(void)
{
tic_frequency = (timer_frequency / (double)NEWTICRATE) / FixedToFloat(cv_timescale.value);
}
static CV_PossibleValue_t timescale_cons_t[] = {{FRACUNIT/20, "MIN"}, {20*FRACUNIT, "MAX"}, {0, NULL}};
consvar_t cv_timescale = CVAR_INIT ("timescale", "1.0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_CALL, timescale_cons_t, UpdateTicFreq);
static void UpdateElapsedTics(void)
{
const Uint64 now = SDL_GetPerformanceCounter();
elapsed_tics += (now - tic_epoch) / tic_frequency;
tic_epoch = now; // moving epoch
}
tic_t I_GetTime(void)
{
float f = 0.0f;
UpdateElapsedTics();
// This needs kept in a separate variable before converting
// to tic_t, due to stupid -Wbad-function-cast error.
f = floor(elapsed_tics);
return (tic_t)f;
}
float I_GetTimeFrac(void)
{
UpdateElapsedTics();
return elapsed_tics;
}
// //
// I_GetPreciseTime // I_GetPreciseTime
// returns time in precise_t // returns time in precise_t
@ -2201,6 +2164,11 @@ int I_PreciseToMicros(precise_t d)
return (int)(UINT64)(d / (timer_frequency / 1000000.0)); return (int)(UINT64)(d / (timer_frequency / 1000000.0));
} }
double I_PreciseElapsedSeconds(precise_t before, precise_t after)
{
return (after - before) / (double)timer_frequency;
}
// //
// I_GetFrameTime // I_GetFrameTime
// returns time in 1/fpscap second tics // returns time in 1/fpscap second tics
@ -2259,15 +2227,9 @@ double I_GetFrameTime(void)
// //
void I_StartupTimer(void) void I_StartupTimer(void)
{ {
CV_RegisterVar(&cv_timescale);
timer_frequency = SDL_GetPerformanceFrequency(); timer_frequency = SDL_GetPerformanceFrequency();
tic_epoch = SDL_GetPerformanceCounter();
tic_frequency = timer_frequency / (double)NEWTICRATE; I_InitFrameTime(0, R_GetFramerateCap());
elapsed_tics = 0.0;
I_InitFrameTime(tic_epoch, R_GetFramerateCap());
elapsed_frames = 0.0; elapsed_frames = 0.0;
} }
@ -2279,6 +2241,10 @@ void I_Sleep(void)
{ {
if (cv_sleep.value > 0) if (cv_sleep.value > 0)
SDL_Delay(cv_sleep.value); 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);
} }
// //

View file

@ -59,6 +59,7 @@
#include "r_textures.h" #include "r_textures.h"
#include "r_patch.h" #include "r_patch.h"
#include "r_picformats.h" #include "r_picformats.h"
#include "i_time.h"
#include "i_system.h" #include "i_system.h"
#include "i_video.h" // rendermode #include "i_video.h" // rendermode
#include "md5.h" #include "md5.h"

View file

@ -35,6 +35,7 @@
#include <mmsystem.h> #include <mmsystem.h>
#include "../m_misc.h" #include "../m_misc.h"
#include "../i_time.h"
#include "../i_video.h" #include "../i_video.h"
#include "../i_sound.h" #include "../i_sound.h"
#include "../i_system.h" #include "../i_system.h"
@ -262,15 +263,12 @@ tic_t I_GetTime(void)
return newtics; return newtics;
} }
fixed_t I_GetTimeFrac(void)
{
return 0;
}
void I_Sleep(void) void I_Sleep(void)
{ {
if (cv_sleep.value != -1) if (cv_sleep.value != -1)
Sleep(cv_sleep.value); Sleep(cv_sleep.value);
I_UpdateTime(cv_timescale.value);
} }
// should move to i_video // should move to i_video