2758 words
14 minutes
Windows DirectX & Graphics Programming: Complete Developer Guide
Windows DirectX & Graphics Programming: Complete Developer Guide
DirectX is Microsoft’s collection of APIs for handling multimedia and graphics tasks on Windows. This guide covers modern DirectX programming, from basic 2D graphics to advanced 3D rendering pipelines.
Why DirectX Programming Matters
- Performance: Hardware-accelerated graphics rendering
- Gaming Industry: Standard for Windows game development
- Multimedia Apps: Professional graphics and video applications
- Real-time Rendering: Interactive 3D applications and simulations
DirectX Architecture Overview
graph TB App[Application Layer] D3D11[Direct3D 11/12] D2D[Direct2D] DW[DirectWrite] DXGI[DXGI - Graphics Infrastructure] Driver[Graphics Driver] Hardware[GPU Hardware]
App --> D3D11 App --> D2D App --> DW D3D11 --> DXGI D2D --> DXGI DXGI --> Driver Driver --> Hardware
1. Direct2D Programming
Basic Direct2D Setup
// Direct2D Application Framework#include <windows.h>#include <d2d1.h>#include <dwrite.h>#include <wincodec.h>#pragma comment(lib, "d2d1.lib")#pragma comment(lib, "dwrite.lib")
class Direct2DApp {private: HWND m_hwnd; ID2D1Factory* m_pD2DFactory; ID2D1HwndRenderTarget* m_pRenderTarget; ID2D1SolidColorBrush* m_pBrush; IDWriteFactory* m_pDWriteFactory; IDWriteTextFormat* m_pTextFormat;
public: Direct2DApp() : m_hwnd(NULL), m_pD2DFactory(NULL), m_pRenderTarget(NULL), m_pBrush(NULL), m_pDWriteFactory(NULL), m_pTextFormat(NULL) {}
~Direct2DApp() { SafeRelease(&m_pD2DFactory); SafeRelease(&m_pRenderTarget); SafeRelease(&m_pBrush); SafeRelease(&m_pDWriteFactory); SafeRelease(&m_pTextFormat); }
// Initialize Direct2D HRESULT Initialize() { HRESULT hr = S_OK;
// Create Direct2D factory hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);
if (SUCCEEDED(hr)) { // Create DirectWrite factory hr = DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(m_pDWriteFactory), reinterpret_cast<IUnknown**>(&m_pDWriteFactory) ); }
if (SUCCEEDED(hr)) { // Create text format hr = m_pDWriteFactory->CreateTextFormat( L"Verdana", NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 50, L"", &m_pTextFormat ); }
return hr; }
// Create render target for window HRESULT CreateDeviceResources(HWND hwnd) { HRESULT hr = S_OK;
if (!m_pRenderTarget) { RECT rc; GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU( rc.right - rc.left, rc.bottom - rc.top );
hr = m_pD2DFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd, size), &m_pRenderTarget );
if (SUCCEEDED(hr)) { hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF(D2D1::ColorF::DarkBlue), &m_pBrush ); } }
return hr; }
// Render scene void OnRender() { HRESULT hr = S_OK;
hr = CreateDeviceResources(m_hwnd);
if (SUCCEEDED(hr)) { m_pRenderTarget->BeginDraw();
// Clear background m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
// Draw shapes DrawShapes();
// Draw text DrawText();
hr = m_pRenderTarget->EndDraw();
if (hr == D2DERR_RECREATE_TARGET) { SafeRelease(&m_pRenderTarget); SafeRelease(&m_pBrush); } } }
private: void DrawShapes() { // Draw rectangle D2D1_RECT_F rectangle = D2D1::RectF(50.0f, 50.0f, 200.0f, 150.0f); m_pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Blue, 1.0f)); m_pRenderTarget->FillRectangle(&rectangle, m_pBrush);
// Draw ellipse D2D1_ELLIPSE ellipse = D2D1::Ellipse(D2D1::Point2F(300.0f, 100.0f), 80.0f, 50.0f); m_pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Red, 1.0f)); m_pRenderTarget->FillEllipse(&ellipse, m_pBrush);
// Draw line m_pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Green, 1.0f)); m_pRenderTarget->DrawLine( D2D1::Point2F(50.0f, 200.0f), D2D1::Point2F(400.0f, 200.0f), m_pBrush, 3.0f ); }
void DrawText() { m_pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black, 1.0f));
static const WCHAR sc_helloWorld[] = L"Hello DirectX!";
m_pRenderTarget->DrawText( sc_helloWorld, ARRAYSIZE(sc_helloWorld) - 1, m_pTextFormat, D2D1::RectF(50.0f, 250.0f, 400.0f, 300.0f), m_pBrush ); }
template<class Interface> inline void SafeRelease(Interface** ppInterfaceToRelease) { if (*ppInterfaceToRelease != NULL) { (*ppInterfaceToRelease)->Release(); (*ppInterfaceToRelease) = NULL; } }};
2. Direct3D 11 Programming
D3D11 Device and Context Setup
// Direct3D 11 Core Implementation#include <d3d11.h>#include <d3dcompiler.h>#include <DirectXMath.h>#pragma comment(lib, "d3d11.lib")#pragma comment(lib, "d3dcompiler.lib")
using namespace DirectX;
class D3D11Application {private: ID3D11Device* m_pd3dDevice; ID3D11DeviceContext* m_pImmediateContext; IDXGISwapChain* m_pSwapChain; ID3D11RenderTargetView* m_pRenderTargetView; ID3D11DepthStencilView* m_pDepthStencilView; ID3D11Buffer* m_pVertexBuffer; ID3D11Buffer* m_pConstantBuffer; ID3D11VertexShader* m_pVertexShader; ID3D11PixelShader* m_pPixelShader; ID3D11InputLayout* m_pVertexLayout;
struct Vertex { XMFLOAT3 Pos; XMFLOAT4 Color; };
struct ConstantBuffer { XMMATRIX mWorld; XMMATRIX mView; XMMATRIX mProjection; };
public: D3D11Application() : m_pd3dDevice(nullptr), m_pImmediateContext(nullptr), m_pSwapChain(nullptr), m_pRenderTargetView(nullptr), m_pDepthStencilView(nullptr) {}
~D3D11Application() { CleanupDevice(); }
// Initialize Direct3D 11 HRESULT InitDevice(HWND hWnd) { HRESULT hr = S_OK;
RECT rc; GetClientRect(hWnd, &rc); UINT width = rc.right - rc.left; UINT height = rc.bottom - rc.top;
UINT createDeviceFlags = 0;#ifdef _DEBUG createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;#endif
D3D_DRIVER_TYPE driverTypes[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_REFERENCE, }; UINT numDriverTypes = ARRAYSIZE(driverTypes);
D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, }; UINT numFeatureLevels = ARRAYSIZE(featureLevels);
for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++) { D3D_DRIVER_TYPE driverType = driverTypes[driverTypeIndex]; hr = D3D11CreateDevice(nullptr, driverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &m_pd3dDevice, nullptr, &m_pImmediateContext);
if (hr == E_INVALIDARG) { hr = D3D11CreateDevice(nullptr, driverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1, D3D11_SDK_VERSION, &m_pd3dDevice, nullptr, &m_pImmediateContext); }
if (SUCCEEDED(hr)) break; }
if (FAILED(hr)) return hr;
// Create swap chain IDXGIFactory1* dxgiFactory = nullptr; { IDXGIDevice* dxgiDevice = nullptr; hr = m_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgiDevice)); if (SUCCEEDED(hr)) { IDXGIAdapter* adapter = nullptr; hr = dxgiDevice->GetAdapter(&adapter); if (SUCCEEDED(hr)) { hr = adapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&dxgiFactory)); adapter->Release(); } dxgiDevice->Release(); } }
if (FAILED(hr)) return hr;
// Create swap chain DXGI_SWAP_CHAIN_DESC sd = {}; sd.BufferCount = 1; sd.BufferDesc.Width = width; sd.BufferDesc.Height = height; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferDesc.RefreshRate.Numerator = 60; sd.BufferDesc.RefreshRate.Denominator = 1; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.OutputWindow = hWnd; sd.SampleDesc.Count = 1; sd.SampleDesc.Quality = 0; sd.Windowed = TRUE;
hr = dxgiFactory->CreateSwapChain(m_pd3dDevice, &sd, &m_pSwapChain);
dxgiFactory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER); dxgiFactory->Release();
if (FAILED(hr)) return hr;
// Create render target view ID3D11Texture2D* pBackBuffer = nullptr; hr = m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&pBackBuffer)); if (FAILED(hr)) return hr;
hr = m_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &m_pRenderTargetView); pBackBuffer->Release(); if (FAILED(hr)) return hr;
// Create depth stencil texture D3D11_TEXTURE2D_DESC descDepth = {}; descDepth.Width = width; descDepth.Height = height; descDepth.MipLevels = 1; descDepth.ArraySize = 1; descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; descDepth.SampleDesc.Count = 1; descDepth.SampleDesc.Quality = 0; descDepth.Usage = D3D11_USAGE_DEFAULT; descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL; descDepth.CPUAccessFlags = 0; descDepth.MiscFlags = 0;
ID3D11Texture2D* pDepthStencil = nullptr; hr = m_pd3dDevice->CreateTexture2D(&descDepth, nullptr, &pDepthStencil); if (FAILED(hr)) return hr;
// Create depth stencil view D3D11_DEPTH_STENCIL_VIEW_DESC descDSV = {}; descDSV.Format = descDepth.Format; descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; descDSV.Texture2D.MipSlice = 0; hr = m_pd3dDevice->CreateDepthStencilView(pDepthStencil, &descDSV, &m_pDepthStencilView); pDepthStencil->Release(); if (FAILED(hr)) return hr;
m_pImmediateContext->OMSetRenderTargets(1, &m_pRenderTargetView, m_pDepthStencilView);
// Setup viewport D3D11_VIEWPORT vp; vp.Width = static_cast<FLOAT>(width); vp.Height = static_cast<FLOAT>(height); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = 0; vp.TopLeftY = 0; m_pImmediateContext->RSSetViewports(1, &vp);
// Initialize shaders and buffers hr = InitShaders(); if (FAILED(hr)) return hr;
hr = InitGeometry(); if (FAILED(hr)) return hr;
return S_OK; }
// Initialize shaders HRESULT InitShaders() { HRESULT hr = S_OK;
// Vertex shader source const char* vsSource = R"( cbuffer ConstantBuffer : register(b0) { matrix World; matrix View; matrix Projection; }
struct VS_OUTPUT { float4 Pos : SV_POSITION; float4 Color : COLOR0; };
VS_OUTPUT VS(float4 Pos : POSITION, float4 Color : COLOR) { VS_OUTPUT output = (VS_OUTPUT)0; output.Pos = mul(Pos, World); output.Pos = mul(output.Pos, View); output.Pos = mul(output.Pos, Projection); output.Color = Color; return output; } )";
// Pixel shader source const char* psSource = R"( struct PS_INPUT { float4 Pos : SV_POSITION; float4 Color : COLOR0; };
float4 PS(PS_INPUT input) : SV_Target { return input.Color; } )";
// Compile vertex shader ID3DBlob* pVSBlob = nullptr; ID3DBlob* pErrorBlob = nullptr; hr = D3DCompile(vsSource, strlen(vsSource), "VS", nullptr, nullptr, "VS", "vs_4_0", 0, 0, &pVSBlob, &pErrorBlob); if (FAILED(hr)) { if (pErrorBlob) pErrorBlob->Release(); return hr; }
hr = m_pd3dDevice->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), nullptr, &m_pVertexShader); if (FAILED(hr)) { pVSBlob->Release(); return hr; }
// Create input layout D3D11_INPUT_ELEMENT_DESC layout[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}, }; UINT numElements = ARRAYSIZE(layout);
hr = m_pd3dDevice->CreateInputLayout(layout, numElements, pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), &m_pVertexLayout); pVSBlob->Release(); if (FAILED(hr)) return hr;
// Compile pixel shader hr = D3DCompile(psSource, strlen(psSource), "PS", nullptr, nullptr, "PS", "ps_4_0", 0, 0, &pVSBlob, &pErrorBlob); if (FAILED(hr)) { if (pErrorBlob) pErrorBlob->Release(); return hr; }
hr = m_pd3dDevice->CreatePixelShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), nullptr, &m_pPixelShader); pVSBlob->Release();
return hr; }
// Initialize geometry HRESULT InitGeometry() { HRESULT hr = S_OK;
// Create vertex buffer Vertex vertices[] = { {XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f)}, {XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f)}, {XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f)}, {XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f)}, {XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f)}, {XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f)}, {XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f)}, {XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f)}, };
D3D11_BUFFER_DESC bd = {}; bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(Vertex) * 8; bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA InitData = {}; InitData.pSysMem = vertices; hr = m_pd3dDevice->CreateBuffer(&bd, &InitData, &m_pVertexBuffer); if (FAILED(hr)) return hr;
// Create constant buffer bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(ConstantBuffer); bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; bd.CPUAccessFlags = 0; hr = m_pd3dDevice->CreateBuffer(&bd, nullptr, &m_pConstantBuffer);
return hr; }
// Render frame void Render() { static float t = 0.0f; t += 0.016f; // ~60 FPS
// Clear render target float ClearColor[4] = {0.0f, 0.125f, 0.3f, 1.0f}; m_pImmediateContext->ClearRenderTargetView(m_pRenderTargetView, ClearColor); m_pImmediateContext->ClearDepthStencilView(m_pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
// Update matrices XMMATRIX mWorld = XMMatrixRotationY(t); XMMATRIX mView = XMMatrixLookAtLH(XMVectorSet(0.0f, 1.0f, -5.0f, 0.0f), XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f), XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)); XMMATRIX mProjection = XMMatrixPerspectiveFovLH(XM_PIDIV2, 800.0f / 600.0f, 0.01f, 100.0f);
ConstantBuffer cb; cb.mWorld = XMMatrixTranspose(mWorld); cb.mView = XMMatrixTranspose(mView); cb.mProjection = XMMatrixTranspose(mProjection); m_pImmediateContext->UpdateSubresource(m_pConstantBuffer, 0, nullptr, &cb, 0, 0);
// Set shaders and resources m_pImmediateContext->VSSetShader(m_pVertexShader, nullptr, 0); m_pImmediateContext->VSSetConstantBuffers(0, 1, &m_pConstantBuffer); m_pImmediateContext->PSSetShader(m_pPixelShader, nullptr, 0); m_pImmediateContext->IASetInputLayout(m_pVertexLayout);
// Set vertex buffer UINT stride = sizeof(Vertex); UINT offset = 0; m_pImmediateContext->IASetVertexBuffers(0, 1, &m_pVertexBuffer, &stride, &offset); m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
// Draw m_pImmediateContext->Draw(8, 0);
// Present m_pSwapChain->Present(0, 0); }
void CleanupDevice() { if (m_pImmediateContext) m_pImmediateContext->ClearState(); if (m_pConstantBuffer) m_pConstantBuffer->Release(); if (m_pVertexBuffer) m_pVertexBuffer->Release(); if (m_pVertexLayout) m_pVertexLayout->Release(); if (m_pVertexShader) m_pVertexShader->Release(); if (m_pPixelShader) m_pPixelShader->Release(); if (m_pDepthStencilView) m_pDepthStencilView->Release(); if (m_pRenderTargetView) m_pRenderTargetView->Release(); if (m_pSwapChain) m_pSwapChain->Release(); if (m_pImmediateContext) m_pImmediateContext->Release(); if (m_pd3dDevice) m_pd3dDevice->Release(); }};
3. Advanced Graphics Programming
Texture Loading and Management
// Texture Loading with WIC (Windows Imaging Component)#include <wincodec.h>#pragma comment(lib, "windowscodecs.lib")
class TextureManager {private: ID3D11Device* m_pDevice; ID3D11DeviceContext* m_pContext; IWICImagingFactory* m_pWICFactory;
public: TextureManager(ID3D11Device* device, ID3D11DeviceContext* context) : m_pDevice(device), m_pContext(context), m_pWICFactory(nullptr) { CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pWICFactory)); }
~TextureManager() { if (m_pWICFactory) m_pWICFactory->Release(); }
// Load texture from file HRESULT LoadTextureFromFile(const wchar_t* fileName, ID3D11ShaderResourceView** textureView) { HRESULT hr = S_OK;
// Create WIC decoder IWICBitmapDecoder* decoder = nullptr; hr = m_pWICFactory->CreateDecoderFromFilename(fileName, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &decoder); if (FAILED(hr)) return hr;
// Get first frame IWICBitmapFrameDecode* frame = nullptr; hr = decoder->GetFrame(0, &frame); if (FAILED(hr)) { decoder->Release(); return hr; }
// Get image dimensions UINT width, height; hr = frame->GetSize(&width, &height); if (FAILED(hr)) { frame->Release(); decoder->Release(); return hr; }
// Convert to RGBA format IWICFormatConverter* converter = nullptr; hr = m_pWICFactory->CreateFormatConverter(&converter); if (SUCCEEDED(hr)) { hr = converter->Initialize(frame, GUID_WICPixelFormat32bppRGBA, WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom); }
if (SUCCEEDED(hr)) { // Create texture std::vector<BYTE> pixels(width * height * 4); hr = converter->CopyPixels(nullptr, width * 4, static_cast<UINT>(pixels.size()), pixels.data());
if (SUCCEEDED(hr)) { D3D11_TEXTURE2D_DESC desc = {}; desc.Width = width; desc.Height = height; desc.MipLevels = 1; desc.ArraySize = 1; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
D3D11_SUBRESOURCE_DATA subResource; subResource.pSysMem = pixels.data(); subResource.SysMemPitch = width * 4; subResource.SysMemSlicePitch = 0;
ID3D11Texture2D* texture = nullptr; hr = m_pDevice->CreateTexture2D(&desc, &subResource, &texture);
if (SUCCEEDED(hr)) { D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.Format = desc.Format; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0;
hr = m_pDevice->CreateShaderResourceView(texture, &srvDesc, textureView); texture->Release(); } } }
if (converter) converter->Release(); frame->Release(); decoder->Release();
return hr; }
// Create render texture HRESULT CreateRenderTexture(UINT width, UINT height, ID3D11Texture2D** texture, ID3D11ShaderResourceView** srv, ID3D11RenderTargetView** rtv) { D3D11_TEXTURE2D_DESC textureDesc = {}; textureDesc.Width = width; textureDesc.Height = height; textureDesc.MipLevels = 1; textureDesc.ArraySize = 1; textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; textureDesc.SampleDesc.Count = 1; textureDesc.Usage = D3D11_USAGE_DEFAULT; textureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
HRESULT hr = m_pDevice->CreateTexture2D(&textureDesc, nullptr, texture); if (FAILED(hr)) return hr;
// Create shader resource view hr = m_pDevice->CreateShaderResourceView(*texture, nullptr, srv); if (FAILED(hr)) return hr;
// Create render target view hr = m_pDevice->CreateRenderTargetView(*texture, nullptr, rtv); return hr; }};
Shader Effects and Post-Processing
// Advanced Shader Effectsclass ShaderEffects {private: ID3D11Device* m_pDevice; ID3D11DeviceContext* m_pContext;
// Post-processing shaders ID3D11VertexShader* m_pFullscreenVS; ID3D11PixelShader* m_pBlurPS; ID3D11PixelShader* m_pBloomPS; ID3D11PixelShader* m_pToneMappingPS;
// Compute shaders ID3D11ComputeShader* m_pParticleCS; ID3D11Buffer* m_pParticleBuffer; ID3D11UnorderedAccessView* m_pParticleUAV;
public: ShaderEffects(ID3D11Device* device, ID3D11DeviceContext* context) : m_pDevice(device), m_pContext(context) {}
// Initialize post-processing effects HRESULT InitializeEffects() { HRESULT hr = S_OK;
// Fullscreen vertex shader const char* fullscreenVS = R"( struct VS_OUTPUT { float4 Pos : SV_POSITION; float2 Tex : TEXCOORD0; };
VS_OUTPUT VS(uint id : SV_VertexID) { VS_OUTPUT output; output.Tex = float2((id << 1) & 2, id & 2); output.Pos = float4(output.Tex * 2.0 - 1.0, 0.0, 1.0); return output; } )";
// Gaussian blur pixel shader const char* blurPS = R"( Texture2D InputTexture : register(t0); SamplerState InputSampler : register(s0);
cbuffer BlurParameters : register(b0) { float2 Direction; float2 TextureSize; };
struct PS_INPUT { float4 Pos : SV_POSITION; float2 Tex : TEXCOORD0; };
static const float weights[5] = { 0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216 };
float4 PS(PS_INPUT input) : SV_Target { float2 texOffset = 1.0 / TextureSize; float3 result = InputTexture.Sample(InputSampler, input.Tex).rgb * weights[0];
for(int i = 1; i < 5; ++i) { result += InputTexture.Sample(InputSampler, input.Tex + Direction * texOffset * i).rgb * weights[i]; result += InputTexture.Sample(InputSampler, input.Tex - Direction * texOffset * i).rgb * weights[i]; }
return float4(result, 1.0); } )";
// Particle compute shader const char* particleCS = R"( struct Particle { float3 position; float life; float3 velocity; float size; float4 color; };
RWStructuredBuffer<Particle> ParticleBuffer : register(u0);
cbuffer SimulationParameters : register(b0) { float DeltaTime; float3 Gravity; float Damping; float3 WindForce; };
[numthreads(256, 1, 1)] void CS(uint3 id : SV_DispatchThreadID) { uint index = id.x; Particle particle = ParticleBuffer[index];
if (particle.life > 0.0) { // Update physics particle.velocity += (Gravity + WindForce) * DeltaTime; particle.velocity *= Damping; particle.position += particle.velocity * DeltaTime; particle.life -= DeltaTime;
// Update color based on life particle.color.a = particle.life / 5.0; // 5 second lifetime
ParticleBuffer[index] = particle; } } )";
// Compile and create shaders ID3DBlob* shaderBlob = nullptr;
// Fullscreen vertex shader hr = D3DCompile(fullscreenVS, strlen(fullscreenVS), nullptr, nullptr, nullptr, "VS", "vs_5_0", 0, 0, &shaderBlob, nullptr); if (SUCCEEDED(hr)) { hr = m_pDevice->CreateVertexShader(shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), nullptr, &m_pFullscreenVS); shaderBlob->Release(); }
// Blur pixel shader hr = D3DCompile(blurPS, strlen(blurPS), nullptr, nullptr, nullptr, "PS", "ps_5_0", 0, 0, &shaderBlob, nullptr); if (SUCCEEDED(hr)) { hr = m_pDevice->CreatePixelShader(shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), nullptr, &m_pBlurPS); shaderBlob->Release(); }
// Particle compute shader hr = D3DCompile(particleCS, strlen(particleCS), nullptr, nullptr, nullptr, "CS", "cs_5_0", 0, 0, &shaderBlob, nullptr); if (SUCCEEDED(hr)) { hr = m_pDevice->CreateComputeShader(shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize(), nullptr, &m_pParticleCS); shaderBlob->Release(); }
// Create particle buffer hr = CreateParticleSystem();
return hr; }
// Apply Gaussian blur effect void ApplyBlur(ID3D11ShaderResourceView* input, ID3D11RenderTargetView* output, UINT width, UINT height) { // Set fullscreen quad rendering m_pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); m_pContext->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr); m_pContext->IASetInputLayout(nullptr);
// Set shaders m_pContext->VSSetShader(m_pFullscreenVS, nullptr, 0); m_pContext->PSSetShader(m_pBlurPS, nullptr, 0);
// Set input texture m_pContext->PSSetShaderResources(0, 1, &input);
// Set render target m_pContext->OMSetRenderTargets(1, &output, nullptr);
// Set viewport D3D11_VIEWPORT viewport = { 0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height), 0.0f, 1.0f }; m_pContext->RSSetViewports(1, &viewport);
// Draw fullscreen quad m_pContext->Draw(3, 0); }
private: HRESULT CreateParticleSystem() { // Create structured buffer for particles const UINT numParticles = 10000; D3D11_BUFFER_DESC bufferDesc = {}; bufferDesc.ByteWidth = sizeof(ParticleData) * numParticles; bufferDesc.Usage = D3D11_USAGE_DEFAULT; bufferDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE; bufferDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; bufferDesc.StructureByteStride = sizeof(ParticleData);
HRESULT hr = m_pDevice->CreateBuffer(&bufferDesc, nullptr, &m_pParticleBuffer); if (FAILED(hr)) return hr;
// Create UAV D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; uavDesc.Format = DXGI_FORMAT_UNKNOWN; uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; uavDesc.Buffer.FirstElement = 0; uavDesc.Buffer.NumElements = numParticles;
hr = m_pDevice->CreateUnorderedAccessView(m_pParticleBuffer, &uavDesc, &m_pParticleUAV); return hr; }
struct ParticleData { float position[3]; float life; float velocity[3]; float size; float color[4]; };};
4. Performance Optimization
GPU Profiling and Optimization
// GPU Performance Profilingclass GPUProfiler {private: ID3D11Device* m_pDevice; ID3D11DeviceContext* m_pContext; ID3D11Query* m_pTimestampStart; ID3D11Query* m_pTimestampEnd; ID3D11Query* m_pTimestampDisjoint;
public: GPUProfiler(ID3D11Device* device, ID3D11DeviceContext* context) : m_pDevice(device), m_pContext(context) { CreateQueries(); }
void CreateQueries() { D3D11_QUERY_DESC queryDesc = {};
// Timestamp queries queryDesc.Query = D3D11_QUERY_TIMESTAMP; m_pDevice->CreateQuery(&queryDesc, &m_pTimestampStart); m_pDevice->CreateQuery(&queryDesc, &m_pTimestampEnd);
// Disjoint query for frequency queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT; m_pDevice->CreateQuery(&queryDesc, &m_pTimestampDisjoint); }
void BeginProfiling() { m_pContext->Begin(m_pTimestampDisjoint); m_pContext->End(m_pTimestampStart); }
void EndProfiling() { m_pContext->End(m_pTimestampEnd); m_pContext->End(m_pTimestampDisjoint); }
double GetProfilingResult() { UINT64 startTime, endTime; D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjointData;
while (m_pContext->GetData(m_pTimestampDisjoint, &disjointData, sizeof(disjointData), 0) == S_FALSE) { // Wait for data }
if (disjointData.Disjoint) return -1.0; // Invalid timing
m_pContext->GetData(m_pTimestampStart, &startTime, sizeof(startTime), 0); m_pContext->GetData(m_pTimestampEnd, &endTime, sizeof(endTime), 0);
return static_cast<double>(endTime - startTime) / static_cast<double>(disjointData.Frequency) * 1000.0; // ms }};
// Memory Management for Graphics Resourcesclass GraphicsResourceManager {private: struct ResourceInfo { ID3D11Resource* resource; size_t size; std::string name; std::chrono::steady_clock::time_point lastUsed; };
std::unordered_map<void*, ResourceInfo> m_resources; size_t m_totalMemoryUsed; size_t m_memoryLimit;
public: GraphicsResourceManager(size_t memoryLimitMB = 512) : m_totalMemoryUsed(0), m_memoryLimit(memoryLimitMB * 1024 * 1024) {}
void RegisterResource(ID3D11Resource* resource, size_t size, const std::string& name) { ResourceInfo info = { resource, size, name, std::chrono::steady_clock::now() }; m_resources[resource] = info; m_totalMemoryUsed += size;
// Check for memory pressure if (m_totalMemoryUsed > m_memoryLimit) { EvictLeastRecentlyUsed(); } }
void TouchResource(ID3D11Resource* resource) { auto it = m_resources.find(resource); if (it != m_resources.end()) { it->second.lastUsed = std::chrono::steady_clock::now(); } }
void UnregisterResource(ID3D11Resource* resource) { auto it = m_resources.find(resource); if (it != m_resources.end()) { m_totalMemoryUsed -= it->second.size; m_resources.erase(it); } }
private: void EvictLeastRecentlyUsed() { // Find oldest resource auto oldest = std::min_element(m_resources.begin(), m_resources.end(), [](const auto& a, const auto& b) { return a.second.lastUsed < b.second.lastUsed; });
if (oldest != m_resources.end()) { oldest->second.resource->Release(); m_totalMemoryUsed -= oldest->second.size; m_resources.erase(oldest); } }};
Best Practices
1. Resource Management
- Always release COM interfaces properly
- Use RAII patterns for automatic cleanup
- Implement resource pooling for frequently used objects
- Monitor memory usage and implement LRU caching
2. Performance Optimization
- Batch similar draw calls together
- Use instancing for repeated geometry
- Implement frustum culling and occlusion culling
- Profile GPU performance regularly
3. Shader Development
- Keep shaders simple and efficient
- Use appropriate precision (half vs full precision)
- Minimize texture samples and branches
- Optimize for target hardware capabilities
4. Error Handling
// Robust error handling pattern#define CHECK_HR(hr) do { \ if (FAILED(hr)) { \ OutputDebugStringA("DirectX Error: " __FILE__ " line " \ STRINGIFY(__LINE__) "\n"); \ return hr; \ } \} while(0)
#define STRINGIFY(x) #x
Conclusion
DirectX programming enables high-performance graphics applications on Windows. This guide covers the fundamentals from basic 2D rendering with Direct2D to advanced 3D graphics with Direct3D 11, including shader programming, texture management, and performance optimization techniques.
Key takeaways:
- Direct2D: Excellent for 2D graphics and UI elements
- Direct3D 11: Modern 3D graphics API with shader support
- Performance: Critical for real-time applications
- Resource Management: Essential for stable applications
- Shader Programming: Core to modern graphics development
Continue exploring advanced topics like DirectX 12, ray tracing, and compute shaders to push the boundaries of graphics programming on Windows.
Windows DirectX & Graphics Programming: Complete Developer Guide
https://mranv.pages.dev/posts/2025/windows-directx-graphics-programming/