Updated to handle frame buffering a bit better

This commit is contained in:
William McVicar
2024-04-10 23:35:05 +02:00
parent 90f8185cbc
commit 7293cde4bb
3 changed files with 59 additions and 36 deletions

View File

@@ -24,7 +24,6 @@ public:
virtual void RecordCommands() = 0; virtual void RecordCommands() = 0;
virtual void Render() = 0; virtual void Render() = 0;
virtual void WaitForPreviousFrame() = 0;
virtual void Destroy() = 0; virtual void Destroy() = 0;
static std::unique_ptr<IRenderer> Create(const RendererCreateParams& params); static std::unique_ptr<IRenderer> Create(const RendererCreateParams& params);

View File

@@ -9,7 +9,7 @@ namespace dx12_starter
namespace namespace
{ {
static constexpr UINT s_MaxBackBufferCount = 2; static constexpr UINT s_FrameCount = 2;
auto ParseAPIVersion(APIVersion version) -> D3D_FEATURE_LEVEL auto ParseAPIVersion(APIVersion version) -> D3D_FEATURE_LEVEL
{ {
@@ -96,9 +96,11 @@ struct RendererDX12 final : public IRenderer
void RecordCommands(); void RecordCommands();
void Render(); void Render();
void WaitForPreviousFrame();
void Destroy(); void Destroy();
void WaitForGPU();
void AdvanceFrame();
HWND m_windowHandle = nullptr; HWND m_windowHandle = nullptr;
struct Vertex struct Vertex
@@ -112,8 +114,8 @@ struct RendererDX12 final : public IRenderer
CD3DX12_RECT m_scissorRect = {}; CD3DX12_RECT m_scissorRect = {};
ComPtr<IDXGISwapChain3> m_swapChain = nullptr; ComPtr<IDXGISwapChain3> m_swapChain = nullptr;
ComPtr<ID3D12Device> m_device = nullptr; ComPtr<ID3D12Device> m_device = nullptr;
ComPtr<ID3D12Resource> m_renderTargets[s_MaxBackBufferCount] = {}; ComPtr<ID3D12Resource> m_renderTargets[s_FrameCount] = {};
ComPtr<ID3D12CommandAllocator> m_commandAllocator = nullptr; ComPtr<ID3D12CommandAllocator> m_commandAllocators[s_FrameCount] = {};
ComPtr<ID3D12CommandQueue> m_commandQueue = nullptr; ComPtr<ID3D12CommandQueue> m_commandQueue = nullptr;
ComPtr<ID3D12RootSignature> m_rootSignature = nullptr; ComPtr<ID3D12RootSignature> m_rootSignature = nullptr;
ComPtr<ID3D12DescriptorHeap> m_rtvHeap = nullptr; ComPtr<ID3D12DescriptorHeap> m_rtvHeap = nullptr;
@@ -129,7 +131,7 @@ struct RendererDX12 final : public IRenderer
UINT m_frameIndex = 0u; UINT m_frameIndex = 0u;
HANDLE m_fenceEvent = nullptr; HANDLE m_fenceEvent = nullptr;
ComPtr<ID3D12Fence> m_fence = nullptr; ComPtr<ID3D12Fence> m_fence = nullptr;
UINT64 m_fenceValue = 0llu; UINT64 m_fenceValues[s_FrameCount] = {};
}; };
void RendererDX12::RecordCommands() void RendererDX12::RecordCommands()
@@ -137,7 +139,7 @@ void RendererDX12::RecordCommands()
// Command list allocators can only be reset when the associated // Command list allocators can only be reset when the associated
// command lists have finished execution on the GPU; apps should use // command lists have finished execution on the GPU; apps should use
// fences to determine GPU execution progress. // fences to determine GPU execution progress.
if (FAILED(m_commandAllocator->Reset())) if (FAILED(m_commandAllocators[m_frameIndex]->Reset()))
{ {
MessageBox(m_windowHandle, "Failed to reset command allocator", "Command Allocator Failure", MB_ICONERROR | MB_OK); MessageBox(m_windowHandle, "Failed to reset command allocator", "Command Allocator Failure", MB_ICONERROR | MB_OK);
return; return;
@@ -146,7 +148,7 @@ void RendererDX12::RecordCommands()
// However, when ExecuteCommandList() is called on a particular command // However, when ExecuteCommandList() is called on a particular command
// list, that command list can then be reset at any time and must be before // list, that command list can then be reset at any time and must be before
// re-recording. // re-recording.
if (FAILED(m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState.Get()))) if (FAILED(m_commandList->Reset(m_commandAllocators[m_frameIndex].Get(), m_pipelineState.Get())))
{ {
MessageBox(m_windowHandle, "Failed to reset command list", "Command Allocator Failure", MB_ICONERROR | MB_OK); MessageBox(m_windowHandle, "Failed to reset command list", "Command Allocator Failure", MB_ICONERROR | MB_OK);
return; return;
@@ -196,42 +198,64 @@ void RendererDX12::Render()
{ {
MessageBox(m_windowHandle, "Failed to preset frame", "Swapchain Failure", MB_ICONERROR | MB_OK); MessageBox(m_windowHandle, "Failed to preset frame", "Swapchain Failure", MB_ICONERROR | MB_OK);
} }
AdvanceFrame();
} }
void RendererDX12::Destroy() void RendererDX12::Destroy()
{ {
WaitForPreviousFrame(); WaitForGPU();
CloseHandle(m_fenceEvent); CloseHandle(m_fenceEvent);
} }
void RendererDX12::WaitForPreviousFrame() void RendererDX12::WaitForGPU()
{ {
// WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE.
// This is code implemented as such for simplicity. The D3D12HelloFrameBuffering
// sample illustrates how to use fences for efficient resource usage and to
// maximize GPU utilization.
// Signal and increment the fence value. // Signal and increment the fence value.
const UINT64 fence = std::exchange(m_fenceValue, m_fenceValue + 1); if (FAILED(m_commandQueue->Signal(m_fence.Get(), m_fenceValues[m_frameIndex])))
if (FAILED(m_commandQueue->Signal(m_fence.Get(), fence)))
{ {
MessageBox(m_windowHandle, "Failed to signal command queue fence", "Command Queue Failure", MB_ICONERROR | MB_OK); MessageBox(m_windowHandle, "Failed to signal command queue fence", "Command Queue Failure", MB_ICONERROR | MB_OK);
return; return;
} }
// Wait until the previous frame is finished. // Wait until the previous frame is finished.
if (m_fence->GetCompletedValue() < fence) if (FAILED(m_fence->SetEventOnCompletion(m_fenceValues[m_frameIndex], m_fenceEvent)))
{ {
if (FAILED(m_fence->SetEventOnCompletion(fence, m_fenceEvent))) MessageBox(m_windowHandle, "Failed to signal command queue fence", "Command Queue Failure", MB_ICONERROR | MB_OK);
return;
}
WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
m_fenceValues[m_frameIndex]++;
}
void RendererDX12::AdvanceFrame()
{
// Schedule a Signal command in the queue.
const UINT64 currentFenceValue = m_fenceValues[m_frameIndex];
if (FAILED(m_commandQueue->Signal(m_fence.Get(), currentFenceValue)))
{
MessageBox(m_windowHandle, "Failed to signal command queue fence", "Command Queue Failure", MB_ICONERROR | MB_OK);
return;
}
// Update the frame index.
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
// If the next frame is not ready to be rendered yet, wait until it is ready.
if (m_fence->GetCompletedValue() < m_fenceValues[m_frameIndex])
{
if (FAILED(m_fence->SetEventOnCompletion(m_fenceValues[m_frameIndex], m_fenceEvent)))
{ {
MessageBox(m_windowHandle, "Failed to signal command queue fence", "Command Queue Failure", MB_ICONERROR | MB_OK); MessageBox(m_windowHandle, "Failed to set fence event on completion", "Fence Failure", MB_ICONERROR | MB_OK);
return; return;
} }
WaitForSingleObject(m_fenceEvent, INFINITE); WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
} }
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex(); // Set the fence value for the next frame.
m_fenceValues[m_frameIndex] = currentFenceValue + 1;
} }
std::unique_ptr<IRenderer> IRenderer::Create(const RendererCreateParams& params) std::unique_ptr<IRenderer> IRenderer::Create(const RendererCreateParams& params)
@@ -363,13 +387,14 @@ std::unique_ptr<IRenderer> IRenderer::Create(const RendererCreateParams& params)
renderer->m_device->CreateRenderTargetView(renderer->m_renderTargets[i].Get(), nullptr, rtvHandle); renderer->m_device->CreateRenderTargetView(renderer->m_renderTargets[i].Get(), nullptr, rtvHandle);
rtvHandle.Offset(1, renderer->m_rtvDescriptorSize); rtvHandle.Offset(1, renderer->m_rtvDescriptorSize);
if (FAILED(renderer->m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&renderer->m_commandAllocators[i]))))
{
MessageBoxA(hWnd, "Failed to create Command Allocator", "Device Error", MB_ICONERROR | MB_OK);
return nullptr;
}
} }
if (FAILED(renderer->m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&renderer->m_commandAllocator))))
{
MessageBoxA(hWnd, "Failed to create Command Allocator", "Device Error", MB_ICONERROR | MB_OK);
return nullptr;
}
// Create an empty root signature. // Create an empty root signature.
CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
@@ -414,9 +439,10 @@ std::unique_ptr<IRenderer> IRenderer::Create(const RendererCreateParams& params)
// Define the vertex input layout. // Define the vertex input layout.
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{ {
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}}; {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
};
// Describe and create the graphics pipeline state object (PSO). // Describe and create the graphics pipeline state object (PSO).
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
@@ -441,7 +467,7 @@ std::unique_ptr<IRenderer> IRenderer::Create(const RendererCreateParams& params)
} }
// Create the command list. // Create the command list.
if (FAILED(renderer->m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, renderer->m_commandAllocator.Get(), renderer->m_pipelineState.Get(), IID_PPV_ARGS(&renderer->m_commandList)))) if (FAILED(renderer->m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, renderer->m_commandAllocators[renderer->m_frameIndex].Get(), renderer->m_pipelineState.Get(), IID_PPV_ARGS(&renderer->m_commandList))))
{ {
MessageBox(nullptr, "Failed to Create Command List", "Device failure", MB_ICONERROR | MB_OK); MessageBox(nullptr, "Failed to Create Command List", "Device failure", MB_ICONERROR | MB_OK);
return nullptr; return nullptr;
@@ -512,16 +538,15 @@ std::unique_ptr<IRenderer> IRenderer::Create(const RendererCreateParams& params)
return nullptr; return nullptr;
} }
renderer->m_fenceValue = 1; renderer->m_fenceValues[renderer->m_frameIndex]++;
renderer->m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); if(renderer->m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); !renderer->m_fenceEvent)
if (renderer->m_fenceEvent == nullptr)
{ {
MessageBoxA(hWnd, "Failed to create Fence Event Handle", "Win32API Error", MB_ICONERROR | MB_OK); MessageBoxA(hWnd, "Failed to create Fence Event Handle", "Win32API Error", MB_ICONERROR | MB_OK);
return nullptr; return nullptr;
} }
renderer->WaitForPreviousFrame(); renderer->WaitForGPU();
return renderer; return renderer;
} }

View File

@@ -34,7 +34,6 @@ auto main() -> int
renderer->RecordCommands(); renderer->RecordCommands();
renderer->Render(); renderer->Render();
renderer->WaitForPreviousFrame();
} }
renderer->Destroy(); renderer->Destroy();