Skip to content

Instantly share code, notes, and snippets.

@d7samurai
Last active August 24, 2025 05:57
Show Gist options
  • Save d7samurai/032c63fd91c400bc33fb79c9e74adc46 to your computer and use it in GitHub Desktop.
Save d7samurai/032c63fd91c400bc33fb79c9e74adc46 to your computer and use it in GitHub Desktop.
Minimal D3D11 bonus material: even simpler 2D

Minimal D3D11 bonus material: even simpler 2D

image

Plain 2D drawing in D3D11, without the distracting feature set of a complete sprite renderer. Like the original Minimal D3D11, this one uses a canonical 1:1 TRIANGLELIST vertex buffer with input layout, so no fancy manual custom buffer fetching and instanced quad expansion in shader etc, as well as absolute pixel coordinate positioning (there's really no need for the sometimes confusing complication of a full-on projection matrix pipeline for UI and 2D games). As usual: complete, runnable single-function app. No modern C++, OOP or (other) obscuring cruft.

This 'even simpler' variant renders solid color triangles; no texturing or alpha blending. For line drawing, change this to:

devicecontext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
#pragma comment(lib, "user32")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d3dcompiler")
///////////////////////////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <d3d11.h>
#include <d3dcompiler.h>
///////////////////////////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASSA wndclass = { 0, DefWindowProcA, 0, 0, 0, 0, 0, 0, 0, "d7" };
RegisterClassA(&wndclass);
HWND window = CreateWindowExA(0, "d7", 0, 0x91000000, 0, 0, 0, 0, 0, 0, 0, 0);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D_FEATURE_LEVEL featurelevels[] = { D3D_FEATURE_LEVEL_11_0 };
DXGI_SWAP_CHAIN_DESC swapchaindesc = {};
swapchaindesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // note: non-srgb for simplicity here
swapchaindesc.SampleDesc.Count = 1;
swapchaindesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapchaindesc.BufferCount = 2;
swapchaindesc.OutputWindow = window;
swapchaindesc.Windowed = TRUE;
swapchaindesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
IDXGISwapChain* swapchain;
ID3D11Device* device;
ID3D11DeviceContext* devicecontext;
D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featurelevels, ARRAYSIZE(featurelevels), D3D11_SDK_VERSION, &swapchaindesc, &swapchain, &device, nullptr, &devicecontext);
swapchain->GetDesc(&swapchaindesc); // get actual dimensions
///////////////////////////////////////////////////////////////////////////////////////////////
ID3D11Texture2D* framebuffer;
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&framebuffer); // grab buffer from swapchain
ID3D11RenderTargetView* framebufferRTV;
device->CreateRenderTargetView(framebuffer, nullptr, &framebufferRTV);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3DBlob* vertexshaderCSO;
D3DCompileFromFile(L"gpu.hlsl", 0, 0, "VsMain", "vs_5_0", 0, 0, &vertexshaderCSO, 0);
ID3D11VertexShader* vertexshader;
device->CreateVertexShader(vertexshaderCSO->GetBufferPointer(), vertexshaderCSO->GetBufferSize(), 0, &vertexshader);
D3D11_INPUT_ELEMENT_DESC inputelementdesc[] = // maps to vertexdesc struct in gpu.hlsl via semantic names ("POS", "COL")
{
{ "POS", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // float2 position
{ "COL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // float3 color
};
ID3D11InputLayout* inputlayout;
device->CreateInputLayout(inputelementdesc, ARRAYSIZE(inputelementdesc), vertexshaderCSO->GetBufferPointer(), vertexshaderCSO->GetBufferSize(), &inputlayout);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3DBlob* pixelshaderCSO;
D3DCompileFromFile(L"gpu.hlsl", 0, 0, "PsMain", "ps_5_0", 0, 0, &pixelshaderCSO, 0);
ID3D11PixelShader* pixelshader;
device->CreatePixelShader(pixelshaderCSO->GetBufferPointer(), pixelshaderCSO->GetBufferSize(), 0, &pixelshader);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_RASTERIZER_DESC rasterizerdesc = { D3D11_FILL_SOLID, D3D11_CULL_NONE }; // CULL_NONE to be agnostic of triangle winding order
ID3D11RasterizerState* rasterizerstate;
device->CreateRasterizerState(&rasterizerdesc, &rasterizerstate);
///////////////////////////////////////////////////////////////////////////////////////////////
float constants[2] = { 2.0f / swapchaindesc.BufferDesc.Width, -2.0f / swapchaindesc.BufferDesc.Height }; // precalc for simple screen position conversion in shader (instead of full-on projection matrix)
D3D11_BUFFER_DESC constantbufferdesc = {};
constantbufferdesc.ByteWidth = sizeof(constants) + 0xf & 0xfffffff0;
constantbufferdesc.Usage = D3D11_USAGE_IMMUTABLE;
constantbufferdesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
D3D11_SUBRESOURCE_DATA constantbufferSRD = { constants };
ID3D11Buffer* constantbuffer;
device->CreateBuffer(&constantbufferdesc, &constantbufferSRD, &constantbuffer);
///////////////////////////////////////////////////////////////////////////////////////////////
#define MAX_VERTICES 1024 // arbitrary limit
struct vertexdesc { float x, y, r, g, b; }; // float2 position, float3 color
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_BUFFER_DESC vertexbufferdesc = {};
vertexbufferdesc.ByteWidth = sizeof(vertexdesc) * MAX_VERTICES;
vertexbufferdesc.Usage = D3D11_USAGE_DYNAMIC; // updated every frame
vertexbufferdesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
vertexbufferdesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
ID3D11Buffer* vertexbuffer;
device->CreateBuffer(&vertexbufferdesc, nullptr, &vertexbuffer);
///////////////////////////////////////////////////////////////////////////////////////////////
FLOAT clearcolor[4] = { 0.1725f, 0.1725f, 0.1725f, 1.0f }; // RGBA
D3D11_VIEWPORT viewport = { 0, 0, (float)swapchaindesc.BufferDesc.Width, (float)swapchaindesc.BufferDesc.Height, 0, 1 };
UINT stride = sizeof(vertexdesc);
UINT offset = 0;
///////////////////////////////////////////////////////////////////////////////////////////////
vertexdesc* vertexbatch = (vertexdesc*)HeapAlloc(GetProcessHeap(), 0, sizeof(vertexdesc) * MAX_VERTICES); // per frame vertex batch (cpu-local buffer)
///////////////////////////////////////////////////////////////////////////////////////////////
while (true)
{
MSG msg;
while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_KEYDOWN) return 0; // PRESS ANY KEY TO EXIT
DispatchMessageA(&msg);
}
///////////////////////////////////////////////////////////////////////////////////////////
int vertexcount = 0; // start a new vertex batch every frame
vertexbatch[vertexcount++] = { 150, 100, 1.0f, 0.0f, 0.0f }; // x, y, r, g, b
vertexbatch[vertexcount++] = { 200, 250, 0.0f, 1.0f, 0.0f };
vertexbatch[vertexcount++] = { 100, 200, 0.0f, 0.0f, 1.0f };
///////////////////////////////////////////////////////////////////////////////////////////
D3D11_MAPPED_SUBRESOURCE vertexbufferMSR;
devicecontext->Map(vertexbuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &vertexbufferMSR);
{
memcpy(vertexbufferMSR.pData, vertexbatch, sizeof(vertexdesc) * vertexcount); // send vertex batch to gpu
}
devicecontext->Unmap(vertexbuffer, 0);
///////////////////////////////////////////////////////////////////////////////////////////
devicecontext->ClearRenderTargetView(framebufferRTV, clearcolor);
devicecontext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // 3 vertices per triangle
devicecontext->IASetInputLayout(inputlayout);
devicecontext->IASetVertexBuffers(0, 1, &vertexbuffer, &stride, &offset);
devicecontext->VSSetShader(vertexshader, nullptr, 0);
devicecontext->VSSetConstantBuffers(0, 1, &constantbuffer);
devicecontext->RSSetViewports(1, &viewport);
devicecontext->RSSetState(rasterizerstate);
devicecontext->PSSetShader(pixelshader, nullptr, 0);
devicecontext->OMSetRenderTargets(1, &framebufferRTV, nullptr);
///////////////////////////////////////////////////////////////////////////////////////////
devicecontext->Draw(vertexcount, 0);
///////////////////////////////////////////////////////////////////////////////////////////
swapchain->Present(1, 0);
}
}
cbuffer constants : register(b0)
{
float2 rn_screensize; // { 2 / width, -2 / height }
}
struct vertexdesc
{
float2 position : POS;
float3 color : COL;
};
struct pixeldesc
{
float4 position : SV_POSITION;
float4 color : COL;
};
pixeldesc VsMain(vertexdesc vertex)
{
pixeldesc output;
output.position = float4(vertex.position * rn_screensize - float2(1, -1), 0, 1);
output.color = float4(vertex.color, 1);
return output;
}
float4 PsMain(pixeldesc pixel) : SV_TARGET
{
return pixel.color;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment