Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Last active Nov 30, 2021
Embed
What would you like to do?
setting up and using D3D11 in C
// example how to set up D3D11 rendering on Windows in C
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <d3d11.h>
#include <d3dcompiler.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>
#include <stddef.h>
// replace this with your favorite Assert() implementation
#include <intrin.h>
#define Assert(cond) do { if (!(cond)) __debugbreak(); } while (0)
#define AssertHR(hr) Assert(SUCCEEDED(hr))
#pragma comment (lib, "gdi32.lib")
#pragma comment (lib, "user32.lib")
#pragma comment (lib, "dxguid.lib")
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dcompiler.lib")
#define STR2(x) #x
#define STR(x) STR2(x)
static void FatalError(const char* message)
{
MessageBoxA(NULL, message, "Error", MB_ICONEXCLAMATION);
ExitProcess(0);
}
static LRESULT CALLBACK WindowProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(wnd, msg, wparam, lparam);
}
int WINAPI WinMain(HINSTANCE instance, HINSTANCE previnstance, LPSTR cmdline, int cmdshow)
{
// register window class to have custom WindowProc callback
WNDCLASSEXW wc =
{
.cbSize = sizeof(wc),
.lpfnWndProc = WindowProc,
.hInstance = instance,
.hIcon = LoadIcon(NULL, IDI_APPLICATION),
.hCursor = LoadCursor(NULL, IDC_ARROW),
.lpszClassName = L"d3d11_window_class",
};
ATOM atom = RegisterClassExW(&wc);
Assert(atom && "Failed to register window class");
// window properties - width, height and style
int width = CW_USEDEFAULT;
int height = CW_USEDEFAULT;
DWORD exstyle = WS_EX_APPWINDOW | WS_EX_NOREDIRECTIONBITMAP;
DWORD style = WS_OVERLAPPEDWINDOW;
// uncomment in case you want fixed size window
//style &= ~WS_THICKFRAME & ~WS_MAXIMIZEBOX;
//RECT rect = { 0, 0, 1280, 720 };
//AdjustWindowRectEx(&rect, style, FALSE, exstyle);
//width = rect.right - rect.left;
//height = rect.bottom - rect.top;
// create window
HWND window = CreateWindowExW(
exstyle, wc.lpszClassName, L"D3D11 Window", style,
CW_USEDEFAULT, CW_USEDEFAULT, width, height,
NULL, NULL, wc.hInstance, NULL);
Assert(window && "Failed to create window");
HRESULT hr;
IDXGISwapChain* swapChain;
ID3D11Device* device;
ID3D11DeviceContext* context;
// create swap chain, device and context
{
DXGI_SWAP_CHAIN_DESC desc =
{
.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc = { 1, 0 },
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = 2,
.OutputWindow = window,
.Windowed = TRUE,
// use more efficient flip model, available in Windows 10
// if Windows 8 compatibility required, use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
// if Windows 7/Vista compatibility required, use DXGI_SWAP_EFFECT_DISCARD
// NOTE: flip models do not allow MSAA framebuffer, so if you want MSAA then
// you'll need to render offscreen and afterwards resolve to non-MSAA framebuffer
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
};
UINT flags = 0;
#ifndef NDEBUG
flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_0 };
hr = D3D11CreateDeviceAndSwapChain(
NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, levels, _countof(levels),
D3D11_SDK_VERSION, &desc, &swapChain, &device, NULL, &context);
AssertHR(hr);
}
#ifndef NDEBUG
// for debug builds enable debug break on API errors
{
ID3D11InfoQueue* info;
hr = ID3D11Device_QueryInterface(device, &IID_ID3D11InfoQueue, (void**)&info);
AssertHR(hr);
hr = ID3D11InfoQueue_SetBreakOnSeverity(info, D3D11_MESSAGE_SEVERITY_CORRUPTION, TRUE);
AssertHR(hr);
hr = ID3D11InfoQueue_SetBreakOnSeverity(info, D3D11_MESSAGE_SEVERITY_ERROR, TRUE);
AssertHR(hr);
ID3D11InfoQueue_Release(info);
}
#endif
// disable stupid Alt+Enter changing monitor resolution to match window size
{
IDXGIFactory* factory;
hr = IDXGISwapChain_GetParent(swapChain, &IID_IDXGIFactory, (void**)&factory);
AssertHR(hr);
IDXGIFactory_MakeWindowAssociation(factory, window, DXGI_MWA_NO_ALT_ENTER);
AssertHR(hr);
IDXGIFactory_Release(factory);
}
struct Vertex
{
float position[2];
float uv[2];
float color[3];
};
ID3D11Buffer* vbuffer;
{
struct Vertex data[] =
{
{ { -0.00f, +0.75f }, { 25.0f, 50.0f }, { 1, 0, 0 } },
{ { +0.75f, -0.50f }, { 0.0f, 0.0f }, { 0, 1, 0 } },
{ { -0.75f, -0.50f }, { 50.0f, 0.0f }, { 0, 0, 1 } },
};
D3D11_BUFFER_DESC desc =
{
.ByteWidth = sizeof(data),
.Usage = D3D11_USAGE_IMMUTABLE,
.BindFlags = D3D11_BIND_VERTEX_BUFFER,
};
D3D11_SUBRESOURCE_DATA initial = { .pSysMem = data };
hr = ID3D11Device_CreateBuffer(device, &desc, &initial, &vbuffer);
AssertHR(hr);
}
// vertex & pixel shaders for drawing triangle, plus input layout for vertex input
ID3D11InputLayout* layout;
ID3D11VertexShader* vshader;
ID3D11PixelShader* pshader;
{
// these must match vertex shader input layout
D3D11_INPUT_ELEMENT_DESC desc[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(struct Vertex, position), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(struct Vertex, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, offsetof(struct Vertex, color), D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
#if 0
// alternative to hlsl compilation at runtime is to precompile shaders offline
// it improves startup time - no need to parse hlsl files at runtime!
// and it allows to remove runtime dependency on d3dcompiler dll file
// a) save shader source code into "shader.hlsl" file
// b) run hlsl compiler to compile shader, these run compilation with optimizations and without debug info:
// fxc.exe /nologo /T vs_5_0 /E vs /O3 /WX /Zpc /Ges /Fh d3d11_vshader.h /Vn d3d11_vshader /Qstrip_reflect /Qstrip_debug /Qstrip_priv shader.hlsl
// fxc.exe /nologo /T ps_5_0 /E ps /O3 /WX /Zpc /Ges /Fh d3d11_pshader.h /Vn d3d11_pshader /Qstrip_reflect /Qstrip_debug /Qstrip_priv shader.hlsl
// they will save output to d3d11_vshader.h and d3d11_pshader.h files
// c) change #if 0 above to #if 1
// you can also use "/Fo d3d11_*shader.bin" argument to save compiled shader as binary file to store with your assets
// then provide binary data for Create*Shader functions below without need to include shader bytes in C
#include "d3d11_vshader.h"
hr = ID3D11Device_CreateVertexShader(device, d3d11_vshader, sizeof(d3d11_vshader), NULL, &vshader);
AssertHR(hr);
#include "d3d11_pshader.h"
hr = ID3D11Device_CreatePixelShader(device, d3d11_pshader, sizeof(d3d11_pshader), NULL, &pshader);
AssertHR(hr);
hr = ID3D11Device_CreateInputLayout(device, desc, _countof(desc), d3d11_vshader, sizeof(d3d11_vshader), &layout);
AssertHR(hr);
#else
const char hlsl[] =
"#line " STR(__LINE__) " \n\n" // actual line number in this file for nicer error messages
" \n"
"struct VS_INPUT \n"
"{ \n"
" float2 pos : POSITION; \n" // these names must match D3D11_INPUT_ELEMENT_DESC array
" float2 uv : TEXCOORD; \n"
" float3 color : COLOR; \n"
"}; \n"
" \n"
"struct PS_INPUT \n"
"{ \n"
" float4 pos : SV_POSITION; \n" // these names do not matter, except SV_... ones
" float2 uv : TEXCOORD; \n"
" float4 color : COLOR; \n"
"}; \n"
" \n"
"cbuffer cbuffer0 : register(b0) \n" // b0 = constant buffer bound to slot 0
"{ \n"
" float4x2 uTransform; \n"
"} \n"
" \n"
"sampler sampler0 : register(s0); \n" // s0 = sampler bound to slot 0
" \n"
"Texture2D<float4> texture0 : register(t0); \n" // t0 = shader resource bound to slot 0
" \n"
"PS_INPUT vs(VS_INPUT input) \n"
"{ \n"
" float2 pos = mul(uTransform, input.pos).xy; \n"
" PS_INPUT output; \n"
" output.pos = float4(pos, 0.f, 1.f); \n"
" output.uv = input.uv; \n"
" output.color = float4(input.color, 1.f); \n"
" return output; \n"
"} \n"
" \n"
"float4 ps(PS_INPUT input) : SV_TARGET \n"
"{ \n"
" float4 tex = texture0.Sample(sampler0, input.uv); \n"
" return input.color * tex; \n"
"} \n";
;
UINT flags = D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS;
#ifndef NDEBUG
flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
flags |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
#endif
ID3DBlob* error;
ID3DBlob* vblob;
hr = D3DCompile(hlsl, sizeof(hlsl), NULL, NULL, NULL, "vs", "vs_5_0", flags, 0, &vblob, &error);
if (FAILED(hr))
{
const char* message = ID3D10Blob_GetBufferPointer(error);
OutputDebugStringA(message);
Assert(!"Failed to compile vertex shader!");
}
ID3DBlob* pblob;
hr = D3DCompile(hlsl, sizeof(hlsl), NULL, NULL, NULL, "ps", "ps_5_0", flags, 0, &pblob, &error);
if (FAILED(hr))
{
const char* message = ID3D10Blob_GetBufferPointer(error);
OutputDebugStringA(message);
Assert(!"Failed to compile pixel shader!");
}
hr = ID3D11Device_CreateVertexShader(device, ID3D10Blob_GetBufferPointer(vblob), ID3D10Blob_GetBufferSize(vblob), NULL, &vshader);
AssertHR(hr);
hr = ID3D11Device_CreatePixelShader(device, ID3D10Blob_GetBufferPointer(pblob), ID3D10Blob_GetBufferSize(pblob), NULL, &pshader);
AssertHR(hr);
hr = ID3D11Device_CreateInputLayout(device, desc, _countof(desc), ID3D10Blob_GetBufferPointer(vblob), ID3D10Blob_GetBufferSize(vblob), &layout);
AssertHR(hr);
ID3D10Blob_Release(pblob);
ID3D10Blob_Release(vblob);
#endif
}
ID3D11Buffer* ubuffer;
{
D3D11_BUFFER_DESC desc =
{
.ByteWidth = 2 * 4 * sizeof(float),
.Usage = D3D11_USAGE_DYNAMIC,
.BindFlags = D3D11_BIND_CONSTANT_BUFFER,
.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
};
hr = ID3D11Device_CreateBuffer(device, &desc, NULL, &ubuffer);
AssertHR(hr);
}
ID3D11ShaderResourceView* textureView;
{
// checkerboard texture, with 50% transparency on black colors
unsigned int pixels[] =
{
0x80000000, 0xffffffff,
0xffffffff, 0x80000000,
};
UINT width = 2;
UINT height = 2;
D3D11_TEXTURE2D_DESC desc =
{
.Width = width,
.Height = height,
.MipLevels = 1,
.ArraySize = 1,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc = { 1, 0 },
.Usage = D3D11_USAGE_IMMUTABLE,
.BindFlags = D3D11_BIND_SHADER_RESOURCE,
};
D3D11_SUBRESOURCE_DATA data =
{
.pSysMem = pixels,
.SysMemPitch = width * sizeof(unsigned int),
};
ID3D11Texture2D* texture;
hr = ID3D11Device_CreateTexture2D(device, &desc, &data, &texture);
AssertHR(hr);
hr = ID3D11Device_CreateShaderResourceView(device, (ID3D11Resource*)texture, NULL, &textureView);
AssertHR(hr);
ID3D11Texture2D_Release(texture);
}
ID3D11SamplerState* sampler;
{
D3D11_SAMPLER_DESC desc =
{
.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT,
.AddressU = D3D11_TEXTURE_ADDRESS_WRAP,
.AddressV = D3D11_TEXTURE_ADDRESS_WRAP,
.AddressW = D3D11_TEXTURE_ADDRESS_WRAP,
};
hr = ID3D11Device_CreateSamplerState(device, &desc, &sampler);
AssertHR(hr);
}
ID3D11BlendState* blendState;
{
// enable alpha blending
D3D11_BLEND_DESC desc =
{
.RenderTarget[0] =
{
.BlendEnable = TRUE,
.SrcBlend = D3D11_BLEND_SRC_ALPHA,
.DestBlend = D3D11_BLEND_INV_SRC_ALPHA,
.BlendOp = D3D11_BLEND_OP_ADD,
.SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA,
.DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA,
.BlendOpAlpha = D3D11_BLEND_OP_ADD,
.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL,
},
};
hr = ID3D11Device_CreateBlendState(device, &desc, &blendState);
AssertHR(hr);
}
ID3D11RasterizerState* rasterizerState;
{
// disable culling
D3D11_RASTERIZER_DESC desc =
{
.FillMode = D3D11_FILL_SOLID,
.CullMode = D3D11_CULL_NONE,
};
hr = ID3D11Device_CreateRasterizerState(device, &desc, &rasterizerState);
AssertHR(hr);
}
ID3D11DepthStencilState* depthState;
{
// disable depth & stencil test
D3D11_DEPTH_STENCIL_DESC desc =
{
.DepthEnable = FALSE,
.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL,
.DepthFunc = D3D11_COMPARISON_LESS,
.StencilEnable = FALSE,
.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK,
.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK,
// .FrontFace = ...
// .BackFace = ...
};
hr = ID3D11Device_CreateDepthStencilState(device, &desc, &depthState);
AssertHR(hr);
}
ID3D11RenderTargetView* rtView = NULL;
ID3D11DepthStencilView* dsView = NULL;
// show the window
ShowWindow(window, SW_SHOWDEFAULT);
LARGE_INTEGER freq, c1;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&c1);
float angle = 0;
DWORD currentWidth = 0;
DWORD currentHeight = 0;
for (;;)
{
// process all incoming Windows messages
MSG msg;
if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
break;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
continue;
}
// get current window client area size
RECT rect;
GetClientRect(window, &rect);
width = rect.right - rect.left;
height = rect.bottom - rect.top;
// resize swap chain if needed
if (rtView == NULL || width != currentWidth || height != currentHeight)
{
if (rtView)
{
// release old swap chain buffers
ID3D11DeviceContext_ClearState(context);
ID3D11RenderTargetView_Release(rtView);
ID3D11DepthStencilView_Release(dsView);
rtView = NULL;
}
if (width != 0 && height != 0)
{
// resize to new size for non-zero sizes
hr = IDXGISwapChain_ResizeBuffers(swapChain, 0, width, height, DXGI_FORMAT_UNKNOWN, 0);
if (FAILED(hr))
{
FatalError("Failed to resize swap chain!");
}
D3D11_RENDER_TARGET_VIEW_DESC rtDesc =
{
.Format = DXGI_FORMAT_R8G8B8A8_UNORM, // or use DXGI_FORMAT_R8G8B8A8_UNORM_SRGB for storing sRGB
.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D,
};
// create RenderTarget view for new backbuffer texture
ID3D11Texture2D* backbuffer;
hr = IDXGISwapChain_GetBuffer(swapChain, 0, &IID_ID3D11Texture2D, (void**)&backbuffer);
AssertHR(hr);
hr = ID3D11Device_CreateRenderTargetView(device, (ID3D11Resource*)backbuffer, &rtDesc, &rtView);
AssertHR(hr);
ID3D11Texture2D_Release(backbuffer);
D3D11_TEXTURE2D_DESC depthDesc =
{
.Width = width,
.Height = height,
.MipLevels = 1,
.ArraySize = 1,
.Format = DXGI_FORMAT_D24_UNORM_S8_UINT, // or use DXGI_FORMAT_D32_FLOAT if you don't need stencil
.SampleDesc = { 1, 0 },
.Usage = D3D11_USAGE_DEFAULT,
.BindFlags = D3D11_BIND_DEPTH_STENCIL,
};
// create new depth stencil texture & DepthStencil view
ID3D11Texture2D* depth;
hr = ID3D11Device_CreateTexture2D(device, &depthDesc, NULL, &depth);
AssertHR(hr);
hr = ID3D11Device_CreateDepthStencilView(device, (ID3D11Resource*)depth, NULL, &dsView);
AssertHR(hr);
ID3D11Texture2D_Release(depth);
}
currentWidth = width;
currentHeight = height;
}
// can render only if window size is non-zero - we must have backbuffer & RenderTarget view created
if (rtView)
{
LARGE_INTEGER c2;
QueryPerformanceCounter(&c2);
float delta = (float)((double)(c2.QuadPart - c1.QuadPart) / freq.QuadPart);
c1 = c2;
// output viewport covering all client area of window
D3D11_VIEWPORT viewport =
{
.TopLeftX = 0,
.TopLeftY = 0,
.Width = (FLOAT)width,
.Height = (FLOAT)height,
.MinDepth = 0,
.MaxDepth = 1,
};
// clear screen
FLOAT color[] = { 0.392f, 0.584f, 0.929f, 1.f };
ID3D11DeviceContext_ClearRenderTargetView(context, rtView, color);
ID3D11DeviceContext_ClearDepthStencilView(context, dsView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.f, 0);
// setup rotation matrix in uniform
{
angle += delta * 2.0f * (float)M_PI / 20.0f; // full rotation in 20 seconds
angle = fmodf(angle, 2.0f * (float)M_PI);
float aspect = (float)height / width;
float matrix[] =
{
cosf(angle) * aspect, -sinf(angle), 0.f, 0.f,
sinf(angle) * aspect, cosf(angle), 0.f, 0.f,
};
D3D11_MAPPED_SUBRESOURCE mapped;
hr = ID3D11DeviceContext_Map(context, (ID3D11Resource*)ubuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
AssertHR(hr);
memcpy(mapped.pData, matrix, sizeof(matrix));
ID3D11DeviceContext_Unmap(context, (ID3D11Resource*)ubuffer, 0);
}
// Input Assembler
ID3D11DeviceContext_IASetInputLayout(context, layout);
ID3D11DeviceContext_IASetPrimitiveTopology(context, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
UINT stride = sizeof(struct Vertex);
UINT offset = 0;
ID3D11DeviceContext_IASetVertexBuffers(context, 0, 1, &vbuffer, &stride, &offset);
// Vertex Shader
ID3D11DeviceContext_VSSetConstantBuffers(context, 0, 1, &ubuffer);
ID3D11DeviceContext_VSSetShader(context, vshader, NULL, 0);
// Rasterizer Stage
ID3D11DeviceContext_RSSetViewports(context, 1, &viewport);
ID3D11DeviceContext_RSSetState(context, rasterizerState);
// Pixel Shader
ID3D11DeviceContext_PSSetSamplers(context, 0, 1, &sampler);
ID3D11DeviceContext_PSSetShaderResources(context, 0, 1, &textureView);
ID3D11DeviceContext_PSSetShader(context, pshader, NULL, 0);
// Output Merger
ID3D11DeviceContext_OMSetBlendState(context, blendState, NULL, ~0U);
ID3D11DeviceContext_OMSetDepthStencilState(context, depthState, 0);
ID3D11DeviceContext_OMSetRenderTargets(context, 1, &rtView, dsView);
// draw 3 vertices
ID3D11DeviceContext_Draw(context, 3, 0);
}
// change to FALSE to disable vsync
BOOL vsync = TRUE;
hr = IDXGISwapChain_Present(swapChain, vsync ? 1 : 0, 0);
if (hr == DXGI_STATUS_OCCLUDED)
{
// window is minimized, cannot vsync - instead sleep a bit
if (vsync)
{
Sleep(10);
}
}
else if (FAILED(hr))
{
FatalError("Failed to present swap chain! Device lost?");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment