134 lines
2.7 KiB
C++
134 lines
2.7 KiB
C++
#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;
|
|
}
|