Wednesday 26 January 2011

Super high precision wxDateTime

I've been working slowly on the promised program that would show some test patterns for your beloved monitor.
Recently I got stuck on the response time "patterns" for two reasons:
1. wxWidgets that I used for the platform don't exactly support fast rendering. Only method supported is GDI and OpenGL has issues with text.
2. Even deciding on what to use I still had a serious issue of wxWidgets time functions not having the required resolution. The best they manage - with wxDateTime::UNow() - is around 15ms resolution which suffices for 67FPS assuming those FPS match screen refresh intervals.

It doesn't help if I can reach 250000 FPS, but the numbers on those frames remain the same, so I went looking for a better / more precise version.

Turns out this isn't in such a high demand or at least that there aren't many solutions around.
So I just wrote my own.
I used the wxDateTime class which is already precise to the millisecond (in data, not in implementation). Unfortunately for me, but still precise enough since my solution manages quite a bit more. On my particular computer, 1/3339736 seconds precision. That is better than 1 microsecond precision.
Also, my solution is platform specific (windows) since I don't yet have any relevant Linux development VMs. If anyone cares to add cross platform code, I'm all inbox for changes required :)

I give you super high precision timer by courtesy of WTFPL license. Enjoy.

supertimer.h
#ifndef __supertimer_h
#define __supertimer_h

#include "windows.h"
#include "wx/datetime.h"

void initSuperTimer();
wxDateTime getDateTime();

#endif


supertimer.cpp
#include "supertimer.h"

LARGE_INTEGER offsetCounter, perfFreq;
wxDateTime refTime;

void initSuperTimer()
{
//Initializes global variables for use in getDateTime() function
QueryPerformanceFrequency(&perfFreq);
wxDateTime a;
a = wxDateTime::UNow();
while (((refTime = wxDateTime::UNow()) - a).GetMilliseconds() == 0)
; //This loop really hopes that UNow() has a decent resolution, otherwise it will take forever :(
QueryPerformanceCounter(&offsetCounter);
}

wxDateTime getDateTime()
{
//Gets system time accurate to the millisecond
//It could do more, but unfortunately wxDateTime isn't that precise
wxDateTime now, ref;
LARGE_INTEGER pc;
QueryPerformanceCounter(&pc);
pc.QuadPart -= offsetCounter.QuadPart;
pc.QuadPart *= 1000;
pc.QuadPart = pc.QuadPart / perfFreq.QuadPart;
ref = wxDateTime::UNow(); //Get system time for reference
now = refTime + wxTimeSpan(0, 0, 0, pc.QuadPart); //Calculate current time from reference time and time elapsed since then
if ((now - ref).GetMilliseconds() > 125)
{ //If there is more than 125ms difference between calculated and system time, reinitialize
//This also assumes that wxDateTime::UNow() is at least precise to 125ms. If it's not, this
//will constantly reinitialize the globals
initSuperTimer();
return getDateTime();
}
return now;
}