Chapter 4: Direct3D Initialization

 

First Things First

 

Direct3D requires more than just the inclusion of a few header files and proper compile-time linkage to get started. Due to its graphical nature, applications that make use of Direct3D require a series of initialization steps before they can be expected to work properly with your PC’s video card and desktop settings. This generally entails a two step process of asking the video card what it’s capable of supporting and then picking the mode of operation which is closet to what your application needs. For example, if you’re writing an application that needs to run within a window, then it must conform to the desktop’s current screen and color resolution and your hands are basically tied. However, if your window is going to run full-screen, you can pick any screen and color resolution you want as long the video card will support it.

 

Another issue to consider during initialization is whether or not the PC’s video card supports a specific feature. Suppose that you were writing a new game and you wanted to use vertex and pixel shaders. You can’t very well run off and just assume that everyone has the latest-greatest video card with shader support. If you did that, your game would only run on the newest PCs and that would definitely take a bite out of your potential sales. What we need do is probe the hardware for support of the desired feature and if it doesn’t support it, our application must decide whether or not it can work around this loss of functionality or simply abort. Of course, aborting may be fine for a sample or a demo, but when you’re writing a commercial-grade product, this is never an acceptable option.

 

In this chapter we’ll cover two approaches to initializing Direct3D. The first approach is very basic and straightforward and will work fine for small demos and samples, which wish to run windowed. The second approach is a little more in-depth and involves probing the video card for hardware support of certain features and enumerating through all possible display modes while looking for a suitable match. This approach is best for commercial applications or special demos that want to run full-screen.

 

Chapter Samples

 

For those of you that like to follow along with the code while reading, the topic of basic initialization will be covered in the basic_initialization sample and the topic of enumerated initialization will be covered in the enumerated_initialization sample. These two new samples simply add code to the render_loop sample, which was covered in the last chapter. The new code dealing with initialization will obviously be added to the init function and code responsible for shutting down Direct3D will be added to shutdown. You should also note that each sample adds three new global variables:

 

HWND              g_hWnd       = NULL; // Handle to a window

LPDIRECT3D9       g_pD3D       = NULL; // Pointer to a IDirect3D9 interface

LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Pointer to a IDirect3DDevice9 interface

 

I don’t typically use global in commercial applications, but I do make an exception when it comes to samples. I simply feel that they make the code easier to follow than the alternative of passing pointers about.

 

Basic Direct3D Initialization

 

If you’ve ever considered learning Direct3D in the past, you’ve probably heard your fare share of horror stories about how difficult it is to initialize Direct3D. This was mainly due to its usage of COM interfaces. Thankfully though, this is no longer true as initialization has been simplified through a series of specialized functions, which were created to help hide Direct3D’s COM heritage. Actually, DirectX 9.0 does such a good job of hiding COM, I’m going to break from tradition and not even cover it. I know most other DirectX books talk it up a bit, but I don’t really see the point. The COM aspect of Direct3D is rarely seen except for a few mentions here and there involving reference counting and that’s easy enough to understand with out digging into COM.

 

The following steps represent the general approach to getting Direct3D up and running:

 

  1. Create an instance of a Direct3D9 interface object with a call to Direct3DCreate9.
  2. Use GetAdapterDisplayMode to retrieve the video adapter’s current display mode.
  3. Check hardware support issues on items such as maximum z-buffer depth by calling CheckDeviceFormat.
  4. Call GetDeviceCaps to probe the video card for hardware acceleration and adjust accordingly by creating a combination of one or more behavior flags.
  5. Create a D3DPRESENT_PARAMETERS structure and set its members to reflect our application’s rendering needs (i.e. color resolution, z-buffer depth, double-buffering).
  6. Use the CreateDevice method of your Direct3D9 object to create an instance of an IDirect3DDevice9 interface object by passing in your D3DPRESENT_PARAMETERS structure and a DWORD variable representing our behavior flags.

 

With that said, the following function uses these steps to create a windowed Direct3D application, which is 640 pixels wide and 480 pixels high, and has a 16 bit depth-buffer.

 

Listing 4-1: init function of the “basic_initialization” sample

 

//-----------------------------------------------------------------------------

// Name: init()

// Desc: Initializes Direct3D.

//-----------------------------------------------------------------------------

void init( void )

{

    g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );

 

    if( g_pD3D == NULL )

    {

        // TO DO: Respond to failure of Direct3DCreate9

        return;

    }

 

    D3DDISPLAYMODE d3ddm;

 

    if(FAILED( g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm )))

    {

        // TO DO: Respond to failure of GetAdapterDisplayMode

        return;

    }

 

    //

    // Verify hardware support...

    //

   

    HRESULT hr;

 

    if( FAILED( hr = g_pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT,

                                                D3DDEVTYPE_HAL,

                                                d3ddm.Format,

                                                D3DUSAGE_DEPTHSTENCIL,

                                                D3DRTYPE_SURFACE,

                                                D3DFMT_D16 ) ) )

    {

        if( hr == D3DERR_NOTAVAILABLE )

            // POTENTIAL PROBLEM: We need at least a 16-bit z-buffer!

            return;

    }

 

    //

    // Check for device capabilities...

    //

   

    D3DCAPS9 d3dCaps;

 

    if( FAILED( g_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT,

                D3DDEVTYPE_HAL, &d3dCaps ) ) )

    {

        // TO DO: Respond to failure of GetDeviceCaps

        return;

    }

 

    DWORD dwBehaviorFlags = 0;

 

    if( d3dCaps.VertexProcessingCaps != 0 )

        dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;

    else

        dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

 

    //

    // Everything checks out - create a regular windowed device...

    //

 

    D3DPRESENT_PARAMETERS d3dpp;

    memset(&d3dpp, 0, sizeof(d3dpp));

 

    d3dpp.BackBufferFormat       = d3ddm.Format;

    d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;

    d3dpp.Windowed               = TRUE;

    d3dpp.EnableAutoDepthStencil = TRUE;

    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

 

    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,

                dwBehaviorFlags, &d3dpp, &g_pd3dDevice ) ) )

    {

        // TO DO: Respond to failure of CreateDevice

        return;

    }

}

 

Creating the Direct3D Object

 

Our first step to initializing Direct3D is to make a call to Direct3DCreate9. This function doesn’t appear to do much at first, but if you’ve ever tried to create and query any Direct3D COM interfaces in the past, you’ll know how useful this function is. The only valid argument that can be passed to Direct3DCreate9 is D3D_SDK_VERSION, which allows Direct3D to verify that the proper header files have been included for this project. If Direct3DCreate9 succeeds, the global variable g_pD3D will become a valid pointer to a Direct3D object and your journey into Direct3D begins.

 

g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );

 

if( g_pD3D == NULL )

{

       // TO DO: Respond to failure of Direct3DCreate9

       return;

}

 

It’s important to note that since the method’s name indicates the SDK version, this function is only useful for creating and querying a 9.0 interface.  If you actually wanted a legacy interface, you would need to either find an equivalent function or manually query the COM object directly for the interface, but why would we do that? The art and science of 3D graphics is constantly moving forward… so forward we go!

 

Getting the Display Mode

 

Now that we have a valid Direct3D object, we can use one the methods contained in its interface to retrieve the current display mode. To do this, we simply declare a new variable of type D3DDISPLAYMODE and pass it into GetAdapterDisplayMode along with a request for the default display adaptor or video card.

 

typedef struct _D3DDISPLAYMODE {

    UINT            Width;

    UINT            Height;

    UINT            RefreshRate;

    D3DFORMAT       Format;

} D3DDISPLAYMODE;

 

If the call to GetAdapterDisplayMode is successful, the D3DDISPLAYMODE structured passed in will be filled with valid information concerning the display mode that was queried. The Width and Height members will give the window’s width and height in pixels. The third member, RefreshRate, will obviously give us the monitor’s refresh rate while the last member, Format, will come back set to one of the values contained in the D3DFORMAT enumerated type, which describes the surface format of the display mode.

 

D3DDISPLAYMODE d3ddm;

 

if( FAILED( g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) )

{

       // TO DO: Respond to failure of GetAdapterDisplayMode

       return;

}

 

The importance of this call will be demonstrated later when we need to set the format of the back buffer. You should also note that the samples that come with this book always pass D3DADAPTER_DEFAULT when calling GetAdapterDisplayMode. This is mainly due to the fact that most users don’t have two video cards to choose from and if they did, the best card for 3D rendering is probably going to be the default anyway. The only real exceptions to passing D3DADAPTER_DEFAULT would be if you were writing an application that needed to support multiple monitors or dual-head video cards, which this book will not be covering

 

Determining Hardware Support

 

The CheckDeviceFormat method allows you verify hardware support for certain color resolutions or surface formats by querying the validity of the format as a resource of your target adaptor. We’ll cover this method more when we cover enumerated initialization and full-screen rendering setups. The code below uses the CheckDeviceFormat method to verify whether or not a 16-bit z-buffer can be used in combination with the desktop’s current format on the current adaptor.

 

HRESULT hr;

 

if( FAILED( hr = g_pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,                                                                                                                 

                                            d3ddm.Format, D3DUSAGE_DEPTHSTENCIL,

                                            D3DRTYPE_SURFACE, D3DFMT_D16 ) ) )

{

       if( hr == D3DERR_NOTAVAILABLE )

              // POTENTIAL PROBLEM: We need at least a 16-bit z-buffer!

       return;

}

 

Once again, we pass D3DADAPTER_DEFAULT to identify the PC’s default adaptor as our target adaptor. The second argument, D3DDEVTYPE_HAL, specifies a desire to use hardware acceleration when available. I’ll talk about this more when we actually create our device object. The third argument is where we pass in the desktop’s format, which we retrieved earlier with a call to GetAdapterDisplayMode. The next two arguments are flags, which specify the usage and type of resource that we want to test for. Our call sets D3DUSAGE_DEPTHSTENCIL for the usage and D3DRTYPE_SURFACE as the resource type, which is the long-winded way of identifying a z-buffer. The last argument specifies the format we want to test for. In short, if this call returns D3D_OK it’s safe to expect support for a 16-bit z-buffer. If not, we’ll need to keep searching. Again, this will be covered more when we reach our discussion on enumerated initialization.

 

Getting the Device’s Capabilities

 

Even though we still haven’t created a Direct3D device, we can still probe the video card and verify hardware accelerated support for certain features. This is accomplished by calling GetDeviceCaps, which is one of the methods contained in the IDirect3D9 interface. The basic_initialization sample uses this call to identify the level of support for vertex processing in hardware. This piece of knowledge will be important later when we actually create our Direct3D device.

 

D3DCAPS9 d3dCaps;

 

if( FAILED( g_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT,

                                    D3DDEVTYPE_HAL, &d3dCaps ) ) )

{

       // TO DO: Respond to failure of GetDeviceCaps

       return;

}

 

DWORD dwBehaviorFlags = 0;

 

if( d3dCaps.VertexProcessingCaps != 0 )

       dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;

else

       dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

 

It’s important to keep in mind that this call asks the driver whether or not it can support certain features individually without regard to any particular mode of operation. For example, support for D3DCREATE_HARDWARE_VERTEXPROCESSING may come back as true only to be set to false because the call to CreateDevice specified software processing instead. Another example would be hardware that does support 32-bit z-buffers, but only in certain modes of a Direct3D device. To verify feature support for the actual device, you’ll need to call the GetDeviceCaps method, as defined by the IDirect3DDevice9 interface, of the device object itself.

 

The D3DPRESENT_PARAMETERS Structure

 

The PIXELFORMATDESCRIPTOR structure acts sort of like a 3D graphics “wish-list” of hardware functionality and features. This is basically how we request a specific combination of capabilities for our Direc3D application. Due to its nature, making use of this structure can range any where from simplistic to overly complicated depending on what you’re trying to set up, so you’ll want take a look at the SDK documentation for more details.

 

typedef struct _D3DPRESENT_PARAMETERS_

{

    UINT                    BackBufferWidth;

    UINT                    BackBufferHeight;

    D3DFORMAT               BackBufferFormat;

    UINT                    BackBufferCount;

    D3DMULTISAMPLE_TYPE     MultiSampleType;

    D3DSWAPEFFECT           SwapEffect;

    HWND                    hDeviceWindow;

    BOOL                    Windowed;

    BOOL                    EnableAutoDepthStencil;

    D3DFORMAT               AutoDepthStencilFormat;

    DWORD                   Flags;

    UINT                    FullScreen_RefreshRateInHz;

    UINT                    FullScreen_PresentationInterval;

} D3DPRESENT_PARAMETERS;

 

The following code shows how our sample uses the D3DPRESENT_PARAMETERS structure to request a windowed display mode, which supports double buffering and a 16-bit z-buffer.

 

D3DPRESENT_PARAMETERS d3dpp;

memset(&d3dpp, 0, sizeof(d3dpp));

 

d3dpp.BackBufferFormat       = d3ddm.Format;

d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;

d3dpp.Windowed               = TRUE;

d3dpp.EnableAutoDepthStencil = TRUE;

d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

 

The first two members that we set, BackBufferFormat and SwapEffect, define the format and handling of the back-buffer’s surface. We’ll cover back-buffers in more detail in a future chapter, but for now take note of how we’re using our D3DDISPLAYMODE structure from our last step to setup the format of our new back buffer. This is important if we want the color resolution of our back buffer to match up with our monitor’s resolution. Next, we set the Windowed member to TRUE since we intend the application to run windowed. And the last two members that we set are EnableAutoDepthStencil and AutoDepthStencilFormat, which are used to request a 16-bit depth buffer.

 

Creating the Direct3D Device

 

Once the desired mode has been decided on by filling in our D3DPRESENT_PARAMETERS structure and probing for device capabilities, we can move on to the final step of initializing Direct3D by creating the actual device interface with a call to CreateDevice.

 

HRESULT CreateDevice(

  UINT Adapter,

  D3DDEVTYPE DeviceType,

  HWND  hFocusWindow,

  DWORD BehaviorFlags,

  D3DPRESENT_PARAMETERS* pPresentationParameters,

  IDirect3DDevice9** ppReturnedDeviceInterface

);

 

The device interface basically encapsulates and stores the rendering state for your application and is what allows Direct3D to render 3D primitives on your monitor. Here’s how our sample calls CreateDevice to obtain a valid device interface:

 

if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,

                                  dwBehaviorFlags, &d3dpp, &g_pd3dDevice ) ) )

{

       // TO DO: Respond to failure of CreateDevice

       return;

}

 

While you’re already familiar with using D3DADAPTER_DEFAULT to specify the default adaptor for the system, there are several other arguments here that need some covering. The value of the second argument, D3DDEVTYPE_HAL, denotes your preference of a hardware-accelerated device over a software-emulated device. This seems like a no-brainer, but there may be cases where you want to demo or test a feature that isn’t supported by your hardware. This can be accomplished by passing D3DDEVTYPE_REF instead of D3DDEVTYPE_HAL, which will force Direct3D to emulate everything in software. Of course, the application will run considerably slower if you opt for a reference rasterizer. The third argument is again the window’s handle.

 

The fourth argument represents a bitwise collection of one or more behavior flags for controlling several of Direct3D’s global settings and this is where we’ll pass the local variable, dwBehaviorFlags, which we created and set earlier with our call to GetDeviceCaps. These behavior flags allow you control certain features such as whether or not to enable double precision floating-point unit (FPU) or if Direct3D should take extra precautions for a multi-threaded environment. You’ll need to check out the SDK documentation for all the gory details, but we’ve already identified the proper setting for the only behavior flag that must be set.

 

The last two arguments are fairly self-explanatory. The fifth argument is where we pass in our D3DPRESENT_PARAMETERS structure that specifies the mode we want, and the sixth and final argument is our pointer to a device interface. If the call to CreateDevice returns DD_OK , our pointer will then point to valid device object and we’ll be ready to render. Unfortunately, actual 3D rendering is covered in the next chapter, so we’ll just move to cleanup. I know… what a bummer, but first things first.

 

Direct3D Shutdown

 

Typically, the cleanup of an application’s resources simply involves checking all of its object pointers for NULL and deleting any objects whose memory needs to be freed. Unfortunately, our simple plans prove too simple for Direct3D since most Direct3D objects represent COM interfaces and any attempt to call delete on an interface object will throw an assertion. This is because the correct way to cleanup after COM objects is call the object’s Release method.

 

Listing 4-2: shutdown function of the “basic_initialization” sample

 

//-----------------------------------------------------------------------------

// Name: shutDown()

// Desc: Release all Direct3D resources.

//-----------------------------------------------------------------------------

void shutDown( void )

{

    if( g_pd3dDevice != NULL )

        g_pd3dDevice->Release();

 

    if( g_pD3D != NULL )

        g_pD3D->Release();

}

 

Of course, the Release method we’ll only cleanup the interface and free the memory if the object’s internal reference count becomes zero after the call to Release. The reason for maintaining an internal reference count is due to the fact that a COM interface object may be used by more than one application. When this happens, the reference count becomes either automatically incremented by the COM object itself during assignments or manually incremented by the programmer by calling the AddRef method of the interface. Fortunately for us, the samples that ship with this book are all very simple and don’t require the use of the AddRef method so we can feel secure that when we call the Release method on an object it will actually cleanup.

 

In short, if we don’t use the AddRef method, we only need to call the Release method once to cleanup up the interface object, but if we did require the usage of AddRef to share resources, we’ll need to call Release for every call to AddRef that our code makes or the object’s count will be thrown off and the object may fail to cleanup properly. This is not too critical here because this is only a sample and we’re about to kill the application anyway, but a failure to AddRef and Release your interfaces correctly in larger applications can lead to serious problems.

 

Enumerated Initialization

 

The style of initialization that we’ve covered so far is fairly straightforward and to the point. We specified a few parameters for our desired rendering setup and with little concern for failure; we made the appropriate calls to initialize everything. We did stop briefly to double check for a minimum z-buffer depth, but for the most part, we just assumed that our requests would pass. This style of initialization works fine for small applications or test samples that don’t require anything out of the ordinary, but for larger more complicated applications, this simplified approach to initialization will most likely work against us as we begin to add more and more conditions to our initialization code. To solve this problem in a more realistic manner, we’ll need to adjust our method of initialization so that it enumerates through each adaptor mode looking for the closet match possible.

 

The sample will also require a small change to our window’s style flags. We will now be using WS_POPUP | WS_SYSMENU | WS_VISIBLE as our style flag combination. In other words, we’re replacing WS_OVERLAPPEDWINDOW with WS_POPUP and adding WS_SYSMENU to the sample. The WS_POPUP flag removes the window border, which we don’t want to see in full-screen mode, and WS_SYSMENU can be very helpful if we want to kill a Direct3D application from the toolbar.

 

g_hWnd = CreateWindowEx( NULL, "MY_WINDOWS_CLASS",

                         "Direct3D (DX9) - Enumerated Initialization",

                         WS_POPUP | WS_SYSMENU | WS_VISIBLE,

                         0, 0, 640, 480, NULL, NULL, hInstance, NULL );

 

As a demonstration of enumerated initialization, the enumerated_initialization sample will probe the video card for a suitable display mode which will allow the sample application to render full-screen at 640 by 480 with a color resolution of 32-bits. Additionally, the sample will check for hardware support for a 16-bit z-buffer and a back buffer setup that works correctly with a 32-bit front buffer or display.

 

Listing 4-3: init function of the “enumerated_initialization” sample

 

//-----------------------------------------------------------------------------

// Name: init()

// Desc: Initializes Direct3D.

//-----------------------------------------------------------------------------

void init( void )

{

    g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );

 

    if( g_pD3D == NULL )

    {

        // TO DO: Respond to failure of Direct3DCreate8

        return;

    }

 

    //

    // Enumerate Adaptor or Display Modes...

    //

 

    int nMode = 0;

    D3DDISPLAYMODE d3ddm;

    bool bDesiredAdaptorModeFound = false;

 

    int nMaxAdaptorModes = g_pD3D->GetAdapterModeCount( D3DADAPTER_DEFAULT,

                                                        D3DFMT_X8R8G8B8 );

 

    for( nMode = 0; nMode < nMaxAdaptorModes; ++nMode )

    {

        if( FAILED( g_pD3D->EnumAdapterModes( D3DADAPTER_DEFAULT,

                                              D3DFMT_X8R8G8B8,

                                              nMode, &d3ddm ) ) )

        {

            // TO DO: Respond to failure of EnumAdapterModes

            return;

        }

 

        // Does this adaptor mode support a mode of 640 x 480?

        if( d3ddm.Width != 640 || d3ddm.Height != 480 )

            continue;

 

        // Does this adaptor mode support a 32-bit RGB pixel format?

        if( d3ddm.Format != D3DFMT_X8R8G8B8 )

            continue;

 

        // Does this adaptor mode support a refresh rate of 75 Hz?

        if( d3ddm.RefreshRate != 75 )

            continue;

 

        // We found a match...

        bDesiredAdaptorModeFound = true;

        break;

    }

 

    if( bDesiredAdaptorModeFound == false )

    {

        // TO DO: Handle lack of support for desired adaptor mode...

        return;

    }

 

    //

    // Verify hardware support...

    //

 

    // Can we get a 32-bit back buffer?

    if( FAILED( g_pD3D->CheckDeviceType( D3DADAPTER_DEFAULT,

                                         D3DDEVTYPE_HAL,

                                         D3DFMT_X8R8G8B8,

                                         D3DFMT_X8R8G8B8,

                                         FALSE ) ) )

    {

        // TO DO: Handle lack of support for a 32-bit back buffer...

        return;

    }

 

    // Can we get a z-buffer that's at least 16 bits?

    if( FAILED( g_pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT,

                                           D3DDEVTYPE_HAL,

                                           D3DFMT_X8R8G8B8,

                                           D3DUSAGE_DEPTHSTENCIL,

                                           D3DRTYPE_SURFACE,

                                           D3DFMT_D16 ) ) )

    {

        // TO DO: Handle lack of support for a 16-bit z-buffer...

        return;

    }

 

    //

    // Check for device capabilities...

    //

 

    D3DCAPS9 d3dCaps;

 

    if( FAILED( g_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,

                                       &d3dCaps ) ) )

    {

        // TO DO: Respond to failure of GetDeviceCaps

        return;

    }

 

    DWORD dwBehaviorFlags = 0;

 

    if( d3dCaps.VertexProcessingCaps != 0 )

        dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;

    else

        dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

 

    //

    // Everything checks out - create a full-screen device...

    //

 

    D3DPRESENT_PARAMETERS d3dpp;

    memset(&d3dpp, 0, sizeof(d3dpp));

 

    d3dpp.Windowed               = FALSE;

    d3dpp.EnableAutoDepthStencil = TRUE;

    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;

    d3dpp.BackBufferWidth        = 640;

    d3dpp.BackBufferHeight       = 480;

    d3dpp.BackBufferFormat       = D3DFMT_X8R8G8B8;

 

    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,

                                      g_hWnd, dwBehaviorFlags, &d3dpp,

                                      &g_pd3dDevice ) ) )

    {

        // TO DO: Respond to failure of CreateDevice

        return;

    }

}

 

Enumerating Adaptor Modes

 

When we perform an enumerated style initialization, we are basically probing the PC’s video card or adaptor, for a suitable display mode to work with. A typical example of a display mode (also known as an adaptor mode) would be a screen dimension of 1024 pixels by 768 pixels, with a 32-bit color resolution and refresh rate of 75 hertz, or stated another way: 1024 x 768 x 32 at 75 Hz. Technically speaking, we could also enumerate the adaptors themselves, but the typical PC only has one video card to start with, so that puts us back to enumerating just the display modes.

 

Since we only want to enumerate display modes, the enumeration process is not too overwhelming, especially when we’re going to render inside of a window, which is what we did earlier in the basic_initialization sample. This is because things are a little stricter when Direct3D is forced to work within a window, it must conform to the properties of the desktop and it can not freely pick a display mode without interfering with other applications that render via the GDI. On the other hand, if Direct3D is going to be full-screen, it can pick any display mode it wants as long the mode supports the rendering setup it needs, which obviously complicates things slightly. Either way, the new code in enumerated_initialization can be used to support both full-screen and windowed modes of operation. If running windowed, you’ll just need to ignore the actual enumeration of the display modes since the only legal one you can use when rendering to a window, is the one currently being used by the desktop.

 

Here’s the enumeration portion of the new init function:

 

int nMode = 0;

D3DDISPLAYMODE d3ddm;

bool bDesiredAdaptorModeFound = false;

 

int nMaxAdaptorModes = g_pD3D->GetAdapterModeCount( D3DADAPTER_DEFAULT,

                                                    D3DFMT_X8R8G8B8 );

 

for( nMode = 0; nMode < nMaxAdaptorModes; ++nMode )

{

    if( FAILED( g_pD3D->EnumAdapterModes( D3DADAPTER_DEFAULT,

                                          D3DFMT_X8R8G8B8,

                                          nMode, &d3ddm ) ) )

    {

        // TO DO: Respond to failure of EnumAdapterModes

        return;

    }

 

    // Does this adaptor mode support a mode of 640 x 480?

    if( d3ddm.Width != 640 || d3ddm.Height != 480 )

        continue;

 

    // Does this adaptor mode support a 32-bit RGB pixel format?

    if( d3ddm.Format != D3DFMT_X8R8G8B8 )

        continue;

 

    // Does this adaptor mode support a refresh rate of 75 Hz?

    if( d3ddm.RefreshRate != 75 )

        continue;

 

    // We found a match...

    bDesiredAdaptorModeFound = true;

    break;

}

 

if( bDesiredAdaptorModeFound == false )

{

    // TO DO: Handle lack of support for desired adaptor mode...

    return;

}

 

We kick-start our mode enumeration by calling the GetAdapterModeCount method, which will report back with the total number of adaptor modes supported. We then follow this with a for loop, which will test each mode by calling the EnumAdapterModes method on each valid index and passing in a D3DDISPLAYMODE structure to be set. If the method returns without error, we can then proceed to examine the properties of the D3DDISPLAYMODE structure breaking from the loop as soon as we find a match.

 

Determining Hardware Support

 

Once we get past the enumeration of the display modes, the only issue that remains questionable is whether or not our rendering needs can be met by our choice of display mode. For example, our video card may support a 32-bit z-buffer, but not in combination with an adaptor mode of 1280 x 1024 x 32.

 

To verify support in advance, we can use methods like CheckDeviceFormat and CheckDeviceType to probe the driver and hardware for support on a feature-by-feature basis. For example, CheckDeviceFormat can be used to verify the existence of a particular z-buffer format or to verify if textures of a certain format can be rendered given the current display mode. The code below demonstrates how CheckDeviceFormat and CheckDeviceType are used by the sample to verify hardware support for double buffering with 32-bit color and 16-bit z-buffer along with a call to GetDeviceCaps to identify the type of vertex processing we can expect from the video card.

 

// Can we get a 32-bit back buffer?

if( FAILED( g_pD3D->CheckDeviceType( D3DADAPTER_DEFAULT,

                                     D3DDEVTYPE_HAL,

                                     D3DFMT_X8R8G8B8,

                                     D3DFMT_X8R8G8B8,

                                     FALSE ) ) )

{

    // TO DO: Handle lack of support for a 32-bit back buffer...

    return;

}

 

// Can we get a z-buffer that's at least 16 bits?

if( FAILED( g_pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT,

                                       D3DDEVTYPE_HAL,

                                       D3DFMT_X8R8G8B8,

                                       D3DUSAGE_DEPTHSTENCIL,

                                       D3DRTYPE_SURFACE,

                                       D3DFMT_D16 ) ) )

{

    // TO DO: Handle lack of support for a 16-bit z-buffer...

    return;

}

 

//

// Check for device capabilities...

//

 

D3DCAPS9 d3dCaps;

 

if( FAILED( g_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,

                                   &d3dCaps ) ) )

{

    // TO DO: Respond to failure of GetDeviceCaps

    return;

}

 

DWORD dwBehaviorFlags = 0;

 

if( d3dCaps.VertexProcessingCaps != 0 )

    dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;

else

    dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

 

Most of this code above has already been covered in our discussion on basic initialization. The only new method that has been added is our call to CheckDeviceType, which will test for the support of a 32-bit back buffer when double buffering a display of 32-bits.

 

We start off calling CheckDeviceType just like our call to the CheckDeviceFormat method. We pass D3DADAPTER_DEFAULT and D3DDEVTYPE_HAL for the first and second argument to identify the target adaptor and ask for hardware acceleration when possible. The third and fourth arguments identify the desired format of the display and back buffer respectively while the fifth argument specifies whether or not the application will be windowed. If our call to CheckDeviceType succeeds, support for a 32-bit color display combined with a 32-bit back buffer is considered a valid combination on the target adaptor and it’s safe to expect support for it during device creation.

Full-Screen Device Creation

 

After we’ve identified support for both our desired display settings and hardware requirements, we can create and fill in a D3DPRESENT_PARAMETERS structure representing our full-screen setup and be nearly a 100% sure that our call to CreateDevice will succeed without a problem. I say, “nearly sure” because a few of the members of the D3DPRESENT_PARAMETERS structure can be quite contrary, so there’s still some room for failure if our application requires anything out of the ordinary.

 

D3DPRESENT_PARAMETERS d3dpp;

memset(&d3dpp, 0, sizeof(d3dpp));

 

d3dpp.Windowed               = FALSE;

d3dpp.EnableAutoDepthStencil = TRUE;

d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;

d3dpp.BackBufferWidth        = 640;

d3dpp.BackBufferHeight       = 480;

d3dpp.BackBufferFormat       = D3DFMT_X8R8G8B8;

 

if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,

                                  g_hWnd, dwBehaviorFlags, &d3dpp,

                                  &g_pd3dDevice ) ) )

{

    // TO DO: Respond to failure of CreateDevice

    return;

}

 

The setting of the D3DPRESENT_PARAMETERS structure above doesn’t seem too different than the code used in our basic initialization discussion, but there are some important differences to note. The first difference is fairly obvious; we’re no longer requesting a device that renders to a window. This is because we want to go full-screen with this sample. The second and most important difference is our setting of the back buffer’s actual width, height and depth. As you remember from our first discussion on initializing Direct3D, the back buffer setup for working in a window only requires that you set the back buffer’s format so it matches the desktop’s format, but when you work in full-screen mode, you can set it to what ever you want as long it’s supported by the hardware. Now, all that’s left to do is create the Direct3D device with a call to CreateDevice and we’re done.

 

This concludes our chapter on Direct3D initialization and I must apologize if the subject matter was less than exciting, but initialization is just as important as any other part of Direct3D. The code covered here will basically become the boiler-plate code or foundation layer for future samples, so it’s important that you become comfortable with at least the basics.

 

Of course, with initialization out of the way, we can finally move on to the fun stuff – 3D rendering!

 

© 2003 Kevin R. Harris All rights reserved.

Legal Disclaimer and Copyright Notice