Chapter 11. Direct3D Initialization

In this chapter, you finalize the foundation of your rendering engine. You take your first look at the Direct3D C++ API and write an initial Direct3D application. Along the way, you revisit some previously discussed topics, including the DirectX swap chain and depth/stencil buffering.

Initializing Direct3D

You can perform Direct3D initialization through the following steps:

1. Create the Direct3D device and device context interfaces.

2. Check for multisampling support.

3. Create the swap chain.

4. Create a render target view.

5. Create a depth-stencil view.

6. Associate the render target view and depth-stencil view with the output-merger stage of the Direct3D pipeline.

7. Set the viewport.

The next few sections cover each of these steps.

Creating the Device and Device Context

A Direct3D device represents a display adapter, an abstraction of the underlying graphics capability of your computer. Your PC likely contains several adapters, including the graphics card itself (fast) and adapters that are implemented entirely in software (slow). A device is represented with the ID3D11Device interface, which creates resources and enumerates the capabilities of the display adapter.

A device context also represents the display adapter, but within a particular setting. The device context is encapsulated in the ID3D11DeviceContext interface, which generates rendering commands using the resources of a device. You create a Direct3D device through the function D3D11CreateDevice() whose prototype and parameters are listed here:

HRESULT WINAPI D3D11CreateDevice(
    IDXGIAdapter* pAdapter,
    D3D_DRIVER_TYPE DriverType,
    HMODULE Software,
    UINT Flags,
    CONST D3D_FEATURE_LEVEL* pFeatureLevels,
    UINT FeatureLevels,
    UINT SDKVersion,
    ID3D11Device** ppDevice,
    D3D_FEATURE_LEVEL* pFeatureLevel,
    ID3D11DeviceContext** ppImmediateContext );

Image pAdapter:  A pointer to the display adapter that this device will represent. Pass a value of NULL to use the default adapter. You can enumerate the available adapters with a call to IDXGIFactory::EnumAdapters().

Image DriverType:  The driver type to use with the device. Specify D3D_DRIVER_TYPE_HARDWARE to use the actual video card. This offers the best performance. However, you could specify D3D_DRIVER_TYPE_REFERENCE or D3D_DRIVER_TYPE_WARP for software implementations provided by Microsoft. See the online documentation for a complete list of driver types. All the work in this book uses the hardware driver.

Image Software: A handle to a third-party DLL that implements Direct3D in software. This parameter is used in conjunction with a driver type of D3D_DRIVER_TYPE_SOFTWARE.

Image Flags: Options to use when creating the Direct3D device. This parameter consists of bitwise OR’d values from the D3D11_CREATE_DEVICE_FLAG enumeration. A common option is D3D11_CREATE_DEVICE_DEBUG to create a device with debugging capability.

Image pFeatureLevels: A pointer to an array of targeted feature levels, listed in order of preference from most to least preferred. Feature levels describe the capabilities of a device and roughly correspond to a version of Direct3D. If you specify a value of NULL, you get the highest feature level the device supports—unless that device supports Direct3D version 11.1. In that scenario, you see support for only version 11.0, not version 11.1. You must explicitly list D3D_FEATURE_LEVEL_11_1 in the pFeatureLevels array if you want to target Direct3D 11.1. Furthermore, if you specify 11.1 as a targeted feature level and the device doesn’t support it, the D3D11CreateDevice() function fails. Table 11.1 lists the possible feature levels.

Image

Table 11.1 Direct3D Feature Levels

Image FeatureLevels: Specifies the number of elements in the pFeatureLevels array.

Image SDKVersion: States the version of the SDK. You will always specify D3D11_SDK_VERSION.

Image ppDevice: Returns the created device.

Image pFeatureLevel: Returns the selected feature level.

Image ppImmediateContext: Returns the created device context.

Listing 11.1 gives an example of the D3D11CreateDevice() call.

Listing 11.1 An Example Call to D3D11CreateDevice()


HRESULT hr;
UINT createDeviceFlags = 0;

#if defined(DEBUG) || defined(_DEBUG)
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

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

    ID3D11Device* direct3DDevice;
    D3D_FEATURE_LEVEL selectedFeatureLevel;
    ID3D11DeviceContext* direct3DDeviceContext;
    if (FAILED(hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE,
NULL, createDeviceFlags, featureLevels, ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION, &direct3DDevice, &selectedFeatureLevel,
&direct3DDeviceContext)))
    {
        throw GameException("D3D11CreateDevice() failed", hr);
    }


Checking for Multisampling Support

Multisample Anti-Aliasing (MSAA) is a technique to improve image quality. Aliasing refers to the jagged look of a line or triangle edge when presented on a computer monitor. It occurs because monitors use discrete points of light (pixels) to display virtual objects. As small as a pixel might be, it is not infinitesimal. Anti-aliasing refers to techniques to remove these “jaggies.” Figure 11.1 shows an example of a shape before and after anti-aliasing is applied.

Image

Figure 11.1 An example of a shape rendered before (top) and after (bottom) anti-aliasing is applied.

Reducing the size of each pixel can ameliorate aliasing, so increasing your monitor’s resolution can help. But even at your monitor’s maximum resolution, aliasing artifacts might still be unacceptable. Supersampling is a technique that reduces the effective size of a pixel by increasing the resolution of the render target. Thus, multiple subpixels are sampled and averaged together (in a process known as downsampling) to produce the final color of each pixel displayed. A render target four times (4x) larger than the display resolution is commonly used for super-sampling, although higher resolutions can be used at the cost of memory and performance. With supersampling, the pixel shader is executed for each subpixel. Thus, to produce a render target 4x larger than the display, the pixel shader must run 4x as often. Multisampling is an optimization of supersampling that executes the pixel shader just once per display resolution pixel (at the pixel’s center) and uses that color for each of the subpixels. All Direct3D 11 devices are required to support 4x MSAA, although they can support even higher-quality levels. You can query the available quality levels through ID3D11Device::CheckMultisampleQuality-Levels(); the prototype and parameters are listed here:

HRESULT CheckMultisampleQualityLevels(
    DXGI_FORMAT Format,
    UINT SampleCount,
    UINT *pNumQualityLevels);

Image Format: The texture format. Commonly, you specify this parameter as DXGI_FORMAT_R8G8B8A8_UNORM, a four-component format with 8 bits for each of the red, green, blue, and alpha channels. However, you can choose from many texture formats. Visit the online documentation for details.

Image SampleCount: The number of samples per pixel (for example, 4 for 4x multisampling).

Image pNumQualityLevels: The number of quality levels supported for the given format and sample count. The actual meaning of “quality level” can differ among hardware manufacturers. Generally, higher quality levels mean better results at higher performance cost. A returned value of 0 indicates that this device does not support the combination of format and sample count.

The multisampling quality values are used during swap chain creation, so a code example is deferred to the next section.

Creating the Swap Chain

A swap chain is a set of buffers used to display frames to the monitor. As discussed back in Chapter 4, “Hello, Shaders!,” double buffering refers to a swap chain containing two buffers, a front buffer and a back buffer. The front buffer is what’s actually displayed while the back buffer is being filled with new data. When the application presents the newly completed back buffer, the two are swapped and the process starts over.

Populating a Swap Chain Description

You create the swap chain by first populating an instance of the DXGI_SWAP_CHAIN_DESC1 structure, whose members are presented and described here:

typedef struct DXGI_SWAP_CHAIN_DESC1
    {
    UINT Width;
    UINT Height;
    DXGI_FORMAT Format;
    BOOL Stereo;
    DXGI_SAMPLE_DESC SampleDesc;
    DXGI_USAGE BufferUsage;
    UINT BufferCount;
    DXGI_SCALING Scaling;
    DXGI_SWAP_EFFECT SwapEffect;
    DXGI_ALPHA_MODE AlphaMode;
    UINT Flags;
    } DXGI_SWAP_CHAIN_DESC1;

Image Width: The resolution width.

Image Height: The resolution height.

Image Format: The display format of the back buffer, e.g. DXGI_FORMAT_R8G8B8A8_UNORM.

Image Stereo: Whether the back buffer will be used for stereoscopic rendering.

Image SampleDesc: Multisampling parameters. When multisampling is enabled, this member is populated with data validated through ID3D11Device::CheckMultisampleQualityLevels().

Image BufferUsage: Buffer usage and CPU access options. For example, the back buffer is commonly used for render target output (DXGI_USAGE_RENDER_TARGET_OUTPUT) but can also be used for shader input (DXGI_USAGE_SHADER_INPUT).

Image BufferCount: The number of back buffers to include in the swap chain. A value of 1 indicates double buffering (a front buffer and one back buffer). A value of 2 indicates two back buffers (triple buffering).

Image Scaling: The resize behavior when the size of the back buffer is not equal to the target output (such as with a window). For example, a value of DXGI_SCALING_STRETCH scales the image to fit the output.

Image SwapEffect: How the contents of a buffer are handled after being presented. DXGI_SWAP_EFFECT_DISCARD is the most efficient of the options and is the only one that supports multisampling.

Image AlphaMode: The transparency behavior of the back buffer.

Image Flags: Options to use when creating the swap chain. This value consists of bitwise OR’d values from the D3D11_SWAP_CHAIN_FLAG enumeration. Visit the online documentation for a list of available options.

Listing 11.2 presents the code for populating a DXGI_SWAP_CHAIN_DESC1 instance and the aforementioned CheckMultisampleQualityLevels() call.

Listing 11.2 Populating a Swap Chain Description Structure


mDirect3DDevice->CheckMultisampleQualityLevels(D
XGI_FORMAT_R8G8B8A8_UNORM,
mMultiSamplingCount, &mMultiSamplingQualityLevels);
 if (mMultiSamplingQualityLevels == 0)
 {
    throw GameException("Unsupported multi-sampling quality");
 }

 DXGI_SWAP_CHAIN_DESC1 swapChainDesc;
 ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
 swapChainDesc.Width = mScreenWidth;
 swapChainDesc.Height = mScreenHeight;
 swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

 if (mMultiSamplingEnabled)
 {
    swapChainDesc.SampleDesc.Count = mMultiSamplingCount;
    swapChainDesc.SampleDesc.Quality = mMultiSamplingQualityLevels - 1;
 }
 else
 {
     swapChainDesc.SampleDesc.Count = 1;
     swapChainDesc.SampleDesc.Quality = 0;
 }

 swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
 swapChainDesc.BufferCount = 1;
 swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;


Notice that the code in Listing 11.2 omits explicit assignment of the fields Stereo, Scaling, AlphaMode, and Flags. This is possible because the “zero” values align with the default values for these members.

Populating a Full-Screen Description

You can define your swap chain as either full screen or windowed. To create a full-screen swap chain, you must populate an instance of the DXGI_SWAP_CHAIN_FULLSCREEN_DESC structure; its members are presented and described next:

typedef struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC
    {
    DXGI_RATIONAL RefreshRate;
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
    DXGI_MODE_SCALING Scaling;
    BOOL Windowed;
    } DXGI_SWAP_CHAIN_FULLSCREEN_DESC;

Image RefreshRate: Describes the refresh rate of the display, in hertz. The DXGI_RATIONAL structure has two fields, Numerator and Denominator, both unsigned integers. If your refresh rate were, for example, 60Hz, you would specify 60 for the numerator and 1 for the denominator.

Image ScanlineOrdering: Indicates how the monitor should draw the image. For example, DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE specifies that the image should be created one line after another without skipping any (noninterlaced scanning).

Image Scaling: Indicates how the image is stretched to fit the monitor’s resolution. For example, DXGI_MODE_SCALING_CENTERED specifies that the image should be centered on the display and that no scaling should be performed.

Image Windowed: Specifies whether the swap chain is in windowed mode.

Listing 11.3 presents the code for populating a DXGI_SWAP_CHAIN_FULLSCREEN_DESC instance.

Listing 11.3 Populating a Full-Screen Description Structure


DXGI_SWAP_CHAIN_FULLSCREEN_DESC fullScreenDesc;
ZeroMemory(&fullScreenDesc, sizeof(fullScreenDesc));
fullScreenDesc.RefreshRate.Numerator = mFrameRate;
fullScreenDesc.RefreshRate.Denominator = 1;
fullScreenDesc.Windowed = !mIsFullScreen;


DirectX Graphics Infrastructure Interfaces

With swap chain and full-screen descriptions ready, you can create the swap chain through a call to IDXGIFactory2::CreateSwapChainForHwnd().The prototype and parameters are listed here:

HRESULT CreateSwapChainForHwnd(
    IUnknown *pDevice,
    HWND hWnd,
    const DXGI_SWAP_CHAIN_DESC1 *pDesc,
    const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc,
    IDXGIOutput *pRestrictToOutput,
    IDXGISwapChain1 **ppSwapChain);

Image pDevice: The Direct3D device.

Image hWnd: The window handle to associate with the swap chain. This is the return value of the CreateWindow() call.

Image pDesc: The swap chain description structure.

Image pFullscreenDesc: The full-screen description structure. You can set this to NULL to use a windowed swap chain, or you can set the Windowed member of the description structure to true.

Image pRestrictToOutput: Enables you to restrict content to a specific output target. If this parameter is set to NULL, the output is not restricted.

Image ppSwapChain: Returns the created swap chain.

Before examining a code sample, notice that the CreateSwapChainForHwnd() function is a member of the IDXGIFactory2 interface, which no section thus far has explicitly defined. DXGI is the DirectX Graphics Infrastructure and manages such tasks as presenting rendered frames to the screen and transitioning from windowed to full-screen display modes. These tasks are common across many APIs and are therefore independent of Direct3D. An IDXGIFactory2 object was instantiated when you created the Direct3D device, but you need to query the device to extract the required interface. You do this through the IUnknown::QueryInterface() method. IUknown is a Component Object Model (COM) interface, and COM interfaces have different acquisition and release patterns than typical C++ classes. After you query the interface, you call IUnknown::Release() when you are finished with the object. Listing 11.4 presents the code required to acquire the IDXGIFactory2 instance, along with the call to CreateSwapChainForHwnd().

Listing 11.4 Creating a Swap Chain


#define ReleaseObject(object) if((object) != NULL) { object->Release();
object = NULL; }

IDXGIDevice* dxgiDevice = nullptr;
if (FAILED(hr = mDirect3DDevice->QueryInterface(__uuidof(IDXGIDevice),
reinterpret_cast<void**>(&dxgiDevice))))
{
    throw GameException("ID3D11Device::QueryInterface() failed", hr);
}

IDXGIAdapter *dxgiAdapter = nullptr;
if (FAILED(hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter),
reinterpret_cast<void**>(&dxgiAdapter))))
{
    ReleaseObject(dxgiDevice);
    throw GameException("IDXGIDevice::GetParent() failed retrieving
adapter."
, hr);
}

IDXGIFactory2* dxgiFactory = nullptr;
if (FAILED(hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory2),
reinterpret_cast<void**>(&dxgiFactory))))
{
    ReleaseObject(dxgiDevice);
    ReleaseObject(dxgiAdapter);
    throw GameException("IDXGIAdapter::GetParent() failed retrieving
factory."
, hr);
}

IDXGISwapChain1* mSwapChain;
if (FAILED(hr = dxgiFactory->CreateSwapChainForHwnd(dxgiDevice,
mWindowHandle, &swapChainDesc, &fullScreenDesc, nullptr, &mSwapChain)))
{
    ReleaseObject(dxgiDevice);
    ReleaseObject(dxgiAdapter);
    ReleaseObject(dxgiFactory);
    throw GameException("IDXGIDevice::CreateSwapChainForHwnd()
failed."
, hr);
}

ReleaseObject(dxgiDevice);
ReleaseObject(dxgiAdapter);
ReleaseObject(dxgiFactory);


Creating a Render Target View

When you created the swap chain, you established a back buffer, a texture to render to. The intent is to bind that texture to the output-merger stage of the Direct3D pipeline. However, such resources aren’t bound directly to a pipeline stage; instead, you create a resource view of the texture and bind the view to the pipeline. You create a render target view with a call to ID3D11Device::CreateRenderTargetView(); its prototype and parameters are listed next:

HRESULT CreateRenderTargetView(
    ID3D11Resource *pResource,
    const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,
    ID3D11RenderTargetView **ppRTView);

Image pResource: The render target resource.

Image pDesc: A render target view description structure that allows explicit definition of the render target view. Set this parameter to NULL to create a view that accesses all subresources at mip-level 0.

Image ppRTView: The created render target view.

You can query the texture resource for the CreateRenderTargetView() function from the swap chain with a call to IDXGISwapChain::GetBuffer(). Listing 11.5 presents the code for querying the swap chain and creating a render target view.

Listing 11.5 Creating a Render Target View


ID3D11Texture2D* backBuffer;
if (FAILED(hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
reinterpret_cast<void**>(&backBuffer))))
{
    throw GameException("IDXGISwapChain::GetBuffer() failed.", hr);
}

if (FAILED(hr = mDirect3DDevice->CreateRenderTargetView(backBuffer,
nullptr, &mRenderTargetView)))
{
    ReleaseObject(backBuffer);
    throw GameException("IDXGIDevice::CreateRenderTargetView()
failed."
, hr);
}

ReleaseObject(backBuffer);


Creating a Depth-Stencil View

Objects in a scene can overlap. An object nearer the camera can partially or completely occlude an object that is farther away. When a pixel is written to the render target, its depth (its distance from the camera) can be written to a depth buffer. The depth buffer is just a 2D texture, but instead of storing colors, it stores depths. The output-merger stage can use these depths to determine whether new pixels should overwrite existing colors already present in the render target. This process is known as depth testing; we discussed it briefly back in Chapter 1, “Introducing DirectX.”

Stencil testing uses a mask to determine which pixels to update. This is conceptually similar to a cardboard or plastic stencil you might use to paint or print a design on a physical surface. The depth and stencil buffers are attached, hence the terms depth-stencil buffer and depth-stencil view.

Populating a 2D Texture Description

When you created the swap chain, you implicitly created the associated 2D texture for the back buffer, but a depth buffer was not created. You build a 2D texture for the depth buffer through a call to ID3D11Device::CreateTexture2D(). This method follows the description structure pattern you used when creating the swap chain and requires you to populate an instance of the D3D11_TEXTURE2D_DESC structure. The members of this structure are presented and described next:

typedef struct D3D11_TEXTURE2D_DESC
    {
    UINT Width;
    UINT Height;
    UINT MipLevels;
    UINT ArraySize;
    DXGI_FORMAT Format;
    DXGI_SAMPLE_DESC SampleDesc;
    D3D11_USAGE Usage;
    UINT BindFlags;
    UINT CPUAccessFlags;
    UINT MiscFlags;
    } D3D11_TEXTURE2D_DESC;

Image Width: The texture width.

Image Height: The texture height.

Image MipLevels: The number of mip-levels in the texture.

Image ArraySize: The number of textures in a texture array.

Image Format: The texture format. Unlike the back buffer format, which stores color, the depth buffer stores depth and stencil data. Common formats include DXGI_FORMAT_D24_UNORM_S8_UINT (a 24-bit depth buffer and an 8-bit stencil buffer) and DXGI_FORMAT_D32_FLOAT (all 32 bits are for the depth buffer).

Image SampleDesc: Multisampling parameters.

Image Usage: How the texture will be read from and written to. Most commonly set to D3D11_USAGE_DEFAULT.

Image BindFlags: D3D11_BIND_DEPTH_STENCIL for a depth-stencil buffer.

Image CPUAccessFlags: Bitwise OR’d values that specify whether the CPU is allowed to read or write to the texture. Use 0 when no CPU access is required.

Image MiscFlags: Flags for less commonly used resource options. Not used for the depth-stencil buffer.

Listing 11.6 shows an example of populating an instance of the D3D11_TEXTURE2D_DESC structure.

Listing 11.6 Populating a 2D Texture Description Structure


D3D11_TEXTURE2D_DESC depthStencilDesc;
ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));
depthStencilDesc.Width = mScreenWidth;
depthStencilDesc.Height = mScreenHeight;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;

if (mMultiSamplingEnabled)
{
    depthStencilDesc.SampleDesc.Count = mMultiSamplingCount;
    depthStencilDesc.SampleDesc.Quality = mMultiSamplingQualityLevels - 1;
}
else
{
    depthStencilDesc.SampleDesc.Count = 1;
    depthStencilDesc.SampleDesc.Quality = 0;
}


Creating a 2D Texture and Depth-Stencil View

You use the D3D11_TEXTURE2D_DESC structure instance with a call to ID3D11Device::CreateTexture2D(), whose prototype and parameters are listed next:

HRESULT CreateTexture2D(
    const D3D11_TEXTURE2D_DESC *pDesc,
    const D3D11_SUBRESOURCE_DATA *pInitialData,
    ID3D11Texture2D **ppTexture2D);

Image pDesc: The 2D texture description structure.

Image pInitialData: Initial data used to populate the texture. No initial data is required for the depth buffer, so this parameter is set to NULL.

Image ppTexture2D: Returns the created 2D texture.

Finally, the depth-stencil view is created with a call to ID3D11Device::CreateDepthStencilView(). This method has a set of parameters analogous to those of CreateRenderTargetView(). Listing 11.7 presents example code for creating a depth buffer and depth-stencil view.

Listing 11.7 Creating a Depth Buffer and Depth-Stencil View


ID3D11Texture2D* mDepthStencilBuffer;
ID3D11DepthStencilView* mDepthStencilView;

if (FAILED(hr = mDirect3DDevice->CreateTexture2D(&depthStencilDesc,
nullptr, &mDepthStencilBuffer)))
{
    throw GameException("IDXGIDevice::CreateTexture2D() failed.", hr);
}

if (FAILED(hr = mDirect3DDevice->CreateDepthStencilView(mDepthStencil
Buffer, nullptr, &mDepthStencilView)))
{
    throw GameException("IDXGIDevice::CreateDepthStencilView()
failed."
, hr);
}


Associating the Views to the Output-Merger Stage

With the render target view and the depth-stencil view created, you can now bind them to the output-merger stage of the Direct3D pipeline with a call to ID3D11DeviceContext::OMSet-RenderTargets(). The prototype and parameters of this method are listed next:

void OMSetRenderTargets(
    UINT NumViews,
    ID3D11RenderTargetView *const *ppRenderTargetViews,
    ID3D11DepthStencilView *pDepthStencilView);

Image NumViews: The number of render targets to bind to the pipeline. Part IV, “Intermediate-Level Rendering Topics,” covers using multiple render targets. For now, you specify 1 to bind just a single render target.

Image ppRenderTargetViews: The (input) array of render targets to bind to the pipeline (must have the same number of elements specified in NumViews).

Image pDepthStencilView: The depth-stencil view to bind to the pipeline.

Listing 11.8 presents the code for binding the views to the output-merger stage.

Listing 11.8 Binding the Views to the Output-Merger Stage


mDirect3DDeviceContext->OMSetRenderTargets(1, &mRenderTargetView,
mDepthStencilView);


Setting the Viewport

The final step of Direct3D initialization is to set a viewport. A viewport is a rectangular area that can encompass the entire back buffer, or some portion of it; your scenes are rendered to this area. Viewports are commonly used for split-screen multiplayer games, where different views of the game world are displayed to separate areas of the screen. You set the viewport by populating an instance of the D3D11_VIEWPORT structure and calling ID3D11DeviceContext::RSSetViewports(). The members of the D3D11_VIEWPORT structure are described here:

typedef struct D3D11_VIEWPORT
    {
    FLOAT TopLeftX;
    FLOAT TopLeftY;
    FLOAT Width;
    FLOAT Height;
    FLOAT MinDepth;
    FLOAT MaxDepth;
    } D3D11_VIEWPORT;

Image TopLeftX, TopLeftY, Width, Height: Members that define the area of the viewport.

Image MinDepth, MaxDepth: The minimum and maximum values for the depth buffer. These values are typically set to 0.0, and 1.0, respectively.

The RSSetViewports() method has just two parameters: the number of viewports to bind and an array of viewports. Listing 11.9 shows an example of creating a viewport structure and binding it to the pipeline.

Listing 11.9 Setting the Viewport


D3D11_VIEWPORT mViewport;
mViewport.TopLeftX = 0.0f;
mViewport.TopLeftY = 0.0f;
mViewport.Width = static_cast<float>(mScreenWidth);
mViewport.Height = static_cast<float>(mScreenHeight);
mViewport.MinDepth = 0.0f;
mViewport.MaxDepth = 1.0f;

mDirect3DDeviceContext->RSSetViewports(1, &mViewport);


Putting It All Together

With the specifics of Direct3D initialization in hand, you are ready to create your first Direct3D application. This entails augmenting the general-purpose Game class to incorporate the Direct3D initialization phases, creating a specialized Game class, and modifying the WinMain() function to use the specialized game class. The next few sections describe these steps.

Updated General-Purpose Game Class

Listing 11.10 presents an abbreviated version of the Game.h file. This code builds upon the window initialization code from Chapter 10, “Project Setup and Window Initialization.” Visit the book’s companion website for the full source code.

Listing 11.10 An Abbreviated Game.h Header File


#pragma once

#include "Common.h"
#include "GameClock.h"
#include "GameTime.h"

namespace Library
{
    class Game
    {
    public:
        /* ... Previously presented members removed for brevity ... */

        ID3D11Device1* Direct3DDevice() const;
        ID3D11DeviceContext1* Direct3DDeviceContext() const;
        bool DepthBufferEnabled() const;
        bool IsFullScreen() const;
        const D3D11_TEXTURE2D_DESC& BackBufferDesc() const;
        const D3D11_VIEWPORT& Viewport() const;

    protected:
        /* ... Previously presented members removed for brevity ... */

        virtual void InitializeDirectX();

        static const UINT DefaultFrameRate;
        static const UINT DefaultMultiSamplingCount;

        D3D_FEATURE_LEVEL mFeatureLevel;
        ID3D11Device1* mDirect3DDevice;
        ID3D11DeviceContext1* mDirect3DDeviceContext;
        IDXGISwapChain1* mSwapChain;

        UINT mFrameRate;
        bool mIsFullScreen;
        bool mDepthStencilBufferEnabled;
        bool mMultiSamplingEnabled;
        UINT mMultiSamplingCount;
        UINT mMultiSamplingQualityLevels;

        ID3D11Texture2D* mDepthStencilBuffer;
        D3D11_TEXTURE2D_DESC mBackBufferDesc;
        ID3D11RenderTargetView* mRenderTargetView;
        ID3D11DepthStencilView* mDepthStencilView;
        D3D11_VIEWPORT mViewport;
    };
}


As this code demonstrates, the Game class now stores members for the Direct3D device and device context, multisampling settings, the depth-stencil buffer and view, the render target view, and the viewport. A few of these members are exposed through public accessors, and Direct3D initialization is performed through the InitializeDirectX() method. Before presenting the implementation of these new Game members, notice the include directive for Common.h. This is a header file for commonly needed declarations (see Listing 11.11).

Listing 11.11 The Common.h Header File


#pragma once

#include <windows.h>
#include <exception>
#include <cassert>
#include <string>
#include <vector>
#include <map>
#include <memory>

#include <d3d11_1.h>
#include <DirectXMath.h>
#include <DirectXPackedVector.h>

#define DeleteObject(object) if((object) != NULL) { delete object;
object = NULL; }
#define DeleteObjects(objects) if((objects) != NULL) { delete[]
objects; objects = NULL; }
#define ReleaseObject(object) if((object) != NULL) { object->Release();
object = NULL; }

namespace Library
{
    typedef unsigned char byte;
}

using namespace DirectX;
using namespace DirectX::PackedVector;


The d3d11_1.h, DirectXMath.h and DirectXPackedVector.h files include declarations for Direct3D 11.1 and DirectXMath. The DirectXMath types are part of the DirectX and DirectX::PackedVector namespaces, hence their inclusion.

Listing 11.12 presents the updated Game class implementation. Again, this is an abbreviated listing of the file that omits the previously presented material and implementations of pass-through accessors.

Listing 11.12 An Abbreviated Game.cpp File


#include "Game.h"
#include "GameException.h"

namespace Library
{
    const UINT Game::DefaultScreenWidth = 1024;
    const UINT Game::DefaultScreenHeight = 768;
    const UINT Game::DefaultFrameRate = 60;
    const UINT Game::DefaultMultiSamplingCount = 4;

    Game::Game(HINSTANCE instance, const std::wstring& windowClass,
const std::wstring& windowTitle, int showCommand)
        : mInstance(instance), mWindowClass(windowClass),
          mWindowTitle(windowTitle), mShowCommand(showCommand),
          mWindowHandle(), mWindow(),
          mScreenWidth(DefaultScreenWidth), mScreenHeight(DefaultScreen
Height),
          mGameClock(), mGameTime(),
          mFeatureLevel(D3D_FEATURE_LEVEL_9_1),
mDirect3DDevice(nullptr),
          mDirect3DDeviceContext(nullptr), mSwapChain(nullptr),
          mFrameRate(DefaultFrameRate), mIsFullScreen(false),
          mDepthStencilBufferEnabled(false),
mMultiSamplingEnabled(false),
          mMultiSamplingCount(DefaultMultiSamplingCount),
          mMultiSamplingQualityLevels(0),
          mDepthStencilBuffer(nullptr), mRenderTargetView(nullptr),
          mDepthStencilView(nullptr), mViewport()
    {
    }

    void Game::Run()
    {
        InitializeWindow();
        InitializeDirectX();
        Initialize();

        MSG message;
        ZeroMemory(&message, sizeof(message));

        mGameClock.Reset();

        while(message.message != WM_QUIT)
        {
            if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE))
            {
                TranslateMessage(&message);
                DispatchMessage(&message);
            }
            else
            {
                mGameClock.UpdateGameTime(mGameTime);
                Update(mGameTime);
                Draw(mGameTime);
            }
        }

        Shutdown();
    }

    void Game::Shutdown()
    {
        ReleaseObject(mRenderTargetView);
        ReleaseObject(mDepthStencilView);
        ReleaseObject(mSwapChain);
        ReleaseObject(mDepthStencilBuffer);

        if (mDirect3DDeviceContext != nullptr)
        {
            mDirect3DDeviceContext->ClearState();
        }

        ReleaseObject(mDirect3DDeviceContext);
        ReleaseObject(mDirect3DDevice);

        UnregisterClass(mWindowClass.c_str(), mWindow.hInstance);
    }

    void Game::InitializeDirectX()
    {
        HRESULT hr;
        UINT createDeviceFlags = 0;

#if defined(DEBUG) || defined(_DEBUG)
        createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

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

        // Step 1: Create the Direct3D device and device context
interfaces

        ID3D11Device* direct3DDevice = nullptr;
        ID3D11DeviceContext* direct3DDeviceContext = nullptr;
        if (FAILED(hr = D3D11CreateDevice(NULL, D3D_DRIVER_
TYPE_HARDWARE, NULL, createDeviceFlags, featureLevels,
ARRAYSIZE(featureLevels),D3D11_SDK_VERSION, &direct3DDevice,
&mFeatureLevel, &direct3DDeviceContext)))
        {
            throw GameException("D3D11CreateDevice() failed", hr);
        }

        if (FAILED(hr = direct3DDevice->QueryInterface(__
uuidof
(ID3D11Device1), reinterpret_cast<void**>(&mDirect3DDevice))))
        {
            throw GameException("ID3D11Device::QueryInterface()
failed"
, hr);
        }

        if (FAILED(hr = direct3DDeviceContext->QueryInterface
(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void**>
(&mDirect3DDeviceContext))))
        {
            throw GameException("ID3D11Device::QueryInterface()
failed"
, hr);
        }

        ReleaseObject(direct3DDevice);
        ReleaseObject(direct3DDeviceContext);

        // Step 2: Check for multisampling support
        mDirect3DDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_
R8G8B8A8_UNORM, mMultiSamplingCount, &mMultiSamplingQualityLevels);
        if (mMultiSamplingQualityLevels == 0)
        {
            throw GameException("Unsupported multi-sampling quality");
        }

        DXGI_SWAP_CHAIN_DESC1 swapChainDesc;
        ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
        swapChainDesc.Width = mScreenWidth;
        swapChainDesc.Height = mScreenHeight;
        swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

        if (mMultiSamplingEnabled)
        {
            swapChainDesc.SampleDesc.Count = mMultiSamplingCount;
            swapChainDesc.SampleDesc.Quality =
mMultiSamplingQualityLevels - 1;
        }
        else
        {
            swapChainDesc.SampleDesc.Count = 1;
            swapChainDesc.SampleDesc.Quality = 0;
        }

        swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        swapChainDesc.BufferCount = 1;
        swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

        // Step 3: Create the swap chain
        IDXGIDevice* dxgiDevice = nullptr;
        if (FAILED(hr = mDirect3DDevice->QueryInterface(__
uuidof
(IDXGIDevice), reinterpret_cast<void**>(&dxgiDevice))))
        {
            throw GameException("ID3D11Device::QueryInterface()
failed"
, hr);
        }

        IDXGIAdapter *dxgiAdapter = nullptr;
        if (FAILED(hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter),
reinterpret_cast<void**>(&dxgiAdapter))))
        {
            ReleaseObject(dxgiDevice);
            throw GameException("IDXGIDevice::GetParent() failed
retrieving adapter."
, hr);
        }

        IDXGIFactory2* dxgiFactory = nullptr;
        if (FAILED(hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory2),
reinterpret_cast<void**>(&dxgiFactory))))
        {
            ReleaseObject(dxgiDevice);
            ReleaseObject(dxgiAdapter);
            throw GameException("IDXGIAdapter::GetParent() failed
retrieving factory."
, hr);
        }

        DXGI_SWAP_CHAIN_FULLSCREEN_DESC fullScreenDesc;
        ZeroMemory(&fullScreenDesc, sizeof(fullScreenDesc));
        fullScreenDesc.RefreshRate.Numerator = mFrameRate;
        fullScreenDesc.RefreshRate.Denominator = 1;
        fullScreenDesc.Windowed = !mIsFullScreen;

        if (FAILED(hr = dxgiFactory->CreateSwapChainForHwnd(dxgiDevice,
mWindowHandle, &swapChainDesc, &fullScreenDesc, nullptr, &mSwapChain)))
        {
            ReleaseObject(dxgiDevice);
            ReleaseObject(dxgiAdapter);
            ReleaseObject(dxgiFactory);
            throw GameException("IDXGIDevice::CreateSwapChainForHwnd()
failed."
, hr);
        }

        ReleaseObject(dxgiDevice);
        ReleaseObject(dxgiAdapter);
        ReleaseObject(dxgiFactory);

        // Step 4: Create the render target view
        ID3D11Texture2D* backBuffer;
        if (FAILED(hr = mSwapChain->GetBuffer(0, __
uuidof
(ID3D11Texture2D), reinterpret_cast<void**>(&backBuffer))))
        {
            throw GameException("IDXGISwapChain::GetBuffer() failed.", hr);
        }

        backBuffer->GetDesc(&mBackBufferDesc);

        if (FAILED(hr = mDirect3DDevice->CreateRenderTargetView
(backBuffer, nullptr, &mRenderTargetView)))
        {
            ReleaseObject(backBuffer);
            throw GameException("IDXGIDevice::CreateRenderTargetView()
failed."
, hr);
        }

        ReleaseObject(backBuffer);

        // Step 5: Create the depth-stencil view
        if (mDepthStencilBufferEnabled)
        {
            D3D11_TEXTURE2D_DESC depthStencilDesc;
            ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));
            depthStencilDesc.Width = mScreenWidth;
            depthStencilDesc.Height = mScreenHeight;
            depthStencilDesc.MipLevels = 1;
            depthStencilDesc.ArraySize = 1;
            depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
            depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
            depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;

            if (mMultiSamplingEnabled)
            {
                depthStencilDesc.SampleDesc.Count =
mMultiSamplingCount;
                depthStencilDesc.SampleDesc.Quality =
mMultiSamplingQualityLevels - 1;
            }
            else
            {
                depthStencilDesc.SampleDesc.Count = 1;
                depthStencilDesc.SampleDesc.Quality = 0;
            }

            if (FAILED(hr = mDirect3DDevice->CreateTexture2D
(&depthStencilDesc, nullptr, &mDepthStencilBuffer)))
            {
                throw GameException("IDXGIDevice::CreateTexture2D()
failed."
, hr);
            }

            if (FAILED(hr = mDirect3DDevice->CreateDepthStencilView
(mDepthStencilBuffer, nullptr, &mDepthStencilView)))
            {
                throw GameException("IDXGIDevice::CreateDepthStencilView()
failed."
, hr);
            }
        }

        // Step 6: Bind the render target and depth-stencil views to OM
stage

        mDirect3DDeviceContext->OMSetRenderTargets(1,
&mRenderTargetView, mDepthStencilView);

        mViewport.TopLeftX = 0.0f;
        mViewport.TopLeftY = 0.0f;
        mViewport.Width = static_cast<float>(mScreenWidth);
        mViewport.Height = static_cast<float>(mScreenHeight);
        mViewport.MinDepth = 0.0f;
        mViewport.MaxDepth = 1.0f;

        // Step 7: Set the viewport
        mDirect3DDeviceContext->RSSetViewports(1, &mViewport);
    }


The new code in Listing 11.12 includes the Game class constructor and the Shutdown() and InitializeDirectX() methods. The constructor simply initializes all the class members; the Shutdown() method releases Direct3D objects instantiated within InitializeDirectX(). The Run() method has been updated to call the DirectX initialization method. Recognize that the InitiaizeDirectX(), Shutdown(), and Run() methods are marked virtual and can therefore be overridden in a derived class. The presented implementations cover general-purpose uses of the Game class.

Specialized Game Class

For all the demo projects, you’ll create a specialized version of the Game class that resides within the executable project of your Visual Studio solution. Generally, this derived class overrides the general-purpose Initialize() method and the Update() and Draw() methods invoked by the base class Run() implementation. The base Game class implementations of these three methods are currently empty. In the next chapter, we discuss the concept of game components, a modular approach for adding functionality to your applications. These methods will initialize, update, and draw the collection of active components.

Listing 11.13 presents the header file for the RenderingGame class, which derives from the Library::Game class. Create this class within your Game project, not the Library project, which houses the majority of your code thus far.

Listing 11.13 The RenderingGame.h File


#pragma once

#include "Common.h"
#include "Game.h"

using namespace Library;

namespace Rendering
{
    class RenderingGame : public Game
    {
    public:
        RenderingGame(HINSTANCE instance, const std::wstring&
windowClass, const std::wstring& windowTitle, int showCommand);
        ~RenderingGame();

        virtual void Initialize() override;
        virtual void Update(const GameTime& gameTime) override;
        virtual void Draw(const GameTime& gameTime) override;

    private:
        static const XMVECTORF32 BackgroundColor;
    };
}


The only item to point out from this header file is the BackgroundColor member of type XMVECTORF32. This is a SIMD DirectXMath type that allows initializer syntax. Listing 11.14 shows the implementation of the RenderingGame class.

Listing 11.14 The RenderingGame.cpp File


#include "RenderingGame.h"
#include "GameException.h"

namespace Rendering
{
    const XMVECTORF32 RenderingGame::BackgroundColor = { 0.392f,
0.584f, 0.929f, 1.0f };

    RenderingGame::RenderingGame(HINSTANCE instance, const
std::wstring& windowClass, const std::wstring& windowTitle, int
showCommand)
        :  Game(instance, windowClass, windowTitle, showCommand)
    {
        mDepthStencilBufferEnabled = true;
        mMultiSamplingEnabled = true;
    }

    RenderingGame::~RenderingGame()
    {
    }

    void RenderingGame::Initialize()
    {
        Game::Initialize();
    }

    void RenderingGame::Update(const GameTime &gameTime)
    {
        Game::Update(gameTime);
    }

    void RenderingGame::Draw(const GameTime &gameTime)
    {
        mDirect3DDeviceContext->ClearRenderTargetView(mRenderTarget
View, reinterpret_cast<const float*>(&BackgroundColor));
        mDirect3DDeviceContext->ClearDepthStencilView(mDepthStencil
View, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

        Game::Draw(gameTime);

        HRESULT hr = mSwapChain->Present(0, 0);
        if (FAILED(hr))
        {
            throw GameException("IDXGISwapChain::Present() failed.", hr);
        }
    }
}


This class renders a blank screen in cornflower blue (or whatever color values you specify for the BackgroundColor member). The code shows the general structure of the Game-derived class, although you’ll actually extend the Initialize() and Update() methods in more interesting applications.

In the constructor of the RenderingGame class, you enable the use of a depth-stencil buffer and multisampling (although you aren’t specifically using them in this example). In the Draw() method, you clear the render target with a call to ID3D11DeviceContext1::Clear-RenderTargetView(). This method sets the entire render target to the specified color. Next, you clear the depth-stencil buffer with a call to ID3D11DeviceContext1::ClearDepth-StencilView(). The second parameter of this method is one or more D3D11_CLEAR_FLAG options, bitwise OR’d together to specify which part of the depth-stencil buffer to clear. The third parameter specifies the clear value for the depth buffer (typically 1.0), and the fourth parameter is the clear value for the stencil buffer (typically 0).

After you clear the render and depth-stencil views, you perform any game-specific rendering calls. In Listing 11.14, the call to Game::Draw() is intended to render any visible game components. Again, the next chapter covers the topic of game components. When all game-specific rendering has been completed, you flip the buffers in the swap chain with a call to IDXGISwapChain1::Present(). The first parameter in the Present() method specifies how the frame is presented with respect to the monitor’s vertical refresh. A value of 0 indicates that presentation should occur immediately, with no vertical refresh synchronization. The second parameter is a set of bitwise OR’d DXGI_PRESENT flags. A value of 0 simply presents the frame to the output without any special options. You can find details on more advanced vertical synchronization and presentation settings in the online documentation.

Updated WinMain()

The last step before running your application is to modify the WinMain() function to use the new RenderingGame class. The changes are simple; you include RenderingGame.h instead of Game.h and update the game variable instantiation to the corresponding class. Listing 11.15 presents the code for the updated WinMain() from Program.cpp.

Listing 11.15 The Program.cpp File


#include <memory>
#include "GameException.h"
#include "RenderingGame.h"

#if defined(DEBUG) || defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif

using namespace Library;
using namespace Rendering;

int WINAPI WinMain(HINSTANCE instance, HINSTANCE previousInstance,
LPSTR commandLine, int showCommand)
{
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

    std::unique_ptr<RenderingGame> game(new RenderingGame(instance,
L"RenderingClass", L"Real-Time 3D Rendering", showCommand));

    try
    {
        game->Run();
    }
    catch (GameException ex)
    {
        MessageBox(game->WindowHandle(), ex.whatw().c_str(),
game->WindowTitle().c_str(), MB_ABORTRETRYIGNORE);
    }

    return 0;
}


Build and run your application; you should see the image in Figure 11.2.

Image

Figure 11.2 A solid-color window rendered with Direct3D using the demo framework.

Again, the results of your efforts are a bit anticlimactic, but most of the preliminary work is now complete and you’ve set the stage for rendering with Direct3D.

Summary

In this chapter, you learned how to initialize Direct3D. You took a first look at the Direct3D C++ API and finalized the foundation of your rendering engine. Additional supporting systems are left to develop, but you have successfully completed your first Direct3D application.

In the next chapter, you create interfaces for game components, develop mouse and keyboard input systems, build a dynamic 3D camera, and learn how to render 2D text to the screen.

Exercise

1. From within the debugger, walk through the code used to initialize Direct3D, to better understand the initialization process. You’ll find an associated demo application on the book’s companion website.

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

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