From 841bc57cebc58609e928820748bf3cd773d62ef5 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Sun, 4 Oct 2026 20:42:37 +0300 Subject: [PATCH 1/3] perf: let dx12 frames overlap instead of waiting on every frame BeginRender now waits only for the frame that last used the slot it is about to reuse, against one monotonically increasing fence. Constant buffers and the light constants are copied into the per-frame upload ring at bind time, buffer and texture uploads are batched into one shared command list, PSOs come from a small cache, and DX12 textures get a mip chain. --- CMakeLists.txt | 1 + .../Graphics/DirectX12/DirectX12Buffer.hpp | 83 +- .../DirectX12/DirectX12CubemapTexture.hpp | 9 +- .../Graphics/DirectX12/DirectX12Renderer.hpp | 83 +- .../Graphics/DirectX12/DirectX12Shader.hpp | 4 + .../Graphics/DirectX12/DirectX12Texture.hpp | 12 +- .../DirectX12/DirectX12UploadContext.hpp | 68 ++ src/Graphics/DirectX12/DirectX12Buffer.cpp | 279 +++---- .../DirectX12/DirectX12CubemapTexture.cpp | 100 +-- src/Graphics/DirectX12/DirectX12Renderer.cpp | 761 +++++++----------- src/Graphics/DirectX12/DirectX12Shader.cpp | 10 +- src/Graphics/DirectX12/DirectX12Texture.cpp | 214 +++-- .../DirectX12/DirectX12UploadContext.cpp | 176 ++++ 13 files changed, 883 insertions(+), 917 deletions(-) create mode 100644 include/private/Graphics/DirectX12/DirectX12UploadContext.hpp create mode 100644 src/Graphics/DirectX12/DirectX12UploadContext.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3034b8f2..7b6a5436 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/DirectX12UploadContext.cpp src/Graphics/DirectX12/DirectX12UploadRing.cpp ) diff --git a/include/private/Graphics/DirectX12/DirectX12Buffer.hpp b/include/private/Graphics/DirectX12/DirectX12Buffer.hpp index 32715085..6a843810 100644 --- a/include/private/Graphics/DirectX12/DirectX12Buffer.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Buffer.hpp @@ -3,34 +3,40 @@ #include "../Common/BufferBase.hpp" #include #include +#include #include +#include namespace Sleak { namespace RenderEngine { +class DirectX12UploadContext; + /// D3D12 buffer wrapper: default-heap resource plus an upload heap for CPU-to-GPU copies. class DirectX12Buffer : public BufferBase { public: - // Constructor for common buffer types - DirectX12Buffer(ID3D12Device* device, ID3D12CommandQueue* queue, size_t size, BufferType type); + /// Uploads go through the shared uploader when given, else they block. + DirectX12Buffer(ID3D12Device* device, ID3D12CommandQueue* queue, + size_t size, BufferType type, + DirectX12UploadContext* uploader = nullptr); // Constructor for custom buffer configuration DirectX12Buffer(ID3D12Device* device, size_t size, D3D12_HEAP_TYPE heapType, D3D12_RESOURCE_STATES resourceState); - + // Prevent copying DirectX12Buffer(const DirectX12Buffer&) = delete; DirectX12Buffer& operator=(const DirectX12Buffer&) = delete; - + // Allow moving DirectX12Buffer(DirectX12Buffer&&) noexcept; DirectX12Buffer& operator=(DirectX12Buffer&&) noexcept; - + ~DirectX12Buffer() override; /// Creates the default-heap resource and uploads initial data via a staging buffer, if any. bool Initialize(void* Data) override; void Update() override; void Cleanup() override; - + /// Maps the buffer for CPU writes (upload-heap buffers only). bool Map() override; void Unmap() override; @@ -38,64 +44,69 @@ class DirectX12Buffer : public BufferBase { void Update(void* data, size_t size) override; /// Creates the buffer sized and pre-populated from a raw payload. bool Initialize(const void* data, size_t size); - + void* GetData() override; // Getter for the underlying D3D buffer ID3D12Resource* GetD3DBuffer() const { return m_buffer.Get(); } - - // Get command list for execution - ID3D12GraphicsCommandList* GetCommandList() const { return m_commandList.Get(); } - - // Check if command list is ready for execution - bool HasPendingCommands() const { return m_commandList != nullptr; } - + + /// Latest constant buffer contents, copied into the upload ring on bind. + const void* GetConstantData() const { + return m_constantData.empty() ? nullptr : m_constantData.data(); + } + size_t GetConstantDataSize() const { return m_constantData.size(); } + + /// Ring address already holding the current contents for this frame, or 0. + D3D12_GPU_VIRTUAL_ADDRESS GetFrameAddress(uint64_t frame) const { + return m_frameSerial == frame ? m_frameAddress : 0; + } + /// Remembers where this frame's copy of the contents lives in the ring. + void SetFrameAddress(uint64_t frame, D3D12_GPU_VIRTUAL_ADDRESS address) { + m_frameSerial = frame; + m_frameAddress = address; + } + // Additional utility methods bool IsValid() const { return m_buffer != nullptr; } D3D12_HEAP_TYPE GetHeapType() const { return m_heapType; } D3D12_RESOURCE_STATES GetResourceState() const { return m_resourceState; } - + private: // Helper method to set up buffer description D3D12_RESOURCE_DESC CreateBufferDesc() const; - + // Convert BufferType to DirectX configuration void ConfigureFromBufferType(BufferType type); - - // Create an upload buffer and copy data to the default buffer - bool CreateUploadBuffer(const void* data, size_t dataSize); - - void SetAsVertexBuffer (ID3D12GraphicsCommandList* commandList, UINT slot, UINT stride, UINT offset = 0); - void SetAsIndexBuffer (ID3D12GraphicsCommandList* commandList, DXGI_FORMAT format, UINT offset = 0); - void SetAsConstantBuffer(ID3D12GraphicsCommandList* commandList, UINT rootParameterIndex); - - // Wait for pending GPU upload to complete (for command list/allocator reuse) - void WaitForUploadComplete(); -public: - // Release staging upload resources (call after GPU copy is guaranteed complete) - void ReleaseUploadResources(); + /// Records a staged copy into the default-heap buffer. + bool UploadToDefaultHeap(const void* data, size_t dataSize); - // Deferred cleanup: queue buffers for deletion after GPU is done - static void DeferCleanup(Microsoft::WRL::ComPtr resource); - static void ProcessDeferredCleanup(); +public: + /// Queues a GPU object for release once the frame that used it is done. + static void DeferCleanup(Microsoft::WRL::ComPtr object); + /// Stamps everything queued since the last call with its retire value. + static void TagDeferredCleanup(uint64_t fenceValue); + /// Releases every queued object whose fence value has completed. + static void ProcessDeferredCleanup(uint64_t completedValue); private: // Smart pointers for proper resource management Microsoft::WRL::ComPtr m_device; ID3D12CommandQueue* m_commandQueue = nullptr; // non-owning, for upload execution + DirectX12UploadContext* m_uploader = nullptr; // non-owning Microsoft::WRL::ComPtr m_buffer; - Microsoft::WRL::ComPtr m_uploadBuffer; - Microsoft::WRL::ComPtr m_commandAllocator; - Microsoft::WRL::ComPtr m_commandList; D3D12_HEAP_TYPE m_heapType = D3D12_HEAP_TYPE_DEFAULT; D3D12_RESOURCE_STATES m_resourceState = D3D12_RESOURCE_STATE_COMMON; // Actual current GPU resource state (tracked for correct barriers on re-upload) D3D12_RESOURCE_STATES m_currentState = D3D12_RESOURCE_STATE_COMMON; void* m_mappedData = nullptr; + + std::vector m_constantData; + uint64_t m_frameSerial = 0; + D3D12_GPU_VIRTUAL_ADDRESS m_frameAddress = 0; }; } // namespace RenderEngine } // namespace Sleak -#endif // DIRECTX12BUFFER_HPP_ \ No newline at end of file +#endif // DIRECTX12BUFFER_HPP_ diff --git a/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp b/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp index 0eaca991..5fe47ab1 100644 --- a/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp +++ b/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp @@ -14,12 +14,15 @@ namespace Sleak { namespace RenderEngine { +class DirectX12UploadContext; /// D3D12 cubemap texture, loadable from six face images or a single equirectangular panorama. class ENGINE_API DirectX12CubemapTexture : public ::Sleak::Texture { public: + /// Uploads go through the shared uploader when given, else they block. DirectX12CubemapTexture(ID3D12Device* device, - ID3D12CommandQueue* commandQueue); + ID3D12CommandQueue* commandQueue, + DirectX12UploadContext* uploader = nullptr); ~DirectX12CubemapTexture() override; /// Loads and uploads 6 face images: +X, -X, +Y, -Y, +Z, -Z. @@ -60,14 +63,12 @@ class ENGINE_API DirectX12CubemapTexture : public ::Sleak::Texture { /// Builds the cubemap texture array and shader resource view from decoded face pixels. bool CreateCubemapFromFaces(const std::vector& faceData, uint32_t faceSize); - /// Blocks until the upload-heap copy to the GPU-resident cubemap resource completes. - void WaitForUpload(); ID3D12Device* m_device = nullptr; ID3D12CommandQueue* m_commandQueue = nullptr; + DirectX12UploadContext* m_uploader = nullptr; Microsoft::WRL::ComPtr m_texture; - Microsoft::WRL::ComPtr m_uploadBuffer; Microsoft::WRL::ComPtr m_srvHeap; // fallback D3D12_GPU_DESCRIPTOR_HANDLE m_srvGpuHandle = {}; bool m_usesSharedHeap = false; diff --git a/include/private/Graphics/DirectX12/DirectX12Renderer.hpp b/include/private/Graphics/DirectX12/DirectX12Renderer.hpp index aaafe968..99f31e00 100644 --- a/include/private/Graphics/DirectX12/DirectX12Renderer.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Renderer.hpp @@ -11,12 +11,16 @@ #include "Graphics/DirectX12/DirectX12Buffer.hpp" #include "Graphics/DirectX12/DirectX12Shader.hpp" #include "Graphics/DirectX12/DirectX12Texture.hpp" +#include "Graphics/DirectX12/DirectX12UploadContext.hpp" #include "Graphics/DirectX12/DirectX12UploadRing.hpp" #include #include #include #include #include +#include +#include +#include #include #include @@ -95,7 +99,39 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { virtual void BeginDebugLinePass() override; virtual void EndDebugLinePass() override; + /// Sets a shader's PSO unless a skybox or debug line pass owns it. + void BindShaderPipeline(ID3D12PipelineState* pso); + private: + /// Fixed-function state that, with the shader bytecode, identifies a PSO. + struct PipelineKey { + uint64_t vsHash = 0; + uint64_t psHash = 0; + uint32_t vertexFormat = 0; + D3D12_CULL_MODE cullMode = D3D12_CULL_MODE_FRONT; + BOOL depthClip = TRUE; + INT depthBias = 0; + float depthBiasClamp = 0.0f; + float slopeScaledDepthBias = 0.0f; + D3D12_DEPTH_WRITE_MASK depthWrite = D3D12_DEPTH_WRITE_MASK_ALL; + D3D12_COMPARISON_FUNC depthFunc = D3D12_COMPARISON_FUNC_LESS; + BOOL blendEnable = FALSE; + D3D12_PRIMITIVE_TOPOLOGY_TYPE topology = + D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + DXGI_FORMAT rtvFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + DXGI_FORMAT dsvFormat = DXGI_FORMAT_D32_FLOAT; + + bool operator==(const PipelineKey&) const = default; + }; + + /// Returns the cached PSO for the key, building it on a miss. + ID3D12PipelineState* GetPipeline(const PipelineKey& key, ID3DBlob* vs, + ID3DBlob* ps); + /// Reapplies the last shader PSO after a pass override ends. + void RestoreShaderPipeline(); + /// Copies the light/fog constants into the ring and binds them at b2. + void BindLightConstants(); + /// Creates the DXGI factory, enumerates adapters, and creates the D3D12 device. bool CreateDevice(); bool CreateCommandQueue(); @@ -108,11 +144,6 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { bool CreateDepthStencilView(); bool CreateFence(); bool CreateRootSignature(); - /// Builds the default opaque-geometry pipeline state object. - bool CreatePipelineState(); - /// Builds a pipeline state object from precompiled vertex/pixel shader bytecode. - bool CreatePipelineStateFromShader(ID3DBlob* vertexShaderBlob, - ID3DBlob* pixelShaderBlob); virtual void ConfigureRenderMode() override; virtual void ConfigureRenderFace() override; @@ -143,14 +174,23 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { Microsoft::WRL::ComPtr renderTargets[FrameCount]; Microsoft::WRL::ComPtr depthStencilBuffer; - // Pipeline state + // Pipeline state (PSOs are owned by the cache) Microsoft::WRL::ComPtr rootSignature; - Microsoft::WRL::ComPtr pipelineState; - - // Synchronization (per-frame fence values for non-blocking overlap) + std::vector>> + m_pipelineCache; + ID3D12PipelineState* m_defaultPipelineState = nullptr; + ID3D12PipelineState* m_shaderPipeline = nullptr; + ID3D12PipelineState* m_passPipeline = nullptr; + + // Synchronization: one monotonically increasing fence, and the value + // that marks the end of the last frame recorded into each slot Microsoft::WRL::ComPtr fence; HANDLE fenceEvent = nullptr; + UINT64 m_fenceValue = 0; UINT64 fenceValues[FrameCount] = {}; + uint64_t m_frameSerial = 0; + bool m_frameActive = false; // Descriptor sizes UINT rtvDescriptorSize = 0; @@ -169,24 +209,24 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { DirectX12Texture* m_defaultTexture = nullptr; // Skybox PSO (depth write off, LEQUAL, no cull) - Microsoft::WRL::ComPtr m_skyboxPipelineState; + ID3D12PipelineState* m_skyboxPipelineState = nullptr; + bool m_skyboxPipelineFailed = false; /// Builds the skybox PSO (depth write disabled, LEQUAL compare, no culling). bool CreateSkyboxPipelineState(); // Debug line PSO (line topology) - Microsoft::WRL::ComPtr m_debugLinePipelineState; + ID3D12PipelineState* m_debugLinePipelineState = nullptr; + bool m_debugLinePipelineFailed = false; /// Builds the line-topology PSO used for debug line rendering. bool CreateDebugLinePipelineState(); - // Transient per-frame constant data (per-draw shadow transforms) + // Transient per-frame constant data (constant buffers, light/fog) DirectX12UploadRing m_uploadRing; + // Batched init-time and dynamic buffer/texture uploads + DirectX12UploadContext m_uploader; - // Light/fog constant buffer (persistently-mapped upload heap) - Microsoft::WRL::ComPtr m_lightUBO; - void* m_lightUBOMapped = nullptr; - bool m_lightUBOCreated = false; - /// Allocates the persistently-mapped upload-heap buffer backing the light/fog UBO. - bool CreateLightUBO(); + // Latest light/fog constants, copied into the ring when bound + std::vector m_lightData; // Shadow mapping Microsoft::WRL::ComPtr m_shadowDepthBuffer; @@ -199,8 +239,11 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { bool m_hasPendingLightVP = false; 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 + ID3D12PipelineState* m_shadowPassPSO = nullptr; + Microsoft::WRL::ComPtr m_shadowVSBlob; + bool m_shadowVSCompiled = false; + // First shader VS, fallback for the shadow PSO + Microsoft::WRL::ComPtr m_cachedVSBlob; void SetLightVP(const float* mat) override; /// Allocates the shadow-pass depth buffer, DSV, and shared-heap SRV slot. bool CreateShadowMapResources(); diff --git a/include/private/Graphics/DirectX12/DirectX12Shader.hpp b/include/private/Graphics/DirectX12/DirectX12Shader.hpp index 1fd11a33..48442984 100644 --- a/include/private/Graphics/DirectX12/DirectX12Shader.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Shader.hpp @@ -13,6 +13,7 @@ namespace Sleak { namespace RenderEngine { +class DirectX12Renderer; /// D3D12 vertex+pixel shader pair; the pipeline state object itself is owned per-shader. class DirectX12Shader : public Shader { @@ -35,6 +36,8 @@ class DirectX12Shader : public Shader { void SetPipelineState(Microsoft::WRL::ComPtr pso); ID3D12PipelineState* GetPipelineState() const; void SetCommandList(ID3D12GraphicsCommandList* cmdList); + /// Routes bind() through the renderer so pass-specific PSOs are respected. + void SetRenderer(DirectX12Renderer* renderer); private: /// Compiles one HLSL entry point/profile via D3DCompileFromFile into bytecode. @@ -49,6 +52,7 @@ class DirectX12Shader : public Shader { Microsoft::WRL::ComPtr m_pixelShaderBlob; Microsoft::WRL::ComPtr m_pipelineState; ID3D12GraphicsCommandList* m_commandList = nullptr; + DirectX12Renderer* m_renderer = nullptr; }; } // namespace RenderEngine diff --git a/include/private/Graphics/DirectX12/DirectX12Texture.hpp b/include/private/Graphics/DirectX12/DirectX12Texture.hpp index 2daab27d..85c6f767 100644 --- a/include/private/Graphics/DirectX12/DirectX12Texture.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Texture.hpp @@ -11,13 +11,16 @@ namespace Sleak { namespace RenderEngine { +class DirectX12UploadContext; /// D3D12 2D texture: default-heap resource with an upload-heap staging path and shared-SRV-heap support. class ENGINE_API DirectX12Texture : public Texture { public: + /// Uploads go through the shared uploader when given, else they block. DirectX12Texture(ID3D12Device* device, ID3D12CommandQueue* commandQueue, - ID3D12GraphicsCommandList* commandList = nullptr); + ID3D12GraphicsCommandList* commandList = nullptr, + DirectX12UploadContext* uploader = nullptr); ~DirectX12Texture() override; /// Uploads raw pixel data as a new D3D12 texture resource. @@ -56,28 +59,27 @@ class ENGINE_API DirectX12Texture : public Texture { /// Allocates the default-heap 2D texture resource at the given size/format. bool CreateTextureResource(uint32_t width, uint32_t height, DXGI_FORMAT format); - /// Stages pixel data through the upload heap and copies it into the GPU resource. + /// Stages the pixels plus a box filtered mip chain and copies them in. bool UploadTextureData(const void* data, uint32_t width, uint32_t height); /// Creates the shader resource view into either the shared heap or a per-texture fallback heap. bool CreateSRV(DXGI_FORMAT format); /// Maps the engine's TextureFormat to the matching DXGI_FORMAT. DXGI_FORMAT GetDXGIFormat(TextureFormat format) const; - /// Blocks until the upload-heap copy to the GPU-resident texture completes. - void WaitForUpload(); ID3D12Device* m_device = nullptr; ID3D12CommandQueue* m_commandQueue = nullptr; ID3D12GraphicsCommandList* m_commandList = nullptr; + DirectX12UploadContext* m_uploader = nullptr; Microsoft::WRL::ComPtr m_texture; - Microsoft::WRL::ComPtr m_uploadBuffer; Microsoft::WRL::ComPtr m_srvHeap; // fallback per-texture heap D3D12_GPU_DESCRIPTOR_HANDLE m_srvGpuHandle = {}; bool m_usesSharedHeap = false; uint32_t m_width = 0; uint32_t m_height = 0; + uint32_t m_mipLevels = 1; TextureFormat m_format = TextureFormat::RGBA8; TextureFilter m_filter = TextureFilter::Linear; TextureWrapMode m_wrapMode = TextureWrapMode::Repeat; diff --git a/include/private/Graphics/DirectX12/DirectX12UploadContext.hpp b/include/private/Graphics/DirectX12/DirectX12UploadContext.hpp new file mode 100644 index 00000000..6cd522ca --- /dev/null +++ b/include/private/Graphics/DirectX12/DirectX12UploadContext.hpp @@ -0,0 +1,68 @@ +#ifndef _DIRECTX12_UPLOAD_CONTEXT_HPP_ +#define _DIRECTX12_UPLOAD_CONTEXT_HPP_ + +#include +#include + +#include +#include + +namespace Sleak { +namespace RenderEngine { + +/// Batches CPU to GPU copies into one command list on the direct queue. +/// Staging memory is freed once the batch that used it is done on the GPU. +class DirectX12UploadContext { + public: + DirectX12UploadContext() = default; + ~DirectX12UploadContext() { Release(); } + + DirectX12UploadContext(const DirectX12UploadContext&) = delete; + DirectX12UploadContext& operator=(const DirectX12UploadContext&) = delete; + + /// Creates the fence used to track submitted batches. + bool Initialize(ID3D12Device* device, ID3D12CommandQueue* queue); + + /// Returns the open copy command list, starting a new batch if needed. + ID3D12GraphicsCommandList* GetCommandList(); + + /// Returns a mapped upload buffer that lives until the open batch is done. + void* AllocateStaging(uint64_t size, ID3D12Resource** resource); + + /// Executes the recorded copies without waiting for them. + void Submit(); + + /// Submits and blocks until every batch has finished on the GPU. + void Flush(); + + /// Frees staging memory of batches the GPU has finished. + void Retire(); + + /// Flushes outstanding work and frees everything. + void Release(); + + private: + struct Batch { + Microsoft::WRL::ComPtr allocator; + std::vector> staging; + uint64_t fenceValue = 0; + }; + + ID3D12Device* m_device = nullptr; + ID3D12CommandQueue* m_queue = nullptr; + Microsoft::WRL::ComPtr m_commandList; + Microsoft::WRL::ComPtr m_fence; + HANDLE m_event = nullptr; + uint64_t m_fenceValue = 0; + + Batch m_open; + bool m_recording = false; + std::vector m_inFlight; + std::vector> + m_freeAllocators; +}; + +} // namespace RenderEngine +} // namespace Sleak + +#endif // _DIRECTX12_UPLOAD_CONTEXT_HPP_ diff --git a/src/Graphics/DirectX12/DirectX12Buffer.cpp b/src/Graphics/DirectX12/DirectX12Buffer.cpp index 00905e91..10c14003 100644 --- a/src/Graphics/DirectX12/DirectX12Buffer.cpp +++ b/src/Graphics/DirectX12/DirectX12Buffer.cpp @@ -1,22 +1,28 @@ #include "../../include/private/Graphics/DirectX12/DirectX12Buffer.hpp" +#include "../../include/private/Graphics/DirectX12/DirectX12UploadContext.hpp" #include #include +#include namespace Sleak { namespace RenderEngine { -DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, ID3D12CommandQueue* queue, size_t size, BufferType type) +DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, + ID3D12CommandQueue* queue, size_t size, + BufferType type, + DirectX12UploadContext* uploader) : BufferBase() { assert(device != nullptr); m_device = device; m_commandQueue = queue; + m_uploader = uploader; Size = size; Type = type; // Set the buffer type in the base class ConfigureFromBufferType(type); } -DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, size_t size, +DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, size_t size, D3D12_HEAP_TYPE heapType, D3D12_RESOURCE_STATES resourceState) : BufferBase(), m_heapType(heapType), @@ -31,38 +37,46 @@ DirectX12Buffer::DirectX12Buffer(DirectX12Buffer&& other) noexcept : BufferBase(std::move(other)), m_device(std::move(other.m_device)), m_commandQueue(other.m_commandQueue), + m_uploader(other.m_uploader), m_buffer(std::move(other.m_buffer)), - m_uploadBuffer(std::move(other.m_uploadBuffer)), - m_commandAllocator(std::move(other.m_commandAllocator)), - m_commandList(std::move(other.m_commandList)), m_heapType(other.m_heapType), m_resourceState(other.m_resourceState), m_currentState(other.m_currentState), - m_mappedData(other.m_mappedData) + m_mappedData(other.m_mappedData), + m_constantData(std::move(other.m_constantData)), + m_frameSerial(other.m_frameSerial), + m_frameAddress(other.m_frameAddress) { other.m_commandQueue = nullptr; + other.m_uploader = nullptr; other.m_mappedData = nullptr; + other.m_frameSerial = 0; + other.m_frameAddress = 0; } DirectX12Buffer& DirectX12Buffer::operator=(DirectX12Buffer&& other) noexcept { if (this != &other) { Cleanup(); - + BufferBase::operator=(std::move(other)); m_device = std::move(other.m_device); m_commandQueue = other.m_commandQueue; + m_uploader = other.m_uploader; m_buffer = std::move(other.m_buffer); - m_uploadBuffer = std::move(other.m_uploadBuffer); - m_commandAllocator = std::move(other.m_commandAllocator); - m_commandList = std::move(other.m_commandList); m_heapType = other.m_heapType; m_resourceState = other.m_resourceState; m_currentState = other.m_currentState; m_mappedData = other.m_mappedData; + m_constantData = std::move(other.m_constantData); + m_frameSerial = other.m_frameSerial; + m_frameAddress = other.m_frameAddress; other.m_commandQueue = nullptr; + other.m_uploader = nullptr; other.m_mappedData = nullptr; + other.m_frameSerial = 0; + other.m_frameAddress = 0; } return *this; } @@ -79,22 +93,22 @@ void DirectX12Buffer::ConfigureFromBufferType(BufferType type) m_heapType = D3D12_HEAP_TYPE_DEFAULT; m_resourceState = D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; break; - + case BufferType::Index: m_heapType = D3D12_HEAP_TYPE_DEFAULT; m_resourceState = D3D12_RESOURCE_STATE_INDEX_BUFFER; break; - + case BufferType::Constant: m_heapType = D3D12_HEAP_TYPE_UPLOAD; m_resourceState = D3D12_RESOURCE_STATE_GENERIC_READ; break; - + case BufferType::ShaderResource: m_heapType = D3D12_HEAP_TYPE_DEFAULT; m_resourceState = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; break; - + default: m_heapType = D3D12_HEAP_TYPE_DEFAULT; m_resourceState = D3D12_RESOURCE_STATE_COMMON; @@ -141,7 +155,7 @@ bool DirectX12Buffer::Initialize(const void* data, size_t size) auto desc = CreateBufferDesc(); // When we have initial data for a DEFAULT heap buffer, create in // COPY_DEST state so the upload copy can succeed. The post-copy - // barrier in CreateUploadBuffer transitions to m_resourceState. + // barrier in UploadToDefaultHeap transitions to m_resourceState. D3D12_RESOURCE_STATES initialState = (data && m_heapType == D3D12_HEAP_TYPE_DEFAULT) ? D3D12_RESOURCE_STATE_COPY_DEST @@ -160,11 +174,14 @@ bool DirectX12Buffer::Initialize(const void* data, size_t size) return false; } + if (Type == BufferType::Constant) + m_constantData.assign(Size, 0); + if (data) { // If we have initial data, update the buffer if (m_heapType == D3D12_HEAP_TYPE_DEFAULT) { - // For GPU-only buffers, we need to create an upload buffer - CreateUploadBuffer(data, size); + // For GPU-only buffers, we need to stage the data + UploadToDefaultHeap(data, size); } else { // For CPU-accessible buffers, we can directly map and update Update(const_cast(data), size); @@ -175,71 +192,23 @@ bool DirectX12Buffer::Initialize(const void* data, size_t size) return true; } -bool DirectX12Buffer::CreateUploadBuffer(const void* data, size_t dataSize) +bool DirectX12Buffer::UploadToDefaultHeap(const void* data, size_t dataSize) { - // Create upload heap for transferring data to the default heap - D3D12_HEAP_PROPERTIES uploadHeapProps = {}; - uploadHeapProps.Type = D3D12_HEAP_TYPE_UPLOAD; - uploadHeapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - uploadHeapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - uploadHeapProps.CreationNodeMask = 1; - uploadHeapProps.VisibleNodeMask = 1; - - D3D12_RESOURCE_DESC uploadDesc = CreateBufferDesc(); - - HRESULT hr = m_device->CreateCommittedResource( - &uploadHeapProps, - D3D12_HEAP_FLAG_NONE, - &uploadDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, - nullptr, - IID_PPV_ARGS(&m_uploadBuffer)); - - if (FAILED(hr)) { - // Error handling - return false; + DirectX12UploadContext local; + DirectX12UploadContext* uploader = m_uploader; + if (!uploader) { + if (!local.Initialize(m_device.Get(), m_commandQueue)) + return false; + uploader = &local; } - // Map the upload buffer - void* mappedData = nullptr; - hr = m_uploadBuffer->Map(0, nullptr, &mappedData); - - if (FAILED(hr)) { - // Error handling + ID3D12Resource* staging = nullptr; + void* mapped = uploader->AllocateStaging(dataSize, &staging); + if (!mapped) return false; - } + memcpy(mapped, data, dataSize); - // Copy data to the upload buffer - memcpy(mappedData, data, dataSize); - m_uploadBuffer->Unmap(0, nullptr); - - // Initialize command objects - if (!m_commandAllocator) { - hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, - IID_PPV_ARGS(&m_commandAllocator)); - if (FAILED(hr)) - return false; - } else { - // Allocator already exists from a previous upload — wait for the GPU - // to finish using it before resetting. Create a temporary fence. - WaitForUploadComplete(); - hr = m_commandAllocator->Reset(); - if (FAILED(hr)) - return false; - } - - if (!m_commandList) { - hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, - m_commandAllocator.Get(), nullptr, - IID_PPV_ARGS(&m_commandList)); - if (FAILED(hr)) - return false; - } else { - // Command list already exists in CLOSED state — reset it - hr = m_commandList->Reset(m_commandAllocator.Get(), nullptr); - if (FAILED(hr)) - return false; - } + ID3D12GraphicsCommandList* commandList = uploader->GetCommandList(); // If the buffer is NOT in COPY_DEST state (i.e. it was already uploaded // once and transitioned to its target state), transition it back to @@ -253,11 +222,10 @@ bool DirectX12Buffer::CreateUploadBuffer(const void* data, size_t dataSize) barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - m_commandList->ResourceBarrier(1, &barrier); + commandList->ResourceBarrier(1, &barrier); } - // Copy data from upload buffer to default buffer - m_commandList->CopyBufferRegion(m_buffer.Get(), 0, m_uploadBuffer.Get(), 0, dataSize); + commandList->CopyBufferRegion(m_buffer.Get(), 0, staging, 0, dataSize); // Transition resource to its target state if (m_resourceState != D3D12_RESOURCE_STATE_COPY_DEST) { @@ -269,42 +237,18 @@ bool DirectX12Buffer::CreateUploadBuffer(const void* data, size_t dataSize) barrier.Transition.StateAfter = m_resourceState; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - m_commandList->ResourceBarrier(1, &barrier); + commandList->ResourceBarrier(1, &barrier); } m_currentState = m_resourceState; - hr = m_commandList->Close(); - if (FAILED(hr)) - return false; - + if (uploader == &local) + local.Flush(); return true; } void DirectX12Buffer::Update() { - if (!m_commandList) { - // Constant buffers on UPLOAD heap don't have their own - // command list. They are bound through the renderer's - // command list during ExecuteCommands. - if (Type == BufferType::Constant) - return; - SLEAK_ERROR("Command list is null for buffer Update!"); - return; - } - - switch (Type) { - case BufferType::Vertex: - SetAsVertexBuffer(m_commandList.Get(),0, sizeof(Sleak::Vertex)); - break; - case BufferType::Index: - SetAsIndexBuffer(m_commandList.Get(),DXGI_FORMAT_R32_UINT); - break; - case BufferType::Constant: - SetAsConstantBuffer(m_commandList.Get(),0); - break; - default: - SLEAK_ERROR("Unknown buffer type passed!"); - } + // Binding happens through the renderer's command list. } void DirectX12Buffer::Cleanup() @@ -314,25 +258,21 @@ void DirectX12Buffer::Cleanup() } // Defer GPU buffer destruction — may still be referenced by in-flight commands. - // Upload resources can be freed immediately (copy already submitted). - ReleaseUploadResources(); if (m_buffer) { DeferCleanup(std::move(m_buffer)); m_buffer = nullptr; } + m_constantData.clear(); + m_frameSerial = 0; + m_frameAddress = 0; + bIsInitialized = false; Size = 0; Data = nullptr; bIsMapped = false; } -void DirectX12Buffer::ReleaseUploadResources() { - m_uploadBuffer.Reset(); - m_commandList.Reset(); - m_commandAllocator.Reset(); -} - bool DirectX12Buffer::Map() { if (!m_buffer || bIsMapped) @@ -343,7 +283,7 @@ bool DirectX12Buffer::Map() return false; HRESULT hr = m_buffer->Map(0, nullptr, &m_mappedData); - + if (FAILED(hr)) { // Error handling return false; @@ -369,99 +309,60 @@ void DirectX12Buffer::Update(void* data, size_t size) if (!m_buffer || size > Size || !data) return; - // Store CPU shadow copy for transform CBs (needed by shadow pass) - if (Type == BufferType::Constant && size <= 128) { - StoreCPUShadowCopy(data, size); + if (Type == BufferType::Constant) { + // Store CPU shadow copy for transform CBs (needed by shadow pass) + if (size <= 128) + StoreCPUShadowCopy(data, size); + + if (m_constantData.size() < size) + m_constantData.resize(size); + memcpy(m_constantData.data(), data, size); + m_frameSerial = 0; + m_frameAddress = 0; + return; } if (m_heapType == D3D12_HEAP_TYPE_UPLOAD) { - // CPU-accessible buffer (like constant buffers) if (!bIsMapped && !Map()) return; - memcpy(m_mappedData, data, size); - // Note: For constant buffers, we typically don't unmap until the buffer is destroyed - // If you need different behavior, you can adjust this } else { - // GPU-only buffer (like vertex/index buffers) - // Use an upload buffer and command list to update the buffer - if (CreateUploadBuffer(data, size) && m_commandQueue) { - ID3D12CommandList* ppCmdLists[] = {m_commandList.Get()}; - m_commandQueue->ExecuteCommandLists(1, ppCmdLists); - WaitForUploadComplete(); - } + UploadToDefaultHeap(data, size); } } -void DirectX12Buffer::SetAsVertexBuffer(ID3D12GraphicsCommandList* commandList, UINT slot, UINT stride, UINT offset) { - D3D12_VERTEX_BUFFER_VIEW vbView; - vbView.BufferLocation = m_buffer->GetGPUVirtualAddress() + offset; - vbView.StrideInBytes = stride; - vbView.SizeInBytes = static_cast(Size - offset); - - commandList->IASetVertexBuffers(slot, 1, &vbView); -} - -// New method to set index buffer in a command list -void DirectX12Buffer::SetAsIndexBuffer(ID3D12GraphicsCommandList* commandList, DXGI_FORMAT format, UINT offset) { - D3D12_INDEX_BUFFER_VIEW ibView; - ibView.BufferLocation = m_buffer->GetGPUVirtualAddress() + offset; - ibView.Format = format; // Typically DXGI_FORMAT_R16_UINT or DXGI_FORMAT_R32_UINT - ibView.SizeInBytes = static_cast(Size - offset); - - commandList->IASetIndexBuffer(&ibView); -} - -// New method to bind constant buffer to root signature slot -void DirectX12Buffer::SetAsConstantBuffer(ID3D12GraphicsCommandList* commandList, UINT rootParameterIndex) { - commandList->SetGraphicsRootConstantBufferView( - rootParameterIndex, - m_buffer->GetGPUVirtualAddress() - ); -} - void* DirectX12Buffer::GetData() { return nullptr; } -void DirectX12Buffer::WaitForUploadComplete() -{ - if (!m_device || !m_commandQueue) - return; - - Microsoft::WRL::ComPtr tempFence; - HRESULT hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&tempFence)); - if (FAILED(hr)) return; +namespace { +struct DeferredObject { + Microsoft::WRL::ComPtr object; + uint64_t fenceValue = 0; +}; - m_commandQueue->Signal(tempFence.Get(), 1); +std::vector> s_untagged; +std::vector s_tagged; +} // namespace - HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (!ev) return; - - if (tempFence->GetCompletedValue() < 1) { - tempFence->SetEventOnCompletion(1, ev); - WaitForSingleObject(ev, INFINITE); - } - CloseHandle(ev); +void DirectX12Buffer::DeferCleanup( + Microsoft::WRL::ComPtr object) { + if (object) + s_untagged.push_back(std::move(object)); } -// Static deferred cleanup queue — buffers released after GPU fence wait -static std::vector> s_deferredResources; -static std::vector> s_pendingResources; - -void DirectX12Buffer::DeferCleanup(Microsoft::WRL::ComPtr resource) { - if (resource) - s_pendingResources.push_back(std::move(resource)); +void DirectX12Buffer::TagDeferredCleanup(uint64_t fenceValue) { + for (auto& object : s_untagged) + s_tagged.push_back({std::move(object), fenceValue}); + s_untagged.clear(); } -void DirectX12Buffer::ProcessDeferredCleanup() { - // Release resources deferred from the PREVIOUS frame (GPU guaranteed done - // since BeginRender waits for all fences before calling this). - s_deferredResources.clear(); - // Move current pending to deferred — will be released next frame - s_deferredResources.swap(s_pendingResources); +void DirectX12Buffer::ProcessDeferredCleanup(uint64_t completedValue) { + std::erase_if(s_tagged, [completedValue](const DeferredObject& d) { + return d.fenceValue <= completedValue; + }); } } // namespace RenderEngine -} // namespace Sleak \ No newline at end of file +} // namespace Sleak diff --git a/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp b/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp index a239ecbc..2563e270 100644 --- a/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp +++ b/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp @@ -3,23 +3,26 @@ #ifdef PLATFORM_WIN #include +#include +#include #include #include #include #include +#include #include namespace Sleak { namespace RenderEngine { DirectX12CubemapTexture::DirectX12CubemapTexture( - ID3D12Device* device, ID3D12CommandQueue* commandQueue) - : m_device(device), m_commandQueue(commandQueue) {} + ID3D12Device* device, ID3D12CommandQueue* commandQueue, + DirectX12UploadContext* uploader) + : m_device(device), m_commandQueue(commandQueue), m_uploader(uploader) {} DirectX12CubemapTexture::~DirectX12CubemapTexture() { - m_uploadBuffer.Reset(); - m_srvHeap.Reset(); - m_texture.Reset(); + DirectX12Buffer::DeferCleanup(std::move(m_srvHeap)); + DirectX12Buffer::DeferCleanup(std::move(m_texture)); } bool DirectX12CubemapTexture::LoadCubemap( @@ -216,43 +219,19 @@ bool DirectX12CubemapTexture::CreateCubemapFromFaces( m_device->GetCopyableFootprints(&texDesc, 0, 6, 0, footprints, numRows, rowSizeInBytes, &totalBytes); - // Create upload buffer - D3D12_HEAP_PROPERTIES uploadHeapProps = {}; - uploadHeapProps.Type = D3D12_HEAP_TYPE_UPLOAD; - uploadHeapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - uploadHeapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - uploadHeapProps.CreationNodeMask = 1; - uploadHeapProps.VisibleNodeMask = 1; - - D3D12_RESOURCE_DESC uploadDesc = {}; - uploadDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - uploadDesc.Alignment = 0; - uploadDesc.Width = totalBytes; - uploadDesc.Height = 1; - uploadDesc.DepthOrArraySize = 1; - uploadDesc.MipLevels = 1; - uploadDesc.Format = DXGI_FORMAT_UNKNOWN; - uploadDesc.SampleDesc.Count = 1; - uploadDesc.SampleDesc.Quality = 0; - uploadDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - uploadDesc.Flags = D3D12_RESOURCE_FLAG_NONE; - - hr = m_device->CreateCommittedResource( - &uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, - IID_PPV_ARGS(&m_uploadBuffer)); - - if (FAILED(hr)) { - SLEAK_ERROR( - "DirectX12CubemapTexture: Failed to create upload buffer"); - return false; + DirectX12UploadContext local; + DirectX12UploadContext* uploader = m_uploader; + if (!uploader) { + if (!local.Initialize(m_device, m_commandQueue)) return false; + uploader = &local; } - // Map and copy data for all 6 faces - BYTE* mapped = nullptr; - hr = m_uploadBuffer->Map(0, nullptr, reinterpret_cast(&mapped)); - if (FAILED(hr)) { - SLEAK_ERROR("DirectX12CubemapTexture: Failed to map upload buffer"); + ID3D12Resource* staging = nullptr; + BYTE* mapped = + static_cast(uploader->AllocateStaging(totalBytes, &staging)); + if (!mapped) { + SLEAK_ERROR( + "DirectX12CubemapTexture: Failed to allocate staging memory"); return false; } @@ -267,20 +246,7 @@ bool DirectX12CubemapTexture::CreateCubemapFromFaces( } } - m_uploadBuffer->Unmap(0, nullptr); - - // Create command allocator and list for the upload - Microsoft::WRL::ComPtr cmdAllocator; - Microsoft::WRL::ComPtr cmdList; - - hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, - IID_PPV_ARGS(&cmdAllocator)); - if (FAILED(hr)) return false; - - hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, - cmdAllocator.Get(), nullptr, - IID_PPV_ARGS(&cmdList)); - if (FAILED(hr)) return false; + ID3D12GraphicsCommandList* cmdList = uploader->GetCommandList(); // Copy each face from upload buffer to texture for (UINT face = 0; face < 6; face++) { @@ -290,7 +256,7 @@ bool DirectX12CubemapTexture::CreateCubemapFromFaces( dst.SubresourceIndex = face; D3D12_TEXTURE_COPY_LOCATION src = {}; - src.pResource = m_uploadBuffer.Get(); + src.pResource = staging; src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; src.PlacedFootprint = footprints[face]; @@ -309,11 +275,7 @@ bool DirectX12CubemapTexture::CreateCubemapFromFaces( D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; cmdList->ResourceBarrier(1, &barrier); - cmdList->Close(); - - ID3D12CommandList* ppCmdLists[] = {cmdList.Get()}; - m_commandQueue->ExecuteCommandLists(1, ppCmdLists); - WaitForUpload(); + if (uploader == &local) local.Flush(); // Create SRV descriptor heap D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {}; @@ -346,24 +308,6 @@ bool DirectX12CubemapTexture::CreateCubemapFromFaces( return true; } -void DirectX12CubemapTexture::WaitForUpload() { - Microsoft::WRL::ComPtr fence; - HRESULT hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, - IID_PPV_ARGS(&fence)); - if (FAILED(hr)) return; - - HANDLE event = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (!event) return; - - m_commandQueue->Signal(fence.Get(), 1); - if (fence->GetCompletedValue() < 1) { - fence->SetEventOnCompletion(1, event); - WaitForSingleObject(event, INFINITE); - } - - CloseHandle(event); -} - bool DirectX12CubemapTexture::LoadFromMemory(const void* data, uint32_t width, uint32_t height, TextureFormat format) { diff --git a/src/Graphics/DirectX12/DirectX12Renderer.cpp b/src/Graphics/DirectX12/DirectX12Renderer.cpp index 8c9b662b..00b3eeae 100644 --- a/src/Graphics/DirectX12/DirectX12Renderer.cpp +++ b/src/Graphics/DirectX12/DirectX12Renderer.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include #include @@ -18,6 +20,53 @@ namespace Sleak { namespace RenderEngine { +namespace { +const D3D12_INPUT_ELEMENT_DESC kVertexInputLayout[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, + static_cast(offsetof(Sleak::Vertex, px)), + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, + static_cast(offsetof(Sleak::Vertex, nx)), + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, + static_cast(offsetof(Sleak::Vertex, tx)), + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, + static_cast(offsetof(Sleak::Vertex, r)), + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, + static_cast(offsetof(Sleak::Vertex, u)), + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, +}; + +uint64_t HashBytecode(ID3DBlob* blob) { + if (!blob) return 0; + const auto* bytes = static_cast(blob->GetBufferPointer()); + uint64_t hash = 14695981039346656037ull; + for (SIZE_T i = 0; i < blob->GetBufferSize(); ++i) { + hash ^= bytes[i]; + hash *= 1099511628211ull; + } + return hash; +} + +Microsoft::WRL::ComPtr CompileStage(const wchar_t* path, + const char* entry, + const char* profile) { + Microsoft::WRL::ComPtr blob, errorBlob; + HRESULT hr = D3DCompileFromFile(path, nullptr, nullptr, entry, profile, 0, + 0, &blob, &errorBlob); + if (FAILED(hr)) { + if (errorBlob) { + SLEAK_ERROR("{} ({}) compile error: {}", entry, profile, + (char*)errorBlob->GetBufferPointer()); + } + return nullptr; + } + return blob; +} +} // namespace + /// Registers this backend's buffer/shader/texture factories with ResourceManager. DirectX12Renderer::DirectX12Renderer(Window* window) : window(window) { fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); @@ -25,6 +74,7 @@ DirectX12Renderer::DirectX12Renderer(Window* window) : window(window) { throw std::runtime_error("Failed to create fence event."); } this->Type = RendererType::DirectX12; + m_lightData.assign(sizeof(RenderEngine::ShadowLightUBO), 0); ResourceManager::RegisterCreateBuffer( this, &DirectX12Renderer::CreateBuffer); @@ -38,7 +88,8 @@ DirectX12Renderer::DirectX12Renderer(Window* window) : window(window) { this, &DirectX12Renderer::CreateCubemapTextureFromPanorama); ResourceManager::RegisterCreateTextureFromMemory( [this](const void* data, uint32_t w, uint32_t h, TextureFormat fmt, uint32_t) -> Texture* { - auto* tex = new DirectX12Texture(device.Get(), commandQueue.Get(), commandList.Get()); + auto* tex = new DirectX12Texture(device.Get(), commandQueue.Get(), + commandList.Get(), &m_uploader); if (!tex->LoadFromMemory(data, w, h, fmt)) { delete tex; return nullptr; } UINT slot = AllocateSRVSlot(); tex->CreateSRVIntoHandle(DXGI_FORMAT_R8G8B8A8_UNORM, @@ -70,6 +121,7 @@ bool DirectX12Renderer::Initialize() { if (!CreateDepthStencilView()) return false; if (!CreateFence()) return false; m_uploadRing.Initialize(device.Get(), FrameCount, 64 * 1024); + if (!m_uploader.Initialize(device.Get(), commandQueue.Get())) return false; if (!CreateRootSignature()) return false; if (!CreateSharedSrvHeap()) return false; // PSO is created lazily when CreateShader() is called @@ -78,7 +130,7 @@ bool DirectX12Renderer::Initialize() { { uint32_t whitePixel = 0xFFFFFFFF; // RGBA(255,255,255,255) m_defaultTexture = new DirectX12Texture( - device.Get(), commandQueue.Get(), commandList.Get()); + device.Get(), commandQueue.Get(), commandList.Get(), &m_uploader); if (!m_defaultTexture->LoadFromMemory( &whitePixel, 1, 1, TextureFormat::RGBA8)) { SLEAK_WARN("Failed to create default white texture for DX12"); @@ -305,6 +357,7 @@ bool DirectX12Renderer::CreateFence() { SLEAK_ERROR("Failed to create fence!"); return false; } + m_fenceValue = 0; for (UINT i = 0; i < FrameCount; i++) fenceValues[i] = 0; return true; @@ -446,177 +499,97 @@ bool DirectX12Renderer::CreateRootSignature() { return true; } -bool DirectX12Renderer::CreatePipelineState() { - // Called from CreateShader when we have compiled shader blobs. - // Requires vertexShaderBlob and pixelShaderBlob to be set on - // the DirectX12Shader before calling this. - // This is a no-op placeholder — PSO creation happens in CreateShader. - return true; -} - -bool DirectX12Renderer::CreatePipelineStateFromShader( - ID3DBlob* vertexShaderBlob, ID3DBlob* pixelShaderBlob) { - if (!vertexShaderBlob || !pixelShaderBlob) return false; - - // Input layout matching Sleak::Vertex - D3D12_INPUT_ELEMENT_DESC inputLayout[] = { - {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, px)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, nx)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, tx)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, r)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, u)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - }; +ID3D12PipelineState* DirectX12Renderer::GetPipeline(const PipelineKey& key, + ID3DBlob* vs, + ID3DBlob* ps) { + for (auto& [cachedKey, pso] : m_pipelineCache) { + if (cachedKey == key) return pso.Get(); + } + if (!vs || key.vertexFormat != 0) return nullptr; D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; - psoDesc.InputLayout = {inputLayout, _countof(inputLayout)}; + psoDesc.InputLayout = {kVertexInputLayout, _countof(kVertexInputLayout)}; psoDesc.pRootSignature = rootSignature.Get(); - psoDesc.VS = {vertexShaderBlob->GetBufferPointer(), - vertexShaderBlob->GetBufferSize()}; - psoDesc.PS = {pixelShaderBlob->GetBufferPointer(), - pixelShaderBlob->GetBufferSize()}; + psoDesc.VS = {vs->GetBufferPointer(), vs->GetBufferSize()}; + if (ps) psoDesc.PS = {ps->GetBufferPointer(), ps->GetBufferSize()}; - // Rasterizer state D3D12_RASTERIZER_DESC rasterDesc = {}; rasterDesc.FillMode = D3D12_FILL_MODE_SOLID; - rasterDesc.CullMode = D3D12_CULL_MODE_FRONT; + rasterDesc.CullMode = key.cullMode; rasterDesc.FrontCounterClockwise = FALSE; - rasterDesc.DepthBias = 0; - rasterDesc.DepthBiasClamp = 0.0f; - rasterDesc.SlopeScaledDepthBias = 0.0f; - rasterDesc.DepthClipEnable = TRUE; - rasterDesc.MultisampleEnable = FALSE; - rasterDesc.AntialiasedLineEnable = FALSE; - rasterDesc.ForcedSampleCount = 0; - rasterDesc.ConservativeRaster = - D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; + rasterDesc.DepthBias = key.depthBias; + rasterDesc.DepthBiasClamp = key.depthBiasClamp; + rasterDesc.SlopeScaledDepthBias = key.slopeScaledDepthBias; + rasterDesc.DepthClipEnable = key.depthClip; + rasterDesc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; psoDesc.RasterizerState = rasterDesc; - // Blend state — opaque (no blending) - D3D12_BLEND_DESC blendDesc = {}; - blendDesc.AlphaToCoverageEnable = FALSE; - blendDesc.IndependentBlendEnable = FALSE; - D3D12_RENDER_TARGET_BLEND_DESC rtBlendDesc = {}; - rtBlendDesc.BlendEnable = FALSE; - rtBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; + const bool hasColor = key.rtvFormat != DXGI_FORMAT_UNKNOWN; + D3D12_RENDER_TARGET_BLEND_DESC rtBlend = {}; + rtBlend.BlendEnable = key.blendEnable; + rtBlend.SrcBlend = + key.blendEnable ? D3D12_BLEND_SRC_ALPHA : D3D12_BLEND_ONE; + rtBlend.DestBlend = + key.blendEnable ? D3D12_BLEND_INV_SRC_ALPHA : D3D12_BLEND_ZERO; + rtBlend.BlendOp = D3D12_BLEND_OP_ADD; + rtBlend.SrcBlendAlpha = D3D12_BLEND_ONE; + rtBlend.DestBlendAlpha = + key.blendEnable ? D3D12_BLEND_INV_SRC_ALPHA : D3D12_BLEND_ZERO; + rtBlend.BlendOpAlpha = D3D12_BLEND_OP_ADD; + rtBlend.LogicOp = D3D12_LOGIC_OP_NOOP; + rtBlend.RenderTargetWriteMask = hasColor ? D3D12_COLOR_WRITE_ENABLE_ALL : 0; for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) - blendDesc.RenderTarget[i] = rtBlendDesc; - psoDesc.BlendState = blendDesc; + psoDesc.BlendState.RenderTarget[i] = rtBlend; - // Depth stencil state - D3D12_DEPTH_STENCIL_DESC depthStencilDesc = {}; - depthStencilDesc.DepthEnable = TRUE; - depthStencilDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; - depthStencilDesc.DepthFunc = D3D12_COMPARISON_FUNC_LESS; - depthStencilDesc.StencilEnable = FALSE; - psoDesc.DepthStencilState = depthStencilDesc; + psoDesc.DepthStencilState.DepthEnable = TRUE; + psoDesc.DepthStencilState.DepthWriteMask = key.depthWrite; + psoDesc.DepthStencilState.DepthFunc = key.depthFunc; + psoDesc.DepthStencilState.StencilEnable = FALSE; psoDesc.SampleMask = UINT_MAX; - psoDesc.PrimitiveTopologyType = - D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; - psoDesc.NumRenderTargets = 1; - psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; - psoDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT; + psoDesc.PrimitiveTopologyType = key.topology; + psoDesc.NumRenderTargets = hasColor ? 1 : 0; + psoDesc.RTVFormats[0] = key.rtvFormat; + psoDesc.DSVFormat = key.dsvFormat; psoDesc.SampleDesc.Count = 1; - HRESULT hr = device->CreateGraphicsPipelineState( - &psoDesc, IID_PPV_ARGS(&pipelineState)); + Microsoft::WRL::ComPtr pso; + HRESULT hr = + device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pso)); if (FAILED(hr)) { SLEAK_ERROR("Failed to create pipeline state! HRESULT: 0x{:08X}", static_cast(hr)); - return false; + return nullptr; } - // Cache VS blob so we can build the depth-only shadow PSO later - m_cachedVSBlob = nullptr; - vertexShaderBlob->AddRef(); - m_cachedVSBlob.Attach(vertexShaderBlob); - - SLEAK_INFO("DirectX 12 pipeline state created successfully"); - return true; + m_pipelineCache.emplace_back(key, pso); + return pso.Get(); } bool DirectX12Renderer::CreateShadowPassPSO() { if (m_shadowPassPSO) return true; - if (!m_cachedVSBlob) { - SLEAK_WARN("CreateShadowPassPSO: no cached VS blob yet"); - return false; - } - - D3D12_INPUT_ELEMENT_DESC inputLayout[] = { - {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, px)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, nx)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, tx)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, r)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, u)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - }; - D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; - psoDesc.InputLayout = {inputLayout, _countof(inputLayout)}; - psoDesc.pRootSignature = rootSignature.Get(); - psoDesc.VS = {m_cachedVSBlob->GetBufferPointer(), - m_cachedVSBlob->GetBufferSize()}; - // No pixel shader — depth-only pass - psoDesc.PS = {nullptr, 0}; - - D3D12_RASTERIZER_DESC rasterDesc = {}; - rasterDesc.FillMode = D3D12_FILL_MODE_SOLID; - rasterDesc.CullMode = D3D12_CULL_MODE_NONE; // no culling: all faces cast shadow - rasterDesc.FrontCounterClockwise = FALSE; - rasterDesc.DepthBias = 1000; // constant bias (units of depth buffer LSB) - rasterDesc.DepthBiasClamp = 0.01f; - rasterDesc.SlopeScaledDepthBias = 2.0f; // slope-scale bias for steep surfaces - rasterDesc.DepthClipEnable = TRUE; - psoDesc.RasterizerState = rasterDesc; - - // Blend: no render targets, all writes masked off - D3D12_BLEND_DESC blendDesc = {}; - blendDesc.AlphaToCoverageEnable = FALSE; - blendDesc.IndependentBlendEnable = FALSE; - for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) { - blendDesc.RenderTarget[i].RenderTargetWriteMask = 0; // no color write + if (!m_shadowVSCompiled) { + m_shadowVSCompiled = true; + m_shadowVSBlob = CompileStage( + L"assets/shaders/default_shader_dx12.hlsl", "VS_Main", "vs_5_0"); } - psoDesc.BlendState = blendDesc; - - D3D12_DEPTH_STENCIL_DESC dsDesc = {}; - dsDesc.DepthEnable = TRUE; - dsDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; - dsDesc.DepthFunc = D3D12_COMPARISON_FUNC_LESS; - dsDesc.StencilEnable = FALSE; - psoDesc.DepthStencilState = dsDesc; - - psoDesc.SampleMask = UINT_MAX; - psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; - psoDesc.NumRenderTargets = 0; // depth-only — no RTV - psoDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT; - psoDesc.SampleDesc.Count = 1; - - HRESULT hr = device->CreateGraphicsPipelineState( - &psoDesc, IID_PPV_ARGS(&m_shadowPassPSO)); - if (FAILED(hr)) { - SLEAK_ERROR("Failed to create shadow pass PSO! HRESULT: 0x{:08X}", - static_cast(hr)); + ID3DBlob* vs = m_shadowVSBlob ? m_shadowVSBlob.Get() : m_cachedVSBlob.Get(); + if (!vs) { + SLEAK_WARN("CreateShadowPassPSO: no vertex shader available yet"); return false; } + + PipelineKey key; + key.vsHash = HashBytecode(vs); + key.cullMode = D3D12_CULL_MODE_NONE; + key.depthBias = 1000; + key.depthBiasClamp = 0.01f; + key.slopeScaledDepthBias = 2.0f; + key.rtvFormat = DXGI_FORMAT_UNKNOWN; + + m_shadowPassPSO = GetPipeline(key, vs, nullptr); + if (!m_shadowPassPSO) return false; SLEAK_INFO("D3D12 shadow pass PSO created"); return true; } @@ -629,33 +602,30 @@ void DirectX12Renderer::BeginRender() { memcpy(m_lightVP, m_pendingLightVP, sizeof(m_lightVP)); } - // Wait for ALL pending GPU work to complete before starting a new frame. - // This prevents race conditions on shared constant buffers: without this, - // only the same-slot frame (N-2) is waited on, but frame N-1 may still be - // reading constant buffer data that the CPU is about to overwrite. - UINT64 waitValue = 0; - for (UINT i = 0; i < FrameCount; i++) { - if (fenceValues[i] > waitValue) waitValue = fenceValues[i]; - } - if (fence->GetCompletedValue() < waitValue) { - fence->SetEventOnCompletion(waitValue, fenceEvent); + // Only the frame that last recorded into this slot has to be finished. + // Everything the CPU rewrites per frame lives in per-slot memory. + const UINT64 slotFence = fenceValues[frameIndex]; + if (fence->GetCompletedValue() < slotFence) { + fence->SetEventOnCompletion(slotFence, fenceEvent); WaitForSingleObject(fenceEvent, INFINITE); } - // Process deferred GPU resource deletions now that GPU is idle - DirectX12Buffer::ProcessDeferredCleanup(); + DirectX12Buffer::ProcessDeferredCleanup(fence->GetCompletedValue()); + m_uploader.Retire(); + m_uploader.Submit(); m_uploadRing.BeginFrame(frameIndex); + ++m_frameSerial; + m_frameActive = true; + m_shaderPipeline = nullptr; + m_passPipeline = nullptr; // Reset the command allocator and command list for this frame commandAllocators[frameIndex]->Reset(); commandList->Reset(commandAllocators[frameIndex].Get(), - pipelineState ? pipelineState.Get() : nullptr); + m_defaultPipelineState); - // Set PSO if available (created by CreateShader) - if (pipelineState) { - commandList->SetPipelineState(pipelineState.Get()); - } else { - SLEAK_WARN("BeginRender: pipeline state is null — nothing will draw"); + if (!m_defaultPipelineState) { + SLEAK_WARN("BeginRender: no pipeline state yet, nothing will draw"); } // Set root signature and viewport/scissor @@ -695,6 +665,8 @@ void DirectX12Renderer::BeginRender() { // Shadow pass before main rendering if (m_shadowPassEnabled) { RenderShadowPass(); + if (m_defaultPipelineState) + commandList->SetPipelineState(m_defaultPipelineState); } // Set render targets @@ -720,11 +692,8 @@ void DirectX12Renderer::BeginRender() { m_defaultTexture->Bind(0); } - // Root param 3: CBV at b2 — light/shadow UBO - if (m_lightUBOCreated && m_lightUBO) { - commandList->SetGraphicsRootConstantBufferView( - 3, m_lightUBO->GetGPUVirtualAddress()); - } + // Root param 3: CBV at b2, light/shadow constants + BindLightConstants(); // Root param 4: SRV table at t3 — shadow map if (m_shadowMapCreated) { @@ -764,19 +733,21 @@ void DirectX12Renderer::EndRender() { D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; commandList->ResourceBarrier(1, &barrier); - // Close and execute the command list + // Uploads recorded this frame must land before the draws that use them commandList->Close(); + m_uploader.Submit(); ID3D12CommandList* ppCommandLists[] = {commandList.Get()}; commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); swapChain->Present(m_vsync ? 1 : 0, 0); - // Signal the fence for this frame — do NOT wait here. - // The wait happens in BeginRender() when we need to reuse this - // frame's command allocator, allowing CPU/GPU overlap. - fenceValues[frameIndex]++; - commandQueue->Signal(fence.Get(), fenceValues[frameIndex]); + // No wait here: BeginRender waits for this value only when the slot + // comes around again. + fenceValues[frameIndex] = ++m_fenceValue; + commandQueue->Signal(fence.Get(), m_fenceValue); + DirectX12Buffer::TagDeferredCleanup(m_fenceValue); + m_frameActive = false; UpdateFrameMetrics(); } @@ -784,20 +755,15 @@ void DirectX12Renderer::EndRender() { void DirectX12Renderer::WaitForGPU() { if (!commandQueue || !fence || !fenceEvent) return; - // Find the highest fence value across all frames and wait for it - UINT64 maxFence = 0; - for (UINT i = 0; i < FrameCount; i++) { - if (fenceValues[i] > maxFence) maxFence = fenceValues[i]; - } - const UINT64 waitValue = maxFence + 1; + m_uploader.Submit(); + const UINT64 waitValue = ++m_fenceValue; commandQueue->Signal(fence.Get(), waitValue); - for (UINT i = 0; i < FrameCount; i++) - fenceValues[i] = waitValue; if (fence->GetCompletedValue() < waitValue) { fence->SetEventOnCompletion(waitValue, fenceEvent); WaitForSingleObject(fenceEvent, INFINITE); } + m_uploader.Retire(); } bool DirectX12Renderer::CreateSharedSrvHeap() { @@ -842,6 +808,8 @@ void DirectX12Renderer::Cleanup() { if (!m_Initialized) return; WaitForGPU(); + DirectX12Buffer::TagDeferredCleanup(m_fenceValue); + DirectX12Buffer::ProcessDeferredCleanup(UINT64_MAX); if (bImInitialized) { ImGui_ImplDX12_Shutdown(); @@ -849,19 +817,19 @@ void DirectX12Renderer::Cleanup() { ImGui::DestroyContext(); bImInitialized = false; } - if (m_lightUBO && m_lightUBOMapped) { - m_lightUBO->Unmap(0, nullptr); - m_lightUBOMapped = nullptr; - } - m_lightUBO.Reset(); - m_lightUBOCreated = false; + m_uploader.Release(); m_uploadRing.Release(); imguiSrvHeap.Reset(); m_sharedSrvHeap.Reset(); - m_skyboxPipelineState.Reset(); - pipelineState.Reset(); + m_skyboxPipelineState = nullptr; + m_debugLinePipelineState = nullptr; + m_shadowPassPSO = nullptr; + m_defaultPipelineState = nullptr; + m_shaderPipeline = nullptr; + m_passPipeline = nullptr; + m_pipelineCache.clear(); rootSignature.Reset(); depthStencilBuffer.Reset(); dsvHeap.Reset(); @@ -1061,6 +1029,27 @@ void DirectX12Renderer::BindConstantBuffer(RefPtr buffer, return; } + // Constant contents are copied into this frame's ring slice on the + // first bind after an update, so frames in flight keep their own copy. + if (const void* data = dx12Buf->GetConstantData()) { + D3D12_GPU_VIRTUAL_ADDRESS address = + dx12Buf->GetFrameAddress(m_frameSerial); + if (!address) { + const size_t size = dx12Buf->GetConstantDataSize(); + DirectX12UploadRing::Allocation alloc; + if (m_uploadRing.Allocate(size, alloc)) { + memcpy(alloc.cpu, data, size); + address = alloc.gpu; + dx12Buf->SetFrameAddress(m_frameSerial, address); + } + } + if (address) { + commandList->SetGraphicsRootConstantBufferView(slot, address); + return; + } + } + + if (!dx12Buf->GetD3DBuffer()) return; commandList->SetGraphicsRootConstantBufferView( slot, dx12Buf->GetD3DBuffer()->GetGPUVirtualAddress()); } @@ -1068,49 +1057,44 @@ void DirectX12Renderer::BindConstantBuffer(RefPtr buffer, BufferBase* DirectX12Renderer::CreateBuffer(BufferType Type, uint32_t size, void* data) { assert(size > 0); - auto* buffer = new DirectX12Buffer(device.Get(), commandQueue.Get(), size, Type); + auto* buffer = new DirectX12Buffer(device.Get(), commandQueue.Get(), size, + Type, &m_uploader); if (!buffer->Initialize(data)) { delete buffer; return nullptr; } - - // Execute the buffer's upload command list if it recorded any - // copy commands (DEFAULT heap buffers with initial data). - // Do NOT call WaitForGPU() here — the upload runs on the same - // command queue, so GPU FIFO ordering guarantees the copy - // completes before subsequent render commands execute. - if (buffer->HasPendingCommands()) { - ID3D12CommandList* ppCmdLists[] = {buffer->GetCommandList()}; - commandQueue->ExecuteCommandLists(1, ppCmdLists); - } - return buffer; } Shader* DirectX12Renderer::CreateShader(const std::string& shaderSource) { auto* shader = new DirectX12Shader(device.Get()); - if (shader->compile(shaderSource)) { - // Create a per-shader PSO so each shader gets its own pipeline - if (shader->getVertexShaderBlob()) { - CreatePipelineStateFromShader( - shader->getVertexShaderBlob(), - shader->getPixelShaderBlob()); - // Store the newly created PSO on this shader - shader->SetPipelineState(pipelineState); + if (!shader->compile(shaderSource)) { + delete shader; + return nullptr; + } + + ID3DBlob* vs = shader->getVertexShaderBlob(); + ID3DBlob* ps = shader->getPixelShaderBlob(); + PipelineKey key; + key.vsHash = HashBytecode(vs); + key.psHash = HashBytecode(ps); + if (ID3D12PipelineState* pso = GetPipeline(key, vs, ps)) { + shader->SetPipelineState( + Microsoft::WRL::ComPtr(pso)); + if (!m_defaultPipelineState) { + m_defaultPipelineState = pso; + m_cachedVSBlob = vs; } - // Give the shader access to the command list for bind() - shader->SetCommandList(commandList.Get()); - return shader; } - delete shader; - return nullptr; + shader->SetCommandList(commandList.Get()); + shader->SetRenderer(this); + return shader; } Texture* DirectX12Renderer::CreateTexture( const std::string& TexturePath) { - auto* texture = - new DirectX12Texture(device.Get(), commandQueue.Get(), - commandList.Get()); + auto* texture = new DirectX12Texture(device.Get(), commandQueue.Get(), + commandList.Get(), &m_uploader); if (!texture->LoadFromFile(TexturePath)) { delete texture; return nullptr; @@ -1127,9 +1111,8 @@ Texture* DirectX12Renderer::CreateTexture( Texture* DirectX12Renderer::CreateTextureFromData(uint32_t width, uint32_t height, void* data) { - auto* texture = - new DirectX12Texture(device.Get(), commandQueue.Get(), - commandList.Get()); + auto* texture = new DirectX12Texture(device.Get(), commandQueue.Get(), + commandList.Get(), &m_uploader); if (!texture->LoadFromMemory(data, width, height, TextureFormat::RGBA8)) { delete texture; @@ -1145,8 +1128,8 @@ Texture* DirectX12Renderer::CreateTextureFromData(uint32_t width, Texture* DirectX12Renderer::CreateCubemapTexture( const std::array& facePaths) { - auto* texture = new DirectX12CubemapTexture(device.Get(), - commandQueue.Get()); + auto* texture = new DirectX12CubemapTexture( + device.Get(), commandQueue.Get(), &m_uploader); if (!texture->LoadCubemap(facePaths)) { delete texture; return nullptr; @@ -1159,8 +1142,8 @@ Texture* DirectX12Renderer::CreateCubemapTexture( Texture* DirectX12Renderer::CreateCubemapTextureFromPanorama( const std::string& panoramaPath) { - auto* texture = new DirectX12CubemapTexture(device.Get(), - commandQueue.Get()); + auto* texture = new DirectX12CubemapTexture( + device.Get(), commandQueue.Get(), &m_uploader); if (!texture->LoadEquirectangular(panoramaPath)) { delete texture; return nullptr; @@ -1199,54 +1182,22 @@ void DirectX12Renderer::BindTextureRaw(Sleak::Texture* texture, uint32_t slot) { } } -bool DirectX12Renderer::CreateLightUBO() { - if (m_lightUBOCreated) return true; - - // 256-byte aligned size (CB requirement) - const UINT uboSize = (sizeof(RenderEngine::ShadowLightUBO) + 255) & ~255; - - D3D12_HEAP_PROPERTIES heapProps = {}; - heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; - - D3D12_RESOURCE_DESC desc = {}; - desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - desc.Width = uboSize; - 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 = device->CreateCommittedResource( - &heapProps, D3D12_HEAP_FLAG_NONE, &desc, - D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, - IID_PPV_ARGS(&m_lightUBO)); - if (FAILED(hr)) { - SLEAK_ERROR("Failed to create light UBO! HRESULT: 0x{:08X}", - static_cast(hr)); - return false; - } - - hr = m_lightUBO->Map(0, nullptr, &m_lightUBOMapped); - if (FAILED(hr)) { - SLEAK_ERROR("Failed to map light UBO!"); - return false; - } - - memset(m_lightUBOMapped, 0, uboSize); - m_lightUBOCreated = true; - return true; +void DirectX12Renderer::BindLightConstants() { + DirectX12UploadRing::Allocation alloc; + if (!m_uploadRing.Allocate(m_lightData.size(), alloc)) return; + memcpy(alloc.cpu, m_lightData.data(), m_lightData.size()); + commandList->SetGraphicsRootConstantBufferView(3, alloc.gpu); } void DirectX12Renderer::UpdateShadowLightUBO(const void* data, uint32_t size) { - if (!m_lightUBOCreated && !CreateLightUBO()) return; if (!data) return; - uint32_t copySize = size; - if (copySize > sizeof(RenderEngine::ShadowLightUBO)) - copySize = sizeof(RenderEngine::ShadowLightUBO); - memcpy(m_lightUBOMapped, data, copySize); + size_t copySize = size; + if (copySize > m_lightData.size()) copySize = m_lightData.size(); + memcpy(m_lightData.data(), data, copySize); + + // The ring slice bound at BeginRender holds last frame's values + if (m_frameActive) BindLightConstants(); } void DirectX12Renderer::SetLightVP(const float* mat) { @@ -1326,6 +1277,7 @@ void DirectX12Renderer::RenderShadowPass() { if (!m_shadowMapCreated) { if (!CreateShadowMapResources()) return; } + if (!CreateShadowPassPSO()) return; // Transition shadow buffer to depth write D3D12_RESOURCE_BARRIER barrier = {}; @@ -1353,13 +1305,8 @@ void DirectX12Renderer::RenderShadowPass() { shadowScissor.bottom = m_shadowMapResolution; commandList->RSSetScissorRects(1, &shadowScissor); - // Use depth-only shadow PSO (no PS, no RTV, CULL_NONE + depth bias) - if (!m_shadowPassPSO) CreateShadowPassPSO(); - if (m_shadowPassPSO) { - commandList->SetPipelineState(m_shadowPassPSO.Get()); - } else if (pipelineState) { - commandList->SetPipelineState(pipelineState.Get()); - } + // Depth-only shadow PSO (no PS, no RTV, CULL_NONE + depth bias) + commandList->SetPipelineState(m_shadowPassPSO); commandList->SetGraphicsRootSignature(rootSignature.Get()); commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); @@ -1371,6 +1318,7 @@ void DirectX12Renderer::RenderShadowPass() { if (m_defaultTexture) { m_defaultTexture->Bind(0); } + BindLightConstants(); // Execute shadow draw commands m_inShadowPass = true; @@ -1387,217 +1335,82 @@ void DirectX12Renderer::RenderShadowPass() { bool DirectX12Renderer::CreateSkyboxPipelineState() { if (m_skyboxPipelineState) return true; - if (!pipelineState) return false; - - // Get the main PSO's description by compiling the skybox shader - // We need the same VS/PS blobs used for the main PSO - // Compile the skybox shader directly - Microsoft::WRL::ComPtr vsBlob, psBlob, errorBlob; - - HRESULT hr = D3DCompileFromFile( - L"assets/shaders/skybox_dx12.hlsl", nullptr, nullptr, - "VS_Main", "vs_5_0", 0, 0, &vsBlob, &errorBlob); - if (FAILED(hr)) { - if (errorBlob) { - SLEAK_ERROR("Skybox VS compile error: {}", - (char*)errorBlob->GetBufferPointer()); - } - SLEAK_WARN("Failed to compile skybox VS, using main PSO blobs"); - } - - hr = D3DCompileFromFile( - L"assets/shaders/skybox_dx12.hlsl", nullptr, nullptr, - "PS_Main", "ps_5_0", 0, 0, &psBlob, &errorBlob); - if (FAILED(hr)) { - if (errorBlob) { - SLEAK_ERROR("Skybox PS compile error: {}", - (char*)errorBlob->GetBufferPointer()); - } - SLEAK_WARN("Failed to compile skybox PS"); - } - - if (!vsBlob || !psBlob) return false; - - // Input layout matching Sleak::Vertex - D3D12_INPUT_ELEMENT_DESC inputLayout[] = { - {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, px)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, nx)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, tx)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, r)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, u)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - }; - - D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; - psoDesc.InputLayout = {inputLayout, _countof(inputLayout)}; - psoDesc.pRootSignature = rootSignature.Get(); - psoDesc.VS = {vsBlob->GetBufferPointer(), vsBlob->GetBufferSize()}; - psoDesc.PS = {psBlob->GetBufferPointer(), psBlob->GetBufferSize()}; - - // Rasterizer: no culling for skybox, depth clip OFF to avoid - // clipping at the z=1.0 boundary produced by the .xyww trick - D3D12_RASTERIZER_DESC rasterDesc = {}; - rasterDesc.FillMode = D3D12_FILL_MODE_SOLID; - rasterDesc.CullMode = D3D12_CULL_MODE_NONE; - rasterDesc.FrontCounterClockwise = FALSE; - rasterDesc.DepthClipEnable = FALSE; - psoDesc.RasterizerState = rasterDesc; - - // Blend state — opaque (skybox is fully opaque) - D3D12_BLEND_DESC blendDesc = {}; - blendDesc.AlphaToCoverageEnable = FALSE; - blendDesc.IndependentBlendEnable = FALSE; - D3D12_RENDER_TARGET_BLEND_DESC rtBlendDesc = {}; - rtBlendDesc.BlendEnable = FALSE; - rtBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; - for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) - blendDesc.RenderTarget[i] = rtBlendDesc; - psoDesc.BlendState = blendDesc; - - // Depth stencil: depth write OFF, LESS_EQUAL compare - D3D12_DEPTH_STENCIL_DESC depthStencilDesc = {}; - depthStencilDesc.DepthEnable = TRUE; - depthStencilDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; - depthStencilDesc.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; - depthStencilDesc.StencilEnable = FALSE; - psoDesc.DepthStencilState = depthStencilDesc; - - psoDesc.SampleMask = UINT_MAX; - psoDesc.PrimitiveTopologyType = - D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; - psoDesc.NumRenderTargets = 1; - psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; - psoDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT; - psoDesc.SampleDesc.Count = 1; - - hr = device->CreateGraphicsPipelineState( - &psoDesc, IID_PPV_ARGS(&m_skyboxPipelineState)); - if (FAILED(hr)) { - SLEAK_ERROR( - "Failed to create skybox pipeline state! HRESULT: 0x{:08X}", - static_cast(hr)); - return false; - } - + if (m_skyboxPipelineFailed) return false; + m_skyboxPipelineFailed = true; + + auto vs = + CompileStage(L"assets/shaders/skybox_dx12.hlsl", "VS_Main", "vs_5_0"); + auto ps = + CompileStage(L"assets/shaders/skybox_dx12.hlsl", "PS_Main", "ps_5_0"); + if (!vs || !ps) return false; + + // No culling, depth write off with LEQUAL, and depth clip off so the + // .xyww trick at z=1.0 is not clipped + PipelineKey key; + key.vsHash = HashBytecode(vs.Get()); + key.psHash = HashBytecode(ps.Get()); + key.cullMode = D3D12_CULL_MODE_NONE; + key.depthClip = FALSE; + key.depthWrite = D3D12_DEPTH_WRITE_MASK_ZERO; + key.depthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; + + m_skyboxPipelineState = GetPipeline(key, vs.Get(), ps.Get()); + if (!m_skyboxPipelineState) return false; + + m_skyboxPipelineFailed = false; SLEAK_INFO("DirectX 12 skybox pipeline state created successfully"); return true; } +void DirectX12Renderer::BindShaderPipeline(ID3D12PipelineState* pso) { + if (!pso || !commandList) return; + m_shaderPipeline = pso; + if (m_passPipeline) return; + commandList->SetPipelineState(pso); +} + +void DirectX12Renderer::RestoreShaderPipeline() { + ID3D12PipelineState* pso = + m_shaderPipeline ? m_shaderPipeline : m_defaultPipelineState; + if (pso) commandList->SetPipelineState(pso); +} + void DirectX12Renderer::BeginSkyboxPass() { if (!CreateSkyboxPipelineState()) { SLEAK_WARN("BeginSkyboxPass: failed to create skybox PSO"); return; } - commandList->SetPipelineState(m_skyboxPipelineState.Get()); + m_passPipeline = m_skyboxPipelineState; + commandList->SetPipelineState(m_skyboxPipelineState); } void DirectX12Renderer::EndSkyboxPass() { - if (pipelineState) { - commandList->SetPipelineState(pipelineState.Get()); - } + m_passPipeline = nullptr; + RestoreShaderPipeline(); } bool DirectX12Renderer::CreateDebugLinePipelineState() { if (m_debugLinePipelineState) return true; - if (!pipelineState) return false; - - Microsoft::WRL::ComPtr vsBlob, psBlob, errorBlob; - - HRESULT hr = D3DCompileFromFile( - L"assets/shaders/debug_line_dx12.hlsl", nullptr, nullptr, - "VS_Main", "vs_5_0", 0, 0, &vsBlob, &errorBlob); - if (FAILED(hr)) { - if (errorBlob) - SLEAK_ERROR("Debug line VS compile error: {}", - (char*)errorBlob->GetBufferPointer()); - return false; - } - - hr = D3DCompileFromFile( - L"assets/shaders/debug_line_dx12.hlsl", nullptr, nullptr, - "PS_Main", "ps_5_0", 0, 0, &psBlob, &errorBlob); - if (FAILED(hr)) { - if (errorBlob) - SLEAK_ERROR("Debug line PS compile error: {}", - (char*)errorBlob->GetBufferPointer()); - return false; - } - - if (!vsBlob || !psBlob) return false; - - D3D12_INPUT_ELEMENT_DESC inputLayout[] = { - {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, px)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, nx)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, tx)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, r)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, - static_cast(offsetof(Sleak::Vertex, u)), - D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - }; - - D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; - psoDesc.InputLayout = {inputLayout, _countof(inputLayout)}; - psoDesc.pRootSignature = rootSignature.Get(); - psoDesc.VS = {vsBlob->GetBufferPointer(), vsBlob->GetBufferSize()}; - psoDesc.PS = {psBlob->GetBufferPointer(), psBlob->GetBufferSize()}; - - D3D12_RASTERIZER_DESC rasterDesc = {}; - rasterDesc.FillMode = D3D12_FILL_MODE_SOLID; - rasterDesc.CullMode = D3D12_CULL_MODE_NONE; - rasterDesc.FrontCounterClockwise = FALSE; - rasterDesc.DepthClipEnable = TRUE; - psoDesc.RasterizerState = rasterDesc; - - D3D12_BLEND_DESC blendDesc = {}; - blendDesc.AlphaToCoverageEnable = FALSE; - blendDesc.IndependentBlendEnable = FALSE; - D3D12_RENDER_TARGET_BLEND_DESC rtBlendDesc = {}; - rtBlendDesc.BlendEnable = FALSE; - rtBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; - for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) - blendDesc.RenderTarget[i] = rtBlendDesc; - psoDesc.BlendState = blendDesc; - - D3D12_DEPTH_STENCIL_DESC depthStencilDesc = {}; - depthStencilDesc.DepthEnable = TRUE; - depthStencilDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; - depthStencilDesc.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; - depthStencilDesc.StencilEnable = FALSE; - psoDesc.DepthStencilState = depthStencilDesc; - - psoDesc.SampleMask = UINT_MAX; - psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; - psoDesc.NumRenderTargets = 1; - psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; - psoDesc.DSVFormat = DXGI_FORMAT_D32_FLOAT; - psoDesc.SampleDesc.Count = 1; - - hr = device->CreateGraphicsPipelineState( - &psoDesc, IID_PPV_ARGS(&m_debugLinePipelineState)); - if (FAILED(hr)) { - SLEAK_ERROR( - "Failed to create debug line pipeline state! HRESULT: 0x{:08X}", - static_cast(hr)); - return false; - } - + if (m_debugLinePipelineFailed) return false; + m_debugLinePipelineFailed = true; + + auto vs = CompileStage(L"assets/shaders/debug_line_dx12.hlsl", "VS_Main", + "vs_5_0"); + auto ps = CompileStage(L"assets/shaders/debug_line_dx12.hlsl", "PS_Main", + "ps_5_0"); + if (!vs || !ps) return false; + + PipelineKey key; + key.vsHash = HashBytecode(vs.Get()); + key.psHash = HashBytecode(ps.Get()); + key.cullMode = D3D12_CULL_MODE_NONE; + key.depthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; + key.topology = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; + + m_debugLinePipelineState = GetPipeline(key, vs.Get(), ps.Get()); + if (!m_debugLinePipelineState) return false; + + m_debugLinePipelineFailed = false; SLEAK_INFO("DirectX 12 debug line pipeline state created successfully"); return true; } @@ -1607,14 +1420,14 @@ void DirectX12Renderer::BeginDebugLinePass() { SLEAK_WARN("BeginDebugLinePass: failed to create debug line PSO"); return; } - commandList->SetPipelineState(m_debugLinePipelineState.Get()); + m_passPipeline = m_debugLinePipelineState; + commandList->SetPipelineState(m_debugLinePipelineState); commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_LINELIST); } void DirectX12Renderer::EndDebugLinePass() { - if (pipelineState) { - commandList->SetPipelineState(pipelineState.Get()); - } + m_passPipeline = nullptr; + RestoreShaderPipeline(); commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); } diff --git a/src/Graphics/DirectX12/DirectX12Shader.cpp b/src/Graphics/DirectX12/DirectX12Shader.cpp index 08299956..e35e1d74 100644 --- a/src/Graphics/DirectX12/DirectX12Shader.cpp +++ b/src/Graphics/DirectX12/DirectX12Shader.cpp @@ -1,4 +1,5 @@ #include "../../include/private/Graphics/DirectX12/DirectX12Shader.hpp" +#include "../../include/private/Graphics/DirectX12/DirectX12Renderer.hpp" #include #include #include @@ -53,7 +54,10 @@ bool DirectX12Shader::compile(const std::string& vertPath, } void DirectX12Shader::bind() { - if (m_pipelineState && m_commandList) { + if (!m_pipelineState) return; + if (m_renderer) { + m_renderer->BindShaderPipeline(m_pipelineState.Get()); + } else if (m_commandList) { m_commandList->SetPipelineState(m_pipelineState.Get()); } } @@ -78,6 +82,10 @@ void DirectX12Shader::SetCommandList(ID3D12GraphicsCommandList* cmdList) { m_commandList = cmdList; } +void DirectX12Shader::SetRenderer(DirectX12Renderer* renderer) { + m_renderer = renderer; +} + bool DirectX12Shader::compileShader(const std::string& filePath, const std::string& entryPoint, const std::string& profile, diff --git a/src/Graphics/DirectX12/DirectX12Texture.cpp b/src/Graphics/DirectX12/DirectX12Texture.cpp index f89d725f..a133d253 100644 --- a/src/Graphics/DirectX12/DirectX12Texture.cpp +++ b/src/Graphics/DirectX12/DirectX12Texture.cpp @@ -3,22 +3,65 @@ #ifdef PLATFORM_WIN #include +#include +#include #include +#include #include +#include +#include namespace Sleak { namespace RenderEngine { +namespace { +uint32_t MipCount(uint32_t width, uint32_t height) { + uint32_t count = 1; + uint32_t size = (std::max)(width, height); + while (size > 1) { + size /= 2; + ++count; + } + return count; +} + +void DownsampleRGBA8(const uint8_t* src, uint32_t width, uint32_t height, + std::vector& dst) { + const uint32_t dstWidth = (std::max)(1u, width / 2); + const uint32_t dstHeight = (std::max)(1u, height / 2); + dst.resize(static_cast(dstWidth) * dstHeight * 4); + + for (uint32_t y = 0; y < dstHeight; ++y) { + const uint32_t y0 = (std::min)(y * 2, height - 1); + const uint32_t y1 = (std::min)(y * 2 + 1, height - 1); + for (uint32_t x = 0; x < dstWidth; ++x) { + const uint32_t x0 = (std::min)(x * 2, width - 1); + const uint32_t x1 = (std::min)(x * 2 + 1, width - 1); + for (uint32_t c = 0; c < 4; ++c) { + const uint32_t sum = src[(y0 * width + x0) * 4 + c] + + src[(y0 * width + x1) * 4 + c] + + src[(y1 * width + x0) * 4 + c] + + src[(y1 * width + x1) * 4 + c]; + dst[(static_cast(y) * dstWidth + x) * 4 + c] = + static_cast((sum + 2) / 4); + } + } + } +} +} // namespace + DirectX12Texture::DirectX12Texture(ID3D12Device* device, ID3D12CommandQueue* commandQueue, - ID3D12GraphicsCommandList* commandList) - : m_device(device), m_commandQueue(commandQueue), - m_commandList(commandList) {} + ID3D12GraphicsCommandList* commandList, + DirectX12UploadContext* uploader) + : m_device(device), + m_commandQueue(commandQueue), + m_commandList(commandList), + m_uploader(uploader) {} DirectX12Texture::~DirectX12Texture() { - m_uploadBuffer.Reset(); - m_srvHeap.Reset(); - m_texture.Reset(); + DirectX12Buffer::DeferCleanup(std::move(m_srvHeap)); + DirectX12Buffer::DeferCleanup(std::move(m_texture)); } bool DirectX12Texture::LoadFromMemory(const void* data, uint32_t width, @@ -29,6 +72,7 @@ bool DirectX12Texture::LoadFromMemory(const void* data, uint32_t width, m_width = width; m_height = height; m_format = format; + m_mipLevels = MipCount(width, height); DXGI_FORMAT dxgiFormat = GetDXGIFormat(format); @@ -102,7 +146,7 @@ bool DirectX12Texture::CreateTextureResource(uint32_t width, texDesc.Width = width; texDesc.Height = height; texDesc.DepthOrArraySize = 1; - texDesc.MipLevels = 1; + texDesc.MipLevels = static_cast(m_mipLevels); texDesc.Format = format; texDesc.SampleDesc.Count = 1; texDesc.SampleDesc.Quality = 0; @@ -135,94 +179,68 @@ bool DirectX12Texture::CreateTextureResource(uint32_t width, bool DirectX12Texture::UploadTextureData(const void* data, uint32_t width, uint32_t height) { - // Get the required upload buffer size (with row pitch alignment) - D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; - UINT numRows; - UINT64 rowSizeInBytes; - UINT64 totalBytes; + const UINT mipCount = m_mipLevels; + std::vector footprints(mipCount); + std::vector numRows(mipCount); + std::vector rowSizes(mipCount); + UINT64 totalBytes = 0; D3D12_RESOURCE_DESC texDesc = m_texture->GetDesc(); - m_device->GetCopyableFootprints(&texDesc, 0, 1, 0, &footprint, - &numRows, &rowSizeInBytes, + m_device->GetCopyableFootprints(&texDesc, 0, mipCount, 0, footprints.data(), + numRows.data(), rowSizes.data(), &totalBytes); - // Create upload buffer - D3D12_HEAP_PROPERTIES uploadHeapProps{}; - uploadHeapProps.Type = D3D12_HEAP_TYPE_UPLOAD; - uploadHeapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - uploadHeapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - uploadHeapProps.CreationNodeMask = 1; - uploadHeapProps.VisibleNodeMask = 1; - - D3D12_RESOURCE_DESC uploadDesc{}; - uploadDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - uploadDesc.Alignment = 0; - uploadDesc.Width = totalBytes; - uploadDesc.Height = 1; - uploadDesc.DepthOrArraySize = 1; - uploadDesc.MipLevels = 1; - uploadDesc.Format = DXGI_FORMAT_UNKNOWN; - uploadDesc.SampleDesc.Count = 1; - uploadDesc.SampleDesc.Quality = 0; - uploadDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - uploadDesc.Flags = D3D12_RESOURCE_FLAG_NONE; - - HRESULT hr = m_device->CreateCommittedResource( - &uploadHeapProps, D3D12_HEAP_FLAG_NONE, &uploadDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, - IID_PPV_ARGS(&m_uploadBuffer)); - - if (FAILED(hr)) { - SLEAK_ERROR( - "DirectX12Texture: Failed to create upload buffer"); - return false; + DirectX12UploadContext local; + DirectX12UploadContext* uploader = m_uploader; + if (!uploader) { + if (!local.Initialize(m_device, m_commandQueue)) return false; + uploader = &local; } - // Map and copy data row-by-row (respecting row pitch alignment) - BYTE* mapped = nullptr; - hr = m_uploadBuffer->Map(0, nullptr, - reinterpret_cast(&mapped)); - if (FAILED(hr)) { - SLEAK_ERROR("DirectX12Texture: Failed to map upload buffer"); + ID3D12Resource* staging = nullptr; + BYTE* mapped = + static_cast(uploader->AllocateStaging(totalBytes, &staging)); + if (!mapped) { + SLEAK_ERROR("DirectX12Texture: Failed to allocate staging memory"); return false; } - const BYTE* srcData = static_cast(data); - UINT srcRowPitch = width * 4; // RGBA8 = 4 bytes per pixel - BYTE* destRow = mapped + footprint.Offset; - - for (UINT row = 0; row < numRows; row++) { - memcpy(destRow + row * footprint.Footprint.RowPitch, - srcData + row * srcRowPitch, - srcRowPitch); + const uint8_t* level = static_cast(data); + std::vector current; + std::vector next; + uint32_t levelWidth = width; + uint32_t levelHeight = height; + + for (UINT mip = 0; mip < mipCount; ++mip) { + const UINT rowBytes = levelWidth * 4; // RGBA8 = 4 bytes per pixel + BYTE* destRow = mapped + footprints[mip].Offset; + for (UINT row = 0; row < numRows[mip]; ++row) { + memcpy(destRow + row * footprints[mip].Footprint.RowPitch, + level + row * rowBytes, rowBytes); + } + + if (mip + 1 < mipCount) { + DownsampleRGBA8(level, levelWidth, levelHeight, next); + current.swap(next); + level = current.data(); + levelWidth = (std::max)(1u, levelWidth / 2); + levelHeight = (std::max)(1u, levelHeight / 2); + } } - m_uploadBuffer->Unmap(0, nullptr); + ID3D12GraphicsCommandList* cmdList = uploader->GetCommandList(); + for (UINT mip = 0; mip < mipCount; ++mip) { + D3D12_TEXTURE_COPY_LOCATION dst{}; + dst.pResource = m_texture.Get(); + dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dst.SubresourceIndex = mip; - // Create command allocator and list for the upload - Microsoft::WRL::ComPtr cmdAllocator; - Microsoft::WRL::ComPtr cmdList; + D3D12_TEXTURE_COPY_LOCATION src{}; + src.pResource = staging; + src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + src.PlacedFootprint = footprints[mip]; - hr = m_device->CreateCommandAllocator( - D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAllocator)); - if (FAILED(hr)) return false; - - hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, - cmdAllocator.Get(), nullptr, - IID_PPV_ARGS(&cmdList)); - if (FAILED(hr)) return false; - - // Copy from upload buffer to texture - D3D12_TEXTURE_COPY_LOCATION dst{}; - dst.pResource = m_texture.Get(); - dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - dst.SubresourceIndex = 0; - - D3D12_TEXTURE_COPY_LOCATION src{}; - src.pResource = m_uploadBuffer.Get(); - src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; - src.PlacedFootprint = footprint; - - cmdList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); + cmdList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); + } // Transition from COPY_DEST to PIXEL_SHADER_RESOURCE D3D12_RESOURCE_BARRIER barrier{}; @@ -236,13 +254,7 @@ bool DirectX12Texture::UploadTextureData(const void* data, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; cmdList->ResourceBarrier(1, &barrier); - cmdList->Close(); - - // Execute and wait - ID3D12CommandList* ppCmdLists[] = {cmdList.Get()}; - m_commandQueue->ExecuteCommandLists(1, ppCmdLists); - WaitForUpload(); - + if (uploader == &local) local.Flush(); return true; } @@ -267,7 +279,7 @@ bool DirectX12Texture::CreateSRV(DXGI_FORMAT format) { D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srvDesc.Format = format; srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = 1; + srvDesc.Texture2D.MipLevels = m_mipLevels; m_device->CreateShaderResourceView( m_texture.Get(), &srvDesc, @@ -282,7 +294,7 @@ bool DirectX12Texture::CreateSRVIntoHandle(DXGI_FORMAT format, srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srvDesc.Format = format; srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = 1; + srvDesc.Texture2D.MipLevels = m_mipLevels; m_device->CreateShaderResourceView(m_texture.Get(), &srvDesc, cpuHandle); return true; } @@ -300,24 +312,6 @@ DXGI_FORMAT DirectX12Texture::GetDXGIFormat(TextureFormat format) const { } } -void DirectX12Texture::WaitForUpload() { - Microsoft::WRL::ComPtr fence; - HRESULT hr = m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, - IID_PPV_ARGS(&fence)); - if (FAILED(hr)) return; - - HANDLE event = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (!event) return; - - m_commandQueue->Signal(fence.Get(), 1); - if (fence->GetCompletedValue() < 1) { - fence->SetEventOnCompletion(1, event); - WaitForSingleObject(event, INFINITE); - } - - CloseHandle(event); -} - } // namespace RenderEngine } // namespace Sleak diff --git a/src/Graphics/DirectX12/DirectX12UploadContext.cpp b/src/Graphics/DirectX12/DirectX12UploadContext.cpp new file mode 100644 index 00000000..69f7931c --- /dev/null +++ b/src/Graphics/DirectX12/DirectX12UploadContext.cpp @@ -0,0 +1,176 @@ +#include +#include +#include + +namespace Sleak { +namespace RenderEngine { + +bool DirectX12UploadContext::Initialize(ID3D12Device* device, + ID3D12CommandQueue* queue) { + Release(); + if (!device || !queue) return false; + + HRESULT hr = + device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)); + if (FAILED(hr)) { + SLEAK_ERROR("Failed to create upload fence! HRESULT: 0x{:08X}", + static_cast(hr)); + return false; + } + + m_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (!m_event) { + SLEAK_ERROR("Failed to create upload fence event!"); + m_fence.Reset(); + return false; + } + + m_device = device; + m_queue = queue; + return true; +} + +ID3D12GraphicsCommandList* DirectX12UploadContext::GetCommandList() { + if (m_recording) return m_commandList.Get(); + if (!m_device) return nullptr; + + Microsoft::WRL::ComPtr allocator; + HRESULT hr = S_OK; + if (!m_freeAllocators.empty()) { + allocator = std::move(m_freeAllocators.back()); + m_freeAllocators.pop_back(); + hr = allocator->Reset(); + } else { + hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(&allocator)); + } + if (FAILED(hr)) { + SLEAK_ERROR("Failed to prepare upload allocator! HRESULT: 0x{:08X}", + static_cast(hr)); + return nullptr; + } + + if (m_commandList) { + hr = m_commandList->Reset(allocator.Get(), nullptr); + } else { + hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, + allocator.Get(), nullptr, + IID_PPV_ARGS(&m_commandList)); + if (SUCCEEDED(hr)) m_commandList->SetName(L"UploadCommandList"); + } + if (FAILED(hr)) { + SLEAK_ERROR("Failed to open upload command list! HRESULT: 0x{:08X}", + static_cast(hr)); + return nullptr; + } + + m_open.allocator = std::move(allocator); + m_recording = true; + return m_commandList.Get(); +} + +void* DirectX12UploadContext::AllocateStaging(uint64_t size, + ID3D12Resource** resource) { + if (!resource || size == 0 || !GetCommandList()) return nullptr; + + 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; + + Microsoft::WRL::ComPtr staging; + HRESULT hr = m_device->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, &desc, + D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&staging)); + if (FAILED(hr)) { + SLEAK_ERROR("Failed to create staging buffer! HRESULT: 0x{:08X}", + static_cast(hr)); + return nullptr; + } + + D3D12_RANGE noRead = {0, 0}; + void* mapped = nullptr; + hr = staging->Map(0, &noRead, &mapped); + if (FAILED(hr)) { + SLEAK_ERROR("Failed to map staging buffer! HRESULT: 0x{:08X}", + static_cast(hr)); + return nullptr; + } + + *resource = staging.Get(); + m_open.staging.push_back(std::move(staging)); + return mapped; +} + +void DirectX12UploadContext::Submit() { + if (!m_recording) return; + m_recording = false; + + HRESULT hr = m_commandList->Close(); + if (FAILED(hr)) { + SLEAK_ERROR("Failed to close upload command list! HRESULT: 0x{:08X}", + static_cast(hr)); + m_open = Batch{}; + return; + } + + ID3D12CommandList* lists[] = {m_commandList.Get()}; + m_queue->ExecuteCommandLists(1, lists); + m_open.fenceValue = ++m_fenceValue; + m_queue->Signal(m_fence.Get(), m_fenceValue); + + m_inFlight.push_back(std::move(m_open)); + m_open = Batch{}; +} + +void DirectX12UploadContext::Flush() { + Submit(); + if (!m_fence) return; + if (m_fence->GetCompletedValue() < m_fenceValue) { + m_fence->SetEventOnCompletion(m_fenceValue, m_event); + WaitForSingleObject(m_event, INFINITE); + } + Retire(); +} + +void DirectX12UploadContext::Retire() { + if (!m_fence) return; + const uint64_t completed = m_fence->GetCompletedValue(); + + size_t finished = 0; + while (finished < m_inFlight.size() && + m_inFlight[finished].fenceValue <= completed) { + m_freeAllocators.push_back(std::move(m_inFlight[finished].allocator)); + ++finished; + } + m_inFlight.erase(m_inFlight.begin(), m_inFlight.begin() + finished); +} + +void DirectX12UploadContext::Release() { + if (m_queue && m_fence) Flush(); + + m_inFlight.clear(); + m_freeAllocators.clear(); + m_open = Batch{}; + m_recording = false; + m_commandList.Reset(); + m_fence.Reset(); + if (m_event) { + CloseHandle(m_event); + m_event = nullptr; + } + m_fenceValue = 0; + m_device = nullptr; + m_queue = nullptr; +} + +} // namespace RenderEngine +} // namespace Sleak From 1df3c2254e6e9bf0e404b43194e9cc48d984ab78 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Thu, 8 Oct 2026 11:23:52 +0300 Subject: [PATCH 2/3] style: apply clang-format to changed lines --- .../Graphics/DirectX12/DirectX12Buffer.hpp | 114 +++++++++--------- .../DirectX12/DirectX12CubemapTexture.hpp | 97 +++++++-------- .../Graphics/DirectX12/DirectX12Renderer.hpp | 17 ++- .../Graphics/DirectX12/DirectX12Shader.hpp | 2 +- .../Graphics/DirectX12/DirectX12Texture.hpp | 71 +++++------ src/Graphics/DirectX12/DirectX12Buffer.cpp | 53 ++++---- .../DirectX12/DirectX12CubemapTexture.cpp | 7 +- src/Graphics/DirectX12/DirectX12Renderer.cpp | 26 ++-- src/Graphics/DirectX12/DirectX12Shader.cpp | 5 +- src/Graphics/DirectX12/DirectX12Texture.cpp | 3 +- 10 files changed, 199 insertions(+), 196 deletions(-) diff --git a/include/private/Graphics/DirectX12/DirectX12Buffer.hpp b/include/private/Graphics/DirectX12/DirectX12Buffer.hpp index 6a843810..eb7e6fbd 100644 --- a/include/private/Graphics/DirectX12/DirectX12Buffer.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Buffer.hpp @@ -1,12 +1,14 @@ #ifndef DIRECTX12BUFFER_HPP_ #define DIRECTX12BUFFER_HPP_ -#include "../Common/BufferBase.hpp" #include #include + #include #include #include +#include "../Common/BufferBase.hpp" + namespace Sleak { namespace RenderEngine { class DirectX12UploadContext; @@ -14,57 +16,57 @@ class DirectX12UploadContext; /// D3D12 buffer wrapper: default-heap resource plus an upload heap for CPU-to-GPU copies. class DirectX12Buffer : public BufferBase { public: - /// Uploads go through the shared uploader when given, else they block. - DirectX12Buffer(ID3D12Device* device, ID3D12CommandQueue* queue, - size_t size, BufferType type, - DirectX12UploadContext* uploader = nullptr); - - // Constructor for custom buffer configuration - DirectX12Buffer(ID3D12Device* device, size_t size, D3D12_HEAP_TYPE heapType, - D3D12_RESOURCE_STATES resourceState); - - // Prevent copying - DirectX12Buffer(const DirectX12Buffer&) = delete; - DirectX12Buffer& operator=(const DirectX12Buffer&) = delete; - - // Allow moving - DirectX12Buffer(DirectX12Buffer&&) noexcept; - DirectX12Buffer& operator=(DirectX12Buffer&&) noexcept; - - ~DirectX12Buffer() override; - /// Creates the default-heap resource and uploads initial data via a staging buffer, if any. - bool Initialize(void* Data) override; - void Update() override; - void Cleanup() override; - - /// Maps the buffer for CPU writes (upload-heap buffers only). - bool Map() override; - void Unmap() override; - /// Overwrites buffer contents via a fresh upload-heap copy. - void Update(void* data, size_t size) override; - /// Creates the buffer sized and pre-populated from a raw payload. - bool Initialize(const void* data, size_t size); - - void* GetData() override; - - // Getter for the underlying D3D buffer - ID3D12Resource* GetD3DBuffer() const { return m_buffer.Get(); } - - /// Latest constant buffer contents, copied into the upload ring on bind. - const void* GetConstantData() const { - return m_constantData.empty() ? nullptr : m_constantData.data(); - } - size_t GetConstantDataSize() const { return m_constantData.size(); } - - /// Ring address already holding the current contents for this frame, or 0. - D3D12_GPU_VIRTUAL_ADDRESS GetFrameAddress(uint64_t frame) const { - return m_frameSerial == frame ? m_frameAddress : 0; - } - /// Remembers where this frame's copy of the contents lives in the ring. - void SetFrameAddress(uint64_t frame, D3D12_GPU_VIRTUAL_ADDRESS address) { - m_frameSerial = frame; - m_frameAddress = address; - } + /// Uploads go through the shared uploader when given, else they block. + DirectX12Buffer(ID3D12Device* device, ID3D12CommandQueue* queue, size_t size, + BufferType type, DirectX12UploadContext* uploader = nullptr); + + // Constructor for custom buffer configuration + DirectX12Buffer(ID3D12Device* device, size_t size, D3D12_HEAP_TYPE heapType, + D3D12_RESOURCE_STATES resourceState); + + // Prevent copying + DirectX12Buffer(const DirectX12Buffer&) = delete; + DirectX12Buffer& operator=(const DirectX12Buffer&) = delete; + + // Allow moving + DirectX12Buffer(DirectX12Buffer&&) noexcept; + DirectX12Buffer& operator=(DirectX12Buffer&&) noexcept; + + ~DirectX12Buffer() override; + /// Creates the default-heap resource and uploads initial data via a staging + /// buffer, if any. + bool Initialize(void* Data) override; + void Update() override; + void Cleanup() override; + + /// Maps the buffer for CPU writes (upload-heap buffers only). + bool Map() override; + void Unmap() override; + /// Overwrites buffer contents via a fresh upload-heap copy. + void Update(void* data, size_t size) override; + /// Creates the buffer sized and pre-populated from a raw payload. + bool Initialize(const void* data, size_t size); + + void* GetData() override; + + // Getter for the underlying D3D buffer + ID3D12Resource* GetD3DBuffer() const { return m_buffer.Get(); } + + /// Latest constant buffer contents, copied into the upload ring on bind. + const void* GetConstantData() const { + return m_constantData.empty() ? nullptr : m_constantData.data(); + } + size_t GetConstantDataSize() const { return m_constantData.size(); } + + /// Ring address already holding the current contents for this frame, or 0. + D3D12_GPU_VIRTUAL_ADDRESS GetFrameAddress(uint64_t frame) const { + return m_frameSerial == frame ? m_frameAddress : 0; + } + /// Remembers where this frame's copy of the contents lives in the ring. + void SetFrameAddress(uint64_t frame, D3D12_GPU_VIRTUAL_ADDRESS address) { + m_frameSerial = frame; + m_frameAddress = address; + } // Additional utility methods bool IsValid() const { return m_buffer != nullptr; } @@ -81,7 +83,7 @@ class DirectX12Buffer : public BufferBase { /// Records a staged copy into the default-heap buffer. bool UploadToDefaultHeap(const void* data, size_t dataSize); -public: + public: /// Queues a GPU object for release once the frame that used it is done. static void DeferCleanup(Microsoft::WRL::ComPtr object); /// Stamps everything queued since the last call with its retire value. @@ -89,12 +91,12 @@ class DirectX12Buffer : public BufferBase { /// Releases every queued object whose fence value has completed. static void ProcessDeferredCleanup(uint64_t completedValue); -private: + private: // Smart pointers for proper resource management Microsoft::WRL::ComPtr m_device; ID3D12CommandQueue* m_commandQueue = nullptr; // non-owning, for upload execution - DirectX12UploadContext* m_uploader = nullptr; // non-owning + DirectX12UploadContext* m_uploader = nullptr; // non-owning Microsoft::WRL::ComPtr m_buffer; D3D12_HEAP_TYPE m_heapType = D3D12_HEAP_TYPE_DEFAULT; @@ -109,4 +111,4 @@ class DirectX12Buffer : public BufferBase { }; } // namespace RenderEngine } // namespace Sleak -#endif // DIRECTX12BUFFER_HPP_ +#endif // DIRECTX12BUFFER_HPP_ diff --git a/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp b/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp index 5fe47ab1..9a817b19 100644 --- a/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp +++ b/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp @@ -19,62 +19,63 @@ class DirectX12UploadContext; /// D3D12 cubemap texture, loadable from six face images or a single equirectangular panorama. class ENGINE_API DirectX12CubemapTexture : public ::Sleak::Texture { public: - /// Uploads go through the shared uploader when given, else they block. - DirectX12CubemapTexture(ID3D12Device* device, - ID3D12CommandQueue* commandQueue, - DirectX12UploadContext* uploader = nullptr); - ~DirectX12CubemapTexture() override; - - /// Loads and uploads 6 face images: +X, -X, +Y, -Y, +Z, -Z. - bool LoadCubemap(const std::array& facePaths); - /// Loads a single equirectangular panorama and resamples it into 6 cube faces. - bool LoadEquirectangular(const std::string& path, uint32_t faceSize = 512); - - // Texture interface - /// Unused for cubemaps; load via LoadCubemap/LoadEquirectangular instead. - bool LoadFromMemory(const void* data, uint32_t width, uint32_t height, - TextureFormat format) override; - /// Unused for cubemaps; load via LoadCubemap/LoadEquirectangular instead. - bool LoadFromFile(const std::string& filePath) override; - - void Bind(uint32_t slot = 0) const override; - void Unbind() const override; - - void SetFilter(TextureFilter filter) override; - void SetWrapMode(TextureWrapMode wrapMode) override; - - uint32_t GetWidth() const override { return m_width; } - uint32_t GetHeight() const override { return m_height; } - TextureFormat GetFormat() const override { return TextureFormat::RGBA8; } - TextureType GetType() const override { return TextureType::TextureCube; } - - ID3D12Resource* GetResource() const { return m_texture.Get(); } - - /// Binds this cubemap's SRV table at the given root parameter for a draw. - void BindToCommandList(ID3D12GraphicsCommandList* cmdList, - UINT rootParameterIndex) const; - - // Set shared SRV heap slot (called by renderer during creation) - void SetSharedSrvGPUHandle(D3D12_GPU_DESCRIPTOR_HANDLE handle) { m_srvGpuHandle = handle; m_usesSharedHeap = true; } + /// Uploads go through the shared uploader when given, else they block. + DirectX12CubemapTexture(ID3D12Device* device, ID3D12CommandQueue* commandQueue, + DirectX12UploadContext* uploader = nullptr); + ~DirectX12CubemapTexture() override; + + /// Loads and uploads 6 face images: +X, -X, +Y, -Y, +Z, -Z. + bool LoadCubemap(const std::array& facePaths); + /// Loads a single equirectangular panorama and resamples it into 6 cube faces. + bool LoadEquirectangular(const std::string& path, uint32_t faceSize = 512); + + // Texture interface + /// Unused for cubemaps; load via LoadCubemap/LoadEquirectangular instead. + bool LoadFromMemory(const void* data, uint32_t width, uint32_t height, + TextureFormat format) override; + /// Unused for cubemaps; load via LoadCubemap/LoadEquirectangular instead. + bool LoadFromFile(const std::string& filePath) override; + + void Bind(uint32_t slot = 0) const override; + void Unbind() const override; + + void SetFilter(TextureFilter filter) override; + void SetWrapMode(TextureWrapMode wrapMode) override; + + uint32_t GetWidth() const override { return m_width; } + uint32_t GetHeight() const override { return m_height; } + TextureFormat GetFormat() const override { return TextureFormat::RGBA8; } + TextureType GetType() const override { return TextureType::TextureCube; } + + ID3D12Resource* GetResource() const { return m_texture.Get(); } + + /// Binds this cubemap's SRV table at the given root parameter for a draw. + void BindToCommandList(ID3D12GraphicsCommandList* cmdList, + UINT rootParameterIndex) const; + + // Set shared SRV heap slot (called by renderer during creation) + void SetSharedSrvGPUHandle(D3D12_GPU_DESCRIPTOR_HANDLE handle) { + m_srvGpuHandle = handle; + m_usesSharedHeap = true; } // Create SRV directly into an externally-provided CPU handle (cube SRV) void CreateSRVIntoHandle(D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle); private: /// Builds the cubemap texture array and shader resource view from decoded face pixels. - bool CreateCubemapFromFaces(const std::vector& faceData, - uint32_t faceSize); + bool CreateCubemapFromFaces(const std::vector& faceData, + uint32_t faceSize); - ID3D12Device* m_device = nullptr; - ID3D12CommandQueue* m_commandQueue = nullptr; - DirectX12UploadContext* m_uploader = nullptr; + ID3D12Device* m_device = nullptr; + ID3D12CommandQueue* m_commandQueue = nullptr; + DirectX12UploadContext* m_uploader = nullptr; - Microsoft::WRL::ComPtr m_texture; - Microsoft::WRL::ComPtr m_srvHeap; // fallback - D3D12_GPU_DESCRIPTOR_HANDLE m_srvGpuHandle = {}; - bool m_usesSharedHeap = false; + Microsoft::WRL::ComPtr m_texture; + Microsoft::WRL::ComPtr m_srvHeap; // fallback + D3D12_GPU_DESCRIPTOR_HANDLE m_srvGpuHandle = {}; + bool m_usesSharedHeap = false; - uint32_t m_width = 0; - uint32_t m_height = 0; + uint32_t m_width = 0; + uint32_t m_height = 0; }; } // namespace RenderEngine diff --git a/include/private/Graphics/DirectX12/DirectX12Renderer.hpp b/include/private/Graphics/DirectX12/DirectX12Renderer.hpp index cfd6e513..fdbb5a95 100644 --- a/include/private/Graphics/DirectX12/DirectX12Renderer.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Renderer.hpp @@ -12,12 +12,11 @@ #include #include +#include #include #include #include -#include - #include "Graphics/Common/RenderContext.hpp" #include "Graphics/Common/Renderer.hpp" #include "Graphics/Common/ResourceManager.hpp" @@ -112,7 +111,7 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { /// Sets a shader's PSO unless a skybox or debug line pass owns it. void BindShaderPipeline(ID3D12PipelineState* pso); -private: + private: /// Fixed-function state that, with the shader bytecode, identifies a PSO. struct PipelineKey { uint64_t vsHash = 0; @@ -186,8 +185,8 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { // Pipeline state (PSOs are owned by the cache) Microsoft::WRL::ComPtr rootSignature; - std::vector>> + std::vector< + std::pair>> m_pipelineCache; ID3D12PipelineState* m_defaultPipelineState = nullptr; ID3D12PipelineState* m_shaderPipeline = nullptr; @@ -249,11 +248,11 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { bool m_hasPendingLightVP = false; bool m_inShadowPass = false; // Depth-only PSO for shadow pass (no PS, no RTV, CULL_NONE) - ID3D12PipelineState* m_shadowPassPSO = nullptr; - Microsoft::WRL::ComPtr m_shadowVSBlob; - bool m_shadowVSCompiled = false; + ID3D12PipelineState* m_shadowPassPSO = nullptr; + Microsoft::WRL::ComPtr m_shadowVSBlob; + bool m_shadowVSCompiled = false; // First shader VS, fallback for the shadow PSO - Microsoft::WRL::ComPtr m_cachedVSBlob; + Microsoft::WRL::ComPtr m_cachedVSBlob; void SetLightVP(const float* mat) override; /// Allocates the shadow-pass depth buffer, DSV, and shared-heap SRV slot. bool CreateShadowMapResources(); diff --git a/include/private/Graphics/DirectX12/DirectX12Shader.hpp b/include/private/Graphics/DirectX12/DirectX12Shader.hpp index 48442984..b0dad66b 100644 --- a/include/private/Graphics/DirectX12/DirectX12Shader.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Shader.hpp @@ -39,7 +39,7 @@ class DirectX12Shader : public Shader { /// Routes bind() through the renderer so pass-specific PSOs are respected. void SetRenderer(DirectX12Renderer* renderer); -private: + private: /// Compiles one HLSL entry point/profile via D3DCompileFromFile into bytecode. bool compileShader(const std::string& filePath, const std::string& entryPoint, diff --git a/include/private/Graphics/DirectX12/DirectX12Texture.hpp b/include/private/Graphics/DirectX12/DirectX12Texture.hpp index 85c6f767..52267ad4 100644 --- a/include/private/Graphics/DirectX12/DirectX12Texture.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Texture.hpp @@ -16,41 +16,42 @@ class DirectX12UploadContext; /// D3D12 2D texture: default-heap resource with an upload-heap staging path and shared-SRV-heap support. class ENGINE_API DirectX12Texture : public Texture { public: - /// Uploads go through the shared uploader when given, else they block. - DirectX12Texture(ID3D12Device* device, - ID3D12CommandQueue* commandQueue, - ID3D12GraphicsCommandList* commandList = nullptr, - DirectX12UploadContext* uploader = nullptr); - ~DirectX12Texture() override; - - /// Uploads raw pixel data as a new D3D12 texture resource. - bool LoadFromMemory(const void* data, uint32_t width, uint32_t height, - TextureFormat format) override; - /// Decodes an image file and uploads it as a new D3D12 texture resource. - bool LoadFromFile(const std::string& filePath) override; - - void Bind(uint32_t slot = 0) const override; - void Unbind() const override; - - void SetFilter(TextureFilter filter) override; - void SetWrapMode(TextureWrapMode wrapMode) override; - - uint32_t GetWidth() const override { return m_width; } - uint32_t GetHeight() const override { return m_height; } - TextureFormat GetFormat() const override { return m_format; } - TextureType GetType() const override { return TextureType::Texture2D; } - - uint64_t GetImGuiTextureID() const override { return m_srvGpuHandle.ptr; } - - ID3D12Resource* GetResource() const { return m_texture.Get(); } - - // Bind SRV table to a command list for rendering (heap already set) - /// Binds this texture's SRV table at the given root parameter for a draw. - void BindToCommandList(ID3D12GraphicsCommandList* cmdList, - UINT rootParameterIndex) const; - - // Set shared SRV heap slot (called by renderer during creation) - void SetSharedSrvGPUHandle(D3D12_GPU_DESCRIPTOR_HANDLE handle) { m_srvGpuHandle = handle; m_usesSharedHeap = true; } + /// Uploads go through the shared uploader when given, else they block. + DirectX12Texture(ID3D12Device* device, ID3D12CommandQueue* commandQueue, + ID3D12GraphicsCommandList* commandList = nullptr, + DirectX12UploadContext* uploader = nullptr); + ~DirectX12Texture() override; + + /// Uploads raw pixel data as a new D3D12 texture resource. + bool LoadFromMemory(const void* data, uint32_t width, uint32_t height, + TextureFormat format) override; + /// Decodes an image file and uploads it as a new D3D12 texture resource. + bool LoadFromFile(const std::string& filePath) override; + + void Bind(uint32_t slot = 0) const override; + void Unbind() const override; + + void SetFilter(TextureFilter filter) override; + void SetWrapMode(TextureWrapMode wrapMode) override; + + uint32_t GetWidth() const override { return m_width; } + uint32_t GetHeight() const override { return m_height; } + TextureFormat GetFormat() const override { return m_format; } + TextureType GetType() const override { return TextureType::Texture2D; } + + uint64_t GetImGuiTextureID() const override { return m_srvGpuHandle.ptr; } + + ID3D12Resource* GetResource() const { return m_texture.Get(); } + + // Bind SRV table to a command list for rendering (heap already set) + /// Binds this texture's SRV table at the given root parameter for a draw. + void BindToCommandList(ID3D12GraphicsCommandList* cmdList, + UINT rootParameterIndex) const; + + // Set shared SRV heap slot (called by renderer during creation) + void SetSharedSrvGPUHandle(D3D12_GPU_DESCRIPTOR_HANDLE handle) { + m_srvGpuHandle = handle; + m_usesSharedHeap = true; } // Create SRV directly into an externally-provided CPU handle (shared heap) bool CreateSRVIntoHandle(DXGI_FORMAT format, D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle); diff --git a/src/Graphics/DirectX12/DirectX12Buffer.cpp b/src/Graphics/DirectX12/DirectX12Buffer.cpp index 10c14003..9eee2d86 100644 --- a/src/Graphics/DirectX12/DirectX12Buffer.cpp +++ b/src/Graphics/DirectX12/DirectX12Buffer.cpp @@ -1,9 +1,11 @@ #include "../../include/private/Graphics/DirectX12/DirectX12Buffer.hpp" -#include "../../include/private/Graphics/DirectX12/DirectX12UploadContext.hpp" + #include #include #include +#include "../../include/private/Graphics/DirectX12/DirectX12UploadContext.hpp" + namespace Sleak { namespace RenderEngine { @@ -11,8 +13,7 @@ DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, ID3D12CommandQueue* queue, size_t size, BufferType type, DirectX12UploadContext* uploader) - : BufferBase() -{ + : BufferBase() { assert(device != nullptr); m_device = device; m_commandQueue = queue; @@ -23,11 +24,9 @@ DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, } DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, size_t size, - D3D12_HEAP_TYPE heapType, D3D12_RESOURCE_STATES resourceState) - : BufferBase(), - m_heapType(heapType), - m_resourceState(resourceState) -{ + D3D12_HEAP_TYPE heapType, + D3D12_RESOURCE_STATES resourceState) + : BufferBase(), m_heapType(heapType), m_resourceState(resourceState) { assert(device != nullptr); m_device = device; Size = size; @@ -45,8 +44,7 @@ DirectX12Buffer::DirectX12Buffer(DirectX12Buffer&& other) noexcept m_mappedData(other.m_mappedData), m_constantData(std::move(other.m_constantData)), m_frameSerial(other.m_frameSerial), - m_frameAddress(other.m_frameAddress) -{ + m_frameAddress(other.m_frameAddress) { other.m_commandQueue = nullptr; other.m_uploader = nullptr; other.m_mappedData = nullptr; @@ -174,8 +172,7 @@ bool DirectX12Buffer::Initialize(const void* data, size_t size) return false; } - if (Type == BufferType::Constant) - m_constantData.assign(Size, 0); + if (Type == BufferType::Constant) m_constantData.assign(Size, 0); if (data) { // If we have initial data, update the buffer @@ -192,20 +189,17 @@ bool DirectX12Buffer::Initialize(const void* data, size_t size) return true; } -bool DirectX12Buffer::UploadToDefaultHeap(const void* data, size_t dataSize) -{ +bool DirectX12Buffer::UploadToDefaultHeap(const void* data, size_t dataSize) { DirectX12UploadContext local; DirectX12UploadContext* uploader = m_uploader; if (!uploader) { - if (!local.Initialize(m_device.Get(), m_commandQueue)) - return false; + if (!local.Initialize(m_device.Get(), m_commandQueue)) return false; uploader = &local; } ID3D12Resource* staging = nullptr; void* mapped = uploader->AllocateStaging(dataSize, &staging); - if (!mapped) - return false; + if (!mapped) return false; memcpy(mapped, data, dataSize); ID3D12GraphicsCommandList* commandList = uploader->GetCommandList(); @@ -242,8 +236,7 @@ bool DirectX12Buffer::UploadToDefaultHeap(const void* data, size_t dataSize) m_currentState = m_resourceState; - if (uploader == &local) - local.Flush(); + if (uploader == &local) local.Flush(); return true; } @@ -257,7 +250,8 @@ void DirectX12Buffer::Cleanup() Unmap(); } - // Defer GPU buffer destruction — may still be referenced by in-flight commands. + // Defer GPU buffer destruction — may still be referenced by in-flight + // commands. if (m_buffer) { DeferCleanup(std::move(m_buffer)); m_buffer = nullptr; @@ -311,11 +305,9 @@ void DirectX12Buffer::Update(void* data, size_t size) if (Type == BufferType::Constant) { // Store CPU shadow copy for transform CBs (needed by shadow pass) - if (size <= 128) - StoreCPUShadowCopy(data, size); + if (size <= 128) StoreCPUShadowCopy(data, size); - if (m_constantData.size() < size) - m_constantData.resize(size); + if (m_constantData.size() < size) m_constantData.resize(size); memcpy(m_constantData.data(), data, size); m_frameSerial = 0; m_frameAddress = 0; @@ -323,11 +315,9 @@ void DirectX12Buffer::Update(void* data, size_t size) } if (m_heapType == D3D12_HEAP_TYPE_UPLOAD) { - if (!bIsMapped && !Map()) - return; + if (!bIsMapped && !Map()) return; memcpy(m_mappedData, data, size); - } - else { + } else { UploadToDefaultHeap(data, size); } } @@ -348,8 +338,7 @@ std::vector s_tagged; void DirectX12Buffer::DeferCleanup( Microsoft::WRL::ComPtr object) { - if (object) - s_untagged.push_back(std::move(object)); + if (object) s_untagged.push_back(std::move(object)); } void DirectX12Buffer::TagDeferredCleanup(uint64_t fenceValue) { @@ -365,4 +354,4 @@ void DirectX12Buffer::ProcessDeferredCleanup(uint64_t completedValue) { } } // namespace RenderEngine -} // namespace Sleak +} // namespace Sleak diff --git a/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp b/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp index 2563e270..434a0bbe 100644 --- a/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp +++ b/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp @@ -2,13 +2,14 @@ #ifdef PLATFORM_WIN +#include + #include #include #include -#include -#include -#include #include +#include +#include #include #include diff --git a/src/Graphics/DirectX12/DirectX12Renderer.cpp b/src/Graphics/DirectX12/DirectX12Renderer.cpp index 31747f4e..eb900ec1 100644 --- a/src/Graphics/DirectX12/DirectX12Renderer.cpp +++ b/src/Graphics/DirectX12/DirectX12Renderer.cpp @@ -1,20 +1,22 @@ #include "../../include/private/Graphics/DirectX12/DirectX12Renderer.hpp" #ifdef PLATFORM_WIN -#include #include -#include -#include -#include +#include + #include +#include +#include +#include +#include +#include +#include #include #include +#include #include #include -#include -#include -#include -#include + #include "Graphics/Common/RenderCommandQueue.hpp" namespace Sleak { @@ -87,10 +89,14 @@ DirectX12Renderer::DirectX12Renderer(Window* window) : window(window) { ResourceManager::RegisterCreateCubemapTextureFromPanorama( this, &DirectX12Renderer::CreateCubemapTextureFromPanorama); ResourceManager::RegisterCreateTextureFromMemory( - [this](const void* data, uint32_t w, uint32_t h, TextureFormat fmt, uint32_t) -> Texture* { + [this](const void* data, uint32_t w, uint32_t h, TextureFormat fmt, + uint32_t) -> Texture* { auto* tex = new DirectX12Texture(device.Get(), commandQueue.Get(), commandList.Get(), &m_uploader); - if (!tex->LoadFromMemory(data, w, h, fmt)) { delete tex; return nullptr; } + if (!tex->LoadFromMemory(data, w, h, fmt)) { + delete tex; + return nullptr; + } UINT slot = AllocateSRVSlot(); tex->CreateSRVIntoHandle(DXGI_FORMAT_R8G8B8A8_UNORM, GetSharedSrvCPUHandle(slot)); diff --git a/src/Graphics/DirectX12/DirectX12Shader.cpp b/src/Graphics/DirectX12/DirectX12Shader.cpp index e35e1d74..05c4a32a 100644 --- a/src/Graphics/DirectX12/DirectX12Shader.cpp +++ b/src/Graphics/DirectX12/DirectX12Shader.cpp @@ -1,10 +1,13 @@ #include "../../include/private/Graphics/DirectX12/DirectX12Shader.hpp" -#include "../../include/private/Graphics/DirectX12/DirectX12Renderer.hpp" + #include #include #include + #include +#include "../../include/private/Graphics/DirectX12/DirectX12Renderer.hpp" + namespace Sleak { namespace RenderEngine { diff --git a/src/Graphics/DirectX12/DirectX12Texture.cpp b/src/Graphics/DirectX12/DirectX12Texture.cpp index a133d253..6e6c4d58 100644 --- a/src/Graphics/DirectX12/DirectX12Texture.cpp +++ b/src/Graphics/DirectX12/DirectX12Texture.cpp @@ -2,10 +2,11 @@ #ifdef PLATFORM_WIN +#include + #include #include #include -#include #include #include #include From 4fa2520d3942e4812921f7556defc5cd23f7929c Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Thu, 8 Oct 2026 11:55:23 +0300 Subject: [PATCH 3/3] style: apply clang-format to changed lines --- include/private/Graphics/DirectX12/DirectX12Buffer.hpp | 1 - include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp | 3 ++- include/private/Graphics/DirectX12/DirectX12Texture.hpp | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/private/Graphics/DirectX12/DirectX12Buffer.hpp b/include/private/Graphics/DirectX12/DirectX12Buffer.hpp index eb7e6fbd..49974ed1 100644 --- a/include/private/Graphics/DirectX12/DirectX12Buffer.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Buffer.hpp @@ -92,7 +92,6 @@ class DirectX12Buffer : public BufferBase { static void ProcessDeferredCleanup(uint64_t completedValue); private: - // Smart pointers for proper resource management Microsoft::WRL::ComPtr m_device; ID3D12CommandQueue* m_commandQueue = nullptr; // non-owning, for upload execution diff --git a/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp b/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp index 9a817b19..0fd60ab8 100644 --- a/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp +++ b/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp @@ -56,7 +56,8 @@ class ENGINE_API DirectX12CubemapTexture : public ::Sleak::Texture { // Set shared SRV heap slot (called by renderer during creation) void SetSharedSrvGPUHandle(D3D12_GPU_DESCRIPTOR_HANDLE handle) { m_srvGpuHandle = handle; - m_usesSharedHeap = true; } + m_usesSharedHeap = true; + } // Create SRV directly into an externally-provided CPU handle (cube SRV) void CreateSRVIntoHandle(D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle); diff --git a/include/private/Graphics/DirectX12/DirectX12Texture.hpp b/include/private/Graphics/DirectX12/DirectX12Texture.hpp index 52267ad4..649fb656 100644 --- a/include/private/Graphics/DirectX12/DirectX12Texture.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Texture.hpp @@ -51,7 +51,8 @@ class ENGINE_API DirectX12Texture : public Texture { // Set shared SRV heap slot (called by renderer during creation) void SetSharedSrvGPUHandle(D3D12_GPU_DESCRIPTOR_HANDLE handle) { m_srvGpuHandle = handle; - m_usesSharedHeap = true; } + m_usesSharedHeap = true; + } // Create SRV directly into an externally-provided CPU handle (shared heap) bool CreateSRVIntoHandle(DXGI_FORMAT format, D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle);