Converting the DirectX timer for easy use
Almost every aspect of a video game uses a timer. Particle systems use them to calculate the motions and new positions of every one of the thousands of little sprites they contain, character animations user them to start and stop the motions and syncronize with other objects in the world. Camera scripts can use them to create video panning effects other cinemetography tricks. All in all a timer is probably one of the most important aspects of any game, and it is critical that th timer you use in your engine is versatile and easy to use. For this I have found it best to take the timer that comes with the DirectX SDK utllities and to simply convert it to be a class of its own so we can use as many instances of it as we need.
Step 1: Retreiving the code from dxmutmisc.cs
The first thing we need to do is find the file we are going to be taking our timer code from, it is ususally located in the managed samples that come with the SDK here: C:\Program Files\Microsoft DirectX 9.0 SDK (October 2005)\Samples\Managed\Common\dxmutmisc.cs. Open this file up and look around at all the wonderful utilities the people at Microsoft have provided us with by default. What we need is located between the #region Timer / #endregion. I am simply going to start by copying that section of code and pasting it into an empty HMUtility.cs file. The code will look like this:
#region Timer public class FrameworkTimer { #region Instance Data private static bool isUsingQPF; private static bool isTimerStopped; private static long ticksPerSecond; private static long stopTime; private static long lastElapsedTime; private static long baseTime; #endregion #region Creation private FrameworkTimer() { } // No creation /// <summary> /// Static creation routine /// </summary> static FrameworkTimer() { isTimerStopped = true; ticksPerSecond = 0; stopTime = 0; lastElapsedTime = 0; baseTime = 0; // Use QueryPerformanceFrequency to get frequency of the timer isUsingQPF = NativeMethods.QueryPerformanceFrequency(ref ticksPerSecond); } #endregion /// <summary> /// Resets the timer /// </summary> public static void Reset() { if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); baseTime = time; lastElapsedTime = time; stopTime = 0; isTimerStopped = false; } /// <summary> /// Starts the timer /// </summary> public static void Start() { if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); if(isTimerStopped) baseTime += (time – stopTime); stopTime = 0; lastElapsedTime = time; isTimerStopped = false; } /// <summary> /// Stop (or pause) the timer /// </summary> public static void Stop() { if(!isUsingQPF) return; // Nothing to do if(!isTimerStopped) { // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); stopTime = time; lastElapsedTime = time; isTimerStopped = true; } } /// <summary> /// Advance the timer a tenth of a second /// </summary> public static void Advance() { if(!isUsingQPF) return; // Nothing to do stopTime += ticksPerSecond / 10; } /// <summary> /// Get the absolute system time /// </summary> public static double GetAbsoluteTime() { if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); double absolueTime = time / (double)ticksPerSecond; return absolueTime; } /// <summary> /// Get the current time /// </summary> public static double GetTime() { if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); double appTime = (double)(time – baseTime) / (double)ticksPerSecond; return appTime; } /// <summary> /// get the time that elapsed between GetElapsedTime() calls /// </summary> public static double GetElapsedTime() { if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); double elapsedTime = (double)(time – lastElapsedTime) / (double)ticksPerSecond; lastElapsedTime = time; return elapsedTime; } /// <summary> /// Returns true if timer stopped /// </summary> public static bool IsStopped { get { return isTimerStopped; } } } #endregion
Now this looks a bit messy, but there really isn’t much we will want to do to clean it up as almost all of the code is required for the timer functions to work correctly. One thing we do need to do however is retrieve the NativeMethods.QueryPerformance(Counter/Frequency) function so we can actually compile the timer. This is in the same file the timer was in in the NativeMethods/Windows API Calls section, and it looks like this:
[System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency); [System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceCounter(ref long PerformanceCount);
Now if we remove the word NativeMethods from in front of each of these calls the class will compile successfully. You could simply leave the code as it is now and be able to use the timer exactly the same way that the DirectX SDK demos and samples do, but I prefer to have an instanceable class so I can more easily keep track of which timer I am using. Converting this timer to a non-static one is simply a matter of removing all of the static declarations in front of the methods and instance variables. I am also going to change the comment structure a bit to save some space here in the tutorial, but feel free to leave the XML formatted comments as they are if you would rather. Here is what the final converted timer looks like:
using System.Runtime.InteropServices; namespace HMEngine { public class FrameworkTimer { private bool isUsingQPF; private bool isTimerStopped; private long ticksPerSecond; private long stopTime; private long lastElapsedTime; private long baseTime; public bool IsStopped { get { return isTimerStopped; } } // Returns true if timer stopped [System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency); [System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceCounter(ref long PerformanceCount); public FrameworkTimer() { isTimerStopped = true; ticksPerSecond = 0; stopTime = 0; lastElapsedTime = 0; baseTime = 0; // Use QueryPerformanceFrequency to get frequency of the timer isUsingQPF = QueryPerformanceFrequency(ref ticksPerSecond); } public void Reset() { // Resets the timer if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); baseTime = time; lastElapsedTime = time; stopTime = 0; isTimerStopped = false; } public void Start() { // Starts the timer if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); if(isTimerStopped) baseTime += (time – stopTime); stopTime = 0; lastElapsedTime = time; isTimerStopped = false; } public void Stop() { // Stop (or pause) the timer if(!isUsingQPF) return; // Nothing to do if(!isTimerStopped) { // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); stopTime = time; lastElapsedTime = time; isTimerStopped = true; } } public void Advance() { // Advance the timer a tenth of a second if(!isUsingQPF) return; // Nothing to do stopTime += ticksPerSecond / 10; } public double GetAbsoluteTime() { // Get the absolute system time if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); double absolueTime = time / (double)ticksPerSecond; return absolueTime; } public double GetTime() { // Get the current time if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); double appTime = (double)(time – baseTime) / (double)ticksPerSecond; return appTime; } public double GetElapsedTime() { // Get time elapsed between GetElapsedTime() calls if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); double elapsedTime = (double)(time – lastElapsedTime) / (double)ticksPerSecond; lastElapsedTime = time; return elapsedTime; } } }
A bit cleaner wouldn’t you say? Well, maybe not, but I sure think it is more readable this way. Anyway, now that we have a timer that we can use, why don’t we get to using it? I will put together a simple frames-per-second counter now just so you can see the timer in action.
Step 2: Using our Timer for FPS
The first thing we will need to do is create a new instance of our timer in the HMSystem code so we can use it to time the seconds that elapse between frames. This code is pretty simple, but I’ll show you where I put it anyway:
private Device myDevice; private HMCamera myCamera; private HMScene myScene; private FrameworkTimer myTimer; // Variables for FPS calculations/drawing private Microsoft.DirectX.Direct3D.Font frameFont; private int framesPassed; private float timeElapsed; private float fps; private int frameResetValue = 10; … if(InitializeGraphics()) { myCamera = new HMCamera(this.Width, this.Height); myScene = new HMScene(); myTimer = new FrameworkTimer(); myTimer.Start(); frameFont = new Microsoft.DirectX.Direct3D.Font( myDevice, new System.Drawing.Font(“Courier New”, 16, FontStyle.Bold) ); }
We are also going to need a function that acutally calculates this FPS value. There are many ways to do this, but I have found that one of the easiest and least buggy methods is to simply keep track of a number of frames and the amount of time that has passed since they have gone by and divide them. We also need to have the video card draw our FPS on the screen, so we will write a small function for that as well. Here are the two functions:
private void CalculateFPS() { framesPassed++; timeElapsed += (float)myTimer.GetElapsedTime(); if(framesPassed > frameResetValue) { fps = (float)framesPassed / (float)timeElapsed; framesPassed = 0; timeElapsed = 0; } } private void ShowFPS() { frameFont.DrawText(null, “FPS: “+fps.ToString(), new Point(0, 0), Color.White); }
Now we just need to add the calls to our new functions in the Render method and we will be able to see what speed our video card is rendering frames at. Here is what the last modification looks like:
private void Render(object sender, PaintEventArgs e) { SetDeviceCamera(); CalculateFPS(); myDevice.BeginScene(); myDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.CornflowerBlue, 1.0f, 0); myScene.Render(myDevice); ShowFPS(); myDevice.EndScene(); myDevice.Present(); this.Invalidate(); }