#include "RHIIntegrationFixture.h" #include #include #include #include #include "XCEngine/RHI/D3D12/D3D12Device.h" #include "XCEngine/RHI/D3D12/D3D12SwapChain.h" #include "XCEngine/RHI/D3D12/D3D12Texture.h" #include "XCEngine/RHI/D3D12/D3D12CommandList.h" #include "XCEngine/RHI/D3D12/D3D12DescriptorHeap.h" #include "XCEngine/RHI/D3D12/D3D12ResourceView.h" #include "XCEngine/RHI/RHIFence.h" #include "XCEngine/RHI/RHIScreenshot.h" #include "XCEngine/RHI/RHIEnums.h" using namespace XCEngine::Debug; using namespace XCEngine::Containers; namespace XCEngine { namespace RHI { namespace Integration { namespace { 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::filesystem::path ResolveRuntimePath(const char* path) { std::filesystem::path resolved(path); if (resolved.is_absolute()) { return resolved; } return GetExecutableDirectory() / resolved; } } // namespace void Log(const char* format, ...) { char buffer[1024]; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); Logger::Get().Debug(LogCategory::Rendering, String(buffer)); } void RHIIntegrationFixture::SetUp() { WNDCLASSEXW wc = {}; wc.cbSize = sizeof(WNDCLASSEXW); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = DefWindowProcW; wc.hInstance = GetModuleHandle(nullptr); wc.lpszClassName = L"XCEngine_RHI_Integration_Test"; RegisterClassExW(&wc); const int width = 1280; const int height = 720; RECT rect = { 0, 0, width, height }; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); mWindow = CreateWindowExW( 0, L"XCEngine_RHI_Integration_Test", L"RHI Integration Test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, GetModuleHandle(nullptr), NULL ); ASSERT_NE(mWindow, nullptr); mDevice = RHIFactory::CreateRHIDevice(GetParam()); ASSERT_NE(mDevice, nullptr); bool initResult = false; if (GetParam() == RHIType::D3D12) { RHIDeviceDesc desc = {}; desc.enableDebugLayer = false; desc.enableGPUValidation = false; initResult = mDevice->Initialize(desc); } else if (GetParam() == RHIType::OpenGL) { auto* oglDevice = static_cast(mDevice); initResult = oglDevice->InitializeWithExistingWindow(mWindow); } ASSERT_TRUE(initResult); CommandQueueDesc queueDesc = {}; queueDesc.queueType = static_cast(CommandQueueType::Direct); mCommandQueue = mDevice->CreateCommandQueue(queueDesc); ASSERT_NE(mCommandQueue, nullptr); SwapChainDesc swapDesc = {}; swapDesc.windowHandle = mWindow; swapDesc.width = width; swapDesc.height = height; swapDesc.bufferCount = 2; mSwapChain = mDevice->CreateSwapChain(swapDesc, mCommandQueue); ASSERT_NE(mSwapChain, nullptr); CommandListDesc cmdDesc = {}; cmdDesc.commandListType = static_cast(CommandQueueType::Direct); mCommandList = mDevice->CreateCommandList(cmdDesc); ASSERT_NE(mCommandList, nullptr); if (GetParam() == RHIType::D3D12) { auto* d3d12Device = static_cast(mDevice); auto* d3d12SwapChain = static_cast(mSwapChain); ID3D12Device* device = d3d12Device->GetDevice(); mRTVHeap = new D3D12DescriptorHeap(); mRTVHeap->Initialize(device, DescriptorHeapType::RTV, 2); mDSVHeap = new D3D12DescriptorHeap(); mDSVHeap->Initialize(device, DescriptorHeapType::DSV, 1); for (int i = 0; i < 2; i++) { D3D12Texture& backBuffer = d3d12SwapChain->GetBackBuffer(i); CPUDescriptorHandle rtvCpuHandle = mRTVHeap->GetCPUDescriptorHandle(i); D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr }; D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = D3D12ResourceView::CreateRenderTargetDesc(Format::R8G8B8A8_UNorm, D3D12_RTV_DIMENSION_TEXTURE2D); auto* rtv = new D3D12ResourceView(); rtv->InitializeAsRenderTarget(device, backBuffer.GetResource(), &rtvDesc, mRTVHeap, i); mRTVs.push_back(rtv); } mDepthStencilTexture = new D3D12Texture(); ASSERT_NE(mDepthStencilTexture, nullptr); ASSERT_TRUE(mDepthStencilTexture->InitializeDepthStencil(device, width, height)); D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = D3D12ResourceView::CreateDepthStencilDesc(Format::D24_UNorm_S8_UInt, D3D12_DSV_DIMENSION_TEXTURE2D); auto* d3d12DSV = new D3D12ResourceView(); d3d12DSV->InitializeAsDepthStencil(device, mDepthStencilTexture->GetResource(), &dsvDesc, mDSVHeap, 0); mDSV = d3d12DSV; } mScreenshot = RHIScreenshot::Create(GetParam()); ASSERT_NE(mScreenshot, nullptr); ShowWindow(mWindow, SW_SHOW); UpdateWindow(mWindow); } void RHIIntegrationFixture::BeginRender() { mCurrentBackBufferIndex = mSwapChain->GetCurrentBackBufferIndex(); Log("[TEST] BeginRender: backBufferIndex=%d", mCurrentBackBufferIndex); } void RHIIntegrationFixture::SetRenderTargetForClear() { if (GetParam() == RHIType::D3D12) { Log("[TEST] SetRenderTargetForClear: D3D12 branch, mRTVs.size=%d, index=%d", (int)mRTVs.size(), mCurrentBackBufferIndex); if (!mRTVs.empty() && mCurrentBackBufferIndex < mRTVs.size()) { D3D12Texture* backBuffer = static_cast(mSwapChain->GetCurrentBackBuffer()); D3D12CommandList* d3d12CmdList = static_cast(mCommandList); Log("[TEST] SetRenderTargetForClear: calling TransitionBarrier"); d3d12CmdList->TransitionBarrier(backBuffer->GetResource(), ResourceStates::Present, ResourceStates::RenderTarget); RHIResourceView* rtv = mRTVs[mCurrentBackBufferIndex]; Log("[TEST] SetRenderTargetForClear: calling SetRenderTargets, rtv=%p", (void*)rtv); mCommandList->SetRenderTargets(1, &rtv, nullptr); Log("[TEST] SetRenderTargetForClear: done"); } else { Log("[TEST] SetRenderTargetForClear: skipped - condition failed"); } } } void RHIIntegrationFixture::EndRender() { if (GetParam() == RHIType::D3D12) { D3D12Texture* backBuffer = static_cast(mSwapChain->GetCurrentBackBuffer()); D3D12CommandList* d3d12CmdList = static_cast(mCommandList); d3d12CmdList->TransitionBarrier(backBuffer->GetResource(), ResourceStates::RenderTarget, ResourceStates::Present); } Log("[TEST] EndRender called"); } void RHIIntegrationFixture::TearDown() { if (mScreenshot) { mScreenshot->Shutdown(); delete mScreenshot; mScreenshot = nullptr; } if (GetParam() == RHIType::D3D12) { for (auto* rtv : mRTVs) { delete rtv; } mRTVs.clear(); if (mDSV) { delete mDSV; mDSV = nullptr; } if (mDepthStencilTexture) { mDepthStencilTexture->Shutdown(); delete mDepthStencilTexture; mDepthStencilTexture = nullptr; } if (mRTVHeap) { mRTVHeap->Shutdown(); delete mRTVHeap; mRTVHeap = nullptr; } if (mDSVHeap) { mDSVHeap->Shutdown(); delete mDSVHeap; mDSVHeap = nullptr; } } if (mCommandList) { mCommandList->Shutdown(); delete mCommandList; mCommandList = nullptr; } if (mCommandQueue) { mCommandQueue->Shutdown(); delete mCommandQueue; mCommandQueue = nullptr; } if (mSwapChain) { mSwapChain->Shutdown(); delete mSwapChain; mSwapChain = nullptr; } if (mDevice) { mDevice->Shutdown(); delete mDevice; mDevice = nullptr; } if (mWindow) { DestroyWindow(mWindow); mWindow = nullptr; } } void RHIIntegrationFixture::WaitForGPU() { if (mDevice == nullptr || mCommandQueue == nullptr) { return; } if (GetParam() == RHIType::D3D12) { FenceDesc fenceDesc = {}; fenceDesc.initialValue = 0; fenceDesc.flags = 0; auto* fence = mDevice->CreateFence(fenceDesc); if (fence) { mCommandQueue->Signal(fence, 1); fence->Wait(1); for (int i = 0; i < 100; i++) { if (fence->GetCompletedValue() >= 1) { break; } Sleep(10); } fence->Shutdown(); delete fence; } Sleep(100); } } bool RHIIntegrationFixture::TakeScreenshot(const char* filename) { if (!mScreenshot || !mDevice || !mSwapChain) { Log("[TEST] TakeScreenshot: failed - mScreenshot=%p, mDevice=%p, mSwapChain=%p", (void*)mScreenshot, (void*)mDevice, (void*)mSwapChain); return false; } const std::filesystem::path outputPath = ResolveRuntimePath(filename); const std::string outputPathString = outputPath.string(); Log("[TEST] TakeScreenshot: capturing to %s", outputPathString.c_str()); bool result = mScreenshot->Capture(mDevice, mSwapChain, outputPathString.c_str()); Log("[TEST] TakeScreenshot: result=%d", result); return result; } bool RHIIntegrationFixture::CompareWithGoldenTemplate(const char* outputPpm, const char* gtPpm, float threshold) { namespace fs = std::filesystem; fs::path exeDir = GetExecutableDirectory(); fs::path outputPath = ResolveRuntimePath(outputPpm); fs::path gtPath = ResolveRuntimePath(gtPpm); fs::path compareScriptPath = exeDir / "compare_ppm.py"; if (!fs::exists(outputPath)) { std::cerr << "Output file not found: " << outputPath << std::endl; return false; } if (!fs::exists(gtPath)) { std::cerr << "Golden template not found: " << gtPath << std::endl; return false; } if (!fs::exists(compareScriptPath)) { std::cerr << "Compare script not found: " << compareScriptPath << std::endl; return false; } std::string cmd = "python \"" + compareScriptPath.string() + "\" \"" + outputPath.string() + "\" \"" + gtPath.string() + "\" " + std::to_string(static_cast(threshold)); int result = system(cmd.c_str()); return result == 0; } } // namespace Integration } // namespace RHI } // namespace XCEngine