diff --git a/docs/plan/Renderer_C++层第七阶段计划_渲染模块收口与SRP宿主去临时化_2026-04-15.md b/docs/plan/Renderer_C++层第七阶段计划_渲染模块收口与SRP宿主去临时化_2026-04-15.md new file mode 100644 index 00000000..634e970e --- /dev/null +++ b/docs/plan/Renderer_C++层第七阶段计划_渲染模块收口与SRP宿主去临时化_2026-04-15.md @@ -0,0 +1,71 @@ +# Renderer C++层第七阶段计划:渲染模块收口与 SRP 宿主去临时化 + +## 阶段目标 + +本阶段不继续扩展新渲染特性,重点处理当前 Rendering 模块里仍然存在的临时组织、隐式依赖和未收口边界,为后续正式构建 SRP/URP 风格宿主层做准备。 + +收口标准: + +1. `CameraFramePlan` 成为真正可安全返回、复制、缓存的值对象,不再依赖 builder 内部临时托管资源的生命周期。 +2. `RenderPipeline` / `RenderPipelineStageRecorder` 生命周期进入统一主链,不再依赖 builtin 分支各自兜底初始化。 +3. builtin 渲染 policy 从 `CameraRenderer` / planner / host 中外提到 renderer asset / renderer 实现层,不再由 engine core 直接决定。 +4. RenderGraph fallback 仅保留必要兼容点,去掉重复桥接和职责重叠实现。 +5. editor 专用 overlay / picking / viewport pass 与 runtime builtin 渲染边界更清晰。 + +## 当前确认的问题 + +### P0 + +1. `CameraFramePlan` 内部保存的 fullscreen `RenderPassSequence*` 由 `CameraFramePlanBuilder` 内部成员托管,plan 对外按值返回时存在悬空生命周期风险。 +2. `ScriptableRenderPipelineHost` 已有生命周期接口,但运行主链没有真正统一调用,managed recorder 未来会直接踩到这个缺口。 +3. 阴影、天空盒、final color、post-process、默认 standalone stage pass 等 policy 仍散落在 engine core,而不是 renderer asset / renderer 自身闭合。 + +### P1 + +1. native RenderGraph 仍保留多处 fallback 录制与重复 surface helper,说明兼容层还未完全收口。 +2. builtin forward 与 graph builder 之间仍存在 `friend` 级耦合,部分逻辑仍是“双轨并存”。 +3. editor 专用 pass 还在 runtime rendering core 中,占用 runtime builtin 命名空间。 + +## 执行顺序 + +### Step 1:修复 CameraFramePlan 所有权 + +目标: + +1. generated fullscreen sequence 由 `CameraFramePlan` 自持有。 +2. `CameraFramePlanBuilder` 不再保管 `postProcess/finalOutput` sequence。 +3. 补测试覆盖“plan 返回后 / 复制后 / 再次 build 后”sequence 仍有效。 + +### Step 2:统一 pipeline 生命周期 + +目标: + +1. 明确 `RenderPipeline`、`RenderPipelineRenderer`、`RenderPipelineStageRecorder` 的初始化时机。 +2. 让 graph 录制与执行链共享同一套 lifecycle 入口。 +3. 去掉 builtin forward 在 graph execute callback 里的额外初始化兜底。 + +### Step 3:回收 builtin policy + +目标: + +1. 方向光阴影启用策略、环境数据、final color 默认策略、fullscreen stage 生成策略逐步下沉到 builtin renderer asset / renderer。 +2. `CameraRenderer` 退化为 scene extract + render plan execute 的薄协调层。 + +### Step 4:压缩 RenderGraph 兼容层 + +目标: + +1. 清理重复的 graph-managed surface helper。 +2. 收敛 fallback raster pass 触发条件。 +3. 继续削减 `RecordCompatible*` 路径在核心主线中的存在感。 + +### Step 5:整理 editor/runtime 渲染边界 + +目标: + +1. editor viewport grid / outline / selection mask 等 pass 从 runtime builtin 语义上剥离。 +2. 形成 editor renderer bundle 或 editor-only pass package 的组织方式。 + +## 本轮起手项 + +本轮先执行 Step 1。 diff --git a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h index 88188194..5ba8fb17 100644 --- a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h +++ b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h @@ -3,6 +3,8 @@ #include #include +#include + namespace XCEngine { namespace Rendering { @@ -43,6 +45,16 @@ struct CameraFramePlan { bool IsValid() const; void ConfigureGraphManagedSceneSurface(); + void ClearOwnedPostProcessSequence(); + void SetOwnedPostProcessSequence(std::shared_ptr sequence); + const std::shared_ptr& GetOwnedPostProcessSequence() const { + return m_ownedPostProcessSequence; + } + void ClearOwnedFinalOutputSequence(); + void SetOwnedFinalOutputSequence(std::shared_ptr sequence); + const std::shared_ptr& GetOwnedFinalOutputSequence() const { + return m_ownedFinalOutputSequence; + } bool UsesGraphManagedSceneColor() const; bool UsesGraphManagedOutputColor(CameraFrameStage stage) const; CameraFrameColorSource ResolveStageColorSource(CameraFrameStage stage) const; @@ -58,6 +70,10 @@ struct CameraFramePlan { const RenderSurface& GetMainSceneSurface() const; const RenderSurface& GetFinalCompositedSurface() const; bool RequiresIntermediateSceneColor() const; + +private: + std::shared_ptr m_ownedPostProcessSequence = {}; + std::shared_ptr m_ownedFinalOutputSequence = {}; }; } // namespace Rendering diff --git a/engine/src/Rendering/Execution/CameraFramePlan.cpp b/engine/src/Rendering/Execution/CameraFramePlan.cpp index 67df545d..f3820b6b 100644 --- a/engine/src/Rendering/Execution/CameraFramePlan.cpp +++ b/engine/src/Rendering/Execution/CameraFramePlan.cpp @@ -36,6 +36,40 @@ void CameraFramePlan::ConfigureGraphManagedSceneSurface() { BuildGraphManagedIntermediateSurfaceTemplate(request.surface); } +void CameraFramePlan::ClearOwnedPostProcessSequence() { + if (postProcess.passes == m_ownedPostProcessSequence.get()) { + postProcess.passes = nullptr; + } + + m_ownedPostProcessSequence.reset(); +} + +void CameraFramePlan::SetOwnedPostProcessSequence( + std::shared_ptr sequence) { + ClearOwnedPostProcessSequence(); + m_ownedPostProcessSequence = std::move(sequence); + if (m_ownedPostProcessSequence != nullptr) { + postProcess.passes = m_ownedPostProcessSequence.get(); + } +} + +void CameraFramePlan::ClearOwnedFinalOutputSequence() { + if (finalOutput.passes == m_ownedFinalOutputSequence.get()) { + finalOutput.passes = nullptr; + } + + m_ownedFinalOutputSequence.reset(); +} + +void CameraFramePlan::SetOwnedFinalOutputSequence( + std::shared_ptr sequence) { + ClearOwnedFinalOutputSequence(); + m_ownedFinalOutputSequence = std::move(sequence); + if (m_ownedFinalOutputSequence != nullptr) { + finalOutput.passes = m_ownedFinalOutputSequence.get(); + } +} + bool CameraFramePlan::UsesGraphManagedSceneColor() const { return colorChain.usesGraphManagedSceneColor; } diff --git a/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp b/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp index cd037588..20fa0c5f 100644 --- a/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp +++ b/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp @@ -46,17 +46,8 @@ void CameraFramePlanBuilder::ResolveCameraFinalColorPolicies( void CameraFramePlanBuilder::AttachFullscreenStageRequests( std::vector& plans) { - m_ownedPostProcessSequences.clear(); - m_ownedPostProcessSequences.resize(plans.size()); - m_ownedFinalOutputSequences.clear(); - m_ownedFinalOutputSequences.resize(plans.size()); - - for (size_t index = 0; index < plans.size(); ++index) { - CameraFramePlan& plan = plans[index]; - Internal::PlanCameraFrameFullscreenStages( - plan, - m_ownedPostProcessSequences[index], - m_ownedFinalOutputSequences[index]); + for (CameraFramePlan& plan : plans) { + Internal::PlanCameraFrameFullscreenStages(plan); } } diff --git a/engine/src/Rendering/Planning/CameraFramePlanBuilder.h b/engine/src/Rendering/Planning/CameraFramePlanBuilder.h index 640a1060..fa88be9f 100644 --- a/engine/src/Rendering/Planning/CameraFramePlanBuilder.h +++ b/engine/src/Rendering/Planning/CameraFramePlanBuilder.h @@ -2,7 +2,6 @@ #include -#include #include namespace XCEngine { @@ -28,9 +27,6 @@ private: std::vector& plans, const RenderPipelineAsset* pipelineAsset) const; void AttachFullscreenStageRequests(std::vector& plans); - - std::vector> m_ownedPostProcessSequences; - std::vector> m_ownedFinalOutputSequences; }; } // namespace Rendering diff --git a/engine/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.cpp b/engine/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.cpp index 31a74346..085d10c7 100644 --- a/engine/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.cpp +++ b/engine/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.cpp @@ -11,12 +11,18 @@ namespace XCEngine { namespace Rendering { namespace Internal { -void PlanCameraFrameFullscreenStages( - CameraFramePlan& plan, - std::unique_ptr& ownedPostProcessSequence, - std::unique_ptr& ownedFinalOutputSequence) { - ownedPostProcessSequence.reset(); - ownedFinalOutputSequence.reset(); +namespace { + +std::shared_ptr SharePassSequence( + std::unique_ptr sequence) { + return std::shared_ptr(sequence.release()); +} + +} // namespace + +void PlanCameraFrameFullscreenStages(CameraFramePlan& plan) { + plan.ClearOwnedPostProcessSequence(); + plan.ClearOwnedFinalOutputSequence(); if (plan.request.camera == nullptr || plan.request.context.device == nullptr || @@ -45,8 +51,8 @@ void PlanCameraFrameFullscreenStages( } if (hasPostProcess) { - ownedPostProcessSequence = std::move(postProcessSequence); - plan.postProcess.passes = ownedPostProcessSequence.get(); + plan.SetOwnedPostProcessSequence( + SharePassSequence(std::move(postProcessSequence))); plan.colorChain.usesGraphManagedSceneColor = true; plan.colorChain.postProcess.source = CameraFrameColorSource::MainSceneColor; plan.colorChain.postProcess.usesGraphManagedOutputColor = hasFinalOutput; @@ -56,8 +62,8 @@ void PlanCameraFrameFullscreenStages( } if (hasFinalOutput) { - ownedFinalOutputSequence = std::move(finalOutputSequence); - plan.finalOutput.passes = ownedFinalOutputSequence.get(); + plan.SetOwnedFinalOutputSequence( + SharePassSequence(std::move(finalOutputSequence))); plan.colorChain.usesGraphManagedSceneColor = true; plan.colorChain.finalOutput.source = hasPostProcess diff --git a/engine/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.h b/engine/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.h index bd57946c..335bc3e9 100644 --- a/engine/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.h +++ b/engine/src/Rendering/Planning/Internal/CameraFrameFullscreenStagePlanner.h @@ -11,10 +11,7 @@ struct CameraFramePlan; namespace Internal { -void PlanCameraFrameFullscreenStages( - CameraFramePlan& plan, - std::unique_ptr& ownedPostProcessSequence, - std::unique_ptr& ownedFinalOutputSequence); +void PlanCameraFrameFullscreenStages(CameraFramePlan& plan); } // namespace Internal } // namespace Rendering diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 9ad14d44..a30f9677 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -3309,6 +3309,73 @@ TEST(SceneRenderer_Test, RoutesPostProcessIntoIntermediateSurfaceBeforeFinalOutp delete backBufferColorView; } +TEST(SceneRenderer_Test, ReturnedPlansOwnGeneratedFullscreenSequences) { + Scene scene("SceneRendererOwnsGeneratedFullscreenSequencesScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(2.0f); + camera->SetPostProcessPasses({ + XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale( + XCEngine::Math::Vector4(1.0f, 0.75f, 0.75f, 1.0f)) + }); + + FinalColorOverrideSettings cameraOverrides = {}; + cameraOverrides.overrideOutputTransferMode = true; + cameraOverrides.outputTransferMode = FinalColorOutputTransferMode::LinearToSRGB; + camera->SetFinalColorOverrides(cameraOverrides); + + auto allocationState = std::make_shared(); + MockShadowDevice device(allocationState); + + auto* backBufferColorView = new MockShadowView( + allocationState, + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::Format::R8G8B8A8_UNorm, + XCEngine::RHI::ResourceViewDimension::Texture2D); + auto* depthView = new MockShadowView( + allocationState, + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::Format::D24_UNorm_S8_UInt, + XCEngine::RHI::ResourceViewDimension::Texture2D); + + RenderContext context = CreateValidContext(); + context.device = &device; + + RenderSurface surface(800, 600); + surface.SetColorAttachment(backBufferColorView); + surface.SetDepthAttachment(depthView); + + CameraFramePlan preservedPlan = {}; + { + SceneRenderer renderer; + const std::vector plans = + renderer.BuildFramePlans(scene, nullptr, context, surface); + + ASSERT_EQ(plans.size(), 1u); + ASSERT_TRUE(static_cast(plans[0].GetOwnedPostProcessSequence())); + ASSERT_TRUE(static_cast(plans[0].GetOwnedFinalOutputSequence())); + preservedPlan = plans[0]; + } + + ASSERT_TRUE(static_cast(preservedPlan.GetOwnedPostProcessSequence())); + ASSERT_TRUE(static_cast(preservedPlan.GetOwnedFinalOutputSequence())); + ASSERT_NE(preservedPlan.postProcess.passes, nullptr); + ASSERT_NE(preservedPlan.finalOutput.passes, nullptr); + EXPECT_EQ( + preservedPlan.postProcess.passes, + preservedPlan.GetOwnedPostProcessSequence().get()); + EXPECT_EQ( + preservedPlan.finalOutput.passes, + preservedPlan.GetOwnedFinalOutputSequence().get()); + EXPECT_EQ(preservedPlan.postProcess.passes->GetPassCount(), 1u); + EXPECT_EQ(preservedPlan.finalOutput.passes->GetPassCount(), 1u); + + delete depthView; + delete backBufferColorView; +} + TEST(SceneRenderer_Test, DoesNotBuildFullscreenStagesForMultisampledMainSceneSurface) { Scene scene("SceneRendererMultisampledFullscreenStageScene");