Files
XCEngine/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp
ssdfasd 238ebb50f4 test: Add RHI integration test framework
Add integration tests for RHI module:
- Add tests/RHI/integration/ directory with CMakeLists.txt
- Add RHIIntegrationFixture for shared test utilities
- Add minimal integration test (window creation, basic rendering)
- Add compare_ppm.py for image comparison
- Add run_integration_test.py test runner script

These integration tests verify the complete rendering pipeline
by comparing rendered output against ground truth PPM files.
2026-03-25 19:00:30 +08:00

188 lines
5.2 KiB
C++

#include "RHIIntegrationFixture.h"
#include <cstdlib>
#include <iostream>
#include <windows.h>
#include <filesystem>
#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/RHIFence.h"
#include "XCEngine/RHI/RHIScreenshot.h"
#include "XCEngine/RHI/RHIEnums.h"
namespace XCEngine {
namespace RHI {
namespace Integration {
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<OpenGLDevice*>(mDevice);
initResult = oglDevice->InitializeWithExistingWindow(mWindow);
}
ASSERT_TRUE(initResult);
SwapChainDesc swapDesc = {};
swapDesc.windowHandle = mWindow;
swapDesc.width = width;
swapDesc.height = height;
swapDesc.bufferCount = 2;
mSwapChain = mDevice->CreateSwapChain(swapDesc);
ASSERT_NE(mSwapChain, nullptr);
CommandQueueDesc queueDesc = {};
queueDesc.queueType = static_cast<uint32_t>(CommandQueueType::Direct);
mCommandQueue = mDevice->CreateCommandQueue(queueDesc);
ASSERT_NE(mCommandQueue, nullptr);
CommandListDesc cmdDesc = {};
cmdDesc.commandListType = static_cast<uint32_t>(CommandQueueType::Direct);
mCommandList = mDevice->CreateCommandList(cmdDesc);
ASSERT_NE(mCommandList, nullptr);
mScreenshot = RHIScreenshot::Create(GetParam());
ASSERT_NE(mScreenshot, nullptr);
ShowWindow(mWindow, SW_SHOW);
UpdateWindow(mWindow);
}
void RHIIntegrationFixture::BeginRender() {
mCurrentBackBufferIndex = mSwapChain->GetCurrentBackBufferIndex();
}
void RHIIntegrationFixture::EndRender() {
}
void RHIIntegrationFixture::TearDown() {
if (mScreenshot) {
mScreenshot->Shutdown();
delete mScreenshot;
mScreenshot = 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) {
return false;
}
return mScreenshot->Capture(mDevice, mSwapChain, filename);
}
bool RHIIntegrationFixture::CompareWithGoldenTemplate(const char* outputPpm, const char* gtPpm, float threshold) {
namespace fs = std::filesystem;
fs::path exeDir = fs::current_path();
fs::path outputPath = exeDir / outputPpm;
fs::path gtPath = exeDir / gtPpm;
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;
}
std::string cmd = "python compare_ppm.py \"" + outputPath.string() + "\" \"" + gtPath.string() + "\" " + std::to_string(static_cast<int>(threshold));
int result = system(cmd.c_str());
return result == 0;
}
} // namespace Integration
} // namespace RHI
} // namespace XCEngine