In my game I use QueryPerformanceCounter for timing. I've got "the timing issue":
Microsoft has confirmed that this is a problem in programs that make erroneous assumptions about the QueryPerformanceCounter and QueryPerformanceFrequency output.
Damn. I hate when I make erroneous assumptions.
On certain computers, the result returned by QueryPerformanceFrequency will be the clock speed of the CPU. On a computer with a processor running faster than 2.1 GHz, this frequency value requires at least 32 bits of precision. Some programs work with the result of QueryPerformanceFrequency as a signed integer value, which has only 31 bits of precision and a sign flag. These programs behave incorrectly on these faster CPUs. To avoid this problem, programs must use all 64 bits returned from both QueryPerformanceFrequency and QueryPerformanceCounter.
I learned to use QueryPerformanceCounter in a tutorial somewhere, but the heart of it is like this:
LARGE_INTEGER m_liTime; LONGLONG m_llLastTime; LONGLONG m_llTicksPerSec; ... // Get current time QueryPerformanceCounter(&m_liTime); // Calculate the frame delta m_fTimeForFrame = (float)(m_liTime.QuadPart - m_llLastTime) / (float)m_llTicksPerSec; // Save the time for other calculations m_llLastTime = m_llTime.QuadPart;
So then how do I go about using all 64 bits returned? Aren't the LARGE_INTEGER and LONGLONG all a bit dodgey to begin with?
Ok, if you're still with me, we're off to another URL!
Games need accurate timing information, but also need to implement their timing code in a way that avoids the problems associated with RDTSC usage. The following steps should be taken when implementing use of high-resolution timing:
Use the QueryPerformanceCounter and QueryPerformanceFrequency API instead of the RDTSC instruction. These APIs may make use of RDTSC, but might instead make use of a motherboard timing chip or some other system services that will provide quality high-resolution timing information.
Right. Isn't this what got me into this problem in the first place?
When computing deltas, the values should be clamped to ensure any bugs in the timing values do not cause crashes or unstable time-related computations. The clamp range should be from 0 (to prevent negative delta values)
Clamp from zero to infinity and beyond?
Finally while the QueryPerformanceCounter / QueryPerformanceFrequency API is intended to be multiprocessor aware, bugs in the BIOS or motherboard drivers may result in these routines returning different values as the thread moves from one processor to another.
Ahh, the joys of PC game development...
We recommend that all game timing be computed on a single thread, and that thread is set to stay running on a single processor through the SetThreadAffinityMask Windows API. Typically this would be the main game thread.
Ok, well I only have the main thread, running in the game's process. I haven't been able to find how to get a handle to that thread. Can I use SetProcessAffinityMask instead using the "pseudo-handle" I get from GetCurrentProcess? Shouldn't that accomplish the same thing?
Even if I can, I have read the msdn entries, but I still can't seem to figure out how to create the affinity mask itself to say "only use processor 0".
[in] The affinity mask for the threads of the process.
Yeah, that's helpful. Is this like a bit mask? It's a DWORD. 0x00000000 for processor 0? 0x00000001 for processor 1?
If I call GetProcessAffinityMask
DWORD_PTR pam; DWORD_PTR sam; if (GetProcessAffinityMask(hProcess, &pam, &sam))
on my single processor laptop, both pam and sam are have a value of 1.
So is this all I need?
It hasn't made any difference. But then again, since I don't have the ability to debug on a dual processor machine, I'm not sure what value is being returned on a multi processor setup.
Can I just do this?
Thanks for any input!