From 8bdb1f34c798aa8eab3e307c49d620147278c422 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 26 Mar 2026 22:28:11 +0800 Subject: [PATCH] Add renderer backpack scene integration test --- engine/src/RHI/D3D12/D3D12Device.cpp | 12 +- .../Pipelines/BuiltinForwardPipeline.cpp | 12 +- tests/Rendering/integration/CMakeLists.txt | 1 + .../integration/backpack_scene/CMakeLists.txt | 62 ++++ .../integration/backpack_scene/main.cpp | 314 ++++++++++++++++++ .../integration/textured_quad_scene/main.cpp | 41 ++- 6 files changed, 435 insertions(+), 7 deletions(-) create mode 100644 tests/Rendering/integration/backpack_scene/CMakeLists.txt create mode 100644 tests/Rendering/integration/backpack_scene/main.cpp diff --git a/engine/src/RHI/D3D12/D3D12Device.cpp b/engine/src/RHI/D3D12/D3D12Device.cpp index 4d4c2fa1..4f6507e6 100644 --- a/engine/src/RHI/D3D12/D3D12Device.cpp +++ b/engine/src/RHI/D3D12/D3D12Device.cpp @@ -155,7 +155,7 @@ void D3D12Device::Shutdown() { bool D3D12Device::CreateDXGIFactory(bool enableDebugLayer) { UINT dxgiFactoryFlags = 0; - if (enableDebugLayer) { + if (enableDebugLayer) { ID3D12Debug* debugController = nullptr; if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) { debugController->EnableDebugLayer(); @@ -342,7 +342,15 @@ RHITexture* D3D12Device::CreateTexture(const TextureDesc& desc) { } d3d12Desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; - if (texture->Initialize(m_device.Get(), d3d12Desc)) { + const bool isDepthFormat = format == Format::D24_UNorm_S8_UInt || + format == Format::D32_Float || + format == Format::D16_UNorm; + const D3D12_RESOURCE_STATES initialState = + isDepthFormat ? D3D12_RESOURCE_STATE_DEPTH_WRITE : D3D12_RESOURCE_STATE_COMMON; + if (texture->Initialize(m_device.Get(), d3d12Desc, initialState)) { + if (isDepthFormat) { + texture->SetState(ResourceStates::DepthWrite); + } return texture; } delete texture; diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp index 4fa2a3ba..2ef8a0a9 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp @@ -91,7 +91,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(RHI::RHIType backendType, RHI::RHIP pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); pipelineDesc.renderTargetCount = 1; pipelineDesc.renderTargetFormats[0] = static_cast(RHI::Format::R8G8B8A8_UNorm); - pipelineDesc.depthStencilFormat = static_cast(RHI::Format::Unknown); + pipelineDesc.depthStencilFormat = static_cast(RHI::Format::D24_UNorm_S8_UInt); pipelineDesc.sampleCount = 1; pipelineDesc.rasterizerState.fillMode = static_cast(RHI::FillMode::Solid); @@ -99,8 +99,9 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(RHI::RHIType backendType, RHI::RHIP pipelineDesc.rasterizerState.frontFace = static_cast(RHI::FrontFace::CounterClockwise); pipelineDesc.rasterizerState.depthClipEnable = true; - pipelineDesc.depthStencilState.depthTestEnable = false; - pipelineDesc.depthStencilState.depthWriteEnable = false; + pipelineDesc.depthStencilState.depthTestEnable = true; + pipelineDesc.depthStencilState.depthWriteEnable = true; + pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::Less); pipelineDesc.depthStencilState.stencilEnable = false; RHI::InputElementDesc position = {}; @@ -152,8 +153,10 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(RHI::RHIType backendType, RHI::RHIP const Resources::Texture* FindMaterialTexture(const Resources::Material& material) { static const char* kTextureNames[] = { + "baseColorTexture", "_BaseColorTexture", "_MainTex", + "albedoTexture", "mainTexture", "texture" }; @@ -592,7 +595,8 @@ bool BuiltinForwardPipeline::DrawVisibleObject( commandList->SetGraphicsDescriptorSets(kDescriptorFirstSet, 3, descriptorSets, m_pipelineLayout); if (cachedMesh->indexBufferView != nullptr && section.indexCount > 0) { - commandList->DrawIndexed(section.indexCount, 1, section.startIndex, static_cast(section.baseVertex), 0); + // MeshLoader flattens section indices into a single global index buffer. + commandList->DrawIndexed(section.indexCount, 1, section.startIndex, 0, 0); } else if (section.vertexCount > 0) { commandList->Draw(section.vertexCount, 1, section.baseVertex, 0); } diff --git a/tests/Rendering/integration/CMakeLists.txt b/tests/Rendering/integration/CMakeLists.txt index 08dffa7e..ecd0b961 100644 --- a/tests/Rendering/integration/CMakeLists.txt +++ b/tests/Rendering/integration/CMakeLists.txt @@ -3,3 +3,4 @@ cmake_minimum_required(VERSION 3.15) project(XCEngine_RenderingIntegrationTests) add_subdirectory(textured_quad_scene) +add_subdirectory(backpack_scene) diff --git a/tests/Rendering/integration/backpack_scene/CMakeLists.txt b/tests/Rendering/integration/backpack_scene/CMakeLists.txt new file mode 100644 index 00000000..89e02f27 --- /dev/null +++ b/tests/Rendering/integration/backpack_scene/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.15) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +project(rendering_integration_backpack_scene) + +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(rendering_integration_backpack_scene + main.cpp + ${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp + ${PACKAGE_DIR}/src/glad.c +) + +target_include_directories(rendering_integration_backpack_scene PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures + ${ENGINE_ROOT_DIR}/include + ${PACKAGE_DIR}/include + ${PROJECT_ROOT_DIR}/engine/src +) + +target_link_libraries(rendering_integration_backpack_scene PRIVATE + d3d12 + dxgi + d3dcompiler + winmm + opengl32 + XCEngine + GTest::gtest +) + +target_compile_definitions(rendering_integration_backpack_scene PRIVATE + UNICODE + _UNICODE + XCENGINE_SUPPORT_OPENGL + XCENGINE_SUPPORT_D3D12 +) + +add_custom_command(TARGET rendering_integration_backpack_scene POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/tests/RHI/integration/backpack/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_SOURCE_DIR}/tests/RHI/integration/backpack/GT.ppm + $/GT.ppm + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${ENGINE_ROOT_DIR}/third_party/renderdoc/renderdoc.dll + $/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${ENGINE_ROOT_DIR}/third_party/assimp/bin/assimp-vc143-mt.dll + $/ +) + +include(GoogleTest) +gtest_discover_tests(rendering_integration_backpack_scene) diff --git a/tests/Rendering/integration/backpack_scene/main.cpp b/tests/Rendering/integration/backpack_scene/main.cpp new file mode 100644 index 00000000..6cf8f7dd --- /dev/null +++ b/tests/Rendering/integration/backpack_scene/main.cpp @@ -0,0 +1,314 @@ +#define NOMINMAX +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h" + +#include +#include +#include +#include + +using namespace XCEngine::Components; +using namespace XCEngine::Debug; +using namespace XCEngine::Math; +using namespace XCEngine::Rendering; +using namespace XCEngine::Resources; +using namespace XCEngine::RHI; +using namespace XCEngine::RHI::Integration; + +namespace { + +constexpr const char* kD3D12Screenshot = "backpack_scene_d3d12.ppm"; +constexpr const char* kOpenGLScreenshot = "backpack_scene_opengl.ppm"; +constexpr uint32_t kFrameWidth = 1280; +constexpr uint32_t kFrameHeight = 720; + +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* GetScreenshotFilename(RHIType backendType) { + return backendType == RHIType::D3D12 ? kD3D12Screenshot : kOpenGLScreenshot; +} + +int GetComparisonThreshold(RHIType backendType) { + return backendType == RHIType::OpenGL ? 8 : 8; +} + +class BackpackSceneTest : public RHIIntegrationFixture { +protected: + void SetUp() override; + void TearDown() override; + void RenderFrame() override; + +private: + void LoadBackpackMesh(); + void BuildScene(); + RHIResourceView* GetCurrentBackBufferView(); + + std::unique_ptr mScene; + std::unique_ptr mSceneRenderer; + std::vector mBackBufferViews; + RHITexture* mDepthTexture = nullptr; + RHIResourceView* mDepthView = nullptr; + Mesh* mMesh = nullptr; +}; + +void BackpackSceneTest::SetUp() { + RHIIntegrationFixture::SetUp(); + + mSceneRenderer = std::make_unique(); + mScene = std::make_unique("BackpackScene"); + + LoadBackpackMesh(); + BuildScene(); + + TextureDesc depthDesc = {}; + depthDesc.width = kFrameWidth; + depthDesc.height = kFrameHeight; + depthDesc.depth = 1; + depthDesc.mipLevels = 1; + depthDesc.arraySize = 1; + depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); + depthDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + depthDesc.sampleCount = 1; + depthDesc.sampleQuality = 0; + depthDesc.flags = 0; + mDepthTexture = GetDevice()->CreateTexture(depthDesc); + ASSERT_NE(mDepthTexture, nullptr); + + ResourceViewDesc depthViewDesc = {}; + depthViewDesc.format = static_cast(Format::D24_UNorm_S8_UInt); + depthViewDesc.dimension = ResourceViewDimension::Texture2D; + depthViewDesc.mipLevel = 0; + mDepthView = GetDevice()->CreateDepthStencilView(mDepthTexture, depthViewDesc); + ASSERT_NE(mDepthView, nullptr); + + mBackBufferViews.resize(2, nullptr); +} + +void BackpackSceneTest::TearDown() { + mSceneRenderer.reset(); + + if (mDepthView != nullptr) { + mDepthView->Shutdown(); + delete mDepthView; + mDepthView = nullptr; + } + + if (mDepthTexture != nullptr) { + mDepthTexture->Shutdown(); + delete mDepthTexture; + mDepthTexture = nullptr; + } + + for (RHIResourceView*& backBufferView : mBackBufferViews) { + if (backBufferView != nullptr) { + backBufferView->Shutdown(); + delete backBufferView; + backBufferView = nullptr; + } + } + mBackBufferViews.clear(); + + mScene.reset(); + + if (mMesh != nullptr) { + for (size_t materialIndex = 0; materialIndex < mMesh->GetMaterials().Size(); ++materialIndex) { + delete mMesh->GetMaterials()[materialIndex]; + } + for (size_t textureIndex = 0; textureIndex < mMesh->GetTextures().Size(); ++textureIndex) { + delete mMesh->GetTextures()[textureIndex]; + } + delete mMesh; + mMesh = nullptr; + } + + RHIIntegrationFixture::TearDown(); +} + +void BackpackSceneTest::LoadBackpackMesh() { + const std::filesystem::path meshPath = ResolveRuntimePath("Res/models/backpack/backpack.obj"); + + MeshLoader loader; + MeshImportSettings settings; + settings.AddImportFlag(MeshImportFlags::FlipUVs); + + LoadResult result = loader.Load(meshPath.string().c_str(), &settings); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + mMesh = static_cast(result.resource); + ASSERT_NE(mMesh, nullptr); + ASSERT_TRUE(mMesh->IsValid()); + ASSERT_GT(mMesh->GetVertexCount(), 0u); + ASSERT_GT(mMesh->GetSections().Size(), 0u); +} + +void BackpackSceneTest::BuildScene() { + ASSERT_NE(mScene, nullptr); + ASSERT_NE(mMesh, nullptr); + + GameObject* cameraObject = mScene->CreateGameObject("MainCamera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetFieldOfView(45.0f); + camera->SetNearClipPlane(0.1f); + camera->SetFarClipPlane(100.0f); + camera->SetClearColor(XCEngine::Math::Color(0.05f, 0.05f, 0.08f, 1.0f)); + + const Vector3 boundsMin = mMesh->GetBounds().GetMin(); + const Vector3 boundsMax = mMesh->GetBounds().GetMax(); + const Vector3 center( + (boundsMin.x + boundsMax.x) * 0.5f, + (boundsMin.y + boundsMax.y) * 0.5f, + (boundsMin.z + boundsMax.z) * 0.5f); + const float sizeX = std::max(boundsMax.x - boundsMin.x, 0.001f); + const float sizeY = std::max(boundsMax.y - boundsMin.y, 0.001f); + const float sizeZ = std::max(boundsMax.z - boundsMin.z, 0.001f); + const float maxExtent = std::max(sizeX, std::max(sizeY, sizeZ)); + const float uniformScale = 1.8f / maxExtent; + + GameObject* root = mScene->CreateGameObject("BackpackRoot"); + root->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.08f, 3.0f)); + + GameObject* rotateY = mScene->CreateGameObject("BackpackRotateY"); + rotateY->SetParent(root, false); + rotateY->GetTransform()->SetLocalRotation(Quaternion::FromAxisAngle(Vector3::Up(), -0.35f)); + + GameObject* rotateX = mScene->CreateGameObject("BackpackRotateX"); + rotateX->SetParent(rotateY, false); + rotateX->GetTransform()->SetLocalRotation(Quaternion::FromAxisAngle(Vector3::Right(), -0.18f)); + + GameObject* scale = mScene->CreateGameObject("BackpackScale"); + scale->SetParent(rotateX, false); + scale->GetTransform()->SetLocalScale(Vector3(uniformScale, uniformScale, uniformScale)); + + GameObject* meshObject = mScene->CreateGameObject("BackpackMesh"); + meshObject->SetParent(scale, false); + meshObject->GetTransform()->SetLocalPosition(Vector3(-center.x, -center.y, -center.z)); + + auto* meshFilter = meshObject->AddComponent(); + auto* meshRenderer = meshObject->AddComponent(); + meshFilter->SetMesh(ResourceHandle(mMesh)); + meshRenderer->ClearMaterials(); +} + +RHIResourceView* BackpackSceneTest::GetCurrentBackBufferView() { + const int backBufferIndex = GetCurrentBackBufferIndex(); + if (backBufferIndex < 0) { + return nullptr; + } + + if (static_cast(backBufferIndex) >= mBackBufferViews.size()) { + mBackBufferViews.resize(static_cast(backBufferIndex) + 1, nullptr); + } + + if (mBackBufferViews[backBufferIndex] == nullptr) { + ResourceViewDesc viewDesc = {}; + viewDesc.format = static_cast(Format::R8G8B8A8_UNorm); + viewDesc.dimension = ResourceViewDimension::Texture2D; + viewDesc.mipLevel = 0; + mBackBufferViews[backBufferIndex] = GetDevice()->CreateRenderTargetView(GetCurrentBackBuffer(), viewDesc); + } + + return mBackBufferViews[backBufferIndex]; +} + +void BackpackSceneTest::RenderFrame() { + ASSERT_NE(mScene, nullptr); + ASSERT_NE(mSceneRenderer, nullptr); + + RHICommandList* commandList = GetCommandList(); + ASSERT_NE(commandList, nullptr); + + commandList->Reset(); + + RenderSurface surface(kFrameWidth, kFrameHeight); + surface.SetColorAttachment(GetCurrentBackBufferView()); + surface.SetDepthAttachment(mDepthView); + + RenderContext renderContext = {}; + renderContext.device = GetDevice(); + renderContext.commandList = commandList; + renderContext.commandQueue = GetCommandQueue(); + renderContext.backendType = GetBackendType(); + + ASSERT_TRUE(mSceneRenderer->Render(*mScene, nullptr, renderContext, surface)); + + commandList->Close(); + void* commandLists[] = { commandList }; + GetCommandQueue()->ExecuteCommandLists(1, commandLists); +} + +TEST_P(BackpackSceneTest, RenderBackpackScene) { + RHICommandQueue* commandQueue = 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) { + commandQueue->WaitForPreviousFrame(); + } + + BeginRender(); + RenderFrame(); + + if (frameCount >= targetFrameCount) { + commandQueue->WaitForIdle(); + ASSERT_TRUE(TakeScreenshot(screenshotFilename)); + ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast(comparisonThreshold))); + break; + } + + swapChain->Present(0, 0); + } +} + +} // namespace + +INSTANTIATE_TEST_SUITE_P(D3D12, BackpackSceneTest, ::testing::Values(RHIType::D3D12)); +INSTANTIATE_TEST_SUITE_P(OpenGL, BackpackSceneTest, ::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/Rendering/integration/textured_quad_scene/main.cpp b/tests/Rendering/integration/textured_quad_scene/main.cpp index 58679d13..074dcb97 100644 --- a/tests/Rendering/integration/textured_quad_scene/main.cpp +++ b/tests/Rendering/integration/textured_quad_scene/main.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h" @@ -35,6 +36,8 @@ namespace { constexpr const char* kD3D12Screenshot = "textured_quad_scene_d3d12.ppm"; constexpr const char* kOpenGLScreenshot = "textured_quad_scene_opengl.ppm"; +constexpr uint32_t kFrameWidth = 1280; +constexpr uint32_t kFrameHeight = 720; Mesh* CreateQuadMesh() { auto* mesh = new Mesh(); @@ -131,6 +134,8 @@ private: std::unique_ptr mScene; std::unique_ptr mSceneRenderer; std::vector mBackBufferViews; + RHITexture* mDepthTexture = nullptr; + RHIResourceView* mDepthView = nullptr; Mesh* mMesh = nullptr; Material* mMaterial = nullptr; Texture* mTexture = nullptr; @@ -158,12 +163,45 @@ void TexturedQuadSceneTest::SetUp() { meshFilter->SetMesh(ResourceHandle(mMesh)); meshRenderer->SetMaterial(0, ResourceHandle(mMaterial)); + TextureDesc depthDesc = {}; + depthDesc.width = kFrameWidth; + depthDesc.height = kFrameHeight; + depthDesc.depth = 1; + depthDesc.mipLevels = 1; + depthDesc.arraySize = 1; + depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); + depthDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + depthDesc.sampleCount = 1; + depthDesc.sampleQuality = 0; + depthDesc.flags = 0; + mDepthTexture = GetDevice()->CreateTexture(depthDesc); + ASSERT_NE(mDepthTexture, nullptr); + + ResourceViewDesc depthViewDesc = {}; + depthViewDesc.format = static_cast(Format::D24_UNorm_S8_UInt); + depthViewDesc.dimension = ResourceViewDimension::Texture2D; + depthViewDesc.mipLevel = 0; + mDepthView = GetDevice()->CreateDepthStencilView(mDepthTexture, depthViewDesc); + ASSERT_NE(mDepthView, nullptr); + mBackBufferViews.resize(2, nullptr); } void TexturedQuadSceneTest::TearDown() { mSceneRenderer.reset(); + if (mDepthView != nullptr) { + mDepthView->Shutdown(); + delete mDepthView; + mDepthView = nullptr; + } + + if (mDepthTexture != nullptr) { + mDepthTexture->Shutdown(); + delete mDepthTexture; + mDepthTexture = nullptr; + } + for (RHIResourceView*& backBufferView : mBackBufferViews) { if (backBufferView != nullptr) { backBufferView->Shutdown(); @@ -215,8 +253,9 @@ void TexturedQuadSceneTest::RenderFrame() { commandList->Reset(); - RenderSurface surface(1280, 720); + RenderSurface surface(kFrameWidth, kFrameHeight); surface.SetColorAttachment(GetCurrentBackBufferView()); + surface.SetDepthAttachment(mDepthView); RenderContext renderContext = {}; renderContext.device = GetDevice();