diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12Enums.h b/engine/include/XCEngine/RHI/D3D12/D3D12Enums.h index 9a1d2b22..26c30c75 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12Enums.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12Enums.h @@ -142,6 +142,7 @@ inline DXGI_FORMAT ToD3D12(Format format) { case Format::R32G32B32A32_Float: return DXGI_FORMAT_R32G32B32A32_FLOAT; case Format::R16_Float: return DXGI_FORMAT_R16_FLOAT; case Format::R32_Float: return DXGI_FORMAT_R32_FLOAT; + case Format::R32G32_Float: return DXGI_FORMAT_R32G32_FLOAT; case Format::D16_UNorm: return DXGI_FORMAT_D16_UNORM; case Format::D24_UNorm_S8_UInt: return DXGI_FORMAT_D24_UNORM_S8_UINT; case Format::D32_Float: return DXGI_FORMAT_D32_FLOAT; @@ -168,6 +169,7 @@ inline Format FromD3D12(DXGI_FORMAT format) { case DXGI_FORMAT_R32G32B32A32_FLOAT: return Format::R32G32B32A32_Float; case DXGI_FORMAT_R16_FLOAT: return Format::R16_Float; case DXGI_FORMAT_R32_FLOAT: return Format::R32_Float; + case DXGI_FORMAT_R32G32_FLOAT: return Format::R32G32_Float; case DXGI_FORMAT_D16_UNORM: return Format::D16_UNorm; case DXGI_FORMAT_D24_UNORM_S8_UINT: return Format::D24_UNorm_S8_UInt; case DXGI_FORMAT_D32_FLOAT: return Format::D32_Float; @@ -319,4 +321,4 @@ inline D3D12_COMMAND_LIST_TYPE ToD3D12(CommandQueueType type) { } } // namespace RHI -} // namespace XCEngine \ No newline at end of file +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12PipelineLayout.h b/engine/include/XCEngine/RHI/D3D12/D3D12PipelineLayout.h index 361f9eb1..a8c76f91 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12PipelineLayout.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12PipelineLayout.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "../RHIPipelineLayout.h" #include "D3D12RootSignature.h" @@ -36,7 +37,8 @@ private: D3D12Device* m_device; std::unordered_map m_registerToRootIndex; std::vector m_rootParameters; + std::vector m_descriptorRanges; }; } // namespace RHI -} // namespace XCEngine \ No newline at end of file +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12Sampler.h b/engine/include/XCEngine/RHI/D3D12/D3D12Sampler.h index ab59d631..c9c43814 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12Sampler.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12Sampler.h @@ -19,7 +19,7 @@ public: bool Initialize(ID3D12Device* device, const D3D12_SAMPLER_DESC& desc); void Shutdown() override; - D3D12_SAMPLER_DESC GetDesc() const { return m_desc; } + const D3D12_SAMPLER_DESC& GetDesc() const { return m_desc; } void* GetNativeHandle() override { return &m_desc; } unsigned int GetID() override { return m_id; } diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12Texture.h b/engine/include/XCEngine/RHI/D3D12/D3D12Texture.h index e7eac6b9..01579de2 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12Texture.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12Texture.h @@ -20,7 +20,8 @@ public: bool Initialize(ID3D12Device* device, const D3D12_RESOURCE_DESC& desc, D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON); bool InitializeFromExisting(ID3D12Resource* resource, bool ownsResource = false); bool InitializeFromData(ID3D12Device* device, ID3D12GraphicsCommandList* commandList, - const void* pixelData, uint32_t width, uint32_t height, DXGI_FORMAT format, uint32_t rowPitch = 0); + const void* pixelData, uint32_t width, uint32_t height, DXGI_FORMAT format, uint32_t rowPitch = 0, + ComPtr* uploadBuffer = nullptr); bool InitializeDepthStencil(ID3D12Device* device, uint32_t width, uint32_t height, DXGI_FORMAT format = DXGI_FORMAT_D24_UNORM_S8_UINT); void Shutdown() override; diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLEnums.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLEnums.h index 54d3806a..e57fe046 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLEnums.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLEnums.h @@ -126,6 +126,9 @@ inline void ToOpenGLFormat(OpenGLFormat fmt, GLint& internalFormat, GLenum& glFo case OpenGLFormat::RG8: internalFormat = GL_RG8; glFormat = GL_RG; glType = GL_UNSIGNED_BYTE; break; + case OpenGLFormat::RG32F: + internalFormat = GL_RG32F; glFormat = GL_RG; glType = GL_FLOAT; + break; case OpenGLFormat::RGBA8: internalFormat = GL_RGBA8; glFormat = GL_RGBA; glType = GL_UNSIGNED_BYTE; break; diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLTexture.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLTexture.h index dff8c430..e7ec41f4 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLTexture.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLTexture.h @@ -20,6 +20,7 @@ enum class OpenGLTextureType { enum class OpenGLFormat { R8, RG8, + RG32F, RGBA8, RGBA16F, RGBA32F, @@ -32,6 +33,7 @@ enum class OpenGLFormat { enum class OpenGLInternalFormat { R8 = 1, RG8 = 2, + RG32F = 13, RGBA8 = 4, RGBA16F = 11, RGBA32F = 16, diff --git a/engine/include/XCEngine/RHI/RHIEnums.h b/engine/include/XCEngine/RHI/RHIEnums.h index dffe2314..5a0b402f 100644 --- a/engine/include/XCEngine/RHI/RHIEnums.h +++ b/engine/include/XCEngine/RHI/RHIEnums.h @@ -314,7 +314,8 @@ enum class Format : uint32_t { BC6H_UF16, BC7_UNorm, R32G32B32A32_UInt, - R32_UInt + R32_UInt, + R32G32_Float }; enum class ResourceStates : uint32_t { diff --git a/engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp b/engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp index 6ea358b9..eb68d03b 100644 --- a/engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp +++ b/engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp @@ -37,6 +37,7 @@ bool D3D12DescriptorHeap::Initialize(ID3D12Device* device, DescriptorHeapType ty return false; } + m_device = device; m_type = type; m_numDescriptors = numDescriptors; m_shaderVisible = shaderVisible; @@ -47,6 +48,7 @@ bool D3D12DescriptorHeap::Initialize(ID3D12Device* device, DescriptorHeapType ty } void D3D12DescriptorHeap::Shutdown() { + m_device.Reset(); m_descriptorHeap.Reset(); m_allocatedSets.clear(); m_numDescriptors = 0; diff --git a/engine/src/RHI/D3D12/D3D12Device.cpp b/engine/src/RHI/D3D12/D3D12Device.cpp index 31588f93..85b08e31 100644 --- a/engine/src/RHI/D3D12/D3D12Device.cpp +++ b/engine/src/RHI/D3D12/D3D12Device.cpp @@ -74,6 +74,8 @@ uint32_t GetFormatBytesPerPixel(Format format) { return 8; case Format::R32_Float: return 4; + case Format::R32G32_Float: + return 8; case Format::R32G32B32A32_Float: return 16; default: @@ -390,6 +392,7 @@ RHITexture* D3D12Device::CreateTexture(const TextureDesc& desc, const void* init uploadCommandList.Reset(); auto* texture = new D3D12Texture(); + ComPtr uploadBuffer; if (!texture->InitializeFromData( m_device.Get(), uploadCommandList.GetCommandList(), @@ -397,7 +400,8 @@ RHITexture* D3D12Device::CreateTexture(const TextureDesc& desc, const void* init desc.width, desc.height, ToD3D12(format), - resolvedRowPitch)) { + resolvedRowPitch, + &uploadBuffer)) { delete texture; uploadCommandList.Shutdown(); uploadAllocator.Shutdown(); @@ -411,6 +415,7 @@ RHITexture* D3D12Device::CreateTexture(const TextureDesc& desc, const void* init ID3D12CommandList* commandLists[] = { uploadCommandList.GetCommandList() }; uploadQueue.ExecuteCommandListsInternal(1, commandLists); uploadQueue.WaitForIdle(); + uploadBuffer.Reset(); uploadCommandList.Shutdown(); uploadAllocator.Shutdown(); @@ -442,13 +447,13 @@ RHIShader* D3D12Device::CreateShader(const ShaderCompileDesc& desc) { RHISampler* D3D12Device::CreateSampler(const SamplerDesc& desc) { auto* sampler = new D3D12Sampler(); D3D12_SAMPLER_DESC d3d12Desc = {}; - d3d12Desc.Filter = static_cast(desc.filter); - d3d12Desc.AddressU = static_cast(desc.addressU); - d3d12Desc.AddressV = static_cast(desc.addressV); - d3d12Desc.AddressW = static_cast(desc.addressW); + d3d12Desc.Filter = ToD3D12(static_cast(desc.filter)); + d3d12Desc.AddressU = ToD3D12(static_cast(desc.addressU)); + d3d12Desc.AddressV = ToD3D12(static_cast(desc.addressV)); + d3d12Desc.AddressW = ToD3D12(static_cast(desc.addressW)); d3d12Desc.MipLODBias = desc.mipLodBias; d3d12Desc.MaxAnisotropy = desc.maxAnisotropy; - d3d12Desc.ComparisonFunc = static_cast(desc.comparisonFunc); + d3d12Desc.ComparisonFunc = ToD3D12(static_cast(desc.comparisonFunc)); d3d12Desc.BorderColor[0] = desc.borderColorR; d3d12Desc.BorderColor[1] = desc.borderColorG; d3d12Desc.BorderColor[2] = desc.borderColorB; @@ -735,7 +740,7 @@ RHIResourceView* D3D12Device::CreateShaderResourceView(RHITexture* texture, cons srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; auto heap = std::make_unique(); - if (!heap->Initialize(m_device.Get(), DescriptorHeapType::CBV_SRV_UAV, 1, true)) { + if (!heap->Initialize(m_device.Get(), DescriptorHeapType::CBV_SRV_UAV, 1, false)) { delete view; return nullptr; } @@ -756,7 +761,7 @@ RHIResourceView* D3D12Device::CreateUnorderedAccessView(RHITexture* texture, con uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; auto heap = std::make_unique(); - if (!heap->Initialize(m_device.Get(), DescriptorHeapType::CBV_SRV_UAV, 1, true)) { + if (!heap->Initialize(m_device.Get(), DescriptorHeapType::CBV_SRV_UAV, 1, false)) { delete view; return nullptr; } diff --git a/engine/src/RHI/D3D12/D3D12PipelineLayout.cpp b/engine/src/RHI/D3D12/D3D12PipelineLayout.cpp index abcc4948..596a0492 100644 --- a/engine/src/RHI/D3D12/D3D12PipelineLayout.cpp +++ b/engine/src/RHI/D3D12/D3D12PipelineLayout.cpp @@ -24,8 +24,20 @@ bool D3D12PipelineLayout::InitializeInternal(D3D12Device* device, const RHIPipel m_device = device; m_rootParameters.clear(); + m_descriptorRanges.clear(); m_registerToRootIndex.clear(); + const uint32_t rootParameterCount = + (desc.constantBufferCount > 0 ? 1u : 0u) + + (desc.textureCount > 0 ? 1u : 0u) + + (desc.samplerCount > 0 ? 1u : 0u); + const uint32_t descriptorRangeCount = + (desc.textureCount > 0 ? 1u : 0u) + + (desc.samplerCount > 0 ? 1u : 0u); + + m_rootParameters.reserve(rootParameterCount); + m_descriptorRanges.reserve(descriptorRangeCount); + uint32_t rootIndex = 0; if (desc.constantBufferCount > 0) { @@ -39,9 +51,10 @@ bool D3D12PipelineLayout::InitializeInternal(D3D12Device* device, const RHIPipel } if (desc.textureCount > 0) { - D3D12_DESCRIPTOR_RANGE range = D3D12RootSignature::CreateDescriptorRange( - D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, desc.textureCount, 0); - D3D12_ROOT_PARAMETER param = D3D12RootSignature::CreateDescriptorTable(1, &range, ShaderVisibility::All); + m_descriptorRanges.push_back(D3D12RootSignature::CreateDescriptorRange( + D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, desc.textureCount, 0)); + D3D12_ROOT_PARAMETER param = D3D12RootSignature::CreateDescriptorTable( + 1, &m_descriptorRanges.back(), ShaderVisibility::All); m_rootParameters.push_back(param); for (uint32_t i = 0; i < desc.textureCount; ++i) { m_registerToRootIndex[100 + i] = rootIndex; @@ -50,9 +63,10 @@ bool D3D12PipelineLayout::InitializeInternal(D3D12Device* device, const RHIPipel } if (desc.samplerCount > 0) { - D3D12_DESCRIPTOR_RANGE range = D3D12RootSignature::CreateDescriptorRange( - D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, desc.samplerCount, 0); - D3D12_ROOT_PARAMETER param = D3D12RootSignature::CreateDescriptorTable(1, &range, ShaderVisibility::All); + m_descriptorRanges.push_back(D3D12RootSignature::CreateDescriptorRange( + D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, desc.samplerCount, 0)); + D3D12_ROOT_PARAMETER param = D3D12RootSignature::CreateDescriptorTable( + 1, &m_descriptorRanges.back(), ShaderVisibility::All); m_rootParameters.push_back(param); for (uint32_t i = 0; i < desc.samplerCount; ++i) { m_registerToRootIndex[200 + i] = rootIndex; @@ -99,6 +113,7 @@ bool D3D12PipelineLayout::InitializeInternal(D3D12Device* device, const RHIPipel void D3D12PipelineLayout::Shutdown() { m_rootSignature.Reset(); m_rootParameters.clear(); + m_descriptorRanges.clear(); m_registerToRootIndex.clear(); m_device = nullptr; } @@ -116,4 +131,4 @@ bool D3D12PipelineLayout::HasRootParameter(uint32_t shaderRegister) const { } } // namespace RHI -} // namespace XCEngine \ No newline at end of file +} // namespace XCEngine diff --git a/engine/src/RHI/D3D12/D3D12Texture.cpp b/engine/src/RHI/D3D12/D3D12Texture.cpp index 7cc52822..c085da52 100644 --- a/engine/src/RHI/D3D12/D3D12Texture.cpp +++ b/engine/src/RHI/D3D12/D3D12Texture.cpp @@ -44,7 +44,8 @@ bool D3D12Texture::InitializeFromExisting(ID3D12Resource* resource, bool ownsRes } bool D3D12Texture::InitializeFromData(ID3D12Device* device, ID3D12GraphicsCommandList* commandList, - const void* pixelData, uint32_t width, uint32_t height, DXGI_FORMAT format, uint32_t rowPitch) { + const void* pixelData, uint32_t width, uint32_t height, DXGI_FORMAT format, uint32_t rowPitch, + ComPtr* uploadBuffer) { D3D12_RESOURCE_DESC textureDesc = {}; textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; @@ -84,7 +85,7 @@ bool D3D12Texture::InitializeFromData(ID3D12Device* device, ID3D12GraphicsComman device->GetCopyableFootprints(&textureDesc, 0, 1, 0, &subresourceFootprint, &rowUsed, &rowSizeInBytes, &memorySizeUsed); - ID3D12Resource* tempBufferObject = nullptr; + ComPtr tempBufferObject; D3D12_HEAP_PROPERTIES d3dTempHeapProperties = {}; d3dTempHeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD; @@ -107,7 +108,7 @@ bool D3D12Texture::InitializeFromData(ID3D12Device* device, ID3D12GraphicsComman &d3d12TempResourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, - IID_PPV_ARGS(&tempBufferObject) + IID_PPV_ARGS(tempBufferObject.GetAddressOf()) ); if (FAILED(hResult)) { @@ -130,7 +131,7 @@ bool D3D12Texture::InitializeFromData(ID3D12Device* device, ID3D12GraphicsComman dst.SubresourceIndex = 0; D3D12_TEXTURE_COPY_LOCATION src = {}; - src.pResource = tempBufferObject; + src.pResource = tempBufferObject.Get(); src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; src.PlacedFootprint = subresourceFootprint; commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); @@ -145,7 +146,9 @@ bool D3D12Texture::InitializeFromData(ID3D12Device* device, ID3D12GraphicsComman commandList->ResourceBarrier(1, &barrier); m_state = ResourceStates::PixelShaderResource; - tempBufferObject->Release(); + if (uploadBuffer != nullptr) { + *uploadBuffer = tempBufferObject; + } return true; } diff --git a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp index 5bd5bb61..5888dade 100644 --- a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp +++ b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp @@ -40,6 +40,9 @@ bool GetOpenGLVertexAttribFormat(Format format, OpenGLVertexAttribFormat& attrib case Format::R32_Float: attributeFormat = { 1, GL_FLOAT, GL_FALSE, false }; return true; + case Format::R32G32_Float: + attributeFormat = { 2, GL_FLOAT, GL_FALSE, false }; + return true; case Format::R32G32B32A32_Float: attributeFormat = { 4, GL_FLOAT, GL_FALSE, false }; return true; diff --git a/engine/src/RHI/OpenGL/OpenGLDevice.cpp b/engine/src/RHI/OpenGL/OpenGLDevice.cpp index 609d3d5b..c75dd43a 100644 --- a/engine/src/RHI/OpenGL/OpenGLDevice.cpp +++ b/engine/src/RHI/OpenGL/OpenGLDevice.cpp @@ -61,6 +61,29 @@ std::string SourceToString(const ShaderCompileDesc& desc) { return std::string(reinterpret_cast(desc.source.data()), desc.source.size()); } +OpenGLFormat ToOpenGLTextureFormat(Format format) { + switch (format) { + case Format::R8_UNorm: + return OpenGLFormat::R8; + case Format::R8G8_UNorm: + return OpenGLFormat::RG8; + case Format::R32G32_Float: + return OpenGLFormat::RG32F; + case Format::R8G8B8A8_UNorm: + return OpenGLFormat::RGBA8; + case Format::R16G16B16A16_Float: + return OpenGLFormat::RGBA16F; + case Format::R32G32B32A32_Float: + return OpenGLFormat::RGBA32F; + case Format::D24_UNorm_S8_UInt: + return OpenGLFormat::Depth24Stencil8; + case Format::D32_Float: + return OpenGLFormat::Depth32F; + default: + return OpenGLFormat::RGBA8; + } +} + } // namespace OpenGLDevice::OpenGLDevice() @@ -338,19 +361,7 @@ RHITexture* OpenGLDevice::CreateTexture(const TextureDesc& desc) { break; } - OpenGLFormat format = OpenGLFormat::RGBA8; - switch (desc.format) { - case 1: format = OpenGLFormat::R8; break; - case 2: format = OpenGLFormat::RG8; break; - case 3: format = OpenGLFormat::RGBA8; break; - case 4: format = OpenGLFormat::RGBA16F; break; - case 5: format = OpenGLFormat::RGBA32F; break; - case 6: format = OpenGLFormat::RGBA16F; break; - case 7: format = OpenGLFormat::RGBA32F; break; - case 8: format = OpenGLFormat::Depth24Stencil8; break; - case 9: format = OpenGLFormat::Depth32F; break; - default: format = OpenGLFormat::RGBA8; break; - } + OpenGLFormat format = ToOpenGLTextureFormat(static_cast(desc.format)); texture->Initialize(type, desc.width, desc.height, desc.depth, desc.mipLevels, format, nullptr); texture->SetFormat(static_cast(desc.format)); return texture; @@ -385,19 +396,7 @@ RHITexture* OpenGLDevice::CreateTexture(const TextureDesc& desc, const void* ini break; } - OpenGLFormat format = OpenGLFormat::RGBA8; - switch (desc.format) { - case 1: format = OpenGLFormat::R8; break; - case 2: format = OpenGLFormat::RG8; break; - case 3: format = OpenGLFormat::RGBA8; break; - case 4: format = OpenGLFormat::RGBA16F; break; - case 5: format = OpenGLFormat::RGBA32F; break; - case 6: format = OpenGLFormat::RGBA16F; break; - case 7: format = OpenGLFormat::RGBA32F; break; - case 8: format = OpenGLFormat::Depth24Stencil8; break; - case 9: format = OpenGLFormat::Depth32F; break; - default: format = OpenGLFormat::RGBA8; break; - } + OpenGLFormat format = ToOpenGLTextureFormat(static_cast(desc.format)); if (!texture->Initialize(type, desc.width, desc.height, desc.depth, desc.mipLevels, format, initialData)) { delete texture; diff --git a/tests/RHI/integration/CMakeLists.txt b/tests/RHI/integration/CMakeLists.txt index 862922b8..14bba7de 100644 --- a/tests/RHI/integration/CMakeLists.txt +++ b/tests/RHI/integration/CMakeLists.txt @@ -6,3 +6,4 @@ enable_testing() add_subdirectory(minimal) add_subdirectory(triangle) +add_subdirectory(quad) diff --git a/tests/RHI/integration/quad/CMakeLists.txt b/tests/RHI/integration/quad/CMakeLists.txt new file mode 100644 index 00000000..66f437fa --- /dev/null +++ b/tests/RHI/integration/quad/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.15) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +project(rhi_integration_quad) + +set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine) +set(PACKAGE_DIR ${CMAKE_SOURCE_DIR}/tests/opengl/package) + +get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE) + +add_executable(rhi_integration_quad + main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../fixtures/RHIIntegrationFixture.cpp + ${PACKAGE_DIR}/src/glad.c +) + +target_include_directories(rhi_integration_quad PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../fixtures + ${ENGINE_ROOT_DIR}/include + ${PACKAGE_DIR}/include + ${PROJECT_ROOT_DIR}/engine/src +) + +target_link_libraries(rhi_integration_quad PRIVATE + d3d12 + dxgi + d3dcompiler + winmm + opengl32 + XCEngine + GTest::gtest +) + +target_compile_definitions(rhi_integration_quad PRIVATE + UNICODE + _UNICODE + XCENGINE_SUPPORT_OPENGL + XCENGINE_SUPPORT_D3D12 +) + +add_custom_command(TARGET rhi_integration_quad POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/Res + $/Res + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_SOURCE_DIR}/tests/RHI/integration/compare_ppm.py + $/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm + $/GT.ppm + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${ENGINE_ROOT_DIR}/third_party/renderdoc/renderdoc.dll + $/ +) + +include(GoogleTest) +gtest_discover_tests(rhi_integration_quad) diff --git a/tests/RHI/integration/quad/GT.ppm b/tests/RHI/integration/quad/GT.ppm new file mode 100644 index 00000000..a8920605 Binary files /dev/null and b/tests/RHI/integration/quad/GT.ppm differ diff --git a/tests/RHI/integration/quad/Res/Image/earth.png b/tests/RHI/integration/quad/Res/Image/earth.png new file mode 100644 index 00000000..663081b5 Binary files /dev/null and b/tests/RHI/integration/quad/Res/Image/earth.png differ diff --git a/tests/RHI/integration/quad/main.cpp b/tests/RHI/integration/quad/main.cpp new file mode 100644 index 00000000..5a4e82e6 --- /dev/null +++ b/tests/RHI/integration/quad/main.cpp @@ -0,0 +1,521 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "../fixtures/RHIIntegrationFixture.h" +#include "XCEngine/Debug/ConsoleLogSink.h" +#include "XCEngine/Debug/Logger.h" +#include "XCEngine/RHI/RHIBuffer.h" +#include "XCEngine/RHI/RHIDescriptorPool.h" +#include "XCEngine/RHI/RHIDescriptorSet.h" +#include "XCEngine/RHI/RHIPipelineLayout.h" +#include "XCEngine/RHI/RHIPipelineState.h" +#include "XCEngine/RHI/RHIResourceView.h" +#include "XCEngine/RHI/RHISampler.h" +#include "XCEngine/RHI/RHITexture.h" + +using namespace XCEngine::Debug; +using namespace XCEngine::RHI; +using namespace XCEngine::RHI::Integration; + +namespace { + +struct Vertex { + float pos[4]; + float uv[2]; +}; + +constexpr Vertex kQuadVertices[] = { + { { -0.5f, -0.5f, 0.0f, 1.0f }, { 0.0f, 1.0f } }, + { { -0.5f, 0.5f, 0.0f, 1.0f }, { 0.0f, 0.0f } }, + { { 0.5f, -0.5f, 0.0f, 1.0f }, { 1.0f, 1.0f } }, + { { 0.5f, 0.5f, 0.0f, 1.0f }, { 1.0f, 0.0f } }, +}; + +constexpr uint32_t kQuadIndices[] = { 0, 1, 2, 2, 1, 3 }; + +std::filesystem::path GetExecutableDirectory() { + char exePath[MAX_PATH] = {}; + const 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::filesystem::path ResolveRuntimePath(const char* relativePath) { + return GetExecutableDirectory() / relativePath; +} + +const char kQuadHlsl[] = R"( +Texture2D gTexture : register(t0); +SamplerState gSampler : register(s0); + +struct VSInput { + float4 position : POSITION; + float2 texcoord : TEXCOORD; +}; + +struct PSInput { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD; +}; + +PSInput MainVS(VSInput input) { + PSInput output; + output.position = input.position; + output.texcoord = input.texcoord; + return output; +} + +float4 MainPS(PSInput input) : SV_TARGET { + return gTexture.Sample(gSampler, input.texcoord); +} +)"; + +const char kQuadVertexShader[] = R"(#version 430 +layout(location = 0) in vec4 aPosition; +layout(location = 1) in vec2 aTexCoord; + +out vec2 vTexCoord; + +void main() { + gl_Position = aPosition; + vTexCoord = aTexCoord; +} +)"; + +const char kQuadFragmentShader[] = R"(#version 430 +layout(binding = 0) uniform sampler2D uTexture; + +in vec2 vTexCoord; + +layout(location = 0) out vec4 fragColor; + +void main() { + fragColor = texture(uTexture, vTexCoord); +} +)"; + +const char* GetScreenshotFilename(RHIType type) { + return type == RHIType::D3D12 ? "quad_d3d12.ppm" : "quad_opengl.ppm"; +} + +RHITexture* LoadQuadTexture(RHIDevice* device) { + const std::filesystem::path texturePath = ResolveRuntimePath("Res/Image/earth.png"); + const std::string texturePathString = texturePath.string(); + + stbi_set_flip_vertically_on_load(0); + + int width = 0; + int height = 0; + int channels = 0; + stbi_uc* pixels = stbi_load(texturePathString.c_str(), &width, &height, &channels, STBI_rgb_alpha); + if (pixels == nullptr) { + Log("[TEST] Failed to load quad texture: %s", texturePathString.c_str()); + return nullptr; + } + + TextureDesc textureDesc = {}; + textureDesc.width = static_cast(width); + textureDesc.height = static_cast(height); + textureDesc.depth = 1; + textureDesc.mipLevels = 1; + textureDesc.arraySize = 1; + textureDesc.format = static_cast(Format::R8G8B8A8_UNorm); + textureDesc.textureType = static_cast(TextureType::Texture2D); + textureDesc.sampleCount = 1; + textureDesc.sampleQuality = 0; + textureDesc.flags = 0; + + RHITexture* texture = device->CreateTexture( + textureDesc, + pixels, + static_cast(width) * static_cast(height) * 4, + static_cast(width) * 4); + stbi_image_free(pixels); + return texture; +} + +int GetComparisonThreshold(RHIType type) { + return type == RHIType::OpenGL ? 5 : 0; +} + +GraphicsPipelineDesc CreateQuadPipelineDesc(RHIType type, RHIPipelineLayout* pipelineLayout) { + GraphicsPipelineDesc desc = {}; + desc.pipelineLayout = pipelineLayout; + desc.topologyType = static_cast(PrimitiveTopologyType::Triangle); + desc.renderTargetFormats[0] = static_cast(Format::R8G8B8A8_UNorm); + desc.depthStencilFormat = static_cast(Format::Unknown); + desc.sampleCount = 1; + + desc.rasterizerState.fillMode = static_cast(FillMode::Solid); + desc.rasterizerState.cullMode = static_cast(CullMode::None); + desc.rasterizerState.frontFace = static_cast(FrontFace::CounterClockwise); + desc.rasterizerState.depthClipEnable = true; + + desc.depthStencilState.depthTestEnable = false; + desc.depthStencilState.depthWriteEnable = false; + desc.depthStencilState.stencilEnable = false; + + InputElementDesc position = {}; + position.semanticName = "POSITION"; + position.semanticIndex = 0; + position.format = static_cast(Format::R32G32B32A32_Float); + position.inputSlot = 0; + position.alignedByteOffset = 0; + desc.inputLayout.elements.push_back(position); + + InputElementDesc texcoord = {}; + texcoord.semanticName = "TEXCOORD"; + texcoord.semanticIndex = 0; + texcoord.format = static_cast(Format::R32G32_Float); + texcoord.inputSlot = 0; + texcoord.alignedByteOffset = sizeof(float) * 4; + desc.inputLayout.elements.push_back(texcoord); + + if (type == RHIType::D3D12) { + desc.vertexShader.source.assign(kQuadHlsl, kQuadHlsl + strlen(kQuadHlsl)); + desc.vertexShader.sourceLanguage = ShaderLanguage::HLSL; + desc.vertexShader.entryPoint = L"MainVS"; + desc.vertexShader.profile = L"vs_5_0"; + + desc.fragmentShader.source.assign(kQuadHlsl, kQuadHlsl + strlen(kQuadHlsl)); + desc.fragmentShader.sourceLanguage = ShaderLanguage::HLSL; + desc.fragmentShader.entryPoint = L"MainPS"; + desc.fragmentShader.profile = L"ps_5_0"; + } else { + desc.vertexShader.source.assign(kQuadVertexShader, kQuadVertexShader + strlen(kQuadVertexShader)); + desc.vertexShader.sourceLanguage = ShaderLanguage::GLSL; + desc.vertexShader.profile = L"vs_4_30"; + + desc.fragmentShader.source.assign(kQuadFragmentShader, kQuadFragmentShader + strlen(kQuadFragmentShader)); + desc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL; + desc.fragmentShader.profile = L"fs_4_30"; + } + + return desc; +} + +class QuadTest : public RHIIntegrationFixture { +protected: + void SetUp() override; + void TearDown() override; + void RenderFrame() override; + +private: + void InitializeQuadResources(); + void ShutdownQuadResources(); + + RHIBuffer* mVertexBuffer = nullptr; + RHIResourceView* mVertexBufferView = nullptr; + RHIBuffer* mIndexBuffer = nullptr; + RHIResourceView* mIndexBufferView = nullptr; + RHITexture* mTexture = nullptr; + RHIResourceView* mTextureView = nullptr; + RHISampler* mSampler = nullptr; + RHIDescriptorPool* mTexturePool = nullptr; + RHIDescriptorSet* mTextureSet = nullptr; + RHIDescriptorPool* mSamplerPool = nullptr; + RHIDescriptorSet* mSamplerSet = nullptr; + RHIPipelineLayout* mPipelineLayout = nullptr; + RHIPipelineState* mPipelineState = nullptr; +}; + +void QuadTest::SetUp() { + RHIIntegrationFixture::SetUp(); + InitializeQuadResources(); +} + +void QuadTest::TearDown() { + ShutdownQuadResources(); + RHIIntegrationFixture::TearDown(); +} + +void QuadTest::InitializeQuadResources() { + BufferDesc vertexBufferDesc = {}; + vertexBufferDesc.size = sizeof(kQuadVertices); + vertexBufferDesc.stride = sizeof(Vertex); + vertexBufferDesc.bufferType = static_cast(BufferType::Vertex); + vertexBufferDesc.flags = 0; + + mVertexBuffer = GetDevice()->CreateBuffer(vertexBufferDesc); + ASSERT_NE(mVertexBuffer, nullptr); + mVertexBuffer->SetData(kQuadVertices, sizeof(kQuadVertices)); + mVertexBuffer->SetStride(sizeof(Vertex)); + mVertexBuffer->SetBufferType(BufferType::Vertex); + + ResourceViewDesc vertexViewDesc = {}; + vertexViewDesc.dimension = ResourceViewDimension::Buffer; + vertexViewDesc.structureByteStride = sizeof(Vertex); + mVertexBufferView = GetDevice()->CreateVertexBufferView(mVertexBuffer, vertexViewDesc); + ASSERT_NE(mVertexBufferView, nullptr); + + BufferDesc indexBufferDesc = {}; + indexBufferDesc.size = sizeof(kQuadIndices); + indexBufferDesc.stride = sizeof(uint32_t); + indexBufferDesc.bufferType = static_cast(BufferType::Index); + indexBufferDesc.flags = 0; + + mIndexBuffer = GetDevice()->CreateBuffer(indexBufferDesc); + ASSERT_NE(mIndexBuffer, nullptr); + mIndexBuffer->SetData(kQuadIndices, sizeof(kQuadIndices)); + mIndexBuffer->SetStride(sizeof(uint32_t)); + mIndexBuffer->SetBufferType(BufferType::Index); + + ResourceViewDesc indexViewDesc = {}; + indexViewDesc.dimension = ResourceViewDimension::Buffer; + indexViewDesc.format = static_cast(Format::R32_UInt); + mIndexBufferView = GetDevice()->CreateIndexBufferView(mIndexBuffer, indexViewDesc); + ASSERT_NE(mIndexBufferView, nullptr); + + mTexture = LoadQuadTexture(GetDevice()); + ASSERT_NE(mTexture, nullptr); + + ResourceViewDesc textureViewDesc = {}; + textureViewDesc.format = static_cast(Format::R8G8B8A8_UNorm); + textureViewDesc.dimension = ResourceViewDimension::Texture2D; + textureViewDesc.mipLevel = 0; + mTextureView = GetDevice()->CreateShaderResourceView(mTexture, textureViewDesc); + ASSERT_NE(mTextureView, nullptr); + + SamplerDesc samplerDesc = {}; + samplerDesc.filter = static_cast(FilterMode::Linear); + samplerDesc.addressU = static_cast(TextureAddressMode::Clamp); + samplerDesc.addressV = static_cast(TextureAddressMode::Clamp); + samplerDesc.addressW = static_cast(TextureAddressMode::Clamp); + samplerDesc.mipLodBias = 0.0f; + samplerDesc.maxAnisotropy = 1; + samplerDesc.comparisonFunc = static_cast(ComparisonFunc::Always); + samplerDesc.borderColorR = 0.0f; + samplerDesc.borderColorG = 0.0f; + samplerDesc.borderColorB = 0.0f; + samplerDesc.borderColorA = 0.0f; + samplerDesc.minLod = 0.0f; + samplerDesc.maxLod = 1000.0f; + mSampler = GetDevice()->CreateSampler(samplerDesc); + ASSERT_NE(mSampler, nullptr); + + DescriptorPoolDesc texturePoolDesc = {}; + texturePoolDesc.type = DescriptorHeapType::CBV_SRV_UAV; + texturePoolDesc.descriptorCount = 1; + texturePoolDesc.shaderVisible = true; + mTexturePool = GetDevice()->CreateDescriptorPool(texturePoolDesc); + ASSERT_NE(mTexturePool, nullptr); + + DescriptorSetLayoutBinding textureBinding = {}; + textureBinding.binding = 0; + textureBinding.type = static_cast(DescriptorType::SRV); + textureBinding.count = 1; + + DescriptorSetLayoutDesc textureLayoutDesc = {}; + textureLayoutDesc.bindings = &textureBinding; + textureLayoutDesc.bindingCount = 1; + + mTextureSet = mTexturePool->AllocateSet(textureLayoutDesc); + ASSERT_NE(mTextureSet, nullptr); + mTextureSet->Update(0, mTextureView); + + DescriptorPoolDesc samplerPoolDesc = {}; + samplerPoolDesc.type = DescriptorHeapType::Sampler; + samplerPoolDesc.descriptorCount = 1; + samplerPoolDesc.shaderVisible = true; + mSamplerPool = GetDevice()->CreateDescriptorPool(samplerPoolDesc); + ASSERT_NE(mSamplerPool, nullptr); + + DescriptorSetLayoutBinding samplerBinding = {}; + samplerBinding.binding = 0; + samplerBinding.type = static_cast(DescriptorType::Sampler); + samplerBinding.count = 1; + + DescriptorSetLayoutDesc samplerLayoutDesc = {}; + samplerLayoutDesc.bindings = &samplerBinding; + samplerLayoutDesc.bindingCount = 1; + + mSamplerSet = mSamplerPool->AllocateSet(samplerLayoutDesc); + ASSERT_NE(mSamplerSet, nullptr); + mSamplerSet->UpdateSampler(0, mSampler); + + RHIPipelineLayoutDesc pipelineLayoutDesc = {}; + pipelineLayoutDesc.textureCount = 1; + pipelineLayoutDesc.samplerCount = 1; + mPipelineLayout = GetDevice()->CreatePipelineLayout(pipelineLayoutDesc); + ASSERT_NE(mPipelineLayout, nullptr); + + GraphicsPipelineDesc pipelineDesc = CreateQuadPipelineDesc(GetBackendType(), mPipelineLayout); + mPipelineState = GetDevice()->CreatePipelineState(pipelineDesc); + ASSERT_NE(mPipelineState, nullptr); + ASSERT_TRUE(mPipelineState->IsValid()); + + Log("[TEST] Quad resources initialized for backend %d", static_cast(GetBackendType())); +} + +void QuadTest::ShutdownQuadResources() { + if (mPipelineState != nullptr) { + mPipelineState->Shutdown(); + delete mPipelineState; + mPipelineState = nullptr; + } + + if (mPipelineLayout != nullptr) { + mPipelineLayout->Shutdown(); + delete mPipelineLayout; + mPipelineLayout = nullptr; + } + + if (mTextureSet != nullptr) { + mTextureSet->Shutdown(); + delete mTextureSet; + mTextureSet = nullptr; + } + + if (mSamplerSet != nullptr) { + mSamplerSet->Shutdown(); + delete mSamplerSet; + mSamplerSet = nullptr; + } + + if (mTexturePool != nullptr) { + mTexturePool->Shutdown(); + delete mTexturePool; + mTexturePool = nullptr; + } + + if (mSamplerPool != nullptr) { + mSamplerPool->Shutdown(); + delete mSamplerPool; + mSamplerPool = nullptr; + } + + if (mSampler != nullptr) { + mSampler->Shutdown(); + delete mSampler; + mSampler = nullptr; + } + + if (mTextureView != nullptr) { + mTextureView->Shutdown(); + delete mTextureView; + mTextureView = nullptr; + } + + if (mTexture != nullptr) { + mTexture->Shutdown(); + delete mTexture; + mTexture = nullptr; + } + + if (mVertexBufferView != nullptr) { + mVertexBufferView->Shutdown(); + delete mVertexBufferView; + mVertexBufferView = nullptr; + } + + if (mIndexBufferView != nullptr) { + mIndexBufferView->Shutdown(); + delete mIndexBufferView; + mIndexBufferView = nullptr; + } + + if (mVertexBuffer != nullptr) { + mVertexBuffer->Shutdown(); + delete mVertexBuffer; + mVertexBuffer = nullptr; + } + + if (mIndexBuffer != nullptr) { + mIndexBuffer->Shutdown(); + delete mIndexBuffer; + mIndexBuffer = nullptr; + } +} + +void QuadTest::RenderFrame() { + ASSERT_NE(mPipelineState, nullptr); + ASSERT_NE(mVertexBufferView, nullptr); + ASSERT_NE(mIndexBufferView, nullptr); + ASSERT_NE(mTextureSet, nullptr); + ASSERT_NE(mSamplerSet, nullptr); + ASSERT_NE(mPipelineLayout, nullptr); + + RHICommandList* cmdList = GetCommandList(); + RHICommandQueue* cmdQueue = GetCommandQueue(); + ASSERT_NE(cmdList, nullptr); + ASSERT_NE(cmdQueue, nullptr); + + cmdList->Reset(); + SetRenderTargetForClear(); + + Viewport viewport = { 0.0f, 0.0f, 1280.0f, 720.0f, 0.0f, 1.0f }; + Rect scissorRect = { 0, 0, 1280, 720 }; + cmdList->SetViewport(viewport); + cmdList->SetScissorRect(scissorRect); + cmdList->Clear(0.0f, 0.0f, 1.0f, 1.0f, 1); + + cmdList->SetPipelineState(mPipelineState); + RHIDescriptorSet* descriptorSets[] = { mTextureSet, mSamplerSet }; + cmdList->SetGraphicsDescriptorSets(0, 2, descriptorSets, mPipelineLayout); + cmdList->SetPrimitiveTopology(PrimitiveTopology::TriangleList); + + RHIResourceView* vertexBuffers[] = { mVertexBufferView }; + uint64_t offsets[] = { 0 }; + uint32_t strides[] = { sizeof(Vertex) }; + cmdList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides); + cmdList->SetIndexBuffer(mIndexBufferView, 0); + cmdList->DrawIndexed(static_cast(sizeof(kQuadIndices) / sizeof(kQuadIndices[0]))); + + EndRender(); + + cmdList->Close(); + void* cmdLists[] = { cmdList }; + cmdQueue->ExecuteCommandLists(1, cmdLists); +} + +TEST_P(QuadTest, RenderQuad) { + RHICommandQueue* cmdQueue = GetCommandQueue(); + RHISwapChain* swapChain = GetSwapChain(); + const int targetFrameCount = 30; + const char* screenshotFilename = GetScreenshotFilename(GetBackendType()); + const int comparisonThreshold = GetComparisonThreshold(GetBackendType()); + + for (int frameCount = 0; frameCount <= targetFrameCount; ++frameCount) { + if (frameCount > 0) { + cmdQueue->WaitForPreviousFrame(); + } + + Log("[TEST] Quad MainLoop: frame %d", frameCount); + BeginRender(); + RenderFrame(); + + if (frameCount >= targetFrameCount) { + cmdQueue->WaitForIdle(); + Log("[TEST] Quad MainLoop: frame %d reached, capturing %s", frameCount, screenshotFilename); + ASSERT_TRUE(TakeScreenshot(screenshotFilename)); + ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast(comparisonThreshold))); + Log("[TEST] Quad MainLoop: frame %d compare passed", frameCount); + break; + } + + swapChain->Present(0, 0); + } +} + +} // namespace + +INSTANTIATE_TEST_SUITE_P(D3D12, QuadTest, ::testing::Values(RHIType::D3D12)); +INSTANTIATE_TEST_SUITE_P(OpenGL, QuadTest, ::testing::Values(RHIType::OpenGL)); + +GTEST_API_ int main(int argc, char** argv) { + Logger::Get().Initialize(); + Logger::Get().AddSink(std::make_unique()); + Logger::Get().SetMinimumLevel(LogLevel::Debug); + + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/RHI/unit/test_views.cpp b/tests/RHI/unit/test_views.cpp index efd7619f..fd20a89e 100644 --- a/tests/RHI/unit/test_views.cpp +++ b/tests/RHI/unit/test_views.cpp @@ -122,6 +122,38 @@ TEST_P(RHITestFixture, Device_CreateUnorderedAccessView) { delete texture; } +TEST_P(RHITestFixture, Device_CreateShaderResourceView_FromInitialDataTexture) { + TextureDesc texDesc = {}; + texDesc.width = 1; + texDesc.height = 1; + texDesc.depth = 1; + texDesc.mipLevels = 1; + texDesc.arraySize = 1; + texDesc.format = static_cast(Format::R8G8B8A8_UNorm); + texDesc.textureType = static_cast(TextureType::Texture2D); + texDesc.sampleCount = 1; + texDesc.sampleQuality = 0; + texDesc.flags = 0; + + const uint8_t pixel[4] = { 255, 255, 255, 255 }; + RHITexture* texture = GetDevice()->CreateTexture(texDesc, pixel, sizeof(pixel), 4); + ASSERT_NE(texture, nullptr); + + ResourceViewDesc viewDesc = {}; + viewDesc.format = static_cast(Format::R8G8B8A8_UNorm); + viewDesc.dimension = ResourceViewDimension::Texture2D; + + RHIResourceView* srv = GetDevice()->CreateShaderResourceView(texture, viewDesc); + ASSERT_NE(srv, nullptr); + EXPECT_TRUE(srv->IsValid()); + EXPECT_EQ(srv->GetViewType(), ResourceViewType::ShaderResource); + + srv->Shutdown(); + delete srv; + texture->Shutdown(); + delete texture; +} + TEST_P(RHITestFixture, Device_CreateVertexBufferView) { BufferDesc bufferDesc = {}; bufferDesc.size = 256; diff --git a/tests/TEST_SPEC.md b/tests/TEST_SPEC.md index 54a45e8e..ad71fc40 100644 --- a/tests/TEST_SPEC.md +++ b/tests/TEST_SPEC.md @@ -65,6 +65,7 @@ cmake --build build --config Debug cmake --build build --config Debug --target rhi_unit_tests cmake --build build --config Debug --target rhi_integration_minimal cmake --build build --config Debug --target rhi_integration_triangle +cmake --build build --config Debug --target rhi_integration_quad ``` ### 3.3 运行 @@ -114,6 +115,7 @@ RHI 当前分成四类测试: | 抽象层单元测试 | `rhi_unit_tests` | | 抽象层集成测试 | `rhi_integration_minimal` | | 抽象层集成测试 | `rhi_integration_triangle` | +| 抽象层集成测试 | `rhi_integration_quad` | | D3D12 后端单元测试 | `rhi_d3d12_tests` | | OpenGL 后端单元测试 | `rhi_opengl_tests` | | D3D12 后端集成测试 | `d3d12_minimal_test` `d3d12_triangle_test` `d3d12_quad_test` `d3d12_sphere_test` | @@ -121,8 +123,7 @@ RHI 当前分成四类测试: 说明: -- 抽象层集成测试目前只正式包含 `minimal` 与 `triangle`。 -- `quad` 仍在向纯 RHI 抽象测试迁移中,未进入本列表。 +- 抽象层集成测试目前正式包含 `minimal`、`triangle` 与 `quad`。 ## 6. RHI 抽象层集成测试规范 @@ -141,6 +142,10 @@ tests/RHI/integration/ │ ├─ CMakeLists.txt │ ├─ GT.ppm │ └─ main.cpp +├─ quad/ +│ ├─ CMakeLists.txt +│ ├─ GT.ppm +│ └─ main.cpp ├─ compare_ppm.py └─ CMakeLists.txt ``` @@ -157,6 +162,8 @@ tests/RHI/integration/ - `minimal_opengl.ppm` - `triangle_d3d12.ppm` - `triangle_opengl.ppm` + - `quad_d3d12.ppm` + - `quad_opengl.ppm` 5. 两个后端都必须与同一张 `GT.ppm` 做比对。 6. 新测试如果暴露抽象层缺口,应先补 RHI,再补测试。