Align rendering orchestration with camera stack plans

This commit is contained in:
2026-04-28 02:42:42 +08:00
parent be0d244372
commit 3bc0cfcf08
20 changed files with 616 additions and 13 deletions

View File

@@ -71,6 +71,7 @@ struct MockPipelineState {
int recordPostProcessCalls = 0;
int recordFinalOutputCalls = 0;
int executeRecordedMainSceneCalls = 0;
int finishCameraStackRenderingCalls = 0;
bool renderResult = true;
bool supportsMainSceneRenderGraph = false;
bool supportsPostProcessRenderGraph = false;
@@ -112,6 +113,7 @@ struct MockPipelineState {
std::vector<CameraComponent*> renderedCameras;
std::vector<RenderClearFlags> renderedClearFlags;
std::vector<XCEngine::Math::Color> renderedClearColors;
std::vector<size_t> finishedCameraStackSizes;
std::vector<std::string> eventLog;
};
@@ -764,6 +766,13 @@ public:
return m_state->renderResult;
}
void FinishCameraStackRendering(
const CameraStackFramePlan& stackPlan) override {
++m_state->finishCameraStackRenderingCalls;
m_state->finishedCameraStackSizes.push_back(
stackPlan.cameraPlans.size());
}
private:
std::shared_ptr<MockPipelineState> m_state;
};
@@ -3420,15 +3429,65 @@ TEST(SceneRenderer_Test, RendersBaseCamerasBeforeOverlayCamerasAndResolvesAutoCl
ASSERT_EQ(plans.size(), 3u);
EXPECT_EQ(plans[0].request.camera, earlyBaseCamera);
EXPECT_EQ(plans[0].request.cameraStackOrder, 0u);
EXPECT_NE(plans[0].request.cameraStackId, 0u);
EXPECT_EQ(plans[0].request.clearFlags, RenderClearFlags::All);
EXPECT_EQ(plans[1].request.camera, lateBaseCamera);
EXPECT_EQ(plans[1].request.cameraStackOrder, 0u);
EXPECT_NE(plans[1].request.cameraStackId, 0u);
EXPECT_NE(
plans[0].request.cameraStackId,
plans[1].request.cameraStackId);
EXPECT_EQ(plans[1].request.clearFlags, RenderClearFlags::Depth);
EXPECT_EQ(plans[2].request.camera, overlayCamera);
EXPECT_EQ(plans[2].request.cameraStackOrder, 1u);
EXPECT_EQ(
plans[2].request.cameraStackId,
plans[1].request.cameraStackId);
EXPECT_EQ(plans[2].request.clearFlags, RenderClearFlags::Depth);
}
TEST(SceneRenderer_Test, BuildsRootCameraStackFramePlansFromSceneRequests) {
Scene scene("SceneRendererCameraStackFramePlanScene");
GameObject* firstBaseCameraObject = scene.CreateGameObject("FirstBaseCamera");
auto* firstBaseCamera = firstBaseCameraObject->AddComponent<CameraComponent>();
firstBaseCamera->SetDepth(1.0f);
firstBaseCamera->SetStackType(CameraStackType::Base);
GameObject* secondBaseCameraObject = scene.CreateGameObject("SecondBaseCamera");
auto* secondBaseCamera = secondBaseCameraObject->AddComponent<CameraComponent>();
secondBaseCamera->SetDepth(2.0f);
secondBaseCamera->SetStackType(CameraStackType::Base);
GameObject* overlayCameraObject = scene.CreateGameObject("OverlayCamera");
auto* overlayCamera = overlayCameraObject->AddComponent<CameraComponent>();
overlayCamera->SetDepth(3.0f);
overlayCamera->SetStackType(CameraStackType::Overlay);
SceneRenderer renderer;
const std::vector<CameraStackFramePlan> stackPlans =
renderer.BuildStackFramePlans(
scene,
nullptr,
CreateValidContext(),
RenderSurface(640, 360));
ASSERT_EQ(stackPlans.size(), 2u);
ASSERT_EQ(stackPlans[0].cameraPlans.size(), 1u);
EXPECT_EQ(stackPlans[0].cameraPlans[0].request.camera, firstBaseCamera);
EXPECT_TRUE(stackPlans[0].HasBaseCamera());
EXPECT_FALSE(stackPlans[0].IsOverlayOnly());
ASSERT_EQ(stackPlans[1].cameraPlans.size(), 2u);
EXPECT_EQ(stackPlans[1].cameraPlans[0].request.camera, secondBaseCamera);
EXPECT_EQ(stackPlans[1].cameraPlans[1].request.camera, overlayCamera);
EXPECT_TRUE(stackPlans[1].HasBaseCamera());
EXPECT_FALSE(stackPlans[1].IsOverlayOnly());
EXPECT_EQ(
stackPlans[1].cameraPlans[1].request.cameraStackId,
stackPlans[1].cameraPlans[0].request.cameraStackId);
}
TEST(SceneRenderer_Test, PreservesSceneTraversalOrderForEqualPriorityCameras) {
Scene scene("SceneRendererStableSceneOrder");
@@ -4531,6 +4590,55 @@ TEST(RenderPipelineHost_Test, ForwardsPipelineAssetLifetimeAndRenderCallsToCamer
EXPECT_EQ(replacementState->shutdownCalls, 1);
}
TEST(RenderPipelineHost_Test, RendersCameraStackFramePlansAsSingleStackLifetime) {
Scene scene("RenderPipelineHostStackPlans");
GameObject* baseCameraObject = scene.CreateGameObject("BaseCamera");
auto* baseCamera = baseCameraObject->AddComponent<CameraComponent>();
baseCamera->SetDepth(1.0f);
baseCamera->SetStackType(CameraStackType::Base);
GameObject* overlayCameraObject = scene.CreateGameObject("OverlayCamera");
auto* overlayCamera = overlayCameraObject->AddComponent<CameraComponent>();
overlayCamera->SetDepth(2.0f);
overlayCamera->SetStackType(CameraStackType::Overlay);
CameraRenderRequest baseRequest = {};
baseRequest.scene = &scene;
baseRequest.camera = baseCamera;
baseRequest.context = CreateValidContext();
baseRequest.surface = RenderSurface(800, 600);
baseRequest.cameraStackId = 42u;
baseRequest.cameraDepth = baseCamera->GetDepth();
baseRequest.cameraStackOrder = 0u;
baseRequest.clearFlags = RenderClearFlags::All;
CameraRenderRequest overlayRequest = baseRequest;
overlayRequest.camera = overlayCamera;
overlayRequest.cameraDepth = overlayCamera->GetDepth();
overlayRequest.cameraStackOrder = 1u;
overlayRequest.clearFlags = RenderClearFlags::Depth;
auto state = std::make_shared<MockPipelineState>();
RenderPipelineHost host(MakeMockPipelineAsset(state));
const std::vector<CameraStackFramePlan> stackPlans =
host.BuildStackFramePlans({ baseRequest, overlayRequest });
ASSERT_EQ(stackPlans.size(), 1u);
ASSERT_EQ(stackPlans[0].cameraPlans.size(), 2u);
EXPECT_EQ(stackPlans[0].cameraStackId, 42u);
EXPECT_EQ(stackPlans[0].cameraPlans[0].request.camera, baseCamera);
EXPECT_EQ(stackPlans[0].cameraPlans[1].request.camera, overlayCamera);
ASSERT_TRUE(host.Render(stackPlans));
ASSERT_EQ(state->renderedCameras.size(), 2u);
EXPECT_EQ(state->renderedCameras[0], baseCamera);
EXPECT_EQ(state->renderedCameras[1], overlayCamera);
EXPECT_EQ(state->finishCameraStackRenderingCalls, 1);
ASSERT_EQ(state->finishedCameraStackSizes.size(), 1u);
EXPECT_EQ(state->finishedCameraStackSizes[0], 2u);
}
TEST(RenderPipelineHost_Test, SortsManualFramePlansByDepthBeforeRendering) {
Scene scene("RenderPipelineHostManualRequests");