Skip to content

Instantly share code, notes, and snippets.

@ALEXMORF
Last active December 26, 2023 23:28
Show Gist options
  • Save ALEXMORF/47ce69b2b6c7cc4184680f5bfa2c8d9d to your computer and use it in GitHub Desktop.
Save ALEXMORF/47ce69b2b6c7cc4184680f5bfa2c8d9d to your computer and use it in GitHub Desktop.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_2.h>
#include <dxgi1_3.h>
#include <dxgi1_4.h>
#include <d3dcompiler.h>
#define ARRAY_COUNT(Array) (sizeof(Array)/sizeof((Array)[0]))
#define BACKBUFFER_COUNT 2
static bool DoneRunning;
LRESULT CALLBACK
WindowCallback(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam)
{
LRESULT Result = 0;
switch (Message)
{
case WM_CLOSE:
case WM_QUIT:
{
DoneRunning = true;
} break;
default:
{
Result = DefWindowProc(Window, Message, WParam, LParam);
} break;
}
return Result;
}
int CALLBACK
WinMain(HINSTANCE CurrentInstance,
HINSTANCE PrevInstance,
LPSTR CommandLine,
int ShowCode)
{
//
//
// Win32 Init
WNDCLASSA WindowClass = {};
WindowClass.lpfnWndProc = WindowCallback;
WindowClass.hInstance = CurrentInstance;
WindowClass.hCursor = LoadCursor(0, IDC_ARROW);
WindowClass.lpszClassName = "MinimalD3DWindowClass";
RegisterClassA(&WindowClass);
HWND Window = CreateWindowExA(0, WindowClass.lpszClassName,
"Minimal D3D12",
WS_OVERLAPPEDWINDOW|WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
0, 0, CurrentInstance, 0);
//
//
// D3D12 Init
ID3D12Device *Device = 0;
D3D12CreateDevice(0, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&Device));
IDXGIFactory2 *Factory2 = 0;
CreateDXGIFactory2(0, IID_PPV_ARGS(&Factory2));
ID3D12CommandQueue *CommandQueue = 0;
D3D12_COMMAND_QUEUE_DESC CommandQueueDesc = {};
CommandQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
CommandQueueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
CommandQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
CommandQueueDesc.NodeMask = 0;
Device->CreateCommandQueue(&CommandQueueDesc, IID_PPV_ARGS(&CommandQueue));
IDXGISwapChain1 *SwapChain1 = 0;
DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = {};
SwapChainDesc.Width = 0; // uses window width
SwapChainDesc.Height = 0; // uses window height
SwapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
SwapChainDesc.Stereo = FALSE;
SwapChainDesc.SampleDesc.Count = 1; // Single sample per pixel
SwapChainDesc.SampleDesc.Quality = 0;
SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
SwapChainDesc.BufferCount = BACKBUFFER_COUNT;
SwapChainDesc.Scaling = DXGI_SCALING_STRETCH;
SwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
SwapChainDesc.Flags = 0;
Factory2->CreateSwapChainForHwnd(CommandQueue, // for d3d12, must be command queue instead of device
Window,
&SwapChainDesc,
0, // doesn't support fullscreen
0,
&SwapChain1);
IDXGISwapChain3 *SwapChain = 0;
SwapChain1->QueryInterface(IID_PPV_ARGS(&SwapChain));
UINT BackbufferIndex = SwapChain->GetCurrentBackBufferIndex();
ID3D12CommandAllocator *CommandAllocator = 0;
Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&CommandAllocator));
ID3D12GraphicsCommandList *CommandList = 0;
Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT,
CommandAllocator, 0,
IID_PPV_ARGS(&CommandList));
CommandList->Close();
//NOTE(chen): resources need descriptors as their handles
// descriptor heap is a space for placing descriptors
ID3D12DescriptorHeap *DescriptorHeap = 0;
D3D12_DESCRIPTOR_HEAP_DESC DescriptorHeapDesc = {};
DescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
DescriptorHeapDesc.NumDescriptors = BACKBUFFER_COUNT;
DescriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
DescriptorHeapDesc.NodeMask = 0;
Device->CreateDescriptorHeap(&DescriptorHeapDesc, IID_PPV_ARGS(&DescriptorHeap));
ID3D12Resource *Backbuffers[BACKBUFFER_COUNT] = {};
D3D12_CPU_DESCRIPTOR_HANDLE BackbufferDescriptors[BACKBUFFER_COUNT] = {};
UINT RTVDescriptorSize = Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
for (int BufferIndex = 0; BufferIndex < BACKBUFFER_COUNT; ++BufferIndex)
{
D3D12_CPU_DESCRIPTOR_HANDLE BufferRTVDescriptor = DescriptorHeap->GetCPUDescriptorHandleForHeapStart();
BufferRTVDescriptor.ptr += BufferIndex * RTVDescriptorSize;
ID3D12Resource *Backbuffer = 0;
SwapChain->GetBuffer(BufferIndex, IID_PPV_ARGS(&Backbuffer));
Device->CreateRenderTargetView(Backbuffer, 0, BufferRTVDescriptor);
Backbuffers[BufferIndex] = Backbuffer;
BackbufferDescriptors[BufferIndex] = BufferRTVDescriptor;
}
ID3D12RootSignature *RootSignature = 0;
D3D12_ROOT_SIGNATURE_DESC RootSignatureDesc = {};
RootSignatureDesc.NumParameters = 0;
RootSignatureDesc.pParameters = 0;
RootSignatureDesc.NumStaticSamplers = 0;
RootSignatureDesc.pStaticSamplers = 0;
RootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
ID3DBlob *Signature = 0;
D3D12SerializeRootSignature(&RootSignatureDesc,
D3D_ROOT_SIGNATURE_VERSION_1_0,
&Signature, 0);
Device->CreateRootSignature(0, Signature->GetBufferPointer(),
Signature->GetBufferSize(),
IID_PPV_ARGS(&RootSignature));
ID3DBlob *VSCodeBlob = 0;
D3DCompileFromFile(L"./shader.hlsl", 0, 0, "VSMain",
"vs_5_0", D3DCOMPILE_OPTIMIZATION_LEVEL3,
0, &VSCodeBlob, 0);
ID3DBlob *PSCodeBlob = 0;
D3DCompileFromFile(L"./shader.hlsl", 0, 0, "PSMain",
"ps_5_0", D3DCOMPILE_OPTIMIZATION_LEVEL3,
0, &PSCodeBlob, 0);
D3D12_INPUT_ELEMENT_DESC InputElements[] = {
{"POS", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"COL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
};
ID3D12PipelineState *PSO = 0;
D3D12_GRAPHICS_PIPELINE_STATE_DESC PSODesc = {};
PSODesc.pRootSignature = RootSignature;
PSODesc.VS = {VSCodeBlob->GetBufferPointer(), VSCodeBlob->GetBufferSize()};
PSODesc.PS = {PSCodeBlob->GetBufferPointer(), PSCodeBlob->GetBufferSize()};
PSODesc.BlendState.AlphaToCoverageEnable = FALSE;
PSODesc.BlendState.IndependentBlendEnable = FALSE;
for (int I = 0; I < ARRAY_COUNT(PSODesc.BlendState.RenderTarget); ++I)
{
PSODesc.BlendState.RenderTarget[I].BlendEnable = FALSE;
PSODesc.BlendState.RenderTarget[I].LogicOpEnable = FALSE;
PSODesc.BlendState.RenderTarget[I].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
}
PSODesc.SampleMask = UINT_MAX;
PSODesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
PSODesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
PSODesc.RasterizerState.FrontCounterClockwise = TRUE;
PSODesc.DepthStencilState.DepthEnable = FALSE;
PSODesc.DepthStencilState.StencilEnable = FALSE;
PSODesc.InputLayout.pInputElementDescs = InputElements;
PSODesc.InputLayout.NumElements = ARRAY_COUNT(InputElements);
PSODesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
PSODesc.NumRenderTargets = 1;
PSODesc.RTVFormats[0] = SwapChainDesc.Format;
PSODesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
PSODesc.SampleDesc.Count = 1; // one sample per pixel
PSODesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
Device->CreateGraphicsPipelineState(&PSODesc, IID_PPV_ARGS(&PSO));
float Vertices[] = {
// position color
0.0f, 0.7f, 1.0f, 0.0f, 0.0f,
-0.4f, -0.5f, 0.0f, 1.0f, 0.0f,
0.4f, -0.5f, 0.0f, 0.0f, 1.0f,
};
//NOTE(chen): Storing vertex buffer in upload heap isn't the best option,
// as it doesn't get the best GPU bandwidth. Best practice is
// to write the buffer onto an upload heap then copy the upload heap
// to a buffer on default heap. However the best practice is
// not done here to keep the code simple.
ID3D12Resource *VertexBuffer = 0;
{
D3D12_HEAP_PROPERTIES UploadHeapProperties = {};
UploadHeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;
UploadHeapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
UploadHeapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
D3D12_RESOURCE_DESC ResourceDesc = {};
ResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
ResourceDesc.Alignment = 0;
ResourceDesc.Width = sizeof(Vertices);
ResourceDesc.Height = 1;
ResourceDesc.DepthOrArraySize = 1;
ResourceDesc.MipLevels = 1;
ResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
ResourceDesc.SampleDesc.Count = 1;
ResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
ResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
Device->CreateCommittedResource(&UploadHeapProperties,
D3D12_HEAP_FLAG_NONE,
&ResourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
0,
IID_PPV_ARGS(&VertexBuffer));
D3D12_RANGE ReadRange = {};
void *MappedAddress = 0;
VertexBuffer->Map(0, &ReadRange, &MappedAddress);
memcpy(MappedAddress, Vertices, sizeof(Vertices));
VertexBuffer->Unmap(0, 0);
}
D3D12_VERTEX_BUFFER_VIEW VertexBufferView = {};
VertexBufferView.BufferLocation = VertexBuffer->GetGPUVirtualAddress();
VertexBufferView.SizeInBytes = sizeof(Vertices);
VertexBufferView.StrideInBytes = 5 * sizeof(float);
// sync objects
UINT64 FenceValue = 0;
ID3D12Fence *Fence = 0;
Device->CreateFence(FenceValue, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&Fence));
HANDLE FenceEvent = CreateEventA(0, 0, FALSE, 0);
//
//
// Render Loop
while (!DoneRunning)
{
MSG Message;
if (PeekMessageA(&Message, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
CommandAllocator->Reset();
CommandList->Reset(CommandAllocator, 0);
ID3D12Resource *Backbuffer = Backbuffers[BackbufferIndex];
D3D12_CPU_DESCRIPTOR_HANDLE BackbufferDescriptor = BackbufferDescriptors[BackbufferIndex];
D3D12_VIEWPORT Viewport = {};
Viewport.Width = (float)Backbuffer->GetDesc().Width;
Viewport.Height = (float)Backbuffer->GetDesc().Height;
Viewport.MinDepth = 0.0f;
Viewport.MaxDepth = 1.0f;
D3D12_RECT ScissorRect = {};
ScissorRect.right = (LONG)Backbuffer->GetDesc().Width;
ScissorRect.bottom = (LONG)Backbuffer->GetDesc().Height;
CommandList->SetPipelineState(PSO);
CommandList->SetGraphicsRootSignature(RootSignature);
CommandList->RSSetViewports(1, &Viewport);
CommandList->RSSetScissorRects(1, &ScissorRect);
D3D12_RESOURCE_BARRIER RenderBarrier = {};
RenderBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
RenderBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
RenderBarrier.Transition.pResource = Backbuffer;
RenderBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
RenderBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
CommandList->ResourceBarrier(1, &RenderBarrier);
CommandList->OMSetRenderTargets(1, &BackbufferDescriptor, 0, 0);
float ClearColor[] = {0.1f, 0.1f, 0.1f, 1.0f};
CommandList->ClearRenderTargetView(BackbufferDescriptor, ClearColor, 0, 0);
CommandList->IASetVertexBuffers(0, 1, &VertexBufferView);
CommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
CommandList->DrawInstanced(3, 1, 0, 0);
D3D12_RESOURCE_BARRIER PresentBarrier = {};
PresentBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
PresentBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
PresentBarrier.Transition.pResource = Backbuffer;
PresentBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
PresentBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
CommandList->ResourceBarrier(1, &PresentBarrier);
CommandList->Close();
ID3D12CommandList *CommandLists[] = {CommandList};
CommandQueue->ExecuteCommandLists(1, CommandLists);
SwapChain->Present(1, 0);
//NOTE(chen): waiting until all the commands are executed,
// effectively doing single buffering. It's inefficient
// but simple and easy to understand.
FenceValue += 1;
CommandQueue->Signal(Fence, FenceValue);
if (Fence->GetCompletedValue() < FenceValue)
{
Fence->SetEventOnCompletion(FenceValue, FenceEvent);
WaitForSingleObject(FenceEvent, INFINITE);
}
BackbufferIndex = SwapChain->GetCurrentBackBufferIndex();
Sleep(2);
}
return 0;
}
struct vs_in
{
float2 P : POS;
float3 Col : COL;
};
struct vs_out
{
float4 SV_P : SV_POSITION;
float3 Col : COL;
};
vs_out VSMain(vs_in In)
{
vs_out Out;
Out.SV_P = float4(In.P, 0.0, 1.0);
Out.Col = In.Col;
return Out;
}
float4 PSMain(vs_out In) : SV_TARGET
{
return float4(sqrt(In.Col), 1.0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment