diff --git a/CMakeLists.txt b/CMakeLists.txt index e3aca59c..26a243af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,6 +185,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..49974ed1 100644 --- a/include/private/Graphics/DirectX12/DirectX12Buffer.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Buffer.hpp @@ -1,101 +1,113 @@ #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; + /// 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); - - // 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(); } - - // 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; } - + /// 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; } 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(); - -private: + 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..0fd60ab8 100644 --- a/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp +++ b/include/private/Graphics/DirectX12/DirectX12CubemapTexture.hpp @@ -14,66 +14,69 @@ 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: - DirectX12CubemapTexture(ID3D12Device* device, - ID3D12CommandQueue* commandQueue); - ~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); - /// Blocks until the upload-heap copy to the GPU-resident cubemap resource completes. - void WaitForUpload(); - - ID3D12Device* m_device = nullptr; - ID3D12CommandQueue* m_commandQueue = 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; - - uint32_t m_width = 0; - uint32_t m_height = 0; + bool CreateCubemapFromFaces(const std::vector& faceData, + uint32_t faceSize); + + 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; + + 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 e6d134e0..fdbb5a95 100644 --- a/include/private/Graphics/DirectX12/DirectX12Renderer.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Renderer.hpp @@ -13,6 +13,9 @@ #include #include +#include +#include +#include #include "Graphics/Common/RenderContext.hpp" #include "Graphics/Common/Renderer.hpp" @@ -20,6 +23,7 @@ #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" namespace Sleak { @@ -104,7 +108,39 @@ class ENGINE_API DirectX12Renderer : public Renderer, public RenderContext { virtual void BeginDebugLinePass() override; virtual void EndDebugLinePass() override; -private: + /// 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(); @@ -117,11 +153,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; @@ -152,14 +183,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< + std::pair>> + 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; @@ -178,24 +218,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; @@ -208,8 +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) - 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..b0dad66b 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,8 +36,10 @@ 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: + private: /// Compiles one HLSL entry point/profile via D3DCompileFromFile into bytecode. bool compileShader(const std::string& filePath, const std::string& entryPoint, @@ -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..649fb656 100644 --- a/include/private/Graphics/DirectX12/DirectX12Texture.hpp +++ b/include/private/Graphics/DirectX12/DirectX12Texture.hpp @@ -11,43 +11,48 @@ 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: - DirectX12Texture(ID3D12Device* device, - ID3D12CommandQueue* commandQueue, - ID3D12GraphicsCommandList* commandList = nullptr); - ~DirectX12Texture() override; + /// 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; + /// 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 Bind(uint32_t slot = 0) const override; + void Unbind() const override; - void SetFilter(TextureFilter filter) override; - void SetWrapMode(TextureWrapMode wrapMode) 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; } + 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; } + uint64_t GetImGuiTextureID() const override { return m_srvGpuHandle.ptr; } - ID3D12Resource* GetResource() const { return m_texture.Get(); } + 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; + // 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; } + // 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); @@ -56,28 +61,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..9eee2d86 100644 --- a/src/Graphics/DirectX12/DirectX12Buffer.cpp +++ b/src/Graphics/DirectX12/DirectX12Buffer.cpp @@ -1,27 +1,32 @@ #include "../../include/private/Graphics/DirectX12/DirectX12Buffer.hpp" + #include #include +#include + +#include "../../include/private/Graphics/DirectX12/DirectX12UploadContext.hpp" namespace Sleak { namespace RenderEngine { -DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, ID3D12CommandQueue* queue, size_t size, BufferType type) - : BufferBase() -{ +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, - D3D12_HEAP_TYPE heapType, D3D12_RESOURCE_STATES resourceState) - : BufferBase(), - m_heapType(heapType), - m_resourceState(resourceState) -{ +DirectX12Buffer::DirectX12Buffer(ID3D12Device* device, size_t size, + D3D12_HEAP_TYPE heapType, + D3D12_RESOURCE_STATES resourceState) + : BufferBase(), m_heapType(heapType), m_resourceState(resourceState) { assert(device != nullptr); m_device = device; Size = size; @@ -31,38 +36,45 @@ 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 +91,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 +153,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 +172,13 @@ 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 +189,20 @@ bool DirectX12Buffer::Initialize(const void* data, size_t size) return true; } -bool DirectX12Buffer::CreateUploadBuffer(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; - } - - // Map the upload buffer - void* mappedData = nullptr; - hr = m_uploadBuffer->Map(0, nullptr, &mappedData); - - if (FAILED(hr)) { - // Error handling - return false; +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; + uploader = &local; } - // 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; - } + ID3D12Resource* staging = nullptr; + void* mapped = uploader->AllocateStaging(dataSize, &staging); + if (!mapped) return false; + memcpy(mapped, data, dataSize); - 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 +216,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 +231,17 @@ 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() @@ -313,26 +250,23 @@ void DirectX12Buffer::Cleanup() Unmap(); } - // Defer GPU buffer destruction — may still be referenced by in-flight commands. - // Upload resources can be freed immediately (copy already submitted). - ReleaseUploadResources(); + // Defer GPU buffer destruction — may still be referenced by in-flight + // commands. 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 +277,7 @@ bool DirectX12Buffer::Map() return false; HRESULT hr = m_buffer->Map(0, nullptr, &m_mappedData); - + if (FAILED(hr)) { // Error handling return false; @@ -369,99 +303,55 @@ 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; - + 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(); - } + } else { + 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; - - m_commandQueue->Signal(tempFence.Get(), 1); +namespace { +struct DeferredObject { + Microsoft::WRL::ComPtr object; + uint64_t fenceValue = 0; +}; - HANDLE ev = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (!ev) return; +std::vector> s_untagged; +std::vector s_tagged; +} // namespace - 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..434a0bbe 100644 --- a/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp +++ b/src/Graphics/DirectX12/DirectX12CubemapTexture.cpp @@ -2,24 +2,28 @@ #ifdef PLATFORM_WIN -#include #include -#include -#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 +220,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 +247,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 +257,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 +276,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 +309,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 d514baaf..eb900ec1 100644 --- a/src/Graphics/DirectX12/DirectX12Renderer.cpp +++ b/src/Graphics/DirectX12/DirectX12Renderer.cpp @@ -1,23 +1,74 @@ #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 { 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 +76,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); @@ -37,9 +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* { - auto* tex = new DirectX12Texture(device.Get(), commandQueue.Get(), commandList.Get()); - if (!tex->LoadFromMemory(data, w, h, fmt)) { delete tex; return nullptr; } + [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; + } UINT slot = AllocateSRVSlot(); tex->CreateSRVIntoHandle(DXGI_FORMAT_R8G8B8A8_UNORM, GetSharedSrvCPUHandle(slot)); @@ -70,6 +127,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 +136,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 +363,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 +505,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 +608,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 +671,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 +698,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 +739,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 +761,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 +814,8 @@ void DirectX12Renderer::Cleanup() { if (!m_Initialized) return; WaitForGPU(); + DirectX12Buffer::TagDeferredCleanup(m_fenceValue); + DirectX12Buffer::ProcessDeferredCleanup(UINT64_MAX); if (bImInitialized) { ImGui_ImplDX12_Shutdown(); @@ -849,19 +823,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(); @@ -1075,6 +1049,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()); } @@ -1082,49 +1077,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; @@ -1141,9 +1131,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; @@ -1159,8 +1148,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; @@ -1173,8 +1162,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; @@ -1213,54 +1202,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) { @@ -1340,6 +1297,7 @@ void DirectX12Renderer::RenderShadowPass() { if (!m_shadowMapCreated) { if (!CreateShadowMapResources()) return; } + if (!CreateShadowPassPSO()) return; // Transition shadow buffer to depth write D3D12_RESOURCE_BARRIER barrier = {}; @@ -1367,13 +1325,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); @@ -1385,6 +1338,7 @@ void DirectX12Renderer::RenderShadowPass() { if (m_defaultTexture) { m_defaultTexture->Bind(0); } + BindLightConstants(); // Execute shadow draw commands m_inShadowPass = true; @@ -1401,217 +1355,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; } @@ -1621,14 +1440,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..05c4a32a 100644 --- a/src/Graphics/DirectX12/DirectX12Shader.cpp +++ b/src/Graphics/DirectX12/DirectX12Shader.cpp @@ -1,9 +1,13 @@ #include "../../include/private/Graphics/DirectX12/DirectX12Shader.hpp" + #include #include #include + #include +#include "../../include/private/Graphics/DirectX12/DirectX12Renderer.hpp" + namespace Sleak { namespace RenderEngine { @@ -53,7 +57,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 +85,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..6e6c4d58 100644 --- a/src/Graphics/DirectX12/DirectX12Texture.cpp +++ b/src/Graphics/DirectX12/DirectX12Texture.cpp @@ -2,23 +2,67 @@ #ifdef PLATFORM_WIN -#include #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 +73,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 +147,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 +180,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); - - // 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; + 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; - hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, - cmdAllocator.Get(), nullptr, - IID_PPV_ARGS(&cmdList)); - if (FAILED(hr)) return false; + D3D12_TEXTURE_COPY_LOCATION src{}; + src.pResource = staging; + src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + src.PlacedFootprint = footprints[mip]; - // 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 +255,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 +280,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 +295,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 +313,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