Debugging Direct3D programs: a taxonomy of error conditions

Originally posted to Shawn Hargreaves Blog on MSDN, Tuesday, November 27, 2012

Eric Lippert wrote a fantastic article that categorizes C# exceptions into four categories:

At this point the attentive reader may be wondering, what on earth does C# exception handling have to do with D3D?  In a COM based C++ API such as D3D, errors are reported via HRESULT return values, not exceptions, right?

Not so fast...

D3D actually has three different mechanisms for reporting errors, which exactly correlate to the first three of Eric's exception categories.  There is no equivalent of vexing exceptions, because the D3D API does not use exceptions.

 

Exogenous errors In D3D

D3D reports exogenous errors by returning failure HRESULT codes.  You should always check these return values and handle any failures.

Note that some D3D methods have a return type of void instead of HRESULT.  This is because not all methods have any exogenous failure paths.  For instance the ID3D11Device::CreateBlendState method returns an HRESULT (which you should check and handle) but ID3D11DeviceContext::OMSetBlendState returns void.  This does not mean it is impossible for OMSetBlendState to fail, but that there is no point checking a return value because all the possible OMSetBlendState failures are boneheaded or fatal.  You could pass a null pointer or try to set a state object that had previously been released, but as long as you specify valid parameters OMSetBlendState will always succeed.

 

Boneheaded errors in D3D

By default, D3D does not bother to check for boneheaded errors at all.  This is because it takes some time to validate everything, and hey, this should be unnecessary as long as you don't have any bugs in your program, right? :-)

If you do make a boneheaded error calling into D3D, the result is undefined behavior.  You might get lucky and the call happens to work like you expect in spite of the mistake, or it might work on one computer but not on others, or it might crash, or it might mess things up so some other call crashes later on, or you might just get weird rendering results.  This is not good.

D3D provides an optional debug layer to help you find and fix such problems. This is enabled if you pass the D3D11_CREATE_DEVICE_DEBUG flag when creating your D3D device.  It adds detailed parameter and state validation, which slows things down but will print nice messages back to the debugger output pane if it finds any boneheaded errors.

If you used the Windows 8 or Windows Phone 8 project templates as a starting point for your D3D app, the debug layer will be turned on by default in debug builds.  The Direct3DBase::CreateDeviceResources method does this like so:

      UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

  #if defined(_DEBUG)
      // If the project is in a debug build, enable debugging via SDK Layers with this flag.
      creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
  #endif

It's always a good idea to test with the debug layer turned on, keep an eye on the output pane in Visual Studio, and fix any issues it complains about.

 

Fatal errors in D3D

Unexpected, non-recoverable D3D errors can (rarely) occur for several reasons:

In all these cases things have gone badly wrong, and not in a clean way where D3D could report back that any single method call was rejected.  We know the D3D device has ended up in a different state to what the program intended, but cannot describe exactly how things are different or what needs to happen to get everything back in sync. At this point D3D switches into a special "device removed" state.  You can still call D3D methods, but they will be ignored and nothing will actually be rendered.  When you eventually come to call Present, it will return one of the error codes:

The only way to recover from device removed state is to destroy the broken D3D device, destroy all its associated resources, create a new device, and reload a whole new set of fresh resources.  Some apps go to great pains to robustly handle this situation, but many (most?) games don't bother, and will just crash in response, or stop rendering and need to be killed by the user.

Note that D3D11 device removed state is different from the old D3D9 device lost behavior, which occurred any time the monitor switched between windowed and fullscreen mode (e.g. when pressing alt+tab).  This was a common occurrence, so all D3D9 apps had to handle device lost.  In D3D11, display mode switches are handled by the runtime without any special effort from the app.  Device removed is reserved for situations where the state of the device is not well defined and thus no automatic recovery is possible, which is much rarer.

If you frequently run into device removed errors, the first step is to check the debug D3D layer to make sure these are not just knock-on consequences of an earlier boneheaded error.

Blog index   -   Back to my homepage