Part Overview
Chapter 4: Direct3D Initialization Overview
4.7 Debugging Direct3D Applications
In this section, we provide some basic tips for debugging Direct3D applications. The first thing you need to do is enable Direct3D debugging; this can be done by going to the Control Panel and double -clicking the DirectX icon (see Figure 4.11).
Figure 4.11: Double-click the DirectX icon from the Control Panel to launch the DirectX Properties dialog box.
A dialog box should appear (Figure 4.12). Select the Direct3D tab and set the Debug Output Level slide bar to the maximum value. Then select the Use Debug Version of Direct3D radio button. Finally, check the debug options you want: Maximum Validation, Enable Shader Debugging, etc. Press Apply and then OK. Note that using the Direct3D debugger will significantly decrease performance speed, so you will probably want to turn it on and off frequently during development.
Figure 4.12: The DirectX Properties dialog box. Select the Direct3D tab, turn up the Debug Output Level setting, select the debug version of the Direct3D Runtime, and check any additional debugging options you want.
By turning on Direct3D debugging, Direct3D should output information in the debug spew of Visual C++, as shown in Figure 4.13.
Figure 4.13: By enabling debugging, Direct3D will output error information to the debug spew window in Visual C++. The figure here shows the errors that can occur if we attempt to draw outside a BeginScene and EndScene block.
You can enable extra Direct3D debugging by defining the following symbol (note that this symbol must be defined before you include the Direct3D header files):
#if defined(DEBUG) | defined(_DEBUG)
#ifndef D3D_DEBUG_INFO
#define D3D_DEBUG_INFO
#endif
#endif
By construction, this symbol will only be defined in debug mode and not release mode, which is exactly what you want. Our demo framework defines this symbol in d3dUtil.h.
Another important thing to do for debugging is to link d3dx9d.lib (for debug builds) instead of d3dx9dib. With the "d" suffix, you link the debug version of the D3DX library. For release versions, just link d3dx9.lib.
For general-purpose (non -Direct3D) memory leak detection, we can call the following function in debug builds:
// Enable run - time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif
This function turns on memory leak checking at application exit, and will provide an error report in the debug spew of Visual C++ if there were memory leaks. We write the above code as the first thing in WinMain of every demo application.
We have seen the use of an HR macro when we use Direct3D methods; for example, HR(gd3dDevice - >Present(0, 0, 0, 0)). Each Direct3D method returns a return value of type HRESULT, which is just a return code. To test whether the function failed or succeeded, we pass the returned HRESULT to the Win32 FAILED macro, which returns true if the HRESULT is an error code and false if there is no error (usually D3D_oK) . For further details about an error code, we can use the DXTrace function, which is prototyped as follows:
HRESULT DXTrace(CHAR *strFile, DWORD dwline, HRESULT hr, CHAR *strMsg, BOOL bPopMsgBox);
The first parameter is the string of the source code file in which the function was called (you can get this with the predefined _FILE_ macro, which returns the filename in which the macro is placed); the second parameter is the source code line number on which the function was called (you can get this with the predefined _LINE_ macro, which returns the source code line number where the macro is placed); the third parameter is the failing HRESULT code; the fourth parameter is a string message to display; and the fifth parameter is true if you want an error dialog box to pop up (which asks the user to break into the debugger — see Figure 4.14), and false otherwise.
Figure 4.14: The message box displayed by the DXTrace function if a Direct3D function returns an error. It tells us the file, the line number, and a description of the error, and asks if we want to break into the debugger.
Note To use DXTrace,link dxerr9.lib and include dxerr9.h.
In our code, we do not call DXTrace explicitly. Instead, we wrap it in another macro that fills in the parameters for us:
#if defined(DEBUG) | defined(_DEBUG)
In this way, we can handle errors with very little additional code. For example:
HR(D3DXCreateFontIndirect(gd3dDevice, &fontDesc, &mFont));
Observe that we pass #x into the fourth parameter; this turns the HR macro's argument token into a string. In this way, we can output the function call that caused the error; see Figure 4.14 (in particular, the "Calling: D3DXCreateFontIndirect…," which corresponds to this fourth parameter of
DXTrace).
By construction, the HR macro only does anything in debug mode. This is fine, because by the time we are shipping an application, all the Direct3D bugs should be worked out.
HR needs to be a macro and not a function, because if it were a function, __FlLE__ and __LINE__ would refer to the file and line of the function implementation, and not the file and line where the function HR was called. So we need a macro so that the code is actually substituted into the place where we write HR.
4.8 Summary
Direct3D can be thought of as a mediator between the programmer and the graphics hardware. The programmer calls a Direct3D function, which in turn instructs the physical hardware to perform the operation by interfacing with the device's HAL (hardware abstraction layer). Direct3D is an immediate mode API, and the hardware will execute commands as immediately as possible.
The REF device allows developers to test features Direct3D exposes but that are not implemented by available hardware.
Component Object Model (COM) is the technology that allows DirectX to be language independent and have backward compatibility. Direct3D programmers don't need to know the details of COM and how it works; they need only to know how to acquire COM interfaces and how to release them.
Surfaces are special Direct3D interfaces used to store 2D images. A member of the D3DFORMAT enumerated type specifies the pixel format of a surface. Surfaces and other Direct3D resources can be stored in several different memory pools as specified by a member of the D3DPOOL enumerated type. In addition, some surfaces can be multisampled if the hardware supports it, which creates a smoother image by using antialiasing.
The IDirect3D9 interface is used to find information about the system's graphics devices. For example, through this interface we can enumerate adapter display modes, obtain the capabilities of a device, check for multisampling support, and verify whether the hardware supports rendering to certain surface formats and in different combinations. It is also used to create the IDirect3DDevice9 interface.
The IDirect3DDevice9 interface can be thought of as our software interface for controlling the graphics device. For instance, calling the IDirect3DDevice9::Clear method will command the graphics device to clear the specified surface.
The sample framework is used to provide a consistent interface that all demo applications in this book follow. The code provided in the d3dUtil.h, d3dApp.h, and d3dApp.cpp files wrap standard initialization code that every application must implement. By wrapping this code up, we hide it, which allows the samples to be more focused on demonstrating the current topic.
To debug Direct3D applications, the debug runtime must be selected in the Direct3D tab of the DirectX Properties dialog box (accessed from the Control Panel). For additional Direct3D debugging help, the application should define D3D_DEBUG_INFO before it includes the Direct3D header files.
For D3DX debugging, link the debug D3DX library, d3dx9d.lib (for debug builds), instead of d3dx9dib. For general-purpose memory leak detection, use the Win32 API function _CrtSetDbgFlag.
4.9 Exercises
1. For each adapter on your system, enumerate the display modes for the formats D3DFMT_X8R8G8B8 and D3DFMT_R5G6B5. Save your results to a text file; your output should look similar to that of Figure 4.6. Check your results by comparing it to the results given in the DirectX Caps Viewer.
2. For the primary display adapter, check to see which of the following multisample types the adapter supports in both windowed mode and full-screen mode: D3DMULTISAMPLE_2_SAMPLES,...,D3DMULTISAMPLE_16_SAMPLES. Use a HAL device, and try it with formats
D3DFMT_X8R8G8B8 and D3DFMT_R5G6B5. Save your results to a text file in a readable descriptive format. Check your results by comparing it to the results given in the DirectX Caps Viewer.
3. Look up the following methods in the SDK and write a summary, in your own words, on how to use them.
a. IDirect3D9::CheckDepthStencilMatch b. IDirect3D9::CheckDeviceFormat c. IDirect3DSurface9::GetDC
d. IDirect3DSwapChain9::GetBackBuffer e. IDirect3DDevice9::Present
4. Modify the Hello Direct3D demo program so that it does not resize the back buffer when the window is resized. Explain your results.
5. Write a program that checks if your primary adapter supports the following device capabilities:
a. D3DPRESENT_INTERVAL_IMMEDIATE b. D3DPTADDRESSCAPS_CLAMP c. D3DFVFCAPS_PSIZE
d. D3DCAPS2_CANAUTOGENMIPMAP e. D3DPRASTERCAPS_DITHER Moreover, determine the
MaxPointSize,MaxPrimitiveCount,MaxActiveLights,MaxUserClipP1anes,MaxVertexIndex,andMaxVertexShaderConst of the primary display adapter. (Hint: All this information can be found by looking at the D3DCAPS9 structure — you will need to look up "D3DCAPS9" in the SDK documentation for help on this exercise.)
6. Look up ID3DXLine in the SDK documentation and see if you can figure out how to draw some lines to the window. (Hint: Particularly concern yourself with the function D3DXCreateLine and the methods ID3DXLine::Begin,ID3DXLine::End,ID3DXLine::OnLostDevice,
ID3DXLine::OnResetDevice, and ID3DXLine::Draw . Once you have basic line drawing working, you can experiment with the other methods that allow you to change the attributes of the line, such as its width and pattern.)