Implement render graph compiler and transient fullscreen execution

This commit is contained in:
2026-04-14 04:37:07 +08:00
parent 72d09a1c49
commit c98b41f6f4
15 changed files with 2161 additions and 112 deletions

View File

@@ -285,6 +285,98 @@ private:
XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {};
};
class MockNoopCommandList final : public XCEngine::RHI::RHICommandList {
public:
void Shutdown() override {}
void Reset() override {}
void Close() override {}
void TransitionBarrier(
XCEngine::RHI::RHIResourceView*,
XCEngine::RHI::ResourceStates,
XCEngine::RHI::ResourceStates) override {
}
void BeginRenderPass(
XCEngine::RHI::RHIRenderPass*,
XCEngine::RHI::RHIFramebuffer*,
const XCEngine::RHI::Rect&,
uint32_t,
const XCEngine::RHI::ClearValue*) override {
}
void EndRenderPass() override {}
void SetShader(XCEngine::RHI::RHIShader*) override {}
void SetPipelineState(XCEngine::RHI::RHIPipelineState*) override {}
void SetGraphicsDescriptorSets(
uint32_t,
uint32_t,
XCEngine::RHI::RHIDescriptorSet**,
XCEngine::RHI::RHIPipelineLayout*) override {
}
void SetComputeDescriptorSets(
uint32_t,
uint32_t,
XCEngine::RHI::RHIDescriptorSet**,
XCEngine::RHI::RHIPipelineLayout*) override {
}
void SetPrimitiveTopology(XCEngine::RHI::PrimitiveTopology) override {}
void SetViewport(const XCEngine::RHI::Viewport&) override {}
void SetViewports(uint32_t, const XCEngine::RHI::Viewport*) override {}
void SetScissorRect(const XCEngine::RHI::Rect&) override {}
void SetScissorRects(uint32_t, const XCEngine::RHI::Rect*) override {}
void SetRenderTargets(
uint32_t,
XCEngine::RHI::RHIResourceView**,
XCEngine::RHI::RHIResourceView*) override {
}
void SetStencilRef(uint8_t) override {}
void SetBlendFactor(const float[4]) override {}
void SetVertexBuffers(
uint32_t,
uint32_t,
XCEngine::RHI::RHIResourceView**,
const uint64_t*,
const uint32_t*) override {
}
void SetIndexBuffer(XCEngine::RHI::RHIResourceView*, uint64_t) override {}
void Draw(uint32_t, uint32_t, uint32_t, uint32_t) override {}
void DrawIndexed(uint32_t, uint32_t, uint32_t, int32_t, uint32_t) override {}
void Clear(float, float, float, float, uint32_t) override {}
void ClearRenderTarget(
XCEngine::RHI::RHIResourceView*,
const float[4],
uint32_t,
const XCEngine::RHI::Rect*) override {
}
void ClearDepthStencil(
XCEngine::RHI::RHIResourceView*,
float,
uint8_t,
uint32_t,
const XCEngine::RHI::Rect*) override {
}
void CopyResource(XCEngine::RHI::RHIResourceView*, XCEngine::RHI::RHIResourceView*) override {}
void Dispatch(uint32_t, uint32_t, uint32_t) override {}
};
class MockNoopCommandQueue final : public XCEngine::RHI::RHICommandQueue {
public:
void Shutdown() override {}
void ExecuteCommandLists(uint32_t, void**) override {}
void Signal(XCEngine::RHI::RHIFence*, uint64_t) override {}
void Wait(XCEngine::RHI::RHIFence*, uint64_t) override {}
uint64_t GetCompletedValue() override { return 0u; }
void WaitForIdle() override {}
XCEngine::RHI::CommandQueueType GetType() const override {
return XCEngine::RHI::CommandQueueType::Direct;
}
uint64_t GetTimestampFrequency() const override { return 0u; }
void* GetNativeHandle() override { return nullptr; }
void WaitForPreviousFrame() override {}
uint64_t GetCurrentFrame() const override { return 0u; }
};
struct MockPipelineAssetState {
int createCalls = 0;
std::shared_ptr<MockPipelineState> lastCreatedPipelineState;
@@ -516,10 +608,13 @@ public:
};
RenderContext CreateValidContext() {
static MockNoopCommandList s_commandList;
static MockNoopCommandQueue s_commandQueue;
RenderContext context;
context.device = reinterpret_cast<XCEngine::RHI::RHIDevice*>(1);
context.commandList = reinterpret_cast<XCEngine::RHI::RHICommandList*>(1);
context.commandQueue = reinterpret_cast<XCEngine::RHI::RHICommandQueue*>(1);
context.commandList = &s_commandList;
context.commandQueue = &s_commandQueue;
return context;
}
@@ -688,7 +783,7 @@ TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) {
request.cameraDepth = overrideCamera->GetDepth();
request.clearFlags = RenderClearFlags::None;
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(state->renderCalls, 1);
EXPECT_EQ(state->lastSurfaceWidth, 640u);
EXPECT_EQ(state->lastSurfaceHeight, 480u);
@@ -728,7 +823,7 @@ TEST(CameraRenderer_Test, AppliesRequestClearColorOverrideToSceneData) {
request.hasClearColorOverride = true;
request.clearColorOverride = XCEngine::Math::Color(0.27f, 0.27f, 0.27f, 1.0f);
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_FLOAT_EQ(state->lastClearColor.r, 0.27f);
EXPECT_FLOAT_EQ(state->lastClearColor.g, 0.27f);
EXPECT_FLOAT_EQ(state->lastClearColor.b, 0.27f);
@@ -756,7 +851,7 @@ TEST(CameraRenderer_Test, PromotesSkyboxMaterialIntoEnvironmentFrameData) {
request.surface = RenderSurface(320, 200);
request.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_TRUE(state->lastHasSkybox);
EXPECT_EQ(state->lastEnvironmentMode, RenderEnvironmentMode::MaterialSkybox);
EXPECT_EQ(state->lastSkyboxMaterial, &skyboxMaterial);
@@ -788,7 +883,7 @@ TEST(CameraRenderer_Test, ExecutesInjectedPreAndPostPassSequencesAroundPipelineR
request.preScenePasses = &prePasses;
request.postScenePasses = &postPasses;
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{
@@ -830,7 +925,7 @@ TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRe
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{
@@ -883,7 +978,7 @@ TEST(CameraRenderer_Test, RoutesSceneColorThroughPostProcessAndFinalOutputStages
request.finalOutput.destinationSurface = request.surface;
request.finalOutput.passes = &finalOutputPasses;
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(state->lastSurfaceWidth, 256u);
EXPECT_EQ(state->lastSurfaceHeight, 128u);
EXPECT_EQ(state->lastCameraViewportWidth, 256u);
@@ -970,7 +1065,7 @@ TEST(CameraRenderer_Test, ChainsMultiPassPostProcessThroughIntermediateSurface)
request.postProcess.destinationSurface.SetColorAttachment(destinationColorAttachment);
request.postProcess.passes = &postProcessPasses;
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
ASSERT_NE(firstPassRaw, nullptr);
EXPECT_TRUE(firstPassRaw->lastHasSourceSurface);
@@ -1082,7 +1177,7 @@ TEST(CameraRenderer_Test, KeepsPostProcessAndFinalOutputScratchSurfacesIndepende
request.finalOutput.destinationSurface.SetColorAttachment(finalOutputDestination);
request.finalOutput.passes = &finalOutputPasses;
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
ASSERT_NE(postSecondPassRaw, nullptr);
EXPECT_EQ(postSecondPassRaw->lastSourceSurfaceWidth, 512u);
@@ -1100,9 +1195,12 @@ TEST(CameraRenderer_Test, KeepsPostProcessAndFinalOutputScratchSurfacesIndepende
EXPECT_EQ(allocationState->createTextureCalls, 2);
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 2);
EXPECT_EQ(allocationState->createShaderViewCalls, 2);
EXPECT_EQ(allocationState->shutdownTextureCalls, 0);
EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 0);
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 0);
EXPECT_EQ(allocationState->shutdownTextureCalls, 2);
EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 2);
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 2);
EXPECT_EQ(allocationState->destroyTextureCalls, 2);
EXPECT_EQ(allocationState->destroyRenderTargetViewCalls, 2);
EXPECT_EQ(allocationState->destroyShaderViewCalls, 2);
EXPECT_EQ(
pipelineState->eventLog,
(std::vector<std::string>{
@@ -1185,7 +1283,7 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) {
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(6));
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(7));
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{
@@ -1245,7 +1343,7 @@ TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipe
request.depthOnly.hasClearColorOverride = true;
request.depthOnly.clearColorOverride = XCEngine::Math::Color(0.3f, 0.2f, 0.1f, 1.0f);
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{
@@ -1309,7 +1407,7 @@ TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) {
request.directionalShadow.cameraData.viewProjection =
XCEngine::Math::Matrix4x4::Translation(XCEngine::Math::Vector3(11.0f, 12.0f, 13.0f));
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
pipelineState->eventLog,
(std::vector<std::string>{
@@ -1391,7 +1489,7 @@ TEST(CameraRenderer_Test, ReusesDirectionalShadowSurfaceWhenPlanMatches) {
request.directionalShadow.cameraData.viewportHeight = 128;
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
XCEngine::RHI::RHIResourceView* firstShadowMap = pipelineState->lastShadowMap;
ASSERT_NE(firstShadowMap, nullptr);
EXPECT_EQ(allocationState->createTextureCalls, 1);
@@ -1400,7 +1498,7 @@ TEST(CameraRenderer_Test, ReusesDirectionalShadowSurfaceWhenPlanMatches) {
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 0);
EXPECT_EQ(allocationState->destroyTextureCalls, 0);
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(allocationState->createTextureCalls, 1);
EXPECT_EQ(allocationState->createDepthViewCalls, 1);
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
@@ -1449,7 +1547,7 @@ TEST(CameraRenderer_Test, RecreatesDirectionalShadowSurfaceWhenPlanSizeChanges)
request.directionalShadow.cameraData.viewportHeight = 128;
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
XCEngine::RHI::RHIResourceView* firstShadowMap = pipelineState->lastShadowMap;
ASSERT_NE(firstShadowMap, nullptr);
@@ -1458,7 +1556,7 @@ TEST(CameraRenderer_Test, RecreatesDirectionalShadowSurfaceWhenPlanSizeChanges)
request.directionalShadow.cameraData.viewportWidth = 512;
request.directionalShadow.cameraData.viewportHeight = 256;
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(allocationState->createTextureCalls, 2);
EXPECT_EQ(allocationState->createDepthViewCalls, 2);
EXPECT_EQ(allocationState->createShaderViewCalls, 2);
@@ -1512,7 +1610,7 @@ TEST(CameraRenderer_Test, EnablesDirectionalShadowSurfaceAfterInitialFrameWithou
request.surface = RenderSurface(320, 180);
request.cameraDepth = camera->GetDepth();
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(pipelineState->eventLog, (std::vector<std::string>{ "pipeline" }));
EXPECT_FALSE(pipelineState->lastHasMainDirectionalShadow);
EXPECT_EQ(pipelineState->lastShadowMap, nullptr);
@@ -1529,7 +1627,7 @@ TEST(CameraRenderer_Test, EnablesDirectionalShadowSurfaceAfterInitialFrameWithou
request.directionalShadow.cameraData.viewportHeight = 128;
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
pipelineState->eventLog,
(std::vector<std::string>{
@@ -1572,7 +1670,7 @@ TEST(CameraRenderer_Test, StopsRenderingWhenShadowCasterRequestIsInvalid) {
request.shadowCaster.surface = RenderSurface(64, 64);
request.shadowCaster.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
EXPECT_FALSE(renderer.Render(request));
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_TRUE(state->eventLog.empty());
}
@@ -1599,7 +1697,7 @@ TEST(CameraRenderer_Test, StopsRenderingWhenPostProcessRequestIsInvalid) {
request.postProcess.destinationSurface = RenderSurface(320, 180);
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
EXPECT_FALSE(renderer.Render(request));
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_TRUE(state->eventLog.empty());
}
@@ -1626,7 +1724,7 @@ TEST(CameraRenderer_Test, ShutsDownInitializedPassesWhenPipelineRenderFails) {
request.cameraDepth = camera->GetDepth();
request.preScenePasses = &prePasses;
EXPECT_FALSE(renderer.Render(request));
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{ "init:pre", "pre", "pipeline" }));
@@ -1663,7 +1761,7 @@ TEST(CameraRenderer_Test, StopsRenderingWhenObjectIdPassFails) {
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
EXPECT_FALSE(renderer.Render(request));
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{ "init:pre", "pre", "pipeline", "objectId" }));
@@ -1695,7 +1793,7 @@ TEST(CameraRenderer_Test, ShutsDownSequencesWhenPostPassInitializationFails) {
request.preScenePasses = &prePasses;
request.postScenePasses = &postPasses;
EXPECT_FALSE(renderer.Render(request));
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{
@@ -2460,7 +2558,7 @@ TEST(CameraRenderer_Test, UsesResolvedRenderAreaForCameraViewportDimensions) {
request.surface = RenderSurface(640, 480);
request.surface.SetRenderArea(XCEngine::Math::RectInt(80, 120, 320, 240));
ASSERT_TRUE(renderer.Render(request));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(state->lastRenderAreaX, 80);
EXPECT_EQ(state->lastRenderAreaY, 120);
EXPECT_EQ(state->lastRenderAreaWidth, 320);
@@ -2574,8 +2672,11 @@ TEST(SceneRenderer_Test, SortsManualCameraRequestsByDepthBeforeRendering) {
nearRequest.cameraStackOrder = 0;
nearRequest.clearFlags = RenderClearFlags::Depth;
const std::vector<CameraRenderRequest> requests = { farRequest, nearRequest };
ASSERT_TRUE(renderer.Render(requests));
const std::vector<CameraFramePlan> plans = {
CameraFramePlan::FromRequest(farRequest),
CameraFramePlan::FromRequest(nearRequest)
};
ASSERT_TRUE(renderer.Render(plans));
ASSERT_EQ(state->renderedCameras.size(), 2u);
ASSERT_EQ(state->renderedClearFlags.size(), 2u);
EXPECT_EQ(state->renderedCameras[0], nearCamera);
@@ -2612,8 +2713,11 @@ TEST(SceneRenderer_Test, PreservesManualSubmissionOrderForEqualPriorityRequests)
CameraRenderRequest secondRequest = firstRequest;
secondRequest.camera = secondCamera;
const std::vector<CameraRenderRequest> requests = { secondRequest, firstRequest };
ASSERT_TRUE(renderer.Render(requests));
const std::vector<CameraFramePlan> plans = {
CameraFramePlan::FromRequest(secondRequest),
CameraFramePlan::FromRequest(firstRequest)
};
ASSERT_TRUE(renderer.Render(plans));
ASSERT_EQ(state->renderedCameras.size(), 2u);
EXPECT_EQ(state->renderedCameras[0], secondCamera);
EXPECT_EQ(state->renderedCameras[1], firstCamera);