mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-10 23:02:08 +00:00
- Rewrite win32 game tick timer backend to use performance counters and only calculate values once per frame
This commit is contained in:
parent
cdf0733c8b
commit
28401cd674
2 changed files with 133 additions and 339 deletions
|
@ -1947,8 +1947,6 @@ void TryRunTics (void)
|
||||||
if (debugfile) fprintf (debugfile, "run tic %d\n", gametic);
|
if (debugfile) fprintf (debugfile, "run tic %d\n", gametic);
|
||||||
C_Ticker ();
|
C_Ticker ();
|
||||||
M_Ticker ();
|
M_Ticker ();
|
||||||
I_GetTime (true);
|
|
||||||
I_SetFrameTime();
|
|
||||||
G_Ticker();
|
G_Ticker();
|
||||||
gametic++;
|
gametic++;
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,6 @@ extern void LayoutMainWindow(HWND hWnd, HWND pane);
|
||||||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||||||
|
|
||||||
static void CalculateCPUSpeed();
|
static void CalculateCPUSpeed();
|
||||||
static void I_SelectTimer();
|
|
||||||
|
|
||||||
static int I_GetTimePolled(bool saveMS);
|
static int I_GetTimePolled(bool saveMS);
|
||||||
static int I_WaitForTicPolled(int prevtic);
|
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);
|
CVAR (Bool, con_debugoutput, false, 0);
|
||||||
|
|
||||||
double PerfToSec, PerfToMillisec;
|
double PerfToSec, PerfToMillisec;
|
||||||
UINT TimerPeriod;
|
|
||||||
UINT TimerEventID;
|
|
||||||
UINT MillisecondsPerTic;
|
|
||||||
HANDLE NewTicArrived;
|
|
||||||
uint32_t LanguageIDs[4];
|
uint32_t LanguageIDs[4];
|
||||||
|
|
||||||
int (*I_GetTime) (bool saveMS);
|
UINT TimerPeriod;
|
||||||
int (*I_WaitForTic) (int);
|
|
||||||
void (*I_FreezeTime) (bool frozen);
|
|
||||||
|
|
||||||
bool gameisdead;
|
bool gameisdead;
|
||||||
|
|
||||||
|
@ -175,23 +168,142 @@ bool gameisdead;
|
||||||
static ticcmd_t emptycmd;
|
static ticcmd_t emptycmd;
|
||||||
static bool HasExited;
|
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 WadStuff *WadList;
|
||||||
static int NumWads;
|
static int NumWads;
|
||||||
static int DefaultWad;
|
static int DefaultWad;
|
||||||
|
|
||||||
static HCURSOR CustomCursor;
|
static HCURSOR CustomCursor;
|
||||||
|
|
||||||
// CODE --------------------------------------------------------------------
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Tick time functions
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static LARGE_INTEGER frequency;
|
||||||
|
|
||||||
|
static uint32_t performanceGetTime()
|
||||||
|
{
|
||||||
|
if (frequency.QuadPart != 0)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER current;
|
||||||
|
QueryPerformanceCounter(¤t);
|
||||||
|
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;
|
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
|
// I_DetectOS
|
||||||
|
@ -736,8 +540,7 @@ void I_Init()
|
||||||
CalculateCPUSpeed();
|
CalculateCPUSpeed();
|
||||||
DumpCPUInfo(&CPU);
|
DumpCPUInfo(&CPU);
|
||||||
|
|
||||||
I_GetTime = I_GetTimeSelect;
|
QueryPerformanceFrequency(&frequency);
|
||||||
I_WaitForTic = I_WaitForTicSelect;
|
|
||||||
|
|
||||||
atterm (I_ShutdownSound);
|
atterm (I_ShutdownSound);
|
||||||
I_InitSound ();
|
I_InitSound ();
|
||||||
|
@ -753,15 +556,8 @@ void I_Quit()
|
||||||
{
|
{
|
||||||
HasExited = true; /* Prevent infinitely recursive exits -- killough */
|
HasExited = true; /* Prevent infinitely recursive exits -- killough */
|
||||||
|
|
||||||
if (TimerEventID != 0)
|
|
||||||
{
|
|
||||||
timeKillEvent(TimerEventID);
|
|
||||||
}
|
|
||||||
if (NewTicArrived != NULL)
|
|
||||||
{
|
|
||||||
CloseHandle(NewTicArrived);
|
|
||||||
}
|
|
||||||
timeEndPeriod(TimerPeriod);
|
timeEndPeriod(TimerPeriod);
|
||||||
|
|
||||||
if (demorecording)
|
if (demorecording)
|
||||||
{
|
{
|
||||||
G_CheckDemoStatus();
|
G_CheckDemoStatus();
|
||||||
|
|
Loading…
Reference in a new issue