Formalize camera post-process frame contract

This commit is contained in:
2026-04-06 03:55:06 +08:00
parent 5e489b61d2
commit 31d14abece
4 changed files with 345 additions and 29 deletions

View File

@@ -390,6 +390,11 @@ public:
lastWorldPosition = context.sceneData.cameraData.worldPosition;
lastSurfaceWidth = context.surface.GetRenderAreaWidth();
lastSurfaceHeight = context.surface.GetRenderAreaHeight();
lastHasSourceSurface = context.sourceSurface != nullptr;
if (context.sourceSurface != nullptr) {
lastSourceSurfaceWidth = context.sourceSurface->GetRenderAreaWidth();
lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight();
}
return m_executeResult;
}
@@ -404,6 +409,9 @@ public:
XCEngine::Math::Vector3 lastWorldPosition = XCEngine::Math::Vector3::Zero();
uint32_t lastSurfaceWidth = 0;
uint32_t lastSurfaceHeight = 0;
bool lastHasSourceSurface = false;
uint32_t lastSourceSurfaceWidth = 0;
uint32_t lastSourceSurfaceHeight = 0;
private:
std::shared_ptr<MockPipelineState> m_state;
@@ -434,8 +442,15 @@ public:
return m_initializeResult;
}
bool Execute(const RenderPassContext&) override {
bool Execute(const RenderPassContext& context) override {
m_state->eventLog.push_back(m_label);
lastSurfaceWidth = context.surface.GetRenderAreaWidth();
lastSurfaceHeight = context.surface.GetRenderAreaHeight();
lastHasSourceSurface = context.sourceSurface != nullptr;
if (context.sourceSurface != nullptr) {
lastSourceSurfaceWidth = context.sourceSurface->GetRenderAreaWidth();
lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight();
}
return m_executeResult;
}
@@ -448,6 +463,13 @@ private:
const char* m_label = "";
bool m_initializeResult = true;
bool m_executeResult = true;
public:
uint32_t lastSurfaceWidth = 0;
uint32_t lastSurfaceHeight = 0;
bool lastHasSourceSurface = false;
uint32_t lastSourceSurfaceWidth = 0;
uint32_t lastSourceSurfaceHeight = 0;
};
RenderContext CreateValidContext() {
@@ -464,9 +486,13 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) {
CameraRenderRequest request;
RenderPassSequence prePasses;
RenderPassSequence postProcessPasses;
RenderPassSequence finalOutputPasses;
RenderPassSequence postPasses;
RenderPassSequence overlayPasses;
request.preScenePasses = &prePasses;
request.postProcess.passes = &postProcessPasses;
request.finalOutput.passes = &finalOutputPasses;
request.postScenePasses = &postPasses;
request.overlayPasses = &overlayPasses;
@@ -479,30 +505,48 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) {
request.depthOnly.surface = RenderSurface(96, 48);
request.depthOnly.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
request.objectId.surface = RenderSurface(320, 180);
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
request.postProcess.sourceSurface = RenderSurface(256, 128);
request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
request.postProcess.destinationSurface = RenderSurface(512, 256);
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
ASSERT_EQ(kOrderedCameraFrameStages.size(), 7u);
request.finalOutput.sourceSurface = RenderSurface(512, 256);
request.finalOutput.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
request.finalOutput.destinationSurface = RenderSurface(640, 360);
request.finalOutput.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(5));
request.objectId.surface = RenderSurface(320, 180);
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(6));
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(7));
ASSERT_EQ(kOrderedCameraFrameStages.size(), 9u);
EXPECT_EQ(kOrderedCameraFrameStages[0].stage, CameraFrameStage::PreScenePasses);
EXPECT_EQ(kOrderedCameraFrameStages[1].stage, CameraFrameStage::ShadowCaster);
EXPECT_EQ(kOrderedCameraFrameStages[2].stage, CameraFrameStage::DepthOnly);
EXPECT_EQ(kOrderedCameraFrameStages[3].stage, CameraFrameStage::MainScene);
EXPECT_EQ(kOrderedCameraFrameStages[4].stage, CameraFrameStage::ObjectId);
EXPECT_EQ(kOrderedCameraFrameStages[5].stage, CameraFrameStage::PostScenePasses);
EXPECT_EQ(kOrderedCameraFrameStages[6].stage, CameraFrameStage::OverlayPasses);
EXPECT_EQ(kOrderedCameraFrameStages[4].stage, CameraFrameStage::PostProcess);
EXPECT_EQ(kOrderedCameraFrameStages[5].stage, CameraFrameStage::FinalOutput);
EXPECT_EQ(kOrderedCameraFrameStages[6].stage, CameraFrameStage::ObjectId);
EXPECT_EQ(kOrderedCameraFrameStages[7].stage, CameraFrameStage::PostScenePasses);
EXPECT_EQ(kOrderedCameraFrameStages[8].stage, CameraFrameStage::OverlayPasses);
EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::MainScene), "MainScene");
EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::PostProcess), "PostProcess");
EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::FinalOutput), "FinalOutput");
EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::OverlayPasses), "OverlayPasses");
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::PreScenePasses));
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::ShadowCaster));
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::DepthOnly));
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::MainScene));
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::PostProcess));
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::FinalOutput));
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::ObjectId));
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::PostScenePasses));
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::OverlayPasses));
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::PreScenePasses), &prePasses);
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::PostProcess), &postProcessPasses);
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::FinalOutput), &finalOutputPasses);
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::PostScenePasses), &postPasses);
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::OverlayPasses), &overlayPasses);
EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::ShadowCaster), &request.shadowCaster);
@@ -510,6 +554,17 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) {
EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::MainScene), nullptr);
EXPECT_EQ(request.GetObjectIdRequest(CameraFrameStage::ObjectId), &request.objectId);
EXPECT_EQ(request.GetObjectIdRequest(CameraFrameStage::MainScene), nullptr);
EXPECT_TRUE(request.RequiresIntermediateSceneColor());
EXPECT_EQ(request.GetMainSceneSurface().GetRenderAreaWidth(), 256u);
EXPECT_EQ(request.GetMainSceneSurface().GetRenderAreaHeight(), 128u);
EXPECT_EQ(request.GetFinalCompositedSurface().GetRenderAreaWidth(), 640u);
EXPECT_EQ(request.GetFinalCompositedSurface().GetRenderAreaHeight(), 360u);
ASSERT_NE(request.GetOutputSurface(CameraFrameStage::PostProcess), nullptr);
EXPECT_EQ(request.GetOutputSurface(CameraFrameStage::PostProcess)->GetRenderAreaWidth(), 512u);
ASSERT_NE(request.GetSourceSurface(CameraFrameStage::PostProcess), nullptr);
EXPECT_EQ(request.GetSourceSurface(CameraFrameStage::PostProcess)->GetRenderAreaWidth(), 256u);
ASSERT_NE(request.GetSourceSurface(CameraFrameStage::FinalOutput), nullptr);
EXPECT_EQ(request.GetSourceSurface(CameraFrameStage::FinalOutput)->GetRenderAreaWidth(), 512u);
}
TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) {
@@ -694,6 +749,74 @@ TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRe
"shutdown:pre" }));
}
TEST(CameraRenderer_Test, RoutesSceneColorThroughPostProcessAndFinalOutputStages) {
Scene scene("CameraRendererPostProcessScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetDepth(3.0f);
auto state = std::make_shared<MockPipelineState>();
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
auto postProcessPass = std::make_unique<MockScenePass>(state, "postProcess");
MockScenePass* postProcessPassRaw = postProcessPass.get();
RenderPassSequence postProcessPasses;
postProcessPasses.AddPass(std::move(postProcessPass));
auto finalOutputPass = std::make_unique<MockScenePass>(state, "finalOutput");
MockScenePass* finalOutputPassRaw = finalOutputPass.get();
RenderPassSequence finalOutputPasses;
finalOutputPasses.AddPass(std::move(finalOutputPass));
CameraRenderRequest request;
request.scene = &scene;
request.camera = camera;
request.context = CreateValidContext();
request.surface = RenderSurface(800, 600);
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
request.cameraDepth = camera->GetDepth();
request.postProcess.sourceSurface = RenderSurface(256, 128);
request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
request.postProcess.destinationSurface = RenderSurface(512, 256);
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
request.postProcess.passes = &postProcessPasses;
request.finalOutput.sourceSurface = request.postProcess.destinationSurface;
request.finalOutput.destinationSurface = request.surface;
request.finalOutput.passes = &finalOutputPasses;
ASSERT_TRUE(renderer.Render(request));
EXPECT_EQ(state->lastSurfaceWidth, 256u);
EXPECT_EQ(state->lastSurfaceHeight, 128u);
EXPECT_EQ(state->lastCameraViewportWidth, 256u);
EXPECT_EQ(state->lastCameraViewportHeight, 128u);
ASSERT_NE(postProcessPassRaw, nullptr);
EXPECT_TRUE(postProcessPassRaw->lastHasSourceSurface);
EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceWidth, 256u);
EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceHeight, 128u);
EXPECT_EQ(postProcessPassRaw->lastSurfaceWidth, 512u);
EXPECT_EQ(postProcessPassRaw->lastSurfaceHeight, 256u);
ASSERT_NE(finalOutputPassRaw, nullptr);
EXPECT_TRUE(finalOutputPassRaw->lastHasSourceSurface);
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceWidth, 512u);
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceHeight, 256u);
EXPECT_EQ(finalOutputPassRaw->lastSurfaceWidth, 800u);
EXPECT_EQ(finalOutputPassRaw->lastSurfaceHeight, 600u);
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{
"pipeline",
"init:postProcess",
"postProcess",
"init:finalOutput",
"finalOutput",
"shutdown:finalOutput",
"shutdown:postProcess" }));
}
TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) {
Scene scene("CameraRendererFormalFrameStageScene");
@@ -716,6 +839,12 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) {
RenderPassSequence prePasses;
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
RenderPassSequence postProcessPasses;
postProcessPasses.AddPass(std::make_unique<TrackingPass>(state, "postProcess"));
RenderPassSequence finalOutputPasses;
finalOutputPasses.AddPass(std::make_unique<TrackingPass>(state, "finalOutput"));
RenderPassSequence postPasses;
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post"));
@@ -727,17 +856,26 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) {
request.camera = camera;
request.context = CreateValidContext();
request.surface = RenderSurface(320, 180);
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
request.cameraDepth = camera->GetDepth();
request.preScenePasses = &prePasses;
request.postProcess.passes = &postProcessPasses;
request.finalOutput.passes = &finalOutputPasses;
request.postScenePasses = &postPasses;
request.overlayPasses = &overlayPasses;
request.shadowCaster.surface = RenderSurface(128, 64);
request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
request.depthOnly.surface = RenderSurface(96, 48);
request.depthOnly.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
request.postProcess.sourceSurface = RenderSurface(256, 128);
request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
request.postProcess.destinationSurface = RenderSurface(320, 180);
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(5));
request.finalOutput.sourceSurface = request.postProcess.destinationSurface;
request.finalOutput.destinationSurface = request.surface;
request.objectId.surface = RenderSurface(320, 180);
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
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));
EXPECT_EQ(
@@ -750,6 +888,10 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) {
"init:depthOnly",
"depthOnly",
"pipeline",
"init:postProcess",
"postProcess",
"init:finalOutput",
"finalOutput",
"objectId",
"init:post",
"post",
@@ -757,6 +899,8 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) {
"overlay",
"shutdown:overlay",
"shutdown:post",
"shutdown:finalOutput",
"shutdown:postProcess",
"shutdown:pre" }));
}
@@ -1056,6 +1200,33 @@ TEST(CameraRenderer_Test, StopsRenderingWhenShadowCasterRequestIsInvalid) {
EXPECT_TRUE(state->eventLog.empty());
}
TEST(CameraRenderer_Test, StopsRenderingWhenPostProcessRequestIsInvalid) {
Scene scene("CameraRendererInvalidPostProcessScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
auto state = std::make_shared<MockPipelineState>();
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
RenderPassSequence postProcessPasses;
postProcessPasses.AddPass(std::make_unique<TrackingPass>(state, "postProcess"));
CameraRenderRequest request;
request.scene = &scene;
request.camera = camera;
request.context = CreateValidContext();
request.surface = RenderSurface(320, 180);
request.postProcess.passes = &postProcessPasses;
request.postProcess.sourceSurface = RenderSurface(256, 128);
request.postProcess.destinationSurface = RenderSurface(320, 180);
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
EXPECT_FALSE(renderer.Render(request));
EXPECT_TRUE(state->eventLog.empty());
}
TEST(CameraRenderer_Test, ShutsDownInitializedPassesWhenPipelineRenderFails) {
Scene scene("CameraRendererFailureScene");