DirectX 11.1

Stephen Marz (28 May 2013)

Introduction

As of the writing of this introduction, DirectX 11.1 is only available for Windows 8. The setup and API is mostly the same as DirectX 11. However, there are newly introduced interfaces that require some special consideration. These interfaces were added to support Windows 8 mobile devices (don't quote me on that, it is just what it seems).

Programming

Windows API Programming
// DirectX uses Windows' windows (I don't have a better way to say that).
// WIN32_LEAN_AND_MEAN prevents the Windows.h include from including many extra things we don't need.
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")

// WndProc stands for Window Procedure. This will be the call back when a message (event) is received through our created window (keys, mouse, quit, etc)
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_CLOSE: // This means the X button was clicked on the window's frame
            PostQuitMessage(0); // Post WM_QUIT with return value 0 (which goes into wParam)
        return 0; // Make sure you return here so we don't handle the message twice (with DefWindowProc())
        case WM_KEYDOWN:
            if (wParam == VK_ESCAPE) { // If the user hits the ESCAPE key, quit
                PostQuitMessage(0);
            }
        return 0;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam); // If we didn't catch it, give it to Windows for default operation
}
// The entry point to a windows program is WinMain with the qualifier WINAPI (which just sets the ABI calling convention to __stdcall)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdShow, BOOL nCmdShow) {
    HWND hwnd; // HWND means Handle-of-a-Window
    MSG msg; // The message loop populates a MSG structure
    WNDCLASSEX wc; // To define the behavior of a window, we define a class to attach it to

    ZeroMemory(&wc, sizeof(wc));
    wc.cbSize = sizeof(wc); // This is required to tell Windows the length of the structure
    wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND+1;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW); // The NULL parameter is telling LoadCursor to load IDC_ARROW from Windows itself (we can specify a module here if we wanted to).
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WndProc; // Wnd Proc is the call back procedure when a message is received (we will define it later)
    wc.lpszClassName = L"MY_CLASS"; // This can be nearly anything (in widechar) as long as you match this when you're creating the window
    wc.style = CS_HREDRAW | CS_VREDRAW;

    RegisterClassEx(&wc);
    //Arguments to CreateWindow: Class Name, Window Title, Window Style, X, Y, Width, Height, Parent Window, Menu Handle, Instance, Extra Parameter)
    hwnd = CreateWindow(L"MY_CLASS", L"DX11.1 Render Output Window", WS_OVERLAPPEDWINDOW, 15, 15, 1200, 1000, NULL, NULL, hInstance, NULL);
    ShowWindow(hwnd, SW_SHOW);
    DirectX 11.1 Initialization Code Goes Here (or a function call to the initialization code)
    do {
        // PeekMessage is like GetMessage except it does not block (thus the need to sleep at the bottom)
        //PM_REMOVE means that it will remove the message from the queue and store it in &msg

        if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
                break;
            }
            TranslateMessage(&msg);
            // DispatchMessage() sends the message to the WndProc
            DispatchMessage(&msg);
        } else {
            // If we're here, no more messages need handling, so add the Render function call here
            Sleep(2); // This is simplistic and does not hold a constant framerate, this just gives the CPU a rest
        }
    } while(true);
    DestroyWindow(hwnd); // This isn't necessary, but it shows you can actually destroy a window
    UnregisterClass(L"MY_CLASS", hInstance); // Same as above
    return (int)msg.wParam; // The return code is typically in the W paramter of the WM_QUIT message
}
Enumerating Adapters and Creating the Device, Context, and Swap Chain
// To enumerate adapters, we will use the DXGI 1.2 factory, create the device, and then use that to create the swap chain
HRESULT result;
DXGI_SWAP_CHAIN_DESC1 swapChainDesc;
D3D_FEATURE_LEVEL afl, fl[] = { D3D_FEATURE_LEVEL_11_1 }; // We're requesting just DirectX 11.1, you may specify others as fall-backs

//If you've used DirectX 11, you'll notice this swap chain descriptor is much different (and it added Stereo)
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 8 bits for 4 channels (RGBA), normalized, and unsigned
swapChainDesc.Stereo = FALSE; // Windows defines FALSE and TRUE. It is best not to mix these with true/false. Choose one and stick with it. DX11 likes FALSE/TRUE.
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // Means this swap chain will be used as output
swapChainDesc.BufferCount = 1; // Just requesting 1 buffer

IDXGIFactory2 *dxgiFactory;
IDXGIAdapter2 *dxgiAdapter;
vector<IDXGIAdapter2*> adapterList;

hresult = CreateDXGIFactory1(__uuidof(IDXGIFactory2), (void**)&dxgiFactory);
int i = 0;
while (dxgiFactory->EnumAdapters1(i++, (IDXGIAdapter1**)&dxgiAdapter) != DXGI_ERROR_NOT_FOUND)
{
   adapterList.push_back(dxgiAdapter);
}
ID3D11Device *oldDevice;
ID3D11DeviceContext *oldContext;
/* The reason we need oldDevice and oldContext is that there is no direct way (that I know of) to create a Device1 and DeviceContext1. This method will use the QueryInterface() function to extract from the old device, the new format */
hresult = D3D11CreateDevice(
    adapterList[0], // You can forego all of the enumeration above by putting NULL here and specifiy the next argument.
    D3D_DRIVER_TYPE_UNKNOWN, // If you specify the adapter, this must be UNKNOWN, otherwise specify what you want it to be (HARDWARE/SOFTWARE/WARP)
    NULL,
    0,
    fl,
    ARRAYSIZE(fl),
    D3D11_SDK_VERSION,
    &oldDevice,
    &afl,
    &oldContext
);
if (FAILED(result)) {
    return false; // The device couldn't be created. This could mean improper arguments, or no device that supports DX11.1
}
//Now we need to translate oldDevice, oldContext into newDevice, newContext
ID3D11Device1 *newDevice;
ID3D11DeviceContext1 *newContext;
hresult = oldDevice->QueryInterface(__uuidof(ID3D11Device1), (void**)&newDevice);
if (FAILED(hresult)) {
    return false; // Could not get the device from old device
}
hresult = oldContext->QueryInterface(__uuidof(ID3D11DeviceContext1), (void**)&newContext);
if (FAILED(hresult)) {
    return false; // Could not get the context from old context
}
// DirectX11 and 11.1 hold references (unlike 10 and 9) so you can release them when YOU'RE done with them
oldDevice->Release();
oldContext->Release();
//Now you need to create the swap chain from the device we selected
IDXGISwapChain1 *swapChain;
hresult = dxgiFactory->CreateSwapChainForHwnd(newDevice, hwnd, &swapChainDesc, NULL, NULL, &swapChain);
//Notice hwnd is undefined, we will cover this when we create a Windows 8 window
if (FAILED(hresult)) {
    return false; // Could not create a swap chain (could be swapChainDesc has erroneous parameters or you didn't ZeroMemory)
}
dxgiFactory->Release();
for (i = 0;i < adapterList.size();i++) {
    adapterList[i]->Release();
}
// Now you have a device, context, and swap chain, everything you need to start DirectX graphics programming
Creating The Default Vertex and Pixel Shader (HLSL)
// I am using the same defshaders.hlsl file to compile both the pixel and vertex shader
// --- VERTEX SHADER ---
// defshaders.hlsl - Default VERTEX shader in HLSL
cbuffer CB0 : register(cb0) { // Register cb0 means CONSTANT-BUFFER slot 0
    matrix<float,4,4> transformMatrix;
}
struct a2v {
    // a2v means Application to Vertex Shader
    // The colon (:) POSITION0 are called SEMANTICS
    float3 pos : POSITION0; // Layout position slot 0
    float3 clr : COLOR0; // Color slot 0
};
struct v2p {
    // v2p means Vertex Shader to Pixel Shader
    // SV_* SEMANTICS are called System-Value semantics and directly correspond with the host application
    float4 pos : SV_POSITION; // Renderer's position
    float4 clr : COLOR0; // Color is not an SV_ because this passes to the pixel shader
};
// v2p is return value (as a structure), input is a2v called IN
v2p VS_Main(in a2v IN) {
    v2p OUT; // The OUTPUT structure to the pixel shader
    OUT.pos = mul(transformMatrix, float4(IN.pos, 1.0f)); // Apply a transform matrix to a 4-vector position (1.0f is the w scale)
    OUT.clr = float4(IN.clr, 1.0f); //float4 is type-casting in HLSL, to go to a system-value semantic, these all need to be 4-vectors, the color will then be RGBA
    return OUT;
}

// --- PIXEL SHADER ---

//Below SV_TARGET goes to the RENDER TARGET, the position was already figured out with the SV_POSITION semantic
float4 PS_Main(in v2p IN) : SV_TARGET {
    return IN.clr; // Simply return the color
}
Compiling The Shader and Laying Out Shader Data
ID3D11InputLayout *inputLayout;
// The input element descriptor describes the SEMANTICS, SLOT, and the size and stride of the data passed to the shader
D3D11_INPUT_ELEMENT_DESC inputElementDesc[] = {
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, sizeof(float)*3, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
/* Looking above, POSITION is the semantic, 0 is the slot, DXGI_FORMAT_R32G32B32_FLOAT means it is a 3-vector float (32-bits per element), D3D11_INPUT_
PER_VERTEX_DATA means there is a POSITION for each vertex. The sizeof(float)*3 for the COLOR means that the color is exactly three floats past the vertex data (inline). */

ID3D11VertexShader *vertexShader;
ID3DBlob *shaderBlob;
ID3DBlob *errorBlob;
hresult = D3DCompileFromFile(
    L"defshader.hlsl", // Windows uses UNICODE for filenames, thus the L is necessary
    NULL, // This is an array of definition macros
    NULL, // This is an array of includes to the shader
    "VS_Main", // Remember above we called the entry point to the vertex shader VS_Main
    "vs_5_0", // This is the profile used to compile the shader vs_5_0 means vertex shader for DirectX 11
    0, // Flags 1
    0, // Flags 2
    &shaderBlob,
    &errorBlob
);
if (FAILED(hresult)) {
    errorBlob->Release(); // We're going to ignore the error message, but it still needs to be freed
    return false; // You can check errorBlob to see what went wrong
}
hresult = newDevice->CreateVertexShader(shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), 0, &vertexShader);
// Now that we have the vertex shader, we can assosicate the layout of that shader with the input layout
hresult = newDevice->CreateInputLayout(inputElementDesc, ARRAYSIZE(inputElementDesc), shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize, &inputLayout);
shaderBlob->Release();
ID3D11PixelShader *pixelShader;
hresult = D3DCompileFromFile(
    L"defshader.hlsl", // Windows uses UNICODE for filenames, thus the L is necessary
    NULL, // This is an array of definition macros
    NULL, // This is an array of includes to the shader
    "PS_Main", // Remember above we called the entry point to the pixel shader PS_Main
    "ps_5_0", // This is the profile used to compile the shader ps_5_0 means pixel shader for DirectX 11
    0, // Flags 1
    0, // Flags 2
    &shaderBlob,
    &errorBlob
);
if (FAILED(hresult)) {
    errorBlob->Release();
    return false;
}
hresult = newDevice->CreatePixelShader(shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), 0, &pixelShader);
shaderBlob->Release();
// Now we have the shaders, so set the input layout of the shaders and set the actual shaders
newContext->IASetInputLayout(inputLayout); // IA stands for Input-Assembler
inputLayout->Release(); // DX11 holds a reference when you do IASetInputLayout (unlike 10 and 10.1), so we can free it here
newContext->VSSetShader(vertexShader, 0, 0); // VS means vertex shader, we set it to slot 0
newContext->PSSetShader(pixelShader, 0, 0); // PS means pixel shader, we set it to slot 0 as well
// If you're working with an engine that switches between shaders, it is best to keep your own shader reference. Since we're not, we can free ours
vertexShader->Release();
pixelShader->Release();
Constant Buffer
// A constant buffer is a buffer that stays constant through the rendering pipeline. It still can change, but not in the middle of the pipeline
D3D11_BUFFER_DESC matrixBufferDesc;
ID3D11Buffer *matrixBuffer;

ZeroMemory(&matrixBufferDesc, sizeof(matrixBufferDesc));
matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; // This is a constant buffer
matrixBufferDesc.ByteWidth = sizeof(float)*16; // we're going to use a 16-float array as our matrix (row major)
matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // Allow the CPU to write to this buffer, notice it does not say READ
matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; // Dynamic means we can map to this resource and update it

newDevice->CreateBuffer(&matrixBufferDesc, 0, &matrixBuffer);
newContext->VSSetConstantBuffers(0, 1, &matrixBuffer); // VS means we are setting this ONLY for the vertex shader. The pixel shader can't access this buffer with this one command (see PSSetConstantBuffers)
// We aren't going to free the matrix buffer because we may need to update it with a transformation (rotate, scale, translate, shear, etc).
Updating The Constant Buffer
// Since we declared the matrix buffer as DYNAMIC usage, we can use the Map() function to update it
float newMatrix[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f };
D3D11_MAPPED_SUBRESOURCE msr;

newContext->Map(matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &msr); // D3D11_MAP_WRITE_DISCARD means that it will discard any updates not flushed prior to the next render
memcpy(msr.pData, newMatrix, sizeof(float)*16); // pData of the D3D11_MAPPED_SUBRESOURCE structure means pointer to Data
// You can also access msr.pData like an array:
if (0) {
    float *matrixData = reinterpret_cast<float*>(msr.pData);
    matrixData[0] = 1.0f;
    matrixData[1] = 2.0f;
    // And so on
}
newContext->Unmap(matrixBuffer, 0); // ALWAYS unmap when you're done, otherwise this resource can't be accessed
Creating The Vertex Buffer
// A vertex buffer will be stored in the GPU for fast access to the rendering pipeline
const float vertices[] = {
   // x,     y,     z,     r,   g,   b
    -0.5f, 0.0f, 2.0f, 1.0f, 1.0f, 0.0f,
    0.0f, 0.5f, 3.0f, 1.0f, 0.0f, 1.0f,
    0.5f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f,

    0.0f, -0.5f, 3.0f, 0.3f, 0.2f, 0.7f,
    -0.5f, 0.0f, 2.0f, 0.5f, 0.7f, 0.3f
};

D3D11_BUFFER_DESC bufferDesc;
D3D11_SUBRESOURCE_DATA bufferData;
ID3D11Buffer *vertexBuffer;

ZeroMemory(&bufferDesc, sizeof(bufferDesc));
ZeroMemory(&bufferData, sizeof(bufferData));

bufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bufferDesc.ByteWidth = sizeof(float)*ARRAYSIZE(vertices);
bufferDesc.StructureByteStride = sizeof(float)*6; // There are six elements per vertex (x, y, z, r, g, b)
bufferDesc.Usage = D3D11_USAGE_DEFAULT;

bufferData.pSysMem = vertices;
newDevice->CreateBuffer(&bufferDesc, &bufferData, &vertexBuffer);
const UINT strides[] = { sizeof(float) * 6 };
const UINT offsets[] = { 0 };
newContext->IASetVertexBuffers(0, 1, &vertexBuffer, strides, offsets);
vertexBuffer->Release(); // It is typically better to recreate the buffer rather than update it, so we release it here
Resizing The Render Window
// Assume newWidth and newHeight are passed into here with the new resized window's width and height
// When the window changes size, we have to resize the size of the render screen
ID3D11RenderTargetView *view;
ID3D11Texture2D *viewBuffer;
D3D11_VIEWPORT vp;

newContext->OMSetRenderTargets(0, 0, 0); // Remove all render targets (OM means Output-Merger)
swapChain->ResizeBuffers(1, newWidth, newHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&viewBuffer);
newDevice->CreateRenderTargetView(viewBuffer, 0, &view);
viewBuffer->Release();
newContext->OMSetRenderTargets(1, &view, 0);

vp.Height = static_cast<FLOAT>(newHeight);
vp.Width = static_cast<FLOAT> (newWidth);
vp.MaxDepth = 1.0f;
vp.MinDepth = 0.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
newContext->RSSetViewports(1, &vp); // RS means Rasterizer-Stage, this sets a box that is "writeable" to the texture
Rendering To The Output Window
FLOAT clearColor[] = { 1.0f, 1.0f, 1.0f, 1.0f }; // Clear the screen with a white background
newContext->ClearRenderTargetView(view, clearColor);
newContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
newContext->Draw(ARRAYSIZE(vertices)/6);
swapChain->Present(0, 0); // Present() flips the offscreen buffer to the onscreen buffer