Time synchronization under Windows

Turms/.exiLe

I thought that this topic could be interesting for some new coders. A good synchronization in your demo or intro is a key to success (or should be :-)). In this article I will show you some of the common techniques used by demo coders.

First of all, I hope that you know what synchronization stands for. :-). Generally speaking it means that demo/intro (that you coded) will be executed on various PCs during this same period of time. On the other hand this mechanism will help you with music and screen synchronization.

Let's go back to coding aspect of synchronization. If you are basically familiar with Windows programming, you know probably almost everything about Windows timers, which are based on WM_TIMER message. These methods are not usable in real-time coding (accuracy is far from perfect). Also you can use the GetTickCount() standard function but it's not so good as Windows Multimedia Timers or High-Resolution Timer. Both of these methods (and a few others) we will discuss in this article. I must admit that all presented topics can be found in MSDN (or at M$ www site).

In the first order, we get High-Resolution Timers on the top, which are the easiest (?) in implementation and the most precise ones. We will discuss two functions: the QueryPerformanceFrequency and the QueryPerformanceCounter. I must admit that installed hardware must support a high-resolution performance counter (check the error codes that are returned by functions).

- The QueryPerformanceFrequency function retrieves the frequency of the high-resolution performance counter, if one exists.

- The QueryPerformanceCounter function retrieves the current value of the high-resolution performance counter.

Now, when you know everything about HRT I will give you a small example (just an exampl!). I assume that we do synchronization to the lost frames.

//
// The first listing.
// Using High-Resolution Timer.
//


// our render function
void RenderProcedure( void )
{
  static bool           bFirstTime = true;      // just a simple flag
  static LONGLONG       llQPFTicksPerSec  = 0;  // frequency of HRT
  static LONGLONG       llLastElapsedTime = 0;  // last elapsed time...
  static LARGE_INTEGER  qwTime;                 // you will see...

  // first time initialising
  if( bFirstTime )
  {
    bFirstTime = false;

    LARGE_INTEGER qwTicksPerSec; // !! LARGE_int

    // Use QueryPerformanceFrequency() to get frequency of timer.
    // If QPF is not supported, we will discusse it next...
    if( !QueryPerformanceFrequency( &qwTicksPerSec ) )
      throw "No QPT";  // or smth...

    llQPFTicksPerSec = qwTicksPerSec.QuadPart;
  }

  // Now, get "time"
  QueryPerformanceCounter( &qwTime );

  // calculate the current elapsed time
    double fElapsedTime = (double) ( qwTime.QuadPart - llLastElapsedTime )
                          / (double) llQPFTicksPerSec;

  if( fElapsedTime < 1.0 )  // one second
    return;

  // set new 'last elapsed time'
  llLastElapsedTime = qwTime.QuadPart;

  //
  // Do smth.... the frame (or frames!!) has/have skipped since last call
  //
}

Now it is time for Multimedia Timers! First we will discuss the timeGetTime function, which is quite similar to the QueryPerformanceCounter function. The timeGetTime function retrieves the system time, in milliseconds. The system time is the time elapsed since Windows has started, therefore there could be the situation that our counter will exceed the maximum value (after 49 days...). An example? No problem Sir.

//
// Listing #2
// Using 'timeGetTime' function (winmm.lib is needed).
//

void RenderProcedure( void )
{
  static DWORD  dwLastElapsedTime  = 0;
    static DWORD  dwTime;
    static DWORD  dwElapsedTime;

  dwTime = timeGetTime();    // get current "time"
  dwElapsedTime = dwTime - dwLastElapsedTime;  // calculate elapsed time

  // check how many milliseconds have there passed
  if( dwElapsedTime < 25 ) // 40 fps
    return;

  dwLastElapsedTime = dwTime;  // save current time as last elapsed time

  //
  // time has passed...
  //
}

In this paragraph we will discuss the most interesting aspect of MTs (IMHO :-)). We will set our own counter in a separated thread. Don't worry! MT's API does everything for us. Functions that we will use are: timeBeginPeriod, timeSetEvent, timeKillEvent, timeEndPeriod and timeGetDevCaps. The whole trick is that our procedure will be called periodically after expected time. In that procedure we will simply increase our counter (which is a volatile variable). It's time for an example (a Windows console application).

//
// Listing #3
// (c) 2001 by Turms/.exiLe
// This is an console aplication. Add winmm.lib to project.
//

#include <windows.h>
#include <mmsystem.h>
#include <iostream.h>
#include <conio.h>


#define TIMER_RESOLUTION 2         // 2-millisecond target resolution

//
// global var.
//
volatile DWORD dwFrames;


//
// our "frame counter"
//
void CALLBACK FrameCounter( UINT wTimerID, UINT msg,
                 DWORD dwUser, DWORD dw1, DWORD dw2 )
{
  ++dwFrames;

  // of course in real app. you shouldn't write like this:
  cout << dwFrames << endl;
}


//
// - MAIN -
//
int main( void )
{
  UINT    wTimerRes;  // timer resolturion
  UINT    wTimerID;  // timer ID
  TIMECAPS  timecaps;  // needed by timeGetDevCaps

  // get max & min of sys timer
  if ( timeGetDevCaps( &timecaps, sizeof( TIMECAPS ) ) != TIMERR_NOERROR )
  {
    cout << "Error, application can't continue" << endl;
    return 0;
  }

  // get optimal resolution
  wTimerRes = max( timecaps.wPeriodMin, TIMER_RESOLUTION );

  // set minimal res for our timer
  if( timeBeginPeriod( wTimerRes ) != TIMERR_NOERROR )
  {
    // error goes here
  }


  // Now, run the timer
    wTimerID = timeSetEvent(
    500,                     // delay in milis. - half a sec.
        wTimerRes,           // resolution
        FrameCounter,        // callback function
        NULL,                // user data
    TIME_PERIODIC );         // periodic timer event

    if( wTimerID == NULL )
    {
      // error
    }

  // do nothing in the main thread
  while( !kbhit() );

  // kill the timer
  timeKillEvent( wTimerID );
  wTimerID = 0;

  timeEndPeriod( wTimerRes );    // return previous setings

return 0;
}

The last method of synchronization that I'll discuss isn't directly connected with the Windows API. When you use e.g. FMOD music player, you can use its API. Check the documentation for details.

Apart from this article you should read about the following topics related to timing under Windows. All of these texts you can easily find in MSDN. Interesting articles and topics are:

- Availability of Multimedia Timers
- High-Precision Timing Under Windows, Windows NT, & Windows 95
- High-Resolution Timer
- SAMPLE: How to Use Multimedia Timer Services on Windows 95
- Using Multimedia Timers.

Any opinions? Write to: adam007@box43.pl

Turms/.exiLe
www.exile.prv.pl (check it now! :-))