Skip to content

Instantly share code, notes, and snippets.

@d7samurai
Last active December 21, 2025 06:13
Show Gist options
  • Select an option

  • Save d7samurai/5915956fb8ce6a63503cf8c85ffd1e84 to your computer and use it in GitHub Desktop.

Select an option

Save d7samurai/5915956fb8ce6a63503cf8c85ffd1e84 to your computer and use it in GitHub Desktop.
Minimal D3D11 bonus material: fullscreen triangle

Minimal D3D11 bonus material: fullscreen triangle

image

Basic setup for rendering a fullscreen triangle - as opposed to a fullscreen quad. This is a triangle that extends past the screen boundaries so that it covers it all, with the excess getting clipped away. The use case for this is typically doing a full screen pass for composition, post processing effects etc (though nowadays you'd probably go for a compute shader instead).

Generally a triangle is preferred over a quad because the geometry is simpler (fewer vertices / shader invocations) and it eliminates the quad’s internal diagonal split. Since pixel shading runs in 2×2 pixel units (incidentally also referred to as "quads"), units that straddle the diagonal will do redundant work on pixels that gets masked out and discarded; both triangles pay this cost along the split, each discarding work that is then redone by the other (see this blog post by Chris Wallis for a more detailed examination).

Vertices are procedurally generated from SV_VertexID, so no input layout, vertex buffer, or geometry setup is needed. Texturing is included to show the UV mapping math alongside the generated vertex coordinates.

As usual: complete, runnable single-function app. No modern C++, OOP or (other) obscuring cruft.

Note that this time there is no texture included with the source code, so you will need to provide your own. To simplify things, the image loader assumes a binary file of raw BGRA bytes, with no header*.

To avoid all that, here's a simpler version, without texturing. Also check out the original Minimal D3D11 for a more complete D3D setup and rendering example.

*I made a little command line utility called TexPrep that can convert most image formats to such a raw binary blob, found here.

Example usage:

texprep.exe -bin mytexture.png

This will create an output file in the right format and the produced filename will include the dimensions of the image (e.g. mytexture_1920x1080_pm0.bin) to make it easier to integrate. Set those values here to ensure the texture is loaded correctly.

#pragma comment(lib, "user32")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d3dcompiler")
///////////////////////////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <d3d11.h>
#include <d3dcompiler.h>
///////////////////////////////////////////////////////////////////////////////////////////////////
#define TITLE "Minimal fullscreen triangle by d7samurai"
///////////////////////////////////////////////////////////////////////////////////////////////////
#define TEXTURE_FILENAME "mytexture_1920x1080_pm0.bin" // bring your own texture
#define TEXTURE_WIDTH 1920
#define TEXTURE_HEIGHT 1080
///////////////////////////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASSA wndclass = { 0, DefWindowProcA, 0, 0, 0, 0, 0, 0, 0, TITLE };
RegisterClassA(&wndclass);
HWND window = CreateWindowExA(0, TITLE, TITLE, 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; // non-srgb for simplicity here. see other minimal gists for srgb setup
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); // get the swapchain's buffer
ID3D11RenderTargetView* framebufferRTV;
device->CreateRenderTargetView(framebuffer, nullptr, &framebufferRTV); // and make it a render target [view]
///////////////////////////////////////////////////////////////////////////////////////////////
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);
///////////////////////////////////////////////////////////////////////////////////////////////
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 };
ID3D11RasterizerState* rasterizerstate;
device->CreateRasterizerState(&rasterizerdesc, &rasterizerstate);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_SAMPLER_DESC samplerdesc = { D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_TEXTURE_ADDRESS_WRAP, D3D11_TEXTURE_ADDRESS_WRAP, D3D11_TEXTURE_ADDRESS_WRAP };
ID3D11SamplerState* samplerstate;
device->CreateSamplerState(&samplerdesc, &samplerstate);
///////////////////////////////////////////////////////////////////////////////////////////////
HANDLE handle = CreateFileA(TEXTURE_FILENAME, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
LARGE_INTEGER size;
GetFileSizeEx(handle, &size);
void* texturedata = HeapAlloc(GetProcessHeap(), 0, size.LowPart);
DWORD read;
ReadFile(handle, texturedata, size.LowPart, &read, nullptr);
CloseHandle(handle);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_TEXTURE2D_DESC texturedesc = {};
texturedesc.Width = TEXTURE_WIDTH;
texturedesc.Height = TEXTURE_HEIGHT;
texturedesc.MipLevels = 1;
texturedesc.ArraySize = 1;
texturedesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
texturedesc.SampleDesc.Count = 1;
texturedesc.Usage = D3D11_USAGE_IMMUTABLE;
texturedesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
D3D11_SUBRESOURCE_DATA textureSRD = {};
textureSRD.pSysMem = texturedata;
textureSRD.SysMemPitch = TEXTURE_WIDTH * 4; // 4 bytes per pixel
ID3D11Texture2D* texture;
device->CreateTexture2D(&texturedesc, &textureSRD, &texture);
ID3D11ShaderResourceView* textureSRV;
device->CreateShaderResourceView(texture, nullptr, &textureSRV);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_VIEWPORT viewport = { 0, 0, (float)swapchaindesc.BufferDesc.Width, (float)swapchaindesc.BufferDesc.Height, 0, 1 };
///////////////////////////////////////////////////////////////////////////////////////////////
devicecontext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
devicecontext->VSSetShader(vertexshader, nullptr, 0);
devicecontext->RSSetViewports(1, &viewport);
devicecontext->RSSetState(rasterizerstate);
devicecontext->PSSetShader(pixelshader, nullptr, 0);
devicecontext->PSSetShaderResources(0, 1, &textureSRV);
devicecontext->PSSetSamplers(0, 1, &samplerstate);
devicecontext->OMSetRenderTargets(1, &framebufferRTV, nullptr);
///////////////////////////////////////////////////////////////////////////////////////////
devicecontext->Draw(3, 0);
///////////////////////////////////////////////////////////////////////////////////////////
swapchain->Present(1, 0);
///////////////////////////////////////////////////////////////////////////////////////////////
MSG msg;
GetMessageA(&msg, nullptr, WM_KEYFIRST, WM_KEYLAST);
}
struct vs_out
{
float4 pos : SV_POSITION;
float2 tex : TEX;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
Texture2D mytexture : register(t0);
SamplerState mysampler : register(s0);
///////////////////////////////////////////////////////////////////////////////////////////////////
vs_out VsMain(uint vertexid : SV_VERTEXID)
{
vs_out output;
output.pos = float4(vertexid >> 1, vertexid & 1, 0, 0.5) * 4 - 1;
output.tex = float2(vertexid >> 1, 1 - vertexid & 1) * 2;
return output;
}
float4 PsMain(vs_out input) : SV_TARGET
{
return mytexture.Sample(mysampler, input.tex);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment