diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12Device.h b/engine/include/XCEngine/RHI/D3D12/D3D12Device.h index 759432ca..96b31c9a 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12Device.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12Device.h @@ -72,6 +72,7 @@ public: RHICommandQueue* CreateCommandQueue(const CommandQueueDesc& desc) override; RHIShader* CreateShader(const ShaderCompileDesc& desc) override; RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc& desc) override; + RHIPipelineState* CreateComputePipelineState(const ComputePipelineDesc& desc) override; RHIPipelineLayout* CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) override; RHIFence* CreateFence(const FenceDesc& desc) override; RHISampler* CreateSampler(const SamplerDesc& desc) override; diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12PipelineState.h b/engine/include/XCEngine/RHI/D3D12/D3D12PipelineState.h index df373a94..adc118ee 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12PipelineState.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12PipelineState.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "../RHIPipelineState.h" @@ -31,7 +32,9 @@ public: void SetTopology(uint32_t topologyType) override; void SetRenderTargetFormats(uint32_t count, const uint32_t* formats, uint32_t depthFormat) override; void SetSampleCount(uint32_t count) override; + void SetSampleQuality(uint32_t quality) override; void SetComputeShader(RHIShader* shader) override; + void SetOwnedComputeShader(std::unique_ptr shader); void SetRootSignature(ID3D12RootSignature* rootSignature); // State query @@ -95,6 +98,7 @@ private: uint32_t m_renderTargetFormats[8] = { 0 }; uint32_t m_depthStencilFormat = 0; uint32_t m_sampleCount = 1; + uint32_t m_sampleQuality = 0; // Shader bytecodes (set externally) D3D12_SHADER_BYTECODE m_vsBytecode = {}; @@ -102,6 +106,7 @@ private: D3D12_SHADER_BYTECODE m_gsBytecode = {}; D3D12_SHADER_BYTECODE m_csBytecode = {}; class RHIShader* m_computeShader = nullptr; + std::unique_ptr m_ownedComputeShader; ComPtr m_rootSignature; // D3D12 resources diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLDevice.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLDevice.h index 1d386d8d..6e619be0 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLDevice.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLDevice.h @@ -42,6 +42,7 @@ public: RHICommandQueue* CreateCommandQueue(const CommandQueueDesc& desc) override; RHIShader* CreateShader(const ShaderCompileDesc& desc) override; RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc& desc) override; + RHIPipelineState* CreateComputePipelineState(const ComputePipelineDesc& desc) override; RHIPipelineLayout* CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) override; RHIFence* CreateFence(const FenceDesc& desc) override; RHISampler* CreateSampler(const SamplerDesc& desc) override; diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h index a990faf1..bd1ddcfe 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h @@ -84,7 +84,9 @@ public: void SetTopology(uint32_t topologyType) override; void SetRenderTargetFormats(uint32_t count, const uint32_t* formats, uint32_t depthFormat) override; void SetSampleCount(uint32_t count) override; + void SetSampleQuality(uint32_t quality) override; void SetComputeShader(RHIShader* shader) override; + void SetOwnedComputeShader(std::unique_ptr shader); // State query const RasterizerDesc& GetRasterizerState() const override { return m_rasterizerDesc; } @@ -142,6 +144,7 @@ private: unsigned int m_computeProgram = 0; class RHIShader* m_computeShader = nullptr; std::unique_ptr m_graphicsShader; + std::unique_ptr m_ownedComputeShader; bool m_programAttached = false; // OpenGL specific state diff --git a/engine/include/XCEngine/RHI/RHIDevice.h b/engine/include/XCEngine/RHI/RHIDevice.h index f60d7e74..d57d6d9e 100644 --- a/engine/include/XCEngine/RHI/RHIDevice.h +++ b/engine/include/XCEngine/RHI/RHIDevice.h @@ -77,6 +77,10 @@ public: virtual RHICommandQueue* CreateCommandQueue(const CommandQueueDesc& desc) = 0; virtual RHIShader* CreateShader(const ShaderCompileDesc& desc) = 0; virtual RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc& desc) = 0; + virtual RHIPipelineState* CreateComputePipelineState(const ComputePipelineDesc& desc) { + (void)desc; + return nullptr; + } virtual RHIPipelineLayout* CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) = 0; virtual RHIFence* CreateFence(const FenceDesc& desc) = 0; virtual RHISampler* CreateSampler(const SamplerDesc& desc) = 0; diff --git a/engine/include/XCEngine/RHI/RHITypes.h b/engine/include/XCEngine/RHI/RHITypes.h index c0844861..51ba7bb0 100644 --- a/engine/include/XCEngine/RHI/RHITypes.h +++ b/engine/include/XCEngine/RHI/RHITypes.h @@ -46,6 +46,12 @@ struct ShaderCompileMacro { }; enum class ShaderLanguage : uint8_t; +enum class ShaderBinaryBackend : uint8_t { + Unknown = 0, + D3D12, + OpenGL, + Vulkan +}; struct ShaderCompileDesc { std::wstring fileName; @@ -55,6 +61,8 @@ struct ShaderCompileDesc { std::wstring entryPoint; std::wstring profile; std::vector macros; + ShaderBinaryBackend compiledBinaryBackend = ShaderBinaryBackend::Unknown; + std::vector compiledBinary; }; struct InputElementDesc { @@ -327,6 +335,11 @@ struct GraphicsPipelineDesc { uint32_t sampleQuality = 0; }; +struct ComputePipelineDesc { + ShaderCompileDesc computeShader; + RHIPipelineLayout* pipelineLayout = nullptr; +}; + struct RHIDeviceDesc { bool enableDebugLayer = false; bool enableGPUValidation = false; diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanDevice.h b/engine/include/XCEngine/RHI/Vulkan/VulkanDevice.h index 92f9028c..6be28b71 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanDevice.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanDevice.h @@ -25,6 +25,7 @@ public: RHICommandQueue* CreateCommandQueue(const CommandQueueDesc& desc) override; RHIShader* CreateShader(const ShaderCompileDesc& desc) override; RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc& desc) override; + RHIPipelineState* CreateComputePipelineState(const ComputePipelineDesc& desc) override; RHIPipelineLayout* CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) override; RHIFence* CreateFence(const FenceDesc& desc) override; RHISampler* CreateSampler(const SamplerDesc& desc) override; diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h index 13a68ae7..b4001086 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h @@ -3,6 +3,8 @@ #include "XCEngine/RHI/RHIPipelineState.h" #include "XCEngine/RHI/Vulkan/VulkanCommon.h" +#include + namespace XCEngine { namespace RHI { @@ -15,6 +17,10 @@ public: ~VulkanPipelineState() override; bool Initialize(VulkanDevice* device, const GraphicsPipelineDesc& desc); + bool InitializeCompute( + VulkanDevice* device, + RHIPipelineLayout* pipelineLayout, + std::unique_ptr shader); void SetInputLayout(const InputLayoutDesc& layout) override; void SetRasterizerState(const RasterizerDesc& state) override; @@ -23,7 +29,9 @@ public: void SetTopology(uint32_t topologyType) override; void SetRenderTargetFormats(uint32_t count, const uint32_t* formats, uint32_t depthFormat) override; void SetSampleCount(uint32_t count) override; + void SetSampleQuality(uint32_t quality) override; void SetComputeShader(RHIShader* shader) override; + void SetOwnedComputeShader(std::unique_ptr shader); const RasterizerDesc& GetRasterizerState() const override { return m_rasterizerDesc; } const BlendDesc& GetBlendState() const override { return m_blendDesc; } @@ -31,7 +39,7 @@ public: const InputLayoutDesc& GetInputLayout() const override { return m_inputLayoutDesc; } PipelineStateHash GetHash() const override; RHIShader* GetComputeShader() const override { return m_computeShader; } - bool HasComputeShader() const override { return m_computeShader != nullptr; } + bool HasComputeShader() const override { return m_hasComputeShader; } bool IsValid() const override { return m_isConfigured; } void EnsureValid() override; @@ -52,6 +60,7 @@ public: private: bool EnsurePipelineLayout(const GraphicsPipelineDesc& desc); + bool EnsurePipelineLayout(RHIPipelineLayout* pipelineLayout); bool CreateGraphicsPipeline(const GraphicsPipelineDesc& desc); bool CreateComputePipeline(); @@ -71,6 +80,8 @@ private: uint32_t m_depthStencilFormat = 0; uint32_t m_sampleCount = 1; RHIShader* m_computeShader = nullptr; + std::unique_ptr m_ownedComputeShader; + bool m_hasComputeShader = false; bool m_isConfigured = false; }; diff --git a/engine/src/RHI/D3D12/D3D12Device.cpp b/engine/src/RHI/D3D12/D3D12Device.cpp index 9bf029b6..760845d8 100644 --- a/engine/src/RHI/D3D12/D3D12Device.cpp +++ b/engine/src/RHI/D3D12/D3D12Device.cpp @@ -48,7 +48,7 @@ uint64_t GetVolumeTraceSteadyMs(); void LogVolumeTraceRendering(const std::string& message); bool HasShaderPayload(const ShaderCompileDesc& desc) { - return !desc.source.empty() || !desc.fileName.empty(); + return !desc.source.empty() || !desc.fileName.empty() || !desc.compiledBinary.empty(); } bool ShouldTraceVolumetricShaderCompile(const ShaderCompileDesc& desc) { @@ -94,6 +94,23 @@ bool CompileD3D12Shader(const ShaderCompileDesc& desc, D3D12Shader& shader) { const char* entryPointPtr = entryPoint.empty() ? nullptr : entryPoint.c_str(); const char* profilePtr = profile.empty() ? nullptr : profile.c_str(); + if (desc.compiledBinaryBackend == ShaderBinaryBackend::D3D12 && + !desc.compiledBinary.empty()) { + const bool compiled = shader.InitializeFromBytecode( + desc.compiledBinary.data(), + desc.compiledBinary.size(), + profilePtr); + if (traceShaderCompile) { + const uint64_t compileEndMs = GetVolumeTraceSteadyMs(); + LogVolumeTraceRendering( + std::string("D3D12 shader compile ") + (compiled ? "cache-hit" : "cache-hit-failed") + + " steady_ms=" + std::to_string(compileEndMs) + + " total_ms=" + std::to_string(compileEndMs - compileStartMs) + " " + + DescribeShaderCompileDesc(desc)); + } + return compiled; + } + if (!desc.source.empty()) { std::vector macroNames; std::vector macroDefinitions; @@ -1228,25 +1245,7 @@ RHITexture* D3D12Device::CreateTexture(const TextureDesc& desc, const void* init RHIShader* D3D12Device::CreateShader(const ShaderCompileDesc& desc) { auto* shader = new D3D12Shader(); - const std::string entryPoint = NarrowAscii(desc.entryPoint); - const std::string profile = NarrowAscii(desc.profile); - const char* entryPointPtr = entryPoint.empty() ? nullptr : entryPoint.c_str(); - const char* profilePtr = profile.empty() ? nullptr : profile.c_str(); - - bool success = false; - if (!desc.source.empty()) { - success = shader->Compile( - desc.source.data(), - desc.source.size(), - desc.fileName.empty() ? nullptr : desc.fileName.c_str(), - nullptr, - entryPointPtr, - profilePtr); - } else if (!desc.fileName.empty()) { - success = shader->CompileFromFile(desc.fileName.c_str(), entryPointPtr, profilePtr); - } - - if (success) { + if (CompileD3D12Shader(desc, *shader)) { return shader; } delete shader; @@ -1524,6 +1523,33 @@ RHIPipelineState* D3D12Device::CreatePipelineState(const GraphicsPipelineDesc& d return pso; } +RHIPipelineState* D3D12Device::CreateComputePipelineState(const ComputePipelineDesc& desc) { + if (!HasShaderPayload(desc.computeShader)) { + return nullptr; + } + + auto* pso = new D3D12PipelineState(m_device.Get()); + if (desc.pipelineLayout != nullptr) { + auto* pipelineLayout = static_cast(desc.pipelineLayout); + pso->SetRootSignature(pipelineLayout->GetRootSignature()); + } + + D3D12Shader computeShader; + if (!CompileD3D12Shader(desc.computeShader, computeShader)) { + delete pso; + return nullptr; + } + + pso->SetComputeShaderBytecodes(computeShader.GetD3D12Bytecode()); + pso->EnsureValid(); + if (!pso->IsValid()) { + delete pso; + return nullptr; + } + + return pso; +} + RHIPipelineLayout* D3D12Device::CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) { auto* pipelineLayout = new D3D12PipelineLayout(); if (!pipelineLayout->InitializeWithDevice(this, desc)) { diff --git a/engine/src/RHI/D3D12/D3D12PipelineState.cpp b/engine/src/RHI/D3D12/D3D12PipelineState.cpp index aafe2605..a8dfa528 100644 --- a/engine/src/RHI/D3D12/D3D12PipelineState.cpp +++ b/engine/src/RHI/D3D12/D3D12PipelineState.cpp @@ -109,6 +109,7 @@ bool D3D12PipelineState::Initialize(ID3D12Device* device, const D3D12_GRAPHICS_P } m_depthStencilFormat = static_cast(desc.DSVFormat); m_sampleCount = desc.SampleDesc.Count; + m_sampleQuality = desc.SampleDesc.Quality; // Set shader bytecodes m_vsBytecode = desc.VS; @@ -173,13 +174,21 @@ void D3D12PipelineState::SetSampleCount(uint32_t count) { m_sampleCount = count; } +void D3D12PipelineState::SetSampleQuality(uint32_t quality) { + m_sampleQuality = quality; +} + PipelineStateHash D3D12PipelineState::GetHash() const { PipelineStateHash hash = {}; hash.blendStateHash = std::hash{}(*reinterpret_cast(&m_blendDesc)); hash.depthStateHash = std::hash{}(*reinterpret_cast(&m_depthStencilDesc)); hash.rasterizerStateHash = std::hash{}(*reinterpret_cast(&m_rasterizerDesc)); hash.topologyHash = m_topologyType; - hash.renderTargetHash = m_renderTargetCount | (m_depthStencilFormat << 8); + hash.renderTargetHash = + m_renderTargetCount | + (static_cast(m_depthStencilFormat) << 8) | + (static_cast(m_sampleCount) << 32) | + (static_cast(m_sampleQuality) << 48); return hash; } @@ -190,6 +199,9 @@ void D3D12PipelineState::SetShaderBytecodes(const D3D12_SHADER_BYTECODE& vs, con } void D3D12PipelineState::SetComputeShader(RHIShader* shader) { + if (shader != m_ownedComputeShader.get()) { + m_ownedComputeShader.reset(); + } m_computeShader = shader; m_csBytecode = {}; m_computePipelineState.Reset(); @@ -204,6 +216,11 @@ void D3D12PipelineState::SetComputeShader(RHIShader* shader) { } } +void D3D12PipelineState::SetOwnedComputeShader(std::unique_ptr shader) { + m_ownedComputeShader = std::move(shader); + SetComputeShader(m_ownedComputeShader.get()); +} + void D3D12PipelineState::SetRootSignature(ID3D12RootSignature* rootSignature) { m_rootSignature = rootSignature; } @@ -275,7 +292,7 @@ bool D3D12PipelineState::CreateD3D12PSO() { } desc.DSVFormat = ToD3D12(static_cast(m_depthStencilFormat)); desc.SampleDesc.Count = m_sampleCount; - desc.SampleDesc.Quality = 0; + desc.SampleDesc.Quality = m_sampleQuality; desc.SampleMask = 0xffffffff; desc.PrimitiveTopologyType = static_cast(m_topologyType); @@ -349,6 +366,7 @@ void D3D12PipelineState::Shutdown() { m_pipelineState.Reset(); m_computePipelineState.Reset(); m_rootSignature.Reset(); + m_ownedComputeShader.reset(); m_vsBytecode = {}; m_psBytecode = {}; m_gsBytecode = {}; diff --git a/engine/src/RHI/OpenGL/OpenGLDevice.cpp b/engine/src/RHI/OpenGL/OpenGLDevice.cpp index 1cf1c1c9..fe152e24 100644 --- a/engine/src/RHI/OpenGL/OpenGLDevice.cpp +++ b/engine/src/RHI/OpenGL/OpenGLDevice.cpp @@ -136,7 +136,7 @@ uint64_t QuerySharedSystemMemoryBytes() { } bool HasShaderPayload(const ShaderCompileDesc& desc) { - return !desc.source.empty() || !desc.fileName.empty(); + return !desc.source.empty() || !desc.fileName.empty() || !desc.compiledBinary.empty(); } std::string SourceToString(const ShaderCompileDesc& desc) { @@ -594,8 +594,20 @@ bool CompileOpenGLShaderHandleFromSpirv(const ShaderCompileDesc& desc, GLuint& outShaderHandle, ShaderType& outShaderType, std::string* errorMessage) { + ShaderCompileDesc spirvDesc = desc; + SpirvTargetEnvironment targetEnvironment = SpirvTargetEnvironment::Vulkan; + if (desc.compiledBinaryBackend == ShaderBinaryBackend::OpenGL && + !desc.compiledBinary.empty()) { + spirvDesc.source = desc.compiledBinary; + spirvDesc.sourceLanguage = ShaderLanguage::SPIRV; + spirvDesc.fileName.clear(); + spirvDesc.compiledBinary.clear(); + spirvDesc.compiledBinaryBackend = ShaderBinaryBackend::Unknown; + targetEnvironment = SpirvTargetEnvironment::OpenGL; + } + CompiledSpirvShader compiledShader = {}; - if (!CompileSpirvShader(desc, SpirvTargetEnvironment::Vulkan, compiledShader, errorMessage)) { + if (!CompileSpirvShader(spirvDesc, targetEnvironment, compiledShader, errorMessage)) { return false; } @@ -1326,7 +1338,7 @@ RHIShader* OpenGLDevice::CreateShader(const ShaderCompileDesc& desc) { return nullptr; } - if (desc.source.empty() && desc.fileName.empty()) { + if (!HasShaderPayload(desc)) { delete shader; return nullptr; } @@ -1378,6 +1390,7 @@ RHIPipelineState* OpenGLDevice::CreatePipelineState(const GraphicsPipelineDesc& pso->SetTopology(desc.topologyType); pso->SetRenderTargetFormats(desc.renderTargetCount, desc.renderTargetFormats, desc.depthStencilFormat); pso->SetSampleCount(desc.sampleCount); + pso->SetSampleQuality(desc.sampleQuality); const bool hasVertexShader = HasShaderPayload(desc.vertexShader); const bool hasFragmentShader = HasShaderPayload(desc.fragmentShader); @@ -1422,6 +1435,21 @@ RHIPipelineState* OpenGLDevice::CreatePipelineState(const GraphicsPipelineDesc& return pso; } +RHIPipelineState* OpenGLDevice::CreateComputePipelineState(const ComputePipelineDesc& desc) { + if (!HasShaderPayload(desc.computeShader) || !MakeContextCurrent()) { + return nullptr; + } + + auto computeShader = std::unique_ptr(static_cast(CreateShader(desc.computeShader))); + if (computeShader == nullptr || computeShader->GetType() != ShaderType::Compute) { + return nullptr; + } + + auto* pso = new OpenGLPipelineState(); + pso->SetOwnedComputeShader(std::move(computeShader)); + return pso; +} + RHIPipelineLayout* OpenGLDevice::CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) { auto* layout = new OpenGLPipelineLayout(); if (!layout->Initialize(desc)) { diff --git a/engine/src/RHI/OpenGL/OpenGLPipelineState.cpp b/engine/src/RHI/OpenGL/OpenGLPipelineState.cpp index 1c409e3f..30ec4b0a 100644 --- a/engine/src/RHI/OpenGL/OpenGLPipelineState.cpp +++ b/engine/src/RHI/OpenGL/OpenGLPipelineState.cpp @@ -72,7 +72,13 @@ void OpenGLPipelineState::SetRenderTargetFormats(uint32_t count, const uint32_t* void OpenGLPipelineState::SetSampleCount(uint32_t count) { } +void OpenGLPipelineState::SetSampleQuality(uint32_t quality) { +} + void OpenGLPipelineState::SetComputeShader(RHIShader* shader) { + if (shader != m_ownedComputeShader.get()) { + m_ownedComputeShader.reset(); + } m_computeShader = shader; if (shader) { OpenGLShader* glShader = static_cast(shader); @@ -82,6 +88,12 @@ void OpenGLPipelineState::SetComputeShader(RHIShader* shader) { } } +void OpenGLPipelineState::SetOwnedComputeShader(std::unique_ptr shader) { + m_ownedComputeShader = std::move(shader); + m_computeShader = m_ownedComputeShader.get(); + m_computeProgram = m_ownedComputeShader != nullptr ? m_ownedComputeShader->GetID() : 0; +} + PipelineStateHash OpenGLPipelineState::GetHash() const { PipelineStateHash hash = {}; return hash; @@ -89,6 +101,7 @@ PipelineStateHash OpenGLPipelineState::GetHash() const { void OpenGLPipelineState::Shutdown() { m_graphicsShader.reset(); + m_ownedComputeShader.reset(); m_program = 0; m_computeProgram = 0; m_computeShader = nullptr; diff --git a/engine/src/RHI/Vulkan/VulkanDevice.cpp b/engine/src/RHI/Vulkan/VulkanDevice.cpp index 62b71121..1e9aa7fa 100644 --- a/engine/src/RHI/Vulkan/VulkanDevice.cpp +++ b/engine/src/RHI/Vulkan/VulkanDevice.cpp @@ -677,6 +677,21 @@ RHIPipelineState* VulkanDevice::CreatePipelineState(const GraphicsPipelineDesc& return nullptr; } +RHIPipelineState* VulkanDevice::CreateComputePipelineState(const ComputePipelineDesc& desc) { + auto computeShader = std::unique_ptr(static_cast(CreateShader(desc.computeShader))); + if (computeShader == nullptr || computeShader->GetType() != ShaderType::Compute) { + return nullptr; + } + + auto* pipelineState = new VulkanPipelineState(); + if (pipelineState->InitializeCompute(this, desc.pipelineLayout, std::move(computeShader))) { + return pipelineState; + } + + delete pipelineState; + return nullptr; +} + RHIPipelineLayout* VulkanDevice::CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) { auto* pipelineLayout = new VulkanPipelineLayout(); if (pipelineLayout->Initialize(m_device, desc)) { diff --git a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp index 66b3c4c5..61cd1a0d 100644 --- a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp +++ b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp @@ -17,7 +17,7 @@ namespace RHI { namespace { bool HasShaderPayload(const ShaderCompileDesc& desc) { - return !desc.source.empty() || !desc.fileName.empty(); + return !desc.source.empty() || !desc.fileName.empty() || !desc.compiledBinary.empty(); } VkShaderModule CreateShaderModule(VkDevice device, const std::vector& words) { @@ -91,6 +91,9 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin m_renderTargetCount = desc.renderTargetCount; m_depthStencilFormat = desc.depthStencilFormat; m_sampleCount = desc.sampleCount > 0 ? desc.sampleCount : 1; + m_computeShader = nullptr; + m_ownedComputeShader.reset(); + m_hasComputeShader = false; for (uint32_t i = 0; i < 8; ++i) { m_renderTargetFormats[i] = desc.renderTargetFormats[i]; } @@ -125,8 +128,41 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin return true; } +bool VulkanPipelineState::InitializeCompute( + VulkanDevice* device, + RHIPipelineLayout* pipelineLayout, + std::unique_ptr shader) { + if (device == nullptr || + device->GetDevice() == VK_NULL_HANDLE || + shader == nullptr || + !shader->IsValid() || + shader->GetType() != ShaderType::Compute) { + return false; + } + + Shutdown(); + + m_deviceOwner = device; + m_device = device->GetDevice(); + m_ownedComputeShader = std::move(shader); + m_computeShader = m_ownedComputeShader.get(); + m_hasComputeShader = m_computeShader != nullptr; + + if (!EnsurePipelineLayout(pipelineLayout) || !CreateComputePipeline()) { + Shutdown(); + return false; + } + + m_isConfigured = true; + return true; +} + bool VulkanPipelineState::EnsurePipelineLayout(const GraphicsPipelineDesc& desc) { - auto* externalPipelineLayout = static_cast(desc.pipelineLayout); + return EnsurePipelineLayout(desc.pipelineLayout); +} + +bool VulkanPipelineState::EnsurePipelineLayout(RHIPipelineLayout* pipelineLayout) { + auto* externalPipelineLayout = static_cast(pipelineLayout); if (externalPipelineLayout != nullptr) { m_pipelineLayout = externalPipelineLayout->GetPipelineLayout(); m_ownsPipelineLayout = false; @@ -425,15 +461,33 @@ void VulkanPipelineState::SetSampleCount(uint32_t count) { m_sampleCount = count > 0 ? count : 1; } +void VulkanPipelineState::SetSampleQuality(uint32_t quality) { + (void)quality; +} + void VulkanPipelineState::SetComputeShader(RHIShader* shader) { if (m_pipeline != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { vkDestroyPipeline(m_device, m_pipeline, nullptr); m_pipeline = VK_NULL_HANDLE; } - m_computeShader = shader; - if (m_computeShader != nullptr && m_pipelineLayout != VK_NULL_HANDLE) { - m_isConfigured = true; + if (shader != m_ownedComputeShader.get()) { + m_ownedComputeShader.reset(); } + m_computeShader = shader; + m_hasComputeShader = m_computeShader != nullptr; + m_isConfigured = m_hasComputeShader && m_pipelineLayout != VK_NULL_HANDLE; +} + +void VulkanPipelineState::SetOwnedComputeShader(std::unique_ptr shader) { + if (m_pipeline != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyPipeline(m_device, m_pipeline, nullptr); + m_pipeline = VK_NULL_HANDLE; + } + + m_ownedComputeShader = std::move(shader); + m_computeShader = m_ownedComputeShader.get(); + m_hasComputeShader = m_computeShader != nullptr; + m_isConfigured = m_hasComputeShader && m_pipelineLayout != VK_NULL_HANDLE; } PipelineStateHash VulkanPipelineState::GetHash() const { @@ -499,7 +553,9 @@ void VulkanPipelineState::Shutdown() { m_deviceOwner = nullptr; m_device = VK_NULL_HANDLE; m_ownsPipelineLayout = false; + m_ownedComputeShader.reset(); m_computeShader = nullptr; + m_hasComputeShader = false; m_isConfigured = false; } diff --git a/tests/RHI/OpenGL/unit/test_backend_specific.cpp b/tests/RHI/OpenGL/unit/test_backend_specific.cpp index 6e2e02e7..3439f45f 100644 --- a/tests/RHI/OpenGL/unit/test_backend_specific.cpp +++ b/tests/RHI/OpenGL/unit/test_backend_specific.cpp @@ -756,10 +756,6 @@ TEST_F(OpenGLTestFixture, CommandList_SetComputeDescriptorSets_UsesSetAwareImage ASSERT_NE(descriptorSet, nullptr); descriptorSet->Update(0, uav); - GraphicsPipelineDesc pipelineDesc = {}; - RHIPipelineState* pipelineState = GetDevice()->CreatePipelineState(pipelineDesc); - ASSERT_NE(pipelineState, nullptr); - ShaderCompileDesc shaderDesc = {}; shaderDesc.sourceLanguage = ShaderLanguage::GLSL; static const char* computeSource = R"( @@ -772,9 +768,11 @@ TEST_F(OpenGLTestFixture, CommandList_SetComputeDescriptorSets_UsesSetAwareImage )"; shaderDesc.source.assign(computeSource, computeSource + std::strlen(computeSource)); shaderDesc.profile = L"cs_4_30"; - RHIShader* computeShader = GetDevice()->CreateShader(shaderDesc); - ASSERT_NE(computeShader, nullptr); - pipelineState->SetComputeShader(computeShader); + ComputePipelineDesc pipelineDesc = {}; + pipelineDesc.pipelineLayout = pipelineLayout; + pipelineDesc.computeShader = shaderDesc; + RHIPipelineState* pipelineState = GetDevice()->CreateComputePipelineState(pipelineDesc); + ASSERT_NE(pipelineState, nullptr); CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); @@ -815,8 +813,6 @@ TEST_F(OpenGLTestFixture, CommandList_SetComputeDescriptorSets_UsesSetAwareImage cmdList->Shutdown(); delete cmdList; - computeShader->Shutdown(); - delete computeShader; pipelineState->Shutdown(); delete pipelineState; descriptorSet->Shutdown(); diff --git a/tests/RHI/Vulkan/unit/test_compute.cpp b/tests/RHI/Vulkan/unit/test_compute.cpp index e06a2786..e95024d0 100644 --- a/tests/RHI/Vulkan/unit/test_compute.cpp +++ b/tests/RHI/Vulkan/unit/test_compute.cpp @@ -8,12 +8,59 @@ #include "XCEngine/RHI/RHIResourceView.h" #include "XCEngine/RHI/Vulkan/VulkanTexture.h" +#include #include using namespace XCEngine::RHI; namespace { +constexpr uint32_t kWriteRedComputeSpirv[] = { + 0x07230203, 0x00010000, 0x0008000B, 0x00000017, 0x00000000, 0x00020011, 0x00000001, 0x0006000B, + 0x00000001, 0x4C534C47, 0x6474732E, 0x3035342E, 0x00000000, 0x0003000E, 0x00000000, 0x00000001, + 0x0005000F, 0x00000005, 0x00000004, 0x6E69616D, 0x00000000, 0x00060010, 0x00000004, 0x00000011, + 0x00000001, 0x00000001, 0x00000001, 0x00030003, 0x00000002, 0x000001C2, 0x00040005, 0x00000004, + 0x6E69616D, 0x00000000, 0x00040005, 0x00000009, 0x616D4975, 0x00006567, 0x00030047, 0x00000009, + 0x00000019, 0x00040047, 0x00000009, 0x00000021, 0x00000000, 0x00040047, 0x00000009, 0x00000022, + 0x00000000, 0x00040047, 0x00000016, 0x0000000B, 0x00000019, 0x00020013, 0x00000002, 0x00030021, + 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00090019, 0x00000007, 0x00000006, + 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000002, 0x00000004, 0x00040020, 0x00000008, + 0x00000000, 0x00000007, 0x0004003B, 0x00000008, 0x00000009, 0x00000000, 0x00040015, 0x0000000B, + 0x00000020, 0x00000001, 0x00040017, 0x0000000C, 0x0000000B, 0x00000002, 0x0004002B, 0x0000000B, + 0x0000000D, 0x00000000, 0x0005002C, 0x0000000C, 0x0000000E, 0x0000000D, 0x0000000D, 0x00040017, + 0x0000000F, 0x00000006, 0x0000000004, 0x0004002B, 0x00000006, 0x00000010, 0x3F800000, 0x0004002B, + 0x00000006, 0x00000011, 0x00000000, 0x0007002C, 0x0000000F, 0x00000012, 0x00000010, 0x00000011, + 0x00000011, 0x00000010, 0x00040015, 0x00000013, 0x00000020, 0x00000000, 0x00040017, 0x00000014, + 0x00000013, 0x00000003, 0x0004002B, 0x00000013, 0x00000015, 0x00000001, 0x0006002C, 0x00000014, + 0x00000016, 0x00000015, 0x00000015, 0x00000015, 0x00050036, 0x00000002, 0x00000004, 0x00000000, + 0x00000003, 0x000200F8, 0x00000005, 0x0004003D, 0x00000007, 0x0000000A, 0x00000009, 0x00040063, + 0x0000000A, 0x0000000E, 0x00000012, 0x000100FD, 0x00010038 +}; + +ShaderCompileDesc MakeWriteRedComputeShaderDesc() { + ShaderCompileDesc shaderDesc = {}; + shaderDesc.sourceLanguage = ShaderLanguage::SPIRV; + shaderDesc.profile = L"cs_6_0"; + shaderDesc.source.resize(sizeof(kWriteRedComputeSpirv)); + std::memcpy(shaderDesc.source.data(), kWriteRedComputeSpirv, sizeof(kWriteRedComputeSpirv)); + return shaderDesc; +} + +ShaderCompileDesc MakeWriteRedComputeShaderFromGlslDesc() { + static const char* computeSource = R"(#version 450 +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; +layout(set = 0, binding = 0, rgba8) uniform writeonly image2D uImage; +void main() { + imageStore(uImage, ivec2(0, 0), vec4(1.0, 0.0, 0.0, 1.0)); +} +)"; + + ShaderCompileDesc shaderDesc = {}; + shaderDesc.sourceLanguage = ShaderLanguage::GLSL; + shaderDesc.source.assign(computeSource, computeSource + std::strlen(computeSource)); + return shaderDesc; +} + TEST_F(VulkanGraphicsFixture, CreateUnorderedAccessViewProducesValidView) { RHITexture* texture = m_device->CreateTexture(CreateColorTextureDesc(4, 4)); ASSERT_NE(texture, nullptr); @@ -70,14 +117,11 @@ TEST_F(VulkanGraphicsFixture, DispatchWritesUavTexture) { ASSERT_NE(descriptorSet, nullptr); descriptorSet->Update(0, uav); - GraphicsPipelineDesc pipelineDesc = {}; + ComputePipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; - RHIPipelineState* pipelineState = m_device->CreatePipelineState(pipelineDesc); + pipelineDesc.computeShader = MakeWriteRedComputeShaderDesc(); + RHIPipelineState* pipelineState = m_device->CreateComputePipelineState(pipelineDesc); ASSERT_NE(pipelineState, nullptr); - - RHIShader* shader = CreateWriteRedComputeShader(); - ASSERT_NE(shader, nullptr); - pipelineState->SetComputeShader(shader); EXPECT_TRUE(pipelineState->HasComputeShader()); EXPECT_EQ(pipelineState->GetType(), PipelineType::Compute); @@ -101,8 +145,6 @@ TEST_F(VulkanGraphicsFixture, DispatchWritesUavTexture) { commandList->Shutdown(); delete commandList; - shader->Shutdown(); - delete shader; pipelineState->Shutdown(); delete pipelineState; descriptorSet->Shutdown(); @@ -154,14 +196,11 @@ TEST_F(VulkanGraphicsFixture, DispatchWritesUavTextureWithGlslComputeShader) { ASSERT_NE(descriptorSet, nullptr); descriptorSet->Update(0, uav); - GraphicsPipelineDesc pipelineDesc = {}; + ComputePipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; - RHIPipelineState* pipelineState = m_device->CreatePipelineState(pipelineDesc); + pipelineDesc.computeShader = MakeWriteRedComputeShaderFromGlslDesc(); + RHIPipelineState* pipelineState = m_device->CreateComputePipelineState(pipelineDesc); ASSERT_NE(pipelineState, nullptr); - - RHIShader* shader = CreateWriteRedComputeShaderFromGlsl(); - ASSERT_NE(shader, nullptr); - pipelineState->SetComputeShader(shader); EXPECT_TRUE(pipelineState->HasComputeShader()); EXPECT_EQ(pipelineState->GetType(), PipelineType::Compute); @@ -185,8 +224,6 @@ TEST_F(VulkanGraphicsFixture, DispatchWritesUavTextureWithGlslComputeShader) { commandList->Shutdown(); delete commandList; - shader->Shutdown(); - delete shader; pipelineState->Shutdown(); delete pipelineState; descriptorSet->Shutdown(); diff --git a/tests/RHI/unit/test_compute.cpp b/tests/RHI/unit/test_compute.cpp index 2604dd05..f7639e03 100644 --- a/tests/RHI/unit/test_compute.cpp +++ b/tests/RHI/unit/test_compute.cpp @@ -165,28 +165,53 @@ TEST_P(RHITestFixture, PipelineState_EnsureValid_Compute) { delete pso; } +TEST_P(RHITestFixture, Device_CreateComputePipelineState_ReturnsValidComputePipeline) { + ComputePipelineDesc desc = {}; + desc.computeShader = MakeComputeShaderDesc(GetBackendType()); + + RHIPipelineState* pso = GetDevice()->CreateComputePipelineState(desc); + ASSERT_NE(pso, nullptr); + EXPECT_TRUE(pso->IsValid()); + EXPECT_TRUE(pso->HasComputeShader()); + EXPECT_EQ(pso->GetType(), PipelineType::Compute); + + pso->Shutdown(); + delete pso; +} + +TEST_P(RHITestFixture, Device_CreateComputePipelineState_UsesExternalPipelineLayout) { + RHIPipelineLayoutDesc pipelineLayoutDesc = {}; + RHIPipelineLayout* pipelineLayout = GetDevice()->CreatePipelineLayout(pipelineLayoutDesc); + ASSERT_NE(pipelineLayout, nullptr); + + ComputePipelineDesc desc = {}; + desc.computeShader = MakeComputeShaderDesc(GetBackendType()); + desc.pipelineLayout = pipelineLayout; + + RHIPipelineState* pso = GetDevice()->CreateComputePipelineState(desc); + ASSERT_NE(pso, nullptr); + EXPECT_TRUE(pso->IsValid()); + EXPECT_TRUE(pso->HasComputeShader()); + EXPECT_EQ(pso->GetType(), PipelineType::Compute); + + pso->Shutdown(); + delete pso; + pipelineLayout->Shutdown(); + delete pipelineLayout; +} + TEST_P(RHITestFixture, CommandList_Dispatch_Basic) { RHICommandList* cmdList = GetDevice()->CreateCommandList({}); ASSERT_NE(cmdList, nullptr); cmdList->Reset(); - GraphicsPipelineDesc desc = {}; - RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc); - + ComputePipelineDesc desc = {}; + desc.computeShader = MakeComputeShaderDesc(GetBackendType()); + RHIPipelineState* pso = GetDevice()->CreateComputePipelineState(desc); if (pso != nullptr) { - ShaderCompileDesc shaderDesc = MakeComputeShaderDesc(GetBackendType()); - - RHIShader* computeShader = GetDevice()->CreateShader(shaderDesc); - if (computeShader != nullptr) { - pso->SetComputeShader(computeShader); - cmdList->SetPipelineState(pso); - cmdList->Dispatch(1, 1, 1); - - computeShader->Shutdown(); - delete computeShader; - } - + cmdList->SetPipelineState(pso); + cmdList->Dispatch(1, 1, 1); pso->Shutdown(); delete pso; }