On Windows, Quake uses the QueryPerformanceFrequency and QueryPerformanceCounter API calls for it's timer. These give microsecond resolution, and worked perfectly fine in the past, but on modern PCs they are causing problems. Depending on combinations of how many CPUs you have, how many CPU cores you have, whether or not you have power-saving in operation and whether or not you're 64-bit, you may or may not encounter one of these problems:
- The CPU (or Core) that Quake starts up on may not be the same CPU (or Core) that it runs on; the OS can freely switch applications between different CPUs or Cores as it sees fit, and there may be subtle differences in the counters for each.
- The initial performance frequency may not be valid if your computer was running in a low power mode when it was queried.
- Modern CPUs can be so fast that they can overflow the 64-bit value returned by the counter query.
To make the process as painless as possible we're going to just copy the code across from QuakeWorld, which - in common with all of ID's subsequent engines - uses these timers insetad of QPf/QPC. It's instructive sometimes to look at the QuakeWorld code as it's the next direct evolution of Quake and can often contain useful bugfixes that never made it back into the original codebase. I believe ID's plan at the time was to amalgamate the two codebases, and it's a shame that they never did as our corner of the world would have been a lot better if they had.
All work is in sys_win.c
First look for the Sys_Init function and replace it with this:
Code: Select all
void Sys_Init (void)
{
int i;
OSVERSIONINFO vinfo;
MaskExceptions ();
Sys_SetFPCW ();
for (i = 1;; i++)
{
MMRESULT mmr = timeBeginPeriod (i);
if (mmr == TIMERR_NOERROR) break;
if (i >= 100) Sys_Error ("Could not initialize Multimedia Timer");
}
vinfo.dwOSVersionInfoSize = sizeof (vinfo);
if (!GetVersionEx (&vinfo)) Sys_Error ("Couldn't get OS info");
if ((vinfo.dwMajorVersion < 4) || (vinfo.dwPlatformId == VER_PLATFORM_WIN32s))
Sys_Error ("QuakeWorld requires at least Win95 or NT 4.0");
if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
WinNT = true;
else WinNT = false;
}
Next we need to change Sys_FloatTime to use the new timer, so once again here's the function from QuakeWorld (it was called Sys_DoubleTime there):
Code: Select all
double Sys_FloatTime (void)
{
static DWORD starttime;
static qboolean first = true;
DWORD now;
double t;
now = timeGetTime ();
if (first)
{
first = false;
starttime = now;
return 0.0;
}
if (now < starttime) return (now / 1000.0) + (LONG_MAX - starttime / 1000.0);
if (now - starttime == 0) return 0.0;
return (now - starttime) / 1000.0;
}
The wraparound case, by the way, is only valid if Quake was started before it wrapped and is running during the actual wraparound; it should be rare enough.
That's about it, no other changes.