Managed Direct3D:
Losing the Device in a Fullscreen App

In a DirectX application, one must handle the fact that the device used to render graphics might be lost to another application. This is true for both windowed and fullscreen applications. A Direct3D application has to account for the fact that it may not be possible to do any rendering at the moment. This is usually accomplished by putting the call to Device.Present in a try..catch statement and if a DeviceLostException gets thrown, we know that we have lost the device (Check out Craig Andera's excellent article on Device Recovery for a detailed explanation and a complete working sample).

Now, the Device class does not expose any events for you to handle when your application loses or regains control of the device, instead you have to actively probe the device to see if your application has regained control of it after it has been lost. This is done by periodically calling the Device.TestCooperativeLevel or Device.CheckCooperativeLevel methods to determine if we have control of the device yet (the difference between the two methods is that the former throws exceptions for you to catch, and the latter returns a status code).

If you call either of the two methods during the time you have lost the device, you will see two states of the device: DeviceLost meaning that some other application still have control of the device, and DeviceNotReset meaning that our application has regained control of the device but as the other application probably has messed upp stuff as render states and buffers on it we need to reset it before we can begin using it again. This is accomplished by calling the Device.Reset method.

Device.Reset raises two events: DeviceLost and DeviceReset. DeviceLost is raised just before the device is reset and DeviceReset is raised after the device has been reset. This gives the application the opportunity to dispose any resources it needs in the event handler for DeviceLost and reallocate them in DeviceReset.

Note: if the Device.Reset is called when the device is lost, the DeviceLost and DeviceReset events will still be fired, but as accessing resources such as the render state is illegal and will throw an exception these event handlers will most probably fail as that kind of resources are exactly what they usually set up for the device.

Enter: Resizing

In a windowed application, managed Direct3D automatically adjusts the parameters of the Device object when the application's window is resized. This is meant to keep the hassle of keeping the rendering from getting distorted away from the developer. This is accomplished by the private Device.OnParentResized method, which gets called by the framework whenever the parent form of the device is resized. Device.OnParentResized simply checks the new client rectangle of the form and resets the device by calling Device.Reset with the new size assigned to the back buffers.

Before the Device.OnParentResized method calls Device.Reset it raises the DeviceResizing event. This event uses the CancelEvent delegate, and thereby allowing the event handlers to cancel the event. If the event is not cancelled, Device.Reset is called.

Fullscreen Applications

When a fullscreen application loses the device, the main form of the application get minimized. When the application regains focus the main form gets maximized. This will cause the framework to call the Device.OnParentResized method, which will in turn call the Device.Reset method. Device.Reset will raise the DeviceLost and DeviceReset events that most probably will attempt to restore the devices render state. The problem is that the application has not regained control of the device just yet! Therefore the event handlers will most probably fail. This is why you need to cancel out the DeviceResizing event for a fullscreen app with the following event handler:

private void DeviceResising(object sender, CancelEventArgs e)
{
  if(device.PresentationParameters.Windowed == false)
    e.Cancel = true;
}

Which is not very intuitive.

The code snippet below shows the Device.OnParentResized method:

private void OnParentResized(object sender, EventArgs e)
{
  // This field seems to be touched only inside the Device.Reset method.
  // Therefore it is never false here.
  if( !this.canDeviceBeReset )
    return;

  // While restoring a maximized winform, the window state
  // is never minimized.
  Form parent = (Form)sender;
  if( parent.WindowState == FormWindowState.Minimized )
    return;

  CancelEventArgs ce = new CancelEventArgs();
  this.DeviceResizing(this, ce);
  if( ce.Cancel )
    return;

  this.localPresent.BackBufferWidth = sender.ClientRectangle.Width;
  this.localPresent.BackBufferHeight = sender.ClientRectangle.Height;
  this.Reset(this.localPresent);
}

If Device.OnParentResized had checked the cooperation level before calling Device.Reset, the cancellation of the DeviceResizing would not be necessary.

The Device.OnParentResized method is great when working in windowed mode; you do not need to worry about the backbuffer's dimensions, Device.OnParentResized will see to it that they always match the size of the rendering winforms' client area. In a fullscreen application, however, you do not want the extra call to Device.Reset to be called. Therefore you need to cancel out the DeviceResizing event in a fullscreen application. Is this a bug? Probably more of a feature. Whatever it is, it sure is less than intuitive! Let us see what the next update (summer 2004?) brings!