Debugging your Direct3D application

Debugging the Direct3D pipeline can be a difficult task at times. There are so many elements that are impacting upon the result that pinpointing the cause of an issue can take some work and ingenuity.

This recipe will show you how to get your project ready for debugging, set up object tracking, and show you how to start the Visual Studio 2012 Graphics Debugger in managed applications.

First it is worth taking a look at a number of areas of Direct3D that require different techniques for debugging:

  • Debugging Direct3D errors: Direct3D errors such as the parameter being incorrect or invalid can be diagnosed with the help of enabling the Direct3D debug layer by passing the DeviceCreationFlags.Debug flag during device creation. When the debug layer is enabled, the Direct3D API will send additional details to the debug output window about any errors that occur. There are four classes of messages, the first two CORRUPTION and ERROR are problems that require addressing, while WARNING and INFO messages may or may not require programmer intervention.
  • Tracking resources: Direct3D resource leaks are another area that can take some tracking down, especially in complicated scenes. SharpDX allows you to enable object tracking that you can query at any time to retrieve a list of Direct3D objects that have not been released along with their creation stack trace. On application exit, a complete list of any unreleased objects is printed to the debug output. Enabling the debug layer and the object tracking takes a toll on performance and should only be enabled as needed rather than always enabled during development.
  • Debugging a pixel: Finally there is per-pixel output and shader debugging. Traditionally the DirectX SDK PIX tool has been used for recording Direct3D API calls, now with the Windows 8 SDK and Visual Studio 2012 you can use the Graphics Debugger. When active, this debugger allows you to capture frames that can then be analyzed. You can determine what has impacted an individual pixel, including support for stepping through shaders.

Getting ready

Before we can debug our Direct3D application, we need to prepare the project settings with the following steps:

  1. Create a new Windows Form Application named Ch01_03Debugging in our D3DRendering.sln solution. Set this new project as the startup project.
  2. To support the Visual Studio 2012 Graphics Debugger and to allow native debug messages from Direct3D to appear in the debugger output window, we must first enable native code debugging for the project. To do this, right-click on the project in the solution explorer and click on Properties, now click on the Debug settings and check Enable native code debugging.
    Getting ready

    Enabling native code debugging

    Note

    Mixed mode debugging for x64 is only supported starting with .NET Framework 4.

How to do it…

First we will be adding some debug information to our code and then use the Visual Studio Graphics Debugger to capture a frame. We'll then continue to enable the Direct3D 11 debug layer and SharpDX object tracking.

Starting the Visual Studio Graphics Debugger:

  1. Implement all the steps from the first recipe, Building a Direct3D 11 application with C# and SharpDX.
  2. Build the project (F6) to be sure everything is working correctly.
  3. Just before the render loop region, we will add the following:
    // Setup object debug names
    device.DebugName = "The Device";
    swapChain.DebugName = "The SwapChain";
    backBuffer.DebugName = "The Backbuffer";
    renderTargetView.DebugName = "The RenderTargetView";
  4. Let's now run the Visual Studio 2012 graphics debugger by navigating to DEBUG | Graphics | Start diagnostics (Alt+F5).

    Tip

    If the option is unavailable or does not do anything, be sure to check that you have enabled native debugging and that the selected .NET Framework version is 4.0 or later. The project must also be using a debug configuration not release.

  5. We can ignore the warning that says that there are no native symbols in the symbol file. This is because we are trying to debug a managed application. Click on Yes to continue.
  6. If all is well, we should see some statistics in the top left of our application as shown in the following screenshot:
    How to do it…

    Graphics debugger text overlay in top left

  7. Press the Prt Scr key and you should now have a frame captured in Visual Studio. Select the frame, and then click anywhere within the preview of the frame. This will now select a single pixel in Graphics Pixel History as shown in the following screenshot:
    How to do it…

    The graphics debugger windows with the pixel history and object table highlighted

  8. Stop the debugger.

Enabling the debug layer and object tracking:

Now that we are able to run the debugger, let's turn on the Direct3D debug layer and enable object tracking with the following steps:

  1. Continuing from where we were, add the following to Program.cs at the start of the Main() function:
    // Enable object tracking
    SharpDX.Configuration.EnableObjectTracking = true;
  2. Within the Direct3D Initialization region, change the CreateWithSwapChain call to pass in the debug flag:
    Device.CreateWithSwapChain(
        SharpDX.Direct3D.DriverType.Hardware,
        // Enable Device debug layer
        DeviceCreationFlags.Debug,
        new SwapChainDescription()
        {
  3. Next, we will replace the existing swapChain.Present with the following:
    // Output the current active Direct3D objects
    System.Diagnostics.Debug.Write(
      SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects());
    
    // This is a deliberate invalid call to Present
    swapChain.Present(0, PresentFlags.RestrictToOutput);
  4. Debug the project (F5) and there should be an exception thrown. The debug output should contain something like this for the ReportActiveObjects call:
    [0]: Active COM Object: [0x11BFB00] Class: [SharpDX.DXGI.SwapChain] Time [05/17/2013 16:32:33] Stack:
            c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(60,13) : Void Main()
    
    [1]: Active COM Object: [0x11A9C1C] Class: [SharpDX.Direct3D11.Device] Time [05/17/2013 16:32:33] Stack:
            c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(60,13) : Void Main()
    
    [2]: Active COM Object: [0x11ABE48] Class: [SharpDX.Direct3D11.DeviceContext] Time [05/17/2013 16:32:33] Stack:
            c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(60,13) : Void Main()
    
    [3]: Active COM Object: [0x11C0034] Class: [SharpDX.Direct3D11.Texture2D] Time [05/17/2013 16:32:33] Stack:
            c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(85,13) : Void Main()
    
    [4]: Active COM Object: [0x11E0A74] Class: [SharpDX.Direct3D11.RenderTargetView] Time [05/17/2013 16:32:33] Stack:
            c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(86,13) : Void Main()
    
    Count per Type:
    Device : 1
    DeviceContext : 1
    RenderTargetView : 1
    SwapChain : 1
    Texture2D : 1
  5. The incorrect call to Present should have resulted in the following being written to the debug output:
    DXGI ERROR: IDXGISwapChain::Present: Present is being called with DXGI_PRESENT_RESTRICT_TO_OUTPUT, which is only valid if the SwapChain was created with a valid pRestrictToOutput. [ MISCELLANEOUS ERROR #120: ]

How it works…

We begin with the code from the first recipe that was rendering a pleasant light blue background for our window.

The debug names that we have added are arbitrary values for you to use to distinguish between different objects of the same type. You may have noticed that these appeared within the Graphics Object Table when a frame has been captured (marked with a red square in the previous screenshot). This will help when you are trying to debug with lots of objects of the same type. From here, it is possible to inspect each object by double clicking, to view the contents of textures and buffers, or the properties of a device or swap chain.

Once we have captured the frame and selected a pixel, the Graphics Pixel History window (circled in red in the previous screenshot) shows the initial color, the color after the call to ClearRenderTarget and the final color of the selected pixel. If there were other operations that took place on the pixel (including shaders), this is where we would be able to delve deeper.

The second part of this recipe introduces Direct3D error debugging and object tracking.

First we enabled object tracking on the first line of Main() to demonstrate how SharpDX will keep track of objects for us.

We create the device as before, but pass through DeviceCreationFlags.Debug instead of DeviceCreationFlags.None. This enables the Direct3D debug layer for this device and we will receive additional messages for any errors after this point.

Next we generate a report of all active Direct3D COM objects. This is the same report that is generated if we were to forget to dispose of any resources before application exit. The report includes the stack trace, and double clicking on the line in the stack trace will take you to the appropriate line in the source code editor.

Finally we have introduced a deliberate error in the call to Present. The message quite clearly indicates that there is a problem with our use of the PresentFlags.RestrictToOutput flag. Either our initialization of the swap chain is incorrect or the call to Present needs changing. In this case we have not configured the swap chain with an output to be restricted to.

There's more…

The graphics debugger has a number of useful debug windows that you can access while you have the recorded graphics experiment open. These are accessible by navigating to the DEBUG/Graphics menu and include Events List (shown on the bottom left of the earlier screenshot) and Pipeline Stages in addition to the ones we have already discussed.

Note

Because the graphics debugger has been initially designed for unmanaged code, the Graphics Event Call Stack window does not resolve the managed source code line numbers correctly. This may change with a future update to Visual Studio.

The SharpDX.Diagnostics.ObjectTracker static class has a number of additional methods that are useful at runtime, such as finding an object reference by its native IntPtr or perhaps iterating the list to check the number of active DeviceContext objects.

It is also possible to debug the HLSL shader code by stepping through the logic based on the selected pixel.

See also

NVIDIA, AMD, and Intel all provide development tools specific to their hardware that can assist with debugging and can be found on the respective websites as follows: