diff --git a/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md b/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md new file mode 100644 index 00000000..2933847d --- /dev/null +++ b/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md @@ -0,0 +1,135 @@ +# 渲染模块长期目标与下一阶段执行计划 + +更新时间: `2026-04-26` + +状态: `Active` + +执行闸门: + +- 每完成一个阶段,先编译并启动 `editor`。 +- 对 `editor` 执行不少于 `12s` 的冒烟测试,确认进程可启动、主循环可持续运行且无立即崩溃。 +- 只有在该阶段编译和 `12s` 冒烟测试都通过后,才允许执行该阶段的 `commit` 和 `push`。 + +说明: 本文基于当前仓库中的实际代码重新审核后编写,用于替代已经失效的 `rendering_srp_urp_closeout_plan.md`。本计划只按现有实现说话,不沿用旧文档中的“已收口完成”结论。 + +## 1. 基于代码的审核结论 + +- 当前默认渲染主线是 + `SceneRenderer -> SceneRenderRequestPlanner -> RenderPipelineHost -> CameraFramePlanBuilder -> CameraRenderer -> ExecuteCameraFrameRenderGraphPlan`,不是“managed URP 直接驱动 native backend”的单线结构。 +- 当前默认顶层 pipeline 已经是 `ScriptableRenderPipelineHost`。在没有显式 preferred asset 时,`RenderPipelineFactory` 会优先尝试 configured managed pipeline asset,失败后回落到 host asset;host 的默认 backend 是 `BuiltinForwardPipelineAsset`。 +- managed SRP 不是空壳。当前已经具备 `ScriptableRenderPipelinePlanningContext`、`RenderSceneSetupContext`、`DirectionalShadowExecutionContext`、`ScriptableRenderContext`,能够参与 request/frame plan、scene setup、shadow policy、scene draw 和 fullscreen stage 录制。 +- 但 managed/backend contract 还没有真正完成。`MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset()` 当前不会返回 shared backend asset,`GetPipelineRendererAssetPolicy()` 在 managed asset 可用时固定返回 `DefaultNativeBackend`。也就是说,现阶段 managed renderer 仍统一依赖默认 native scene backend。 +- `RenderGraph` 的 managed 接口目前是 v1。它能创建 transient texture、记录 scene phase / injection point / native feature pass、按 desc 调 `DrawRenderers`,也能提交 raster pass;但 raster pass execution 仍只覆盖 `ColorScale` / `ShaderVector` / `FinalColor` 三类 fullscreen execution,且 flush 逻辑只对 fullscreen sequence stage 生效。 +- `UniversalRenderPipelineAsset` / `RendererBackedRenderPipelineAsset` 已支持 `rendererDataList`、`defaultRendererIndex`、`UniversalAdditionalCameraData.rendererIndex`,但 `UniversalPostProcessBlock` 仍为空实现,默认后处理主要依赖 feature 注入和 `FinalOutput` 内置 pass。 +- `BuiltinForwardPipeline` 仍同时承担 fallback pipeline、默认 `SceneDrawBackend`、默认 native feature host 三个角色,是当前渲染模块最大的耦合点。 +- editor tooling 仍是独立层。`CameraRenderer` 顶层安装 `BuiltinObjectIdPass`,而 scene viewport 的 grid / selection / helper pass 仍在 editor 专用路径,不应并入本阶段 SRP / URP 主线计划。 + +## 2. 长期目标 + +- 稳定总主线: + `SceneRenderer -> SceneRenderRequestPlanner -> CameraFramePlanBuilder -> CameraRenderer -> ScriptableRenderPipelineHost -> managed stage recorder / native scene draw backend -> RenderGraph / RHI` +- 稳定职责边界: + managed 负责 camera / request / frame plan、renderer 选择、feature / pass 排序、fullscreen stage 组合、scene setup、shadow policy;native 负责 scene extraction、culling、scene draw backend、资源生命周期、RenderGraph 编译执行、RHI。 +- 让 `ScriptableRenderPipelineHost` 成为唯一可信的 SRP 边界,而不是继续把产品层策略散落在 `BuiltinForwardPipeline` 和 editor / runtime 兼容层里。 +- 把 `BuiltinForwardPipeline` 收敛为清晰的 fallback / backend 角色,避免它继续同时承担“默认产品主线”和“可复用 native backend”两套语义。 +- 让 `rendererDataList`、`rendererIndex`、feature invalidation、bridge generation refresh、asset runtime versioning 变成稳定 contract,而不是靠隐式 fallback 维持行为。 +- 如果未来真的需要多个 native backend asset,必须通过明确定义的 backend asset contract 接入,而不是继续复用当前未完成的 placeholder API。 +- 明确 runtime 主渲染与 editor viewport / tooling 的共用层和专用层,避免后续计划再次混写。 + +## 3. 当前约束 + +- 下一阶段不做纯 managed scene draw backend,不承诺把 Gaussian Splat / Volumetric 改写为纯 C# feature。 +- 下一阶段不新增 deferred、clustered lighting、完整 volume framework 等新能力。 +- 下一阶段不改 editor viewport render loop,也不把 grid / selection / helper pass 并入 runtime 主线。 +- 下一阶段不把 managed `RenderGraph` 包装误写成“通用图形 / 计算图 API”;当前它只是 SRP v1 录制接口。 + +## 4. 下一阶段执行计划 + +### 4.1 先把 backend contract 讲真 + +状态: `Completed` (`2026-04-26`) + +目标: 把“当前唯一真实可用的是 `DefaultNativeBackend`”写进代码和测试,不再保留会误导人的伪完成语义。 + +执行: + +- 重新审核 `ManagedRenderPipelineAssetRuntime`、`MonoManagedRenderPipelineAssetRuntime`、`RenderPipelineFactory`、`ScriptableRenderPipelineHost` 的 backend 选择路径。 +- 对未实现的 explicit backend asset 分支做二选一收口:要么真正实现 shared backend asset 解析,要么明确删除 / 禁用该分支并补充注释与测试。 +- 把 `DefaultNativeBackend` 作为当前正式 contract 固化下来,避免再出现“文档已完成、代码仍默认回落”的错位。 + +完成标志: + +- 代码、测试、文档对 backend 选择语义一致。 +- 不再有“managed renderer 已支持 explicit backend asset”这一错误暗示。 + +### 4.2 从 `BuiltinForwardPipeline` 中抽出可复用的 native scene backend + +目标: 减轻 `BuiltinForwardPipeline` 的三重职责,让 host fallback 和 scene draw backend 可以独立演进。 + +执行: + +- 从 `BuiltinForwardSceneSetup`、`BuiltinForwardPipeline`、`NativeSceneRecorder` 梳理出稳定的 `SceneDrawBackend` 组装层。 +- 把 `ShadowCaster`、`DepthOnly`、main scene feature host 的 ownership 拆清楚,避免 host / backend / fallback wrapper 重复配置。 +- 统一 fallback path 和 managed recorder path 对默认 native feature 的消费方式,减少双份默认注册逻辑的漂移风险。 + +完成标志: + +- fallback pipeline 仍可独立工作。 +- managed stage recorder 与 fallback path 共用同一套默认 native backend 组装逻辑。 + +### 4.3 固化 managed SRP v1 能力边界 + +目标: 把现有 managed API 从“隐式能力集合”收敛成明确的 v1 contract,便于后续扩展而不是继续误判能力上限。 + +执行: + +- 整理 `ScriptableRenderContext`、`ScriptableRenderPipelinePlanningContext`、`RenderSceneSetupContext`、`DirectionalShadowExecutionContext` 的能力矩阵。 +- 对“fullscreen raster-only”“仅 fullscreen sequence stage flush managed raster pass”“scene draw 仍依赖 native backend”等限制补齐断言、测试和文档。 +- 补齐 `UniversalPostProcessBlock` 当前的真实语义说明:默认后处理并未在 block 内完成,仍主要依赖 feature / pass 注入与 `FinalOutput` 内置 pass。 + +完成标志: + +- managed 侧能做什么、不能做什么有明确边界。 +- 后续计划不再把现有 API 误写成完整 URP runtime 或通用 RenderGraph API。 + +### 4.4 稳住 renderer authoring / invalidation / bridge refresh + +目标: 把当前已经存在但还容易漂移的 authoring 路径变成可回归验证的稳定面。 + +执行: + +- 围绕 `rendererDataList`、`defaultRendererIndex`、`UniversalAdditionalCameraData.rendererIndex`、feature `SetDirty` / invalidation、bridge generation refresh 增加 focused tests。 +- 验证 default asset selection、explicit managed asset selection、bridge rebinding 这三条路径在 frame plan 和实际渲染行为上保持一致。 +- 明确 renderer 选择、feature 变更和 runtime resource version 之间的刷新链路,避免再次回退到隐式缓存。 + +完成标志: + +- renderer 切换、feature 改动、bridge 切换都有回归保护。 +- `CameraFramePlanBuilder` 和 `CameraRenderer` 对 managed path 的依赖关系稳定可追踪。 + +## 5. 下一阶段完成后的验收口径 + +- `ScriptableRenderPipelineHost` 的角色描述不再含混。 +- `DefaultNativeBackend` 与 explicit backend asset 的语义只保留一种真实状态。 +- `BuiltinForwardPipeline` 不再是继续扩张的“总包类”。 +- managed SRP v1 的能力边界和限制有文档、有测试、有 fail-fast。 +- renderer authoring / invalidation / bridge refresh 不再依赖人工记忆。 + +## 6. 审核依据 + +- `engine/src/Rendering/Execution/SceneRenderer.cpp` +- `engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp` +- `engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp` +- `engine/src/Rendering/Execution/CameraRenderer.cpp` +- `engine/src/Rendering/Internal/RenderPipelineFactory.cpp` +- `engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp` +- `engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp` +- `engine/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.cpp` +- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp` +- `managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs` +- `managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs` +- `managed/XCEngine.ScriptCore/Rendering/Core/RenderSceneSetupContext.cs` +- `managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs` +- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs` +- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs` +- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalPostProcessBlock.cs` diff --git a/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h index 23c9282b..7ca667b5 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h +++ b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h @@ -20,7 +20,9 @@ namespace Pipelines { enum class ManagedPipelineRendererAssetPolicy { Unspecified = 0, + // Runtime resolved a concrete shared native backend asset. ExplicitAsset = 1, + // Runtime explicitly uses the engine default native scene backend. DefaultNativeBackend = 2, }; @@ -110,6 +112,9 @@ public: GetPipelineRendererAsset() const { return nullptr; } + // Optional shared native backend asset for the managed pipeline runtime. + // Current Mono-backed SRP assets typically return nullptr here and rely on + // DefaultNativeBackend instead. virtual std::shared_ptr GetSharedPipelineBackendAsset() const { return GetPipelineRendererAsset(); diff --git a/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp b/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp index 05095323..9396708f 100644 --- a/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp +++ b/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp @@ -9,6 +9,24 @@ namespace XCEngine { namespace Rendering { namespace Pipelines { +namespace { + +std::shared_ptr +ResolveManagedPipelineBackendAsset( + const ManagedRenderPipelineAssetRuntime& runtime) { + switch (runtime.GetPipelineRendererAssetPolicy()) { + case ManagedPipelineRendererAssetPolicy::ExplicitAsset: + return runtime.GetSharedPipelineBackendAsset(); + case ManagedPipelineRendererAssetPolicy::DefaultNativeBackend: + return Internal::CreateDefaultPipelineBackendAsset(); + case ManagedPipelineRendererAssetPolicy::Unspecified: + default: + return runtime.GetSharedPipelineBackendAsset(); + } +} + +} // namespace + ManagedScriptableRenderPipelineAsset::ManagedScriptableRenderPipelineAsset( ManagedRenderPipelineAssetDescriptor descriptor) : m_descriptor(std::move(descriptor)) { @@ -43,21 +61,7 @@ ManagedScriptableRenderPipelineAsset::ResolveSharedPipelineBackendAsset() const if (const std::shared_ptr runtime = ResolveManagedAssetRuntime(); runtime != nullptr) { - if (const std::shared_ptr rendererAsset = - runtime->GetPipelineRendererAsset(); - rendererAsset != nullptr) { - return rendererAsset; - } - - switch (runtime->GetPipelineRendererAssetPolicy()) { - case ManagedPipelineRendererAssetPolicy::ExplicitAsset: - return runtime->GetSharedPipelineBackendAsset(); - case ManagedPipelineRendererAssetPolicy::DefaultNativeBackend: - return Internal::CreateDefaultPipelineBackendAsset(); - case ManagedPipelineRendererAssetPolicy::Unspecified: - default: - return nullptr; - } + return ResolveManagedPipelineBackendAsset(*runtime); } return nullptr; diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index e869fb9b..045ce2c4 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -1702,9 +1702,6 @@ private: mutable int32_t m_runtimeResourceVersion = 0; mutable uint32_t m_pipelineHandle = 0; mutable bool m_pipelineCreationAttempted = false; - mutable bool m_sharedPipelineBackendAssetResolved = false; - mutable std::shared_ptr - m_sharedPipelineBackendAsset = nullptr; }; class MonoManagedRenderPipelineStageRecorder final @@ -2043,21 +2040,15 @@ private: return m_boundSceneDrawBackend; } - const std::shared_ptr - sharedPipelineBackendAsset = - m_assetRuntime != nullptr - ? m_assetRuntime->GetPipelineRendererAsset() - : nullptr; const std::shared_ptr resolvedPipelineBackendAsset = - sharedPipelineBackendAsset != nullptr - ? sharedPipelineBackendAsset - : m_assetRuntime != nullptr && - m_assetRuntime->GetPipelineRendererAssetPolicy() == + m_assetRuntime == nullptr + ? nullptr + : m_assetRuntime->GetPipelineRendererAssetPolicy() == Rendering::Pipelines::ManagedPipelineRendererAssetPolicy:: DefaultNativeBackend ? Rendering::Internal::CreateDefaultPipelineBackendAsset() - : nullptr; + : m_assetRuntime->GetSharedPipelineBackendAsset(); if (resolvedPipelineBackendAsset == nullptr) { return nullptr; } @@ -2335,21 +2326,15 @@ MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAsset() const { return nullptr; } - if (m_sharedPipelineBackendAssetResolved) { - return m_sharedPipelineBackendAsset; - } - - if (GetManagedAssetObject() == nullptr) { - return nullptr; - } - - m_sharedPipelineBackendAssetResolved = true; - return m_sharedPipelineBackendAsset; + // Current Mono-backed SRP assets do not materialize a shared native + // backend asset. They bind the engine default native backend explicitly + // through DefaultNativeBackend. + return nullptr; } Rendering::Pipelines::ManagedPipelineRendererAssetPolicy MonoManagedRenderPipelineAssetRuntime::GetPipelineRendererAssetPolicy() const { - return EnsureManagedAsset() + return SyncManagedAssetRuntimeState() ? Rendering::Pipelines::ManagedPipelineRendererAssetPolicy::DefaultNativeBackend : Rendering::Pipelines::ManagedPipelineRendererAssetPolicy::Unspecified; } @@ -2491,8 +2476,6 @@ bool MonoManagedRenderPipelineAssetRuntime::SyncManagedAssetRuntimeState() const } ReleaseManagedPipeline(); - m_sharedPipelineBackendAsset.reset(); - m_sharedPipelineBackendAssetResolved = false; m_runtimeResourceVersion = runtimeResourceVersion; return true; } @@ -2560,8 +2543,6 @@ void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedAsset() const { m_getRuntimeResourceVersionMethod = nullptr; m_configureRenderSceneSetupMethod = nullptr; m_configureDirectionalShadowExecutionStateMethod = nullptr; - m_sharedPipelineBackendAsset.reset(); - m_sharedPipelineBackendAssetResolved = false; m_runtimeResourceVersionResolved = false; m_runtimeResourceVersion = 0; const bool ownsManagedAssetHandle = m_ownsManagedAssetHandle; diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 2ccede26..1a0cad04 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -820,10 +820,14 @@ struct MockManagedRenderPipelineAssetRuntimeState { int getPipelineRendererAssetCalls = 0; int getDefaultFinalColorSettingsCalls = 0; bool hasDefaultFinalColorSettings = false; + bool hasPipelineRendererAssetPolicyOverride = false; FinalColorSettings defaultFinalColorSettings = {}; size_t lastRenderedBaseCameraCount = 0u; size_t lastRenderedRequestCount = 0u; std::shared_ptr pipelineRendererAsset = nullptr; + Pipelines::ManagedPipelineRendererAssetPolicy + pipelineRendererAssetPolicy = + Pipelines::ManagedPipelineRendererAssetPolicy::Unspecified; std::shared_ptr lastCreatedStageRecorderState; std::functionpipelineRendererAsset; } + Pipelines::ManagedPipelineRendererAssetPolicy + GetPipelineRendererAssetPolicy() const override { + if (m_state->hasPipelineRendererAssetPolicyOverride) { + return m_state->pipelineRendererAssetPolicy; + } + + return ManagedRenderPipelineAssetRuntime::GetPipelineRendererAssetPolicy(); + } + bool TryGetDefaultFinalColorSettings( FinalColorSettings& settings) const override { ++m_state->getDefaultFinalColorSettingsCalls; @@ -892,8 +905,12 @@ struct MockManagedRenderPipelineBridgeState { Pipelines::ManagedRenderPipelineAssetDescriptor lastDescriptor = {}; std::shared_ptr lastCreatedRuntimeState; bool hasDefaultFinalColorSettings = false; + bool hasPipelineRendererAssetPolicyOverride = false; FinalColorSettings defaultFinalColorSettings = {}; std::shared_ptr pipelineRendererAsset = nullptr; + Pipelines::ManagedPipelineRendererAssetPolicy + pipelineRendererAssetPolicy = + Pipelines::ManagedPipelineRendererAssetPolicy::Unspecified; std::function(); m_state->lastCreatedRuntimeState->hasDefaultFinalColorSettings = m_state->hasDefaultFinalColorSettings; + m_state->lastCreatedRuntimeState + ->hasPipelineRendererAssetPolicyOverride = + m_state->hasPipelineRendererAssetPolicyOverride; m_state->lastCreatedRuntimeState->defaultFinalColorSettings = m_state->defaultFinalColorSettings; m_state->lastCreatedRuntimeState->pipelineRendererAsset = m_state->pipelineRendererAsset; + m_state->lastCreatedRuntimeState->pipelineRendererAssetPolicy = + m_state->pipelineRendererAssetPolicy; m_state->lastCreatedRuntimeState->configureCameraRenderRequest = m_state->configureCameraRenderRequest; return std::make_shared( @@ -4775,6 +4797,42 @@ TEST( Pipelines::ClearManagedRenderPipelineBridge(); } +TEST( + ManagedScriptableRenderPipelineAsset_Test, + UsesDefaultNativeBackendForHostCompositionWhenRuntimeDeclaresDefaultNativeBackendPolicy) { + Pipelines::ClearManagedRenderPipelineBridge(); + + const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedRenderPipelineProbeAsset" + }; + auto bridgeState = std::make_shared(); + bridgeState->hasPipelineRendererAssetPolicyOverride = true; + bridgeState->pipelineRendererAssetPolicy = + Pipelines::ManagedPipelineRendererAssetPolicy::DefaultNativeBackend; + Pipelines::SetManagedRenderPipelineBridge( + std::make_shared(bridgeState)); + + Pipelines::ManagedScriptableRenderPipelineAsset asset(descriptor); + std::unique_ptr pipeline = asset.CreatePipeline(); + ASSERT_NE(pipeline, nullptr); + + auto runtimeState = bridgeState->lastCreatedRuntimeState; + ASSERT_NE(runtimeState, nullptr); + auto* host = + dynamic_cast(pipeline.get()); + ASSERT_NE(host, nullptr); + EXPECT_EQ( + host->GetPipelineBackendAsset(), + Internal::CreateDefaultPipelineBackendAsset().get()); + EXPECT_NE(host->GetPipelineBackend(), nullptr); + EXPECT_NE(host->GetStageRecorder(), nullptr); + EXPECT_EQ(runtimeState->getPipelineRendererAssetCalls, 0); + + Pipelines::ClearManagedRenderPipelineBridge(); +} + TEST(ManagedScriptableRenderPipelineAsset_Test, LetsManagedBridgeConfigureCameraRenderRequests) { Pipelines::ClearManagedRenderPipelineBridge(); diff --git a/tests/scripting/test_project_script_assembly.cpp b/tests/scripting/test_project_script_assembly.cpp index fc14078a..04e7d527 100644 --- a/tests/scripting/test_project_script_assembly.cpp +++ b/tests/scripting/test_project_script_assembly.cpp @@ -241,7 +241,11 @@ TEST_F( std::shared_ptr assetRuntime = bridge->CreateAssetRuntime(descriptor); ASSERT_NE(assetRuntime, nullptr); - EXPECT_NE(assetRuntime->GetPipelineRendererAsset(), nullptr); + EXPECT_EQ(assetRuntime->GetPipelineRendererAsset(), nullptr); + EXPECT_EQ( + assetRuntime->GetPipelineRendererAssetPolicy(), + XCEngine::Rendering::Pipelines::ManagedPipelineRendererAssetPolicy:: + DefaultNativeBackend); std::unique_ptr recorder = assetRuntime->CreateStageRecorder(); @@ -333,7 +337,11 @@ TEST_F( std::shared_ptr assetRuntime = bridge->CreateAssetRuntime(descriptor); ASSERT_NE(assetRuntime, nullptr); - EXPECT_NE(assetRuntime->GetPipelineRendererAsset(), nullptr); + EXPECT_EQ(assetRuntime->GetPipelineRendererAsset(), nullptr); + EXPECT_EQ( + assetRuntime->GetPipelineRendererAssetPolicy(), + XCEngine::Rendering::Pipelines::ManagedPipelineRendererAssetPolicy:: + DefaultNativeBackend); std::unique_ptr recorder = assetRuntime->CreateStageRecorder();