#include "ITimer.h" // QueryPerformanceCounter is very accurate, but hardware bugs can cause it to return inaccurate results // this code uses multimedia timers to check for glitches in QPC double ITimer::s_secondsPerCount = 0; TIMECAPS ITimer::s_timecaps = { 0 }; bool ITimer::s_setTime = false; UInt64 ITimer::s_lastQPC = 0; UInt64 ITimer::s_qpcWrapMargin = 0; bool ITimer::s_hasLastQPC = false; UInt32 ITimer::s_qpcWrapCount = 0; UInt32 ITimer::s_qpcInaccurateCount = 0; ITimer::ITimer() :m_qpcBase(0), m_tickBase(0) { Init(); } ITimer::~ITimer() { } void ITimer::Init(void) { if(!s_secondsPerCount) { // init qpc UInt64 countsPerSecond; BOOL res = QueryPerformanceFrequency((LARGE_INTEGER *)&countsPerSecond); ASSERT_STR(res, "ITimer: no high-resolution timer support"); s_secondsPerCount = 1.0 / countsPerSecond; s_qpcWrapMargin = (UInt64)(-((SInt64)(countsPerSecond * 60))); // detect if we've wrapped around by a delta greater than this - also limits max time _MESSAGE("s_qpcWrapMargin: %016I64X", s_qpcWrapMargin); _MESSAGE("wrap time: %fs", ((double)0xFFFFFFFFFFFFFFFF) * s_secondsPerCount); // init multimedia timer timeGetDevCaps(&s_timecaps, sizeof(s_timecaps)); _MESSAGE("min timer period = %d", s_timecaps.wPeriodMin); s_setTime = (timeBeginPeriod(s_timecaps.wPeriodMin) == TIMERR_NOERROR); if(!s_setTime) _WARNING("couldn't change timer period"); } } void ITimer::DeInit(void) { if(s_secondsPerCount) { if(s_setTime) { timeEndPeriod(s_timecaps.wPeriodMin); s_setTime = false; } if(s_qpcWrapCount) _MESSAGE("s_qpcWrapCount: %d", s_qpcWrapCount); s_secondsPerCount = 0; } } void ITimer::Start(void) { m_qpcBase = GetQPC(); m_tickBase = timeGetTime(); } double ITimer::GetElapsedTime(void) { UInt64 qpcNow = GetQPC(); UInt32 tickNow = timeGetTime(); UInt64 qpcDelta = qpcNow - m_qpcBase; UInt64 tickDelta = tickNow - m_tickBase; double qpcSeconds = ((double)qpcDelta) * s_secondsPerCount; double tickSeconds = ((double)tickDelta) * 0.001; // ticks are in milliseconds double qpcTickDelta = qpcSeconds - tickSeconds; if(qpcTickDelta < 0) qpcTickDelta = -qpcTickDelta; // if they differ by more than one second, something's wrong, return if(qpcTickDelta > 1) { s_qpcInaccurateCount++; return tickSeconds; } else { return qpcSeconds; } } UInt64 ITimer::GetQPC(void) { UInt64 now; QueryPerformanceCounter((LARGE_INTEGER *)&now); if(s_hasLastQPC) { UInt64 delta = now - s_lastQPC; if(delta > s_qpcWrapMargin) { // we've gone back in time, return a kludged value s_lastQPC = now; now = s_lastQPC + 1; s_qpcWrapCount++; } else { s_lastQPC = now; } } else { s_hasLastQPC = true; s_lastQPC = now; } return now; }