Understanding Game Time Revisited
generalOriginally posted to Chuck Walbourn's Blog on MSDN,
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.
Templates: This class is in use for the stock DirectX app templates in Visual Studio 2013 or later, and in the Xbox One XDK templates. I also make use of it in all the directx-vs-templates.
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 Microsoft Docs 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 as you’d expect–this is fixed in Visual Studio 2015 or later.
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 is subject to the MIT license and is available on GitHub