From 6aa0e73a05459d98618575c93487389953ee8cc7 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 27 Mar 2026 20:21:36 +0800 Subject: [PATCH] Add Vulkan coverage to generic RHI unit tests --- .../XCEngine/RHI/Vulkan/VulkanCommon.h | 20 ++++++++++ .../include/XCEngine/RHI/Vulkan/VulkanFence.h | 2 +- .../XCEngine/RHI/Vulkan/VulkanPipelineState.h | 3 +- .../XCEngine/RHI/Vulkan/VulkanTexture.h | 4 +- engine/src/RHI/Vulkan/VulkanDevice.cpp | 11 ++++++ engine/src/RHI/Vulkan/VulkanPipelineState.cpp | 6 +++ engine/src/RHI/Vulkan/VulkanTexture.cpp | 4 ++ tests/RHI/unit/fixtures/RHITestFixture.cpp | 15 ++++++- tests/RHI/unit/test_pipeline_layout.cpp | 7 ++++ tests/RHI/unit/test_shader.cpp | 39 ++++++++++++++----- 10 files changed, 96 insertions(+), 15 deletions(-) diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h index 95878e90..22fae948 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommon.h @@ -71,8 +71,18 @@ inline bool TryResolveShaderTypeFromTarget(const char* target, ShaderType& type) inline VkFormat ToVulkanFormat(Format format) { switch (format) { + case Format::R8_UNorm: + return VK_FORMAT_R8_UNORM; + case Format::R8G8_UNorm: + return VK_FORMAT_R8G8_UNORM; case Format::R8G8B8A8_UNorm: return VK_FORMAT_R8G8B8A8_UNORM; + case Format::R16_UInt: + return VK_FORMAT_R16_UINT; + case Format::R16_Float: + return VK_FORMAT_R16_SFLOAT; + case Format::D16_UNorm: + return VK_FORMAT_D16_UNORM; case Format::D24_UNorm_S8_UInt: return VK_FORMAT_D24_UNORM_S8_UINT; case Format::D32_Float: @@ -115,9 +125,19 @@ inline uint32_t GetFormatSize(Format format) { inline Format ToRHIFormat(VkFormat format) { switch (format) { + case VK_FORMAT_R8_UNORM: + return Format::R8_UNorm; + case VK_FORMAT_R8G8_UNORM: + return Format::R8G8_UNorm; case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_B8G8R8A8_UNORM: return Format::R8G8B8A8_UNorm; + case VK_FORMAT_R16_UINT: + return Format::R16_UInt; + case VK_FORMAT_R16_SFLOAT: + return Format::R16_Float; + case VK_FORMAT_D16_UNORM: + return Format::D16_UNorm; case VK_FORMAT_D24_UNORM_S8_UINT: return Format::D24_UNorm_S8_UInt; case VK_FORMAT_D32_SFLOAT: diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanFence.h b/engine/include/XCEngine/RHI/Vulkan/VulkanFence.h index c15af9f4..a1200acf 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanFence.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanFence.h @@ -15,7 +15,7 @@ public: void Signal(uint64_t value) override { m_value = value; } void Wait(uint64_t value) override { if (m_value < value) m_value = value; } uint64_t GetCompletedValue() const override { return m_value; } - void* GetNativeHandle() override { return nullptr; } + void* GetNativeHandle() override { return &m_value; } private: uint64_t m_value = 0; diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h index 20453b3d..80a6df2f 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h @@ -33,7 +33,7 @@ public: RHIShader* GetComputeShader() const override { return m_computeShader; } bool HasComputeShader() const override { return m_computeShader != nullptr; } - bool IsValid() const override { return m_pipeline != VK_NULL_HANDLE; } + bool IsValid() const override { return m_isConfigured; } void EnsureValid() override; void Shutdown() override; @@ -70,6 +70,7 @@ private: uint32_t m_depthStencilFormat = 0; uint32_t m_sampleCount = 1; RHIShader* m_computeShader = nullptr; + bool m_isConfigured = false; }; } // namespace RHI diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h b/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h index 4bc39fc0..4acffe8e 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h @@ -26,6 +26,7 @@ public: VkDeviceMemory memory, uint32_t width, uint32_t height, + uint32_t depth, uint32_t mipLevels, Format format, TextureType textureType, @@ -35,7 +36,7 @@ public: uint32_t GetWidth() const override { return m_width; } uint32_t GetHeight() const override { return m_height; } - uint32_t GetDepth() const override { return 1; } + uint32_t GetDepth() const override { return m_depth; } uint32_t GetMipLevels() const override { return m_mipLevels; } Format GetFormat() const override { return m_format; } TextureType GetTextureType() const override { return m_textureType; } @@ -60,6 +61,7 @@ private: VkDeviceMemory m_memory = VK_NULL_HANDLE; uint32_t m_width = 0; uint32_t m_height = 0; + uint32_t m_depth = 1; uint32_t m_mipLevels = 1; Format m_format = Format::Unknown; TextureType m_textureType = TextureType::Texture2D; diff --git a/engine/src/RHI/Vulkan/VulkanDevice.cpp b/engine/src/RHI/Vulkan/VulkanDevice.cpp index 7f8ce9d5..6aa013c5 100644 --- a/engine/src/RHI/Vulkan/VulkanDevice.cpp +++ b/engine/src/RHI/Vulkan/VulkanDevice.cpp @@ -427,8 +427,10 @@ bool VulkanDevice::CreateLogicalDevice() { void VulkanDevice::QueryDeviceInfo() { VkPhysicalDeviceProperties properties = {}; VkPhysicalDeviceFeatures features = {}; + VkPhysicalDeviceMemoryProperties memoryProperties = {}; vkGetPhysicalDeviceProperties(m_physicalDevice, &properties); vkGetPhysicalDeviceFeatures(m_physicalDevice, &features); + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties); m_deviceInfo.description = WidenAscii(properties.deviceName); m_deviceInfo.vendor = ResolveVendorName(properties.vendorID); @@ -439,6 +441,14 @@ void VulkanDevice::QueryDeviceInfo() { m_deviceInfo.vendorId = properties.vendorID; m_deviceInfo.deviceId = properties.deviceID; m_deviceInfo.isSoftware = properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU; + for (uint32_t heapIndex = 0; heapIndex < memoryProperties.memoryHeapCount; ++heapIndex) { + const VkMemoryHeap& heap = memoryProperties.memoryHeaps[heapIndex]; + if ((heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) { + m_deviceInfo.dedicatedVideoMemory += heap.size; + } else { + m_deviceInfo.sharedSystemMemory += heap.size; + } + } m_capabilities.bSupportsGeometryShaders = features.geometryShader == VK_TRUE; m_capabilities.bSupportsTessellation = features.tessellationShader == VK_TRUE; @@ -580,6 +590,7 @@ RHITexture* VulkanDevice::CreateTexture(const TextureDesc& desc) { memory, desc.width, desc.height, + desc.depth > 0 ? desc.depth : 1u, imageInfo.mipLevels, format, static_cast(desc.textureType), diff --git a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp index 2187f50a..1a0e7fc8 100644 --- a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp +++ b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp @@ -66,6 +66,7 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin const bool hasVertexShader = HasShaderPayload(desc.vertexShader); const bool hasFragmentShader = HasShaderPayload(desc.fragmentShader); if (!hasVertexShader && !hasFragmentShader) { + m_isConfigured = true; return true; } @@ -79,6 +80,7 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin return false; } + m_isConfigured = true; return true; } @@ -362,6 +364,9 @@ void VulkanPipelineState::SetComputeShader(RHIShader* shader) { m_pipeline = VK_NULL_HANDLE; } m_computeShader = shader; + if (m_computeShader != nullptr && m_pipelineLayout != VK_NULL_HANDLE) { + m_isConfigured = true; + } } PipelineStateHash VulkanPipelineState::GetHash() const { @@ -425,6 +430,7 @@ void VulkanPipelineState::Shutdown() { m_device = VK_NULL_HANDLE; m_ownsPipelineLayout = false; m_computeShader = nullptr; + m_isConfigured = false; } } // namespace RHI diff --git a/engine/src/RHI/Vulkan/VulkanTexture.cpp b/engine/src/RHI/Vulkan/VulkanTexture.cpp index acbdba82..b157c3df 100644 --- a/engine/src/RHI/Vulkan/VulkanTexture.cpp +++ b/engine/src/RHI/Vulkan/VulkanTexture.cpp @@ -18,6 +18,7 @@ bool VulkanTexture::InitializeSwapChainImage( m_image = image; m_width = width; m_height = height; + m_depth = 1; m_mipLevels = 1; m_format = format; m_vkFormat = vkFormat; @@ -33,6 +34,7 @@ bool VulkanTexture::InitializeOwnedImage( VkDeviceMemory memory, uint32_t width, uint32_t height, + uint32_t depth, uint32_t mipLevels, Format format, TextureType textureType, @@ -46,6 +48,7 @@ bool VulkanTexture::InitializeOwnedImage( m_memory = memory; m_width = width; m_height = height; + m_depth = depth > 0 ? depth : 1u; m_mipLevels = mipLevels; m_format = format; m_vkFormat = vkFormat; @@ -69,6 +72,7 @@ void VulkanTexture::Shutdown() { m_device = VK_NULL_HANDLE; m_width = 0; m_height = 0; + m_depth = 1; m_mipLevels = 1; m_format = Format::Unknown; m_vkFormat = VK_FORMAT_UNDEFINED; diff --git a/tests/RHI/unit/fixtures/RHITestFixture.cpp b/tests/RHI/unit/fixtures/RHITestFixture.cpp index 582974c0..1e872d60 100644 --- a/tests/RHI/unit/fixtures/RHITestFixture.cpp +++ b/tests/RHI/unit/fixtures/RHITestFixture.cpp @@ -8,12 +8,18 @@ #include "XCEngine/RHI/D3D12/D3D12CommandAllocator.h" #include "XCEngine/RHI/D3D12/D3D12CommandList.h" #include "XCEngine/RHI/OpenGL/OpenGLDevice.h" +#if defined(XCENGINE_SUPPORT_VULKAN) +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#endif namespace XCEngine { namespace RHI { INSTANTIATE_TEST_SUITE_P(D3D12, RHITestFixture, ::testing::Values(RHIType::D3D12)); INSTANTIATE_TEST_SUITE_P(OpenGL, RHITestFixture, ::testing::Values(RHIType::OpenGL)); +#if defined(XCENGINE_SUPPORT_VULKAN) +INSTANTIATE_TEST_SUITE_P(Vulkan, RHITestFixture, ::testing::Values(RHIType::Vulkan)); +#endif void RHITestFixture::SetUpTestSuite() { } @@ -36,7 +42,7 @@ void RHITestFixture::SetUp() { CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); bool initResult = false; - if (GetParam() == RHIType::D3D12) { + if (GetParam() == RHIType::D3D12 || GetParam() == RHIType::Vulkan) { RHIDeviceDesc desc = {}; desc.enableDebugLayer = false; initResult = mDevice->Initialize(desc); @@ -66,14 +72,19 @@ void RHITestFixture::SetUp() { } void RHITestFixture::WaitForGPU() { - if (mDevice == nullptr || mCommandQueue == nullptr || mFence == nullptr) { + if (mDevice == nullptr || mCommandQueue == nullptr) { return; } if (GetParam() == RHIType::D3D12) { + if (mFence == nullptr) { + return; + } mFenceValue++; mCommandQueue->Signal(mFence, mFenceValue); mFence->Wait(mFenceValue); + } else { + mCommandQueue->WaitForIdle(); } } diff --git a/tests/RHI/unit/test_pipeline_layout.cpp b/tests/RHI/unit/test_pipeline_layout.cpp index 2f358689..b4474f25 100644 --- a/tests/RHI/unit/test_pipeline_layout.cpp +++ b/tests/RHI/unit/test_pipeline_layout.cpp @@ -1,6 +1,9 @@ #include "fixtures/RHITestFixture.h" #include "XCEngine/RHI/D3D12/D3D12PipelineLayout.h" #include "XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h" +#if defined(XCENGINE_SUPPORT_VULKAN) +#include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h" +#endif #include "XCEngine/RHI/RHIPipelineLayout.h" #include "XCEngine/RHI/RHIDescriptorSet.h" @@ -181,6 +184,10 @@ TEST_P(RHITestFixture, PipelineLayout_DeepCopiesSetLayoutsAndInfersCounts) { const RHIPipelineLayoutDesc* storedDesc = nullptr; if (GetBackendType() == RHIType::D3D12) { storedDesc = &static_cast(layout)->GetDesc(); +#if defined(XCENGINE_SUPPORT_VULKAN) + } else if (GetBackendType() == RHIType::Vulkan) { + storedDesc = &static_cast(layout)->GetDesc(); +#endif } else { storedDesc = &static_cast(layout)->GetDesc(); } diff --git a/tests/RHI/unit/test_shader.cpp b/tests/RHI/unit/test_shader.cpp index 290b7213..2cfc0fed 100644 --- a/tests/RHI/unit/test_shader.cpp +++ b/tests/RHI/unit/test_shader.cpp @@ -1,9 +1,24 @@ #include "fixtures/RHITestFixture.h" #include "XCEngine/RHI/RHIShader.h" #include +#include using namespace XCEngine::RHI; +namespace { + +std::wstring ResolveShaderPath(const wchar_t* relativePath) { + wchar_t exePath[MAX_PATH] = {}; + const DWORD length = GetModuleFileNameW(nullptr, exePath, MAX_PATH); + std::filesystem::path rootPath = length > 0 ? std::filesystem::path(exePath).parent_path() : std::filesystem::current_path(); + for (int i = 0; i < 5; ++i) { + rootPath = rootPath.parent_path(); + } + return (rootPath / relativePath).wstring(); +} + +} // namespace + TEST_P(RHITestFixture, Shader_Compile_EmptyDesc_ReturnsNullptr) { ShaderCompileDesc desc = {}; RHIShader* shader = GetDevice()->CreateShader(desc); @@ -13,7 +28,7 @@ TEST_P(RHITestFixture, Shader_Compile_EmptyDesc_ReturnsNullptr) { TEST_P(RHITestFixture, Shader_Compile_ValidVertexShader) { ShaderCompileDesc desc = {}; if (GetBackendType() == RHIType::D3D12) { - desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"; + desc.fileName = ResolveShaderPath(L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"); desc.entryPoint = L"MainVS"; desc.profile = L"vs_5_0"; } else { @@ -36,7 +51,7 @@ TEST_P(RHITestFixture, Shader_Compile_ValidVertexShader) { TEST_P(RHITestFixture, Shader_Compile_ValidFragmentShader) { ShaderCompileDesc desc = {}; if (GetBackendType() == RHIType::D3D12) { - desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"; + desc.fileName = ResolveShaderPath(L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"); desc.entryPoint = L"MainPS"; desc.profile = L"ps_5_0"; } else { @@ -59,7 +74,7 @@ TEST_P(RHITestFixture, Shader_Compile_ValidFragmentShader) { TEST_P(RHITestFixture, Shader_GetType_VertexShader) { ShaderCompileDesc desc = {}; if (GetBackendType() == RHIType::D3D12) { - desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"; + desc.fileName = ResolveShaderPath(L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"); desc.entryPoint = L"MainVS"; desc.profile = L"vs_5_0"; } else { @@ -80,7 +95,7 @@ TEST_P(RHITestFixture, Shader_GetType_VertexShader) { TEST_P(RHITestFixture, Shader_GetType_FragmentShader) { ShaderCompileDesc desc = {}; if (GetBackendType() == RHIType::D3D12) { - desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"; + desc.fileName = ResolveShaderPath(L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"); desc.entryPoint = L"MainPS"; desc.profile = L"ps_5_0"; } else { @@ -101,7 +116,7 @@ TEST_P(RHITestFixture, Shader_GetType_FragmentShader) { TEST_P(RHITestFixture, Shader_GetNativeHandle_ValidShader) { ShaderCompileDesc desc = {}; if (GetBackendType() == RHIType::D3D12) { - desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"; + desc.fileName = ResolveShaderPath(L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"); desc.entryPoint = L"MainVS"; desc.profile = L"vs_5_0"; } else { @@ -123,7 +138,7 @@ TEST_P(RHITestFixture, Shader_GetNativeHandle_ValidShader) { TEST_P(RHITestFixture, Shader_Shutdown_Invalidates) { ShaderCompileDesc desc = {}; if (GetBackendType() == RHIType::D3D12) { - desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"; + desc.fileName = ResolveShaderPath(L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"); desc.entryPoint = L"MainVS"; desc.profile = L"vs_5_0"; } else { @@ -145,11 +160,15 @@ TEST_P(RHITestFixture, Shader_Shutdown_Invalidates) { TEST_P(RHITestFixture, Shader_Compile_FromFile_ReturnsValidShader) { ShaderCompileDesc desc = {}; if (GetBackendType() == RHIType::D3D12) { - desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"; + desc.fileName = ResolveShaderPath(L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl"); desc.entryPoint = L"MainVS"; desc.profile = L"vs_5_0"; + } else if (GetBackendType() == RHIType::Vulkan) { + desc.fileName = ResolveShaderPath(L"tests/RHI/integration/triangle/Res/Shader/triangle_vulkan.vert"); + desc.entryPoint = L"main"; + desc.profile = L"vs"; } else { - desc.fileName = L"tests/RHI/OpenGL/integration/triangle/Res/Shader/triangle.vert"; + desc.fileName = ResolveShaderPath(L"tests/RHI/OpenGL/integration/triangle/Res/Shader/triangle.vert"); desc.entryPoint = L"main"; desc.profile = L"vs_4_30"; } @@ -165,11 +184,11 @@ TEST_P(RHITestFixture, Shader_Compile_FromFile_ReturnsValidShader) { TEST_P(RHITestFixture, Shader_Compile_MissingFile_ReturnsNullptr) { ShaderCompileDesc desc = {}; if (GetBackendType() == RHIType::D3D12) { - desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/does_not_exist.hlsl"; + desc.fileName = ResolveShaderPath(L"tests/RHI/D3D12/integration/quad/Res/Shader/does_not_exist.hlsl"); desc.entryPoint = L"MainVS"; desc.profile = L"vs_5_0"; } else { - desc.fileName = L"tests/RHI/OpenGL/integration/triangle/Res/Shader/does_not_exist.vert"; + desc.fileName = ResolveShaderPath(L"tests/RHI/OpenGL/integration/triangle/Res/Shader/does_not_exist.vert"); desc.entryPoint = L"main"; desc.profile = L"vs_4_30"; }