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 twoCORRUPTION
andERROR
are problems that require addressing, whileWARNING
andINFO
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:
- Create a new Windows Form Application named
Ch01_03Debugging
in ourD3DRendering.sln
solution. Set this new project as the startup project. - 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.
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:
- Implement all the steps from the first recipe, Building a Direct3D 11 application with C# and SharpDX.
- Build the project (F6) to be sure everything is working correctly.
- 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";
- Let's now run the Visual Studio 2012 graphics debugger by navigating to DEBUG | Graphics | Start diagnostics (Alt+F5).
- 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.
- If all is well, we should see some statistics in the top left of our application as shown in the following screenshot:
- 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:
- 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:
- Continuing from where we were, add the following to
Program.cs
at the start of theMain()
function:// Enable object tracking SharpDX.Configuration.EnableObjectTracking = true;
- Within the
Direct3D Initialization
region, change theCreateWithSwapChain
call to pass in the debug flag:Device.CreateWithSwapChain( SharpDX.Direct3D.DriverType.Hardware, // Enable Device debug layer DeviceCreationFlags.Debug, new SwapChainDescription() {
- 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);
- 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
- 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.
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:
- NVIDIA Nsight Visual Studio Edition at https://developer.nvidia.com/nvidia-nsight-visual-studio-edition
- AMD GPU PerfStudio at http://developer.amd.com/tools-and-sdks/graphics-development/gpu-perfstudio-2/
- Intel® GPA at http://software.intel.com/en-us/vcsource/tools/intel-gpa