Files
XCEngine/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp

328 lines
11 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/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<OpenGLDevice*>(mDevice);
initResult = oglDevice->InitializeWithExistingWindow(mWindow);
}
ASSERT_TRUE(initResult);
CommandQueueDesc queueDesc = {};
queueDesc.queueType = static_cast<uint32_t>(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<uint32_t>(CommandQueueType::Direct);
mCommandList = mDevice->CreateCommandList(cmdDesc);
ASSERT_NE(mCommandList, nullptr);
if (GetParam() == RHIType::D3D12) {
auto* d3d12Device = static_cast<D3D12Device*>(mDevice);
auto* d3d12SwapChain = static_cast<D3D12SwapChain*>(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(bool includeDepthStencil) {
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<D3D12Texture*>(mSwapChain->GetCurrentBackBuffer());
D3D12CommandList* d3d12CmdList = static_cast<D3D12CommandList*>(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, includeDepthStencil ? mDSV : nullptr);
Log("[TEST] SetRenderTargetForClear: done");
} else {
Log("[TEST] SetRenderTargetForClear: skipped - condition failed");
}
}
}
void RHIIntegrationFixture::EndRender() {
if (GetParam() == RHIType::D3D12) {
D3D12Texture* backBuffer = static_cast<D3D12Texture*>(mSwapChain->GetCurrentBackBuffer());
D3D12CommandList* d3d12CmdList = static_cast<D3D12CommandList*>(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<int>(threshold));
int result = system(cmd.c_str());
return result == 0;
}
} // namespace Integration
} // namespace RHI
} // namespace XCEngine