- Rewrite win32 game tick timer backend to use performance counters and only calculate values once per frame

This commit is contained in:
Magnus Norddahl 2017-11-10 22:26:59 +01:00
parent cdf0733c8b
commit 28401cd674
2 changed files with 133 additions and 339 deletions

View file

@ -1947,8 +1947,6 @@ void TryRunTics (void)
if (debugfile) fprintf (debugfile, "run tic %d\n", gametic);
C_Ticker ();
M_Ticker ();
I_GetTime (true);
I_SetFrameTime();
G_Ticker();
gametic++;

View file

@ -118,7 +118,6 @@ extern void LayoutMainWindow(HWND hWnd, HWND pane);
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
static void CalculateCPUSpeed();
static void I_SelectTimer();
static int I_GetTimePolled(bool saveMS);
static int I_WaitForTicPolled(int prevtic);
@ -158,15 +157,9 @@ CVAR (String, queryiwad_key, "shift", CVAR_GLOBALCONFIG|CVAR_ARCHIVE);
CVAR (Bool, con_debugoutput, false, 0);
double PerfToSec, PerfToMillisec;
UINT TimerPeriod;
UINT TimerEventID;
UINT MillisecondsPerTic;
HANDLE NewTicArrived;
uint32_t LanguageIDs[4];
int (*I_GetTime) (bool saveMS);
int (*I_WaitForTic) (int);
void (*I_FreezeTime) (bool frozen);
UINT TimerPeriod;
bool gameisdead;
@ -175,23 +168,142 @@ bool gameisdead;
static ticcmd_t emptycmd;
static bool HasExited;
static DWORD basetime = 0;
// These are for the polled timer.
static DWORD TicStart;
static DWORD TicNext;
static int TicFrozen;
// These are for the event-driven timer.
static int tics;
static DWORD ted_start, ted_next;
static WadStuff *WadList;
static int NumWads;
static int DefaultWad;
static HCURSOR CustomCursor;
// CODE --------------------------------------------------------------------
//==========================================================================
//
// Tick time functions
//
//==========================================================================
static LARGE_INTEGER frequency;
static uint32_t performanceGetTime()
{
if (frequency.QuadPart != 0)
{
LARGE_INTEGER current;
QueryPerformanceCounter(&current);
return current.QuadPart * 1000 / frequency.QuadPart;
}
else
{
return timeGetTime();
}
}
static unsigned int FirstFrameStartTime;
static unsigned int CurrentFrameStartTime;
static bool TimeFrozen;
void I_SetFrameTime()
{
// It is critical that all timing is calculated only once at a start of a frame.
//
// performanceGetTime() must only ever be called once or otherwise the playsim
// processing time will affect the interpolation done by the renderer.
if (!TimeFrozen)
{
CurrentFrameStartTime = performanceGetTime();
if (FirstFrameStartTime == 0)
FirstFrameStartTime = CurrentFrameStartTime;
}
}
void I_WaitVBL(int count)
{
// I_WaitVBL is never used to actually synchronize to the vertical blank.
// Instead, it's used for delay purposes. Doom used a 70 Hz display mode,
// so that's what we use to determine how long to wait for.
Sleep(1000 * count / 70);
I_SetFrameTime();
}
static int I_WaitForTicWin32(int prevtic)
{
// Waits until the current tic is greater than prevtic. Time must not be frozen.
int time;
assert(TicFrozen == 0);
while ((time = I_GetTime(false)) <= prevtic)
{
// The minimum amount of time a thread can sleep is controlled by timeBeginPeriod.
// We set this to 1 ms in DoMain.
int sleepTime = prevtic - time;
if (sleepTime > 2)
Sleep(sleepTime - 2);
I_SetFrameTime();
}
return time;
}
unsigned int I_FPSTime()
{
if (!TimeFrozen)
return CurrentFrameStartTime;
else
return performanceGetTime();
}
unsigned int I_MSTime()
{
if (!TimeFrozen)
{
return CurrentFrameStartTime - FirstFrameStartTime;
}
else
{
if (FirstFrameStartTime == 0)
{
FirstFrameStartTime = performanceGetTime();
return 0;
}
else
{
return performanceGetTime() - FirstFrameStartTime;
}
}
}
int I_GetTimeWin32(bool saveMS)
{
return (CurrentFrameStartTime - FirstFrameStartTime) * TICRATE / 1000 + 1;
}
double I_GetTimeFrac(uint32_t *ms)
{
unsigned int currentTic = (CurrentFrameStartTime - FirstFrameStartTime) * TICRATE / 1000;
unsigned int ticStartTime = FirstFrameStartTime + currentTic * 1000 / TICRATE;
unsigned int ticNextTime = FirstFrameStartTime + (currentTic + 1) * 1000 / TICRATE;
if (ms)
*ms = currentTic + 1;
return (CurrentFrameStartTime - ticStartTime) / (double)(ticNextTime - ticStartTime);
}
void I_FreezeTimeWin32(bool frozen)
{
if (TimeFrozen && !frozen)
{
unsigned int timeFrozen = performanceGetTime() - CurrentFrameStartTime;
FirstFrameStartTime += timeFrozen;
}
TimeFrozen = frozen;
}
int(*I_GetTime)(bool saveMS) = I_GetTimeWin32;
int(*I_WaitForTic)(int) = I_WaitForTicWin32;
void(*I_FreezeTime)(bool frozen) = I_FreezeTimeWin32;
//==========================================================================
//
@ -222,314 +334,6 @@ ticcmd_t *I_BaseTiccmd()
return &emptycmd;
}
// Stubs that select the timer to use and then call into it ----------------
//==========================================================================
//
// I_GetTimeSelect
//
//==========================================================================
static int I_GetTimeSelect(bool saveMS)
{
I_SelectTimer();
return I_GetTime(saveMS);
}
//==========================================================================
//
// I_WaitForTicSelect
//
//==========================================================================
static int I_WaitForTicSelect(int prevtic)
{
I_SelectTimer();
return I_WaitForTic(prevtic);
}
//==========================================================================
//
// I_SelectTimer
//
// Tries to create a timer event for efficent CPU use when the FPS is
// capped. Failing that, it sets things up for a polling timer instead.
//
//==========================================================================
static void I_SelectTimer()
{
assert(basetime == 0);
// Use a timer event if possible.
NewTicArrived = CreateEvent(NULL, FALSE, FALSE, NULL);
if (NewTicArrived)
{
UINT delay;
const char *cmdDelay;
cmdDelay = Args->CheckValue("-timerdelay");
delay = 0;
if (cmdDelay != 0)
{
delay = atoi(cmdDelay);
}
if (delay == 0)
{
delay = 1000/TICRATE;
}
MillisecondsPerTic = delay;
TimerEventID = timeSetEvent(delay, 0, TimerTicked, 0, TIME_PERIODIC);
}
// Get the current time as the basetime.
basetime = timeGetTime();
// Set timer functions.
if (TimerEventID != 0)
{
I_GetTime = I_GetTimeEventDriven;
I_WaitForTic = I_WaitForTicEvent;
I_FreezeTime = I_FreezeTimeEventDriven;
}
else
{
I_GetTime = I_GetTimePolled;
I_WaitForTic = I_WaitForTicPolled;
I_FreezeTime = I_FreezeTimePolled;
}
}
//==========================================================================
//
// I_MSTime
//
// Returns the current time in milliseconds, where 0 is the first call
// to I_GetTime or I_WaitForTic.
//
//==========================================================================
unsigned int I_MSTime()
{
assert(basetime != 0);
return timeGetTime() - basetime;
}
//==========================================================================
//
// I_FPSTime
//
// Returns the current system time in milliseconds. This is used by the FPS
// meter of DFrameBuffer::DrawRateStuff(). Since the screen can display
// before the play simulation is ready to begin, this needs to be
// separate from I_MSTime().
//
//==========================================================================
unsigned int I_FPSTime()
{
return timeGetTime();
}
//==========================================================================
//
// I_GetTimePolled
//
// Returns the current time in tics. If saveMS is true, then calls to
// I_GetTimeFrac() will use this tic as 0 and the next tic as 1.
//
//==========================================================================
static int I_GetTimePolled(bool saveMS)
{
DWORD tm;
if (TicFrozen != 0)
{
return TicFrozen;
}
tm = timeGetTime();
if (basetime == 0)
{
basetime = tm;
}
if (saveMS)
{
TicStart = tm;
TicNext = (tm * TICRATE / 1000 + 1) * 1000 / TICRATE;
}
return ((tm-basetime)*TICRATE)/1000;
}
//==========================================================================
//
// I_WaitForTicPolled
//
// Busy waits until the current tic is greater than prevtic. Time must not
// be frozen.
//
//==========================================================================
static int I_WaitForTicPolled(int prevtic)
{
int time;
assert(TicFrozen == 0);
while ((time = I_GetTimePolled(false)) <= prevtic)
{ }
return time;
}
//==========================================================================
//
// I_FreezeTimePolled
//
// Freeze/unfreeze the timer.
//
//==========================================================================
static void I_FreezeTimePolled(bool frozen)
{
if (frozen)
{
assert(TicFrozen == 0);
TicFrozen = I_GetTimePolled(false);
}
else
{
assert(TicFrozen != 0);
int froze = TicFrozen;
TicFrozen = 0;
int now = I_GetTimePolled(false);
basetime += (now - froze) * 1000 / TICRATE;
}
}
//==========================================================================
//
// I_GetTimeEventDriven
//
// Returns the current tick counter. This is incremented asynchronously as
// the timer event fires.
//
//==========================================================================
static int I_GetTimeEventDriven(bool saveMS)
{
if (saveMS)
{
TicStart = ted_start;
TicNext = ted_next;
}
return tics;
}
//==========================================================================
//
// I_WaitForTicEvent
//
// Waits on the timer event as long as the current tic is not later than
// prevtic.
//
//==========================================================================
static int I_WaitForTicEvent(int prevtic)
{
assert(!TicFrozen);
while (prevtic >= tics)
{
WaitForSingleObject(NewTicArrived, 1000/TICRATE);
}
return tics;
}
//==========================================================================
//
// I_FreezeTimeEventDriven
//
// Freeze/unfreeze the ticker.
//
//==========================================================================
static void I_FreezeTimeEventDriven(bool frozen)
{
TicFrozen = frozen;
}
//==========================================================================
//
// TimerTicked
//
// Advance the tick count and signal the NewTicArrived event.
//
//==========================================================================
static void CALLBACK TimerTicked(UINT id, UINT msg, DWORD_PTR user, DWORD_PTR dw1, DWORD_PTR dw2)
{
if (!TicFrozen)
{
tics++;
}
ted_start = timeGetTime ();
ted_next = ted_start + MillisecondsPerTic;
SetEvent(NewTicArrived);
}
//==========================================================================
//
// I_GetTimeFrac
//
// Returns the fractional amount of a tic passed since the most recently
// saved tic.
//
//==========================================================================
static uint32_t FrameTime;
void I_SetFrameTime()
{
FrameTime = timeGetTime();
}
double I_GetTimeFrac(uint32_t *ms)
{
//DWORD now = MAX<uint32_t>(FrameTime, TicStart);
DWORD now = FrameTime;
if (FrameTime < TicStart)
{
// Preliminary kept in to see if this can happen. Should be removed once confirmed ok.
Printf("Timer underflow!\n");
}
if (ms != NULL)
{
*ms = TicNext;
}
DWORD step = TicNext - TicStart;
if (step == 0)
{
return 1.;
}
else
{
return clamp<double>(double(now - TicStart) / step, 0, 1);
}
}
//==========================================================================
//
// I_WaitVBL
//
// I_WaitVBL is never used to actually synchronize to the vertical blank.
// Instead, it's used for delay purposes. Doom used a 70 Hz display mode,
// so that's what we use to determine how long to wait for.
//
//==========================================================================
void I_WaitVBL(int count)
{
Sleep(1000 * count / 70);
}
//==========================================================================
//
// I_DetectOS
@ -736,8 +540,7 @@ void I_Init()
CalculateCPUSpeed();
DumpCPUInfo(&CPU);
I_GetTime = I_GetTimeSelect;
I_WaitForTic = I_WaitForTicSelect;
QueryPerformanceFrequency(&frequency);
atterm (I_ShutdownSound);
I_InitSound ();
@ -753,15 +556,8 @@ void I_Quit()
{
HasExited = true; /* Prevent infinitely recursive exits -- killough */
if (TimerEventID != 0)
{
timeKillEvent(TimerEventID);
}
if (NewTicArrived != NULL)
{
CloseHandle(NewTicArrived);
}
timeEndPeriod(TimerPeriod);
if (demorecording)
{
G_CheckDemoStatus();