diff --git a/CMakeLists.txt b/CMakeLists.txt index 2212728b..3034b8f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,7 @@ set(GRAPHICS_DIRECTX12_SOURCES src/Graphics/DirectX12/DirectX12Renderer.cpp src/Graphics/DirectX12/DirectX12Shader.cpp src/Graphics/DirectX12/DirectX12Texture.cpp + src/Graphics/DirectX12/DirectX12UploadRing.cpp ) set(LIGHTING_SOURCES diff --git a/include/private/Graphics/DirectX12/DirectX12Renderer.hpp b/include/private/Graphics/DirectX12/DirectX12Renderer.hpp index 4dc3181c..f3c87927 100644 --- a/include/private/Graphics/DirectX12/DirectX12Renderer.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Renderer.hpp @@ -5,19 +5,22 @@ #ifndef _DIRECTX12RENDERER_H #define _DIRECTX12RENDERER_H -#include "Graphics/Common/Renderer.hpp" +#include +#include +#include +#include +#include +#include + +#include + #include "Graphics/Common/RenderContext.hpp" +#include "Graphics/Common/Renderer.hpp" #include "Graphics/Common/ResourceManager.hpp" #include "Graphics/DirectX12/DirectX12Buffer.hpp" #include "Graphics/DirectX12/DirectX12Shader.hpp" #include "Graphics/DirectX12/DirectX12Texture.hpp" -#include -#include -#include -#include -#include -#include -#include +#include "Graphics/DirectX12/DirectX12UploadRing.hpp" namespace Sleak { namespace RenderEngine { @@ -177,6 +180,9 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { /// Builds the line-topology PSO used for debug line rendering. bool CreateDebugLinePipelineState(); + // Transient per-frame constant data (per-draw shadow transforms) + DirectX12UploadRing m_uploadRing; + // Light/fog constant buffer (persistently-mapped upload heap) Microsoft::WRL::ComPtr m_lightUBO; void* m_lightUBOMapped = nullptr; @@ -193,9 +199,7 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { float m_lightVP[16] = {}; float m_pendingLightVP[16] = {}; bool m_hasPendingLightVP = false; - bool m_inShadowPass = false; - Microsoft::WRL::ComPtr m_shadowTransformCB; - void* m_shadowTransformMapped = nullptr; + bool m_inShadowPass = false; // Depth-only PSO for shadow pass (no PS, no RTV, CULL_NONE) Microsoft::WRL::ComPtr m_shadowPassPSO; Microsoft::WRL::ComPtr m_cachedVSBlob; // saved for shadow PSO diff --git a/include/private/Graphics/DirectX12/DirectX12UploadRing.hpp b/include/private/Graphics/DirectX12/DirectX12UploadRing.hpp new file mode 100644 index 00000000..7663e14c --- /dev/null +++ b/include/private/Graphics/DirectX12/DirectX12UploadRing.hpp @@ -0,0 +1,67 @@ +#ifndef _DIRECTX12_UPLOAD_RING_HPP_ +#define _DIRECTX12_UPLOAD_RING_HPP_ + +#include +#include + +#include +#include + +namespace Sleak { +namespace RenderEngine { + +/// Per-frame linear allocator over upload heap pages for transient GPU data. +/// Each frame in flight owns its own pages, which are rewound by BeginFrame. +class DirectX12UploadRing { + public: + /// CPU write pointer and matching GPU address of one allocation. + struct Allocation { + void* cpu = nullptr; + D3D12_GPU_VIRTUAL_ADDRESS gpu = 0; + }; + + DirectX12UploadRing() = default; + ~DirectX12UploadRing() { Release(); } + + DirectX12UploadRing(const DirectX12UploadRing&) = delete; + DirectX12UploadRing& operator=(const DirectX12UploadRing&) = delete; + + /// Stores the device and sizes the ring. Pages are created on demand. + void Initialize(ID3D12Device* device, uint32_t frameCount, + uint64_t pageSize); + + /// Rewinds the frame's pages. The GPU must be done with that frame. + void BeginFrame(uint32_t frameIndex); + + /// Returns 256-byte aligned memory valid until the frame is reused. + bool Allocate(uint64_t size, Allocation& out); + + /// Unmaps and frees every page. The GPU must be idle. + void Release(); + + private: + struct Page { + Microsoft::WRL::ComPtr resource; + uint8_t* cpu = nullptr; + D3D12_GPU_VIRTUAL_ADDRESS gpu = 0; + uint64_t size = 0; + }; + + struct Frame { + std::vector pages; + size_t page = 0; + uint64_t offset = 0; + }; + + bool CreatePage(uint64_t size, Page& out); + + ID3D12Device* m_device = nullptr; + std::vector m_frames; + uint32_t m_current = 0; + uint64_t m_pageSize = 0; +}; + +} // namespace RenderEngine +} // namespace Sleak + +#endif // _DIRECTX12_UPLOAD_RING_HPP_ diff --git a/src/Graphics/DirectX12/DirectX12Renderer.cpp b/src/Graphics/DirectX12/DirectX12Renderer.cpp index b421f504..8c9b662b 100644 --- a/src/Graphics/DirectX12/DirectX12Renderer.cpp +++ b/src/Graphics/DirectX12/DirectX12Renderer.cpp @@ -69,6 +69,7 @@ bool DirectX12Renderer::Initialize() { if (!CreateRenderTargetViews()) return false; if (!CreateDepthStencilView()) return false; if (!CreateFence()) return false; + m_uploadRing.Initialize(device.Get(), FrameCount, 64 * 1024); if (!CreateRootSignature()) return false; if (!CreateSharedSrvHeap()) return false; // PSO is created lazily when CreateShader() is called @@ -643,6 +644,7 @@ void DirectX12Renderer::BeginRender() { // Process deferred GPU resource deletions now that GPU is idle DirectX12Buffer::ProcessDeferredCleanup(); + m_uploadRing.BeginFrame(frameIndex); // Reset the command allocator and command list for this frame commandAllocators[frameIndex]->Reset(); @@ -853,6 +855,7 @@ void DirectX12Renderer::Cleanup() { } m_lightUBO.Reset(); m_lightUBOCreated = false; + m_uploadRing.Release(); imguiSrvHeap.Reset(); m_sharedSrvHeap.Reset(); @@ -1051,29 +1054,10 @@ void DirectX12Renderer::BindConstantBuffer(RefPtr buffer, } memcpy(&shadowPC[16], srcWorld, sizeof(float) * 16); - // Create dedicated shadow transform CB on first use - if (!m_shadowTransformCB) { - const UINT cbSize = 256; // 256-byte aligned - D3D12_HEAP_PROPERTIES heapProps = {}; - heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; - D3D12_RESOURCE_DESC desc = {}; - desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - desc.Width = cbSize; - desc.Height = 1; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.Format = DXGI_FORMAT_UNKNOWN; - desc.SampleDesc.Count = 1; - desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &desc, - D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, - IID_PPV_ARGS(&m_shadowTransformCB)); - m_shadowTransformCB->Map(0, nullptr, &m_shadowTransformMapped); - } - - memcpy(m_shadowTransformMapped, shadowPC, sizeof(shadowPC)); - commandList->SetGraphicsRootConstantBufferView( - slot, m_shadowTransformCB->GetGPUVirtualAddress()); + DirectX12UploadRing::Allocation alloc; + if (!m_uploadRing.Allocate(sizeof(shadowPC), alloc)) return; + memcpy(alloc.cpu, shadowPC, sizeof(shadowPC)); + commandList->SetGraphicsRootConstantBufferView(slot, alloc.gpu); return; } diff --git a/src/Graphics/DirectX12/DirectX12UploadRing.cpp b/src/Graphics/DirectX12/DirectX12UploadRing.cpp new file mode 100644 index 00000000..e2cecbb7 --- /dev/null +++ b/src/Graphics/DirectX12/DirectX12UploadRing.cpp @@ -0,0 +1,118 @@ +#include +#include +#include + +namespace Sleak { +namespace RenderEngine { + +namespace { +constexpr uint64_t kAlignment = D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; + +uint64_t AlignUp(uint64_t value) { + return (value + kAlignment - 1) & ~(kAlignment - 1); +} +} // namespace + +void DirectX12UploadRing::Initialize(ID3D12Device* device, uint32_t frameCount, + uint64_t pageSize) { + Release(); + m_device = device; + m_pageSize = AlignUp(pageSize); + m_frames.resize(frameCount); + m_current = 0; +} + +void DirectX12UploadRing::BeginFrame(uint32_t frameIndex) { + if (frameIndex >= m_frames.size()) return; + m_current = frameIndex; + m_frames[frameIndex].page = 0; + m_frames[frameIndex].offset = 0; +} + +bool DirectX12UploadRing::Allocate(uint64_t size, Allocation& out) { + if (!m_device || m_current >= m_frames.size() || size == 0) return false; + + const uint64_t aligned = AlignUp(size); + Frame& frame = m_frames[m_current]; + + while (frame.page < frame.pages.size()) { + Page& page = frame.pages[frame.page]; + if (frame.offset + aligned <= page.size) { + out.cpu = page.cpu + frame.offset; + out.gpu = page.gpu + frame.offset; + frame.offset += aligned; + return true; + } + ++frame.page; + frame.offset = 0; + } + + Page page; + if (!CreatePage(aligned > m_pageSize ? aligned : m_pageSize, page)) + return false; + frame.pages.push_back(std::move(page)); + frame.page = frame.pages.size() - 1; + + Page& fresh = frame.pages.back(); + out.cpu = fresh.cpu; + out.gpu = fresh.gpu; + frame.offset = aligned; + return true; +} + +void DirectX12UploadRing::Release() { + for (Frame& frame : m_frames) { + for (Page& page : frame.pages) { + if (page.resource && page.cpu) page.resource->Unmap(0, nullptr); + } + frame.pages.clear(); + frame.page = 0; + frame.offset = 0; + } + m_frames.clear(); + m_device = nullptr; + m_current = 0; +} + +bool DirectX12UploadRing::CreatePage(uint64_t size, Page& out) { + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + + D3D12_RESOURCE_DESC desc = {}; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Width = size; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.SampleDesc.Count = 1; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + + HRESULT hr = m_device->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, &desc, + D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, + IID_PPV_ARGS(&out.resource)); + if (FAILED(hr)) { + SLEAK_ERROR("Failed to create upload ring page! HRESULT: 0x{:08X}", + static_cast(hr)); + return false; + } + out.resource->SetName(L"UploadRingPage"); + + D3D12_RANGE noRead = {0, 0}; + void* mapped = nullptr; + hr = out.resource->Map(0, &noRead, &mapped); + if (FAILED(hr)) { + SLEAK_ERROR("Failed to map upload ring page! HRESULT: 0x{:08X}", + static_cast(hr)); + out.resource.Reset(); + return false; + } + out.cpu = static_cast(mapped); + out.gpu = out.resource->GetGPUVirtualAddress(); + out.size = size; + return true; +} + +} // namespace RenderEngine +} // namespace Sleak