Skip to content

Instantly share code, notes, and snippets.

@oluigipo
Last active December 1, 2023 06:47
Show Gist options
  • Save oluigipo/e0ecb775031870933e7e610667f14731 to your computer and use it in GitHub Desktop.
Save oluigipo/e0ecb775031870933e7e610667f14731 to your computer and use it in GitHub Desktop.
learn-d3d12
/*
* fxc /nologo shader.hlsl /Fovs.bin /EVertex /Tvs_5_0
* fxc /nologo shader.hlsl /Fops.bin /EPixel /Tps_5_0
* clang learn-d3d12.c -o a.exe -g -Wall -fuse-ld=lld
*/
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#define INITGUID
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#define ArrayLength(x) (sizeof(x)/sizeof*(x))
#define SafeAssert(...) do { \
if (!(__VA_ARGS__)) { \
if (IsDebuggerPresent()) \
__debugbreak(); \
SafeAssertFailed(__FILE__, __LINE__, __func__, #__VA_ARGS__); \
} \
} \
while (0)
static bool g_running = true;
static void
SafeAssertFailed(char const* file, int line, char const* func, char const* expr)
{
wchar_t wstr[4096];
wchar_t* head = wstr;
wchar_t* end = wstr + ArrayLength(wstr);
char linestr[32];
snprintf(linestr, sizeof(linestr), "%i", line);
head += -1+MultiByteToWideChar(CP_UTF8, 0, "SafeAssert failure!\nFile: ", -1, head, end-head);
head += -1+MultiByteToWideChar(CP_UTF8, 0, file, -1, head, end-head);
head += -1+MultiByteToWideChar(CP_UTF8, 0, "\nLine: ", -1, head, end-head);
head += -1+MultiByteToWideChar(CP_UTF8, 0, linestr, -1, head, end-head);
head += -1+MultiByteToWideChar(CP_UTF8, 0, "\nFunction: ", -1, head, end-head);
head += -1+MultiByteToWideChar(CP_UTF8, 0, func, -1, head, end-head);
head += -1+MultiByteToWideChar(CP_UTF8, 0, "\nExpr: ", -1, head, end-head);
head += -1+MultiByteToWideChar(CP_UTF8, 0, expr, -1, head, end-head);
head[0] = L'\0';
MessageBoxW(NULL, wstr, L"Fatal error", MB_OK);
ExitProcess(1);
}
struct LoadFileResult
{
void* data;
size_t size;
}
typedef LoadFileResult;
static LoadFileResult
LoadFile(LPWSTR path)
{
LoadFileResult result = { 0 };
HANDLE handle = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (handle != INVALID_HANDLE_VALUE)
{
LARGE_INTEGER size_largeint;
if (GetFileSizeEx(handle, &size_largeint))
{
size_t size = size_largeint.QuadPart;
uint8_t* buffer = HeapAlloc(GetProcessHeap(), 0, size);
size_t read = 0;
while (read < size)
{
DWORD to_read = (size >= UINT32_MAX) ? UINT32_MAX : (DWORD)size;
DWORD did_read = 0;
if (!ReadFile(handle, buffer+read, to_read, &did_read, NULL))
break;
read += did_read;
}
if (read == size)
{
result.data = buffer;
result.size = size;
}
}
CloseHandle(handle);
}
return result;
}
LRESULT CALLBACK
WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
LRESULT result = 0;
switch (msg)
{
case WM_CLOSE: g_running = false; break;
default: result = DefWindowProcW(hwnd, msg, wparam, lparam);
}
return result;
}
int WINAPI
wWinMain(HINSTANCE instance, HINSTANCE prev, LPWSTR wargs, int cmd_show)
{
HRESULT hr;
//~ Win32 basics
WNDCLASSW wndclass = {
.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
.lpfnWndProc = WindowProc,
.lpszClassName = L"ClassName",
.hCursor = LoadCursorA(NULL, IDC_ARROW),
.hIcon = LoadIconA(instance, MAKEINTRESOURCE(101)),
};
SafeAssert(RegisterClassW(&wndclass));
UINT style = WS_OVERLAPPEDWINDOW;
style &= ~WS_MAXIMIZEBOX;
style &= ~WS_THICKFRAME;
HWND hwnd = CreateWindowExW(0, wndclass.lpszClassName, L"Title", style, CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720, NULL, NULL, instance, NULL);
SafeAssert(hwnd);
int32_t window_width, window_height;
{
RECT client_area;
SafeAssert(GetClientRect(hwnd, &client_area));
window_width = client_area.right - client_area.left;
window_height = client_area.bottom - client_area.top;
}
//~ D3D12 basics
D3D12_VIEWPORT d3d12_viewport = {
.TopLeftX = 0.0f,
.TopLeftY = 0.0f,
.Width = (float)window_width,
.Height = (float)window_height,
.MinDepth = 0.0f,
.MaxDepth = 1.0f,
};
D3D12_RECT d3d12_scissor_rect = {
.left = 0,
.top = 0,
.right = window_width,
.bottom = window_height,
};
ID3D12Debug* d3d12_debug = NULL;
if (SUCCEEDED(D3D12GetDebugInterface(&IID_ID3D12Debug, (void**)&d3d12_debug)))
ID3D12Debug_EnableDebugLayer(d3d12_debug);
ID3D12Resource* d3d12_render_targets[2] = { 0 };
IDXGIFactory4* dxgi_factory4 = NULL;
{
hr = CreateDXGIFactory1(&IID_IDXGIFactory4, (void**)&dxgi_factory4);
SafeAssert(SUCCEEDED(hr));
}
IDXGIAdapter1* dxgi_adapter1 = NULL;
for (UINT index = 0;; ++index)
{
IDXGIAdapter1* adapter;
if (IDXGIFactory4_EnumAdapters1(dxgi_factory4, index, &adapter) == DXGI_ERROR_NOT_FOUND)
break;
hr = D3D12CreateDevice((IUnknown*)adapter, D3D_FEATURE_LEVEL_11_0, &IID_ID3D12Device, NULL);
if (SUCCEEDED(hr))
{
dxgi_adapter1 = adapter;
break;
}
IDXGIAdapter1_Release(adapter);
}
SafeAssert(dxgi_adapter1);
ID3D12Device* d3d12_device = NULL;
{
hr = D3D12CreateDevice((IUnknown*)dxgi_adapter1, D3D_FEATURE_LEVEL_12_0, &IID_ID3D12Device, (void**)&d3d12_device);
SafeAssert(SUCCEEDED(hr));
}
ID3D12CommandQueue* d3d12_cmdqueue = NULL;
{
D3D12_COMMAND_QUEUE_DESC cmdqueue_desc = {
.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
.Type = D3D12_COMMAND_LIST_TYPE_DIRECT,
};
hr = ID3D12Device_CreateCommandQueue(d3d12_device, &cmdqueue_desc, &IID_ID3D12CommandQueue, (void**)&d3d12_cmdqueue);
SafeAssert(SUCCEEDED(hr));
}
IDXGISwapChain1* dxgi_swapchain1 = NULL;
{
DXGI_SWAP_CHAIN_DESC1 swapchain_desc1 = {
.BufferCount = ArrayLength(d3d12_render_targets),
.Width = 0,
.Height = 0,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
.SampleDesc.Count = 1,
};
hr = IDXGIFactory4_CreateSwapChainForHwnd(dxgi_factory4, (IUnknown*)d3d12_cmdqueue, hwnd, &swapchain_desc1, NULL, NULL, &dxgi_swapchain1);
SafeAssert(SUCCEEDED(hr));
}
IDXGISwapChain3* dxgi_swapchain3 = NULL;
hr = IDXGISwapChain1_QueryInterface(dxgi_swapchain1, &IID_IDXGISwapChain3, (void**)&dxgi_swapchain3);
SafeAssert(SUCCEEDED(hr));
hr = IDXGIFactory4_MakeWindowAssociation(dxgi_factory4, hwnd, DXGI_MWA_NO_ALT_ENTER);
SafeAssert(SUCCEEDED(hr));
UINT frame_index = IDXGISwapChain3_GetCurrentBackBufferIndex(dxgi_swapchain3);
ID3D12DescriptorHeap* d3d12_rtvheap = NULL;
{
D3D12_DESCRIPTOR_HEAP_DESC rtvheap_desc = {
.NumDescriptors = ArrayLength(d3d12_render_targets),
.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
};
hr = ID3D12Device_CreateDescriptorHeap(d3d12_device, &rtvheap_desc, &IID_ID3D12DescriptorHeap, (void**)&d3d12_rtvheap);
SafeAssert(SUCCEEDED(hr));
}
UINT rtv_descriptor_size = ID3D12Device_GetDescriptorHandleIncrementSize(d3d12_device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
{
D3D12_CPU_DESCRIPTOR_HANDLE rtvhandle;
ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(d3d12_rtvheap, &rtvhandle);
for (UINT i = 0; i < ArrayLength(d3d12_render_targets); ++i)
{
hr = IDXGISwapChain3_GetBuffer(dxgi_swapchain3, i, &IID_ID3D12Resource, (void**)&d3d12_render_targets[i]);
SafeAssert(SUCCEEDED(hr));
ID3D12Device_CreateRenderTargetView(d3d12_device, d3d12_render_targets[i], NULL, rtvhandle);
// https://github.com/microsoft/DirectX-Headers/blob/48f23952bc08a6dce0727339c07cedbc4797356c/include/directx/d3dx12_root_signature.h#L889
rtvhandle.ptr = (SIZE_T)((INT64)rtvhandle.ptr + rtv_descriptor_size);
}
}
ID3D12CommandAllocator* d3d12_cmdallocator = NULL;
{
hr = ID3D12Device_CreateCommandAllocator(d3d12_device, D3D12_COMMAND_LIST_TYPE_DIRECT, &IID_ID3D12CommandAllocator, (void**)&d3d12_cmdallocator);
SafeAssert(SUCCEEDED(hr));
}
//~ D3D12 resources
ID3D12RootSignature* d3d12_rootsig = NULL;
{
D3D12_ROOT_SIGNATURE_DESC rootsig_desc = {
.NumParameters = 0,
.pParameters = NULL,
.NumStaticSamplers = 0,
.pStaticSamplers = NULL,
.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT,
};
ID3DBlob* signature = NULL;
ID3DBlob* error = NULL;
hr = D3D12SerializeRootSignature(&rootsig_desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error);
SafeAssert(SUCCEEDED(hr));
hr = ID3D12Device_CreateRootSignature(d3d12_device, 0, ID3D10Blob_GetBufferPointer(signature), ID3D10Blob_GetBufferSize(signature), &IID_ID3D12RootSignature, (void**)&d3d12_rootsig);
SafeAssert(SUCCEEDED(hr));
if (signature)
ID3D10Blob_Release(signature);
if (error)
ID3D10Blob_Release(error);
}
ID3D12PipelineState* d3d12_pipeline_state = NULL;
{
LoadFileResult vs_file = LoadFile(L"vs.bin");
LoadFileResult ps_file = LoadFile(L"ps.bin");
D3D12_INPUT_ELEMENT_DESC input_layout[] = {
{ "V0INPUT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "V1INPUT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeline_state_desc = {
.InputLayout = { input_layout, ArrayLength(input_layout) },
.pRootSignature = d3d12_rootsig,
.VS = { vs_file.data, vs_file.size },
.PS = { ps_file.data, ps_file.size },
.RasterizerState = {
.FillMode = D3D12_FILL_MODE_SOLID,
.CullMode = D3D12_CULL_MODE_NONE,
.FrontCounterClockwise = true,
.DepthBias = D3D12_DEFAULT_DEPTH_BIAS,
.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
.DepthClipEnable = true,
.MultisampleEnable = false,
.AntialiasedLineEnable = false,
.ForcedSampleCount = 0,
.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF,
},
.DepthStencilState = {
.DepthEnable = false,
.StencilEnable = false,
},
.BlendState = {
.RenderTarget[0] = {
.BlendEnable = true,
.LogicOpEnable = false,
.SrcBlend = D3D12_BLEND_SRC_ALPHA,
.DestBlend = D3D12_BLEND_INV_SRC_ALPHA,
.BlendOp = D3D12_BLEND_OP_ADD,
.SrcBlendAlpha = D3D12_BLEND_SRC_ALPHA,
.DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA,
.BlendOpAlpha = D3D12_BLEND_OP_ADD,
.LogicOp = D3D12_LOGIC_OP_NOOP,
.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL,
},
},
.SampleMask = UINT_MAX,
.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
.NumRenderTargets = 1,
.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc.Count = 1,
};
hr = ID3D12Device_CreateGraphicsPipelineState(d3d12_device, &pipeline_state_desc, &IID_ID3D12PipelineState, (void**)&d3d12_pipeline_state);
SafeAssert(SUCCEEDED(hr));
}
ID3D12GraphicsCommandList* d3d12_cmdlist = NULL;
{
hr = ID3D12Device_CreateCommandList(d3d12_device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT, d3d12_cmdallocator, d3d12_pipeline_state, &IID_ID3D12GraphicsCommandList, (void**)&d3d12_cmdlist);
SafeAssert(SUCCEEDED(hr));
hr = ID3D12GraphicsCommandList_Close(d3d12_cmdlist);
SafeAssert(SUCCEEDED(hr));
}
ID3D12Resource* d3d12_vbuffer = NULL;
D3D12_VERTEX_BUFFER_VIEW d3d12_vbuffer_view = { 0 };
{
struct
{
float position[3];
float color[4];
}
vertices[6] = {
{ { 0.0f, 0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } },
};
D3D12_HEAP_PROPERTIES heap_properties = {
.Type = D3D12_HEAP_TYPE_UPLOAD,
.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
.CreationNodeMask = 1,
.VisibleNodeMask = 1,
};
D3D12_RESOURCE_DESC resource_desc = {
.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
.Alignment = 0,
.Width = sizeof(vertices),
.Height = 1,
.DepthOrArraySize = 1,
.MipLevels = 1,
.Format = DXGI_FORMAT_UNKNOWN,
.SampleDesc = {
.Count = 1,
.Quality = 0,
},
.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
.Flags = D3D12_RESOURCE_FLAG_NONE,
};
hr = ID3D12Device_CreateCommittedResource(d3d12_device, &heap_properties, D3D12_HEAP_FLAG_NONE, &resource_desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &IID_ID3D12Resource, (void**)&d3d12_vbuffer);
SafeAssert(SUCCEEDED(hr));
uint8_t* data_begin = NULL;
D3D12_RANGE range = { 0 };
hr = ID3D12Resource_Map(d3d12_vbuffer, 0, &range, (void**)&data_begin);
SafeAssert(SUCCEEDED(hr));
memcpy(data_begin, vertices, sizeof(vertices));
ID3D12Resource_Unmap(d3d12_vbuffer, 0, NULL);
d3d12_vbuffer_view.BufferLocation = ID3D12Resource_GetGPUVirtualAddress(d3d12_vbuffer);
d3d12_vbuffer_view.StrideInBytes = sizeof(vertices[0]);
d3d12_vbuffer_view.SizeInBytes = sizeof(vertices);
}
ID3D12Fence* d3d12_fence = NULL;
HANDLE fence_event = NULL;
UINT64 fence_value = 0;
{
hr = ID3D12Device_CreateFence(d3d12_device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, (void**)&d3d12_fence);
SafeAssert(SUCCEEDED(hr));
fence_event = CreateEventW(NULL, false, false, NULL);
SafeAssert(fence_event && fence_event != INVALID_HANDLE_VALUE);
}
//~ Message Loop
ShowWindow(hwnd, SW_SHOW);
while (true)
{
//~ Wait for previous frame
{
const UINT64 value = fence_value;
hr = ID3D12CommandQueue_Signal(d3d12_cmdqueue, d3d12_fence, fence_value);
SafeAssert(SUCCEEDED(hr));
++fence_value;
if (ID3D12Fence_GetCompletedValue(d3d12_fence) < value)
{
hr = ID3D12Fence_SetEventOnCompletion(d3d12_fence, value, fence_event);
SafeAssert(SUCCEEDED(hr));
WaitForSingleObject(fence_event, INFINITE);
}
frame_index = IDXGISwapChain3_GetCurrentBackBufferIndex(dxgi_swapchain3);
}
//~ Update
{
MSG message;
while (PeekMessageW(&message, 0, 0, 0, PM_REMOVE) > 0)
{
TranslateMessage(&message);
DispatchMessageW(&message);
}
}
if (!g_running)
break;
//~ Render
{
hr = ID3D12CommandAllocator_Reset(d3d12_cmdallocator);
SafeAssert(SUCCEEDED(hr));
hr = ID3D12GraphicsCommandList_Reset(d3d12_cmdlist, d3d12_cmdallocator, d3d12_pipeline_state);
SafeAssert(SUCCEEDED(hr));
ID3D12GraphicsCommandList_SetGraphicsRootSignature(d3d12_cmdlist, d3d12_rootsig);
ID3D12GraphicsCommandList_RSSetViewports(d3d12_cmdlist, 1, &d3d12_viewport);
ID3D12GraphicsCommandList_RSSetScissorRects(d3d12_cmdlist, 1, &d3d12_scissor_rect);
D3D12_RESOURCE_BARRIER barrier = {
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition = {
.pResource = d3d12_render_targets[frame_index],
.StateBefore = D3D12_RESOURCE_STATE_PRESENT,
.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET,
.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
},
};
ID3D12GraphicsCommandList_ResourceBarrier(d3d12_cmdlist, 1, &barrier);
D3D12_CPU_DESCRIPTOR_HANDLE rtvhandle = { 0 };
ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(d3d12_rtvheap, &rtvhandle);
rtvhandle.ptr = (SIZE_T)((INT64)rtvhandle.ptr + rtv_descriptor_size * frame_index);
ID3D12GraphicsCommandList_OMSetRenderTargets(d3d12_cmdlist, 1, &rtvhandle, false, NULL);
float clear_color[] = { 0.2f, 0.0f, 0.3f, 1.0f };
ID3D12GraphicsCommandList_ClearRenderTargetView(d3d12_cmdlist, rtvhandle, clear_color, 0, NULL);
ID3D12GraphicsCommandList_IASetPrimitiveTopology(d3d12_cmdlist, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
ID3D12GraphicsCommandList_IASetVertexBuffers(d3d12_cmdlist, 0, 1, &d3d12_vbuffer_view);
ID3D12GraphicsCommandList_DrawInstanced(d3d12_cmdlist, 3, 1, 0, 0);
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
ID3D12GraphicsCommandList_ResourceBarrier(d3d12_cmdlist, 1, &barrier);
hr = ID3D12GraphicsCommandList_Close(d3d12_cmdlist);
SafeAssert(SUCCEEDED(hr));
}
//~ Execute & Present
{
ID3D12CommandList* cmdlists[] = { (ID3D12CommandList*)d3d12_cmdlist };
ID3D12CommandQueue_ExecuteCommandLists(d3d12_cmdqueue, ArrayLength(cmdlists), cmdlists);
hr = IDXGISwapChain3_Present(dxgi_swapchain3, 1, 0);
SafeAssert(SUCCEEDED(hr));
}
}
// TODO: cleanup. this is left as an exercise for the reader.
return 0;
}
struct VS_INPUT
{
float3 position : V0INPUT;
float4 color : V1INPUT;
};
struct VS_OUTPUT
{
float4 position : SV_POSITION;
float4 color : COLOR0;
};
VS_OUTPUT Vertex(VS_INPUT input)
{
VS_OUTPUT output;
output.position = float4(input.position, 1.0);
output.color = input.color;
return output;
}
float4 Pixel(VS_OUTPUT input) : SV_TARGET
{
return input.color;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment