mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-28 23:12:24 +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);
|
||||
C_Ticker ();
|
||||
M_Ticker ();
|
||||
I_GetTime (true);
|
||||
I_SetFrameTime();
|
||||
G_Ticker();
|
||||
gametic++;
|
||||
|
||||
|
|
|
@ -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(¤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;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
|
Loading…
Reference in a new issue