Listing 5.3. Implementation of the High-Resolution Timer
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;
}
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;
}
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;
}
}
public static void Advance()
{
if (!isUsingQPF)
return; // Nothing to do
stopTime += ticksPerSecond / 10;
}
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 absoluteTime = time / (double)ticksPerSecond;
return absoluteTime;
}
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;
}
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;
}
public static bool IsStopped
{
get { return isTimerStopped; }
}
This is a relatively simple implementation. Everything you need to know about the state of the timer you have here, including starting, stopping, and getting the elapsed time or the total time. Each of these properties returns a float value based in seconds; for example, 1.0f is exactly 1 second, and 1.5f is exactly a second and a half. With that, you have a generic high-resolution timer available for your games.
Handling Lost Devices
Before you start using that timer, you need to take care of something more pressing. To see the problem in action, change your project to run in release mode. You can do so by selecting Build, Configuration Manager.
Running the application now renders the fledgling game in full-screen mode. On the surface, everything should appear the same; however, you might notice quickly that with the window border no longer around, you can't close the application. Luckily, you are using the sample framework; otherwise, if you decided to minimize the window (for example, by using the Alt+Tab key combination to switch to the next application) or close the window, the game would throw an exception as soon as it lost focus. Even though you don't have this problem because you're using the sample framework, it's important to know what causes this.
When you are running the application in full-screen mode, Direct3D takes exclusive mode over the monitor that is being used to render the full screen image.
When you switch to a different application, that exclusive mode must be released to allow the other application to run, so the current application is minimized and the device is lost. Once a device is lost, you cannot render to it any longer, and trying to do so throws an exception (specifically the DeviceLostException).
Because this is a common problem that every game has to solve, you wouldn't want to clutter your rendering method with code to handle the situation, which is why the sample framework handles it for you. Look at the code snippet in Listing 5.4 for more information.
Listing 5.4. Handling Lost Devices
int result;
// Check the cooperative level to see if it's ok to render
if (!device.CheckCooperativeLevel(out result))
{
if (result == (int)ResultCode.DeviceLost)
{
// The device has been lost but cannot be reset at this time.
// So wait until it can be reset.
System.Threading.Thread.Sleep(50);
return;
}
// Other code snippets
}
// Other code snippets
// Show the frame on the primary surface
try
{
device.Present();
}
catch (DeviceLostException)
{
// Whoops, device is lost now
State.IsDeviceLost = true;
}
catch (DriverInternalErrorException)
{
// When DriverInternalErrorException is thrown from Present(),
// the application can do one of the following:
//
// - End, with the pop-up window saying that the application cannot continue
// because of problems in the display adapter and that the user should
// contact the adapter manufacturer.
//
// - Attempt to restart by calling Device.Reset, which is essentially the
// same path as recovering from a lost device. If Device.Reset throws the
// DriverInternalErrorException, the application should end immediately
// with the message that the user should contact the adapter manufacturer.
//
// The framework attempts the path of resetting the device
//
State.IsDeviceLost = true;
}
You'll notice immediately that this method updates the variable IsDeviceLost on the State class. In this method, you check to see whether the device is currently lost, and if it is, you call the CheckCooperativeLevel method on the device. This call returns a Boolean value, true if the device is available and ready to use and false otherwise. Notice that an integer value is returned as an out parameter, which is the reason why the device is still not available.
After this call (assuming it returned false), you want to check the reason why the device is not available to render. The two likely reasons are that the device is still lost (the application still doesn't have focus) or that the application has regained focus, but the device hasn't been reset. If the device is still lost, there's nothing you can do right now, so you simply return from the method. However, if the device needs to be reset (which it always does after regaining focus), you simply call the Reset method, which should return the device back to the exclusive mode and be ready for rendering (the framework handles this for you).
Assuming you made it past that section, either the device was never lost to begin with or it is reset and ready to use now. Next, you are ready to render your scene. The framework calls the render method you have written and then makes the Present call. This call is the one that throws the exception when the device is lost, so after the render method is called, Present is called, wrapped in a try/catch block. Notice that the only exceptions caught are DeviceLostException and DriverInternalErrorException and the only action taken is to set the Boolean variable to TRue to start the check of the current cooperative level.
|