From 53ac1dbc441a903a1259b6556be6eba058f200fb Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 27 Mar 2026 18:55:38 +0800 Subject: [PATCH] Add Vulkan shader, UAV, and compute coverage --- engine/CMakeLists.txt | 2 + .../XCEngine/RHI/Vulkan/VulkanCommon.h | 2 +- .../XCEngine/RHI/Vulkan/VulkanPipelineState.h | 14 +- .../XCEngine/RHI/Vulkan/VulkanResourceView.h | 1 + .../XCEngine/RHI/Vulkan/VulkanShader.h | 47 +++++ engine/src/RHI/Vulkan/VulkanCommandList.cpp | 19 +- engine/src/RHI/Vulkan/VulkanDescriptorSet.cpp | 21 +- engine/src/RHI/Vulkan/VulkanDevice.cpp | 72 ++++++- engine/src/RHI/Vulkan/VulkanPipelineState.cpp | 107 +++++++--- engine/src/RHI/Vulkan/VulkanResourceView.cpp | 26 +++ engine/src/RHI/Vulkan/VulkanShader.cpp | 190 ++++++++++++++++++ tests/RHI/unit/test_vulkan_graphics.cpp | 150 ++++++++++++++ 12 files changed, 611 insertions(+), 40 deletions(-) create mode 100644 engine/include/XCEngine/RHI/Vulkan/VulkanShader.h create mode 100644 engine/src/RHI/Vulkan/VulkanShader.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 2c672694..c1d7f977 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -156,6 +156,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanBuffer.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanTexture.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanSampler.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanShader.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanDescriptorPool.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanDescriptorSet.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanPipelineLayout.h @@ -172,6 +173,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanBuffer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanTexture.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanSampler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanDescriptorPool.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanDescriptorSet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanPipelineLayout.cpp diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h index cfa6c0c5..3b443ba7 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h @@ -275,7 +275,7 @@ inline VkShaderStageFlags ToVulkanShaderStageFlags(uint32_t visibility) { case ShaderVisibility::Domain: case ShaderVisibility::All: default: - return VK_SHADER_STAGE_ALL_GRAPHICS; + return VK_SHADER_STAGE_ALL; } } diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h index 9970daa3..20453b3d 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h @@ -7,6 +7,7 @@ namespace XCEngine { namespace RHI { class VulkanDevice; +class VulkanShader; class VulkanPipelineState : public RHIPipelineState { public: @@ -29,17 +30,17 @@ public: const DepthStencilStateDesc& GetDepthStencilState() const override { return m_depthStencilDesc; } const InputLayoutDesc& GetInputLayout() const override { return m_inputLayoutDesc; } PipelineStateHash GetHash() const override; - RHIShader* GetComputeShader() const override { return nullptr; } - bool HasComputeShader() const override { return false; } + RHIShader* GetComputeShader() const override { return m_computeShader; } + bool HasComputeShader() const override { return m_computeShader != nullptr; } bool IsValid() const override { return m_pipeline != VK_NULL_HANDLE; } - void EnsureValid() override {} + void EnsureValid() override; void Shutdown() override; void Bind() override {} void Unbind() override {} void* GetNativeHandle() override { return m_pipeline; } - PipelineType GetType() const override { return PipelineType::Graphics; } + PipelineType GetType() const override { return HasComputeShader() ? PipelineType::Compute : PipelineType::Graphics; } VkPipeline GetPipeline() const { return m_pipeline; } VkPipelineLayout GetPipelineLayout() const { return m_pipelineLayout; } @@ -49,6 +50,10 @@ public: } private: + bool EnsurePipelineLayout(const GraphicsPipelineDesc& desc); + bool CreateGraphicsPipeline(const GraphicsPipelineDesc& desc); + bool CreateComputePipeline(); + VulkanDevice* m_deviceOwner = nullptr; VkDevice m_device = VK_NULL_HANDLE; VkPipeline m_pipeline = VK_NULL_HANDLE; @@ -64,6 +69,7 @@ private: uint32_t m_renderTargetFormats[8] = { 0 }; uint32_t m_depthStencilFormat = 0; uint32_t m_sampleCount = 1; + RHIShader* m_computeShader = nullptr; }; } // namespace RHI diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h b/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h index c1146a4a..137234c2 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h @@ -17,6 +17,7 @@ public: bool InitializeAsRenderTarget(VkDevice device, VulkanTexture* texture, const ResourceViewDesc& desc); bool InitializeAsDepthStencil(VkDevice device, VulkanTexture* texture, const ResourceViewDesc& desc); bool InitializeAsShaderResource(VkDevice device, VulkanTexture* texture, const ResourceViewDesc& desc); + bool InitializeAsUnorderedAccess(VkDevice device, VulkanTexture* texture, const ResourceViewDesc& desc); bool InitializeAsVertexBuffer(VulkanBuffer* buffer, const ResourceViewDesc& desc); bool InitializeAsIndexBuffer(VulkanBuffer* buffer, const ResourceViewDesc& desc); diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanShader.h b/engine/include/XCEngine/RHI/Vulkan/VulkanShader.h new file mode 100644 index 00000000..dc66b790 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanShader.h @@ -0,0 +1,47 @@ +#pragma once + +#include "XCEngine/RHI/RHIShader.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +#include +#include + +namespace XCEngine { +namespace RHI { + +class VulkanShader : public RHIShader { +public: + explicit VulkanShader(VkDevice device = VK_NULL_HANDLE) + : m_device(device) {} + ~VulkanShader() override; + + bool CompileFromFile(const wchar_t* filePath, const char* entryPoint, const char* target) override; + bool Compile(const void* sourceData, size_t sourceSize, const char* entryPoint, const char* target) override; + void Shutdown() override; + + ShaderType GetType() const override { return m_type; } + bool IsValid() const override { return m_shaderModule != VK_NULL_HANDLE; } + void* GetNativeHandle() override { return m_shaderModule; } + + const std::vector& GetUniformInfos() const override { return m_uniformInfos; } + const UniformInfo* GetUniformInfo(const char* name) const override; + + void SetDevice(VkDevice device) { m_device = device; } + + VkShaderModule GetShaderModule() const { return m_shaderModule; } + const char* GetEntryPoint() const { return m_entryPoint.empty() ? "main" : m_entryPoint.c_str(); } + +private: + bool InitializeFromSpirvWords(const uint32_t* words, size_t wordCount, const char* entryPointHint, const char* targetHint); + static bool ResolveShaderTypeFromTarget(const char* target, ShaderType& type); + static bool ResolveShaderTypeFromSpirv(const uint32_t* words, size_t wordCount, ShaderType& type, std::string& entryPoint); + + VkDevice m_device = VK_NULL_HANDLE; + VkShaderModule m_shaderModule = VK_NULL_HANDLE; + ShaderType m_type = ShaderType::Vertex; + std::string m_entryPoint; + std::vector m_uniformInfos; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanCommandList.cpp b/engine/src/RHI/Vulkan/VulkanCommandList.cpp index 84942138..d3433f81 100644 --- a/engine/src/RHI/Vulkan/VulkanCommandList.cpp +++ b/engine/src/RHI/Vulkan/VulkanCommandList.cpp @@ -762,9 +762,22 @@ void VulkanCommandList::CopyResource(RHIResourceView* dst, RHIResourceView* src) } void VulkanCommandList::Dispatch(uint32_t x, uint32_t y, uint32_t z) { - (void)x; - (void)y; - (void)z; + EndActiveRenderPass(); + + if (m_commandBuffer == VK_NULL_HANDLE || m_currentPipelineState == nullptr || !m_currentPipelineState->HasComputeShader()) { + return; + } + + m_currentPipelineState->EnsureValid(); + if (!m_currentPipelineState->IsValid()) { + return; + } + + vkCmdBindPipeline( + m_commandBuffer, + VK_PIPELINE_BIND_POINT_COMPUTE, + m_currentPipelineState->GetPipeline()); + vkCmdDispatch(m_commandBuffer, x, y, z); } bool VulkanCommandList::EnsureGraphicsRenderPass() { diff --git a/engine/src/RHI/Vulkan/VulkanDescriptorSet.cpp b/engine/src/RHI/Vulkan/VulkanDescriptorSet.cpp index 45a113f9..b64536fc 100644 --- a/engine/src/RHI/Vulkan/VulkanDescriptorSet.cpp +++ b/engine/src/RHI/Vulkan/VulkanDescriptorSet.cpp @@ -75,8 +75,7 @@ void VulkanDescriptorSet::Update(uint32_t offset, RHIResourceView* view) { } const DescriptorSetLayoutBinding* binding = FindBinding(offset); - if (binding == nullptr || - static_cast(binding->type) != DescriptorType::SRV) { + if (binding == nullptr) { return; } @@ -86,7 +85,6 @@ void VulkanDescriptorSet::Update(uint32_t offset, RHIResourceView* view) { } VkDescriptorImageInfo imageInfo = {}; - imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; imageInfo.imageView = vulkanView->GetImageView(); VkWriteDescriptorSet write = {}; @@ -94,8 +92,21 @@ void VulkanDescriptorSet::Update(uint32_t offset, RHIResourceView* view) { write.dstSet = m_descriptorSet; write.dstBinding = offset; write.descriptorCount = 1; - write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; - write.pImageInfo = &imageInfo; + + switch (static_cast(binding->type)) { + case DescriptorType::SRV: + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + write.pImageInfo = &imageInfo; + break; + case DescriptorType::UAV: + imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + write.pImageInfo = &imageInfo; + break; + default: + return; + } vkUpdateDescriptorSets(m_device, 1, &write, 0, nullptr); } diff --git a/engine/src/RHI/Vulkan/VulkanDevice.cpp b/engine/src/RHI/Vulkan/VulkanDevice.cpp index a78f0359..2d89bb6b 100644 --- a/engine/src/RHI/Vulkan/VulkanDevice.cpp +++ b/engine/src/RHI/Vulkan/VulkanDevice.cpp @@ -12,10 +12,12 @@ #include "XCEngine/RHI/Vulkan/VulkanRenderPass.h" #include "XCEngine/RHI/Vulkan/VulkanResourceView.h" #include "XCEngine/RHI/Vulkan/VulkanSampler.h" +#include "XCEngine/RHI/Vulkan/VulkanShader.h" #include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" #include "XCEngine/RHI/Vulkan/VulkanTexture.h" #include +#include #include #include #include @@ -25,6 +27,15 @@ namespace RHI { namespace { +std::string NarrowAscii(const std::wstring& value) { + std::string result; + result.reserve(value.size()); + for (wchar_t ch : value) { + result.push_back(static_cast(ch)); + } + return result; +} + std::wstring ResolveVendorName(uint32_t vendorId) { switch (vendorId) { case 0x10DE: return L"NVIDIA"; @@ -62,10 +73,18 @@ VkImageLayout ToImageLayout(ResourceStates state) { case ResourceStates::Present: return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; case ResourceStates::PixelShaderResource: + case ResourceStates::NonPixelShaderResource: + case ResourceStates::GenericRead: return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; case ResourceStates::DepthWrite: return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + case ResourceStates::DepthRead: + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + case ResourceStates::UnorderedAccess: + return VK_IMAGE_LAYOUT_GENERAL; case ResourceStates::Common: + case ResourceStates::VertexAndConstantBuffer: + case ResourceStates::IndexBuffer: default: return VK_IMAGE_LAYOUT_UNDEFINED; } @@ -75,14 +94,21 @@ VkAccessFlags ToAccessMask(ResourceStates state) { switch (state) { case ResourceStates::RenderTarget: return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + case ResourceStates::UnorderedAccess: + return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; case ResourceStates::CopySrc: return VK_ACCESS_TRANSFER_READ_BIT; case ResourceStates::CopyDst: return VK_ACCESS_TRANSFER_WRITE_BIT; case ResourceStates::PixelShaderResource: + case ResourceStates::NonPixelShaderResource: return VK_ACCESS_SHADER_READ_BIT; case ResourceStates::DepthWrite: return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + case ResourceStates::DepthRead: + return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + case ResourceStates::GenericRead: + return VK_ACCESS_MEMORY_READ_BIT; case ResourceStates::Present: case ResourceStates::Common: default: @@ -94,13 +120,20 @@ VkPipelineStageFlags ToStageMask(ResourceStates state) { switch (state) { case ResourceStates::RenderTarget: return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + case ResourceStates::UnorderedAccess: + return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; case ResourceStates::CopySrc: case ResourceStates::CopyDst: return VK_PIPELINE_STAGE_TRANSFER_BIT; case ResourceStates::PixelShaderResource: return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + case ResourceStates::NonPixelShaderResource: + return VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; case ResourceStates::DepthWrite: + case ResourceStates::DepthRead: return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + case ResourceStates::GenericRead: + return VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; case ResourceStates::Present: return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; case ResourceStates::Common: @@ -115,7 +148,7 @@ VkImageUsageFlags ResolveTextureUsageFlags(const TextureDesc& desc) { if ((GetImageAspectMask(format) & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) { usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; } else { - usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT; } return usage; } @@ -611,7 +644,34 @@ RHICommandQueue* VulkanDevice::CreateCommandQueue(const CommandQueueDesc& desc) } RHIShader* VulkanDevice::CreateShader(const ShaderCompileDesc& desc) { - (void)desc; + auto* shader = new VulkanShader(m_device); + bool success = false; + + const std::string entryPoint = NarrowAscii(desc.entryPoint); + const std::string profile = NarrowAscii(desc.profile); + + const bool isSpirvSource = desc.sourceLanguage == ShaderLanguage::SPIRV; + const bool isSpirvFile = !desc.fileName.empty() && + std::filesystem::path(desc.fileName).extension() == L".spv"; + + if (!desc.source.empty() && isSpirvSource) { + success = shader->Compile( + desc.source.data(), + desc.source.size(), + entryPoint.empty() ? nullptr : entryPoint.c_str(), + profile.empty() ? nullptr : profile.c_str()); + } else if (!desc.fileName.empty() && (isSpirvSource || isSpirvFile)) { + success = shader->CompileFromFile( + desc.fileName.c_str(), + entryPoint.empty() ? nullptr : entryPoint.c_str(), + profile.empty() ? nullptr : profile.c_str()); + } + + if (success) { + return shader; + } + + delete shader; return nullptr; } @@ -734,8 +794,12 @@ RHIResourceView* VulkanDevice::CreateShaderResourceView(RHITexture* texture, con } RHIResourceView* VulkanDevice::CreateUnorderedAccessView(RHITexture* texture, const ResourceViewDesc& desc) { - (void)texture; - (void)desc; + auto* view = new VulkanResourceView(); + if (view->InitializeAsUnorderedAccess(m_device, static_cast(texture), desc)) { + return view; + } + + delete view; return nullptr; } diff --git a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp index f4e50dd4..874085d9 100644 --- a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp +++ b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp @@ -2,6 +2,7 @@ #include "XCEngine/RHI/Vulkan/VulkanDevice.h" #include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h" +#include "XCEngine/RHI/Vulkan/VulkanShader.h" #include #include @@ -66,6 +67,10 @@ bool LoadSpirvBytes(const ShaderCompileDesc& desc, std::vector& words, return !words.empty(); } +bool HasShaderPayload(const ShaderCompileDesc& desc) { + return !desc.source.empty() || !desc.fileName.empty(); +} + VkShaderModule CreateShaderModule(VkDevice device, const std::vector& words) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; @@ -91,6 +96,8 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin return false; } + Shutdown(); + m_deviceOwner = device; m_device = device->GetDevice(); m_inputLayoutDesc = desc.inputLayout; @@ -105,10 +112,48 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin m_renderTargetFormats[i] = desc.renderTargetFormats[i]; } - if (m_renderTargetCount != 1 || m_renderTargetFormats[0] == 0) { + if (!EnsurePipelineLayout(desc)) { return false; } + const bool hasVertexShader = HasShaderPayload(desc.vertexShader); + const bool hasFragmentShader = HasShaderPayload(desc.fragmentShader); + if (!hasVertexShader && !hasFragmentShader) { + return true; + } + + if (m_renderTargetCount != 1 || m_renderTargetFormats[0] == 0 || !hasVertexShader || !hasFragmentShader) { + Shutdown(); + return false; + } + + if (!CreateGraphicsPipeline(desc)) { + Shutdown(); + return false; + } + + return true; +} + +bool VulkanPipelineState::EnsurePipelineLayout(const GraphicsPipelineDesc& desc) { + auto* externalPipelineLayout = static_cast(desc.pipelineLayout); + if (externalPipelineLayout != nullptr) { + m_pipelineLayout = externalPipelineLayout->GetPipelineLayout(); + m_ownsPipelineLayout = false; + return m_pipelineLayout != VK_NULL_HANDLE; + } + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + if (vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) { + return false; + } + + m_ownsPipelineLayout = true; + return true; +} + +bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& desc) { std::vector vertexWords; std::vector fragmentWords; std::string vertexEntryPoint; @@ -130,21 +175,6 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin return false; } - auto* externalPipelineLayout = static_cast(desc.pipelineLayout); - if (externalPipelineLayout != nullptr) { - m_pipelineLayout = externalPipelineLayout->GetPipelineLayout(); - m_ownsPipelineLayout = false; - } else { - VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; - pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - if (vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) { - vkDestroyShaderModule(m_device, fragmentModule, nullptr); - vkDestroyShaderModule(m_device, vertexModule, nullptr); - return false; - } - m_ownsPipelineLayout = true; - } - std::vector attachments; attachments.reserve(2); @@ -197,11 +227,6 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin renderPassInfo.pSubpasses = &subpass; if (vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) { - if (m_ownsPipelineLayout) { - vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); - } - m_pipelineLayout = VK_NULL_HANDLE; - m_ownsPipelineLayout = false; vkDestroyShaderModule(m_device, fragmentModule, nullptr); vkDestroyShaderModule(m_device, vertexModule, nullptr); return false; @@ -346,7 +371,6 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin vkDestroyShaderModule(m_device, vertexModule, nullptr); if (!success) { - Shutdown(); return false; } @@ -386,7 +410,11 @@ void VulkanPipelineState::SetSampleCount(uint32_t count) { } void VulkanPipelineState::SetComputeShader(RHIShader* shader) { - (void)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; } PipelineStateHash VulkanPipelineState::GetHash() const { @@ -396,6 +424,38 @@ PipelineStateHash VulkanPipelineState::GetHash() const { return hash; } +void VulkanPipelineState::EnsureValid() { + if (m_pipeline != VK_NULL_HANDLE || !HasComputeShader()) { + return; + } + + CreateComputePipeline(); +} + +bool VulkanPipelineState::CreateComputePipeline() { + if (m_device == VK_NULL_HANDLE || m_pipelineLayout == VK_NULL_HANDLE || m_computeShader == nullptr) { + return false; + } + + auto* vulkanShader = static_cast(m_computeShader); + if (vulkanShader->GetShaderModule() == VK_NULL_HANDLE) { + return false; + } + + VkPipelineShaderStageCreateInfo shaderStage = {}; + shaderStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + shaderStage.module = vulkanShader->GetShaderModule(); + shaderStage.pName = vulkanShader->GetEntryPoint(); + + VkComputePipelineCreateInfo pipelineInfo = {}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + pipelineInfo.stage = shaderStage; + pipelineInfo.layout = m_pipelineLayout; + + return vkCreateComputePipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_pipeline) == VK_SUCCESS; +} + void VulkanPipelineState::Shutdown() { if (m_pipeline != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { vkDestroyPipeline(m_device, m_pipeline, nullptr); @@ -417,6 +477,7 @@ void VulkanPipelineState::Shutdown() { m_deviceOwner = nullptr; m_device = VK_NULL_HANDLE; m_ownsPipelineLayout = false; + m_computeShader = nullptr; } } // namespace RHI diff --git a/engine/src/RHI/Vulkan/VulkanResourceView.cpp b/engine/src/RHI/Vulkan/VulkanResourceView.cpp index 07b196bc..8f085392 100644 --- a/engine/src/RHI/Vulkan/VulkanResourceView.cpp +++ b/engine/src/RHI/Vulkan/VulkanResourceView.cpp @@ -85,6 +85,31 @@ bool VulkanResourceView::InitializeAsShaderResource(VkDevice device, VulkanTextu return vkCreateImageView(device, &viewInfo, nullptr, &m_imageView) == VK_SUCCESS; } +bool VulkanResourceView::InitializeAsUnorderedAccess(VkDevice device, VulkanTexture* texture, const ResourceViewDesc& desc) { + if (device == VK_NULL_HANDLE || texture == nullptr || texture->GetImage() == VK_NULL_HANDLE) { + return false; + } + + m_device = device; + m_texture = texture; + m_viewType = ResourceViewType::UnorderedAccess; + m_dimension = desc.dimension != ResourceViewDimension::Unknown ? desc.dimension : ResourceViewDimension::Texture2D; + m_format = desc.format != 0 ? static_cast(desc.format) : texture->GetFormat(); + + VkImageViewCreateInfo viewInfo = {}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = texture->GetImage(); + viewInfo.viewType = ToVulkanImageViewType(m_dimension, texture->GetTextureType()); + viewInfo.format = m_format != Format::Unknown ? ToVulkanFormat(m_format) : texture->GetVkFormat(); + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = desc.mipLevel; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = desc.firstArraySlice; + viewInfo.subresourceRange.layerCount = 1; + + return vkCreateImageView(device, &viewInfo, nullptr, &m_imageView) == VK_SUCCESS; +} + bool VulkanResourceView::InitializeAsVertexBuffer(VulkanBuffer* buffer, const ResourceViewDesc& desc) { if (buffer == nullptr || buffer->GetBuffer() == VK_NULL_HANDLE) { return false; @@ -131,6 +156,7 @@ bool VulkanResourceView::IsValid() const { case ResourceViewType::RenderTarget: case ResourceViewType::DepthStencil: case ResourceViewType::ShaderResource: + case ResourceViewType::UnorderedAccess: return m_imageView != VK_NULL_HANDLE; default: return false; diff --git a/engine/src/RHI/Vulkan/VulkanShader.cpp b/engine/src/RHI/Vulkan/VulkanShader.cpp new file mode 100644 index 00000000..6047990c --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanShader.cpp @@ -0,0 +1,190 @@ +#include "XCEngine/RHI/Vulkan/VulkanShader.h" + +#include +#include +#include +#include + +namespace XCEngine { +namespace RHI { + +namespace { + +constexpr uint16_t kSpirvOpEntryPoint = 15; + +ShaderType ToShaderType(uint32_t executionModel) { + switch (executionModel) { + case 0: + return ShaderType::Vertex; + case 1: + return ShaderType::TessControl; + case 2: + return ShaderType::TessEvaluation; + case 3: + return ShaderType::Geometry; + case 4: + return ShaderType::Fragment; + case 5: + return ShaderType::Compute; + default: + return ShaderType::Vertex; + } +} + +} // namespace + +VulkanShader::~VulkanShader() { + Shutdown(); +} + +bool VulkanShader::CompileFromFile(const wchar_t* filePath, const char* entryPoint, const char* target) { + if (filePath == nullptr) { + return false; + } + + std::ifstream file(filePath, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + return false; + } + + const std::streamsize fileSize = file.tellg(); + if (fileSize <= 0 || (fileSize % static_cast(sizeof(uint32_t))) != 0) { + return false; + } + + std::vector bytes(static_cast(fileSize)); + file.seekg(0, std::ios::beg); + if (!file.read(reinterpret_cast(bytes.data()), fileSize)) { + return false; + } + + return Compile(bytes.data(), bytes.size(), entryPoint, target); +} + +bool VulkanShader::Compile(const void* sourceData, size_t sourceSize, const char* entryPoint, const char* target) { + if (sourceData == nullptr || sourceSize == 0 || (sourceSize % sizeof(uint32_t)) != 0) { + return false; + } + + return InitializeFromSpirvWords( + static_cast(sourceData), + sourceSize / sizeof(uint32_t), + entryPoint, + target); +} + +void VulkanShader::Shutdown() { + if (m_shaderModule != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyShaderModule(m_device, m_shaderModule, nullptr); + } + + m_shaderModule = VK_NULL_HANDLE; + m_type = ShaderType::Vertex; + m_entryPoint.clear(); + m_uniformInfos.clear(); +} + +const RHIShader::UniformInfo* VulkanShader::GetUniformInfo(const char* name) const { + if (name == nullptr) { + return nullptr; + } + + for (const UniformInfo& uniformInfo : m_uniformInfos) { + if (uniformInfo.name == name) { + return &uniformInfo; + } + } + + return nullptr; +} + +bool VulkanShader::InitializeFromSpirvWords(const uint32_t* words, size_t wordCount, const char* entryPointHint, const char* targetHint) { + if (m_device == VK_NULL_HANDLE || words == nullptr || wordCount < 5 || words[0] != 0x07230203u) { + return false; + } + + ShaderType shaderType = ShaderType::Vertex; + std::string moduleEntryPoint; + if (!ResolveShaderTypeFromSpirv(words, wordCount, shaderType, moduleEntryPoint) && + !ResolveShaderTypeFromTarget(targetHint, shaderType)) { + return false; + } + + VkShaderModuleCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = wordCount * sizeof(uint32_t); + createInfo.pCode = words; + + VkShaderModule shaderModule = VK_NULL_HANDLE; + if (vkCreateShaderModule(m_device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + return false; + } + + Shutdown(); + + m_shaderModule = shaderModule; + m_type = shaderType; + if (entryPointHint != nullptr && entryPointHint[0] != '\0') { + m_entryPoint = entryPointHint; + } else if (!moduleEntryPoint.empty()) { + m_entryPoint = moduleEntryPoint; + } else { + m_entryPoint = "main"; + } + + return true; +} + +bool VulkanShader::ResolveShaderTypeFromTarget(const char* target, ShaderType& type) { + if (target == nullptr) { + return false; + } + + if (std::strstr(target, "vs_") != nullptr || std::strcmp(target, "vs") == 0) { + type = ShaderType::Vertex; + return true; + } + if (std::strstr(target, "ps_") != nullptr || std::strstr(target, "fs_") != nullptr || std::strcmp(target, "ps") == 0) { + type = ShaderType::Fragment; + return true; + } + if (std::strstr(target, "gs_") != nullptr || std::strcmp(target, "gs") == 0) { + type = ShaderType::Geometry; + return true; + } + if (std::strstr(target, "cs_") != nullptr || std::strcmp(target, "cs") == 0) { + type = ShaderType::Compute; + return true; + } + return false; +} + +bool VulkanShader::ResolveShaderTypeFromSpirv(const uint32_t* words, size_t wordCount, ShaderType& type, std::string& entryPoint) { + if (words == nullptr || wordCount < 5) { + return false; + } + + size_t index = 5; + while (index < wordCount) { + const uint32_t instruction = words[index]; + const uint16_t wordCountInInstruction = static_cast(instruction >> 16); + const uint16_t opcode = static_cast(instruction & 0xFFFFu); + if (wordCountInInstruction == 0 || index + wordCountInInstruction > wordCount) { + return false; + } + + if (opcode == kSpirvOpEntryPoint && wordCountInInstruction >= 4) { + type = ToShaderType(words[index + 1]); + const char* nameBytes = reinterpret_cast(&words[index + 3]); + entryPoint = nameBytes; + return true; + } + + index += wordCountInInstruction; + } + + return false; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/tests/RHI/unit/test_vulkan_graphics.cpp b/tests/RHI/unit/test_vulkan_graphics.cpp index b7255220..4db566f7 100644 --- a/tests/RHI/unit/test_vulkan_graphics.cpp +++ b/tests/RHI/unit/test_vulkan_graphics.cpp @@ -4,15 +4,21 @@ #include #include +#include #include #include "XCEngine/RHI/RHICommandList.h" #include "XCEngine/RHI/RHICommandQueue.h" +#include "XCEngine/RHI/RHIDescriptorPool.h" +#include "XCEngine/RHI/RHIDescriptorSet.h" #include "XCEngine/RHI/RHIDevice.h" #include "XCEngine/RHI/RHIFramebuffer.h" #include "XCEngine/RHI/RHIFactory.h" +#include "XCEngine/RHI/RHIPipelineLayout.h" +#include "XCEngine/RHI/RHIPipelineState.h" #include "XCEngine/RHI/RHIRenderPass.h" #include "XCEngine/RHI/RHIResourceView.h" +#include "XCEngine/RHI/RHIShader.h" #include "XCEngine/RHI/RHITexture.h" #include "XCEngine/RHI/Vulkan/VulkanCommon.h" #include "XCEngine/RHI/Vulkan/VulkanDevice.h" @@ -106,6 +112,28 @@ VkPipelineStageFlags ToStageMask(ResourceStates state) { } } +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, 0x00000004, 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 +}; + class VulkanGraphicsFixture : public ::testing::Test { protected: void SetUp() override { @@ -169,6 +197,15 @@ protected: return m_device->CreateCommandList(desc); } + RHIShader* CreateWriteRedComputeShader() const { + 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 m_device->CreateShader(shaderDesc); + } + void SubmitAndWait(RHICommandList* commandList) { ASSERT_NE(commandList, nullptr); commandList->Close(); @@ -417,6 +454,119 @@ TEST_F(VulkanGraphicsFixture, CopyResourceCopiesTexturePixels) { delete sourceTexture; } +TEST_F(VulkanGraphicsFixture, CreateShaderFromSpirvProducesValidComputeShader) { + RHIShader* shader = CreateWriteRedComputeShader(); + ASSERT_NE(shader, nullptr); + EXPECT_TRUE(shader->IsValid()); + EXPECT_EQ(shader->GetType(), ShaderType::Compute); + EXPECT_NE(shader->GetNativeHandle(), nullptr); + shader->Shutdown(); + delete shader; +} + +TEST_F(VulkanGraphicsFixture, CreateUnorderedAccessViewProducesValidView) { + RHITexture* texture = m_device->CreateTexture(CreateColorTextureDesc(4, 4)); + ASSERT_NE(texture, nullptr); + + ResourceViewDesc viewDesc = {}; + viewDesc.format = static_cast(Format::R8G8B8A8_UNorm); + viewDesc.dimension = ResourceViewDimension::Texture2D; + + RHIResourceView* uav = m_device->CreateUnorderedAccessView(texture, viewDesc); + ASSERT_NE(uav, nullptr); + EXPECT_TRUE(uav->IsValid()); + EXPECT_EQ(uav->GetViewType(), ResourceViewType::UnorderedAccess); + + uav->Shutdown(); + delete uav; + texture->Shutdown(); + delete texture; +} + +TEST_F(VulkanGraphicsFixture, DispatchWritesUavTexture) { + RHITexture* texture = m_device->CreateTexture(CreateColorTextureDesc(4, 4)); + ASSERT_NE(texture, nullptr); + + ResourceViewDesc uavDesc = {}; + uavDesc.format = static_cast(Format::R8G8B8A8_UNorm); + uavDesc.dimension = ResourceViewDimension::Texture2D; + RHIResourceView* uav = m_device->CreateUnorderedAccessView(texture, uavDesc); + ASSERT_NE(uav, nullptr); + + DescriptorPoolDesc poolDesc = {}; + poolDesc.type = DescriptorHeapType::CBV_SRV_UAV; + poolDesc.descriptorCount = 1; + poolDesc.shaderVisible = true; + RHIDescriptorPool* pool = m_device->CreateDescriptorPool(poolDesc); + ASSERT_NE(pool, nullptr); + + DescriptorSetLayoutBinding uavBinding = {}; + uavBinding.binding = 0; + uavBinding.type = static_cast(DescriptorType::UAV); + uavBinding.count = 1; + uavBinding.visibility = static_cast(ShaderVisibility::All); + + DescriptorSetLayoutDesc setLayout = {}; + setLayout.bindings = &uavBinding; + setLayout.bindingCount = 1; + + RHIPipelineLayoutDesc pipelineLayoutDesc = {}; + pipelineLayoutDesc.setLayouts = &setLayout; + pipelineLayoutDesc.setLayoutCount = 1; + RHIPipelineLayout* pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc); + ASSERT_NE(pipelineLayout, nullptr); + + RHIDescriptorSet* descriptorSet = pool->AllocateSet(setLayout); + ASSERT_NE(descriptorSet, nullptr); + descriptorSet->Update(0, uav); + + GraphicsPipelineDesc pipelineDesc = {}; + pipelineDesc.pipelineLayout = pipelineLayout; + RHIPipelineState* pipelineState = m_device->CreatePipelineState(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); + + RHICommandList* commandList = CreateCommandList(); + ASSERT_NE(commandList, nullptr); + + commandList->Reset(); + commandList->TransitionBarrier(uav, ResourceStates::Common, ResourceStates::UnorderedAccess); + commandList->SetPipelineState(pipelineState); + RHIDescriptorSet* descriptorSets[] = { descriptorSet }; + commandList->SetComputeDescriptorSets(0, 1, descriptorSets, pipelineLayout); + commandList->Dispatch(1, 1, 1); + SubmitAndWait(commandList); + + const std::vector pixels = ReadTextureRgba8(static_cast(texture)); + ASSERT_GE(pixels.size(), 4u); + EXPECT_EQ(pixels[0], 255u); + EXPECT_EQ(pixels[1], 0u); + EXPECT_EQ(pixels[2], 0u); + EXPECT_EQ(pixels[3], 255u); + + commandList->Shutdown(); + delete commandList; + shader->Shutdown(); + delete shader; + pipelineState->Shutdown(); + delete pipelineState; + descriptorSet->Shutdown(); + delete descriptorSet; + pipelineLayout->Shutdown(); + delete pipelineLayout; + pool->Shutdown(); + delete pool; + uav->Shutdown(); + delete uav; + texture->Shutdown(); + delete texture; +} + } // namespace #endif