Chapter 14. Hello, Rendering!

You’ve made it! You’ve completed the application framework, and you have enough code to start rendering. In this chapter, you implement your first full 3D applications. You examine numerous Direct3D and Effects 11 API calls. And when you’re finished, you’ll have a deeper understanding of the Direct3D graphics pipeline.

Your First Full Rendering Application

It’s now time to render your first 3D object to the screen: a triangle. It seems only fitting to call this application “Hello, Rendering” because it’s your first full Direct3D program. But the name belies its complexity because, as you’ve discovered over the last few chapters, a lot of infrastructure is required just to render a single triangle. Furthermore, you encounter a lot of new code in this section. This is far from a single-line “Hello, World!”. Let’s get started.

An Updated Game Project

You are welcome to continue using the Game project you’ve been developing or to create a new one. You’ll make no modifications to the structure of the Game project; you’ll have the same Program.cpp file that instantiates a RenderingGame object and invokes its Run() method. The implementation of the RenderingGame class is the same, with two exceptions: the inclusion of the FirstPersonCamera and a TriangleDemo class member. The TriangleDemo class will derive from DrawableGameComponent and will contain all the code for rendering a triangle. With these new members, the implementation of your RenderingGame class should resemble Listing 14.1.

Listing 14.1 The RenderingGame.cpp File


#include "RenderingGame.h"
#include "GameException.h"
#include "Keyboard.h"
#include "Mouse.h"
#include "FpsComponent.h"
#include "ColorHelper.h"
#include "FirstPersonCamera.h"
#include "TriangleDemo.h"

namespace Rendering
{
    const XMVECTORF32 RenderingGame::BackgroundColor = ColorHelper::CornflowerBlue;

    RenderingGame::RenderingGame(HINSTANCE instance, const
std::wstring& windowClass, const std::wstring& windowTitle, int
showCommand)
        :  Game(instance, windowClass, windowTitle, showCommand),
           mFpsComponent(nullptr),
           mDirectInput(nullptr), mKeyboard(nullptr), mMouse(nullptr),
           mDemo(nullptr)
    {
        mDepthStencilBufferEnabled = true;
        mMultiSamplingEnabled = true;
    }

    RenderingGame::~RenderingGame()
    {
    }

    void RenderingGame::Initialize()
    {
        if (FAILED(DirectInput8Create(mInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&mDirectInput, nullptr)))
        {
            throw GameException("DirectInput8Create() failed");
        }

        mKeyboard = new Keyboard(*this, mDirectInput);
        mComponents.push_back(mKeyboard);
        mServices.AddService(Keyboard::TypeIdClass(), mKeyboard);

        mMouse = new Mouse(*this, mDirectInput);
        mComponents.push_back(mMouse);
        mServices.AddService(Mouse::TypeIdClass(), mMouse);

        mCamera = new FirstPersonCamera(*this);
        mComponents.push_back(mCamera);
        mServices.AddService(Camera::TypeIdClass(), mCamera);

        mFpsComponent = new FpsComponent(*this);
        mComponents.push_back(mFpsComponent);

        mDemo = new TriangleDemo(*this, *mCamera);
        mComponents.push_back(mDemo);

        Game::Initialize();

        mCamera->SetPosition(0.0f, 0.0f, 5.0f);
    }

    void RenderingGame::Shutdown()
    {
        DeleteObject(mDemo);
        DeleteObject(mKeyboard);
        DeleteObject(mMouse);
        DeleteObject(mFpsComponent);
        DeleteObject(mCamera);

        ReleaseObject(mDirectInput);

        Game::Shutdown();
    }

    void RenderingGame::Update(const GameTime &gameTime)
    {
        if (mKeyboard->WasKeyPressedThisFrame(DIK_ESCAPE))
        {
            Exit();
        }

        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);
        }
    }
}


The RenderingGame::Initialize() method now instantiates the mCamera and mDemo class members and adds them to the components container. Additionally, a ColorHelper class is introduced that initializes the background color. This class contains a few static members for common colors (of type XMVECTORF32) and isn’t listed here, but the companion website does include it. A final note is for the FirstPersonCamera::SetPosition() call at the end of the Initialize() method. This moves the camera five units along the positive z-axis. You’ll be creating a triangle at the origin, and this call moves the camera so that the triangle is immediately visible upon program execution. This demo uses a right-handed coordinate system, so positive z is headed out of the screen.

A TriangleDemo Component

Listing 14.2 presents the declaration of the TriangleDemo class. Notice the BasicEffectVertex structure and the class members that begin with ID3DX11Effect. The former defines the structure of the triangle’s vertices and what data will be passed as shader input. The latter refers to types from the Effects 11 library, which Chapter 3, “Tools of the Trade,” introduced. Extract this library to the external directory if you haven’t already done so. Refer to the “Linking Libraries” section of Chapter 10, “Project Setup and Window Initialization,” if you have difficulty referencing this package. Shaders are required for all rendering in Direct3D. Thus, such class members are necessary regardless of the complexity of your scene.

A class member of type ID3DInputLayout also stores the definition of vertex data for the input-assembler pipeline stage, and a class member of type ID3D11Buffer exists for the vertex buffer. We discuss all these items after the code listing.

Listing 14.2 The TriangleDemo.h Header File


#pragma once

#include "DrawableGameComponent.h"

using namespace Library;

namespace Rendering
{
    class TriangleDemo : public DrawableGameComponent
    {
        RTTI_DECLARATIONS(TriangleDemo, DrawableGameComponent)

    public:
        TriangleDemo(Game& game, Camera& camera);
        ~TriangleDemo();

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

    private:
        typedef struct _BasicEffectVertex
        {
            XMFLOAT4 Position;
            XMFLOAT4 Color;

            _BasicEffectVertex() { }

            _BasicEffectVertex(XMFLOAT4 position, XMFLOAT4 color)
                : Position(position), Color(color) { }
        } BasicEffectVertex;

        TriangleDemo();
        TriangleDemo(const TriangleDemo& rhs);
        TriangleDemo& operator=(const TriangleDemo& rhs);

        ID3DX11Effect* mEffect;
        ID3DX11EffectTechnique* mTechnique;
        ID3DX11EffectPass* mPass;
        ID3DX11EffectMatrixVariable* mWvpVariable;

        ID3D11InputLayout* mInputLayout;
        ID3D11Buffer* mVertexBuffer;

        XMFLOAT4X4 mWorldMatrix;
    };
}


The Effects 11 Library

The Effects 11 library provides a set of C++ interfaces for the effect file format (the .fx format you’ve been using for all your shaders). The root interface is ID3DX11Effect, which encapsulates (among other things) the effect’s variables and techniques. A technique is exposed through the ID3DX11EffectTechnique interface, and it contains one or more passes, represented by the ID3DX11EffectPass interface. An effect’s variables are represented by the ID3DX11EffectVariable interface, but a set of derived classes also exists for specific types of shader constant. For example, the TriangleDemo::mWvpVariable class member is of type ID3DEffectMatrixVariable and is used to access the WorldViewProjection matrix within the effect. All types within the Effects 11 library are found in the d3dx11Effect.h header file.

Compiling an Effect

An effect must be compiled before it can be used. You can do this when the Visual Studio project is built, or you can compile the effect at runtime. In the next chapter, you learn how to compile effects at build time, but this section covers runtime effect compilation. You’ll compile an effect using the D3DCompileFromFile() function, found in the D3DCompiler.h header file. This function’s prototype and parameters are listed here:

HRESULT D3DCompileFromFile(
                   LPCWSTR pFileName,
                   D3D_SHADER_MACRO* pDefines,
                   ID3DInclude* pInclude,
                   LPCSTR pEntrypoint,
                   LPCSTR pTarget,
                   UINT Flags1,
                   UINT Flags2,
                   ID3DBlob** ppCode,
                   ID3DBlob** ppErrorMsgs);

Image pFileName: The name of the file containing the effect.

Image pDefines: An optional array of shader macros defined by the D3D_SHADER_MACRO structure. Your effects aren’t using any shader macros, so you can set this parameter to NULL.

Image pInclude: An optional ID3DInclude pointer that directs the application to call user-defined methods for opening and closing #include files. If you set this parameter to NULL, the compilation will fail on effects with #include directives. You can pass the D3D_COMPILE_STANDARD_FILE_INCLUDE macro to specify the default include handler. The default handler includes files relative to the current directory, so it’s important to set the current working directory to the correct location before compiling effects with this option.

Image pEntryPoint: The name of the shader’s entry point function. This always is set to NULL when compiling effect files.

Image pTarget: The compiler’s shader target. You’ll specify fx_5_0 for all your effects.

Image Flags1: Bitwise OR’d flags from the D3DCOMPILE enumeration that specify how your shader code is compiled. Common options include D3DCOMPILE_DEBUG and D3DCOMPILE_SKIP_OPTIMIZATIONS, which are useful for debugging shaders. Visit the online documentation for a full list of options.

Image Flags2: Bitwise OR’d flags from the D3DCOMPILE_EFFECT enumeration that specify how your effect file is compiled. These are for advanced features and aren’t commonly employed.

Image ppCode: The compiled code returned as a pointer to an ID3DBlob object. The ID3DBlob interface describes a buffer of arbitrary length. It has only two methods: ID3DBlob::GetBufferPointer() and ID3DBlob::GetBufferSize(). They return a pointer to the data and the size (in bytes) of the data, respectively.

Image ppErrorMsgs: An optional parameter for storing compiler error messages.

Listing 14.3 demonstrates the D3DCompileFromFile() function.

Listing 14.3 Compiling an Effect


SetCurrentDirectory(Utility::ExecutableDirectory().c_str());

UINT shaderFlags = 0;

#if defined( DEBUG ) || defined( _DEBUG )
    shaderFlags |= D3DCOMPILE_DEBUG;
    shaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

ID3D10Blob* compiledShader = nullptr;
ID3D10Blob* errorMessages = nullptr;
HRESULT hr = D3DCompileFromFile(L"Content\Effects\BasicEffect.fx",
nullptr, nullptr, nullptr, "fx_5_0", shaderFlags, 0, &compiledShader,
&errorMessages);
if (errorMessages != nullptr)
{
    GameException ex((char*)errorMessages->GetBufferPointer(), hr);
    ReleaseObject(errorMessages);

    throw ex;
}

if (FAILED(hr))
{
    throw GameException("D3DX11CompileFromFile() failed.", hr);
}


This code compiles the BasicEffect.fx file from the ContentEffects folder relative to the directory housing the application’s executable. The compiled effect is returned through the compiledShader variable, and any error messages are returned through the errorMessages variable.

The BasicEffect.fx file is a derivation of the HelloStructs.fx effect you wrote back in Chapter 4. It accepts a vertex position and color for shader inputs, transforms the position to homogenous clip space, and outputs the vertex color, interpolated per pixel. Listing 14.4 presents the complete effect. Note the use of the technique11 keyword to specify a DirectX 11 technique.

Listing 14.4 The BasicEffect.fx File


cbuffer CBufferPerObject
{
    float4x4 WorldViewProjection : WORLDVIEWPROJECTION;
}

struct VS_INPUT
{
    float4 ObjectPosition: POSITION;
    float4 Color : COLOR;
};

struct VS_OUTPUT
{
    float4 Position: SV_Position;
    float4 Color : COLOR;
};

RasterizerState DisableCulling
{
    CullMode = NONE;
};

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;

    OUT.Position = mul(IN.ObjectPosition, WorldViewProjection);
    OUT.Color = IN.Color;

    return OUT;
}

float4 pixel_shader(VS_OUTPUT IN) : SV_Target
{
    return IN.Color;
}

technique11 main11
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, pixel_shader()));

        SetRasterizerState(DisableCulling);
    }
}


You should store this file under the ContentEffects directory of either your Game or your Library project. Be certain that you have the proper pre- and post-build events set up to copy this content to the executable directory. See Chapter 12 for details on these build events.

Creating an Effect Object

With the effect compiled, you can use the resulting ID3DBlob to create an ID3DX11Effect object. You do so through the D3DX11CreateEffectFromMemory() function. The prototype and parameters are listed next:

HRESULT D3DX11CreateEffectFromMemory(
                            LPCVOID pData,
                            SIZE_T DataLength,
                            UINT FXFlags,
                            ID3D11Device *pDevice,
                            ID3DX11Effect **ppEffect );

Image pData: The compiled effect.

Image DataLength: The size (in bytes) of the compiled effect.

Image FXFlags: Currently, no effect flags exist. This parameter always is set to 0.

Image pDevice: The Direct3D device.

Image ppEffect: The created effect instance.

Listing 14.5 presents an example of the D3DX11CreateEffectFromMemory() call. Note the release of the compiled shader blob. After the effect object has been created, there is little use for the compiled shader, so its reference is released.

Listing 14.5 Creating an Effect Object


hr = D3DX11CreateEffectFromMemory(compiledShader->GetBufferPointer(), compiledShader->GetBufferSize(), 0, mGame->Direct3DDevice(), &mEffect);
if (FAILED(hr))
{
    throw GameException("D3DX11CreateEffectFromMemory() failed.", hr);
}

ReleaseObject(compiledShader);


Technique, Pass, and Variable Lookup

With an effect object created, you can find the technique, pass, and variables to use. The methods for accomplishing these tasks share a consistent naming convention: ID3DX11Effect::GetTechniqueByName(), ID3DX11EffectTechnique::GetPassByName(), and ID3DX11Effect::GetVariableByName(). All three methods have exactly one parameter, the string representing the value to find; they also each return the corresponding interface, or NULL if the identifier is not found.

The ID3DX11EffectVariable interface has a set of “As” methods that cast the variable to a more specific interface. For example, the ID3DX11EffectVariable::AsMatrix() method attempts to cast the object to the ID3DX11EffectMatrixVariable interface. You must test the success of this method with a call to ID3DX11EffectVariable::IsValid() against the cast instance.

Listing 14.6 demonstrates the calls to find the main11 technique, the p0 pass, and the World-ViewProjection variable from the BasicEffect.fx file. It also includes an example call for casting the WorldViewProjection variable to the ID3DX11EffectMatrixVariable interface.

Listing 14.6 Retrieving Techniques, Passes, and Variables


mTechnique = mEffect->GetTechniqueByName("main11");
if (mTechnique == nullptr)
{
    throw GameException("ID3DX11Effect::GetTechniqueByName() could not
find the specified technique."
, hr);
}

mPass = mTechnique->GetPassByName("p0");
if (mPass == nullptr)
{
    throw GameException("ID3DX11EffectTechnique::GetPassByName() could
not find the specified pass."
, hr);
}

ID3DX11EffectVariable* variable = mEffect->GetVariableByName
("WorldViewProjection");
if (variable == nullptr)
{
    throw GameException("ID3DX11Effect::GetVariableByName() could not
find the specified variable."
, hr);
}

mWvpVariable = variable->AsMatrix();
if (mWvpVariable->IsValid() == false)
{
    throw GameException("Invalid effect variable cast.");
}


Creating an Input Layout

Following is the VS_INPUT structure of the BasicEffect.fx file and the BasicEffectVertex structure from the TriangleDemo class:

struct VS_INPUT
  {
    float4 ObjectPosition: POSITION;
    float4 Color : COLOR;
  };

typedef struct _BasicEffectVertex
{
    XMFLOAT4 Position;
    XMFLOAT4 Color;

    _BasicEffectVertex() { }

    _BasicEffectVertex(XMFLOAT4 position, XMFLOAT4 color)
        : Position(position), Color(color) { }
} BasicEffectVertex;

These structures both represent the same vertex data (a position and a color), the BasicEffectVertex structure from the C++/CPU side, and the VS_INPUT structure from the HLSL/GPU side. Direct3D requires an input layout to map the vertex data from the CPU to the GPU. You create an input layout by first configuring an array of D3D11_INPUT_ELEMENT_DESC instances. This structure has the following definition:

typedef struct D3D11_INPUT_ELEMENT_DESC
    {
    LPCSTR SemanticName;
    UINT SemanticIndex;
    DXGI_FORMAT Format;
    UINT InputSlot;
    UINT AlignedByteOffset;
    D3D11_INPUT_CLASSIFICATION InputSlotClass;
    UINT InstanceDataStepRate;
    } D3D11_INPUT_ELEMENT_DESC;

Image SemanticName: The HLSL semantic associated with the input element (for example, POSITION or COLOR).

Image SemanticIndex: The semantic index for the element (for example, 1 for TEXCOORD1 and 2 for COLOR2). Indices are not required unless the same semantic is assigned to more than one input element. For example, the semantic POSITION is equivalent to POSITION0.

Image Format: The type of data in the input element. For example, DXGI_FORMAT_R32G32B32A32_FLOAT describes a format with four 32-bit floating-point channels.

Image InputSlot: Depending on the feature level, there are 16 or 32 available input slots through which your vertex data can be sent. These slots accommodate multiple vertex buffers, where each vertex buffer is assigned a different slot.

Image AlignedByteOffset: The offset (in bytes) between each input element. Use D3D11_APPEND_ALIGNED_ELEMENT to autoalign the elements.

Image InputSlotClass: Input data specification as either per-vertex or per-instance. Part IV, “Intermediate-Level Rendering Topics,” discusses hardware instancing. For now, you’ll use D3D_INPUT_PER_VERTEX_DATA.

Image InstanceDataStepRate: This parameter is used in conjunction with per-instance data (an InputSlotClass specified as D3D_INPUT_PER_INSTANCE_DATA). For per-vertex data, this parameter always is set to 0.

With an array of input element descriptions, you invoke the ID3D11Device::CreateInput-Layout() method; the prototype and parameters are listed here:

HRESULT CreateInputLayout(
            const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
            UINT NumElements,
            const void *pShaderBytecodeWithInputSignature,
            SIZE_T BytecodeLength,
            ID3D11InputLayout **ppInputLayout);

Image pInputElementDescs: An array of input element descriptions.

Image NumElements: The number of elements in pInputElementDescs.

Image pShaderBytecodeWithInputSignature: The compiled shader (contains an input signature that is validated against the input element descriptions).

Image BytecodeLength: The size (in bytes) of the compiled shader.

Image ppInputLayout: The created input layout.

Listing 14.7 demonstrates the creation of the input layout for the triangle demo.

Listing 14.7 Input Layout Creation


D3DX11_PASS_DESC passDesc;
mPass->GetDesc(&passDesc);

D3D11_INPUT_ELEMENT_DESC inputElementDescriptions[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_
PER_VERTEX_DATA, 0 },
    { "COLOR",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_
ALIGNED_ELEMENT
, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};

if (FAILED(hr = mGame->Direct3DDevice()->CreateInputLayout
(inputElementDescriptions,ARRAYSIZE(inputElementDescriptions),
passDesc.pIAInputSignature,
passDesc.IAInputSignatureSize, &mInputLayout)))
{
    throw GameException("ID3D11Device::CreateInputLayout() failed.",
hr);
}


The first two lines of Listing 14.7 get a pass description structure. This data type contains the input signature that the ID3D11Device::CreateInputLayout() method requires. The input signature is associated with a pass because a pass specifies which vertex shader to use and, hence, what shader inputs to use. Next, an array of input element descriptions is created for the POSITION and COLOR inputs. Finally, the input layout object is created.

Creating a Vertex Buffer

The last step in the initialization of the TriangleDemo class is the creation of a vertex buffer. A vertex buffer stores the vertices processed by the Direct3D graphics pipeline and is represented by the ID3D11Buffer interface. To create a vertex buffer, you first configure a D3D11_BUFFER_DESC structure which is defined as follows:

typedef struct D3D11_BUFFER_DESC
    {
    UINT ByteWidth;
    D3D11_USAGE Usage;
    UINT BindFlags;
    UINT CPUAccessFlags;
    UINT MiscFlags;
    UINT StructureByteStride;
    } D3D11_BUFFER_DESC;

Image ByteWidth: The size of the buffer.

Image Usage: How the buffer will be read from and written to. Set this parameter to D3D11_USAGE_IMMUTABLE if the vertex buffer won’t be modified after creation. Only the GPU can access immutable buffers (read, not write); the CPU can’t access them at all.

Image BindFlags: Set to D3D11_BIND_VERTEX_BUFFER for vertex buffers.

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 buffer options.

Image StructureByteStride: The size of an element when the buffer is a structured buffer. A structured buffer contains elements of equal sizes. This is set to 0 for all the demos in this text.

Next, you populate a D3D11_SUBRESOURCE_DATA structure to provide initial data to the vertex buffer. For immutable buffers (buffers with a usage of D3D11_USAGE_IMMUTABLE), data must be provided when the buffer is created. Otherwise, this is an optional step. The D3D11_SUBRESOURCE_DATA structure has the following definition:

typedef struct D3D11_SUBRESOURCE_DATA
    {
    const void *pSysMem;
    UINT SysMemPitch;
    UINT SysMemSlicePitch;
    } D3D11_SUBRESOURCE_DATA;

Image pSysMem: A pointer to the initialization data.

Image SysMemPitch: The length (in bytes) of one line of a texture. This parameter applies only to 2D and 3D textures and has no bearing on vertex buffers.

Image SysMemSlicePitch: The distance (in bytes) from one depth level to the next. This parameter is used only for 3D textures.

The final step for creating a vertex buffer is to invoke the method  ID3D11Device:::CreateBuffer().

HRESULT CreateBuffer(
    const D3D11_BUFFER_DESC *pDesc,
    const D3D11_SUBRESOURCE_DATA *pInitialData,
    ID3D11Buffer **ppBuffer);

Image pDesc: The buffer description structure

Image pInitialData: The (optional) initial data for the buffer

Image ppBuffer: The created buffer

Listing 14.8 presents the code for creating the vertex buffer for the triangle demo.

Listing 14.8 Vertex Buffer Creation


BasicEffectVertex vertices[] =
{
    BasicEffectVertex(XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Red))),
    BasicEffectVertex(XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Green))),
    BasicEffectVertex(XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Blue)))
};

D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory(&vertexBufferDesc, sizeof(vertexBufferDesc));
vertexBufferDesc.ByteWidth = sizeof(BasicEffectVertex) *
ARRAYSIZE(vertices);
vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

D3D11_SUBRESOURCE_DATA vertexSubResourceData;
ZeroMemory(&vertexSubResourceData, sizeof(vertexSubResourceData));
vertexSubResourceData.pSysMem = vertices;
if (FAILED(mGame->Direct3DDevice()->CreateBuffer(&vertexBufferDesc, &vertexSubResourceData, &mVertexBuffer)))
{
    throw GameException("ID3D11Device::CreateBuffer() failed.");
}


The first statements of Listing 14.8 initialize an array of three vertices at positions (-1, 0, 0), (0, 1, 0), and (1, 0, 0) with red, green, and blue vertex colors, respectively. Next a buffer description structure is configured for an immutable vertex buffer, and a D3D11_SUBRESOURCE_DATA structure is populated with the array of vertices. Finally, the vertex buffer is created and stored in the mVertexBuffer class member.

Rendering a Triangle

You are now ready to draw the triangle. You render a set of nonindexed primitives (for example, a triangle without an index buffer) in these steps:

1. Set the primitive topology for the input-assembler stage.

2. Bind the input layout to the input-assembler stage.

3. Bind the vertex buffer to the input-assembler stage.

4. Set any shader constants.

5. Apply the effect pass to the device.

6. Execute the nonindexed draw call.

The next sections cover each of these steps.

Setting the Primitive Topology

The first step for rendering a triangle is to tell the input-assembler stage what topology to use for the incoming vertices. You accomplish this with the ID3D11DeviceContext::IASetPrimitiveTopology() method, which has no return value and accepts a single argument of type D3D11_PRIMITIVE_TOPOLOGY. Table 14.1 lists the most common topologies.

Image

Table 14.1 Common Primitive Topologies

Binding the Input Layout

The next step is to bind the input layout to the input-assembler stage with a call to ID3D11DeviceContext::IASetInputLayout(). This method has no return value and only a single input parameter: the ID3D11InputLayout object you created.

Binding the Vertex Buffer

Next, you must bind the vertex buffer to the input-assembler stage with a call to ID3D11Device Context::IASetVertexBuffers(). This method establishes the CPU-to-GPU vertex input stream and has the following prototype:

void IASetVertexBuffers(
            UINT StartSlot,
            UINT NumBuffers,
            ID3D11Buffer *const *ppVertexBuffers,
            const UINT *pStrides,
            const UINT *pOffsets);

Image StartSlot: The first input slot to use for the list of vertex buffers. Recall that 16 or 32 input slots are available (depending on the feature level) for binding multiple vertex buffers.

Image NumBuffers: The number of vertex buffers in ppVertexBuffers.

Image ppVertexBuffers: An array of vertex buffers to bind.

Image pStrides: An array of stride values, one for each of the vertex buffers. A stride value is the size (in bytes) of the elements in the vertex buffer (for example, the size of the BasicEffectVertex structure for the triangle demo).

Image pOffsets: An array of offsets, one for each of the vertex buffers. An offset is the number of bytes in the vertex buffer to skip before processing vertices. This is useful if a single buffer contains vertices for multiple objects and you intend to draw only a subset of the complete vertex buffer.

The ID3D11DeviceContext::IASetVertexBuffers() method might feel a bit cumbersome at this stage because the triangle demo won’t be using multiple vertex buffers or offsets. But this is a one-size-fits-all sort of method that accommodates more advanced usage. Listing 14.9 demonstrates the calls for setting the primitive topology, binding the input layout, and binding the triangle demo’s vertex buffer.

Listing 14.9 Setting the Primitive Topology and Binding the Input Layout and Vertex Buffer


ID3D11DeviceContext* direct3DDeviceContext =
mGame->Direct3DDeviceContext();
direct3DDeviceContext->IASetPrimitiveTopology(
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
direct3DDeviceContext->IASetInputLayout(&mInputLayout);

UINT stride = sizeof(BasicEffectVertex);
UINT offset = 0;
direct3DDeviceContext->IASetVertexBuffers(0, 1, &mVertexBuffer,
&stride, &offset);


Setting Shader Constants and Applying the Effect Pass

You must update the variables in your shaders (for example, the WorldViewProjection matrix) before executing a draw call. The ID3DX11EffectPass and ID3DX11EffectVariable types from the Effects 11 library provide this functionality. For example, the ID3DX11 EffectVariable::SetRawValue() method enables you to send arbitrary data to a shader variable. It has the following declaration:

HRESULT SetRawValue(
    void *pData,
    UINT Offset,
    UINT ByteCount);

Image pData: The data to set

Image Offset: An offset (in bytes) from the beginning of the data

Image ByteCount: The number of bytes to set

You can use this method to set variables of any type. However, the derived effect variable types are a bit more user friendly. For example, the ID3DX11EffectMatrixVariable type has a SetMatrix() method that accepts just a single input parameter: an array of floating-point values to set. Regardless of which interface you use, all “Set” methods are cached, and their updates are not sent to the GPU until you invoke the ID3DX11EffectPass::Apply() method. This is a performance-saving mechanism because you typically set multiple shader variables when drawing an object. By caching these updates, the GPU state can be modified just once per batch instead of once per “Set” call. The ID3DX11EffectPass::Apply() method has two parameters, an unsigned integer for flags (which is unused) and the ID3D11DeviceContext to apply the pass to. Listing 14.10 demonstrates the calls for updating the WorldViewProjection variable and applying the effect pass.

Listing 14.10 Setting Shader Constants and Applying the Effect Pass


XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);
XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() *
mCamera->ProjectionMatrix();
mWvpVariable->SetMatrix(reinterpret_cast<const float*>(&wvp));

mPass->Apply(0, direct3DDeviceContext);


The first two statements in Listing 14.10 load the mWorldMatrix class member into a SIMD XMMATRIX object and then construct the world view projection matrix. The world matrix must be initialized to the identity matrix or must contain a valid combination of translation, rotation, and scaling transformations.

Executing the Draw Call

The final step is to execute the nonindexed draw call against the Direct3D device context. This is done through the ID3D11DeviceContext::Draw() method, which has the following prototype and parameters:

void Draw(
    UINT VertexCount,
    UINT StartVertexLocation);

Image VertexCount: The number of vertices to render.

Image StartVertexLocation: The index of the first vertex you’d like to render. This supplies an offset into the vertex buffer; it is useful if multiple objects are contained within a shared vertex buffer and you want to render only a subset.

Listing 14.11 demonstrates the draw call for the triangle demo. It specifies three vertices to draw, with no vertex buffer offset.

Listing 14.11 Executing the Nonindexed Draw Call


direct3DDeviceContext->Draw(3, 0);


Putting It All Together

You now have all the pieces for rendering a triangle. Listing 14.12 presents the complete implementation of the TriangleDemo class. All the steps in this chapter are split between the TriangleDemo::Initialize() and TriangleDemo::Draw() methods. Note that the ID3D11DeviceContext::IASetPrimitiveTopology(), IASetInputLayout(), and IASetVertexBuffers() methods could have been invoked within the TriangleDemo::Initialize() method but are instead invoked (each frame) from the TriangleDemo::Draw() method. This is done to emphasize that components have no knowledge of changes to the Direct3D pipeline state that happen between frames. Another component could have modified any of these settings, which would break the TriangleDemo’s rendering if the states weren’t reset each frame.

Listing 14.12 The TriangleDemo.cpp File


#include "TriangleDemo.h"
#include "Game.h"
#include "GameException.h"
#include "MatrixHelper.h"
#include "ColorHelper.h"
#include "Camera.h"
#include "Utility.h"
#include "D3DCompiler.h"

namespace Rendering
{
    RTTI_DEFINITIONS(TriangleDemo)

    TriangleDemo::TriangleDemo(Game& game, Camera& camera)
        : DrawableGameComponent(game, camera),
          mEffect(nullptr), mTechnique(nullptr), mPass(nullptr),
mWvpVariable(nullptr),
          mInputLayout(nullptr), mWorldMatrix(MatrixHelper::Identity),
mVertexBuffer(nullptr)
    {
    }

    TriangleDemo::~TriangleDemo()
    {
        ReleaseObject(mWvpVariable);
        ReleaseObject(mPass);
        ReleaseObject(mTechnique);
        ReleaseObject(mEffect);
        ReleaseObject(mInputLayout);
        ReleaseObject(mVertexBuffer);
    }

    void TriangleDemo::Initialize()
    {
        SetCurrentDirectory(Utility::ExecutableDirectory().c_str());

        // Compile the shader
        UINT shaderFlags = 0;

#if defined( DEBUG ) || defined( _DEBUG )
    shaderFlags |= D3DCOMPILE_DEBUG;
    shaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

        ID3D10Blob* compiledShader = nullptr;
        ID3D10Blob* errorMessages = nullptr;
        HRESULT hr = D3DCompileFromFile(L"Content\Effects\
BasicEffect.fx"
,nullptr, nullptr, nullptr, "fx_5_0", shaderFlags, 0,
&compiledShader,
&errorMessages);
        if (errorMessages != nullptr)
        {
            GameException ex((char*)errorMessages->GetBufferPointer(),
hr);
            ReleaseObject(errorMessages);

            throw ex;
        }

        if (FAILED(hr))
        {
            throw GameException("D3DX11CompileFromFile() failed.", hr);
        }

        // Create an effect object from the compiled shader
        hr = D3DX11CreateEffectFromMemory(compiledShader->
GetBufferPointer(),
compiledShader->GetBufferSize(), 0, mGame->Direct3DDevice(), &mEffect);
        if (FAILED(hr))
        {
            throw GameException("D3DX11CreateEffectFromMemory()
failed."
, hr);
        }

        ReleaseObject(compiledShader);

        // Look up the technique, pass, and WVP variable from the
effect

        mTechnique = mEffect->GetTechniqueByName("main11");
        if (mTechnique == nullptr)
        {
            throw GameException("ID3DX11Effect::GetTechniqueByName()
could not find the specified technique."
, hr);
        }

        mPass = mTechnique->GetPassByName("p0");
        if (mPass == nullptr)
        {
            throw GameException("ID3DX11EffectTechnique::GetPassBy
Name() could not find the specified pass."
, hr);
        }

        ID3DX11EffectVariable* variable = mEffect->GetVariableByName
("WorldViewProjection");
        if (variable == nullptr)
        {
            throw GameException("ID3DX11Effect::GetVariableByName()
could not find the specified variable."
, hr);
        }

        mWvpVariable = variable->AsMatrix();
        if (mWvpVariable->IsValid() == false)
        {
            throw GameException("Invalid effect variable cast.");
        }

        // Create the input layout
        D3DX11_PASS_DESC passDesc;
        mPass->GetDesc(&passDesc);

        D3D11_INPUT_ELEMENT_DESC inputElementDescriptions[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "COLOR",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0,
D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
        };

        if (FAILED(hr = mGame->Direct3DDevice()->
CreateInputLayout(inputElementDescriptions,
ARRAYSIZE(inputElementDescriptions), passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &mInputLayout)))
        {
            throw GameException("ID3D11Device::CreateInputLayout()
failed."
, hr);
        }

        // Create the vertex buffer
        BasicEffectVertex vertices[] =
        {
            BasicEffectVertex(XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Red))),
            BasicEffectVertex(XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Green))),
            BasicEffectVertex(XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Blue)))
        };

        D3D11_BUFFER_DESC vertexBufferDesc;
        ZeroMemory(&vertexBufferDesc, sizeof(vertexBufferDesc));
        vertexBufferDesc.ByteWidth = sizeof(BasicEffectVertex) *
ARRAYSIZE(vertices);
        vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
        vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

        D3D11_SUBRESOURCE_DATA vertexSubResourceData;
        ZeroMemory(&vertexSubResourceData, sizeof
(vertexSubResourceData));
        vertexSubResourceData.pSysMem = vertices;
        if (FAILED(mGame->Direct3DDevice()->CreateBuffer
(&vertexBufferDesc, &vertexSubResourceData, &mVertexBuffer)))
        {
            throw GameException("ID3D11Device::CreateBuffer()
failed."
);
        }
    }

    void TriangleDemo::Draw(const GameTime& gameTime)
    {
        ID3D11DeviceContext* direct3DDeviceContext =
mGame->Direct3DDeviceContext();
        direct3DDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_
TOPOLOGY_TRIANGLELIST);
        direct3DDeviceContext->IASetInputLayout(mInputLayout);

        UINT stride = sizeof(BasicEffectVertex);
        UINT offset = 0;
        direct3DDeviceContext->IASetVertexBuffers(0, 1, &mVertexBuffer,
&stride, &offset);

        XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);
        XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() *
mCamera->ProjectionMatrix();
        mWvpVariable->SetMatrix(reinterpret_cast<const float*>(&wvp));

        mPass->Apply(0, direct3DDeviceContext);

        direct3DDeviceContext->Draw(3, 0);
    }
}


Figure 14.1 shows the output of the TriangleDemo component. Note that you can maneuver your camera with the mouse and keyboard and even view the back of the triangle (because the BasicEffect.fx file disabled backface culling).

Image

Figure 14.1 The output of the triangle demo.

Spicing Things Up

Let’s add some interest to this demo by spinning the triangle around the z-axis. With all the infrastructure you’ve built, this is incredibly simple to do. First, add a class member called mAngle (of type float) to your TriangleDemo class, and initialize it to zero in the constructor. Then add an override for the DrawableGameComponent::Update() method to your class declaration and drop in the method’s implementation from Listing 14.13.

Listing 14.13 The TriangleDemo::Update() Method


void TriangleDemo::Update(const GameTime& gameTime)
{
    mAngle += XM_PI * static_cast<float>(gameTime.ElapsedGameTime());
    XMStoreFloat4x4(&mWorldMatrix, XMMatrixRotationZ(mAngle));
}


This code increments the mAngle member each frame (at a rotation rate of 180 degrees per second). This angle is used to construct a rotation matrix around the z-axis, which is stored in the world matrix member. Remember that the world matrix is concatenated with the camera’s view and projection matrices and is sent to the GPU in the TriangleDemo::Draw() method. That’s all there is to it! If you run your application now, you’ll see a rotating triangle.

An Indexed Cube

Let’s finish off this chapter by creating another demo, this time for rendering a 3D cube using an index buffer. You can duplicate your TriangleDemo to create a CubeDemo class with one addition: an mIndexBuffer class member of type ID3D11Buffer pointer. This will store the array of indices into the vertex buffer.

Recall the discussion of index buffers from Chapter 1, “Introducing DirectX.” Index buffers enable you to eliminate duplicate vertices in the vertex buffer. A cube has six faces, and each cube face can be decomposed into two triangles. Without an index buffer, you’d need 36 vertices to describe the cube (6 faces * 2 triangles/face * 3 vertices/triangle). With an index buffer, you need to store only the unique vertices of the cube: eight total. Then you can build an index buffer with 36 indices.

Building an index buffer follows the same steps as for a vertex buffer: You configure a D3D11_BUFFER_DESC structure to describe the buffer and then populate a D3D11_SUBRESOURCE_DATA structure to fill the buffer with initial data. With these two structures, you invoke the ID3D11Device::CreateBuffer() method. One difference is that you specify D3D_BIND_INDEX_BUFFER for the D3D11_BUFFER_DESC.BindFlags member; another is that the initial data is an array of unsigned integers instead of vertex structures.

Listing 14.14 presents the code for initializing both the vertex and index buffers for the cube demo. This code should reside in your CubeDemo::Initialize() method.

Listing 14.14 Creating the Vertex and Index Buffers for a Cube


BasicEffectVertex vertices[] =
{
    BasicEffectVertex(XMFLOAT4(-1.0f, +1.0f, -1.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Green))),
    BasicEffectVertex(XMFLOAT4(+1.0f, +1.0f, -1.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Yellow))),
    BasicEffectVertex(XMFLOAT4(+1.0f, +1.0f, +1.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::White))),
    BasicEffectVertex(XMFLOAT4(-1.0f, +1.0f, +1.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::BlueGreen))),

    BasicEffectVertex(XMFLOAT4(-1.0f, -1.0f, +1.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Blue))),
    BasicEffectVertex(XMFLOAT4(+1.0f, -1.0f, +1.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Purple))),
    BasicEffectVertex(XMFLOAT4(+1.0f, -1.0f, -1.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Red))),
    BasicEffectVertex(XMFLOAT4(-1.0f, -1.0f, -1.0f, 1.0f),
XMFLOAT4(reinterpret_cast<const float*>(&ColorHelper::Black)))
};

D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory(&vertexBufferDesc, sizeof(vertexBufferDesc));
vertexBufferDesc.ByteWidth = sizeof(BasicEffectVertex) *
ARRAYSIZE(vertices);
vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

D3D11_SUBRESOURCE_DATA vertexSubResourceData;
ZeroMemory(&vertexSubResourceData, sizeof(vertexSubResourceData));
vertexSubResourceData.pSysMem = vertices;
if (FAILED(mGame->Direct3DDevice()->CreateBuffer(&vertexBufferDesc,
&vertexSubResourceData, &mVertexBuffer)))
{
    throw GameException("ID3D11Device::CreateBuffer() failed.");
}

UINT indices[] =
{
    0, 1, 2,
    0, 2, 3,

    4, 5, 6,
    4, 6, 7,

    3, 2, 5,
    3, 5, 4,

    2, 1, 6,
    2, 6, 5,

    1, 7, 6,
    1, 0, 7,

    0, 3, 4,
    0, 4, 7
};

D3D11_BUFFER_DESC indexBufferDesc;
ZeroMemory(&indexBufferDesc, sizeof(indexBufferDesc));
indexBufferDesc.ByteWidth = sizeof(UINT) * 36;
indexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;

D3D11_SUBRESOURCE_DATA indexSubResourceData;
ZeroMemory(&indexSubResourceData, sizeof(indexSubResourceData));
indexSubResourceData.pSysMem = indices;
if (FAILED(mGame->Direct3DDevice()->CreateBuffer(&indexBufferDesc,
&indexSubResourceData, &mIndexBuffer)))
{
    throw GameException("ID3D11Device::CreateBuffer() failed.");
}


The code in Listing 14.14 creates a vertex buffer from 8 vertices, each with a different color, and an index buffer for the 12 triangles (2 per face) of the cube. Drawing with an index buffer is only slightly different than drawing without one. First, you must bind the index buffer to the input-assembler stage with a call to ID3D11DeviceContext::IASetIndexBuffer(). The prototype and parameters are listed here:

void IASetIndexBuffer(
            ID3D11Buffer *pIndexBuffer,
            DXGI_FORMAT Format,
            UINT Offset);

Image pIndexBuffer: The index buffer to bind.

Image Format: The format of the data in the index buffer. Only two possible formats exist: DXGI_FORMAT_R16_UINT and DXGI_FORMAT_R32_UINT, 16-bit and 32-bit unsigned integers, respectively.

Image Offset: The number of bytes to skip before processing indices.

Note that, unlike ID3D11DeviceContext::IASetVertexBuffers(), you bind only a single index buffer to the input-assembler stage. Aside from binding the index buffer, the only other difference is in the specific draw call. You render indexed primitives through the ID3DDevice-Context::DrawIndexed() method; its prototype and parameters are listed next:

void DrawIndexed(
    UINT IndexCount,
    UINT StartIndexLocation,
    INT BaseVertexLocation);

Image IndexCount: The number of indices to render

Image StartIndexLocation: The location of the first index to process

Image BaseVertexLocation: A value to add to each index before it is used to look up a vertex from the vertex buffer

The last parameter of the ID3DDeviceContext::DrawIndexed() method needs some additional explanation. Overhead is present for switching out vertex and index buffers, so you might consider sharing a vertex buffer between objects. In practice, this means concatenating the vertex buffers. But what happens to the associated index buffers? Each index buffer refers to its own local vertex buffer. For example, although two objects will likely have different vertices, the first ten vertices in one vertex buffer will have the same indices (0 to 9) as the first ten vertices of another buffer. When you concatenate the index buffers, their indices will be pointing to incorrect vertices without compensating with the BaseVertexLocation parameter. If you aren’t using shared vertex and index buffers, this value will be 0.

Listing 14.15 presents the CubeDemo::Draw() method and demonstrates the ID3D11DeviceContext::IASetIndexBuffer() and ID3D11DeviceContext::DrawIndexed() calls.

Listing 14.15 Drawing an Indexed Cube


void CubeDemo::Draw(const GameTime& gameTime)
{
    ID3D11DeviceContext* direct3DDeviceContext =
mGame->Direct3DDeviceContext();
    direct3DDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_
TOPOLOGY_TRIANGLELIST);
    direct3DDeviceContext->IASetInputLayout(mInputLayout);

    UINT stride = sizeof(BasicEffectVertex);
    UINT offset = 0;
    direct3DDeviceContext->IASetVertexBuffers(0, 1, &mVertexBuffer,
&stride, &offset);
    direct3DDeviceContext->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_
R32_UINT, 0);

    XMMATRIX worldMatrix = XMLoadFloat4x4(&mWorldMatrix);
    XMMATRIX wvp = worldMatrix * mCamera->ViewMatrix() *
mCamera->ProjectionMatrix();
    mWvpVariable->SetMatrix(reinterpret_cast<const float*>(&wvp));

    mPass->Apply(0, direct3DDeviceContext);

    direct3DDeviceContext->DrawIndexed(36, 0, 0);
}


To use your CubeDemo, you’ll likely want to disable the TriangleDemo (they’ll happily coexist, but the triangle will be rendered inside the cube unless you apply a translation transform). Also, if you are using the FpsComponent and you run the application now, you’ll see some strange output, like the left side of Figure 14.2.

Image

Figure 14.2 The cube demo output with the frame rate component (left) and without it (right).

A depth buffer issue occurs that’s only now expressed because this is the first time you’ve rendered triangles that occlude each other. If you remove the frame rate component, your cube will render correctly (the right-side image of Figure 14.2). The problem stems from the SpriteBatch system that the frame rate component uses; it changes some render states that you need to address.

A Render State Helper Class

Recall the rasterizer state and blend state objects you worked with when authoring shaders. These have C++ counterparts of type ID3D11RasterizerState and ID3D11BlendState, and there is also a ID3D11DepthStencilState interface. Sometimes (such as in the scenario you just encountered with the cube demo) you’ll want to get and set these render states. For example, you could capture these states just before drawing 2D objects with the SpriteBatch class and then restore them after the sprite rendering is complete. Following is the list of getter and setter methods for each of the three render state objects:

ID3D11DeviceContext::RSGetState()
ID3D11DeviceContext::OMGetBlendState()
ID3D11DeviceContext::OMGetDepthStencilState()

ID3D11DeviceContext::RSSetState()
ID3D11DeviceContext::OMSetBlendState()
ID3D11DeviceContext::OMSetDepthStencilState()

These methods all follow the same pattern: You get the current state of the Direct3D pipeline into an object with the corresponding interface, and you can set such an object back to update the pipeline. If you set a state to NULL, you update the pipeline to its default state. Instead of listing the prototypes and parameters for each of these methods, Listings 14.16 and 14.17 present a RenderStateHelper class that provides functionality to save and restore these states individually or as a group. You can use this class to restore the render states after drawing the frame rate component’s text to the screen.

Listing 14.16 The RenderStateHelper.h File


#pragma once

#include "Common.h"

namespace Library
{
    class Game;

    class RenderStateHelper
    {
    public:
        RenderStateHelper(Game& game);
        ~RenderStateHelper();

        static void ResetAll(ID3D11DeviceContext* deviceContext);

        ID3D11RasterizerState* RasterizerState();
        ID3D11BlendState* BlendState();
        ID3D11DepthStencilState* DepthStencilState();

        void SaveRasterizerState();
        void RestoreRasterizerState() const;

        void SaveBlendState();
        void RestoreBlendState() const;

        void SaveDepthStencilState();
        void RestoreDepthStencilState() const;

        void SaveAll();
        void RestoreAll() const;

    private:
        RenderStateHelper(const RenderStateHelper& rhs);
        RenderStateHelper& operator=(const RenderStateHelper& rhs);

        Game& mGame;

        ID3D11RasterizerState* mRasterizerState;
        ID3D11BlendState* mBlendState;
        FLOAT* mBlendFactor;
        UINT mSampleMask;
        ID3D11DepthStencilState* mDepthStencilState;
        UINT mStencilRef;
    };
}


Listing 14.17 The RenderStateHelper.cpp File


#include "RenderStateHelper.h"
#include "Game.h"

namespace Library
{
    RenderStateHelper::RenderStateHelper(Game& game)
        : mGame(game), mRasterizerState(nullptr), mBlendState(nullptr),
          mBlendFactor(new FLOAT[4]), mSampleMask(UINT_MAX),
          mDepthStencilState(nullptr), mStencilRef(UINT_MAX)
    {
    }

    RenderStateHelper::~RenderStateHelper()
    {
        ReleaseObject(mRasterizerState);
        ReleaseObject(mBlendState);
        ReleaseObject(mDepthStencilState);
        DeleteObjects(mBlendFactor);
    }

    void RenderStateHelper::ResetAll(ID3D11DeviceContext*
deviceContext)
    {
        deviceContext->RSSetState(nullptr);
        deviceContext->OMSetBlendState(nullptr, nullptr, UINT_MAX);
        deviceContext->OMSetDepthStencilState(nullptr, UINT_MAX);
    }

    void RenderStateHelper::SaveRasterizerState()
    {
        ReleaseObject(mRasterizerState);
        mGame.Direct3DDeviceContext()->RSGetState(&mRasterizerState);
    }

    void RenderStateHelper::RestoreRasterizerState() const
    {
        mGame.Direct3DDeviceContext()->RSSetState(mRasterizerState);
    }

    void RenderStateHelper::SaveBlendState()
    {
        ReleaseObject(mBlendState);
        mGame.Direct3DDeviceContext()->OMGetBlendState(&mBlendState, mBlendFactor, &mSampleMask);
    }

    void RenderStateHelper::RestoreBlendState() const
    {
        mGame.Direct3DDeviceContext()->OMSetBlendState(mBlendState, mBlendFactor, mSampleMask);
    }

    void RenderStateHelper::SaveDepthStencilState()
    {
        ReleaseObject(mDepthStencilState);
        mGame.Direct3DDeviceContext()->OMGetDepthStencilState
(&mDepthStencilState, &mStencilRef);
    }

    void RenderStateHelper::RestoreDepthStencilState() const
    {
        mGame.Direct3DDeviceContext()->OMSetDepthStencilState
(mDepthStencilState, mStencilRef);
    }

    void RenderStateHelper::SaveAll()
    {
        SaveRasterizerState();
        SaveBlendState();
        SaveDepthStencilState();
    }

    void RenderStateHelper::RestoreAll() const
    {
        RestoreRasterizerState();
        RestoreBlendState();
        RestoreDepthStencilState();
    }
}


You can integrate this class directly into the FpsComponent class, within the CubeDemo class, or within the RenderingGame class. If you integrate it within the RenderingGame class, you’ll want to pull the FpsComponent out of the components container and manually draw it with calls such as the following:

mRenderStateHelper->SaveAll();
mFpsComponent->Draw(gameTime);
mRenderStateHelper->RestoreAll();

Such code guarantees that the render states, changed within the frame rate component’s Draw() method, don’t affect anything else. See the book’s companion site for a full code listing of the cube demo.

Summary

In this chapter, you completed your first full 3D rendering applications! You examined the API calls for compiling and using effects, creating input layouts, and binding vertex and index buffers to the graphics pipeline. You learned about indexed and nonindexed draw calls and how to save and restore render states. In short, you have completed the foundation on which your remaining investigation of Direct3D will stand.

In the next chapter, you begin loading 3D models and develop a more sophisticated material system to accommodate the library of shaders you have developed.

Exercises

1. Apply transformations to either the triangle or the cube to view them simultaneously (their untransformed vertices place the triangle inside the cube and, thus, out of view of the camera).

2. Attach the keyboard to the cube to translate or rotate it (for example, you can use the arrow keys or the number pad).

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

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