Add Vulkan triangle integration path

This commit is contained in:
2026-03-27 12:40:17 +08:00
parent fd0b19fd11
commit 79e7452245
15 changed files with 1260 additions and 40 deletions

View File

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

View File

@@ -0,0 +1,57 @@
#pragma once
#include "XCEngine/RHI/RHIBuffer.h"
#include "XCEngine/RHI/Vulkan/VulkanCommon.h"
#include <string>
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

View File

@@ -3,10 +3,13 @@
#include "XCEngine/RHI/RHICommandList.h"
#include "XCEngine/RHI/Vulkan/VulkanCommon.h"
#include <vector>
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<VkFramebuffer> m_transientFramebuffers;
};
} // namespace RHI

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,123 @@
#include "XCEngine/RHI/Vulkan/VulkanBuffer.h"
#include "XCEngine/RHI/Vulkan/VulkanDevice.h"
#include <cstring>
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<BufferType>(desc.bufferType);
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = static_cast<VkDeviceSize>(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<uint8_t*>(mappedData) + offset, data, size);
if (!wasMapped) {
Unmap();
}
}
} // namespace RHI
} // namespace XCEngine

View File

@@ -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 <vector>
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<VulkanResourceView*>(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<VulkanResourceView*>(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<VulkanPipelineState*>(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<uint32_t>(rect.right - rect.left);
m_scissor.extent.height = static_cast<uint32_t>(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;
if (count == 0 || buffers == nullptr) {
return;
}
std::vector<VkBuffer> nativeBuffers;
std::vector<VkDeviceSize> nativeOffsets;
nativeBuffers.reserve(count);
nativeOffsets.reserve(count);
for (uint32_t i = 0; i < count; ++i) {
auto* view = static_cast<VulkanResourceView*>(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<uint32_t>(nativeBuffers.size()),
nativeBuffers.data(),
nativeOffsets.data());
}
}
void VulkanCommandList::SetIndexBuffer(RHIResourceView* buffer, uint64_t offset) {
(void)buffer;
(void)offset;
auto* view = static_cast<VulkanResourceView*>(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<VulkanResourceView*>(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

View File

@@ -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 <algorithm>
#include <iterator>
@@ -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<BufferType>(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<VulkanBuffer*>(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<VulkanBuffer*>(buffer), desc)) {
return view;
}
delete view;
return nullptr;
}

View File

@@ -0,0 +1,384 @@
#include "XCEngine/RHI/Vulkan/VulkanPipelineState.h"
#include "XCEngine/RHI/Vulkan/VulkanDevice.h"
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <map>
#include <vector>
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<char>(ch));
}
return result;
}
bool LoadSpirvBytes(const ShaderCompileDesc& desc, std::vector<uint32_t>& words, std::string& entryPoint) {
entryPoint = NarrowAscii(desc.entryPoint);
if (entryPoint.empty()) {
entryPoint = "main";
}
if (desc.sourceLanguage != ShaderLanguage::SPIRV) {
return false;
}
std::vector<char> 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<size_t>(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<uint32_t>& 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<uint32_t> vertexWords;
std::vector<uint32_t> 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<Format>(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<uint32_t, uint32_t> strideBySlot;
for (const InputElementDesc& element : m_inputLayoutDesc.elements) {
const uint32_t attributeSize = GetFormatSize(static_cast<Format>(element.format));
strideBySlot[element.inputSlot] = (std::max)(strideBySlot[element.inputSlot], element.alignedByteOffset + attributeSize);
}
std::vector<VkVertexInputBindingDescription> 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<VkVertexInputAttributeDescription> 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<Format>(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<uint32_t>(bindings.size());
vertexInputInfo.pVertexBindingDescriptions = bindings.data();
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributes.size());
vertexInputInfo.pVertexAttributeDescriptions = attributes.data();
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = ToVulkanPrimitiveTopology(static_cast<PrimitiveTopologyType>(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<FillMode>(m_rasterizerDesc.fillMode));
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = ToVulkanCullMode(static_cast<CullMode>(m_rasterizerDesc.cullMode));
rasterizer.frontFace = ToVulkanFrontFace(static_cast<FrontFace>(m_rasterizerDesc.frontFace));
rasterizer.depthBiasEnable = m_rasterizerDesc.depthBias != 0 || m_rasterizerDesc.slopeScaledDepthBias != 0.0f;
rasterizer.depthBiasConstantFactor = static_cast<float>(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<BlendFactor>(m_blendDesc.srcBlend));
colorBlendAttachment.dstColorBlendFactor = ToVulkanBlendFactor(static_cast<BlendFactor>(m_blendDesc.dstBlend));
colorBlendAttachment.colorBlendOp = ToVulkanBlendOp(static_cast<BlendOp>(m_blendDesc.blendOp));
colorBlendAttachment.srcAlphaBlendFactor = ToVulkanBlendFactor(static_cast<BlendFactor>(m_blendDesc.srcBlendAlpha));
colorBlendAttachment.dstAlphaBlendFactor = ToVulkanBlendFactor(static_cast<BlendFactor>(m_blendDesc.dstBlendAlpha));
colorBlendAttachment.alphaBlendOp = ToVulkanBlendOp(static_cast<BlendOp>(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<ComparisonFunc>(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<uint32_t>(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

View File

@@ -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<uint32_t>(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<Format>(desc.format) : Format::R32_UInt;
m_buffer = buffer;
m_bufferOffset = desc.bufferLocation;
m_bufferSize = static_cast<uint32_t>(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

View File

@@ -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
$<TARGET_FILE_DIR:rhi_integration_triangle>/triangle_vulkan.vert.spv
${TRIANGLE_VULKAN_VERTEX_SOURCE}
COMMAND ${XCENGINE_GLSLANG_VALIDATOR}
-V
-S
frag
-o
$<TARGET_FILE_DIR:rhi_integration_triangle>/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

View File

@@ -0,0 +1,8 @@
#version 450
layout(location = 0) in vec4 vColor;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vColor;
}

View File

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

View File

@@ -2,6 +2,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <filesystem>
#include <fstream>
#include <gtest/gtest.h>
@@ -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<uint8_t> 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<uint8_t> bytes(static_cast<size_t>(size));
file.seekg(0, std::ios::beg);
if (!file.read(reinterpret_cast<char*>(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();