Improved Direct3D Initialization

  CU_InitializeD3DImproved.zip (14.0 KiB, 3,946 hits)

In the last tutorial, we initialized DirectX Graphics the quick and dirty way. We ignored some settings and told the CPU to handle a lot of calculations instead of the GPU. This is bad since the GPU is a lot faster at doing graphical calculations. To get the most hardware acceleration, we need to query the hardware to see what it is capable of. We could then let the user choose from a list of screen resolutions, refresh rates, color formats, etc., that would all be supported by the current hardware. That’s kind of pushing it for my liking right now so we won’t do quite that much just yet 🙂 Instead, we’ll set some additional settings in the D3DPRESENT_PARAMETERS structure. We’ll also do a little hardware querying with IDirect3D9::GetDeviceCaps. This is a handy function that gives us all sorts of information about the current hardware capabilities. If you want to view your own display adapter’s capabilities, you can run the DirectX Caps Viewer utility that comes with the DirectX SDK.

Besides the extra initialization settings, we’ll also added fullscreen capabilities. Windows are exciting and all, but when I’m playing a game, it’s either fullscreen or death! In this tutorial, the fullscreen mode is hard coded in the source. In the next tutorial, we’ll add a fullscreen toggle. Enough babbling! Let’s look at some code.

#define WINDOW_TITLE “Improved DirectX Initialization”
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define WINDOWED TRUE
LPDIRECT3D9 g_pD3D9 = NULL;
LPDIRECT3DDEVICE9 g_pD3DDevice = NULL;
D3DDISPLAYMODE g_displayMode;
D3DPRESENT_PARAMETERS g_D3Dpp;
HWND g_hWnd;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
  // Clip…window creation code here

  // Create the window 
  g_hWnd = CreateWindow(WINDOW_TITLE, WINDOW_TITLE,
                        (WINDOWED) ? WS_OVERLAPPEDWINDOW 
                                   : WS_EX_TOPMOST,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        WINDOW_WIDTH, WINDOW_HEIGHT,
                        0, 0, hInstance, 0 );

  // Clip...

  // Create Direct3D Object 
  g_pD3D9 = Direct3DCreate9(D3D_SDK_VERSION);
  if (!g_pD3D9) {
    MessageBox(0, “Direct3DCreate9() – Failed”, 0, 0);
    return 0;
  }

  // Get display mode 
  g_pD3D9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &g_displayMode);

  // Check for hardware T&L 
  D3DCAPS9 D3DCaps;
  g_pD3D9->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &D3DCaps);
  DWORD vertexProcessing = 0;
  if (D3DCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
    vertexProcessing = D3DCREATE_HARDWARE_VERTEXPROCESSING;
    // Check for pure device 
    if (D3DCaps.DevCaps & D3DDEVCAPS_PUREDEVICE) {
      vertexProcessing |= D3DCREATE_PUREDEVICE;
    }
  } else {
    vertexProcessing = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
  }

  // Fill out the presentation parameters 
  if (!BuildPresentationParameters()) {
    MessageBox(0, “Unable to find a valid DepthStencil Format”, 0, 0);
    SAFE_RELEASE(g_pD3D9);
    return 0;
  }

  // Create the device 
  if (FAILED(g_pD3D9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
                                   g_hWnd, vertexProcessing, &g_D3Dpp, &g_pD3DDevice))) {
    SAFE_RELEASE(g_pD3D9);
    MessageBox( 0, “CreateDevice() – Failed”, 0, 0 );
    return 0;
  }

 // Clip...message loop here
}

The first updates appear in the CreateWindow function. The style is set depending on whether we want a fullscreen window or not. In fullscreen mode, we use the WS_EX_TOPMOST flag, which specifies that the created window should be placed above all non-topmost windows and should stay above them, even when the window is deactivated.

After we create the Direct3D object, we get the system’s current display mode by calling IDirect3D9::GetAdapterDisplayMode. The D3DDISPLAYMODE structure contains information such as the current screen resolution, refresh rate, and color format. We’ll use this information when we create a fullscreen device. After we get the display mode, we’ll see what the video card is capable of by calling IDirect3D9::GetDeviceCaps. This function fills up a D3DCAPS9 structure, which we can use to see what the capabilities are such as what shader versions are supported and whether hardware vertex processing is available. In the above code, I check if the hardware supports transformation and lighting and set the device behavior flag accordingly. Hardware vertex processing creates a big performance gain for video cards that support it. I also check if the hardware supports a pure device. MSDN has the following definition of a pure device:

A pure device does not save the current state (during state changes), which often improves performance; this device also requires hardware vertex processing. A pure device is typically used when development and debugging are completed, and you want to achieve the best performance.

One drawback of a pure device is that it does not support Get* API calls; this means you can not use a pure device to query the pipeline state. This makes it more difficult to debug while running an application.

A second drawback of a pure device is that it does not filter any redundant state changes. When using a pure device, your application should reduce the number of state changes in the render loop to a minimum; this may include filtering state changes to make sure that states do not get set more than once. This trade-off is application dependent; if you use more than a 1000 Set calls per frame, you should consider taking advantage of the redundancy filtering that is done automatically by a non-pure device.

As with all performance issues, the only way to know whether or not your application will perform better with a pure device is to compare your application’s performance with a pure vs. non-pure device. A pure device has the potential to speed up an application by reducing the CPU overhead of the API. But be careful! For some scenarios, a pure device will slow down your application (due to the additional CPU work caused by redundant state changes). If you are not sure which type of device will work best for your application, and you do not filter redundant changes in the application, use a non-pure device.

We probably don’t really need a pure device for these demos, but we’ll just go ahead and create one anyways. Once the vertex processing flags are created, we fill up the D3DPRESENT_PARAMETERS structure and then create the Direct3D device.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Purpose: 
Builds the D3DPRESENT_PARAMETERS structure using the current window size. 
Returns: TRUE on success. FALSE if a valid depth format cannot be found.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 
BOOL BuildPresentationParameters() {
  ZeroMemory(&g_D3Dpp, sizeof(g_D3Dpp)); 
  D3DFORMAT adapterFormat = (WINDOWED) ? g_displayMode.Format : D3DFMT_X8R8G8B8; 
  if (SUCCEEDED(g_pD3D9->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, adapterFormat,
                                           D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D24S8))) {
    g_D3Dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
  } else if (SUCCEEDED(g_pD3D9->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, adapterFormat,
                                                  D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D24X8))) {
    g_D3Dpp.AutoDepthStencilFormat = D3DFMT_D24X8;
  } else if (SUCCEEDED(g_pD3D9->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, adapterFormat,
                                                  D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_SURFACE, D3DFMT_D16))) {
    g_D3Dpp.AutoDepthStencilFormat = D3DFMT_D16;
  } else {
    return false;
  }

  g_D3Dpp.BackBufferWidth = (WINDOWED) ? 0 : g_displayMode.Width; 
  g_D3Dpp.BackBufferHeight = (WINDOWED) ? 0 : g_displayMode.Height; 
  g_D3Dpp.BackBufferFormat = adapterFormat; 
  g_D3Dpp.BackBufferCount = 1; 
  g_D3Dpp.MultiSampleType = D3DMULTISAMPLE_NONE; 
  g_D3Dpp.MultiSampleQuality = 0; 
  g_D3Dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; 
  g_D3Dpp.hDeviceWindow = g_hWnd; 
  g_D3Dpp.Windowed = WINDOWED; 
  g_D3Dpp.EnableAutoDepthStencil = TRUE; 
  g_D3Dpp.FullScreen_RefreshRateInHz = (WINDOWED) ? 0 : g_displayMode.RefreshRate; 
  g_D3Dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

  return TRUE;
}

Here I fill out the D3DPRESENT_PARAMETERS structure. Rather than me explaining all the members and the values I set, I’ll just send you on over to MSDN to read about them. Am I being lazy? Well…yes and no. Yes, it would be crazy amounts of typing but MSDN is there for a reason 😛 However, note that when I set the AutoDepthStencilFormat, I first see if the format is supported with IDirect3D9::CheckDeviceFormat.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Application event handler. 
Parameters: 
    [in] hWnd - Unique handle to the window. 
    [in] message - Incoming message. 
    [in] wParam - Parameter of the message (unsigned int). 
    [in] lParam - Parameter of the message (long). 
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  switch (message) {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    case WM_PAINT:
      if (g_pD3DDevice) {
        RenderFrame();
      }
      ValidateRect(hWnd, 0);
      return 0;
    case WM_KEYDOWN:
      switch (wParam) {
        case VK_ESCAPE:
          PostQuitMessage( 0 );
          break;
      }
      return 0;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

The WndProc also has a couple of updates. First, in WM_PAINT, I added a call to RenderFrame(), which contains all the Clear, BeginScene, EndScene, Present code. Second, I’ve added a keypress event, so when the user presses escape, the application will quit. We’ll need this since we can use fullscreen now and there won’t be any close box available.

Direct3D is all initialized and ready to go, so now let’s create a framework that we will be able to reuse in future projects.