diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 540f511d..c905dba2 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -155,6 +155,10 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanCommon.h ${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/VulkanDescriptorPool.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanDescriptorSet.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanPipelineLayout.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanResourceView.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanFence.h @@ -165,6 +169,10 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanScreenshot.h ${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/VulkanDescriptorPool.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanDescriptorSet.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanPipelineLayout.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanPipelineState.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanResourceView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanCommandQueue.cpp diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h index 87f63b32..cfa6c0c5 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h @@ -8,6 +8,7 @@ #include "XCEngine/RHI/RHIEnums.h" +#include #include namespace XCEngine { @@ -245,6 +246,162 @@ inline VkSampleCountFlagBits ToVulkanSampleCount(uint32_t sampleCount) { } } +inline VkDescriptorType ToVulkanDescriptorType(DescriptorType type) { + switch (type) { + case DescriptorType::CBV: + return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + case DescriptorType::SRV: + return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + case DescriptorType::UAV: + return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + case DescriptorType::Sampler: + return VK_DESCRIPTOR_TYPE_SAMPLER; + default: + return VK_DESCRIPTOR_TYPE_MAX_ENUM; + } +} + +inline VkShaderStageFlags ToVulkanShaderStageFlags(uint32_t visibility) { + switch (static_cast(visibility)) { + case ShaderVisibility::Vertex: + return VK_SHADER_STAGE_VERTEX_BIT; + case ShaderVisibility::Geometry: + return VK_SHADER_STAGE_GEOMETRY_BIT; + case ShaderVisibility::Pixel: + return VK_SHADER_STAGE_FRAGMENT_BIT; + case ShaderVisibility::Amplification: + case ShaderVisibility::Mesh: + case ShaderVisibility::Hull: + case ShaderVisibility::Domain: + case ShaderVisibility::All: + default: + return VK_SHADER_STAGE_ALL_GRAPHICS; + } +} + +inline VkFilter ToVulkanMinFilter(FilterMode filter) { + switch (filter) { + case FilterMode::Point: + case FilterMode::ComparisonPoint: + case FilterMode::MinimumPoint: + case FilterMode::MaximumPoint: + return VK_FILTER_NEAREST; + default: + return VK_FILTER_LINEAR; + } +} + +inline VkFilter ToVulkanMagFilter(FilterMode filter) { + switch (filter) { + case FilterMode::Point: + case FilterMode::ComparisonPoint: + case FilterMode::MinimumPoint: + case FilterMode::MaximumPoint: + return VK_FILTER_NEAREST; + default: + return VK_FILTER_LINEAR; + } +} + +inline VkSamplerMipmapMode ToVulkanSamplerMipmapMode(FilterMode filter) { + switch (filter) { + case FilterMode::Point: + case FilterMode::ComparisonPoint: + case FilterMode::MinimumPoint: + case FilterMode::MaximumPoint: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + default: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + } +} + +inline bool UsesVulkanSamplerAnisotropy(FilterMode filter) { + return filter == FilterMode::Anisotropic || + filter == FilterMode::ComparisonAnisotropic || + filter == FilterMode::MinimumAnisotropic || + filter == FilterMode::MaximumAnisotropic; +} + +inline bool UsesVulkanComparisonSampling(FilterMode filter) { + return filter == FilterMode::ComparisonPoint || + filter == FilterMode::ComparisonLinear || + filter == FilterMode::ComparisonAnisotropic; +} + +inline VkSamplerAddressMode ToVulkanSamplerAddressMode(TextureAddressMode mode) { + switch (mode) { + case TextureAddressMode::Wrap: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case TextureAddressMode::Mirror: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case TextureAddressMode::Clamp: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case TextureAddressMode::Border: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + case TextureAddressMode::MirrorOnce: + return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; + default: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + } +} + +inline VkBorderColor ToVulkanBorderColor(float r, float g, float b, float a) { + const bool opaqueBlack = std::fabs(r) < 0.0001f && + std::fabs(g) < 0.0001f && + std::fabs(b) < 0.0001f && + std::fabs(a - 1.0f) < 0.0001f; + if (opaqueBlack) { + return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + } + + const bool opaqueWhite = std::fabs(r - 1.0f) < 0.0001f && + std::fabs(g - 1.0f) < 0.0001f && + std::fabs(b - 1.0f) < 0.0001f && + std::fabs(a - 1.0f) < 0.0001f; + if (opaqueWhite) { + return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + } + + return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; +} + +inline VkImageViewType ToVulkanImageViewType(ResourceViewDimension dimension, TextureType textureType) { + switch (dimension) { + case ResourceViewDimension::Texture1D: + return VK_IMAGE_VIEW_TYPE_1D; + case ResourceViewDimension::Texture1DArray: + return VK_IMAGE_VIEW_TYPE_1D_ARRAY; + case ResourceViewDimension::Texture2DArray: + return VK_IMAGE_VIEW_TYPE_2D_ARRAY; + case ResourceViewDimension::Texture3D: + return VK_IMAGE_VIEW_TYPE_3D; + case ResourceViewDimension::TextureCube: + return VK_IMAGE_VIEW_TYPE_CUBE; + case ResourceViewDimension::TextureCubeArray: + return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; + case ResourceViewDimension::Texture2D: + case ResourceViewDimension::Unknown: + default: + break; + } + + switch (textureType) { + case TextureType::Texture1D: + return VK_IMAGE_VIEW_TYPE_1D; + case TextureType::Texture2DArray: + return VK_IMAGE_VIEW_TYPE_2D_ARRAY; + case TextureType::Texture3D: + return VK_IMAGE_VIEW_TYPE_3D; + case TextureType::TextureCube: + return VK_IMAGE_VIEW_TYPE_CUBE; + case TextureType::TextureCubeArray: + return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; + case TextureType::Texture2D: + default: + return VK_IMAGE_VIEW_TYPE_2D; + } +} + inline uint32_t ResolveVulkanApiMajor(uint32_t apiVersion) { return VK_API_VERSION_MAJOR(apiVersion); } diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanDescriptorPool.h b/engine/include/XCEngine/RHI/Vulkan/VulkanDescriptorPool.h new file mode 100644 index 00000000..d97702f1 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanDescriptorPool.h @@ -0,0 +1,42 @@ +#pragma once + +#include "XCEngine/RHI/RHIDescriptorPool.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +#include + +namespace XCEngine { +namespace RHI { + +class VulkanDescriptorSet; + +class VulkanDescriptorPool : public RHIDescriptorPool { +public: + VulkanDescriptorPool() = default; + ~VulkanDescriptorPool() override; + + bool Initialize(VkDevice device, const DescriptorPoolDesc& desc); + bool Initialize(const DescriptorPoolDesc& desc) override; + void Shutdown() override; + + void* GetNativeHandle() override { return m_descriptorPool; } + + uint32_t GetDescriptorCount() const override { return m_descriptorCount; } + DescriptorHeapType GetType() const override { return m_type; } + + RHIDescriptorSet* AllocateSet(const DescriptorSetLayoutDesc& layout) override; + void FreeSet(RHIDescriptorSet* set) override; + + VkDevice GetDevice() const { return m_device; } + VkDescriptorPool GetDescriptorPool() const { return m_descriptorPool; } + +private: + VkDevice m_device = VK_NULL_HANDLE; + VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE; + DescriptorHeapType m_type = DescriptorHeapType::CBV_SRV_UAV; + uint32_t m_descriptorCount = 0; + std::vector m_allocatedSets; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanDescriptorSet.h b/engine/include/XCEngine/RHI/Vulkan/VulkanDescriptorSet.h new file mode 100644 index 00000000..a2f6db5a --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanDescriptorSet.h @@ -0,0 +1,63 @@ +#pragma once + +#include "XCEngine/RHI/RHIDescriptorSet.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +#include +#include + +namespace XCEngine { +namespace RHI { + +class VulkanDescriptorPool; + +class VulkanDescriptorSet : public RHIDescriptorSet { +public: + VulkanDescriptorSet() = default; + ~VulkanDescriptorSet() override; + + bool Initialize( + VkDevice device, + VulkanDescriptorPool* pool, + VkDescriptorSetLayout layout, + VkDescriptorSet descriptorSet, + const DescriptorSetLayoutDesc& desc); + + void Shutdown() override; + void Bind() override; + void Unbind() override; + + void Update(uint32_t offset, RHIResourceView* view) override; + void UpdateSampler(uint32_t offset, RHISampler* sampler) override; + void WriteConstant(uint32_t binding, const void* data, size_t size, size_t offset = 0) override; + + uint32_t GetBindingCount() const override { return static_cast(m_bindings.size()); } + const DescriptorSetLayoutBinding* GetBindings() const override { + return m_bindings.empty() ? nullptr : m_bindings.data(); + } + + void* GetConstantBufferData() override { + return m_constantBufferData.empty() ? nullptr : m_constantBufferData.data(); + } + size_t GetConstantBufferSize() const override { return m_constantBufferData.size(); } + bool IsConstantDirty() const override { return m_constantDirty; } + void MarkConstantClean() override { m_constantDirty = false; } + + VkDescriptorSet GetDescriptorSet() const { return m_descriptorSet; } + VkDescriptorSetLayout GetDescriptorSetLayout() const { return m_descriptorSetLayout; } + +private: + const DescriptorSetLayoutBinding* FindBinding(uint32_t binding) const; + + VkDevice m_device = VK_NULL_HANDLE; + VulkanDescriptorPool* m_pool = nullptr; + VkDescriptorSetLayout m_descriptorSetLayout = VK_NULL_HANDLE; + VkDescriptorSet m_descriptorSet = VK_NULL_HANDLE; + std::vector m_bindings; + std::unordered_map m_bindingToIndex; + std::vector m_constantBufferData; + bool m_constantDirty = false; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineLayout.h b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineLayout.h new file mode 100644 index 00000000..ed868f27 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineLayout.h @@ -0,0 +1,40 @@ +#pragma once + +#include "XCEngine/RHI/RHIPipelineLayout.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +#include + +namespace XCEngine { +namespace RHI { + +class VulkanPipelineLayout : public RHIPipelineLayout { +public: + VulkanPipelineLayout() = default; + ~VulkanPipelineLayout() override; + + bool Initialize(VkDevice device, const RHIPipelineLayoutDesc& desc); + bool Initialize(const RHIPipelineLayoutDesc& desc) override; + void Shutdown() override; + + void* GetNativeHandle() override { return m_pipelineLayout; } + + VkPipelineLayout GetPipelineLayout() const { return m_pipelineLayout; } + const RHIPipelineLayoutDesc& GetDesc() const { return m_desc; } + bool UsesSetLayouts() const { return m_desc.setLayoutCount > 0 && m_desc.setLayouts != nullptr; } + +private: + void PrepareExplicitLayouts(const RHIPipelineLayoutDesc& desc); + void PrepareFlatLayouts(const RHIPipelineLayoutDesc& desc); + bool CreateNativeSetLayouts(); + + VkDevice m_device = VK_NULL_HANDLE; + VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; + RHIPipelineLayoutDesc m_desc = {}; + std::vector m_setLayouts; + std::vector> m_setLayoutBindings; + std::vector m_nativeSetLayouts; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h index f48907a3..4391a7b7 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h @@ -51,6 +51,7 @@ private: VkPipeline m_pipeline = VK_NULL_HANDLE; VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; VkRenderPass m_renderPass = VK_NULL_HANDLE; + bool m_ownsPipelineLayout = false; InputLayoutDesc m_inputLayoutDesc = {}; RasterizerDesc m_rasterizerDesc = {}; BlendDesc m_blendDesc = {}; diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h b/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h index ffb07c71..4f3e2dce 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h @@ -15,6 +15,7 @@ public: ~VulkanResourceView() override; bool InitializeAsRenderTarget(VkDevice device, VulkanTexture* texture, const ResourceViewDesc& desc); + bool InitializeAsShaderResource(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/VulkanSampler.h b/engine/include/XCEngine/RHI/Vulkan/VulkanSampler.h new file mode 100644 index 00000000..2fa65962 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanSampler.h @@ -0,0 +1,31 @@ +#pragma once + +#include "XCEngine/RHI/RHISampler.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +namespace XCEngine { +namespace RHI { + +class VulkanSampler : public RHISampler { +public: + VulkanSampler() = default; + ~VulkanSampler() override; + + bool Initialize(VkDevice device, const SamplerDesc& desc); + void Shutdown() override; + + void Bind(unsigned int unit) override; + void Unbind(unsigned int unit) override; + + void* GetNativeHandle() override { return m_sampler != VK_NULL_HANDLE ? &m_sampler : nullptr; } + unsigned int GetID() override { return 0; } + + VkSampler GetSampler() const { return m_sampler; } + +private: + VkDevice m_device = VK_NULL_HANDLE; + VkSampler m_sampler = VK_NULL_HANDLE; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h b/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h index 74cc8eab..c08f272f 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h @@ -20,13 +20,23 @@ public: uint32_t height, Format format, VkFormat vkFormat); + bool InitializeOwnedImage( + VkDevice device, + VkImage image, + VkDeviceMemory memory, + uint32_t width, + uint32_t height, + uint32_t mipLevels, + Format format, + TextureType textureType, + VkFormat vkFormat); void Shutdown() override; uint32_t GetWidth() const override { return m_width; } uint32_t GetHeight() const override { return m_height; } uint32_t GetDepth() const override { return 1; } - uint32_t GetMipLevels() const override { return 1; } + uint32_t GetMipLevels() const override { return m_mipLevels; } Format GetFormat() const override { return m_format; } TextureType GetTextureType() const override { return m_textureType; } @@ -39,17 +49,21 @@ public: void SetName(const std::string& name) override { m_name = name; } VkImage GetImage() const { return m_image; } + VkDeviceMemory GetMemory() const { return m_memory; } VkFormat GetVkFormat() const { return m_vkFormat; } private: VkDevice m_device = VK_NULL_HANDLE; VkImage m_image = VK_NULL_HANDLE; + VkDeviceMemory m_memory = VK_NULL_HANDLE; uint32_t m_width = 0; uint32_t m_height = 0; + uint32_t m_mipLevels = 1; Format m_format = Format::Unknown; TextureType m_textureType = TextureType::Texture2D; ResourceStates m_state = ResourceStates::Common; VkFormat m_vkFormat = VK_FORMAT_UNDEFINED; + bool m_ownsImage = false; std::string m_name; }; diff --git a/engine/src/RHI/Vulkan/VulkanCommandList.cpp b/engine/src/RHI/Vulkan/VulkanCommandList.cpp index 4b2f84c7..a5540959 100644 --- a/engine/src/RHI/Vulkan/VulkanCommandList.cpp +++ b/engine/src/RHI/Vulkan/VulkanCommandList.cpp @@ -1,7 +1,9 @@ #include "XCEngine/RHI/Vulkan/VulkanCommandList.h" #include "XCEngine/RHI/Vulkan/VulkanBuffer.h" +#include "XCEngine/RHI/Vulkan/VulkanDescriptorSet.h" #include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h" #include "XCEngine/RHI/Vulkan/VulkanPipelineState.h" #include "XCEngine/RHI/Vulkan/VulkanResourceView.h" #include "XCEngine/RHI/Vulkan/VulkanTexture.h" @@ -217,17 +219,66 @@ void VulkanCommandList::SetPipelineState(RHIPipelineState* pso) { } void VulkanCommandList::SetGraphicsDescriptorSets(uint32_t firstSet, uint32_t count, RHIDescriptorSet** descriptorSets, RHIPipelineLayout* pipelineLayout) { - (void)firstSet; - (void)count; - (void)descriptorSets; - (void)pipelineLayout; + if (count == 0 || descriptorSets == nullptr || m_commandBuffer == VK_NULL_HANDLE) { + return; + } + + VkPipelineLayout nativePipelineLayout = VK_NULL_HANDLE; + if (pipelineLayout != nullptr) { + nativePipelineLayout = static_cast(pipelineLayout)->GetPipelineLayout(); + } else if (m_currentPipelineState != nullptr) { + nativePipelineLayout = m_currentPipelineState->GetPipelineLayout(); + } + + if (nativePipelineLayout == VK_NULL_HANDLE) { + return; + } + + std::vector nativeSets; + nativeSets.reserve(count); + for (uint32_t i = 0; i < count; ++i) { + auto* descriptorSet = static_cast(descriptorSets[i]); + if (descriptorSet == nullptr || descriptorSet->GetDescriptorSet() == VK_NULL_HANDLE) { + return; + } + nativeSets.push_back(descriptorSet->GetDescriptorSet()); + } + + vkCmdBindDescriptorSets( + m_commandBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + nativePipelineLayout, + firstSet, + static_cast(nativeSets.size()), + nativeSets.data(), + 0, + nullptr); } void VulkanCommandList::SetComputeDescriptorSets(uint32_t firstSet, uint32_t count, RHIDescriptorSet** descriptorSets, RHIPipelineLayout* pipelineLayout) { - (void)firstSet; - (void)count; - (void)descriptorSets; - (void)pipelineLayout; + if (count == 0 || descriptorSets == nullptr || m_commandBuffer == VK_NULL_HANDLE || pipelineLayout == nullptr) { + return; + } + + std::vector nativeSets; + nativeSets.reserve(count); + for (uint32_t i = 0; i < count; ++i) { + auto* descriptorSet = static_cast(descriptorSets[i]); + if (descriptorSet == nullptr || descriptorSet->GetDescriptorSet() == VK_NULL_HANDLE) { + return; + } + nativeSets.push_back(descriptorSet->GetDescriptorSet()); + } + + vkCmdBindDescriptorSets( + m_commandBuffer, + VK_PIPELINE_BIND_POINT_COMPUTE, + static_cast(pipelineLayout)->GetPipelineLayout(), + firstSet, + static_cast(nativeSets.size()), + nativeSets.data(), + 0, + nullptr); } void VulkanCommandList::SetPrimitiveTopology(PrimitiveTopology topology) { diff --git a/engine/src/RHI/Vulkan/VulkanDescriptorPool.cpp b/engine/src/RHI/Vulkan/VulkanDescriptorPool.cpp new file mode 100644 index 00000000..0d913fed --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanDescriptorPool.cpp @@ -0,0 +1,140 @@ +#include "XCEngine/RHI/Vulkan/VulkanDescriptorPool.h" + +#include "XCEngine/RHI/Vulkan/VulkanDescriptorSet.h" + +#include + +namespace XCEngine { +namespace RHI { + +namespace { + +std::vector BuildVulkanLayoutBindings(const DescriptorSetLayoutDesc& layout) { + std::vector bindings; + bindings.reserve(layout.bindingCount); + + for (uint32_t i = 0; i < layout.bindingCount; ++i) { + const DescriptorSetLayoutBinding& binding = layout.bindings[i]; + VkDescriptorSetLayoutBinding vkBinding = {}; + vkBinding.binding = binding.binding; + vkBinding.descriptorType = ToVulkanDescriptorType(static_cast(binding.type)); + vkBinding.descriptorCount = binding.count > 0 ? binding.count : 1u; + vkBinding.stageFlags = ToVulkanShaderStageFlags(binding.visibility); + bindings.push_back(vkBinding); + } + + return bindings; +} + +} // namespace + +VulkanDescriptorPool::~VulkanDescriptorPool() { + Shutdown(); +} + +bool VulkanDescriptorPool::Initialize(VkDevice device, const DescriptorPoolDesc& desc) { + if (device == VK_NULL_HANDLE || desc.descriptorCount == 0) { + return false; + } + + m_device = device; + m_type = desc.type; + m_descriptorCount = desc.descriptorCount; + + std::vector poolSizes; + switch (desc.type) { + case DescriptorHeapType::CBV_SRV_UAV: + poolSizes.push_back({ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, desc.descriptorCount }); + poolSizes.push_back({ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, desc.descriptorCount }); + poolSizes.push_back({ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, desc.descriptorCount }); + break; + case DescriptorHeapType::Sampler: + poolSizes.push_back({ VK_DESCRIPTOR_TYPE_SAMPLER, desc.descriptorCount }); + break; + default: + return false; + } + + VkDescriptorPoolCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + createInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + createInfo.maxSets = desc.descriptorCount; + createInfo.poolSizeCount = static_cast(poolSizes.size()); + createInfo.pPoolSizes = poolSizes.data(); + + return vkCreateDescriptorPool(device, &createInfo, nullptr, &m_descriptorPool) == VK_SUCCESS; +} + +bool VulkanDescriptorPool::Initialize(const DescriptorPoolDesc& desc) { + return Initialize(static_cast(desc.device), desc); +} + +void VulkanDescriptorPool::Shutdown() { + m_allocatedSets.clear(); + + if (m_descriptorPool != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyDescriptorPool(m_device, m_descriptorPool, nullptr); + } + + m_descriptorPool = VK_NULL_HANDLE; + m_device = VK_NULL_HANDLE; + m_type = DescriptorHeapType::CBV_SRV_UAV; + m_descriptorCount = 0; +} + +RHIDescriptorSet* VulkanDescriptorPool::AllocateSet(const DescriptorSetLayoutDesc& layout) { + if (m_device == VK_NULL_HANDLE || m_descriptorPool == VK_NULL_HANDLE) { + return nullptr; + } + + const std::vector vkBindings = BuildVulkanLayoutBindings(layout); + + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(vkBindings.size()); + layoutInfo.pBindings = vkBindings.empty() ? nullptr : vkBindings.data(); + + VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; + if (vkCreateDescriptorSetLayout(m_device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + return nullptr; + } + + VkDescriptorSetAllocateInfo allocateInfo = {}; + allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocateInfo.descriptorPool = m_descriptorPool; + allocateInfo.descriptorSetCount = 1; + allocateInfo.pSetLayouts = &descriptorSetLayout; + + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + if (vkAllocateDescriptorSets(m_device, &allocateInfo, &descriptorSet) != VK_SUCCESS) { + vkDestroyDescriptorSetLayout(m_device, descriptorSetLayout, nullptr); + return nullptr; + } + + auto* set = new VulkanDescriptorSet(); + if (!set->Initialize(m_device, this, descriptorSetLayout, descriptorSet, layout)) { + set->Shutdown(); + delete set; + return nullptr; + } + + m_allocatedSets.push_back(set); + return set; +} + +void VulkanDescriptorPool::FreeSet(RHIDescriptorSet* set) { + if (set == nullptr) { + return; + } + + auto* vulkanSet = static_cast(set); + auto it = std::find(m_allocatedSets.begin(), m_allocatedSets.end(), vulkanSet); + if (it != m_allocatedSets.end()) { + m_allocatedSets.erase(it); + } + + delete vulkanSet; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanDescriptorSet.cpp b/engine/src/RHI/Vulkan/VulkanDescriptorSet.cpp new file mode 100644 index 00000000..9055c3da --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanDescriptorSet.cpp @@ -0,0 +1,155 @@ +#include "XCEngine/RHI/Vulkan/VulkanDescriptorSet.h" + +#include "XCEngine/RHI/Vulkan/VulkanDescriptorPool.h" +#include "XCEngine/RHI/Vulkan/VulkanResourceView.h" +#include "XCEngine/RHI/Vulkan/VulkanSampler.h" + +#include + +namespace XCEngine { +namespace RHI { + +VulkanDescriptorSet::~VulkanDescriptorSet() { + Shutdown(); +} + +bool VulkanDescriptorSet::Initialize( + VkDevice device, + VulkanDescriptorPool* pool, + VkDescriptorSetLayout layout, + VkDescriptorSet descriptorSet, + const DescriptorSetLayoutDesc& desc) { + if (device == VK_NULL_HANDLE || pool == nullptr || layout == VK_NULL_HANDLE || descriptorSet == VK_NULL_HANDLE) { + return false; + } + + m_device = device; + m_pool = pool; + m_descriptorSetLayout = layout; + m_descriptorSet = descriptorSet; + if (desc.bindingCount > 0 && desc.bindings != nullptr) { + m_bindings.assign(desc.bindings, desc.bindings + desc.bindingCount); + } + + for (uint32_t i = 0; i < m_bindings.size(); ++i) { + m_bindingToIndex[m_bindings[i].binding] = i; + } + + return true; +} + +void VulkanDescriptorSet::Shutdown() { + if (m_descriptorSet != VK_NULL_HANDLE && + m_pool != nullptr && + m_pool->GetDevice() != VK_NULL_HANDLE && + m_pool->GetDescriptorPool() != VK_NULL_HANDLE) { + vkFreeDescriptorSets(m_pool->GetDevice(), m_pool->GetDescriptorPool(), 1, &m_descriptorSet); + } + + if (m_descriptorSetLayout != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(m_device, m_descriptorSetLayout, nullptr); + } + + m_device = VK_NULL_HANDLE; + m_pool = nullptr; + m_descriptorSetLayout = VK_NULL_HANDLE; + m_descriptorSet = VK_NULL_HANDLE; + m_bindings.clear(); + m_bindingToIndex.clear(); + m_constantBufferData.clear(); + m_constantDirty = false; +} + +void VulkanDescriptorSet::Bind() { +} + +void VulkanDescriptorSet::Unbind() { +} + +void VulkanDescriptorSet::Update(uint32_t offset, RHIResourceView* view) { + if (m_device == VK_NULL_HANDLE || m_descriptorSet == VK_NULL_HANDLE || view == nullptr) { + return; + } + + const DescriptorSetLayoutBinding* binding = FindBinding(offset); + if (binding == nullptr || + static_cast(binding->type) != DescriptorType::SRV) { + return; + } + + auto* vulkanView = static_cast(view); + if (vulkanView == nullptr || vulkanView->GetImageView() == VK_NULL_HANDLE) { + return; + } + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = vulkanView->GetImageView(); + + VkWriteDescriptorSet write = {}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = m_descriptorSet; + write.dstBinding = offset; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + write.pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(m_device, 1, &write, 0, nullptr); +} + +void VulkanDescriptorSet::UpdateSampler(uint32_t offset, RHISampler* sampler) { + if (m_device == VK_NULL_HANDLE || m_descriptorSet == VK_NULL_HANDLE || sampler == nullptr) { + return; + } + + const DescriptorSetLayoutBinding* binding = FindBinding(offset); + if (binding == nullptr || + static_cast(binding->type) != DescriptorType::Sampler) { + return; + } + + auto* vulkanSampler = static_cast(sampler); + if (vulkanSampler == nullptr || vulkanSampler->GetSampler() == VK_NULL_HANDLE) { + return; + } + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.sampler = vulkanSampler->GetSampler(); + + VkWriteDescriptorSet write = {}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = m_descriptorSet; + write.dstBinding = offset; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + write.pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(m_device, 1, &write, 0, nullptr); +} + +void VulkanDescriptorSet::WriteConstant(uint32_t binding, const void* data, size_t size, size_t offset) { + (void)binding; + + if (data == nullptr || size == 0) { + return; + } + + if (m_constantBufferData.size() < offset + size) { + m_constantBufferData.resize(offset + size); + } + + std::memcpy(m_constantBufferData.data() + offset, data, size); + m_constantDirty = true; +} + +const DescriptorSetLayoutBinding* VulkanDescriptorSet::FindBinding(uint32_t binding) const { + auto it = m_bindingToIndex.find(binding); + if (it == m_bindingToIndex.end()) { + return nullptr; + } + + return &m_bindings[it->second]; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanDevice.cpp b/engine/src/RHI/Vulkan/VulkanDevice.cpp index c4d1842a..cdb573de 100644 --- a/engine/src/RHI/Vulkan/VulkanDevice.cpp +++ b/engine/src/RHI/Vulkan/VulkanDevice.cpp @@ -3,14 +3,19 @@ #include "XCEngine/RHI/Vulkan/VulkanBuffer.h" #include "XCEngine/RHI/Vulkan/VulkanCommandList.h" #include "XCEngine/RHI/Vulkan/VulkanCommandQueue.h" +#include "XCEngine/RHI/Vulkan/VulkanDescriptorPool.h" +#include "XCEngine/RHI/Vulkan/VulkanDescriptorSet.h" #include "XCEngine/RHI/Vulkan/VulkanFence.h" +#include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h" #include "XCEngine/RHI/Vulkan/VulkanPipelineState.h" #include "XCEngine/RHI/Vulkan/VulkanResourceView.h" +#include "XCEngine/RHI/Vulkan/VulkanSampler.h" #include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" #include "XCEngine/RHI/Vulkan/VulkanTexture.h" #include #include +#include #include namespace XCEngine { @@ -29,6 +34,261 @@ std::wstring ResolveVendorName(uint32_t vendorId) { } } +VkImageType ToVulkanImageType(TextureType type) { + switch (type) { + case TextureType::Texture1D: + return VK_IMAGE_TYPE_1D; + case TextureType::Texture3D: + return VK_IMAGE_TYPE_3D; + case TextureType::Texture2D: + case TextureType::Texture2DArray: + case TextureType::TextureCube: + case TextureType::TextureCubeArray: + default: + return VK_IMAGE_TYPE_2D; + } +} + +VkImageLayout ToImageLayout(ResourceStates state) { + switch (state) { + case ResourceStates::RenderTarget: + return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + case ResourceStates::CopySrc: + return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + case ResourceStates::CopyDst: + return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + case ResourceStates::Present: + return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + case ResourceStates::PixelShaderResource: + return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + case ResourceStates::DepthWrite: + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + case ResourceStates::Common: + default: + return VK_IMAGE_LAYOUT_UNDEFINED; + } +} + +VkAccessFlags ToAccessMask(ResourceStates state) { + switch (state) { + case ResourceStates::RenderTarget: + return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + case ResourceStates::CopySrc: + return VK_ACCESS_TRANSFER_READ_BIT; + case ResourceStates::CopyDst: + return VK_ACCESS_TRANSFER_WRITE_BIT; + case ResourceStates::PixelShaderResource: + return VK_ACCESS_SHADER_READ_BIT; + case ResourceStates::DepthWrite: + return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + case ResourceStates::Present: + case ResourceStates::Common: + default: + return 0; + } +} + +VkPipelineStageFlags ToStageMask(ResourceStates state) { + switch (state) { + case ResourceStates::RenderTarget: + return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_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::DepthWrite: + return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + case ResourceStates::Present: + return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + case ResourceStates::Common: + default: + return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + } +} + +VkImageUsageFlags ResolveTextureUsageFlags(const TextureDesc& desc) { + const Format format = static_cast(desc.format); + VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + 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; + } + return usage; +} + +bool BeginSingleUseCommands(VkDevice device, uint32_t queueFamilyIndex, VkCommandPool& commandPool, VkCommandBuffer& commandBuffer) { + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = queueFamilyIndex; + poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + return false; + } + + VkCommandBufferAllocateInfo allocateInfo = {}; + allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocateInfo.commandPool = commandPool; + allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocateInfo.commandBufferCount = 1; + if (vkAllocateCommandBuffers(device, &allocateInfo, &commandBuffer) != VK_SUCCESS) { + vkDestroyCommandPool(device, commandPool, nullptr); + commandPool = VK_NULL_HANDLE; + return false; + } + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + vkDestroyCommandPool(device, commandPool, nullptr); + commandPool = VK_NULL_HANDLE; + commandBuffer = VK_NULL_HANDLE; + return false; + } + + return true; +} + +bool EndSingleUseCommands(VkDevice device, VkQueue queue, VkCommandPool commandPool, VkCommandBuffer commandBuffer) { + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + vkDestroyCommandPool(device, commandPool, nullptr); + return false; + } + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + const bool success = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE) == VK_SUCCESS && + vkQueueWaitIdle(queue) == VK_SUCCESS; + + vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); + vkDestroyCommandPool(device, commandPool, nullptr); + return success; +} + +void TransitionImage( + VkCommandBuffer commandBuffer, + VkImage image, + Format format, + ResourceStates before, + ResourceStates after, + uint32_t mipLevels, + uint32_t arraySize) { + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = ToImageLayout(before); + barrier.newLayout = ToImageLayout(after); + barrier.srcAccessMask = ToAccessMask(before); + barrier.dstAccessMask = ToAccessMask(after); + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = GetImageAspectMask(format); + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevels > 0 ? mipLevels : 1u; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = arraySize > 0 ? arraySize : 1u; + + vkCmdPipelineBarrier( + commandBuffer, + ToStageMask(before), + ToStageMask(after), + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); +} + +bool UploadTextureData( + VulkanDevice* device, + VulkanTexture* texture, + const TextureDesc& desc, + const void* initialData, + size_t initialDataSize, + uint32_t rowPitch) { + if (device == nullptr || texture == nullptr || initialData == nullptr || initialDataSize == 0) { + return false; + } + + const Format format = static_cast(desc.format); + const uint32_t pixelSize = GetFormatSize(format); + if (pixelSize == 0) { + return false; + } + + BufferDesc stagingDesc = {}; + stagingDesc.size = initialDataSize; + stagingDesc.stride = 1; + stagingDesc.bufferType = static_cast(BufferType::Vertex); + + VulkanBuffer stagingBuffer; + if (!stagingBuffer.Initialize( + device, + stagingDesc, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { + return false; + } + + stagingBuffer.SetData(initialData, initialDataSize); + + VkCommandPool commandPool = VK_NULL_HANDLE; + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + if (!BeginSingleUseCommands(device->GetDevice(), device->GetGraphicsQueueFamilyIndex(), commandPool, commandBuffer)) { + stagingBuffer.Shutdown(); + return false; + } + + TransitionImage( + commandBuffer, + texture->GetImage(), + format, + ResourceStates::Common, + ResourceStates::CopyDst, + desc.mipLevels, + desc.arraySize); + + VkBufferImageCopy copyRegion = {}; + copyRegion.bufferOffset = 0; + copyRegion.bufferRowLength = rowPitch > 0 ? rowPitch / pixelSize : 0; + copyRegion.bufferImageHeight = 0; + copyRegion.imageSubresource.aspectMask = GetImageAspectMask(format); + copyRegion.imageSubresource.mipLevel = 0; + copyRegion.imageSubresource.baseArrayLayer = 0; + copyRegion.imageSubresource.layerCount = desc.arraySize > 0 ? desc.arraySize : 1u; + copyRegion.imageExtent.width = desc.width; + copyRegion.imageExtent.height = desc.height; + copyRegion.imageExtent.depth = desc.depth > 0 ? desc.depth : 1u; + + vkCmdCopyBufferToImage( + commandBuffer, + stagingBuffer.GetBuffer(), + texture->GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©Region); + + TransitionImage( + commandBuffer, + texture->GetImage(), + format, + ResourceStates::CopyDst, + ResourceStates::PixelShaderResource, + desc.mipLevels, + desc.arraySize); + + const bool submitted = EndSingleUseCommands(device->GetDevice(), device->GetGraphicsQueue(), commandPool, commandBuffer); + stagingBuffer.Shutdown(); + return submitted; +} + } // namespace VulkanDevice::~VulkanDevice() { @@ -233,16 +493,91 @@ RHIBuffer* VulkanDevice::CreateBuffer(const BufferDesc& desc) { } RHITexture* VulkanDevice::CreateTexture(const TextureDesc& desc) { - (void)desc; - return nullptr; + const Format format = static_cast(desc.format); + const VkFormat vkFormat = ToVulkanFormat(format); + if (m_device == VK_NULL_HANDLE || + vkFormat == VK_FORMAT_UNDEFINED || + desc.width == 0 || + desc.height == 0) { + return nullptr; + } + + VkImageCreateInfo imageInfo = {}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.imageType = ToVulkanImageType(static_cast(desc.textureType)); + imageInfo.extent.width = desc.width; + imageInfo.extent.height = desc.height; + imageInfo.extent.depth = desc.depth > 0 ? desc.depth : 1u; + imageInfo.mipLevels = desc.mipLevels > 0 ? desc.mipLevels : 1u; + imageInfo.arrayLayers = desc.arraySize > 0 ? desc.arraySize : 1u; + imageInfo.format = vkFormat; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = ResolveTextureUsageFlags(desc); + imageInfo.samples = ToVulkanSampleCount(desc.sampleCount > 0 ? desc.sampleCount : 1u); + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkImage image = VK_NULL_HANDLE; + if (vkCreateImage(m_device, &imageInfo, nullptr, &image) != VK_SUCCESS) { + return nullptr; + } + + VkMemoryRequirements memoryRequirements = {}; + vkGetImageMemoryRequirements(m_device, image, &memoryRequirements); + + VkMemoryAllocateInfo allocateInfo = {}; + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = memoryRequirements.size; + allocateInfo.memoryTypeIndex = FindMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (allocateInfo.memoryTypeIndex == UINT32_MAX) { + vkDestroyImage(m_device, image, nullptr); + return nullptr; + } + + VkDeviceMemory memory = VK_NULL_HANDLE; + if (vkAllocateMemory(m_device, &allocateInfo, nullptr, &memory) != VK_SUCCESS) { + vkDestroyImage(m_device, image, nullptr); + return nullptr; + } + + if (vkBindImageMemory(m_device, image, memory, 0) != VK_SUCCESS) { + vkFreeMemory(m_device, memory, nullptr); + vkDestroyImage(m_device, image, nullptr); + return nullptr; + } + + auto* texture = new VulkanTexture(); + if (!texture->InitializeOwnedImage( + m_device, + image, + memory, + desc.width, + desc.height, + imageInfo.mipLevels, + format, + static_cast(desc.textureType), + vkFormat)) { + delete texture; + return nullptr; + } + + return texture; } RHITexture* VulkanDevice::CreateTexture(const TextureDesc& desc, const void* initialData, size_t initialDataSize, uint32_t rowPitch) { - (void)desc; - (void)initialData; - (void)initialDataSize; - (void)rowPitch; - return nullptr; + auto texture = std::unique_ptr(static_cast(CreateTexture(desc))); + if (!texture) { + return nullptr; + } + + if (initialData != nullptr && initialDataSize > 0) { + if (!UploadTextureData(this, texture.get(), desc, initialData, initialDataSize, rowPitch)) { + return nullptr; + } + texture->SetState(ResourceStates::PixelShaderResource); + } + + return texture.release(); } RHISwapChain* VulkanDevice::CreateSwapChain(const SwapChainDesc& desc, RHICommandQueue* presentQueue) { @@ -289,7 +624,12 @@ RHIPipelineState* VulkanDevice::CreatePipelineState(const GraphicsPipelineDesc& } RHIPipelineLayout* VulkanDevice::CreatePipelineLayout(const RHIPipelineLayoutDesc& desc) { - (void)desc; + auto* pipelineLayout = new VulkanPipelineLayout(); + if (pipelineLayout->Initialize(m_device, desc)) { + return pipelineLayout; + } + + delete pipelineLayout; return nullptr; } @@ -298,7 +638,12 @@ RHIFence* VulkanDevice::CreateFence(const FenceDesc& desc) { } RHISampler* VulkanDevice::CreateSampler(const SamplerDesc& desc) { - (void)desc; + auto* sampler = new VulkanSampler(); + if (sampler->Initialize(m_device, desc)) { + return sampler; + } + + delete sampler; return nullptr; } @@ -320,14 +665,17 @@ RHIFramebuffer* VulkanDevice::CreateFramebuffer(class RHIRenderPass* renderPass, } RHIDescriptorPool* VulkanDevice::CreateDescriptorPool(const DescriptorPoolDesc& desc) { - (void)desc; + auto* pool = new VulkanDescriptorPool(); + if (pool->Initialize(m_device, desc)) { + return pool; + } + + delete pool; return nullptr; } RHIDescriptorSet* VulkanDevice::CreateDescriptorSet(RHIDescriptorPool* pool, const DescriptorSetLayoutDesc& layout) { - (void)pool; - (void)layout; - return nullptr; + return pool != nullptr ? pool->AllocateSet(layout) : nullptr; } RHIResourceView* VulkanDevice::CreateVertexBufferView(RHIBuffer* buffer, const ResourceViewDesc& desc) { @@ -366,8 +714,12 @@ RHIResourceView* VulkanDevice::CreateDepthStencilView(RHITexture* texture, const } RHIResourceView* VulkanDevice::CreateShaderResourceView(RHITexture* texture, const ResourceViewDesc& desc) { - (void)texture; - (void)desc; + auto* view = new VulkanResourceView(); + if (view->InitializeAsShaderResource(m_device, static_cast(texture), desc)) { + return view; + } + + delete view; return nullptr; } diff --git a/engine/src/RHI/Vulkan/VulkanPipelineLayout.cpp b/engine/src/RHI/Vulkan/VulkanPipelineLayout.cpp new file mode 100644 index 00000000..4e3b6a6f --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanPipelineLayout.cpp @@ -0,0 +1,190 @@ +#include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h" + +namespace XCEngine { +namespace RHI { + +namespace { + +void AppendSyntheticSetLayout( + std::vector& setLayouts, + std::vector>& setLayoutBindings, + DescriptorType type, + uint32_t count) { + if (count == 0) { + return; + } + + setLayoutBindings.emplace_back(); + setLayoutBindings.back().push_back({ + 0, + static_cast(type), + count, + static_cast(ShaderVisibility::All) + }); + + DescriptorSetLayoutDesc setLayout = {}; + setLayout.bindingCount = 1; + setLayout.bindings = setLayoutBindings.back().data(); + setLayouts.push_back(setLayout); +} + +} // namespace + +VulkanPipelineLayout::~VulkanPipelineLayout() { + Shutdown(); +} + +bool VulkanPipelineLayout::Initialize(VkDevice device, const RHIPipelineLayoutDesc& desc) { + if (device == VK_NULL_HANDLE) { + return false; + } + + m_device = device; + m_desc = desc; + m_setLayouts.clear(); + m_setLayoutBindings.clear(); + m_nativeSetLayouts.clear(); + + if (desc.setLayoutCount > 0 && desc.setLayouts != nullptr) { + PrepareExplicitLayouts(desc); + } else { + PrepareFlatLayouts(desc); + } + + if (!CreateNativeSetLayouts()) { + Shutdown(); + return false; + } + + VkPipelineLayoutCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + createInfo.setLayoutCount = static_cast(m_nativeSetLayouts.size()); + createInfo.pSetLayouts = m_nativeSetLayouts.empty() ? nullptr : m_nativeSetLayouts.data(); + + if (vkCreatePipelineLayout(m_device, &createInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) { + Shutdown(); + return false; + } + + return true; +} + +bool VulkanPipelineLayout::Initialize(const RHIPipelineLayoutDesc& desc) { + (void)desc; + return false; +} + +void VulkanPipelineLayout::PrepareExplicitLayouts(const RHIPipelineLayoutDesc& desc) { + m_desc.constantBufferCount = 0; + m_desc.textureCount = 0; + m_desc.samplerCount = 0; + m_desc.uavCount = 0; + + m_setLayouts.resize(desc.setLayoutCount); + m_setLayoutBindings.resize(desc.setLayoutCount); + + for (uint32_t setIndex = 0; setIndex < desc.setLayoutCount; ++setIndex) { + const DescriptorSetLayoutDesc& srcSetLayout = desc.setLayouts[setIndex]; + auto& dstBindings = m_setLayoutBindings[setIndex]; + if (srcSetLayout.bindingCount > 0 && srcSetLayout.bindings != nullptr) { + dstBindings.assign(srcSetLayout.bindings, srcSetLayout.bindings + srcSetLayout.bindingCount); + } + + DescriptorSetLayoutDesc dstSetLayout = {}; + dstSetLayout.bindingCount = srcSetLayout.bindingCount; + dstSetLayout.bindings = dstBindings.empty() ? nullptr : dstBindings.data(); + m_setLayouts[setIndex] = dstSetLayout; + + for (const DescriptorSetLayoutBinding& binding : dstBindings) { + switch (static_cast(binding.type)) { + case DescriptorType::CBV: + m_desc.constantBufferCount += binding.count; + break; + case DescriptorType::SRV: + m_desc.textureCount += binding.count; + break; + case DescriptorType::UAV: + m_desc.uavCount += binding.count; + break; + case DescriptorType::Sampler: + m_desc.samplerCount += binding.count; + break; + default: + break; + } + } + } + + m_desc.setLayouts = m_setLayouts.empty() ? nullptr : m_setLayouts.data(); + m_desc.setLayoutCount = static_cast(m_setLayouts.size()); +} + +void VulkanPipelineLayout::PrepareFlatLayouts(const RHIPipelineLayoutDesc& desc) { + AppendSyntheticSetLayout(m_setLayouts, m_setLayoutBindings, DescriptorType::CBV, desc.constantBufferCount); + AppendSyntheticSetLayout(m_setLayouts, m_setLayoutBindings, DescriptorType::SRV, desc.textureCount); + AppendSyntheticSetLayout(m_setLayouts, m_setLayoutBindings, DescriptorType::UAV, desc.uavCount); + AppendSyntheticSetLayout(m_setLayouts, m_setLayoutBindings, DescriptorType::Sampler, desc.samplerCount); + + for (uint32_t i = 0; i < m_setLayouts.size(); ++i) { + m_setLayouts[i].bindingCount = static_cast(m_setLayoutBindings[i].size()); + m_setLayouts[i].bindings = m_setLayoutBindings[i].empty() ? nullptr : m_setLayoutBindings[i].data(); + } + + m_desc.setLayouts = m_setLayouts.empty() ? nullptr : m_setLayouts.data(); + m_desc.setLayoutCount = static_cast(m_setLayouts.size()); +} + +bool VulkanPipelineLayout::CreateNativeSetLayouts() { + m_nativeSetLayouts.reserve(m_setLayouts.size()); + + for (const DescriptorSetLayoutDesc& setLayout : m_setLayouts) { + std::vector vkBindings; + vkBindings.reserve(setLayout.bindingCount); + + for (uint32_t i = 0; i < setLayout.bindingCount; ++i) { + const DescriptorSetLayoutBinding& binding = setLayout.bindings[i]; + VkDescriptorSetLayoutBinding vkBinding = {}; + vkBinding.binding = binding.binding; + vkBinding.descriptorType = ToVulkanDescriptorType(static_cast(binding.type)); + vkBinding.descriptorCount = binding.count > 0 ? binding.count : 1u; + vkBinding.stageFlags = ToVulkanShaderStageFlags(binding.visibility); + vkBindings.push_back(vkBinding); + } + + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = static_cast(vkBindings.size()); + layoutInfo.pBindings = vkBindings.empty() ? nullptr : vkBindings.data(); + + VkDescriptorSetLayout nativeLayout = VK_NULL_HANDLE; + if (vkCreateDescriptorSetLayout(m_device, &layoutInfo, nullptr, &nativeLayout) != VK_SUCCESS) { + return false; + } + + m_nativeSetLayouts.push_back(nativeLayout); + } + + return true; +} + +void VulkanPipelineLayout::Shutdown() { + if (m_pipelineLayout != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); + } + m_pipelineLayout = VK_NULL_HANDLE; + + for (VkDescriptorSetLayout nativeSetLayout : m_nativeSetLayouts) { + if (nativeSetLayout != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(m_device, nativeSetLayout, nullptr); + } + } + m_nativeSetLayouts.clear(); + + m_desc = {}; + m_setLayouts.clear(); + m_setLayoutBindings.clear(); + m_device = VK_NULL_HANDLE; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp index 3bfb0b13..20ec31ae 100644 --- a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp +++ b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp @@ -1,6 +1,7 @@ #include "XCEngine/RHI/Vulkan/VulkanPipelineState.h" #include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h" #include #include @@ -129,12 +130,19 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin return false; } - 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; + 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; } VkAttachmentDescription colorAttachment = {}; @@ -164,8 +172,11 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin renderPassInfo.pSubpasses = &subpass; if (vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) { - vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); + 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; @@ -372,12 +383,15 @@ void VulkanPipelineState::Shutdown() { } if (m_pipelineLayout != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); + if (m_ownsPipelineLayout) { + vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); + } m_pipelineLayout = VK_NULL_HANDLE; } m_deviceOwner = nullptr; m_device = VK_NULL_HANDLE; + m_ownsPipelineLayout = false; } } // namespace RHI diff --git a/engine/src/RHI/Vulkan/VulkanResourceView.cpp b/engine/src/RHI/Vulkan/VulkanResourceView.cpp index eac23bc8..22dc6836 100644 --- a/engine/src/RHI/Vulkan/VulkanResourceView.cpp +++ b/engine/src/RHI/Vulkan/VulkanResourceView.cpp @@ -24,8 +24,33 @@ bool VulkanResourceView::InitializeAsRenderTarget(VkDevice device, VulkanTexture VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = texture->GetImage(); - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = texture->GetVkFormat(); + viewInfo.viewType = ToVulkanImageViewType(m_dimension, texture->GetTextureType()); + viewInfo.format = m_format != Format::Unknown ? ToVulkanFormat(m_format) : texture->GetVkFormat(); + viewInfo.subresourceRange.aspectMask = GetImageAspectMask(texture->GetFormat()); + 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::InitializeAsShaderResource(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::ShaderResource; + 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 = GetImageAspectMask(texture->GetFormat()); viewInfo.subresourceRange.baseMipLevel = desc.mipLevel; viewInfo.subresourceRange.levelCount = 1; @@ -80,6 +105,7 @@ bool VulkanResourceView::IsValid() const { return m_buffer != nullptr && m_buffer->GetBuffer() != VK_NULL_HANDLE; case ResourceViewType::RenderTarget: case ResourceViewType::DepthStencil: + case ResourceViewType::ShaderResource: return m_imageView != VK_NULL_HANDLE; default: return false; diff --git a/engine/src/RHI/Vulkan/VulkanSampler.cpp b/engine/src/RHI/Vulkan/VulkanSampler.cpp new file mode 100644 index 00000000..55fd1be0 --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanSampler.cpp @@ -0,0 +1,56 @@ +#include "XCEngine/RHI/Vulkan/VulkanSampler.h" + +namespace XCEngine { +namespace RHI { + +VulkanSampler::~VulkanSampler() { + Shutdown(); +} + +bool VulkanSampler::Initialize(VkDevice device, const SamplerDesc& desc) { + if (device == VK_NULL_HANDLE) { + return false; + } + + m_device = device; + + VkSamplerCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + createInfo.magFilter = ToVulkanMagFilter(static_cast(desc.filter)); + createInfo.minFilter = ToVulkanMinFilter(static_cast(desc.filter)); + createInfo.mipmapMode = ToVulkanSamplerMipmapMode(static_cast(desc.filter)); + createInfo.addressModeU = ToVulkanSamplerAddressMode(static_cast(desc.addressU)); + createInfo.addressModeV = ToVulkanSamplerAddressMode(static_cast(desc.addressV)); + createInfo.addressModeW = ToVulkanSamplerAddressMode(static_cast(desc.addressW)); + createInfo.mipLodBias = desc.mipLodBias; + createInfo.anisotropyEnable = UsesVulkanSamplerAnisotropy(static_cast(desc.filter)) ? VK_TRUE : VK_FALSE; + createInfo.maxAnisotropy = desc.maxAnisotropy > 0 ? static_cast(desc.maxAnisotropy) : 1.0f; + createInfo.compareEnable = UsesVulkanComparisonSampling(static_cast(desc.filter)) ? VK_TRUE : VK_FALSE; + createInfo.compareOp = ToVulkanCompareOp(static_cast(desc.comparisonFunc)); + createInfo.minLod = desc.minLod; + createInfo.maxLod = desc.maxLod; + createInfo.borderColor = ToVulkanBorderColor(desc.borderColorR, desc.borderColorG, desc.borderColorB, desc.borderColorA); + createInfo.unnormalizedCoordinates = VK_FALSE; + + return vkCreateSampler(device, &createInfo, nullptr, &m_sampler) == VK_SUCCESS; +} + +void VulkanSampler::Shutdown() { + if (m_sampler != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroySampler(m_device, m_sampler, nullptr); + } + + m_sampler = VK_NULL_HANDLE; + m_device = VK_NULL_HANDLE; +} + +void VulkanSampler::Bind(unsigned int unit) { + (void)unit; +} + +void VulkanSampler::Unbind(unsigned int unit) { + (void)unit; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanSwapChain.cpp b/engine/src/RHI/Vulkan/VulkanSwapChain.cpp index 106dab27..3cf2a20f 100644 --- a/engine/src/RHI/Vulkan/VulkanSwapChain.cpp +++ b/engine/src/RHI/Vulkan/VulkanSwapChain.cpp @@ -62,11 +62,19 @@ bool VulkanSwapChain::CreateSwapChainResources() { VkSurfaceFormatKHR selectedFormat = formats[0]; for (const VkSurfaceFormatKHR& candidate : formats) { - if (candidate.format == VK_FORMAT_B8G8R8A8_UNORM) { + if (candidate.format == VK_FORMAT_R8G8B8A8_UNORM) { selectedFormat = candidate; break; } } + if (selectedFormat.format != VK_FORMAT_R8G8B8A8_UNORM) { + for (const VkSurfaceFormatKHR& candidate : formats) { + if (candidate.format == VK_FORMAT_B8G8R8A8_UNORM) { + selectedFormat = candidate; + break; + } + } + } m_surfaceFormat = selectedFormat.format; if ((capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0 || diff --git a/engine/src/RHI/Vulkan/VulkanTexture.cpp b/engine/src/RHI/Vulkan/VulkanTexture.cpp index 6a3e78a1..acbdba82 100644 --- a/engine/src/RHI/Vulkan/VulkanTexture.cpp +++ b/engine/src/RHI/Vulkan/VulkanTexture.cpp @@ -18,21 +18,62 @@ bool VulkanTexture::InitializeSwapChainImage( m_image = image; m_width = width; m_height = height; + m_mipLevels = 1; m_format = format; m_vkFormat = vkFormat; m_textureType = TextureType::Texture2D; m_state = ResourceStates::Common; + m_ownsImage = false; return m_image != VK_NULL_HANDLE; } +bool VulkanTexture::InitializeOwnedImage( + VkDevice device, + VkImage image, + VkDeviceMemory memory, + uint32_t width, + uint32_t height, + uint32_t mipLevels, + Format format, + TextureType textureType, + VkFormat vkFormat) { + if (device == VK_NULL_HANDLE || image == VK_NULL_HANDLE || memory == VK_NULL_HANDLE) { + return false; + } + + m_device = device; + m_image = image; + m_memory = memory; + m_width = width; + m_height = height; + m_mipLevels = mipLevels; + m_format = format; + m_vkFormat = vkFormat; + m_textureType = textureType; + m_state = ResourceStates::Common; + m_ownsImage = true; + return true; +} + void VulkanTexture::Shutdown() { + if (m_ownsImage && m_image != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyImage(m_device, m_image, nullptr); + } + + if (m_memory != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkFreeMemory(m_device, m_memory, nullptr); + } + m_image = VK_NULL_HANDLE; + m_memory = VK_NULL_HANDLE; m_device = VK_NULL_HANDLE; m_width = 0; m_height = 0; + m_mipLevels = 1; m_format = Format::Unknown; m_vkFormat = VK_FORMAT_UNDEFINED; m_state = ResourceStates::Common; + m_ownsImage = false; } } // namespace RHI diff --git a/tests/RHI/integration/quad/CMakeLists.txt b/tests/RHI/integration/quad/CMakeLists.txt index 66f437fa..5a1f16d1 100644 --- a/tests/RHI/integration/quad/CMakeLists.txt +++ b/tests/RHI/integration/quad/CMakeLists.txt @@ -9,6 +9,8 @@ set(PACKAGE_DIR ${CMAKE_SOURCE_DIR}/tests/opengl/package) get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE) +find_package(Vulkan QUIET) + add_executable(rhi_integration_quad main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../fixtures/RHIIntegrationFixture.cpp @@ -33,6 +35,60 @@ target_link_libraries(rhi_integration_quad PRIVATE GTest::gtest ) +if(Vulkan_FOUND) + set(XCENGINE_GLSLANG_VALIDATOR_HINT "$ENV{VULKAN_SDK}") + find_program( + XCENGINE_GLSLANG_VALIDATOR + NAMES glslangValidator glslangValidator.exe + HINTS + "${XCENGINE_GLSLANG_VALIDATOR_HINT}/Bin" + "${Vulkan_ROOT}/Bin") + + if(NOT XCENGINE_GLSLANG_VALIDATOR) + file(GLOB XCENGINE_VULKAN_BIN_DIRS "D:/VulkanSDK/*/Bin") + if(XCENGINE_VULKAN_BIN_DIRS) + list(SORT XCENGINE_VULKAN_BIN_DIRS COMPARE NATURAL ORDER DESCENDING) + foreach(XCENGINE_VULKAN_BIN_DIR IN LISTS XCENGINE_VULKAN_BIN_DIRS) + find_program( + XCENGINE_GLSLANG_VALIDATOR + NAMES glslangValidator glslangValidator.exe + PATHS "${XCENGINE_VULKAN_BIN_DIR}" + NO_DEFAULT_PATH) + if(XCENGINE_GLSLANG_VALIDATOR) + break() + endif() + endforeach() + endif() + endif() + + if(NOT XCENGINE_GLSLANG_VALIDATOR) + message(FATAL_ERROR "glslangValidator not found for Vulkan quad shaders") + endif() + + set(QUAD_VULKAN_VERTEX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/Res/Shader/quad_vulkan.vert) + set(QUAD_VULKAN_FRAGMENT_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/Res/Shader/quad_vulkan.frag) + + add_custom_command(TARGET rhi_integration_quad PRE_BUILD + COMMAND ${XCENGINE_GLSLANG_VALIDATOR} + -V + -S + vert + -o + $/quad_vulkan.vert.spv + ${QUAD_VULKAN_VERTEX_SOURCE} + COMMAND ${XCENGINE_GLSLANG_VALIDATOR} + -V + -S + frag + -o + $/quad_vulkan.frag.spv + ${QUAD_VULKAN_FRAGMENT_SOURCE} + VERBATIM) + + target_link_libraries(rhi_integration_quad PRIVATE Vulkan::Vulkan) + target_compile_definitions(rhi_integration_quad PRIVATE XCENGINE_SUPPORT_VULKAN) +endif() + target_compile_definitions(rhi_integration_quad PRIVATE UNICODE _UNICODE diff --git a/tests/RHI/integration/quad/Res/Shader/quad_vulkan.frag b/tests/RHI/integration/quad/Res/Shader/quad_vulkan.frag new file mode 100644 index 00000000..c7a6afb9 --- /dev/null +++ b/tests/RHI/integration/quad/Res/Shader/quad_vulkan.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(set = 0, binding = 0) uniform texture2D uTexture; +layout(set = 1, binding = 0) uniform sampler uSampler; + +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 fragColor; + +void main() { + fragColor = texture(sampler2D(uTexture, uSampler), vTexCoord); +} diff --git a/tests/RHI/integration/quad/Res/Shader/quad_vulkan.vert b/tests/RHI/integration/quad/Res/Shader/quad_vulkan.vert new file mode 100644 index 00000000..ad56c287 --- /dev/null +++ b/tests/RHI/integration/quad/Res/Shader/quad_vulkan.vert @@ -0,0 +1,11 @@ +#version 450 + +layout(location = 0) in vec4 aPosition; +layout(location = 1) in vec2 aTexCoord; + +layout(location = 0) out vec2 vTexCoord; + +void main() { + gl_Position = aPosition; + vTexCoord = aTexCoord; +} diff --git a/tests/RHI/integration/quad/main.cpp b/tests/RHI/integration/quad/main.cpp index 5a4e82e6..c7a106da 100644 --- a/tests/RHI/integration/quad/main.cpp +++ b/tests/RHI/integration/quad/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -53,6 +54,27 @@ std::filesystem::path ResolveRuntimePath(const char* relativePath) { return GetExecutableDirectory() / relativePath; } +std::vector LoadBinaryFileRelative(const char* filename) { + const std::filesystem::path path = ResolveRuntimePath(filename); + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + return {}; + } + + const std::streamsize size = file.tellg(); + if (size <= 0) { + return {}; + } + + std::vector bytes(static_cast(size)); + file.seekg(0, std::ios::beg); + if (!file.read(reinterpret_cast(bytes.data()), size)) { + return {}; + } + + return bytes; +} + const char kQuadHlsl[] = R"( Texture2D gTexture : register(t0); SamplerState gSampler : register(s0); @@ -104,7 +126,16 @@ void main() { )"; const char* GetScreenshotFilename(RHIType type) { - return type == RHIType::D3D12 ? "quad_d3d12.ppm" : "quad_opengl.ppm"; + switch (type) { + case RHIType::D3D12: + return "quad_d3d12.ppm"; + case RHIType::OpenGL: + return "quad_opengl.ppm"; + case RHIType::Vulkan: + return "quad_vulkan.ppm"; + default: + return "quad_unknown.ppm"; + } } RHITexture* LoadQuadTexture(RHIDevice* device) { @@ -190,7 +221,7 @@ GraphicsPipelineDesc CreateQuadPipelineDesc(RHIType type, RHIPipelineLayout* pip desc.fragmentShader.sourceLanguage = ShaderLanguage::HLSL; desc.fragmentShader.entryPoint = L"MainPS"; desc.fragmentShader.profile = L"ps_5_0"; - } else { + } else if (type == RHIType::OpenGL) { desc.vertexShader.source.assign(kQuadVertexShader, kQuadVertexShader + strlen(kQuadVertexShader)); desc.vertexShader.sourceLanguage = ShaderLanguage::GLSL; desc.vertexShader.profile = L"vs_4_30"; @@ -198,6 +229,14 @@ GraphicsPipelineDesc CreateQuadPipelineDesc(RHIType type, RHIPipelineLayout* pip desc.fragmentShader.source.assign(kQuadFragmentShader, kQuadFragmentShader + strlen(kQuadFragmentShader)); desc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL; desc.fragmentShader.profile = L"fs_4_30"; + } else if (type == RHIType::Vulkan) { + desc.vertexShader.source = LoadBinaryFileRelative("quad_vulkan.vert.spv"); + desc.vertexShader.sourceLanguage = ShaderLanguage::SPIRV; + desc.vertexShader.entryPoint = L"main"; + + desc.fragmentShader.source = LoadBinaryFileRelative("quad_vulkan.frag.spv"); + desc.fragmentShader.sourceLanguage = ShaderLanguage::SPIRV; + desc.fragmentShader.entryPoint = L"main"; } return desc; @@ -342,9 +381,34 @@ void QuadTest::InitializeQuadResources() { ASSERT_NE(mSamplerSet, nullptr); mSamplerSet->UpdateSampler(0, mSampler); + DescriptorSetLayoutBinding pipelineTextureBinding = {}; + pipelineTextureBinding.binding = 0; + pipelineTextureBinding.type = static_cast(DescriptorType::SRV); + pipelineTextureBinding.count = 1; + pipelineTextureBinding.visibility = static_cast(ShaderVisibility::Pixel); + + DescriptorSetLayoutDesc textureSetLayout = {}; + textureSetLayout.bindings = &pipelineTextureBinding; + textureSetLayout.bindingCount = 1; + + DescriptorSetLayoutBinding pipelineSamplerBinding = {}; + pipelineSamplerBinding.binding = 0; + pipelineSamplerBinding.type = static_cast(DescriptorType::Sampler); + pipelineSamplerBinding.count = 1; + pipelineSamplerBinding.visibility = static_cast(ShaderVisibility::Pixel); + + DescriptorSetLayoutDesc samplerSetLayout = {}; + samplerSetLayout.bindings = &pipelineSamplerBinding; + samplerSetLayout.bindingCount = 1; + + DescriptorSetLayoutDesc setLayouts[] = { + textureSetLayout, + samplerSetLayout, + }; + RHIPipelineLayoutDesc pipelineLayoutDesc = {}; - pipelineLayoutDesc.textureCount = 1; - pipelineLayoutDesc.samplerCount = 1; + pipelineLayoutDesc.setLayouts = setLayouts; + pipelineLayoutDesc.setLayoutCount = 2; mPipelineLayout = GetDevice()->CreatePipelineLayout(pipelineLayoutDesc); ASSERT_NE(mPipelineLayout, nullptr); @@ -510,6 +574,9 @@ TEST_P(QuadTest, RenderQuad) { INSTANTIATE_TEST_SUITE_P(D3D12, QuadTest, ::testing::Values(RHIType::D3D12)); INSTANTIATE_TEST_SUITE_P(OpenGL, QuadTest, ::testing::Values(RHIType::OpenGL)); +#if defined(XCENGINE_SUPPORT_VULKAN) +INSTANTIATE_TEST_SUITE_P(Vulkan, QuadTest, ::testing::Values(RHIType::Vulkan)); +#endif GTEST_API_ int main(int argc, char** argv) { Logger::Get().Initialize();