Understanding Game Time Revisited


For as simple a task as it seems, tracking time in Windows games is full of potential pitfalls. There are a number of different ways to do it, and the naive ones seem to work fine initially but then you have all kinds of problems later.

Case in point: the Visual Studio 2012 templates for Windows Store apps for Windows 8.0 and Windows phone 8.0. The initial version of the Direct3D game template included a simple WinRT BasicTimer class. This uses QueryPerformanceCounter to track delta and elapsed time per frame. This variable-length timing approach is very common in games, and is used by the legacy DXUT framework as well. This implementation does suffer from two major problems, however. First, it makes the mistake of using a float rather than a double to track accumulated elapsed time (see Bruce Dawson’s blog for why this is a classic blunder). Second, it does not support fixed-step gaming time which is often easier and can be more robust.

XNA Game Studio demonstrated that fixed-step timing can be a lot more useful, which was the default for the framework. See Shawn Hargreaves posts Understanding GameTime and Game timing in XNA Game Studio 2.0.

For the Visual Studio 2013 templates for Windows Store apps for Windows 8.1 and Windows phone 8.1, they no longer include BasicTimer and instead they have the C++ StepTimer class. This class also uses QueryPerformanceCounter, but supports both variable-length and fixed-step timing. It makes use of 64-bit accumulation for elapsed time, and returns time in units of seconds as a double. The timer also ensures that there's an upper-bound to the maximum delta since debugging or pausing can otherwise result in huge time jumps that are not well handled by game code. As an added bonus, since it’s no longer a WinRT class you can use it for Win32 desktop C++ programs too (with a minor switch of basic types). 

 #include <windows.h>
#include “StepTimer.h”

DX::StepTimer s_timer;

void Update(DX::StepTimer const& timer)
{
float delta = float(timer.GetElapsedSeconds());
// Do your game update here
}

void Render()
{
// Do your frame render here
}

void Tick()
{
s_timer.Tick([&]()
{
Update(s_timer);
});

Render();
}

By default StepTimer uses variable-length timing, but you can easily make it used fixed-step timing instead (for example 60 times a second):

 timer.SetFixedTimeStep(true);
timer.SetTargetElapsedSeconds(1.f / 60.f);

For each frame of your game, you’d call Tick once. This will call Update as many times as needed to ensure you are up-to-date, and then call Render once.

For pause/resume behavior, be sure to make use of ResetElapsedTime.

See this topic page for more details.

Windows Store apps/Windows phone: If your application is still using BasicTimer, you should consider updating your code to use StepTimer instead. It has no dependencies on Windows 8.1 or Windows phone 8.1.

QueryPerformanceCounter vs. RDTSC

The Intel Pentium rdtsc instruction was introduced as a way to reliably get processor cycle counts for profiling and high-resolution timing. Early games used this instruction extensively to get and compute game time. Compared to older techniques like hooking the timer interrupt, this was much better. Over time, however, problems have cropped up. The transition to multiple-core computing was a problem in the Windows XP era when the AMD Athalon X2 did not synchronize the rdtsc clock between the cores (see KB 909944), breaking a common assumption that time would always be monotonically increasing (i.e. not go backwards!). Aggressive power management schemes like Intel’s SpeedStep also broke another basic assumption that the CPU processor frequency (required to convert a processor cycle count into time units of seconds) was fixed. In fact, this is another version of age-old problem with PC games that first lead to the "Turbo button".

The work around to all these problems is the Win32 API QueryPerformanceCounter and QueryPerformanceFrequency. See Acquiring high-resolution time stamps on MSDN for more details and recommendations.

Note that the main issue game developers have hit switching from rdtsc to QPC is that rdtsc was so cheap that they called it tens of thousands of time a frame, where QPC is a system call that can be a bit slower and potentially relies on some other hardware component in the system to get a steady clock result. The best solution here is to try to centralize your delta and elapsed time computation so you don’t feel the need to recompute the delta more than a few times per frame.

Windows RT/Windows phone: The ARM instruction rdpmccntr64 is not guaranteed to be sync'd between cores, and rdtsc is not supported for this platform. Use QueryPerformanceCounter.

C++11 <chrono>

With C++11's <chrono> header you may well be tempted to go with the ‘standards-based’ solution and use high_precision_clock. Unfortunately, the VS 2012 and VS 2013 implementations of both high_precision_clock and steady_clock are not based on QueryPerformanceCounter, and instead use GetSystemTimeAsFileTime which is not nearly as high-precision. This is fixed in Visual Studio 2015.

GetTickCount

If you don’t really need a high-precision timer and instead can handle a resolution of 10 to 16 milliseconds, then GetTickCount may be a good option which returns the number of milliseconds since the system was started. Note that you should really use GetTickCount64 (requires Windows Vista or later) instead to avoid potential overflow problems. See MSDN.

Xbox One: The Xbox One XDK and Xbox One ADK Direct3D game templates also made use of BasicTimer. As of the September 2014 version, they now use StepTimer instead.

The source file attached to this post is provided subject to the MIT license

StepTimer.h


Comments (4)

  1. Dev says:

    XNA… Good times – thanks for posting!

    I am a bit disappointed that Microsoft doesn't see C# developers as game developers (unlike Sony). E.g. he is not mentioning C# at all (and post comments are not working):

    davevoyles.azurewebsites.net/publish-apps-xbox-one

    And two very explicit and very popular requests are STILL not addressed despite promises:

    visualstudio.uservoice.com/…/3725445-xna-5

    visualstudio.uservoice.com/…/4233646-allow-net-games-on-xbox-one

  2. @Dev – This blog's focus is on C++ developers for a couple of reasons. The first: The DirectX SDK historically has been mostly a C++ developer SDK. Yes, it was used to ship the Managed DIrectX 1.1 assemblies and VB 6/VB 7 support in the past, but the central purpose of the DirectX SDK had always been for C++ developers.  Second: the Games for Windows branding was a logo program for improving the quality of AAA (often "box") titles for Windows. The vast majority of such games are written in C++, not C#. As such, my focus is on helping C++ developers transition from a world dominated by the now legacy DirectX SDK to the world where it's a mix of the Windows 8.x SDK, Visual Studio tools, things on MSDN Code Gallery, and things on CodePlex.

    The story for C# development on Microsoft gaming platforms is a bit fragmented at the moment, but it is there in one form or another and should get better over time. Much of the reason I'm borrowing heavily from XNA Game Studio's design for DirectX Tool Kit is that (a) it works well, (b) the indie/student community has a bit of 'lore' around it, (c) it helps those developers interested in moving to C++ from XNA Game Studio C# get a start, and (d) it more closely matches the coding styles of the "WinRT" world for developers using it for Windows Store apps, Windows phone, and/or Xbox One so it helps with 'preparing' Win32 desktop developers when they are ready to move to the other platforms.

    Finally, while all my CodePlex projects including DirectX Tool Kit are C++, they also enable C# development scenarios. SharpDX, for example, borrowed heavily from DirectX Tool KIt's SpriteBatch class and other functionality. Ideally all existing engines would ultimately be consuming DirectXTex, DirectXTK, DirectXMesh, etc. rather than the legacy D3DX library.

    BTW, your comment got flagged for moderation automatically, likely because it had so many links in it; comments are working.

  3. @Chuck Walbourn - MSFT says:

    Thanks for responding!

    "BTW, your comment got flagged for moderation automatically, likely because it had so many links in it; comments are working." – no, it was telling something about incorrect captcha, but there is no captcha entry fields, MS-only links are usually fine even if there are lots of them.

  4. Tony says:

    Chuck….keep up the good work!

Skip to main content