#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/RHIPipelineState.h" #include "XCEngine/RHI/RHIResourceView.h" using namespace XCEngine::Debug; using namespace XCEngine::RHI; using namespace XCEngine::RHI::Integration; namespace { struct Vertex { float pos[4]; float col[4]; }; constexpr Vertex kTriangleVertices[] = { { { 0.0f, 0.5f, 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } }, { { -0.5f, -0.5f, 0.0f, 1.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } }, { { 0.5f, -0.5f, 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }, }; const char kTriangleHlsl[] = R"( struct VSInput { float4 position : POSITION; float4 color : COLOR; }; struct PSInput { float4 position : SV_POSITION; float4 color : COLOR; }; PSInput MainVS(VSInput input) { PSInput output; output.position = input.position; output.color = input.color; return output; } float4 MainPS(PSInput input) : SV_TARGET { return input.color; } )"; const char kTriangleVertexShader[] = R"(#version 430 layout(location = 0) in vec4 aPosition; layout(location = 1) in vec4 aColor; out vec4 vColor; void main() { gl_Position = aPosition; vColor = aColor; } )"; const char kTriangleFragmentShader[] = R"(#version 430 in vec4 vColor; layout(location = 0) out vec4 fragColor; void main() { fragColor = vColor; } )"; const char* GetScreenshotFilename(RHIType type) { return type == RHIType::D3D12 ? "triangle_d3d12.ppm" : "triangle_opengl.ppm"; } int GetComparisonThreshold(RHIType type) { return type == RHIType::OpenGL ? 5 : 0; } GraphicsPipelineDesc CreateTrianglePipelineDesc(RHIType type) { GraphicsPipelineDesc desc = {}; 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 color = {}; color.semanticName = "COLOR"; color.semanticIndex = 0; color.format = static_cast(Format::R32G32B32A32_Float); color.inputSlot = 0; color.alignedByteOffset = sizeof(float) * 4; desc.inputLayout.elements.push_back(color); if (type == RHIType::D3D12) { desc.vertexShader.source.assign(kTriangleHlsl, kTriangleHlsl + strlen(kTriangleHlsl)); desc.vertexShader.sourceLanguage = ShaderLanguage::HLSL; desc.vertexShader.entryPoint = L"MainVS"; desc.vertexShader.profile = L"vs_5_0"; desc.fragmentShader.source.assign(kTriangleHlsl, kTriangleHlsl + strlen(kTriangleHlsl)); desc.fragmentShader.sourceLanguage = ShaderLanguage::HLSL; desc.fragmentShader.entryPoint = L"MainPS"; desc.fragmentShader.profile = L"ps_5_0"; } else { desc.vertexShader.source.assign(kTriangleVertexShader, kTriangleVertexShader + strlen(kTriangleVertexShader)); desc.vertexShader.sourceLanguage = ShaderLanguage::GLSL; desc.vertexShader.profile = L"vs_4_30"; desc.fragmentShader.source.assign(kTriangleFragmentShader, kTriangleFragmentShader + strlen(kTriangleFragmentShader)); desc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL; desc.fragmentShader.profile = L"fs_4_30"; } return desc; } class TriangleTest : public RHIIntegrationFixture { protected: void SetUp() override; void TearDown() override; void RenderFrame() override; private: void InitializeTriangleResources(); void ShutdownTriangleResources(); RHIBuffer* mVertexBuffer = nullptr; RHIResourceView* mVertexBufferView = nullptr; RHIPipelineState* mPipelineState = nullptr; }; void TriangleTest::SetUp() { RHIIntegrationFixture::SetUp(); InitializeTriangleResources(); } void TriangleTest::TearDown() { ShutdownTriangleResources(); RHIIntegrationFixture::TearDown(); } void TriangleTest::InitializeTriangleResources() { BufferDesc bufferDesc = {}; bufferDesc.size = sizeof(kTriangleVertices); bufferDesc.stride = sizeof(Vertex); bufferDesc.bufferType = static_cast(BufferType::Vertex); mVertexBuffer = GetDevice()->CreateBuffer(bufferDesc); ASSERT_NE(mVertexBuffer, nullptr); mVertexBuffer->SetData(kTriangleVertices, sizeof(kTriangleVertices)); mVertexBuffer->SetStride(sizeof(Vertex)); mVertexBuffer->SetBufferType(BufferType::Vertex); ResourceViewDesc viewDesc = {}; viewDesc.dimension = ResourceViewDimension::Buffer; viewDesc.structureByteStride = sizeof(Vertex); mVertexBufferView = GetDevice()->CreateVertexBufferView(mVertexBuffer, viewDesc); ASSERT_NE(mVertexBufferView, nullptr); GraphicsPipelineDesc pipelineDesc = CreateTrianglePipelineDesc(GetBackendType()); mPipelineState = GetDevice()->CreatePipelineState(pipelineDesc); ASSERT_NE(mPipelineState, nullptr); ASSERT_TRUE(mPipelineState->IsValid()); Log("[TEST] Triangle resources initialized for backend %d", static_cast(GetBackendType())); } void TriangleTest::ShutdownTriangleResources() { if (mPipelineState != nullptr) { mPipelineState->Shutdown(); delete mPipelineState; mPipelineState = nullptr; } if (mVertexBufferView != nullptr) { mVertexBufferView->Shutdown(); delete mVertexBufferView; mVertexBufferView = nullptr; } if (mVertexBuffer != nullptr) { mVertexBuffer->Shutdown(); delete mVertexBuffer; mVertexBuffer = nullptr; } } void TriangleTest::RenderFrame() { ASSERT_NE(mPipelineState, nullptr); ASSERT_NE(mVertexBufferView, 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); cmdList->SetPrimitiveTopology(PrimitiveTopology::TriangleList); RHIResourceView* vertexBuffers[] = { mVertexBufferView }; uint64_t offsets[] = { 0 }; uint32_t strides[] = { sizeof(Vertex) }; cmdList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides); cmdList->Draw(3); EndRender(); cmdList->Close(); void* cmdLists[] = { cmdList }; cmdQueue->ExecuteCommandLists(1, cmdLists); } TEST_P(TriangleTest, RenderTriangle) { 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] Triangle MainLoop: frame %d", frameCount); BeginRender(); RenderFrame(); if (frameCount >= targetFrameCount) { cmdQueue->WaitForIdle(); Log("[TEST] Triangle MainLoop: frame %d reached, capturing %s", frameCount, screenshotFilename); ASSERT_TRUE(TakeScreenshot(screenshotFilename)); ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast(comparisonThreshold))); Log("[TEST] Triangle MainLoop: frame %d compare passed", frameCount); break; } swapChain->Present(0, 0); } } } // namespace INSTANTIATE_TEST_SUITE_P(D3D12, TriangleTest, ::testing::Values(RHIType::D3D12)); INSTANTIATE_TEST_SUITE_P(OpenGL, TriangleTest, ::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(); }