5

I'm using visual studio 2012 and would like to know about the accuracy of high_resolution_clock.

Basically I'm writing some code to display sound and images, but I need them to be very well synchronised, and the images must be tear free. I'm using directX to give tear free images and am timing screen refreshes using high_resolution_clock. The display claims to be 60 fps, however, timing with high_resolution_clock gives a refresh rate of 60.035 fps, averaged over 10000 screen refreshes. Depending upon which is correct my audio will end up out by 0.5 ms after a second, which is around 2 s after an hour. I would expect any clock to be more accurate than that - more like 1 s drift over a year, not an hour.

Has anyone ever looked at this kind of stuff before. Should I expect my sound card clock to be different again?

edit Here is my timing code. This while loop runs in my rendering thread. m_renderData is and array of structs containing the data needed for rendering my scene, it has one element per screen. For the tests I'm only running on one screen so it has only one elements

while(!TestDestroy())
{
    for(size_t i=0; i<m_renderData.size(); ++i)
    {
        //work out where in the vsync cycle we are and increment the render cycle
        //as needed until we need to actually render
        D3DRASTER_STATUS rStatus;  
        m_renderData[i].deviceD3D9->GetRasterStatus(0, &rStatus);
        if(m_renderData[i].renderStage==inVBlankRenderingComplete)
        {
            if(!rStatus.InVBlank)
                m_renderData[i].renderStage=notInVBlank;
        }
        else if(m_renderData[i].renderStage==notInVBlank)
        {
            if(rStatus.InVBlank)
                m_renderData[i].renderStage=inVBlankReadyToRender;
        }

        //check for missing the vsync for rendering
        bool timeOut=false;
        if(m_renderData[i].durations.size()>0)
        {
            double timeSinceLastRender=std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now()-m_renderData[i].durations.back()).count();
            if (timeSinceLastRender>expectedUpdatePeriod*1.2)
                timeOut=true;

        }

        if(m_renderData[i].renderStage==inVBlankReadyToRender || timeOut)
        {
            //We have reached the time to render

            //record the time and increment the number of renders that have been performed
            m_renderData[i].durations.push_back(std::chrono::high_resolution_clock::now());
            ++m_renderData[i].nRenders;

            //we calculate the fps using 10001 times - i.e. an interval of 10000 frames
            size_t fpsUpdatePeriod=10001;
            if(m_renderData[i].nRenders<fpsUpdatePeriod)
            {
                //if we don't have enough times then display a message
                m_renderData[i].fpsString = "FPS: Calculating";
            }
            else
            {
                //we have enough timing info, calculate the fps
                double meanFrameTime = std::chrono::duration_cast<std::chrono::microseconds>(m_renderData[i].durations.back()-*(m_renderData[i].durations.end()-fpsUpdatePeriod)).count()/double(fpsUpdatePeriod-1);
                double fps = 1000000.0/meanFrameTime;
                saveFps(fps);
            }

            //render to the back buffer for this screen
            renderToBackBuffer(i);

            //display the back buffer
            if(!TestDestroy())
                m_renderData[i].deviceD3D9->Present(NULL, NULL, NULL, NULL);
            //make sure we render to the correct back buffer next time
            m_renderData[i].bufferToRender--;
            //update the render cycle
            m_renderData[i].renderStage=inVBlankRenderingComplete;
        }
    }
}
Phil Rosenberg
  • 1,597
  • 1
  • 14
  • 22
  • Have you compensated for the clock drift? (i. e. make sure you set your deadline relative to the last deadline, not current time) – Erbureth Jun 24 '14 at 12:07
  • See also: http://stackoverflow.com/questions/8386128/how-to-get-the-precision-of-high-resolution-clock and http://stackoverflow.com/questions/16299029/resolution-of-stdchronohigh-resolution-clock-doesnt-correspond-to-measureme (accepted answer of second question is about MSVS2012) – Ilya Jun 24 '14 at 12:09
  • 1
    Most SO and other posts imply the C++11 chrono::high_resolution_clock is using the RDTSC/RDTSCP instructions (didn't confirm). MS guidance on high-res timers is to use QPC API instead (e.g. http://msdn.microsoft.com/en-us/library/windows/desktop/dn553408%28v=vs.85%29.aspx ) – holtavolt Jun 24 '14 at 12:18
  • 1
    Anyway, show us the code you are using for sychnronization. – Erbureth Jun 24 '14 at 12:21
  • @Erbureth Not sure what you mean about clock drift. I record the time at every screen refresh and subtract the time at the latest refresh from that 10000 refreshes ago to calculate the refresh rate – Phil Rosenberg Jun 24 '14 at 13:10
  • @Ilya That question is about precision, not accuracy – Phil Rosenberg Jun 24 '14 at 13:10
  • @holtavolt From this page http://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx it seems like QPC is also built upon TSC where available – Phil Rosenberg Jun 24 '14 at 13:10
  • @Erburth Timing code added. I haven't yet attempted to synchronise with audio as I am trying to decide if this is even feasible before I spend more tme on it (I have already spent a very long time on it!) – Phil Rosenberg Jun 24 '14 at 13:34
  • You should reverse your approach -- don't try to guess the refresh rate, but update the frame based on the high precision clock and then you can send it to the renderer. – Erbureth Jun 24 '14 at 18:16
  • @Erbureth Then how do I ensure tear free rendering and synchronise with the vsync. – Phil Rosenberg Jun 25 '14 at 09:02
  • You can still rely on those methods to time the actual rendering. You just have to determine what to render based on the clock value, not on frame count. – Erbureth Jun 25 '14 at 09:29
  • @Erubeth Okay, I see where you are going - unfortunately in this case I need to render at exactly the frame rate. So for example I need to render a particular pattern for 60 frames then a different pattern for another 60 frames. The audio must change at the time the pattern changes. The idea is deliberately that the pattern and audio change faster if the screen refresh rate is faster. Hence I need to time everything based on the screen refresh rate. Sorry I didn't include this info to start with - never know how much detail to put on these things – Phil Rosenberg Jun 25 '14 at 11:28

1 Answers1

0

Unfortunately, in VS 2012 and 2013 the chrono::high_resolution_clock is not using RDTSC. This is fixed for a future version of VS. See VS Connect. In the meantime, use QPC instead.

Chuck Walbourn
  • 38,259
  • 2
  • 58
  • 81
  • RDTSC is unreliable way to measure time and thus shouldn't be used for the purpose – JarkkoL Aug 27 '14 at 06:08
  • Using RDTSC correctly is challenging, which is why I said to use QPC. The only time that QPC is unreliable is on Athlon 64 X2 systems without updated processor drivers. – Chuck Walbourn Aug 27 '14 at 06:14
  • Well there is no way to use RDTSC "correctly" for timing. You are in the mercy of CPU throttling and potentially HW thread swapping. QPC is fine though. – JarkkoL Aug 27 '14 at 06:20
  • As noted in the VS connect bug status, ``chrono::high_resolution_clock`` will use QPC in a future release which handles all these various issues. Note that the 'HW thread swapping' problem with RDTSC was always specific to the [AMD Athalon 64 X2](http://support.microsoft.com/kb/909944/en-us) and is not an issue for any other x86/x64 processor. Of course, on Windows RT the ``__rdpmccntr64`` instruction for ARM has the exact same problem. Anyhow, don't use ``chrono::high_resolution_clock`` in VS 2012 or VS 2013. Use QPC instead. – Chuck Walbourn Aug 27 '14 at 17:54
  • HW thread swapping isn't AMD Athlon specific. It's the problem that the timing thread might be asigned to different HW thread by OS (if you don't force specific HW thread) which has its own TSC. Thing to realize is that RDTSC returns processor cycles but each HW thread can freely change its running frequency, so even if your thread runs on the same HW thread you have no way knowing the elapsed time by measuring elapsed cycles. – JarkkoL Aug 27 '14 at 18:20
  • Fair enough. Power stepping is a problem, but the first time this really blew up was when AMD X2 didn't physically sync RDTSC between cores. Anyhow, I think we aggressively agree: use QPC. – Chuck Walbourn Aug 27 '14 at 19:01