diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index d42e5b89..88c3d59f 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -152,7 +152,9 @@ add_library(XCEngine STATIC # Vulkan RHI ${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/VulkanPipelineState.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanResourceView.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanFence.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanCommandQueue.h @@ -160,7 +162,9 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanDevice.h ${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/VulkanPipelineState.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanResourceView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanCommandQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanCommandList.cpp diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanBuffer.h b/engine/include/XCEngine/RHI/Vulkan/VulkanBuffer.h new file mode 100644 index 00000000..2b598c1c --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanBuffer.h @@ -0,0 +1,57 @@ +#pragma once + +#include "XCEngine/RHI/RHIBuffer.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +#include + +namespace XCEngine { +namespace RHI { + +class VulkanDevice; + +class VulkanBuffer : public RHIBuffer { +public: + VulkanBuffer() = default; + ~VulkanBuffer() override; + + bool Initialize(VulkanDevice* device, const BufferDesc& desc, VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryProperties); + + void Shutdown() override; + + void* Map() override; + void Unmap() override; + void SetData(const void* data, size_t size, size_t offset = 0) override; + + uint64_t GetSize() const override { return m_size; } + BufferType GetBufferType() const override { return m_bufferType; } + void SetBufferType(BufferType type) override { m_bufferType = type; } + uint32_t GetStride() const override { return m_stride; } + void SetStride(uint32_t stride) override { m_stride = stride; } + + void* GetNativeHandle() override { return m_buffer; } + + ResourceStates GetState() const override { return m_state; } + void SetState(ResourceStates state) override { m_state = state; } + + const std::string& GetName() const override { return m_name; } + void SetName(const std::string& name) override { m_name = name; } + + VkBuffer GetBuffer() const { return m_buffer; } + VkDeviceMemory GetMemory() const { return m_memory; } + +private: + VulkanDevice* m_deviceOwner = nullptr; + VkDevice m_device = VK_NULL_HANDLE; + VkBuffer m_buffer = VK_NULL_HANDLE; + VkDeviceMemory m_memory = VK_NULL_HANDLE; + void* m_mappedData = nullptr; + uint64_t m_size = 0; + BufferType m_bufferType = BufferType::Vertex; + uint32_t m_stride = 0; + ResourceStates m_state = ResourceStates::Common; + std::string m_name; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h index cdf91e3d..bea2a187 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h @@ -3,10 +3,13 @@ #include "XCEngine/RHI/RHICommandList.h" #include "XCEngine/RHI/Vulkan/VulkanCommon.h" +#include + namespace XCEngine { namespace RHI { class VulkanDevice; +class VulkanPipelineState; class VulkanTexture; class VulkanCommandList : public RHICommandList { @@ -58,6 +61,9 @@ public: VkCommandBuffer GetCommandBuffer() const { return m_commandBuffer; } private: + bool EnsureGraphicsRenderPass(); + void EndActiveRenderPass(); + void DestroyTransientFramebuffers(); void TransitionTexture(VulkanTexture* texture, ResourceStates newState); VulkanDevice* m_device = nullptr; @@ -65,6 +71,14 @@ private: VkCommandBuffer m_commandBuffer = VK_NULL_HANDLE; RHIResourceView* m_currentColorTarget = nullptr; RHIResourceView* m_currentDepthTarget = nullptr; + VulkanPipelineState* m_currentPipelineState = nullptr; + PrimitiveTopology m_currentPrimitiveTopology = PrimitiveTopology::TriangleList; + bool m_renderPassActive = false; + bool m_hasViewport = false; + bool m_hasScissor = false; + VkViewport m_viewport = {}; + VkRect2D m_scissor = {}; + std::vector m_transientFramebuffers; }; } // namespace RHI diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h index 7cf03681..87f63b32 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h @@ -41,6 +41,31 @@ inline VkFormat ToVulkanFormat(Format format) { } } +inline uint32_t GetFormatSize(Format format) { + switch (format) { + case Format::R8_UNorm: + return 1; + case Format::R8G8_UNorm: + return 2; + case Format::R8G8B8A8_UNorm: + return 4; + case Format::R16_UInt: + case Format::R16_Float: + return 2; + case Format::R16G16B16A16_Float: + return 8; + case Format::R32_Float: + case Format::R32_UInt: + return 4; + case Format::R32G32_Float: + return 8; + case Format::R32G32B32A32_Float: + return 16; + default: + return 0; + } +} + inline Format ToRHIFormat(VkFormat format) { switch (format) { case VK_FORMAT_R8G8B8A8_UNORM: @@ -73,6 +98,153 @@ inline VkImageAspectFlags GetImageAspectMask(Format format) { } } +inline VkPrimitiveTopology ToVulkanPrimitiveTopology(PrimitiveTopologyType topologyType) { + switch (topologyType) { + case PrimitiveTopologyType::Point: + return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + case PrimitiveTopologyType::Line: + return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + case PrimitiveTopologyType::Triangle: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + case PrimitiveTopologyType::Patch: + return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; + case PrimitiveTopologyType::Undefined: + default: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + } +} + +inline VkPrimitiveTopology ToVulkanPrimitiveTopology(PrimitiveTopology topology) { + switch (topology) { + case PrimitiveTopology::PointList: + return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + case PrimitiveTopology::LineList: + return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + case PrimitiveTopology::LineStrip: + return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + case PrimitiveTopology::TriangleStrip: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + case PrimitiveTopology::TriangleList: + default: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + } +} + +inline VkPolygonMode ToVulkanPolygonMode(FillMode mode) { + switch (mode) { + case FillMode::Wireframe: + return VK_POLYGON_MODE_LINE; + case FillMode::Solid: + default: + return VK_POLYGON_MODE_FILL; + } +} + +inline VkCullModeFlags ToVulkanCullMode(CullMode mode) { + switch (mode) { + case CullMode::Front: + return VK_CULL_MODE_FRONT_BIT; + case CullMode::Back: + return VK_CULL_MODE_BACK_BIT; + case CullMode::None: + default: + return VK_CULL_MODE_NONE; + } +} + +inline VkFrontFace ToVulkanFrontFace(FrontFace frontFace) { + return frontFace == FrontFace::Clockwise ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE; +} + +inline VkCompareOp ToVulkanCompareOp(ComparisonFunc func) { + switch (func) { + case ComparisonFunc::Never: + return VK_COMPARE_OP_NEVER; + case ComparisonFunc::Less: + return VK_COMPARE_OP_LESS; + case ComparisonFunc::Equal: + return VK_COMPARE_OP_EQUAL; + case ComparisonFunc::LessEqual: + return VK_COMPARE_OP_LESS_OR_EQUAL; + case ComparisonFunc::Greater: + return VK_COMPARE_OP_GREATER; + case ComparisonFunc::NotEqual: + return VK_COMPARE_OP_NOT_EQUAL; + case ComparisonFunc::GreaterEqual: + return VK_COMPARE_OP_GREATER_OR_EQUAL; + case ComparisonFunc::Always: + default: + return VK_COMPARE_OP_ALWAYS; + } +} + +inline VkBlendFactor ToVulkanBlendFactor(BlendFactor factor) { + switch (factor) { + case BlendFactor::Zero: + return VK_BLEND_FACTOR_ZERO; + case BlendFactor::One: + return VK_BLEND_FACTOR_ONE; + case BlendFactor::SrcColor: + return VK_BLEND_FACTOR_SRC_COLOR; + case BlendFactor::InvSrcColor: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case BlendFactor::SrcAlpha: + return VK_BLEND_FACTOR_SRC_ALPHA; + case BlendFactor::InvSrcAlpha: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case BlendFactor::DstAlpha: + return VK_BLEND_FACTOR_DST_ALPHA; + case BlendFactor::InvDstAlpha: + return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + case BlendFactor::DstColor: + return VK_BLEND_FACTOR_DST_COLOR; + case BlendFactor::InvDstColor: + return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + default: + return VK_BLEND_FACTOR_ONE; + } +} + +inline VkBlendOp ToVulkanBlendOp(BlendOp op) { + switch (op) { + case BlendOp::Subtract: + return VK_BLEND_OP_SUBTRACT; + case BlendOp::ReverseSubtract: + return VK_BLEND_OP_REVERSE_SUBTRACT; + case BlendOp::Min: + return VK_BLEND_OP_MIN; + case BlendOp::Max: + return VK_BLEND_OP_MAX; + case BlendOp::Add: + default: + return VK_BLEND_OP_ADD; + } +} + +inline VkIndexType ToVulkanIndexType(Format format) { + switch (format) { + case Format::R16_UInt: + return VK_INDEX_TYPE_UINT16; + case Format::R32_UInt: + case Format::Unknown: + default: + return VK_INDEX_TYPE_UINT32; + } +} + +inline VkSampleCountFlagBits ToVulkanSampleCount(uint32_t sampleCount) { + switch (sampleCount) { + case 2: + return VK_SAMPLE_COUNT_2_BIT; + case 4: + return VK_SAMPLE_COUNT_4_BIT; + case 8: + return VK_SAMPLE_COUNT_8_BIT; + default: + return VK_SAMPLE_COUNT_1_BIT; + } +} + inline uint32_t ResolveVulkanApiMajor(uint32_t apiVersion) { return VK_API_VERSION_MAJOR(apiVersion); } diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h new file mode 100644 index 00000000..f48907a3 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h @@ -0,0 +1,66 @@ +#pragma once + +#include "XCEngine/RHI/RHIPipelineState.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +namespace XCEngine { +namespace RHI { + +class VulkanDevice; + +class VulkanPipelineState : public RHIPipelineState { +public: + VulkanPipelineState() = default; + ~VulkanPipelineState() override; + + bool Initialize(VulkanDevice* device, const GraphicsPipelineDesc& desc); + + void SetInputLayout(const InputLayoutDesc& layout) override; + void SetRasterizerState(const RasterizerDesc& state) override; + void SetBlendState(const BlendDesc& state) override; + void SetDepthStencilState(const DepthStencilStateDesc& state) override; + void SetTopology(uint32_t topologyType) override; + void SetRenderTargetFormats(uint32_t count, const uint32_t* formats, uint32_t depthFormat) override; + void SetSampleCount(uint32_t count) override; + void SetComputeShader(RHIShader* shader) override; + + const RasterizerDesc& GetRasterizerState() const override { return m_rasterizerDesc; } + const BlendDesc& GetBlendState() const override { return m_blendDesc; } + 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; } + + bool IsValid() const override { return m_pipeline != VK_NULL_HANDLE; } + 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; } + + VkPipeline GetPipeline() const { return m_pipeline; } + VkPipelineLayout GetPipelineLayout() const { return m_pipelineLayout; } + VkRenderPass GetRenderPass() const { return m_renderPass; } + +private: + VulkanDevice* m_deviceOwner = nullptr; + VkDevice m_device = VK_NULL_HANDLE; + VkPipeline m_pipeline = VK_NULL_HANDLE; + VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; + VkRenderPass m_renderPass = VK_NULL_HANDLE; + InputLayoutDesc m_inputLayoutDesc = {}; + RasterizerDesc m_rasterizerDesc = {}; + BlendDesc m_blendDesc = {}; + DepthStencilStateDesc m_depthStencilDesc = {}; + uint32_t m_topologyType = static_cast(PrimitiveTopologyType::Triangle); + uint32_t m_renderTargetCount = 1; + uint32_t m_renderTargetFormats[8] = { 0 }; + uint32_t m_depthStencilFormat = 0; + uint32_t m_sampleCount = 1; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h b/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h index 01f85183..ffb07c71 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanResourceView.h @@ -2,21 +2,25 @@ #include "XCEngine/RHI/RHIResourceView.h" #include "XCEngine/RHI/Vulkan/VulkanCommon.h" -#include "XCEngine/RHI/Vulkan/VulkanTexture.h" namespace XCEngine { namespace RHI { +class VulkanBuffer; +class VulkanTexture; + class VulkanResourceView : public RHIResourceView { public: VulkanResourceView() = default; ~VulkanResourceView() override; bool InitializeAsRenderTarget(VkDevice device, VulkanTexture* texture, const ResourceViewDesc& desc); + bool InitializeAsVertexBuffer(VulkanBuffer* buffer, const ResourceViewDesc& desc); + bool InitializeAsIndexBuffer(VulkanBuffer* buffer, const ResourceViewDesc& desc); void Shutdown() override; - void* GetNativeHandle() override { return m_imageView; } - bool IsValid() const override { return m_imageView != VK_NULL_HANDLE; } + void* GetNativeHandle() override; + bool IsValid() const override; ResourceViewType GetViewType() const override { return m_viewType; } ResourceViewDimension GetDimension() const override { return m_dimension; } @@ -24,14 +28,23 @@ public: VulkanTexture* GetTexture() const { return m_texture; } VkImageView GetImageView() const { return m_imageView; } + VulkanBuffer* GetBufferResource() const { return m_buffer; } + VkBuffer GetBuffer() const; + uint64_t GetBufferOffset() const { return m_bufferOffset; } + uint32_t GetBufferSize() const { return m_bufferSize; } + uint32_t GetBufferStride() const { return m_bufferStride; } private: VkDevice m_device = VK_NULL_HANDLE; VkImageView m_imageView = VK_NULL_HANDLE; VulkanTexture* m_texture = nullptr; + VulkanBuffer* m_buffer = nullptr; ResourceViewType m_viewType = ResourceViewType::RenderTarget; ResourceViewDimension m_dimension = ResourceViewDimension::Texture2D; Format m_format = Format::Unknown; + uint64_t m_bufferOffset = 0; + uint32_t m_bufferSize = 0; + uint32_t m_bufferStride = 0; }; } // namespace RHI diff --git a/engine/src/RHI/Vulkan/VulkanBuffer.cpp b/engine/src/RHI/Vulkan/VulkanBuffer.cpp new file mode 100644 index 00000000..1d831102 --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanBuffer.cpp @@ -0,0 +1,123 @@ +#include "XCEngine/RHI/Vulkan/VulkanBuffer.h" + +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" + +#include + +namespace XCEngine { +namespace RHI { + +VulkanBuffer::~VulkanBuffer() { + Shutdown(); +} + +bool VulkanBuffer::Initialize(VulkanDevice* device, const BufferDesc& desc, VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryProperties) { + if (device == nullptr || device->GetDevice() == VK_NULL_HANDLE || desc.size == 0) { + return false; + } + + m_deviceOwner = device; + m_device = device->GetDevice(); + m_size = desc.size; + m_stride = desc.stride; + m_bufferType = static_cast(desc.bufferType); + + VkBufferCreateInfo bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = static_cast(desc.size); + bufferInfo.usage = usageFlags; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(m_device, &bufferInfo, nullptr, &m_buffer) != VK_SUCCESS) { + return false; + } + + VkMemoryRequirements memoryRequirements = {}; + vkGetBufferMemoryRequirements(m_device, m_buffer, &memoryRequirements); + + VkMemoryAllocateInfo allocateInfo = {}; + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = memoryRequirements.size; + allocateInfo.memoryTypeIndex = device->FindMemoryType(memoryRequirements.memoryTypeBits, memoryProperties); + if (allocateInfo.memoryTypeIndex == UINT32_MAX) { + Shutdown(); + return false; + } + + if (vkAllocateMemory(m_device, &allocateInfo, nullptr, &m_memory) != VK_SUCCESS) { + Shutdown(); + return false; + } + + if (vkBindBufferMemory(m_device, m_buffer, m_memory, 0) != VK_SUCCESS) { + Shutdown(); + return false; + } + + return true; +} + +void VulkanBuffer::Shutdown() { + if (m_mappedData != nullptr && m_device != VK_NULL_HANDLE && m_memory != VK_NULL_HANDLE) { + vkUnmapMemory(m_device, m_memory); + m_mappedData = nullptr; + } + + if (m_buffer != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyBuffer(m_device, m_buffer, nullptr); + m_buffer = VK_NULL_HANDLE; + } + + if (m_memory != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkFreeMemory(m_device, m_memory, nullptr); + m_memory = VK_NULL_HANDLE; + } + + m_deviceOwner = nullptr; + m_device = VK_NULL_HANDLE; + m_size = 0; + m_stride = 0; + m_state = ResourceStates::Common; +} + +void* VulkanBuffer::Map() { + if (m_device == VK_NULL_HANDLE || m_memory == VK_NULL_HANDLE) { + return nullptr; + } + + if (m_mappedData == nullptr) { + if (vkMapMemory(m_device, m_memory, 0, VK_WHOLE_SIZE, 0, &m_mappedData) != VK_SUCCESS) { + m_mappedData = nullptr; + } + } + + return m_mappedData; +} + +void VulkanBuffer::Unmap() { + if (m_mappedData != nullptr && m_device != VK_NULL_HANDLE && m_memory != VK_NULL_HANDLE) { + vkUnmapMemory(m_device, m_memory); + m_mappedData = nullptr; + } +} + +void VulkanBuffer::SetData(const void* data, size_t size, size_t offset) { + if (data == nullptr || size == 0 || offset + size > m_size) { + return; + } + + const bool wasMapped = m_mappedData != nullptr; + void* mappedData = Map(); + if (mappedData == nullptr) { + return; + } + + std::memcpy(static_cast(mappedData) + offset, data, size); + + if (!wasMapped) { + Unmap(); + } +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanCommandList.cpp b/engine/src/RHI/Vulkan/VulkanCommandList.cpp index 73f216c9..4b2f84c7 100644 --- a/engine/src/RHI/Vulkan/VulkanCommandList.cpp +++ b/engine/src/RHI/Vulkan/VulkanCommandList.cpp @@ -1,9 +1,13 @@ #include "XCEngine/RHI/Vulkan/VulkanCommandList.h" +#include "XCEngine/RHI/Vulkan/VulkanBuffer.h" #include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanPipelineState.h" #include "XCEngine/RHI/Vulkan/VulkanResourceView.h" #include "XCEngine/RHI/Vulkan/VulkanTexture.h" +#include + namespace XCEngine { namespace RHI { @@ -12,7 +16,7 @@ namespace { VkImageLayout ToVulkanImageLayout(ResourceStates state) { switch (state) { case ResourceStates::RenderTarget: - return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; case ResourceStates::CopySrc: return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; case ResourceStates::CopyDst: @@ -32,7 +36,7 @@ VkImageLayout ToVulkanImageLayout(ResourceStates state) { VkAccessFlags ToVulkanAccessMask(ResourceStates state) { switch (state) { case ResourceStates::RenderTarget: - return VK_ACCESS_TRANSFER_WRITE_BIT; + return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; case ResourceStates::CopySrc: return VK_ACCESS_TRANSFER_READ_BIT; case ResourceStates::CopyDst: @@ -51,6 +55,7 @@ VkAccessFlags ToVulkanAccessMask(ResourceStates state) { VkPipelineStageFlags ToVulkanStageMask(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; @@ -96,6 +101,9 @@ bool VulkanCommandList::Initialize(VulkanDevice* device) { } void VulkanCommandList::Shutdown() { + EndActiveRenderPass(); + DestroyTransientFramebuffers(); + if (m_commandPool != VK_NULL_HANDLE && m_device != nullptr) { if (m_commandBuffer != VK_NULL_HANDLE) { vkFreeCommandBuffers(m_device->GetDevice(), m_commandPool, 1, &m_commandBuffer); @@ -107,6 +115,7 @@ void VulkanCommandList::Shutdown() { m_commandPool = VK_NULL_HANDLE; m_currentColorTarget = nullptr; m_currentDepthTarget = nullptr; + m_currentPipelineState = nullptr; m_device = nullptr; } @@ -116,6 +125,13 @@ void VulkanCommandList::Reset() { } vkResetCommandPool(m_device->GetDevice(), m_commandPool, 0); + DestroyTransientFramebuffers(); + + m_currentPipelineState = nullptr; + m_currentPrimitiveTopology = PrimitiveTopology::TriangleList; + m_renderPassActive = false; + m_hasViewport = false; + m_hasScissor = false; VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; @@ -124,6 +140,13 @@ void VulkanCommandList::Reset() { } void VulkanCommandList::Close() { + EndActiveRenderPass(); + + auto* colorView = static_cast(m_currentColorTarget); + if (colorView != nullptr && colorView->GetTexture() != nullptr) { + TransitionTexture(colorView->GetTexture(), ResourceStates::Present); + } + if (m_commandBuffer != VK_NULL_HANDLE) { vkEndCommandBuffer(m_commandBuffer); } @@ -134,6 +157,10 @@ void VulkanCommandList::TransitionTexture(VulkanTexture* texture, ResourceStates return; } + if (texture->GetState() == newState) { + return; + } + VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.oldLayout = ToVulkanImageLayout(texture->GetState()); @@ -164,7 +191,7 @@ void VulkanCommandList::TransitionTexture(VulkanTexture* texture, ResourceStates void VulkanCommandList::TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) { (void)stateBefore; auto* view = static_cast(resource); - if (view != nullptr) { + if (view != nullptr && view->GetTexture() != nullptr) { TransitionTexture(view->GetTexture(), stateAfter); } } @@ -178,6 +205,7 @@ void VulkanCommandList::BeginRenderPass(class RHIRenderPass* renderPass, class R } void VulkanCommandList::EndRenderPass() { + EndActiveRenderPass(); } void VulkanCommandList::SetShader(RHIShader* shader) { @@ -185,7 +213,7 @@ void VulkanCommandList::SetShader(RHIShader* shader) { } void VulkanCommandList::SetPipelineState(RHIPipelineState* pso) { - (void)pso; + m_currentPipelineState = static_cast(pso); } void VulkanCommandList::SetGraphicsDescriptorSets(uint32_t firstSet, uint32_t count, RHIDescriptorSet** descriptorSets, RHIPipelineLayout* pipelineLayout) { @@ -203,25 +231,44 @@ void VulkanCommandList::SetComputeDescriptorSets(uint32_t firstSet, uint32_t cou } void VulkanCommandList::SetPrimitiveTopology(PrimitiveTopology topology) { - (void)topology; + m_currentPrimitiveTopology = topology; } void VulkanCommandList::SetViewport(const Viewport& viewport) { - (void)viewport; + m_viewport.x = viewport.topLeftX; + // Match the RHI viewport convention used by D3D12/OpenGL, where the origin is top-left. + m_viewport.y = viewport.topLeftY + viewport.height; + m_viewport.width = viewport.width; + m_viewport.height = -viewport.height; + m_viewport.minDepth = viewport.minDepth; + m_viewport.maxDepth = viewport.maxDepth; + m_hasViewport = true; + vkCmdSetViewport(m_commandBuffer, 0, 1, &m_viewport); } void VulkanCommandList::SetViewports(uint32_t count, const Viewport* viewports) { - (void)count; - (void)viewports; + if (count == 0 || viewports == nullptr) { + return; + } + + SetViewport(viewports[0]); } void VulkanCommandList::SetScissorRect(const Rect& rect) { - (void)rect; + m_scissor.offset.x = rect.left; + m_scissor.offset.y = rect.top; + m_scissor.extent.width = static_cast(rect.right - rect.left); + m_scissor.extent.height = static_cast(rect.bottom - rect.top); + m_hasScissor = true; + vkCmdSetScissor(m_commandBuffer, 0, 1, &m_scissor); } void VulkanCommandList::SetScissorRects(uint32_t count, const Rect* rects) { - (void)count; - (void)rects; + if (count == 0 || rects == nullptr) { + return; + } + + SetScissorRect(rects[0]); } void VulkanCommandList::SetRenderTargets(uint32_t count, RHIResourceView** renderTargets, RHIResourceView* depthStencil) { @@ -238,31 +285,65 @@ void VulkanCommandList::SetBlendFactor(const float factor[4]) { } void VulkanCommandList::SetVertexBuffers(uint32_t startSlot, uint32_t count, RHIResourceView** buffers, const uint64_t* offsets, const uint32_t* strides) { - (void)startSlot; - (void)count; - (void)buffers; - (void)offsets; - (void)strides; + if (count == 0 || buffers == nullptr) { + return; + } + + std::vector nativeBuffers; + std::vector nativeOffsets; + nativeBuffers.reserve(count); + nativeOffsets.reserve(count); + + for (uint32_t i = 0; i < count; ++i) { + auto* view = static_cast(buffers[i]); + if (view == nullptr || !view->IsValid()) { + continue; + } + + nativeBuffers.push_back(view->GetBuffer()); + nativeOffsets.push_back(view->GetBufferOffset() + (offsets != nullptr ? offsets[i] : 0)); + (void)strides; + } + + if (!nativeBuffers.empty()) { + vkCmdBindVertexBuffers( + m_commandBuffer, + startSlot, + static_cast(nativeBuffers.size()), + nativeBuffers.data(), + nativeOffsets.data()); + } } void VulkanCommandList::SetIndexBuffer(RHIResourceView* buffer, uint64_t offset) { - (void)buffer; - (void)offset; + auto* view = static_cast(buffer); + if (view == nullptr || !view->IsValid()) { + return; + } + + vkCmdBindIndexBuffer( + m_commandBuffer, + view->GetBuffer(), + view->GetBufferOffset() + offset, + ToVulkanIndexType(view->GetFormat())); } void VulkanCommandList::Draw(uint32_t vertexCount, uint32_t instanceCount, uint32_t startVertex, uint32_t startInstance) { - (void)vertexCount; - (void)instanceCount; - (void)startVertex; - (void)startInstance; + if (!EnsureGraphicsRenderPass()) { + return; + } + + vkCmdBindPipeline(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_currentPipelineState->GetPipeline()); + vkCmdDraw(m_commandBuffer, vertexCount, instanceCount, startVertex, startInstance); } void VulkanCommandList::DrawIndexed(uint32_t indexCount, uint32_t instanceCount, uint32_t startIndex, int32_t baseVertex, uint32_t startInstance) { - (void)indexCount; - (void)instanceCount; - (void)startIndex; - (void)baseVertex; - (void)startInstance; + if (!EnsureGraphicsRenderPass()) { + return; + } + + vkCmdBindPipeline(m_commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_currentPipelineState->GetPipeline()); + vkCmdDrawIndexed(m_commandBuffer, indexCount, instanceCount, startIndex, baseVertex, startInstance); } void VulkanCommandList::Clear(float r, float g, float b, float a, uint32_t buffers) { @@ -273,8 +354,10 @@ void VulkanCommandList::Clear(float r, float g, float b, float a, uint32_t buffe return; } + EndActiveRenderPass(); + VulkanTexture* texture = colorView->GetTexture(); - TransitionTexture(texture, ResourceStates::RenderTarget); + TransitionTexture(texture, ResourceStates::CopyDst); VkClearColorValue clearColor = {}; clearColor.float32[0] = r; @@ -297,7 +380,7 @@ void VulkanCommandList::Clear(float r, float g, float b, float a, uint32_t buffe 1, &range); - TransitionTexture(texture, ResourceStates::Present); + TransitionTexture(texture, ResourceStates::RenderTarget); } void VulkanCommandList::ClearRenderTarget(RHIResourceView* renderTarget, const float color[4]) { @@ -322,5 +405,81 @@ void VulkanCommandList::Dispatch(uint32_t x, uint32_t y, uint32_t z) { (void)z; } +bool VulkanCommandList::EnsureGraphicsRenderPass() { + if (m_renderPassActive) { + return true; + } + + if (m_currentPipelineState == nullptr || !m_currentPipelineState->IsValid()) { + return false; + } + + auto* colorView = static_cast(m_currentColorTarget); + if (colorView == nullptr || colorView->GetTexture() == nullptr || colorView->GetImageView() == VK_NULL_HANDLE) { + return false; + } + + VulkanTexture* texture = colorView->GetTexture(); + TransitionTexture(texture, ResourceStates::RenderTarget); + + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = m_currentPipelineState->GetRenderPass(); + framebufferInfo.attachmentCount = 1; + const VkImageView attachment = colorView->GetImageView(); + framebufferInfo.pAttachments = &attachment; + framebufferInfo.width = texture->GetWidth(); + framebufferInfo.height = texture->GetHeight(); + framebufferInfo.layers = 1; + + VkFramebuffer framebuffer = VK_NULL_HANDLE; + if (vkCreateFramebuffer(m_device->GetDevice(), &framebufferInfo, nullptr, &framebuffer) != VK_SUCCESS) { + return false; + } + m_transientFramebuffers.push_back(framebuffer); + + VkRenderPassBeginInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = m_currentPipelineState->GetRenderPass(); + renderPassInfo.framebuffer = framebuffer; + renderPassInfo.renderArea.offset = { 0, 0 }; + renderPassInfo.renderArea.extent = { texture->GetWidth(), texture->GetHeight() }; + renderPassInfo.clearValueCount = 0; + renderPassInfo.pClearValues = nullptr; + + vkCmdBeginRenderPass(m_commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + if (m_hasViewport) { + vkCmdSetViewport(m_commandBuffer, 0, 1, &m_viewport); + } + if (m_hasScissor) { + vkCmdSetScissor(m_commandBuffer, 0, 1, &m_scissor); + } + + m_renderPassActive = true; + return true; +} + +void VulkanCommandList::EndActiveRenderPass() { + if (m_renderPassActive && m_commandBuffer != VK_NULL_HANDLE) { + vkCmdEndRenderPass(m_commandBuffer); + m_renderPassActive = false; + } +} + +void VulkanCommandList::DestroyTransientFramebuffers() { + if (m_device == nullptr) { + m_transientFramebuffers.clear(); + return; + } + + for (VkFramebuffer framebuffer : m_transientFramebuffers) { + if (framebuffer != VK_NULL_HANDLE) { + vkDestroyFramebuffer(m_device->GetDevice(), framebuffer, nullptr); + } + } + m_transientFramebuffers.clear(); +} + } // namespace RHI } // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanDevice.cpp b/engine/src/RHI/Vulkan/VulkanDevice.cpp index 3ad6aaa6..c4d1842a 100644 --- a/engine/src/RHI/Vulkan/VulkanDevice.cpp +++ b/engine/src/RHI/Vulkan/VulkanDevice.cpp @@ -1,10 +1,13 @@ #include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanBuffer.h" #include "XCEngine/RHI/Vulkan/VulkanCommandList.h" #include "XCEngine/RHI/Vulkan/VulkanCommandQueue.h" #include "XCEngine/RHI/Vulkan/VulkanFence.h" +#include "XCEngine/RHI/Vulkan/VulkanPipelineState.h" #include "XCEngine/RHI/Vulkan/VulkanResourceView.h" #include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" +#include "XCEngine/RHI/Vulkan/VulkanTexture.h" #include #include @@ -202,7 +205,30 @@ uint32_t VulkanDevice::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags } RHIBuffer* VulkanDevice::CreateBuffer(const BufferDesc& desc) { - (void)desc; + VkBufferUsageFlags usageFlags = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + switch (static_cast(desc.bufferType)) { + case BufferType::Index: + usageFlags |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + break; + case BufferType::Constant: + usageFlags |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + break; + case BufferType::Vertex: + default: + usageFlags |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + break; + } + + auto* buffer = new VulkanBuffer(); + if (buffer->Initialize( + this, + desc, + usageFlags, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { + return buffer; + } + + delete buffer; return nullptr; } @@ -253,7 +279,12 @@ RHIShader* VulkanDevice::CreateShader(const ShaderCompileDesc& desc) { } RHIPipelineState* VulkanDevice::CreatePipelineState(const GraphicsPipelineDesc& desc) { - (void)desc; + auto* pipelineState = new VulkanPipelineState(); + if (pipelineState->Initialize(this, desc)) { + return pipelineState; + } + + delete pipelineState; return nullptr; } @@ -300,14 +331,22 @@ RHIDescriptorSet* VulkanDevice::CreateDescriptorSet(RHIDescriptorPool* pool, con } RHIResourceView* VulkanDevice::CreateVertexBufferView(RHIBuffer* buffer, const ResourceViewDesc& desc) { - (void)buffer; - (void)desc; + auto* view = new VulkanResourceView(); + if (view->InitializeAsVertexBuffer(static_cast(buffer), desc)) { + return view; + } + + delete view; return nullptr; } RHIResourceView* VulkanDevice::CreateIndexBufferView(RHIBuffer* buffer, const ResourceViewDesc& desc) { - (void)buffer; - (void)desc; + auto* view = new VulkanResourceView(); + if (view->InitializeAsIndexBuffer(static_cast(buffer), desc)) { + return view; + } + + delete view; return nullptr; } diff --git a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp new file mode 100644 index 00000000..3bfb0b13 --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp @@ -0,0 +1,384 @@ +#include "XCEngine/RHI/Vulkan/VulkanPipelineState.h" + +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" + +#include +#include +#include +#include +#include +#include + +namespace XCEngine { +namespace RHI { + +namespace { + +std::string NarrowAscii(const std::wstring& value) { + std::string result; + result.reserve(value.size()); + for (wchar_t ch : value) { + result.push_back(static_cast(ch)); + } + return result; +} + +bool LoadSpirvBytes(const ShaderCompileDesc& desc, std::vector& words, std::string& entryPoint) { + entryPoint = NarrowAscii(desc.entryPoint); + if (entryPoint.empty()) { + entryPoint = "main"; + } + + if (desc.sourceLanguage != ShaderLanguage::SPIRV) { + return false; + } + + std::vector bytes; + if (!desc.source.empty()) { + bytes.assign(desc.source.begin(), desc.source.end()); + } else if (!desc.fileName.empty()) { + std::ifstream file(std::filesystem::path(desc.fileName), std::ios::binary | std::ios::ate); + if (!file.is_open()) { + return false; + } + + const std::streamsize fileSize = file.tellg(); + if (fileSize <= 0 || (fileSize % 4) != 0) { + return false; + } + + bytes.resize(static_cast(fileSize)); + file.seekg(0, std::ios::beg); + if (!file.read(bytes.data(), fileSize)) { + return false; + } + } else { + return false; + } + + if ((bytes.size() % sizeof(uint32_t)) != 0) { + return false; + } + + words.resize(bytes.size() / sizeof(uint32_t)); + std::memcpy(words.data(), bytes.data(), bytes.size()); + return !words.empty(); +} + +VkShaderModule CreateShaderModule(VkDevice device, const std::vector& words) { + VkShaderModuleCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = words.size() * sizeof(uint32_t); + createInfo.pCode = words.data(); + + VkShaderModule module = VK_NULL_HANDLE; + if (vkCreateShaderModule(device, &createInfo, nullptr, &module) != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + return module; +} + +} // namespace + +VulkanPipelineState::~VulkanPipelineState() { + Shutdown(); +} + +bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelineDesc& desc) { + if (device == nullptr || device->GetDevice() == VK_NULL_HANDLE) { + return false; + } + + m_deviceOwner = device; + m_device = device->GetDevice(); + m_inputLayoutDesc = desc.inputLayout; + m_rasterizerDesc = desc.rasterizerState; + m_blendDesc = desc.blendState; + m_depthStencilDesc = desc.depthStencilState; + m_topologyType = desc.topologyType; + m_renderTargetCount = desc.renderTargetCount; + m_depthStencilFormat = desc.depthStencilFormat; + m_sampleCount = desc.sampleCount > 0 ? desc.sampleCount : 1; + for (uint32_t i = 0; i < 8; ++i) { + m_renderTargetFormats[i] = desc.renderTargetFormats[i]; + } + + if (m_renderTargetCount != 1 || m_renderTargetFormats[0] == 0) { + return false; + } + + std::vector vertexWords; + std::vector fragmentWords; + std::string vertexEntryPoint; + std::string fragmentEntryPoint; + if (!LoadSpirvBytes(desc.vertexShader, vertexWords, vertexEntryPoint) || + !LoadSpirvBytes(desc.fragmentShader, fragmentWords, fragmentEntryPoint)) { + return false; + } + + const VkShaderModule vertexModule = CreateShaderModule(m_device, vertexWords); + const VkShaderModule fragmentModule = CreateShaderModule(m_device, fragmentWords); + if (vertexModule == VK_NULL_HANDLE || fragmentModule == VK_NULL_HANDLE) { + if (vertexModule != VK_NULL_HANDLE) { + vkDestroyShaderModule(m_device, vertexModule, nullptr); + } + if (fragmentModule != VK_NULL_HANDLE) { + vkDestroyShaderModule(m_device, fragmentModule, nullptr); + } + 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; + } + + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = ToVulkanFormat(static_cast(m_renderTargetFormats[0])); + colorAttachment.samples = ToVulkanSampleCount(m_sampleCount); + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + + if (vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) { + vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); + m_pipelineLayout = VK_NULL_HANDLE; + vkDestroyShaderModule(m_device, fragmentModule, nullptr); + vkDestroyShaderModule(m_device, vertexModule, nullptr); + return false; + } + + std::map strideBySlot; + for (const InputElementDesc& element : m_inputLayoutDesc.elements) { + const uint32_t attributeSize = GetFormatSize(static_cast(element.format)); + strideBySlot[element.inputSlot] = (std::max)(strideBySlot[element.inputSlot], element.alignedByteOffset + attributeSize); + } + + std::vector bindings; + bindings.reserve(strideBySlot.size()); + for (const auto& entry : strideBySlot) { + VkVertexInputBindingDescription binding = {}; + binding.binding = entry.first; + binding.stride = entry.second; + binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + bindings.push_back(binding); + } + + std::vector attributes; + attributes.reserve(m_inputLayoutDesc.elements.size()); + for (uint32_t location = 0; location < m_inputLayoutDesc.elements.size(); ++location) { + const InputElementDesc& element = m_inputLayoutDesc.elements[location]; + VkVertexInputAttributeDescription attribute = {}; + attribute.location = location; + attribute.binding = element.inputSlot; + attribute.format = ToVulkanFormat(static_cast(element.format)); + attribute.offset = element.alignedByteOffset; + attributes.push_back(attribute); + } + + VkPipelineShaderStageCreateInfo shaderStages[2] = {}; + shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + shaderStages[0].module = vertexModule; + shaderStages[0].pName = vertexEntryPoint.c_str(); + + shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + shaderStages[1].module = fragmentModule; + shaderStages[1].pName = fragmentEntryPoint.c_str(); + + VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = static_cast(bindings.size()); + vertexInputInfo.pVertexBindingDescriptions = bindings.data(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributes.size()); + vertexInputInfo.pVertexAttributeDescriptions = attributes.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = ToVulkanPrimitiveTopology(static_cast(m_topologyType)); + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState = {}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = m_rasterizerDesc.depthClipEnable ? VK_FALSE : VK_TRUE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = ToVulkanPolygonMode(static_cast(m_rasterizerDesc.fillMode)); + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = ToVulkanCullMode(static_cast(m_rasterizerDesc.cullMode)); + rasterizer.frontFace = ToVulkanFrontFace(static_cast(m_rasterizerDesc.frontFace)); + rasterizer.depthBiasEnable = m_rasterizerDesc.depthBias != 0 || m_rasterizerDesc.slopeScaledDepthBias != 0.0f; + rasterizer.depthBiasConstantFactor = static_cast(m_rasterizerDesc.depthBias); + rasterizer.depthBiasClamp = m_rasterizerDesc.depthBiasClamp; + rasterizer.depthBiasSlopeFactor = m_rasterizerDesc.slopeScaledDepthBias; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.rasterizationSamples = ToVulkanSampleCount(m_sampleCount); + multisampling.sampleShadingEnable = VK_FALSE; + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | + VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | + VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = m_blendDesc.blendEnable ? VK_TRUE : VK_FALSE; + colorBlendAttachment.srcColorBlendFactor = ToVulkanBlendFactor(static_cast(m_blendDesc.srcBlend)); + colorBlendAttachment.dstColorBlendFactor = ToVulkanBlendFactor(static_cast(m_blendDesc.dstBlend)); + colorBlendAttachment.colorBlendOp = ToVulkanBlendOp(static_cast(m_blendDesc.blendOp)); + colorBlendAttachment.srcAlphaBlendFactor = ToVulkanBlendFactor(static_cast(m_blendDesc.srcBlendAlpha)); + colorBlendAttachment.dstAlphaBlendFactor = ToVulkanBlendFactor(static_cast(m_blendDesc.dstBlendAlpha)); + colorBlendAttachment.alphaBlendOp = ToVulkanBlendOp(static_cast(m_blendDesc.blendOpAlpha)); + + VkPipelineColorBlendStateCreateInfo colorBlending = {}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + + VkPipelineDepthStencilStateCreateInfo depthStencil = {}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencil.depthTestEnable = m_depthStencilDesc.depthTestEnable ? VK_TRUE : VK_FALSE; + depthStencil.depthWriteEnable = m_depthStencilDesc.depthWriteEnable ? VK_TRUE : VK_FALSE; + depthStencil.depthCompareOp = ToVulkanCompareOp(static_cast(m_depthStencilDesc.depthFunc)); + depthStencil.depthBoundsTestEnable = m_depthStencilDesc.depthBoundsEnable ? VK_TRUE : VK_FALSE; + depthStencil.stencilTestEnable = m_depthStencilDesc.stencilEnable ? VK_TRUE : VK_FALSE; + + VkDynamicState dynamicStates[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState = {}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(std::size(dynamicStates)); + dynamicState.pDynamicStates = dynamicStates; + + VkGraphicsPipelineCreateInfo pipelineInfo = {}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = &depthStencil; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = m_pipelineLayout; + pipelineInfo.renderPass = m_renderPass; + pipelineInfo.subpass = 0; + + const bool success = vkCreateGraphicsPipelines( + m_device, + VK_NULL_HANDLE, + 1, + &pipelineInfo, + nullptr, + &m_pipeline) == VK_SUCCESS; + + vkDestroyShaderModule(m_device, fragmentModule, nullptr); + vkDestroyShaderModule(m_device, vertexModule, nullptr); + + if (!success) { + Shutdown(); + return false; + } + + return true; +} + +void VulkanPipelineState::SetInputLayout(const InputLayoutDesc& layout) { + m_inputLayoutDesc = layout; +} + +void VulkanPipelineState::SetRasterizerState(const RasterizerDesc& state) { + m_rasterizerDesc = state; +} + +void VulkanPipelineState::SetBlendState(const BlendDesc& state) { + m_blendDesc = state; +} + +void VulkanPipelineState::SetDepthStencilState(const DepthStencilStateDesc& state) { + m_depthStencilDesc = state; +} + +void VulkanPipelineState::SetTopology(uint32_t topologyType) { + m_topologyType = topologyType; +} + +void VulkanPipelineState::SetRenderTargetFormats(uint32_t count, const uint32_t* formats, uint32_t depthFormat) { + m_renderTargetCount = count; + m_depthStencilFormat = depthFormat; + for (uint32_t i = 0; i < count && i < 8; ++i) { + m_renderTargetFormats[i] = formats[i]; + } +} + +void VulkanPipelineState::SetSampleCount(uint32_t count) { + m_sampleCount = count > 0 ? count : 1; +} + +void VulkanPipelineState::SetComputeShader(RHIShader* shader) { + (void)shader; +} + +PipelineStateHash VulkanPipelineState::GetHash() const { + PipelineStateHash hash = {}; + hash.topologyHash = m_topologyType; + hash.renderTargetHash = m_renderTargetCount ^ (m_renderTargetFormats[0] << 8); + return hash; +} + +void VulkanPipelineState::Shutdown() { + if (m_pipeline != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyPipeline(m_device, m_pipeline, nullptr); + m_pipeline = VK_NULL_HANDLE; + } + + if (m_renderPass != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyRenderPass(m_device, m_renderPass, nullptr); + m_renderPass = VK_NULL_HANDLE; + } + + if (m_pipelineLayout != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); + m_pipelineLayout = VK_NULL_HANDLE; + } + + m_deviceOwner = nullptr; + m_device = VK_NULL_HANDLE; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanResourceView.cpp b/engine/src/RHI/Vulkan/VulkanResourceView.cpp index d5628664..eac23bc8 100644 --- a/engine/src/RHI/Vulkan/VulkanResourceView.cpp +++ b/engine/src/RHI/Vulkan/VulkanResourceView.cpp @@ -1,5 +1,8 @@ #include "XCEngine/RHI/Vulkan/VulkanResourceView.h" +#include "XCEngine/RHI/Vulkan/VulkanBuffer.h" +#include "XCEngine/RHI/Vulkan/VulkanTexture.h" + namespace XCEngine { namespace RHI { @@ -32,6 +35,61 @@ bool VulkanResourceView::InitializeAsRenderTarget(VkDevice device, VulkanTexture 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; + } + + m_viewType = ResourceViewType::VertexBuffer; + m_dimension = ResourceViewDimension::Buffer; + m_format = Format::Unknown; + m_buffer = buffer; + m_bufferOffset = desc.bufferLocation; + m_bufferSize = static_cast(buffer->GetSize()); + m_bufferStride = desc.structureByteStride > 0 ? desc.structureByteStride : buffer->GetStride(); + return true; +} + +bool VulkanResourceView::InitializeAsIndexBuffer(VulkanBuffer* buffer, const ResourceViewDesc& desc) { + if (buffer == nullptr || buffer->GetBuffer() == VK_NULL_HANDLE) { + return false; + } + + m_viewType = ResourceViewType::IndexBuffer; + m_dimension = ResourceViewDimension::Buffer; + m_format = desc.format != 0 ? static_cast(desc.format) : Format::R32_UInt; + m_buffer = buffer; + m_bufferOffset = desc.bufferLocation; + m_bufferSize = static_cast(buffer->GetSize()); + m_bufferStride = buffer->GetStride(); + return true; +} + +void* VulkanResourceView::GetNativeHandle() { + if (m_imageView != VK_NULL_HANDLE) { + return m_imageView; + } + + return m_buffer != nullptr ? m_buffer->GetNativeHandle() : nullptr; +} + +bool VulkanResourceView::IsValid() const { + switch (m_viewType) { + case ResourceViewType::VertexBuffer: + case ResourceViewType::IndexBuffer: + return m_buffer != nullptr && m_buffer->GetBuffer() != VK_NULL_HANDLE; + case ResourceViewType::RenderTarget: + case ResourceViewType::DepthStencil: + return m_imageView != VK_NULL_HANDLE; + default: + return false; + } +} + +VkBuffer VulkanResourceView::GetBuffer() const { + return m_buffer != nullptr ? m_buffer->GetBuffer() : VK_NULL_HANDLE; +} + void VulkanResourceView::Shutdown() { if (m_imageView != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { vkDestroyImageView(m_device, m_imageView, nullptr); @@ -39,8 +97,12 @@ void VulkanResourceView::Shutdown() { m_imageView = VK_NULL_HANDLE; m_device = VK_NULL_HANDLE; m_texture = nullptr; + m_buffer = nullptr; m_format = Format::Unknown; m_dimension = ResourceViewDimension::Unknown; + m_bufferOffset = 0; + m_bufferSize = 0; + m_bufferStride = 0; } } // namespace RHI diff --git a/tests/RHI/integration/triangle/CMakeLists.txt b/tests/RHI/integration/triangle/CMakeLists.txt index e4693a38..1ead4bce 100644 --- a/tests/RHI/integration/triangle/CMakeLists.txt +++ b/tests/RHI/integration/triangle/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_triangle main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../fixtures/RHIIntegrationFixture.cpp @@ -33,6 +35,59 @@ target_link_libraries(rhi_integration_triangle 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 triangle shaders") + endif() + + set(TRIANGLE_VULKAN_VERTEX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/Res/Shader/triangle_vulkan.vert) + set(TRIANGLE_VULKAN_FRAGMENT_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/Res/Shader/triangle_vulkan.frag) + + add_custom_command(TARGET rhi_integration_triangle PRE_BUILD + COMMAND ${XCENGINE_GLSLANG_VALIDATOR} + -V + -S + vert + -o + $/triangle_vulkan.vert.spv + ${TRIANGLE_VULKAN_VERTEX_SOURCE} + COMMAND ${XCENGINE_GLSLANG_VALIDATOR} + -V + -S + frag + -o + $/triangle_vulkan.frag.spv + ${TRIANGLE_VULKAN_FRAGMENT_SOURCE} + VERBATIM) + target_link_libraries(rhi_integration_triangle PRIVATE Vulkan::Vulkan) + target_compile_definitions(rhi_integration_triangle PRIVATE XCENGINE_SUPPORT_VULKAN) +endif() + target_compile_definitions(rhi_integration_triangle PRIVATE UNICODE _UNICODE diff --git a/tests/RHI/integration/triangle/Res/Shader/triangle_vulkan.frag b/tests/RHI/integration/triangle/Res/Shader/triangle_vulkan.frag new file mode 100644 index 00000000..4a59a0cb --- /dev/null +++ b/tests/RHI/integration/triangle/Res/Shader/triangle_vulkan.frag @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) in vec4 vColor; +layout(location = 0) out vec4 fragColor; + +void main() { + fragColor = vColor; +} diff --git a/tests/RHI/integration/triangle/Res/Shader/triangle_vulkan.vert b/tests/RHI/integration/triangle/Res/Shader/triangle_vulkan.vert new file mode 100644 index 00000000..68b4c382 --- /dev/null +++ b/tests/RHI/integration/triangle/Res/Shader/triangle_vulkan.vert @@ -0,0 +1,11 @@ +#version 450 + +layout(location = 0) in vec4 aPosition; +layout(location = 1) in vec4 aColor; + +layout(location = 0) out vec4 vColor; + +void main() { + gl_Position = aPosition; + vColor = aColor; +} diff --git a/tests/RHI/integration/triangle/main.cpp b/tests/RHI/integration/triangle/main.cpp index bde2a773..cf166244 100644 --- a/tests/RHI/integration/triangle/main.cpp +++ b/tests/RHI/integration/triangle/main.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include @@ -76,8 +78,48 @@ void main() { } )"; +std::filesystem::path GetExecutableDirectory() { + char exePath[MAX_PATH] = {}; + DWORD length = GetModuleFileNameA(nullptr, exePath, MAX_PATH); + if (length == 0 || length >= MAX_PATH) { + return std::filesystem::current_path(); + } + + return std::filesystem::path(exePath).parent_path(); +} + +std::vector LoadBinaryFileRelative(const char* filename) { + const std::filesystem::path path = GetExecutableDirectory() / 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* GetScreenshotFilename(RHIType type) { - return type == RHIType::D3D12 ? "triangle_d3d12.ppm" : "triangle_opengl.ppm"; + switch (type) { + case RHIType::D3D12: + return "triangle_d3d12.ppm"; + case RHIType::OpenGL: + return "triangle_opengl.ppm"; + case RHIType::Vulkan: + return "triangle_vulkan.ppm"; + default: + return "triangle_unknown.ppm"; + } } int GetComparisonThreshold(RHIType type) { @@ -126,7 +168,7 @@ GraphicsPipelineDesc CreateTrianglePipelineDesc(RHIType type) { 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(kTriangleVertexShader, kTriangleVertexShader + strlen(kTriangleVertexShader)); desc.vertexShader.sourceLanguage = ShaderLanguage::GLSL; desc.vertexShader.profile = L"vs_4_30"; @@ -134,6 +176,14 @@ GraphicsPipelineDesc CreateTrianglePipelineDesc(RHIType type) { desc.fragmentShader.source.assign(kTriangleFragmentShader, kTriangleFragmentShader + strlen(kTriangleFragmentShader)); desc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL; desc.fragmentShader.profile = L"fs_4_30"; + } else if (type == RHIType::Vulkan) { + desc.vertexShader.source = LoadBinaryFileRelative("triangle_vulkan.vert.spv"); + desc.vertexShader.sourceLanguage = ShaderLanguage::SPIRV; + desc.vertexShader.entryPoint = L"main"; + + desc.fragmentShader.source = LoadBinaryFileRelative("triangle_vulkan.frag.spv"); + desc.fragmentShader.sourceLanguage = ShaderLanguage::SPIRV; + desc.fragmentShader.entryPoint = L"main"; } return desc; @@ -310,6 +360,9 @@ TEST_P(TriangleTest, RenderTriangle) { INSTANTIATE_TEST_SUITE_P(D3D12, TriangleTest, ::testing::Values(RHIType::D3D12)); INSTANTIATE_TEST_SUITE_P(OpenGL, TriangleTest, ::testing::Values(RHIType::OpenGL)); +#if defined(XCENGINE_SUPPORT_VULKAN) +INSTANTIATE_TEST_SUITE_P(Vulkan, TriangleTest, ::testing::Values(RHIType::Vulkan)); +#endif GTEST_API_ int main(int argc, char** argv) { Logger::Get().Initialize();