DirectX 12 native triangle rendering
Drawing a triangle with Command List and Resource Barrier
The articles written on this blog were created with the goal of teaching. Also, the information in the article may be incorrect, so please be careful.
The overall flow of drawing a triangle in DirectX 12 is similar to DX11, but resource status and command submission are managed directly by the developer. Immediately after creating the deviceDrawInstancedIt is not a structure that can be called.
object needed
Device
CommandQueue
SwapChain
DescriptorHeap(RTV)
CommandAllocator
GraphicsCommandList
Fence
VertexBuffer
RootSignature
PipelineState
vertex buffer
struct Vertex
{
DirectX::XMFLOAT3 Position;
DirectX::XMFLOAT4 Color;
};
const Vertex Vertices[] =
{
{ { 0.0f, 0.5f, 0.0f }, { 1, 0, 0, 1 } },
{ { 0.5f, -0.5f, 0.0f }, { 0, 1, 0, 1 } },
{ { -0.5f, -0.5f, 0.0f }, { 0, 0, 1, 1 } }
};
const UINT BufferSize = sizeof(Vertices);
CD3DX12_HEAP_PROPERTIES HeapProperties(D3D12_HEAP_TYPE_UPLOAD);
CD3DX12_RESOURCE_DESC BufferDescription =
CD3DX12_RESOURCE_DESC::Buffer(BufferSize);
Device->CreateCommittedResource(
&HeapProperties,
D3D12_HEAP_FLAG_NONE,
&BufferDescription,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&VertexBuffer)
);
void* MappedData = nullptr;
VertexBuffer->Map(0, nullptr, &MappedData);
std::memcpy(MappedData, Vertices, BufferSize);
VertexBuffer->Unmap(0, nullptr);
VertexBufferView.BufferLocation = VertexBuffer->GetGPUVirtualAddress();
VertexBufferView.SizeInBytes = BufferSize;
VertexBufferView.StrideInBytes = sizeof(Vertex);
The example simply used the Upload Heap. In practice, copying frequently read vertex data to the Default Heap is advantageous for GPU access performance.
Record drawing in Command List
CommandAllocator->Reset();
CommandList->Reset(CommandAllocator, PipelineState);
auto ToRenderTarget = CD3DX12_RESOURCE_BARRIER::Transition(
BackBuffers[FrameIndex],
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RENDER_TARGET
);
CommandList->ResourceBarrier(1, &ToRenderTarget);
CommandList->RSSetViewports(1, &Viewport);
CommandList->RSSetScissorRects(1, &ScissorRect);
CommandList->OMSetRenderTargets(1, &CurrentRTV, FALSE, nullptr);
const float ClearColor[] = { 0.08f, 0.08f, 0.12f, 1.0f };
CommandList->ClearRenderTargetView(CurrentRTV, ClearColor, 0, nullptr);
CommandList->SetGraphicsRootSignature(RootSignature);
CommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
CommandList->IASetVertexBuffers(0, 1, &VertexBufferView);
CommandList->DrawInstanced(3, 1, 0, 0);
auto ToPresent = CD3DX12_RESOURCE_BARRIER::Transition(
BackBuffers[FrameIndex],
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT
);
CommandList->ResourceBarrier(1, &ToPresent);
CommandList->Close();
ID3D12CommandList* Lists[] = { CommandList };
CommandQueue->ExecuteCommandLists(1, Lists);
SwapChain->Present(1, 0);
BackBuffer goes back and forth between Present state and RenderTarget state. If you omit this state transition, an error will occur in the Debug Layer or the screen will not be drawn correctly.
DX12's triangle code is long, but its structure is clear. The three key steps are recording the command, submitting it to the queue, and checking GPU completion with Fence.