Chapter 2. Your First DirectX Program

The best way to begin learning DirectX is to start at the beginning by creating simple demo applications. In this chapter we’ll take you step-by-step through the process of creating your very first DirectX application, specifically focusing on Direct3D. By the end of this chapter you will have a firm understanding of the setup of Direct3D from start to finish.

In this chapter:

  • How to create a project

  • How to set up a Windows application

  • How to initialize DirectX

  • How to clear the screen

  • How to display a scene

Creating the Project

This book assumes you’ve already had experience creating C++ projects and working within Visual Studio. In this section and the one to follow we’ll briefly cover the initial setup of a Win32 application. We’ll modify this code throughout the chapter to include Direct3D initialization and basic rendering. By the end of the chapter we’ll have a set of code used to create and initialize Direct3D that we can use for all demos throughout this book.

The first step to any application is the creation of the Visual Studio project. Start by running Visual Studio .NET with no project loaded. Throughout this book we are using Visual Studio C++ 2010 Express, which can be freely downloaded from Microsoft’s website (www.microsoft.com/express/downloads/).

Note

You should already have Visual Studio .NET and the DirectX SDK installed. Read the Introduction if you have not performed this step.

We’ll start by creating a new project called Blank Win32 Window by performing the following steps:

  1. Within Visual Studio, select New > Project from the File menu to bring up the New Project dialog box, shown in Figure 2.1.

    Visual Studio’s New Project dialog box.

    Figure 2.1. Visual Studio’s New Project dialog box.

  2. Enter “BlankWindow” as the project name and select Empty Project from the list of project templates. Click on the OK button when this is complete. This dialog box is shown in Figure 2.2.

    Creating an empty project.

    Figure 2.2. Creating an empty project.

  3. Click the Finish button.

The empty project is now created. In the next section we’ll add code to the project that will serve as a template for all the demos throughout this book.

Adding Windows Code

At this point, Visual Studio will have created an empty project. The next step is to create the source code to initialize the main application window. You start off by adding a blank source file to the project. This file will become our main source file that we’ll name main.cpp. The steps to create the main.cpp file are:

  1. Right-click the Source folder in the Solution Explorer section of Visual Studio and select Add New Item (see Figure 2.3).

    Creating a new item within the project.

    Figure 2.3. Creating a new item within the project.

  2. In the New Item dialog box select the C++ source file and name it main.cpp (see Figure 2.4).

    Creating the main.cpp source file.

    Figure 2.4. Creating the main.cpp source file.

  3. Click OK to finish.

With the main.cpp source file created within the project, we can now fill it with the Win32-specific code to create a blank window. Once we have our main entry point coded we’ll initialize Direct3D 11 and use Direct3D to render the window’s canvas.

The Main Entry Point

The first task of the main.cpp source file is to include the necessary Win32 header files and to define the main entry point. As you should be aware, the main entry point for Win32 applications is the WinMain function. As of now we’ll only need to include the windows.h header file to the top of the source file. Both the empty WinMain function and the header section of the main.cpp source file can be seen in Listing 2.1.

Example 2.1. The empty WinMain function (Blank Win32 Window Step 1).

#include<Windows.h>


int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE prevInstance,
                   LPWSTR cmdLine, int cmdShow )
{
    return 0;
}

In Listing 2.1 you can see that we are using wWinMain instead of WinMain. The difference between the two is that wWinMain is used to handle Unicode parameters, specifically the third parameter cmdLine, while WinMain performs the conversion for you between Unicode and ANSI. Since this could lead to missing characters in a Unicode string, using wWinMain allows us to properly handle Unicode arguments if they are passed to the application.

The (w)WinMain function takes four parameters. Those parameters are defined as follows:

  • HINSTANCE hInstance. The handle of the application’s current instance.

  • HINSTANCE prevInstance. The handle of the previous instance of the application. This will always be NULL according to the MSDN documentation. Since this will always be NULL, if you need a way to determine whether a previous instance of the application is already running, the documentation recommends creating a uniquely named mutex using CreateMutex. Although the mutex will be created, the CreateMutex function will return ERROR_ALREADY_EXISTS.

  • LPSTR cmdLine(or LPWSTR in Unicode). The command line for the application without the program’s name. This allows you to pass commands to the application, such as from the command prompt, by use of a shortcut with the command string provided, etc.

  • int cmdShow. An ID that specifies how the window should be shown.

The current instance handle and command show parameters are the only ones we’ll use throughout this book. The instance handle is needed by Direct3D’s initialization, as well as window creation, and the command show ID is used after the window’s creation when it is time to show the window, which we’ll see throughout this chapter.

Windows Initialization

Although the application will run, it will not display anything since there is no actual window created. So the next step is to create the Win32 window. This is done by first registering the Window’s class and then creating the window itself. This can be seen in Listing 2.2. Applications must register its windows with the system.

Example 2.2. Window class registration and creation (Blank Win32 Window Step 2).

int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE prevInstance,
                     LPWSTR cmdLine, int cmdShow )
{
    UNREFERENCED_PARAMETER( prevInstance );
    UNREFERENCED_PARAMETER( cmdLine );

    WNDCLASSEX wndClass = { 0 };
    wndClass.cbSize = sizeof( WNDCLASSEX ) ;
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WndProc;
    wndClass.hInstance = hInstance;
    wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
    wndClass.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = "DX11BookWindowClass";

    if( !RegisterClassEx( &wndClass ) )
        return -1;

    RECT rc = { 0, 0, 640, 480 };
    AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );

    HWND hwnd = CreateWindowA( "DX11BookWindowClass", "Blank Win32 Window",
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.
left,
        rc.bottom - rc.top, NULL, NULL, hInstance, NULL );

    if( !hwnd )
        return -1;

    ShowWindow( hwnd, cmdShow );

    return 0;
}

The Win32 macro UNREFERENCED_PARAMETER can be used to avoid compiler warnings about parameters that are unused by a function’s body. Although it technically is not necessary, it is good programming practice to strive for 0 warnings when building source code. Since this specific macro does not do anything, the compiler within Visual Studio will optimize it out.

Following the handling of the unused parameters is the description of the window class. The window class, defined by WNDCLASSEX, contains various properties of the Win32 window, which include the window’s icon, its menu, the application instance the window belongs to, the look of the cursor, and other properties we’ll briefly review. The WNDCLASSEX can be found in Winuser.h, which is included within windows.h and has the following definition:

typedef struct tagWNDCLASSEX {
  UINT       cbSize;
  UINT       style;
  WNDPROC    lpfnWndProc;
  int        cbClsExtra;
  int        cbWndExtra;
  HINSTANCE  hInstance;
  HICON      hIcon;
  HCURSOR    hCursor;
  HBRUSH     hbrBackground;
  LPCTSTR    lpszMenuName;
  LPCTSTR    lpszClassName;
  HICON      hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;

The members of the WNDCLASSEX structure are defined as:

  • cbSize. The size of the structure in bytes.

  • style. Style flags used to define the window’s look.

  • lpfnWndProc. A callback function that is called whenever an event notification comes from the operating system. There will be a function in our demos called WndProc that will be discussed later in this chapter, which is why we are setting this property. This property is a function pointer.

  • cbClsExtra. Number of extra bytes to allocate for the window structure.

  • cbWndExtra. Number of extra bytes to allocate for the window’s instance.

  • hInstance. The application instance that contains the windows procedure (callback) for this window class.

  • hIcon. The resource ID for the icon graphic to be displayed for the application. If NULL, the default icon is used (e.g., Microsoft Word has an icon of a W on top of a document graphic).

  • hCursor. The resource ID for the graphic that will act as the cursor. We’ll usually stick with the standard arrow cursor throughout this book.

  • hbrBackground. A handle to the background brush that will be used for painting the window’s background.

  • lpszMenuName. A null-terminated string of the resource name for the menu.

  • lpszClassName. A null-terminated string for what you wish to name your window class. The maximum length is 256 characters.

  • hIconSm. Handle to the window’s small icon (i.e., like those seen on running applications on the task bar).

Most members of the window structure deal with Win32 programming that is beyond the scope of this book, such as creating menus (outside of an editor, we’ll most likely not want to create a Win32 menu in a game). Many of these members we’ll set to a value of 0.

With the WNDCLASSEX structure created we can send it to RegisterClassEx() to register the window class. The RegisterClassEx() must be called before we attempt to create the window, and it takes as a parameter the address of the window class structure to register. If a value of 0 is returned by this function, then the registration failed, and you’ll likely need to check the values specified for the window class to ensure they are all valid, assuming there is not a bigger problem.

The next step is to create the actual window. First we call AdjustWindowRect to calculate the size required of the window based on our desired dimensions and style. The type of window will determine how much true size we’ll need. If you look at most Win32 applications, there is space for non-client areas such as the title bar, a border around the application, etc. If we are going for a specific window size, we’ll have to keep in mind that we have both a client area and a non-client area.

The AdjustWindowRect function first takes a rectangle (lpRect) that defines the bottom left, bottom right, upper left, and upper right areas of the window. The left and top properties represent the starting location of the window, while the right and bottom represent the width and height. The AdjustWindowRect function also takes as parameters the window style flag of the window being created and a Boolean flag indicating whether or not the window has a menu, which affects the non-client area.

The window is created next by calling the Win32 function CreateWindow. In Listing 2.2 we call a variation of it called CreateWindowA. The main difference is that CreateWindowA accepts ANSI string parameters, while CreateWindow accepts Unicode. If using the Unicode version, we can always prefix the letter L before the quotes of the string to indicate we are providing Unicode characters.

The Win32 function CreateWindow(A) takes as parameters:

  • lpClassName (optional)—The window class name (same name used for the window class structure).

  • lpWindowName (optional)—The window title bar text.

  • dwStyle—The window’s style flags.

  • X—The window’s horizontal position.

  • Y—The window’s vertical position.

  • nWidth—The window’s width.

  • hHeight—The window’s height.

  • hWndParent(optional)—Handle to the parent window’s handle (optional if this new window is a pop-up or child window).

  • hMenu(optional)—Resource handle to the window’s menu.

  • hInstance(optional)—The application instance ID (first parameter of wWinMain).

  • lpParam(optional)—Data to be passed to the window and made available via the lpParam parameter of the windows proc callback function (discussed in the section titled “Windows Callback Procedures”).

The return value of the CreateWindow(A) function is a non-null handle. If CreateWindow(A) succeeds, we can show the window by calling the Win32 function ShowWindow, which takes as parameters the window handle returned by CreateWindow(A) and the command show flag (cmdShow, which is the wWinMain’s last parameter).

With the window created, the application can begin doing its job. Win32 GUI applications are event-based applications. This essentially means that when an event happens, the application is notified of it, and some action then occurs. This continues until the event for quitting the application is encountered. For example, when Microsoft Word launches, a “create” event is fired and the application loads. Whenever the user clicks the mouse on a toolbar button, menu item, etc., an event is triggered and sent to the application for processing. If the mouse-click event over the open file button is triggered, then a dialog box is displayed that allows users to visually select the file they wish to open. Many applications work using an event-based approach.

In video games, the applications are real time, meaning that whether or not some event or action takes place will not keep the application from performing many tasks throughout its lifetime. If the user presses buttons on the game-pad controller, usually it is detected during an update step of the game loop, and the game responds to it appropriately. If there are no events, the game still must process constantly by rendering the current game state (e.g., rendering the menu, cinematic, game world, etc.), performing logic updates, looking for and responding to networking data, playing audio, and so much more.

Either way, both real-time and event-based programs must run until the user decides to quit. This introduces the concept of the application loop. The application loop is literally an infinite loop that continues until it is somehow broken by a user’s action. This can be done by receiving a WM_QUIT (Win32 quit message) event, the user pressing the Escape key at a point in the main menu where it makes sense to close the application (e.g., hitting Escape while the game is being played might bring up a pause screen but shouldn’t just quit the application, unless you design it that way), or any number of other ways you design it to quit. Later on in this book, the demos will quit only when the user presses the Escape key, clicks the close “X” button on the window, or press the Back button on an Xbox 360 game-pad controller.

Listing 2.3 shows an example of the application loop we’ll use throughout this book, minus anything related to Direct3D or the demos themselves. The comments within Listing 2.3 are placeholders for where we’ll place function calls for our demo and Direct3D-related code later on. If we’ve implemented a state management system, which is useful for game menus, in-game interfaces, and more, we could have the demo’s one-time initialize and shutdown in the loop itself. Since the state management would never allow the initialization and shutdown states to occur more than once per run, this would work fine. There are a number of game menu tutorials at www.UltimateGameProgramming.com for those of you interested in more advanced topics beyond the scope of this book.

Example 2.3. The application loop.

int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE prevInstance,
                     LPWSTR cmdLine, int cmdShow )
{
    UNREFERENCED_PARAMETER( prevInstance );
    UNREFERENCED_PARAMETER( cmdLine );

    . . .

    // Demo Initialize

    MSG msg = { 0 };

    while( msg.message != WM_QUIT )
    {
        if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            // Update
            // Draw
        }
    }

    // Demo Shutdown

    return static_cast<int>( msg.wParam );
}

As a quick reference, the static_cast<> operator is a C++ operator used to perform casts. The old C-style casts, for example int a = ( int )floatVar, are not recommended in C++ even though it is legal to do. It is good programming practice to use C++ casting keywords over C-style casts. The static_cast<> operator is best used when casting numerical data, like we are doing in Listing 2.3 by casting the wParam to an integer. But beware when trying to cast pointers of base classes to derived class and vice-versa, as these are not always going to be safe. In such a case it would be better to use dynamic_cast<>. Also, static_cast<> does not do run-time checking like dynamic_cast<>. The dynamic_cast<> is safer but only works on pointers. Although C-style casts are legal, here are some general thoughts to keep in mind:

  • C++ style casts are clear in their intent (dynamic_casts for pointers, const_cast for handling const-ness, etc).

  • If you try to perform illegal casts with C++ style casts, the compiler will give an error.

  • It’s easier to spot casts within code when using C++ style casts versus C-style (although the syntax highlighting in Visual Studio can sometimes help when spotting them).

  • C-style casts on objects with multiple inheritance or casting object addresses to char* and using pointer arithmetic on them can cause undefined behavior.

  • C-style casts are easy to misuse.

In Listing 2.3, MSG is a Win32 structure used to hold window messages, some of which come from the operating system, and it is up to the application to respond to these messages. If this does not happen after a certain amount of time has passed, the operating system will report the application as not responding. Usually we assume this means the application has frozen or has had some error occur, but generally it means the application has not talked back to the operating system in a while. If the application is designed to respond to the OS, after a while it can be safe to assume the program froze whenever Windows displays the “Not Responding” message next to the window’s title on the title bar or next to the application’s name on the Task Manager. There have been times when I’ve run really complex and long queries over a network in Microsoft Access and had the operating system report the program as not responding, although it was just busy doing some task and not processing events.

With window messages we need to do two things. First we need to get new messages and process them, and second we need to dispatch (respond) to these messages. The PeekMessage Win32 function retrieves a message for the associated window (the window we’ve created using CreateWindow). The first parameter is the structure that will hold the message (its address), the window handle (optional), min and max message filter flags (optional), and the remove flag. Specifying PM_REMOVE as the remove flag like we’ve done removes it from the queue. Since we are processing this message, it will not need to stay on the queue once we’ve handled it.

If there is a message obtained by PeekMessage, we can respond to that message by calling TranslateMessage and DispatchMessage. The Win32 function TranslateMessage translates the messages from virtual-key messages to character messages, and the DispatchMessage function dispatches the message to the Windows procedure callback function, which will be covered in the next section. The Windows procedure function will actually perform actions based on the message it receives.

If there are no messages, the only thing to do is to perform game updates and rendering. In the demos throughout this book this boils down to performing any demo-specific updates, which can be detecting and responding to user input, physics calculations, animations, updating audio buffers, etc., and to render the scene geometry.

This is a simplified look at the game loop, which is a series of game-specific steps taken for each rendered frame. One frame in a game is a single iteration of the game loop. Most games strive to reach 30 or 60 frames per second, or in other words 30 to 60 game loop iterations for each second of real-world time. As games become more complex, this is harder to achieve. The frames per second of a game is usually viewed as how fast the game can render, but games are more than just rendering code, and often the game’s physics, collisions, artificial intelligence, audio, streaming, and so forth affect the game’s overall frames per second.

Within the application loop we can perform one iteration of the game loop. In Listing 2.3 we have comment placeholders for where we will add the update and render functions in a later demo. Since we are not at the Direct3D section yet, this will come later.

The last line of code in the wWinMain function of the Blank Win32 Window demo returns 0. Generally you are returning an exit code, which would only matter if you launched one application from another application, and the results of that application’s exit status mattered. Later on, once we add Direct3D, we’ll return the exit code message, although for our purposes the value we return doesn’t matter as much. Even in C++ console applications it is common for coders to just return 0 all the time.

The entire Blank Win32 Window wWinMain function with all the information we’ve discussed can be seen in Listing 2.4.

Example 2.4. The Blank Win32 Window demo’s wWinMain in its entirety.

#include<Windows.h>

LRESULT CALLBACK WndProc( HWND hwnd, UINT message,
    WPARAM wParam, LPARAM lParam );


int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE prevInstance,
                     LPWSTR cmdLine, int cmdShow )
{
    UNREFERENCED_PARAMETER( prevInstance );
    UNREFERENCED_PARAMETER( cmdLine );

    WNDCLASSEX wndClass = { 0 };
    wndClass.cbSize = sizeof( WNDCLASSEX ) ;
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WndProc;
    wndClass.hInstance = hInstance;
    wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
    wndClass.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = "DX11BookWindowClass";

    if( !RegisterClassEx( &wndClass ) )
        return -1;

    RECT rc = { 0, 0, 640, 480 };
    AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
    HWND hwnd = CreateWindowA( "DX11BookWindowClass", "Blank Win32 Window",
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.
left,
        rc.bottom - rc.top, NULL, NULL, hInstance, NULL );

    if( !hwnd )
        return -1;

    ShowWindow( hwnd, cmdShow );

    // Demo Initialize

    MSG msg = { 0 };

    while( msg.message != WM_QUIT )
    {
        if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        // Update
        // Draw
    }

    // Demo Shutdown

    return static_cast<int>( msg.wParam );
}

Windows Callback Procedure

The last piece of the puzzle before we can compile and build our application is to provide the windows procedure, often called windows proc function. Throughout the previous code listing (Listing 2.4) there was a forward declaration for this function, and in the wWinMain function it was passed to the WNDCLASSEX structure to one of its function, pointer members. The windows proc function is a callback function, meaning that it is called whenever messages are being obtained and processed by our application. The Blank Win32 Window demo’s window proc function can be seen in Listing 2.5.

Example 2.5. The Blank Win32 Window demo’s window proc function.

LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT paintStruct;
    HDC hDC;

    switch( message )
    {
        case WM_PAINT:
            hDC = BeginPaint( hwnd, &paintStruct );
            EndPaint( hwnd, &paintStruct );
            break;

        case WM_DESTROY:
            PostQuitMessage( 0 );
            break;

        default:
            return DefWindowProc( hwnd, message, wParam, lParam );
    }

    return 0;
}

The windows proc function has a return type of LRESULT and uses the CALLBACK attribute. The function itself can be named whatever you like, but throughout this book it happens to be named WndProc. The callback function takes as parameters the handle of the window dispatching this message, the message as an unsigned integer, and two parameters specifying additional information (wParam and lparam). The last two parameters are used to supply data to the callback function for messages that require more data to perform some type of action. This topic goes deeper into Win32 programming than what we will need throughout this book, and we will not make use of those last two parameters.

In the Blank Win32 Window demo we respond to the paint message, and we respond to the quit message. The paint message is handled by calling Win32 functions to draw the window’s background, which is handled by calling BeginPaint and EndPaint. Since Direct3D will be doing all of our rendering, this will be all we’ll have to do for this message throughout this book. We won’t have Direct3D-specific code with this message. but it is still a required message to respond to.

The quit message is handled by calling the Win32 PostQuitMessage function, which will cause the MSG object in our application loop to retrieve a WM_QUIT message, which causes the application loop to end and the application to quit as we’ve intended. Since there are situations where we don’t want to just flat-out quit, we can use this to only post a quit message if we really want to quit. For example, in Microsoft Word if you try to quit without saving, a dialog pop-up appears asking you if you wish to save before exiting, exit without saving, or cancel exiting altogether.

The final part of the windows proc function is the calling of DefWindowProc, which takes the same parameters as the windows proc function. This function is only called on messages for which we are not writing custom response code. In this demo we are responding to paint requests from the operating system and quit messages but there is a ton of other events we could also look for, such as key presses, mouse clicks, mouse movements, timers, and much more. The DefWindowProc function is a Win32 function that processes every message sent to it with default processing behavior and is used to ensure that we at least respond to every message, even if we don’t actually care about them.

At this point you can compile and build the demo. There should be 0 warnings and 0 errors and, if so, you can run the application to be greeted to an empty white window. To close this application you can click on the close “X” button on the upper right corner of the window, which will cause the quit message to be processed, the application loop to break, and the program to exit successfully. A screenshot from the Blank Win32 Window demo can be seen in Figure 2.5.

The Blank Win32 Window demo running.

Figure 2.5. The Blank Win32 Window demo running.

The Blank Win32 Window demo code and project can be found on the book’s companion website in the Chapter 2/BlankWindow folder.

Time for Direct3D

We are now ready to add Direct3D 11 to the code we’ve written in the Blank Win32 Window demo to create a new demo called Blank Direct3D Window.

The Blank Direct3D Window demo can be found on the companion website in the Chapter2/BlankD3DWindow folder.

In this section we’ll first discuss all of the Direct3D functions and objects necessary to get started with Direct3D 11. In the next section we’ll cover the Blank Direct3D Window demo in detail. Discussing everything needed to start Direct3D first outside of the specific chapter demo will make implementing the Blank Direct3D Window demo as a chapter tutorial much easier for beginners.

If you wish to follow along with the creation of the Blank Direct3D Window demo, create a new project titled BlankD3DWindow and copy the main.cpp source file from the Blank Win32 Window demo over to this one. This new demo will build off of that code.

Adding the DirectX Libraries

Before we begin we must include the DirectX 11 libraries that we’ll be using. To link the DirectX libraries, click on the Project menu item and select [project name] Properties from the drop-down list, where [project name] is, in this case, BlankD3DWindow. The project properties dialog box will appear and looks similar to what is seen in Figure 2.6.

The Linker (left) and the VC++ Directories (right).

Figure 2.6. The Linker (left) and the VC++ Directories (right).

On the left side of the properties dialog you’ll see a list of property categories. Click on the Configuration Properties tab and then click on the arrow next to the Linker tab to reveal the Linker properties. From the Configuration drop-down list on the upper left of the window, switch from Debug to All Configurations so that we can apply the same data to both debug and release builds.

On the Linker tab, click Input to display the input properties of the project. From there, in the Additional Dependencies text box add to the linked libraries the files d3d11.lib, d3dx11.lib, and dxerr.lib. Keep in mind that when adding each library, end the library’s name with a semi-colon (;).

If the DirectX SDK installation did not add the DirectX directory paths, you can do so yourself by going to the VC++ Directories option under the Configuration Properties tab and add to the Include Directories text box “$(DXSDK_DIR) Include;” and add to the Library Directories text box “$(DXSDK_DIR)Libx86;”. In Figure 2.6 you can see where the Configuration Properties are, where the Linker tab is located, where the VC++ Directories tab is located, and which text boxes to fill in.

Before continuing to build the project, make sure no errors are reported with finding the libraries. If there are errors, make sure you’ve installed the DirectX SDK and make sure you don’t have any typos in the library name or the path directories that you’ve just entered.

Initializing Direct3D

To set up Direct3D we need to complete the following four steps:

  1. Define the device types and feature levels we want to check for.

  2. Create the Direct3D device, rendering context, and swap chain.

  3. Create the render target.

  4. Set the viewport.

In this section we’ll discuss each of these steps one at a time and cover in detail the various Direct3D objects and functions used.

Driver Types and Features Levels

In Direct3D 11 we can have a hardware device, a WARP device, a software driver device, or a reference device.

A hardware device is a Direct3D device that runs on the graphics hardware and is the fastest of all devices. This is the type of device you should be able to create if you have graphics hardware that supports the feature level we are aiming for, which will be discussed momentarily.

A reference device is used for users without hardware support by performing the rendering on the CPU. In other words, the reference device completely emulates hardware rendering on the CPU within software. This process is very slow, inefficient, and should only be used during development if there is no other alternative. This is useful when new versions of DirectX are released, but there is no hardware on the market that can run it with a hardware device.

A software driver device allows developers to write their own software rendering driver and use it with Direct3D. This is called a pluggable software driver. Usually this option is not recommended for high-performance, demanding applications where hardware, or maybe even WARP, would be the better option.

The WARP device is an efficient CPU-rendering device that emulates the full feature set of Direct3D. WARP uses the Windows Graphic runtime found in Windows Vista and Windows 7 and uses highly optimized instructions and code, which makes it a better choice than reference mode. This device is said to be on par with lower-end machines, which can be useful if we wish to see how a game performs with limited performance. This is a good real-time alternative if hardware support is not available, whereas the reference device is usually too slow for real-time applications. WARP is still not as fast as hardware since it is still emulation.

Note

There are also unknown and null device types where a null device is a reference device without rendering capabilities.

The feature levels of Direct3D allow us to specify which feature set we want to target, which eliminates the need for device caps (capabilities) from DirectX 9.0 and below. Throughout this book there are three devices we will target, with the first being a Direct3D 11.0 device, the second being a Direct3D 10.1 device, and the third being a Direct3D 10.0 device. If you do not have DirectX 11–class hardware, then we can code our demos to use Direct3D 10.1 or 10.0 as a fallback if those devices exist in hardware without any additional code. If no hardware option exists for the feature levels we want, we’ll have to try to find support for one of the feature levels in a WARP or reference mode.

Listing 2.6 shows the driver type and feature levels declaration we’ll use later on. By creating an array of each type, we can use that to loop through and try to create the most desired device first before continuing to the other types if the process failed. On a side note, the Win32 macro ARRAYSIZE can be used to return the size of an array, and the GetClientRect Win32 function allows us to calculate the client area of the application, which we’ll use for setting the Direct3D device’s rendering width and height later on in this section. Remember that Win32 applications have client areas as well as non-client areas, but we can only render to the client area.

Example 2.6. Specifying the driver type and feature levels.

RECT dimensions;
GetClientRect( hwnd, &dimensions );

unsigned int width = dimensions.right - dimensions.left;
unsigned int height = dimensions.bottom - dimensions.top;
D3D_DRIVER_TYPE driverTypes[] =
{
    D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_SOFTWARE
};

unsigned int totalDriverTypes = ARRAYSIZE( driverTypes );

D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0
};

unsigned int totalFeatureLevels = ARRAYSIZE( featureLevels );

Device and Swap Chain Creation

The next step is the creation of the swap chain. A swap chain in Direct3D is a collection of rendering destinations for a device. Each device has at least one swap chain, but multiple swap chains can be created for multiple devices. A rendering destination can be a color buffer that is rendered to and displayed to the screen, a depth buffer (discussed in Chapter 3, “The 2D Resurgence”), a stencil buffer (also discussed in Chapter 3), and so forth.

Usually in games we have two color buffers that are being rendered onto called the primary buffer and the secondary buffer, as well as being known as the front and back buffers. The primary buffer (front buffer) is the one that is displayed to the screen, while the secondary buffer (back buffer) is being drawn to for the next frame.

Rendering can occur so fast that parts of the screen can be drawn on top of the previous results before the monitor has finished updating the display. When this happens it causes rendering artifacts and undesirable issues. Switching between the buffers so that one is being written onto and the other is being displayed allows games to avoid these artifacts. This ping-pong technique is called double buffering (also called page flipping) in computer graphics. A swap chain can have one, two, or more of these buffers, and Direct3D handles the buffer page flipping between them all.

Listing 2.7 shows the creation of a swap chain description. A swap chain description is used to define how we want the swap chain created. It has a member for:

  • The buffer count (primary/secondary buffer for page flipping).

  • The width and height of the buffers.

  • The buffer’s format (describe in the section titled “Formats” later in this chapter).

  • The refresh rate, which is used to determine how often the display is refreshed in Hertz (by using 60/1 we are specifying a 60 Hertz refresh rate).

  • The window handle (same window created by CreateWindow(A)).

  • A Boolean flag called Windowed that is used to specify whether Direct3D should stay windowed or resize into full-screen mode.

  • The sample count and quality for the sample description.

Example 2.7. The swap chain description.

DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory( &swapChainDesc, sizeof( swapChainDesc ) );
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hwnd;
swapChainDesc.Windowed = true;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;

The sample description defines the multisampling properties of Direct3D. Multisampling is a technique used to sample and average rendered pixels to create smoother transitions between sharp color changes. The artifacts we are attempting to reduce with multisampling are called jagged edges, also known as the staircase effect. Figure 2.7 shows an example of this effect on the left, as well as the desired smoothing we are looking to achieve with multiple sampling on the right. If these artifacts are bad, it can be unsightly to look at in a game.

Jagged edges versus a smoother display.

Figure 2.7. Jagged edges versus a smoother display.

The buffer usage and description of the swap chain has the most members to set, which are all pretty straightforward. The buffer usage for our swap chain is set to DXGI_USAGE_RENDER_TARGET_OUTPUT so that the swap chain is able to be used for output, or in other words it can be rendered to.

The next step is to create the rendering context, device, and swap chain now that we have the swap chain description. The Direct3D device is the device itself and communicates with the hardware. The Direct3D context is a rendering context that tells the device how to draw. It also includes rendering states and other drawing information. The swap chain as we’ve already discussed is the rendering destinations that the device and context will draw to. The creation of the device, rendering context, and swap chain can be seen in Listing 2.8. The Direct3D device is of type ID3D11Device, the rendering context is of type ID3D11Context, and the swap chain is of type IDXGISwapChain. The code clip from Listing 2.8 is an example setup from the upcoming Blank Direct3D Window demo code we’ll look at later in this chapter.

Example 2.8. Create the Direct3D device, context, and swap chain.

ID3D11Device device_;
ID3D11Context d3dContext_;
IDXGISwapChain swapChain_;
unsigned int creationFlags = 0;

#ifdef _DEBUG
    creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

HRESULT result;
unsigned int driver = 0;

for( driver = 0; driver < totalDriverTypes; ++driver )
{
    result = D3D11CreateDeviceAndSwapChain( 0, driverTypes[driver], 0,
       creationFlags, featureLevels, totalFeatureLevels,
       D3D11_SDK_VERSION, &swapChainDesc, &swapChain_,
       &d3dDevice_, &featureLevel_, &d3dContext_ );

    if( SUCCEEDED( result ) )
    {
        driverType_ = driverTypes[driver];
        break;
    }
}

if( FAILED( result ) )
{
    DXTRACE_MSG( "Failed to create the Direct3D device!" );
    return false;
}

The swap chain, device, and rendering context can be created with a single Direct3D function call or by object-specific Direct3D calls (e.g., CreateSwapChain to create just a swap chain). This function is called D3D11CreateDeviceAndSwap-Chain. In Listing 2.8 we loop through each driver type and attempt to create a hardware device, a WARP device, or a reference device. If all fail, we can’t initialize Direct3D. The D3D11CreateDeviceAndSwapChain function also takes our feature levels, so if at least one of those feature levels exists, and if our device type exists, then this function will succeed.

The D3D11CreateDeviceAndSwapChain takes as parameters the following values:

  • A pointer to the video adapter to use to create the device. Passing null causes Direct3D to use the default device. This can be useful if there is more than one device installed in the machine.

  • The driver type we wish to create (i.e., hardware, WARP, software, or reference).

  • A handle to a DLL that implements a software rendering device. If our driver type is software (D3D_DRIVER_TYPE_SOFTWARE), then this parameter CANNOT be null.

  • The creation flags. In Direct3D a creation flag of 0 can be used in release builds of our games, while a creation flag of D3D11_CREATE_DEVICE_DEBUG allows us to create a device with debugging capabilities, which is useful for development.

  • The feature levels we wish to target, ordered from the most desired to the least desired. In this book we are concerned with Direct3D 11 or Direct3D 10, but we could also target Direct3D 9 through D3D_FEATURE_ LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, or D3D_FEATURE_LEVEL_9_1.

  • Number of feature levels in the feature levels array.

  • The SDK version, which will always be D3D11_SDK_VERSION since we are using the DirectX 11 SDK.

  • The swap chain description object.

  • The address for the device object (with the device being of type ID3D11-Device).

  • The address of the feature level that was selected. Earlier we supplied a list of features we want but only one is chosen, which is stored in this address.

  • The address for the rendering context (with the context being of the type ID3D11Context).

Render Target View Creation

A render target view is a Direct3D resource written to by the output merger stage. In order for the output merger to render to the swap chain’s back buffer (secondary buffer), we create a render target view of it.

In Chapter 3 we’ll discuss textures in detail, but for now understand that a texture is often an image. The primary and secondary rendering buffers of the swap chain are color textures, and to obtain a pointer to it we call the swap chain function GetBuffer. GetBuffer takes as parameters:

  • The buffer index (a value of 0 gives us the first buffer).

  • The type of interface we are trying to manipulate. The type of a 2D texture is ID3D11Texture2D.

  • The address of the buffer we are obtaining. We must cast to LPVOID.

With a pointer to the buffer, we call CreateRenderTargetView, which is a function of the Direct3D device object, to create the render target view. Render target views have the type of ID3D11RenderTargetView, and the CreateRender TargetView function takes as parameters the 2D texture we are creating the view for, the render target description, and the address of the ID3D11RenderTarget View object we are creating. Setting the render target description parameter to null gives us a view of the entire surface at mip level 0. Mip-map levels will be discussed in more detail in Chapter 3.

Once we are done creating the render target view, we can release our pointer to the back buffer of the swap chain. Since we obtained a reference via a COM object, we must call the COM’s Release function to decrement the reference count. This must be done to avoid memory leaks, because we don’t want the system holding onto it once the application quits.

Each time we want to render to a specific render target, we must set it first before any drawing calls. This is done by calling OMSetRenderTarget, which is a function that is part of the output merger (hence the OM in OMSetRenderTarget). The OMSetRenderTarget function takes as parameters the number of views we are binding in this function call, the list of render target views, and the depth/stencil views. We’ll discuss depth and stencils in Chapter 3. Listing 2.9 shows the creation and binding of the render target view.

Example 2.9. The creation and binding of the render target view.

ID3D11RenderTargetView* backBufferTarget_;
ID3D11Texture2D* backBufferTexture;
HRESULT result = swapChain_->GetBuffer( 0, __uuidof( ID3D11Texture2D ),
    ( LPVOID* )&backBufferTexture );

if( FAILED( result ) )
{
    DXTRACE_MSG( "Failed to get the swap chain back buffer!" );
    return false;
}

result = d3dDevice_->CreateRenderTargetView( backBufferTexture, 0,
    &backBufferTarget_ );

if( backBufferTexture )
    backBufferTexture->Release( );

if( FAILED( result ) )
{
    DXTRACE_MSG( "Failed to create the render target view!" );
    return false;
}

d3dContext_->OMSetRenderTargets( 1, &backBufferTarget_, 0 );

You’ll notice in Listing 2.9 that we are using a macro called DXTRACE_MSG. This macro is used for debugging purposes, and we’ll discuss it in more detail at the end of this chapter, along with the other DirectX Error Handling Library.

The Viewport

The last piece of the Direct3D 11 puzzle is the creation and setting of the viewport. The viewport defines the area of the screen we are rendering to. In single player or non-split-screen multiplayer games this is often the entire screen, and so we set the viewport’s width and height to the Direct3D swap chain’s width and height. For split-screen games we can create two viewports, with one defining the upper portion of the screen and one for the lower portion. In order to render split-screen views we would have to render the scene once for player 1’s perspective, once for player 2’s perspective, etc. Although multiplayer games are beyond the scope of this book, an example of creating a split-screen demo can be found at www.UltimateGameProgramming.com.

Viewports are created by filling out a D3D11_VIEWPORT object and setting it to the rendering context by calling the context’s RSSetViewports function. RSSetView ports takes the number of viewports we are setting with this function call and the list of viewport objects. The creation and setting of our full-screen viewport can be seen in Listing 2.10, where the X and Y positions mark the left and top screen positions, and min and max depths are values between 0 and 1 that mark the min and max depths of the viewport.

Example 2.10. The creation and setting of a full-screen viewport.

D3D11_VIEWPORT viewport;
viewport.Width = static_cast<float>(width);
viewport.Height = static_cast<float>(height);
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;

d3dContext_->RSSetViewports( 1, &viewport );

Clearing and Displaying the Screen

Rendering to the screen takes place in a few different steps. The first step is usually to clear any necessary render target surfaces. In most games this includes buffers such as the depth buffer, which we’ll discuss in Chapter 3. In the Blank Direct3D Window demo we’ll implement later in this chapter, we clear the color buffer of the render target view to a specified color. This is done by calling the Direct3D content’s ClearRenderTargetView function. ClearRenderTargetView has the following function prototype:

void ClearRenderTargetView( ID3D11RenderTargetView* pRenderTargetView,
    const FLOAT ColorRGBA[4] );

Note

It is not necessary to clear the color buffer before rendering in most commercial games because the sky and environment geometry ensures every pixel will be overridden in the color buffer anyway, making the clearing step unnecessary.

The ClearRenderTargetView function takes as parameters the render target view we are clearing and a value to clear it to. To clear the screen, we are specifying the color as the color we want the background shaded to. This color is a red, green, blue, and alpha array with color values specified in the 0.0 to 1.0 range. In this case 0.0 is nonintensity, and 1.0 is full intensity. In byte terms, 1.0 would be equivalent to a value of 255. If the red, green, and blue components of the color are 1.0, then we have a pure white color. Colors are discussed more in Chapter 3.

The next step is to draw the scene’s geometry. In this chapter we won’t be drawing any geometry, but in Chapter 3 we’ll cover this in more detail.

The last step is to display the rendered buffer to the screen by calling the swap chain’s Present function. Present has the following function prototype:

HRESULT IDXGISwapChain::Present( UINT SyncInterval, UINT Flags );

The parameters of the Present function are the sync interval and the presentation flags. The sync interval can be 0 for immediate display or 1, 2, 3, or 4 to present after the nth vertical blank. A vertical blank is the time difference between the last line of the frame update for the current frame and the first line update for the next frame. Devices such as computer monitors update the pixels of the display vertically line-by-line.

The Present function flags can be 0 to output each buffer, DXGI_PRESENT_ TEST to not output anything for testing purposes, or DXGI_PRESENT_DO_ NOT_SEQUENCE for presenting the output without sequencing to exploit the vertical blank synchronization. For our purposes we can just pass 0 and 0 to the Present function to display our rendered results.

Listing 2.11 shows an example of clearing the screen and presenting it to the viewer. In Chapter 3 we’ll dive more into rendering buffers for color, depth, double buffering for smooth animations, and much more. In this chapter the focus is learning the setup of Direct3D 11. Listing 2.11 would display a dark blue background color if this were executed in a program.

Example 2.11. Clearing the rendering target and displaying the new rendered scene.

float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f };
d3dContext_->ClearRenderTargetView( backBufferTarget_, clearColor );

swapChain_->Present( 0, 0 );

Cleaning Up

The final thing to do in any Direct3D application is to clean up and release the objects that you’ve created. For instance, at the beginning of a program, you would have created a Direct3D device, a Direct3D rendering context, a swap chain, and a render target view at the least. When the application closes, you need to release these objects so the resources are returned to the system for reuse.

COM objects keep a reference count that tells the system when it’s safe to remove objects from memory. By using the Release function, you decrement the reference count for an object. When the reference count reaches 0, the system reclaims these resources.

An example of releasing our Direct3D objects can be seen in Listing 2.12. The if-statements in Listing 2.12 first check to make sure that the objects is not null and then calls their Release functions. It’s a good idea to release the objects in the reverse order in which they were created.

Example 2.12. Releasing the main Direct3D 11 objects.

if( backBufferTarget_ ) backBufferTarget_->Release( );
if( swapChain_ ) swapChain_->Release( );
if( d3dContext_ ) d3dContext_->Release( );
if( d3dDevice_ ) d3dDevice_->Release( );

Tip

Always check to make sure that DirectX objects are not null before calling Release on them. Attempting to release an invalid pointer can cause your game to crash due to undefined behavior.

Formats

Occasionally you’ll be required to specify a DXGI format. Formats can be used to describe the layout of an image, the number of bits used for each color, or the layout of vertices for a vertex buffer (Chapter 3). Most commonly, DXGI formats are used to describe the layout of the buffers in the swap chain. DXGI formats are not specific to any type of data, only the format in which it comes.

An example DXGI format, DXGI_FORMAT_R8G8B8A8_UNORM says that the data coming in will use 8 bits for each of the RGBA components. When defining vertices, formats like DXGI_FORMAT_R32G32B32_FLOAT are used where 32 bits are available for the three components. Even though a format may specify RGB, it’s only a description of how the data is laid out, not what the data is used for.

Occasionally you’ll see formats that specify the same number of bits for each component but have a different extension to them. For instance, both DXGI_ FORMAT_R32G32B32A32_FLOAT and DXGI_FORMAT_R32G32B32A32_UINT reserve the same number of bits for each component but also specify the type of data contained in those bits. These are considered fully typed formats.

Formats that don’t declare a type are called typeless formats. They reserve the same number of bits for each component but don’t care what type of data is contained, such as DXGI_FORMAT_R32G32B32A32_TYPELESS. The list of common formats can be seen in Table 2.1.

Table 2.1. Common Direct3D Formats

Format

Description

DXGI_FORMAT_R32G32B32A32_TYPELESS

128-bit format consisting of four typeless RGBA components.

DXGI_FORMAT_R32G32B32A32_FLOAT

128-bit format consisting of four float RGBA components.

DXGI_FORMAT_R32G32B32A32_UINT

128-bit format consisting of four unsigned integer RGBA components.

DXGI_FORMAT_R32G32B32A32_SINT

128-bit format consisting of four signed integer RGBA components.

DXGI_FORMAT_R8G8B8A8_TYPELESS

32-bit format consisting of four typeless RGBA components.

DXGI_FORMAT_R8G8B8A8_UINT

32-bit format consisting of four unsigned integer RGBA components.

DXGI_FORMAT_R8G8B8A8_SINT

32-bit format consisting of four signed integer RGBA components.

The Blank D3D Window

The companion website includes a demo in the Chapter 2 folder called Blank D3D Window (the folder is Chapter2/BlankD3DWindow/). In this section we will create this demo step-by-step using the knowledge gained about Direct3D 11 up to this point. What we will accomplish here we’ll be able to use as the base for all demos throughout the book.

Design of the Template Framework

The setup, shutdown, and initialization of non-demo–specific Direct3D objects are fairly straightforward, and rewriting essentially the same thing each time we want to write a new demo can prove unnecessary. Sometimes we want to get straight to the demo itself without having to write the standard Win32 and Direct3D startup code. Although it is not a big deal, it is at times an inconvenience without resorting to copy-and-paste methods.

To resolve this we will write all the Direct3D startup and cleanup code in one source file and reuse that code throughout the book. To facilitate this we will create a class called DX11DemoBase. To create our specific demos, we’ll derive from this base class and override virtual functions necessary for us to create our demos.

In this section we’ll cover the Blank Direct3D Window demo in its entirety and use the code created here as the basis for all demos to follow.

The Direct3D Class

But why use a class? Why not use global variables and functions to act on them like many other books, tutorials, and resources? There are many answers to this, and most of them stem from good versus bad programming practices.

Global variables are looked down upon in programming for many reasons. In general it is just bad programming to use them, and they should be avoided. Alternatively, we could also pass objects as parameters, but there are too many objects that would need to be passed to nearly every function in our application. Of course we could create a class or structure to store them and pass that object around, but in the end it makes sense to have a demo class that demo-specific classes derive from; it makes little sense to return to the C way of doing things.

In our setup, a demo is its own object. It can load the content it needs, render what needs to be rendered, and update what needs to be updated. It has a solitary purpose and exists to perform a single job. The question now is what will we need from our base class? For starters, we’ll need functions to do the following:

  • Initialize Direct3D

  • Release Direct3D objects created during startup

  • Store member variables for our Direct3D objects that are not demo-specific

  • Provide a way to load demo-specific content

  • Provide a way to unload demo-specific content

  • Provide the ability to perform demo-specific updates once per frame

  • Provide the demo-specific rendering code

Judging by the list of needs that we’ve just identified, it makes sense to create a base class with public initialize and shutdown functions, virtual functions for the loading and unloading of content, and virtual functions for the rendering and updating steps of the game loop. By making these demo-specific functions virtual, the demo classes that derive from this base class can implement their custom logic and behaviors. The DX11DemoBase class definition can be seen in Listing 2.13.

Example 2.13. The Demo Bases definition.

#ifndef _DEMO_BASE_H_
#define _DEMO_BASE_H_

#include<d3d11.h>
#include<d3dx11.h>
#include<DxErr.h>


class Dx11DemoBase
{
    public:
        Dx11DemoBase();
        virtual ~Dx11DemoBase();

        bool Initialize( HINSTANCE hInstance, HWND hwnd );
        void Shutdown( );

        virtual bool LoadContent( );
        virtual void UnloadContent( );

        virtual void Update( float dt ) = 0;
        virtual void Render( ) = 0;
    protected:
        HINSTANCE hInstance_;
        HWND hwnd_;

        D3D_DRIVER_TYPE driverType_;
        D3D_FEATURE_LEVEL featureLevel_;

        ID3D11Device* d3dDevice_;
        ID3D11DeviceContext* d3dContext_;
        IDXGISwapChain* swapChain_;
        ID3D11RenderTargetView* backBufferTarget_;
};

#endif

In Listing 2.13 we can see that all of the minimum Direct3D objects we’ll need are included as protected class members. If a demo needs objects specific to that demo, such as additional rendering targets, then it is up to that demo-specific class to declare those as members of the derived class. The base class is just for objects that are common across all demos.

Taking a look at the member functions of the demo base class, we see that we need only a few methods to give us the ability to create a wide range of demos. The job of the constructor is to set the member objects to default values like null. To do this we’ll use the constructor’s member initialize list, which is a method of initializing member variables outside the body of the constructor. Doing this is often good programming practice and can be more efficient than letting a default constructor call occur before a copy constructor on objects. In the case of our Direct3D objects, they have no constructors, so this is done largely for consistency. But in general when working with initializing objects, it is a good idea to avoid unnecessary constructor calls. Although built-in types can be initialized during the member initialize list or through assignment within the constructor, the result will be the same either way, since they are built-in types and not objects.

The class’s destructor is tasked with calling the Shutdown function to ensure that all Direct3D objects are properly released when the demo is either deleted or falls out of scope.

The Shutdown function calls Release on all of our COM-based Direct3D objects. In the Shutdown function it is important to release our demo-specific content; therefore Shutdown first calls the UnloadContent function before releasing its own member objects.

The UnloadContent function releases any demo-specific data and is overridden by the derived class. Since not all demos will necessarily have content, such as this Blank Window demo, this function does not need to be purely virtual. The same goes with the LoadContent function that loads all demo-specific content such as geometry, texture images, shaders, audio files, and so forth.

Note

XNA Game Studio uses a slightly similar idea where each game derives from the Game base class and overrides specific functions to implement game-specific behavior. If you’re not already aware of it, XNA Game Studio is a game development framework built on top of DirectX to create Windows-PC and Xbox 360 games.

The Dx11DemoBase class’s constructor, destructor, LoadContent, UnloadContent, and Shutdown functions can be seen in Listing 2.14.

Example 2.14. Some Dx11DemoBase functions.

#include"Dx11DemoBase.h"


Dx11DemoBase::Dx11DemoBase( ) : driverType_( D3D_DRIVER_TYPE_NULL ),
    featureLevel_( D3D_FEATURE_LEVEL_11_0 ), d3dDevice_( 0 ), d3dContext_( 0 ),
    swapChain_( 0 ), backBufferTarget_( 0 )
{

}


Dx11DemoBase::~Dx11DemoBase( )
{
    Shutdown( );
}


bool Dx11DemoBase::LoadContent( )
{
    // Override with demo specifics, if any. . .
    return true;
}


void Dx11DemoBase::UnloadContent( )
{
    // Override with demo specifics, if any. . .
}


void Dx11DemoBase::Shutdown( )
{
    UnloadContent( );

    if( backBufferTarget_ ) backBufferTarget_->Release( );
    if( swapChain_ ) swapChain_->Release( );
    if( d3dContext_ ) d3dContext_->Release( );
    if( d3dDevice_ ) d3dDevice_->Release( );
    d3dDevice_ = 0;
    d3dContext_ = 0;
    swapChain_ = 0;
    backBufferTarget_ = 0;
}

The last function in the Dx11DemoBase class is the Initialize function. The Initialize function performs the Direct3D initialization as we’ve described throughout this chapter. The function begins by declaring our driver types for hardware, WARP, or software, and our feature levels for Direct3D 11.0, 10.1, or 10.0. The setup of this code is to attempt to create a hardware device in Direct3D 11. If that fails, we’ll try other driver types and feature levels until we find one that is supported. This also means that if you’re using Direct3D 10 hardware you can still run this demo in hardware, since the 10.1 or 10.0 feature level will be chosen.

The next step is to create the swap chain description and use that information to attempt to find a supported device type and feature level. If that passes, we create the render target view out of the swap chain’s back buffer, create the viewport, and call LoadContent to load any demo-specific game content. Since our Direct3D initialization must occur before demo-specific initialization, LoadContent must be called last. By including it here we don’t have to call it manually outside of the class. The same can be said for having to manually call UnloadContent before the application quits since Shutdown calls UnloadContent itself.

The full Direct3D 11 initialization can be seen in Listing 2.15 for the Blank Direct3D Window demo. Most of the code deals with error checking and setting property values.

Example 2.15. The Dx11DemoBase Initialize function.

bool Dx11DemoBase::Initialize( HINSTANCE hInstance, HWND hwnd )
{
    hInstance_ = hInstance;
    hwnd_ = hwnd;

    RECT dimensions;
    GetClientRect( hwnd, &dimensions );

    unsigned int width = dimensions.right - dimensions.left;
    unsigned int height = dimensions.bottom - dimensions.top;

    D3D_DRIVER_TYPE driverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_-
SOFTWARE
    };

    unsigned int totalDriverTypes = ARRAYSIZE( driverTypes );

    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0
    };

    unsigned int totalFeatureLevels = ARRAYSIZE( featureLevels );

    DXGI_SWAP_CHAIN_DESC swapChainDesc;
    ZeroMemory( &swapChainDesc, sizeof( swapChainDesc ) );
    swapChainDesc.BufferCount = 1;
    swapChainDesc.BufferDesc.Width = width;
    swapChainDesc.BufferDesc.Height = height;
    swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
    swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.OutputWindow = hwnd;
    swapChainDesc.Windowed = true;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;

    unsigned int creationFlags = 0;

#ifdef _DEBUG
    creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    HRESULT result;
    unsigned int driver = 0;

    for( driver = 0; driver < totalDriverTypes; ++driver )
    {
        result = D3D11CreateDeviceAndSwapChain( 0, driverTypes[driver], 0,
           creationFlags, featureLevels, totalFeatureLevels,
           D3D11_SDK_VERSION, &swapChainDesc, &swapChain_,
           &d3dDevice_, &featureLevel_, &d3dContext_ );

        if( SUCCEEDED( result ) )
        {
            driverType_ = driverTypes[driver];
            break;
        }
    }

    if( FAILED( result ) )
    {
        DXTRACE_MSG( "Failed to create the Direct3D device!" );
        return false;
    }
    ID3D11Texture2D* backBufferTexture;
    result = swapChain_->GetBuffer( 0, __uuidof( ID3D11Texture2D ),
       ( LPVOID* )&backBufferTexture );

    if( FAILED( result ) )
    {
        DXTRACE_MSG( "Failed to get the swap chain back buffer!" );
        return false;
    }

    result = d3dDevice_->CreateRenderTargetView( backBufferTexture, 0,
       &backBufferTarget_ );

    if( backBufferTexture )
        backBufferTexture->Release( );

    if( FAILED( result ) )
    {
        DXTRACE_MSG( "Failed to create the render target view!" );
        return false;
    }

    d3dContext_->OMSetRenderTargets( 1, &backBufferTarget_, 0 );

    D3D11_VIEWPORT viewport;
    viewport.Width = static_cast<float>(width);
    viewport.Height = static_cast<float>(height);
    viewport.MinDepth = 0.0f;
    viewport.MaxDepth = 1.0f;
    viewport.TopLeftX = 0.0f;
    viewport.TopLeftY = 0.0f;

    d3dContext_->RSSetViewports( 1, &viewport );

    return LoadContent( );
}

The Blank Window Demo Class

Currently we have everything needed to run the Blank Direct3D Window demo. To do this we’ll derive a class from the Dx11DemoBase class and call it BlankDemo. The BlankDemo class definition can be seen in Listing 2.16.

Example 2.16. The BlankDemo class header.

#ifndef _BLANK_DEMO_H_
#define _BLANK_DEMO_H_

#include"Dx11DemoBase.h"


class BlankDemo : public Dx11DemoBase
{
    public:
        BlankDemo( );
        virtual ~BlankDemo( );

        bool LoadContent( );
        void UnloadContent( );

        void Update( float dt );
        void Render( );
};

#endif

The Update function takes a single parameter called dt. We’ll look at this value more closely in Chapter 3, but for now understand that in games we often need to update logic based on real time. The dt parameter will represent the time delta that has passed since the last frame, and we’ll use it for time-based updates. For now we’ll ignore it until we discuss animation.

Since the demo does not do anything special but clears the screen using Direct3D, all of the overridden functions are empty, with the exception of the Render function. In the Render function we only call two Direct3D functions. We first call ClearRenderTargetView to clear the screen to a specified color, and then we call Present to display that newly rendered scene (or lack thereof, since the render was just a uniform color). It is all pretty basic and made even easier to understand since all of the Direct3D functions used throughout this demo have already been discussed in great detail earlier in the chapter. The BlankDemo implemented member functions can be seen in Listing 2.17, in case you’re following along by writing code as you read.

Example 2.17. The implementation of the BlankDemo class.

#include"BlankDemo.h"


BlankDemo::BlankDemo( )
{

}


BlankDemo::~BlankDemo( )
{

}


bool BlankDemo::LoadContent( )
{
    return true;
}


void BlankDemo::UnloadContent( )
{

}


void BlankDemo::Update( float dt )
{

}


void BlankDemo::Render( )
{
    if( d3dContext_ == 0 )
        return;

    float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f };
    d3dContext_->ClearRenderTargetView( backBufferTarget_, clearColor );

    swapChain_->Present( 0, 0 );
}

Updating the Application Loop

Currently we have everything needed to bring the Blank Direct3D Window demo to life. The last and final step is updating the wWinMain function we created in the first demo of this chapter (the Blank Win32 Window demo) to use our demo class. Remember when we only had comments set as placeholders where we would add our Direct3D code? In Listing 2.18 you get to see where those comments were removed and replaced with their corresponding function calls.

Example 2.18. The main source file of the Blank Direct3D Window demo.

#include<Windows.h>
#include<memory>
#include"BlankDemo.h"


LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam
);


int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE prevInstance,
                   LPWSTR cmdLine, int cmdShow )
{
    UNREFERENCED_PARAMETER( prevInstance );
    UNREFERENCED_PARAMETER( cmdLine );

    WNDCLASSEX wndClass = { 0 };
    wndClass.cbSize = sizeof( WNDCLASSEX ) ;
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WndProc;
    wndClass.hInstance = hInstance;
    wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
    wndClass.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = "DX11BookWindowClass";
    if( !RegisterClassEx( &wndClass ) )
        return -1;

    RECT rc = { 0, 0, 640, 480 };
    AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );

    HWND hwnd = CreateWindowA( "DX11BookWindowClass", "Blank Direct3D 11
Window",
       WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left,
       rc.bottom - rc.top, NULL, NULL, hInstance, NULL );

    if( !hwnd )
        return -1;

    ShowWindow( hwnd, cmdShow );

    std::auto_ptr<Dx11DemoBase> demo( new BlankDemo( ) );

    // Demo Initialize
    bool result = demo->Initialize( hInstance, hwnd );

    // Error reporting if there is an issue
    if( result == false )
        return -1;

    MSG msg = { 0 };

    while( msg.message != WM_QUIT )
    {
        if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            // Update and Draw
            demo->Update( 0.0f );
            demo->Render( );
        }
    }

    // Demo Shutdown
    demo->Shutdown( );

    return static_cast<int>( msg.wParam );
}


LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT paintStruct;
    HDC hDC;

    switch( message )
    {
        case WM_PAINT:
            hDC = BeginPaint( hwnd, &paintStruct );
            EndPaint( hwnd, &paintStruct );
            break;

        case WM_DESTROY:
            PostQuitMessage( 0 );
            break;

        default:
            return DefWindowProc( hwnd, message, wParam, lParam );
    }

    return 0;
}

We’ve added seven lines of code to the wWinMain function. First we’ve created the demo instance by using the C++ standard smart pointer auto_ptr<>. The auto_ptr<> smart pointer is one that automatically deletes the memory it is pointing to when the scope it is in ends or when it is copied to another auto_ptr<>. The scope can be function level, within an if-statement, inside a loop, and within a pair of curly braces arbitrarily placed to create a new scope.

The benefit to this is that we don’t need to manually delete the allocated data. The true benefit that goes beyond that (since it would not be hard or even unlikely to just call delete on the demo object) is that using auto_ptr<> is exception safe. This means that even if an exception is triggered and your application halts, the auto_ptr<> will still release its data during stack unwinding. This means no leaks, even when the application crashes. If we manually had to delete this pointer and if execution did halt, we’d be left with leaked memory. There are tons of benefits to using memory objects such as auto_ptr<>. If you are unfamiliar with them it is important to read up on smart pointers and other standards offered by the C++ programming language that are beyond the scope of this book. If you have not brushed up on the recent standard of C++ (C++0x), it is recommended you do so.

The last thing to note in the wWinMain function is that we are returning the wParam member of the MSG object in order to return the application’s exit code. Since wWinMain returns an integer, we cast this object to an integer by using C++’s static_cast<> operator.

A screenshot of the Blank Direct3D Window can be seen in Figure 2.8. The demo is using a dark color to clear the screen, which should be reflected in the screenshot when compared to the earlier Blank Win32 Window demo.

The Blank D3D Window demo screenshot.

Figure 2.8. The Blank D3D Window demo screenshot.

The completion of the Blank Direct3D Window demo marks the completion of the template files we’ll use throughout this book to create future demos. To create the future demos, we just need to derive new classes from Dx11DemoBase and implement the demo-specific logic we need by overriding LoadContent, UnloadContent, Update, and Render.

DirectX Error Handling Library

The DirectX SDK comes with a series of utility functions and macros that aid in the debugging of DirectX applications. To gain access to these utilities, projects must link to Dxerr.lib and include the Dxerr.h header file. In this section we will discuss the various error handling functions and macros that exist.

Error Handling Functions

There are three functions in the DirectX Error Handling library:

  • TCHAR* DXGetErrorDescription( HRESULT hr )

  • TCHAR* DXGetErrorString( HRESULT hr )

  • HRESULT DXTrace( CHAR* strFile, DWORD dwline, HRESULT hr, CHAR* strMsg, BOOL bPopMsgBox )

The function DXGetErrorDescription takes any HRESULT return value and returns a string storing the description of that error.

The function DXGetErrorString also takes any HRESULT error value but returns a string holding the error code. For example, "D3DERR_DEVICELOST" will be returned for a value matching that error code, while DXGetErrorDescription returns the description of what that actually means.

The last function, DXTrace, will display a message with the error string from DXGetErrorString. This function takes as parameters the current file (you can use the Win32 __FILE__ macros for this parameter), the line number (you can use the Win32 macro __LINE__), the HRESULT holding the error value, a pointer to an optional string message that will be displayed along with the file and line, and a Boolean specifying if the trace should display a pop-up message box.

Error Handling Macros

There are three main error handling macros to be aware of, and to fully utilize them you must be running the application in the debugger. These error handling macros include the following, which are related to the DXTrace function:

  • DXTRACE_ERR( str, hr )

  • DXTRACE_ERR_MSGBOX( str, hr )

  • DXTRACE_MSG( str )

The DXTRACE_ERR macro is used to output a message to the debugger, where str is a string you want displayed along with the tracing information and the HRESULT value of the error result. The DXTRACE_ERR_MSGBOX is essentially DXTRA-CE_ERR but will display the output using a message box pop-up. The final macro, DXTRACE_MSG, takes a single parameter, which is the string to display to the output debugger window. The file and line appear with the string parameter with each of these macros.

Throughout this chapter we’ve focused mostly on using DXTRACE_MSG to display errors when they occur during our Direct3D setup. It is important to be aware of these macros, as they can be very useful when building and debugging DirectX applications.

Summary

This chapter covered how to set up and create a Direct3D 11 window using Visual Studio 2010. We’ve ended this chapter with a set of template files that we can use for the rest of the demos throughout this book. If you’re coming to Direct3D 11 from Direct3D 9 or below, you can appreciate how easy it is to get Direct3D up and running. Although it took a number of lines of code, those lines of code were fairly straightforward and easy to digest.

In the next chapter we will begin rendering 2D geometry to the screen, along with discussing the topic of texture mapped images. We will build off the code developed throughout this chapter; therefore it is important to have a solid understanding of each of the Win32 and DirectX functions as well as how the code template works.

Chapter Questions

Answers to the following chapter review questions can be found in Appendix A on this book’s companion website.

1.

Which of the following is a Direct3D device type?

  1. Hardware

  2. Software

  3. WARP

  4. Both A and B

  5. All of the above

2.

The viewport does what?

  1. Draws to the monitor

  2. Defines the area of the window that is drawn to

  3. Is a 3D camera

  4. None of the above

3.

What are swap chains?

  1. Another name for a Direct3D device

  2. Another name for a Direct3D context

  3. Both A and B

  4. A collection of rendering destinations displayed to the screen

4.

Presenting the rendered scene is done with the Present method. Which object does Present belong to?

  1. Swap chains

  2. Direct3D device

  3. Direct3D context

  4. Render target view

5.

What are render targets?

  1. An object used to render geometry

  2. A texture that is the destination of a rendering output

  3. Another name for a swap chain

  4. None of the above

6.

What feature levels did the Blank Window demo support?

  1. Direct3D 11

  2. Direct3D 10

  3. Direct3D 9

  4. Both A and B

  5. Both A and B and Direct3D 10.1

  6. All of the above

7.

What is a WARP device?

  1. A fast hardware-rendering device.

  2. A down-level hardware-rendering device

  3. A fast software-rendering device

  4. None of the above

8.

How are Direct3D objects freed?

  1. Calling delete on them like C++ dynamically located objects

  2. Calling the COM object’s Release method

  3. They are automatically released upon the application’s exit

  4. Use an auto_ptr<>

9.

How can we create a full-screen Direct3D window?

  1. Setting the window’s width and height to a resolution larger than the screen

  2. Can only be done in hardware device mode

  3. Must use a special Direct3D context creation device for full-screen applications

  4. Set the Windowed property of the swap chain description to false

10.

What is the purpose of the DirectX Error Handling Library?

  1. Debug output

  2. Used to create exception-safe code

  3. Fixes errors at run-time

  4. Similar to C++ try/catch but for the graphics card

11.

True or False: auto_ptr<> is exception safe

  1. True

  2. False

12.

True or False: WinMain has Unicode parameters

  1. True

  2. False

13.

True or False: The operator dynamic_cast<> offers run-time safety

  1. True

  2. False

14.

True or False: The operator static_cast<> is used to cast static variables

  1. True

  2. False

15.

True or False: DXTRACE_MSG displays a string to a message box

  1. True

  2. False

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset