From cc24ba1d13b18bcf21da0d029c512bf846ea93ec Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 14 Apr 2024 15:52:13 -0700 Subject: [PATCH 1/4] Fix DXGI present failures caused by race conditions --- CMakeLists.txt | 1 + src/D3D12Renderer.cpp | 152 +++++++++++++++++++---------------- src/D3D12Renderer.hpp | 14 +++- src/d3d12/ComPtr.hpp | 7 ++ src/d3d12/CommandContext.cpp | 83 +++++++++++++++++++ src/d3d12/CommandContext.hpp | 34 ++++++++ 6 files changed, 217 insertions(+), 74 deletions(-) create mode 100644 src/d3d12/ComPtr.hpp create mode 100644 src/d3d12/CommandContext.cpp create mode 100644 src/d3d12/CommandContext.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 431024a..fb410d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ add_library(reframework-d2d SHARED src/D3D12Renderer.cpp src/DrawList.cpp src/Plugin.cpp + src/d3d12/CommandContext.cpp ) target_include_directories(reframework-d2d PRIVATE src diff --git a/src/D3D12Renderer.cpp b/src/D3D12Renderer.cpp index 5d03254..5a58c6f 100644 --- a/src/D3D12Renderer.cpp +++ b/src/D3D12Renderer.cpp @@ -21,20 +21,6 @@ D3D12Renderer::D3D12Renderer(IDXGISwapChain* swapchain_, ID3D12Device* device_, throw std::runtime_error{"Failed to query D3D11On12 device"}; } - // Create command allocator - if (FAILED(m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_cmd_allocator)))) { - throw std::runtime_error{"Failed to create command allocator"}; - } - - // Create command list - if (FAILED(m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_cmd_allocator.Get(), nullptr, IID_PPV_ARGS(&m_cmd_list)))) { - throw std::runtime_error{"Failed to create command list"}; - } - - if (FAILED(m_cmd_list->Close())) { - throw std::runtime_error{"Failed to close command list"}; - } - // Create RTV descriptor heap D3D12_DESCRIPTOR_HEAP_DESC rtv_desc = {}; rtv_desc.NumDescriptors = (int)RTV::COUNT; @@ -63,11 +49,24 @@ D3D12Renderer::D3D12Renderer(IDXGISwapChain* swapchain_, ID3D12Device* device_, } for (int i = 0; i < swapchain_desc.BufferCount; i++) { + // Create a command context for each back buffer. + // We create one for each because the GPU could be doing work on one while we're recording commands on another, + // before we submit them to the command queue. If we did not do this, we would run into some race conditions. + auto cmd_context = std::make_unique(m_device.Get()); + + if (cmd_context->is_setup) { + m_cmd_contexts.push_back(std::move(cmd_context)); + } else { + throw std::runtime_error{"Failed to create command context"}; + } + if (SUCCEEDED(m_swapchain->GetBuffer((UINT)i, IID_PPV_ARGS(&m_rts[i])))) { m_device->CreateRenderTargetView(m_rts[i].Get(), nullptr, get_cpu_rtv((RTV)i)); } } + m_frames_in_flight = m_cmd_contexts.size(); + // Create D2D render target auto& backbuffer = get_rt(RTV::BACKBUFFER_0); auto backbuffer_desc = backbuffer->GetDesc(); @@ -243,54 +242,67 @@ D3D12Renderer::D3D12Renderer(IDXGISwapChain* swapchain_, ID3D12Device* device_, throw std::runtime_error{"Failed to create pipeline state"}; } - D3D12_HEAP_PROPERTIES vertbuf_heap_props{}; - vertbuf_heap_props.Type = D3D12_HEAP_TYPE_UPLOAD; - vertbuf_heap_props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - vertbuf_heap_props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - - D3D12_RESOURCE_DESC vertbuf_desc{}; - vertbuf_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - vertbuf_desc.Width = sizeof(Vert) * 6; - vertbuf_desc.Height = 1; - vertbuf_desc.DepthOrArraySize = 1; - vertbuf_desc.MipLevels = 1; - vertbuf_desc.Format = DXGI_FORMAT_UNKNOWN; - vertbuf_desc.SampleDesc.Count = 1; - vertbuf_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - vertbuf_desc.Flags = D3D12_RESOURCE_FLAG_NONE; - - if (FAILED(m_device->CreateCommittedResource(&vertbuf_heap_props, D3D12_HEAP_FLAG_NONE, &vertbuf_desc, - D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&m_vert_buffer)))) { - throw std::runtime_error{"Failed to create vertex buffer"}; - } + for (auto i = 0; i < m_frames_in_flight; i++) { + auto resources = std::make_unique(); + auto& vert_buffer = resources->vert_buffer; + + D3D12_HEAP_PROPERTIES vertbuf_heap_props{}; + vertbuf_heap_props.Type = D3D12_HEAP_TYPE_UPLOAD; + vertbuf_heap_props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + vertbuf_heap_props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + + D3D12_RESOURCE_DESC vertbuf_desc{}; + vertbuf_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + vertbuf_desc.Width = sizeof(Vert) * 6; + vertbuf_desc.Height = 1; + vertbuf_desc.DepthOrArraySize = 1; + vertbuf_desc.MipLevels = 1; + vertbuf_desc.Format = DXGI_FORMAT_UNKNOWN; + vertbuf_desc.SampleDesc.Count = 1; + vertbuf_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + vertbuf_desc.Flags = D3D12_RESOURCE_FLAG_NONE; + + if (FAILED(m_device->CreateCommittedResource(&vertbuf_heap_props, D3D12_HEAP_FLAG_NONE, &vertbuf_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&vert_buffer)))) { + throw std::runtime_error{"Failed to create vertex buffer"}; + } - // Upload the verticies. - D3D12_RANGE range{}; - Vert* verts{}; + // Upload the verticies. + D3D12_RANGE range{}; + Vert* verts{}; - if (FAILED(m_vert_buffer->Map(0, &range, (void**)&verts))) { - throw std::runtime_error{"Failed to map vertex buffer"}; - } + if (FAILED(vert_buffer->Map(0, &range, (void**)&verts))) { + throw std::runtime_error{"Failed to map vertex buffer"}; + } - auto w = (float)m_width; - auto h = (float)m_height; + auto w = (float)m_width; + auto h = (float)m_height; - // First triangle (top-left of screen). - verts[0] = {0.0f, 0.0f, 0.0f, 0.0f, 0xFFFFFFFF}; - verts[1] = {w, 0.0f, 1.0f, 0.0f, 0xFFFFFFFF}; - verts[2] = {0.0f, h, 0.0f, 1.0f, 0xFFFFFFFF}; + // First triangle (top-left of screen). + verts[0] = {0.0f, 0.0f, 0.0f, 0.0f, 0xFFFFFFFF}; + verts[1] = {w, 0.0f, 1.0f, 0.0f, 0xFFFFFFFF}; + verts[2] = {0.0f, h, 0.0f, 1.0f, 0xFFFFFFFF}; - // Second triangle (bottom-right of screen). - verts[3] = {w, 0.0f, 1.0f, 0.0f, 0xFFFFFFFF}; - verts[4] = {w, h, 1.0f, 1.0f, 0xFFFFFFFF}; - verts[5] = {0.0f, h, 0.0f, 1.0f, 0xFFFFFFFF}; + // Second triangle (bottom-right of screen). + verts[3] = {w, 0.0f, 1.0f, 0.0f, 0xFFFFFFFF}; + verts[4] = {w, h, 1.0f, 1.0f, 0xFFFFFFFF}; + verts[5] = {0.0f, h, 0.0f, 1.0f, 0xFFFFFFFF}; - m_vert_buffer->Unmap(0, &range); + vert_buffer->Unmap(0, &range); + m_render_resources.push_back(std::move(resources)); + } } void D3D12Renderer::render(std::function draw_fn, bool update_d2d) { - m_cmd_allocator->Reset(); - m_cmd_list->Reset(m_cmd_allocator.Get(), nullptr); + auto& cmd_context = m_cmd_contexts[m_swapchain->GetCurrentBackBufferIndex() % m_cmd_contexts.size()]; + auto& resources = m_render_resources[m_swapchain->GetCurrentBackBufferIndex() % m_render_resources.size()]; + + // Wait for the command context to be ready. + cmd_context->wait(INFINITE); + cmd_context->has_commands = true; + + auto& cmd_list = cmd_context->cmd_list; + auto& vert_buffer = resources->vert_buffer; if (update_d2d) { m_d3d11on12_device->AcquireWrappedResources(m_wrapped_rt.GetAddressOf(), 1); @@ -318,27 +330,27 @@ void D3D12Renderer::render(std::function draw_fn, bool update vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0; - m_cmd_list->RSSetViewports(1, &vp); + cmd_list->RSSetViewports(1, &vp); D3D12_RECT sr{}; sr.left = 0; sr.top = 0; sr.right = m_width; sr.bottom = m_height; - m_cmd_list->RSSetScissorRects(1, &sr); + cmd_list->RSSetScissorRects(1, &sr); D3D12_VERTEX_BUFFER_VIEW vbv{}; - vbv.BufferLocation = m_vert_buffer->GetGPUVirtualAddress(); + vbv.BufferLocation = vert_buffer->GetGPUVirtualAddress(); vbv.SizeInBytes = sizeof(Vert) * 6; vbv.StrideInBytes = sizeof(Vert); - m_cmd_list->IASetVertexBuffers(0, 1, &vbv); - m_cmd_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - m_cmd_list->SetPipelineState(m_pipeline_state.Get()); - m_cmd_list->SetGraphicsRootSignature(m_root_signature.Get()); - m_cmd_list->SetGraphicsRoot32BitConstants(0, 16, mvp, 0); + cmd_list->IASetVertexBuffers(0, 1, &vbv); + cmd_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + cmd_list->SetPipelineState(m_pipeline_state.Get()); + cmd_list->SetGraphicsRootSignature(m_root_signature.Get()); + cmd_list->SetGraphicsRoot32BitConstants(0, 16, mvp, 0); const float blend_factor[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - m_cmd_list->OMSetBlendFactor(blend_factor); + cmd_list->OMSetBlendFactor(blend_factor); // Draw to the back buffer. auto bb_index = m_swapchain->GetCurrentBackBufferIndex(); @@ -351,19 +363,19 @@ void D3D12Renderer::render(std::function draw_fn, bool update barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; - m_cmd_list->ResourceBarrier(1, &barrier); + cmd_list->ResourceBarrier(1, &barrier); rts[0] = get_cpu_rtv((RTV)bb_index); - m_cmd_list->OMSetRenderTargets(1, rts, FALSE, NULL); - m_cmd_list->SetDescriptorHeaps(1, m_srv_heap.GetAddressOf()); + cmd_list->OMSetRenderTargets(1, rts, FALSE, NULL); + cmd_list->SetDescriptorHeaps(1, m_srv_heap.GetAddressOf()); // draw. - m_cmd_list->SetGraphicsRootDescriptorTable(1, get_gpu_srv(SRV::D2D)); - m_cmd_list->DrawInstanced(6, 1, 0, 0); + cmd_list->SetGraphicsRootDescriptorTable(1, get_gpu_srv(SRV::D2D)); + cmd_list->DrawInstanced(6, 1, 0, 0); barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; - m_cmd_list->ResourceBarrier(1, &barrier); - m_cmd_list->Close(); + cmd_list->ResourceBarrier(1, &barrier); - m_cmd_queue->ExecuteCommandLists(1, (ID3D12CommandList* const*)m_cmd_list.GetAddressOf()); + // Execute calls Close() on the command list. + cmd_context->execute(m_cmd_queue.Get()); } \ No newline at end of file diff --git a/src/D3D12Renderer.hpp b/src/D3D12Renderer.hpp index 5109205..d8b5b0d 100644 --- a/src/D3D12Renderer.hpp +++ b/src/D3D12Renderer.hpp @@ -8,6 +8,8 @@ #include #include +#include "d3d12/CommandContext.hpp" + #include "D2DPainter.hpp" class D3D12Renderer { @@ -47,16 +49,20 @@ class D3D12Renderer { ComPtr m_d3d11on12_device{}; ComPtr m_wrapped_rt{}; - ComPtr m_cmd_allocator{}; - ComPtr m_cmd_list{}; - ComPtr m_rtv_heap{}; ComPtr m_srv_heap{}; ComPtr m_rts[(int)RTV::COUNT]{}; ComPtr m_root_signature{}; ComPtr m_pipeline_state{}; - ComPtr m_vert_buffer{}; + + struct RenderResources { + ComPtr vert_buffer{}; + }; + + uint32_t m_frames_in_flight{1}; + std::vector> m_cmd_contexts{}; + std::vector> m_render_resources{}; int m_width{}; int m_height{}; diff --git a/src/d3d12/ComPtr.hpp b/src/d3d12/ComPtr.hpp new file mode 100644 index 0000000..6f6ed7f --- /dev/null +++ b/src/d3d12/ComPtr.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace d3d12 { +template using ComPtr = Microsoft::WRL::ComPtr; +} \ No newline at end of file diff --git a/src/d3d12/CommandContext.cpp b/src/d3d12/CommandContext.cpp new file mode 100644 index 0000000..1f35785 --- /dev/null +++ b/src/d3d12/CommandContext.cpp @@ -0,0 +1,83 @@ +#include "CommandContext.hpp" + +namespace d3d12 { +CommandContext::CommandContext(ID3D12Device* device, const wchar_t* name) { + std::scoped_lock _{this->mtx}; + + this->internal_name = name; + + this->cmd_allocator.Reset(); + this->cmd_list.Reset(); + this->fence.Reset(); + + if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&this->cmd_allocator)))) { + throw std::runtime_error("Failed to create command allocator"); + } + + this->cmd_allocator->SetName(name); + + if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, this->cmd_allocator.Get(), nullptr, IID_PPV_ARGS(&this->cmd_list)))) { + throw std::runtime_error("Failed to create command list"); + } + + this->cmd_list->SetName(name); + + if (FAILED(device->CreateFence(this->fence_value, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&this->fence)))) { + throw std::runtime_error("Failed to create fence"); + } + + this->fence->SetName(name); + this->fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + this->is_setup = true; +} + +void CommandContext::reset() { + std::scoped_lock _{this->mtx}; + this->wait(2000); + + this->cmd_allocator.Reset(); + this->cmd_list.Reset(); + this->fence.Reset(); + this->fence_value = 0; + CloseHandle(this->fence_event); + this->fence_event = 0; + this->waiting_for_fence = false; +} + +void CommandContext::wait(uint32_t ms) { + std::scoped_lock _{this->mtx}; + + if (this->fence_event && this->waiting_for_fence) { + WaitForSingleObject(this->fence_event, ms); + ResetEvent(this->fence_event); + this->waiting_for_fence = false; + if (FAILED(this->cmd_allocator->Reset())) { + throw std::runtime_error("Failed to reset command allocator"); + } + + if (FAILED(this->cmd_list->Reset(this->cmd_allocator.Get(), nullptr))) { + throw std::runtime_error("Failed to reset command list"); + } + + this->has_commands = false; + } +} + +void CommandContext::execute(ID3D12CommandQueue* command_queue) { + std::scoped_lock _{this->mtx}; + + if (this->has_commands) { + if (FAILED(this->cmd_list->Close())) { + throw std::runtime_error("Failed to close command list"); + } + + ID3D12CommandList* const cmd_lists[] = {this->cmd_list.Get()}; + command_queue->ExecuteCommandLists(1, cmd_lists); + command_queue->Signal(this->fence.Get(), ++this->fence_value); + this->fence->SetEventOnCompletion(this->fence_value, this->fence_event); + this->waiting_for_fence = true; + this->has_commands = false; + } +} +} \ No newline at end of file diff --git a/src/d3d12/CommandContext.hpp b/src/d3d12/CommandContext.hpp new file mode 100644 index 0000000..8dd3943 --- /dev/null +++ b/src/d3d12/CommandContext.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "ComPtr.hpp" + +namespace d3d12 { +struct TextureContext; + +struct CommandContext { + CommandContext() = delete; + CommandContext(ID3D12Device* device, const wchar_t* name = L"REFD2D CommandContext object"); + virtual ~CommandContext() { this->reset(); } + + void reset(); + void wait(uint32_t ms); + void execute(ID3D12CommandQueue* queue); + + ComPtr cmd_allocator{}; + ComPtr cmd_list{}; + ComPtr fence{}; + UINT64 fence_value{}; + HANDLE fence_event{}; + + std::recursive_mutex mtx{}; + + bool waiting_for_fence{false}; + bool has_commands{false}; + bool is_setup{false}; + + std::wstring internal_name{L"REFD2D CommandContext object"}; +}; +} \ No newline at end of file From 81b6fa3b2de128472c2d33e42c78b3739109ab59 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 14 Apr 2024 16:11:24 -0700 Subject: [PATCH 2/4] Give command contexts cleanup/wait priority on D3D12Renderer destruction --- src/D3D12Renderer.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/D3D12Renderer.hpp b/src/D3D12Renderer.hpp index d8b5b0d..42619b1 100644 --- a/src/D3D12Renderer.hpp +++ b/src/D3D12Renderer.hpp @@ -26,6 +26,15 @@ class D3D12Renderer { enum class SRV : int { D2D }; D3D12Renderer(IDXGISwapChain* swapchain_, ID3D12Device* device_, ID3D12CommandQueue* cmd_queue_); + virtual ~D3D12Renderer() { + // Give the command contexts priority cleanup before everything else + // so any command lists can finish executing before destroying everything + for (auto& cmd_context : m_cmd_contexts) { + cmd_context->reset(); + } + + m_cmd_contexts.clear(); + } void render(std::function draw_fn, bool update_d2d); From 7d1591863eb9ce2c889aeef08adf0405eb83fd8b Mon Sep 17 00:00:00 2001 From: cursey Date: Sun, 14 Apr 2024 17:04:26 -0700 Subject: [PATCH 3/4] Cleanup --- CMakeLists.txt | 2 +- src/D3D12CommandContext.cpp | 87 ++++++++++++++++++++++++++++++++++++ src/D3D12CommandContext.hpp | 40 +++++++++++++++++ src/D3D12Renderer.cpp | 8 ++-- src/D3D12Renderer.hpp | 2 +- src/d3d12/ComPtr.hpp | 7 --- src/d3d12/CommandContext.cpp | 83 ---------------------------------- src/d3d12/CommandContext.hpp | 34 -------------- 8 files changed, 133 insertions(+), 130 deletions(-) create mode 100644 src/D3D12CommandContext.cpp create mode 100644 src/D3D12CommandContext.hpp delete mode 100644 src/d3d12/ComPtr.hpp delete mode 100644 src/d3d12/CommandContext.cpp delete mode 100644 src/d3d12/CommandContext.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fb410d7..5500769 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ add_library(reframework-d2d SHARED src/D3D12Renderer.cpp src/DrawList.cpp src/Plugin.cpp - src/d3d12/CommandContext.cpp + src/D3D12CommandContext.cpp ) target_include_directories(reframework-d2d PRIVATE src diff --git a/src/D3D12CommandContext.cpp b/src/D3D12CommandContext.cpp new file mode 100644 index 0000000..0f0cc44 --- /dev/null +++ b/src/D3D12CommandContext.cpp @@ -0,0 +1,87 @@ +#include "D3D12CommandContext.hpp" + +namespace d3d12 { +CommandContext::CommandContext(ID3D12Device* device, const wchar_t* name) { + std::scoped_lock _{m_mtx}; + + m_cmd_allocator.Reset(); + m_cmd_list.Reset(); + m_fence.Reset(); + + if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_cmd_allocator)))) { + throw std::runtime_error("Failed to create command allocator"); + } + + m_cmd_allocator->SetName(name); + + if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_cmd_allocator.Get(), nullptr, IID_PPV_ARGS(&m_cmd_list)))) { + throw std::runtime_error("Failed to create command list"); + } + + m_cmd_list->SetName(name); + + if (FAILED(device->CreateFence(m_fence_value, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)))) { + throw std::runtime_error("Failed to create fence"); + } + + m_fence->SetName(name); + m_fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + m_is_setup = true; +} + +void CommandContext::reset() { + std::scoped_lock _{m_mtx}; + wait(2000); + + m_cmd_allocator.Reset(); + m_cmd_list.Reset(); + m_fence.Reset(); + m_fence_value = 0; + + CloseHandle(m_fence_event); + + m_fence_event = nullptr; + m_waiting_for_fence = false; +} + +void CommandContext::wait(uint32_t ms) { + std::scoped_lock _{m_mtx}; + + if (m_fence_event && m_waiting_for_fence) { + WaitForSingleObject(m_fence_event, ms); + ResetEvent(m_fence_event); + + m_waiting_for_fence = false; + + if (FAILED(m_cmd_allocator->Reset())) { + throw std::runtime_error("Failed to reset command allocator"); + } + + if (FAILED(m_cmd_list->Reset(m_cmd_allocator.Get(), nullptr))) { + throw std::runtime_error("Failed to reset command list"); + } + + m_has_commands = false; + } +} + +void CommandContext::execute(ID3D12CommandQueue* command_queue) { + std::scoped_lock _{m_mtx}; + + if (m_has_commands) { + if (FAILED(m_cmd_list->Close())) { + throw std::runtime_error("Failed to close command list"); + } + + ID3D12CommandList* const cmd_lists[] = {m_cmd_list.Get()}; + + command_queue->ExecuteCommandLists(1, cmd_lists); + command_queue->Signal(m_fence.Get(), ++m_fence_value); + m_fence->SetEventOnCompletion(m_fence_value, m_fence_event); + + m_waiting_for_fence = true; + m_has_commands = false; + } +} +} // namespace d3d12 \ No newline at end of file diff --git a/src/D3D12CommandContext.hpp b/src/D3D12CommandContext.hpp new file mode 100644 index 0000000..95665dd --- /dev/null +++ b/src/D3D12CommandContext.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace d3d12 { +struct TextureContext; + +class CommandContext { +public: + template using ComPtr = Microsoft::WRL::ComPtr; + + CommandContext() = delete; + CommandContext(ID3D12Device* device, const wchar_t* name = L"REFD2D CommandContext object"); + virtual ~CommandContext() { reset(); } + + void reset(); + void wait(uint32_t ms); + void execute(ID3D12CommandQueue* queue); + + void begin() { m_has_commands = true; } + + [[nodiscard]] bool is_setup() const { return m_is_setup; } + [[nodiscard]] ComPtr& cmd_list() { return m_cmd_list; } + +private: + ComPtr m_cmd_allocator{}; + ComPtr m_cmd_list{}; + ComPtr m_fence{}; + UINT64 m_fence_value{}; + HANDLE m_fence_event{}; + + std::recursive_mutex m_mtx{}; + + bool m_waiting_for_fence{}; + bool m_has_commands{}; + bool m_is_setup{}; +}; +} // namespace d3d12 \ No newline at end of file diff --git a/src/D3D12Renderer.cpp b/src/D3D12Renderer.cpp index 5a58c6f..dfe7f7b 100644 --- a/src/D3D12Renderer.cpp +++ b/src/D3D12Renderer.cpp @@ -54,7 +54,7 @@ D3D12Renderer::D3D12Renderer(IDXGISwapChain* swapchain_, ID3D12Device* device_, // before we submit them to the command queue. If we did not do this, we would run into some race conditions. auto cmd_context = std::make_unique(m_device.Get()); - if (cmd_context->is_setup) { + if (cmd_context->is_setup()) { m_cmd_contexts.push_back(std::move(cmd_context)); } else { throw std::runtime_error{"Failed to create command context"}; @@ -296,12 +296,12 @@ D3D12Renderer::D3D12Renderer(IDXGISwapChain* swapchain_, ID3D12Device* device_, void D3D12Renderer::render(std::function draw_fn, bool update_d2d) { auto& cmd_context = m_cmd_contexts[m_swapchain->GetCurrentBackBufferIndex() % m_cmd_contexts.size()]; auto& resources = m_render_resources[m_swapchain->GetCurrentBackBufferIndex() % m_render_resources.size()]; - + // Wait for the command context to be ready. cmd_context->wait(INFINITE); - cmd_context->has_commands = true; + cmd_context->begin(); - auto& cmd_list = cmd_context->cmd_list; + auto& cmd_list = cmd_context->cmd_list(); auto& vert_buffer = resources->vert_buffer; if (update_d2d) { diff --git a/src/D3D12Renderer.hpp b/src/D3D12Renderer.hpp index 42619b1..e57e531 100644 --- a/src/D3D12Renderer.hpp +++ b/src/D3D12Renderer.hpp @@ -8,7 +8,7 @@ #include #include -#include "d3d12/CommandContext.hpp" +#include "D3D12CommandContext.hpp" #include "D2DPainter.hpp" diff --git a/src/d3d12/ComPtr.hpp b/src/d3d12/ComPtr.hpp deleted file mode 100644 index 6f6ed7f..0000000 --- a/src/d3d12/ComPtr.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -namespace d3d12 { -template using ComPtr = Microsoft::WRL::ComPtr; -} \ No newline at end of file diff --git a/src/d3d12/CommandContext.cpp b/src/d3d12/CommandContext.cpp deleted file mode 100644 index 1f35785..0000000 --- a/src/d3d12/CommandContext.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "CommandContext.hpp" - -namespace d3d12 { -CommandContext::CommandContext(ID3D12Device* device, const wchar_t* name) { - std::scoped_lock _{this->mtx}; - - this->internal_name = name; - - this->cmd_allocator.Reset(); - this->cmd_list.Reset(); - this->fence.Reset(); - - if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&this->cmd_allocator)))) { - throw std::runtime_error("Failed to create command allocator"); - } - - this->cmd_allocator->SetName(name); - - if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, this->cmd_allocator.Get(), nullptr, IID_PPV_ARGS(&this->cmd_list)))) { - throw std::runtime_error("Failed to create command list"); - } - - this->cmd_list->SetName(name); - - if (FAILED(device->CreateFence(this->fence_value, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&this->fence)))) { - throw std::runtime_error("Failed to create fence"); - } - - this->fence->SetName(name); - this->fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); - - this->is_setup = true; -} - -void CommandContext::reset() { - std::scoped_lock _{this->mtx}; - this->wait(2000); - - this->cmd_allocator.Reset(); - this->cmd_list.Reset(); - this->fence.Reset(); - this->fence_value = 0; - CloseHandle(this->fence_event); - this->fence_event = 0; - this->waiting_for_fence = false; -} - -void CommandContext::wait(uint32_t ms) { - std::scoped_lock _{this->mtx}; - - if (this->fence_event && this->waiting_for_fence) { - WaitForSingleObject(this->fence_event, ms); - ResetEvent(this->fence_event); - this->waiting_for_fence = false; - if (FAILED(this->cmd_allocator->Reset())) { - throw std::runtime_error("Failed to reset command allocator"); - } - - if (FAILED(this->cmd_list->Reset(this->cmd_allocator.Get(), nullptr))) { - throw std::runtime_error("Failed to reset command list"); - } - - this->has_commands = false; - } -} - -void CommandContext::execute(ID3D12CommandQueue* command_queue) { - std::scoped_lock _{this->mtx}; - - if (this->has_commands) { - if (FAILED(this->cmd_list->Close())) { - throw std::runtime_error("Failed to close command list"); - } - - ID3D12CommandList* const cmd_lists[] = {this->cmd_list.Get()}; - command_queue->ExecuteCommandLists(1, cmd_lists); - command_queue->Signal(this->fence.Get(), ++this->fence_value); - this->fence->SetEventOnCompletion(this->fence_value, this->fence_event); - this->waiting_for_fence = true; - this->has_commands = false; - } -} -} \ No newline at end of file diff --git a/src/d3d12/CommandContext.hpp b/src/d3d12/CommandContext.hpp deleted file mode 100644 index 8dd3943..0000000 --- a/src/d3d12/CommandContext.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include - -#include "ComPtr.hpp" - -namespace d3d12 { -struct TextureContext; - -struct CommandContext { - CommandContext() = delete; - CommandContext(ID3D12Device* device, const wchar_t* name = L"REFD2D CommandContext object"); - virtual ~CommandContext() { this->reset(); } - - void reset(); - void wait(uint32_t ms); - void execute(ID3D12CommandQueue* queue); - - ComPtr cmd_allocator{}; - ComPtr cmd_list{}; - ComPtr fence{}; - UINT64 fence_value{}; - HANDLE fence_event{}; - - std::recursive_mutex mtx{}; - - bool waiting_for_fence{false}; - bool has_commands{false}; - bool is_setup{false}; - - std::wstring internal_name{L"REFD2D CommandContext object"}; -}; -} \ No newline at end of file From f83ecd50615d6d8eb689e2d5d6d8c5a3752b9071 Mon Sep 17 00:00:00 2001 From: cursey Date: Sun, 14 Apr 2024 17:15:20 -0700 Subject: [PATCH 4/4] Cleanup --- src/D3D12CommandContext.cpp | 51 ++++++++++++++++++------------------- src/D3D12CommandContext.hpp | 21 +++++---------- src/D3D12Renderer.cpp | 13 +++------- src/D3D12Renderer.hpp | 2 +- 4 files changed, 37 insertions(+), 50 deletions(-) diff --git a/src/D3D12CommandContext.cpp b/src/D3D12CommandContext.cpp index 0f0cc44..c063737 100644 --- a/src/D3D12CommandContext.cpp +++ b/src/D3D12CommandContext.cpp @@ -1,7 +1,6 @@ #include "D3D12CommandContext.hpp" -namespace d3d12 { -CommandContext::CommandContext(ID3D12Device* device, const wchar_t* name) { +D3D12CommandContext::D3D12CommandContext(ID3D12Device* device, const wchar_t* name) { std::scoped_lock _{m_mtx}; m_cmd_allocator.Reset(); @@ -9,28 +8,28 @@ CommandContext::CommandContext(ID3D12Device* device, const wchar_t* name) { m_fence.Reset(); if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_cmd_allocator)))) { - throw std::runtime_error("Failed to create command allocator"); + throw std::runtime_error{"Failed to create command allocator"}; } m_cmd_allocator->SetName(name); if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_cmd_allocator.Get(), nullptr, IID_PPV_ARGS(&m_cmd_list)))) { - throw std::runtime_error("Failed to create command list"); + throw std::runtime_error{"Failed to create command list"}; } m_cmd_list->SetName(name); if (FAILED(device->CreateFence(m_fence_value, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)))) { - throw std::runtime_error("Failed to create fence"); + throw std::runtime_error{"Failed to create fence"}; } m_fence->SetName(name); - m_fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + m_fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); m_is_setup = true; } -void CommandContext::reset() { +void D3D12CommandContext::reset() { std::scoped_lock _{m_mtx}; wait(2000); @@ -45,7 +44,7 @@ void CommandContext::reset() { m_waiting_for_fence = false; } -void CommandContext::wait(uint32_t ms) { +void D3D12CommandContext::wait(uint32_t ms) { std::scoped_lock _{m_mtx}; if (m_fence_event && m_waiting_for_fence) { @@ -55,33 +54,33 @@ void CommandContext::wait(uint32_t ms) { m_waiting_for_fence = false; if (FAILED(m_cmd_allocator->Reset())) { - throw std::runtime_error("Failed to reset command allocator"); + throw std::runtime_error{"Failed to reset command allocator"}; } if (FAILED(m_cmd_list->Reset(m_cmd_allocator.Get(), nullptr))) { - throw std::runtime_error("Failed to reset command list"); + throw std::runtime_error{"Failed to reset command list"}; } - - m_has_commands = false; } } -void CommandContext::execute(ID3D12CommandQueue* command_queue) { - std::scoped_lock _{m_mtx}; +D3D12CommandContext::ComPtr& D3D12CommandContext::begin() { + m_mtx.lock(); + wait(INFINITE); + return m_cmd_list; +} - if (m_has_commands) { - if (FAILED(m_cmd_list->Close())) { - throw std::runtime_error("Failed to close command list"); - } +void D3D12CommandContext::end(ID3D12CommandQueue* command_queue) { + if (FAILED(m_cmd_list->Close())) { + throw std::runtime_error("Failed to close command list"); + } - ID3D12CommandList* const cmd_lists[] = {m_cmd_list.Get()}; + ID3D12CommandList* const cmd_lists[] = {m_cmd_list.Get()}; - command_queue->ExecuteCommandLists(1, cmd_lists); - command_queue->Signal(m_fence.Get(), ++m_fence_value); - m_fence->SetEventOnCompletion(m_fence_value, m_fence_event); + command_queue->ExecuteCommandLists(1, cmd_lists); + command_queue->Signal(m_fence.Get(), ++m_fence_value); + m_fence->SetEventOnCompletion(m_fence_value, m_fence_event); - m_waiting_for_fence = true; - m_has_commands = false; - } + m_waiting_for_fence = true; + + m_mtx.unlock(); } -} // namespace d3d12 \ No newline at end of file diff --git a/src/D3D12CommandContext.hpp b/src/D3D12CommandContext.hpp index 95665dd..052952f 100644 --- a/src/D3D12CommandContext.hpp +++ b/src/D3D12CommandContext.hpp @@ -4,25 +4,20 @@ #include #include -namespace d3d12 { -struct TextureContext; - -class CommandContext { +class D3D12CommandContext { public: template using ComPtr = Microsoft::WRL::ComPtr; - CommandContext() = delete; - CommandContext(ID3D12Device* device, const wchar_t* name = L"REFD2D CommandContext object"); - virtual ~CommandContext() { reset(); } + D3D12CommandContext() = delete; + D3D12CommandContext(ID3D12Device* device, const wchar_t* name = L"REFD2D D3D12CommandContext object"); + virtual ~D3D12CommandContext() { reset(); } void reset(); void wait(uint32_t ms); - void execute(ID3D12CommandQueue* queue); - - void begin() { m_has_commands = true; } + ComPtr& begin(); + void end(ID3D12CommandQueue* queue); [[nodiscard]] bool is_setup() const { return m_is_setup; } - [[nodiscard]] ComPtr& cmd_list() { return m_cmd_list; } private: ComPtr m_cmd_allocator{}; @@ -34,7 +29,5 @@ class CommandContext { std::recursive_mutex m_mtx{}; bool m_waiting_for_fence{}; - bool m_has_commands{}; bool m_is_setup{}; -}; -} // namespace d3d12 \ No newline at end of file +}; \ No newline at end of file diff --git a/src/D3D12Renderer.cpp b/src/D3D12Renderer.cpp index dfe7f7b..435d8d9 100644 --- a/src/D3D12Renderer.cpp +++ b/src/D3D12Renderer.cpp @@ -52,7 +52,7 @@ D3D12Renderer::D3D12Renderer(IDXGISwapChain* swapchain_, ID3D12Device* device_, // Create a command context for each back buffer. // We create one for each because the GPU could be doing work on one while we're recording commands on another, // before we submit them to the command queue. If we did not do this, we would run into some race conditions. - auto cmd_context = std::make_unique(m_device.Get()); + auto cmd_context = std::make_unique(m_device.Get()); if (cmd_context->is_setup()) { m_cmd_contexts.push_back(std::move(cmd_context)); @@ -296,12 +296,7 @@ D3D12Renderer::D3D12Renderer(IDXGISwapChain* swapchain_, ID3D12Device* device_, void D3D12Renderer::render(std::function draw_fn, bool update_d2d) { auto& cmd_context = m_cmd_contexts[m_swapchain->GetCurrentBackBufferIndex() % m_cmd_contexts.size()]; auto& resources = m_render_resources[m_swapchain->GetCurrentBackBufferIndex() % m_render_resources.size()]; - - // Wait for the command context to be ready. - cmd_context->wait(INFINITE); - cmd_context->begin(); - - auto& cmd_list = cmd_context->cmd_list(); + auto& cmd_list = cmd_context->begin(); auto& vert_buffer = resources->vert_buffer; if (update_d2d) { @@ -376,6 +371,6 @@ void D3D12Renderer::render(std::function draw_fn, bool update barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; cmd_list->ResourceBarrier(1, &barrier); - // Execute calls Close() on the command list. - cmd_context->execute(m_cmd_queue.Get()); + // end(...) calls Close() on the command list. + cmd_context->end(m_cmd_queue.Get()); } \ No newline at end of file diff --git a/src/D3D12Renderer.hpp b/src/D3D12Renderer.hpp index e57e531..319ad21 100644 --- a/src/D3D12Renderer.hpp +++ b/src/D3D12Renderer.hpp @@ -70,7 +70,7 @@ class D3D12Renderer { }; uint32_t m_frames_in_flight{1}; - std::vector> m_cmd_contexts{}; + std::vector> m_cmd_contexts{}; std::vector> m_render_resources{}; int m_width{};