feat(rendering): add managed fullscreen stage planning seam

This commit is contained in:
2026-04-17 23:39:08 +08:00
parent 4a4e921eb1
commit 6838b00d97
16 changed files with 935 additions and 18 deletions

View File

@@ -776,6 +776,9 @@ struct MockManagedRenderPipelineBridgeState {
int createStageRecorderCalls = 0;
Pipelines::ManagedRenderPipelineAssetDescriptor lastDescriptor = {};
std::shared_ptr<MockStageRecorderState> lastCreatedStageRecorderState;
int configureCameraFramePlanCalls = 0;
Pipelines::ManagedRenderPipelineAssetDescriptor lastPlanningDescriptor = {};
std::function<void(CameraFramePlan&)> configureCameraFramePlan = {};
};
class MockManagedRenderPipelineBridge final
@@ -796,6 +799,16 @@ public:
m_state->lastCreatedStageRecorderState);
}
void ConfigureCameraFramePlan(
const Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor,
CameraFramePlan& plan) const override {
++m_state->configureCameraFramePlanCalls;
m_state->lastPlanningDescriptor = descriptor;
if (m_state->configureCameraFramePlan) {
m_state->configureCameraFramePlan(plan);
}
}
private:
std::shared_ptr<MockManagedRenderPipelineBridgeState> m_state;
};
@@ -4471,6 +4484,180 @@ TEST(ManagedScriptableRenderPipelineAsset_Test, CreatesHostWithStageRecorderFrom
Pipelines::ClearManagedRenderPipelineBridge();
}
TEST(ManagedScriptableRenderPipelineAsset_Test, LetsManagedBridgeRequestFullscreenStagesDuringPlanConfiguration) {
Pipelines::ClearManagedRenderPipelineBridge();
const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = {
"GameScripts",
"Gameplay",
"ManagedPlannedFullscreenRenderPipelineProbeAsset"
};
auto bridgeState = std::make_shared<MockManagedRenderPipelineBridgeState>();
bridgeState->configureCameraFramePlan = [](
CameraFramePlan& plan) {
plan.ClearFullscreenStage(CameraFrameStage::PostProcess);
plan.ClearFullscreenStage(CameraFrameStage::FinalOutput);
EXPECT_TRUE(
plan.RequestFullscreenStage(
CameraFrameStage::PostProcess,
CameraFrameColorSource::MainSceneColor,
true));
EXPECT_TRUE(
plan.RequestFullscreenStage(
CameraFrameStage::FinalOutput,
CameraFrameColorSource::PostProcessColor));
};
Pipelines::SetManagedRenderPipelineBridge(
std::make_shared<MockManagedRenderPipelineBridge>(bridgeState));
auto allocationState = std::make_shared<MockShadowAllocationState>();
MockShadowView colorView(
allocationState,
XCEngine::RHI::ResourceViewType::RenderTarget,
XCEngine::RHI::Format::R8G8B8A8_UNorm,
XCEngine::RHI::ResourceViewDimension::Texture2D);
MockShadowView depthView(
allocationState,
XCEngine::RHI::ResourceViewType::DepthStencil,
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
XCEngine::RHI::ResourceViewDimension::Texture2D);
CameraRenderRequest request = {};
request.context = CreateValidContext();
request.surface = RenderSurface(320, 180);
request.surface.SetColorAttachment(&colorView);
request.surface.SetDepthAttachment(&depthView);
CameraFramePlan plan = CameraFramePlan::FromRequest(request);
Pipelines::ManagedScriptableRenderPipelineAsset asset(descriptor);
asset.ConfigureCameraFramePlan(plan);
EXPECT_EQ(bridgeState->configureCameraFramePlanCalls, 1);
EXPECT_EQ(bridgeState->lastPlanningDescriptor.assemblyName, "GameScripts");
EXPECT_EQ(bridgeState->lastPlanningDescriptor.namespaceName, "Gameplay");
EXPECT_EQ(
bridgeState->lastPlanningDescriptor.className,
"ManagedPlannedFullscreenRenderPipelineProbeAsset");
EXPECT_TRUE(plan.IsFullscreenStageRequested(CameraFrameStage::PostProcess));
EXPECT_TRUE(plan.IsFullscreenStageRequested(CameraFrameStage::FinalOutput));
EXPECT_EQ(plan.postProcess.passes, nullptr);
EXPECT_EQ(plan.finalOutput.passes, nullptr);
EXPECT_TRUE(plan.UsesGraphManagedSceneColor());
EXPECT_TRUE(plan.UsesGraphManagedOutputColor(CameraFrameStage::PostProcess));
EXPECT_EQ(
plan.ResolveStageColorSource(CameraFrameStage::PostProcess),
CameraFrameColorSource::MainSceneColor);
EXPECT_EQ(
plan.ResolveStageColorSource(CameraFrameStage::FinalOutput),
CameraFrameColorSource::PostProcessColor);
EXPECT_TRUE(plan.IsPostProcessStageValid());
EXPECT_TRUE(plan.IsFinalOutputStageValid());
Pipelines::ClearManagedRenderPipelineBridge();
}
TEST(CameraRenderer_Test, RendersManagedRequestedPostProcessWithoutLegacySequence) {
Scene scene("CameraRendererManagedRequestedPostProcessScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
auto state = std::make_shared<MockPipelineState>();
state->supportsMainSceneRenderGraph = true;
state->supportsPostProcessRenderGraph = true;
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
auto allocationState = std::make_shared<MockShadowAllocationState>();
MockShadowDevice device(allocationState);
MockShadowView colorView(
allocationState,
XCEngine::RHI::ResourceViewType::RenderTarget,
XCEngine::RHI::Format::R8G8B8A8_UNorm,
XCEngine::RHI::ResourceViewDimension::Texture2D);
MockShadowView depthView(
allocationState,
XCEngine::RHI::ResourceViewType::DepthStencil,
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
XCEngine::RHI::ResourceViewDimension::Texture2D);
CameraRenderRequest request = {};
request.scene = &scene;
request.camera = camera;
request.context = CreateValidContext();
request.context.device = &device;
request.surface = RenderSurface(320, 180);
request.surface.SetColorAttachment(&colorView);
request.surface.SetDepthAttachment(&depthView);
request.cameraDepth = camera->GetDepth();
CameraFramePlan plan = CameraFramePlan::FromRequest(request);
ASSERT_TRUE(
plan.RequestFullscreenStage(
CameraFrameStage::PostProcess,
CameraFrameColorSource::MainSceneColor,
false));
ASSERT_TRUE(plan.IsFullscreenStageRequested(CameraFrameStage::PostProcess));
ASSERT_EQ(plan.postProcess.passes, nullptr);
ASSERT_TRUE(plan.IsPostProcessStageValid());
ASSERT_TRUE(renderer.Render(plan));
EXPECT_EQ(state->recordMainSceneCalls, 1);
EXPECT_EQ(state->recordPostProcessCalls, 1);
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{
"pipelineGraph",
"pipelineGraph" }));
}
TEST(CameraRenderer_Test, RejectsManagedRequestedPostProcessWithoutSupportedStageRecorder) {
Scene scene("CameraRendererManagedRequestedPostProcessUnsupportedScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
auto state = std::make_shared<MockPipelineState>();
state->supportsMainSceneRenderGraph = true;
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
auto allocationState = std::make_shared<MockShadowAllocationState>();
MockShadowDevice device(allocationState);
MockShadowView colorView(
allocationState,
XCEngine::RHI::ResourceViewType::RenderTarget,
XCEngine::RHI::Format::R8G8B8A8_UNorm,
XCEngine::RHI::ResourceViewDimension::Texture2D);
MockShadowView depthView(
allocationState,
XCEngine::RHI::ResourceViewType::DepthStencil,
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
XCEngine::RHI::ResourceViewDimension::Texture2D);
CameraRenderRequest request = {};
request.scene = &scene;
request.camera = camera;
request.context = CreateValidContext();
request.context.device = &device;
request.surface = RenderSurface(320, 180);
request.surface.SetColorAttachment(&colorView);
request.surface.SetDepthAttachment(&depthView);
request.cameraDepth = camera->GetDepth();
CameraFramePlan plan = CameraFramePlan::FromRequest(request);
ASSERT_TRUE(
plan.RequestFullscreenStage(
CameraFrameStage::PostProcess,
CameraFrameColorSource::MainSceneColor,
false));
ASSERT_TRUE(plan.IsPostProcessStageValid());
EXPECT_FALSE(renderer.Render(plan));
EXPECT_EQ(state->recordMainSceneCalls, 0);
EXPECT_EQ(state->recordPostProcessCalls, 0);
}
TEST(CameraRenderer_Test, DefaultPipelineAssetUsesManagedSelectionWhenPresent) {
const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = {
"GameScripts",