Add Vulkan shader, UAV, and compute coverage

This commit is contained in:
2026-03-27 18:55:38 +08:00
parent 704d2067ce
commit 53ac1dbc44
12 changed files with 611 additions and 40 deletions

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -0,0 +1,47 @@
#pragma once
#include "XCEngine/RHI/RHIShader.h"
#include "XCEngine/RHI/Vulkan/VulkanCommon.h"
#include <string>
#include <vector>
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<UniformInfo>& 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<UniformInfo> m_uniformInfos;
};
} // namespace RHI
} // namespace XCEngine

View File

@@ -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() {

View File

@@ -75,8 +75,7 @@ void VulkanDescriptorSet::Update(uint32_t offset, RHIResourceView* view) {
}
const DescriptorSetLayoutBinding* binding = FindBinding(offset);
if (binding == nullptr ||
static_cast<DescriptorType>(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<DescriptorType>(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);
}

View File

@@ -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 <algorithm>
#include <filesystem>
#include <iterator>
#include <memory>
#include <vector>
@@ -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<char>(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<VulkanTexture*>(texture), desc)) {
return view;
}
delete view;
return nullptr;
}

View File

@@ -2,6 +2,7 @@
#include "XCEngine/RHI/Vulkan/VulkanDevice.h"
#include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h"
#include "XCEngine/RHI/Vulkan/VulkanShader.h"
#include <algorithm>
#include <cstring>
@@ -66,6 +67,10 @@ bool LoadSpirvBytes(const ShaderCompileDesc& desc, std::vector<uint32_t>& words,
return !words.empty();
}
bool HasShaderPayload(const ShaderCompileDesc& desc) {
return !desc.source.empty() || !desc.fileName.empty();
}
VkShaderModule CreateShaderModule(VkDevice device, const std::vector<uint32_t>& 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<VulkanPipelineLayout*>(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<uint32_t> vertexWords;
std::vector<uint32_t> fragmentWords;
std::string vertexEntryPoint;
@@ -130,21 +175,6 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin
return false;
}
auto* externalPipelineLayout = static_cast<VulkanPipelineLayout*>(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<VkAttachmentDescription> 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<VulkanShader*>(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

View File

@@ -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<Format>(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;

View File

@@ -0,0 +1,190 @@
#include "XCEngine/RHI/Vulkan/VulkanShader.h"
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
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<std::streamsize>(sizeof(uint32_t))) != 0) {
return false;
}
std::vector<uint8_t> bytes(static_cast<size_t>(fileSize));
file.seekg(0, std::ios::beg);
if (!file.read(reinterpret_cast<char*>(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<const uint32_t*>(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<uint16_t>(instruction >> 16);
const uint16_t opcode = static_cast<uint16_t>(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<const char*>(&words[index + 3]);
entryPoint = nameBytes;
return true;
}
index += wordCountInInstruction;
}
return false;
}
} // namespace RHI
} // namespace XCEngine

View File

@@ -4,15 +4,21 @@
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <vector>
#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<uint32_t>(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<uint32_t>(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<uint32_t>(DescriptorType::UAV);
uavBinding.count = 1;
uavBinding.visibility = static_cast<uint32_t>(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<uint8_t> pixels = ReadTextureRgba8(static_cast<VulkanTexture*>(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