From 2c49ac58d9d553b7c1b372b6cdcf5af8ddea97da Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 21 Apr 2026 15:54:26 +0800 Subject: [PATCH] refactor(srp): infer fullscreen stages from renderer pass queue --- ...nStageInferencePlan_完成归档_2026-04-21.md | 96 ++++++++++++++ .../ColorScalePostProcessRendererFeature.cs | 16 --- .../Rendering/Universal/ScriptableRenderer.cs | 123 ++++++++++++++++++ .../Universal/ScriptableRendererData.cs | 6 + .../Rendering/Universal/UniversalRenderer.cs | 16 ++- 5 files changed, 239 insertions(+), 18 deletions(-) create mode 100644 docs/used/SRP_UnityStyleURPFullscreenStageInferencePlan_完成归档_2026-04-21.md diff --git a/docs/used/SRP_UnityStyleURPFullscreenStageInferencePlan_完成归档_2026-04-21.md b/docs/used/SRP_UnityStyleURPFullscreenStageInferencePlan_完成归档_2026-04-21.md new file mode 100644 index 00000000..8c2f1c3b --- /dev/null +++ b/docs/used/SRP_UnityStyleURPFullscreenStageInferencePlan_完成归档_2026-04-21.md @@ -0,0 +1,96 @@ +# SRP Unity-Style URP Fullscreen Stage Inference Plan + +日期:2026-04-21 + +## 阶段目标 + +继续沿着 Unity 的 `ScriptableRenderer + RendererFeature + RenderPassEvent` 模型收敛,解决当前 fullscreen stage 仍然需要 feature 手写 `ConfigureCameraFramePlan(...)` 的问题。 + +本阶段的核心目标是: + +- 让 `ScriptableRenderer` 能基于 pass queue 自动推导 `PostProcess / FinalOutput` 这类 fullscreen stage 需求 +- 让 `UniversalRenderer` 在 feature 参与之后再完成 final output 的依赖收口 +- 减少 feature 作者同时维护 “AddRenderPasses + ConfigureCameraFramePlan” 两套逻辑的重复负担 + +## 当前问题 + +当前结构里,feature 如果要往 `PostProcess` 塞一个 pass,通常必须同时做两件事: + +1. 在 `AddRenderPasses(...)` 里 enqueue pass +2. 在 `ConfigureCameraFramePlan(...)` 里手动请求 `PostProcess` + +这不符合 Unity 的主思路。Unity 里用户主要写的是: + +- `RendererFeature` +- `ScriptableRenderPass` +- `renderPassEvent` + +而不是再额外手工参与底层 stage existence planning。 + +当前这套方式还有一个实际问题: + +1. `UniversalRenderer` 现在已经接管了 `FinalOutput` +2. 但 feature 对 `PostProcess` 的请求发生在 renderer 默认规划之后 +3. 这会让 `FinalOutput` 难以及时感知 feature 注入出来的 `PostProcess` 依赖 + +## 本阶段实施内容 + +### 1. 增加 renderer 规划收尾阶段 + +在 renderer 规划流程中补一个 finalize/fixup 阶段: + +1. renderer 先做 core default planning +2. feature 再做显式 override +3. renderer 最后基于 pass queue 和当前 stage 状态做依赖收口 + +这样 renderer 才能在 feature 参与后,看到最终的 fullscreen stage 需求。 + +### 2. 基于 pass queue 自动推导 fullscreen stage + +让 `ScriptableRenderer` 使用已经 enqueue 的 pass 队列自动推导: + +1. 是否需要 `PostProcess` +2. 是否需要 `FinalOutput` + +优先先做 fullscreen stage,不一口气泛化到所有 stage。 + +### 3. 清理 feature 侧重复请求 + +拿 `ColorScalePostProcessRendererFeature` 作为第一批清理对象: + +1. 保留 `AddRenderPasses(...)` +2. 去掉手写的 `ConfigureCameraFramePlan(...)` +3. 改由 renderer 自动根据 pass queue 推导 `PostProcess` + +### 4. 让 UniversalRenderer 在 finalize 阶段收 final output + +`UniversalRenderer` 的 `FinalOutput` 依赖不再在 feature 之前就定死,而是在 fullscreen stage 自动推导之后再做: + +1. 若 feature 导致 `PostProcess` 存在 +2. 且 pipeline 需要 final color processing +3. 则 `FinalOutput` 应该正确依赖 `PostProcessColor` + +## 本阶段不做的事情 + +1. 不做 deferred renderer +2. 不做 GBuffer +3. 不做 shadow pass managed 化 +4. 不重写完整的 Unity RenderPassEvent 大表 +5. 不一口气移除所有 `ConfigureCameraFramePlan(...)` + +## 完成标准 + +1. renderer 规划流程具备 finalize/fixup 阶段 +2. `ScriptableRenderer` 能基于 pass queue 自动推导 `PostProcess / FinalOutput` +3. `ColorScalePostProcessRendererFeature` 不再手写请求 `PostProcess` +4. `UniversalRenderer` 在 finalize 阶段正确收口 final output 依赖 +5. 旧版 `XCEditor` Debug 编译通过 +6. 旧版编辑器冒烟 10 秒以上,并在新的 `editor.log` 中出现 `SceneReady` + +## 下一阶段前置条件 + +只有这一阶段收口后,再进入下一个更接近 Unity URP 的阶段: + +- `Renderer Block / Event Semantics Expansion` + +也就是进一步把 renderer 的 block 边界和 `RenderPassEvent` 语义扩全,为未来的不同 renderer variant 和更完整的 URP 风格扩展点做准备。 diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs index ad02796c..a7b6b402 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs @@ -54,22 +54,6 @@ namespace XCEngine.Rendering.Universal new ColorScalePostProcessPass(this); } - public override void ConfigureCameraFramePlan( - ScriptableRenderPipelinePlanningContext context) - { - if (context == null || - context.IsStageRequested( - CameraFrameStage.PostProcess)) - { - return; - } - - context.RequestFullscreenStage( - CameraFrameStage.PostProcess, - CameraFrameColorSource.MainSceneColor, - context.HasFinalColorProcessing()); - } - public override void AddRenderPasses( ScriptableRenderer renderer, RenderingData renderingData) diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs index 32ce58c9..93311065 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs @@ -95,6 +95,12 @@ namespace XCEngine.Rendering.Universal ConfigureCameraFramePlan(context); } + internal void FinalizeCameraFramePlanInstance( + ScriptableRenderPipelinePlanningContext context) + { + FinalizeCameraFramePlan(context); + } + internal bool RecordRendererInstance( RendererRecordingContext context) { @@ -181,6 +187,18 @@ namespace XCEngine.Rendering.Universal { } + protected virtual void FinalizeCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + if (context == null) + { + return; + } + + ApplyInferredFullscreenStageRequests( + context); + } + private void BuildPassQueue( RenderingData renderingData) { @@ -205,6 +223,111 @@ namespace XCEngine.Rendering.Universal protected virtual void ReleaseRuntimeResources() { } + + protected virtual RenderingData CreatePlanningRenderingData( + ScriptableRenderPipelinePlanningContext context) + { + return context != null + ? new RenderingData( + CameraFrameStage.MainScene, + context.rendererIndex) + : null; + } + + private void ApplyInferredFullscreenStageRequests( + ScriptableRenderPipelinePlanningContext context) + { + RenderingData planningData = + CreatePlanningRenderingData(context); + if (planningData == null) + { + return; + } + + BuildPassQueue(planningData); + + bool hasPostProcessPass = + HasQueuedPassForStage( + CameraFrameStage.PostProcess); + bool hasFinalOutputPass = + HasQueuedPassForStage( + CameraFrameStage.FinalOutput); + bool needsFinalOutputDependency = + context.HasFinalColorProcessing() || + context.IsStageRequested( + CameraFrameStage.FinalOutput) || + hasFinalOutputPass; + + if (hasPostProcessPass) + { + EnsureInferredPostProcessStage( + context, + needsFinalOutputDependency); + } + + if (hasFinalOutputPass && + !context.IsStageRequested( + CameraFrameStage.FinalOutput)) + { + context.RequestFullscreenStage( + CameraFrameStage.FinalOutput, + context.IsStageRequested( + CameraFrameStage.PostProcess) + ? CameraFrameColorSource.PostProcessColor + : CameraFrameColorSource.MainSceneColor); + } + } + + private bool HasQueuedPassForStage( + CameraFrameStage stage) + { + for (int i = 0; i < m_activePassQueue.Count; ++i) + { + ScriptableRenderPass renderPass = + m_activePassQueue[i]; + if (renderPass != null && + renderPass.SupportsStage(stage)) + { + return true; + } + } + + return false; + } + + private static void EnsureInferredPostProcessStage( + ScriptableRenderPipelinePlanningContext context, + bool needsGraphManagedOutputColor) + { + if (!context.IsStageRequested( + CameraFrameStage.PostProcess)) + { + context.RequestFullscreenStage( + CameraFrameStage.PostProcess, + CameraFrameColorSource.MainSceneColor, + needsGraphManagedOutputColor); + return; + } + + CameraFrameColorSource source = + context.GetStageColorSource( + CameraFrameStage.PostProcess); + if (!needsGraphManagedOutputColor || + source == + CameraFrameColorSource.ExplicitSurface || + context.UsesGraphManagedOutputColor( + CameraFrameStage.PostProcess)) + { + return; + } + + context.ClearFullscreenStage( + CameraFrameStage.PostProcess); + context.RequestFullscreenStage( + CameraFrameStage.PostProcess, + source, + true); + } } } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs index 57262aa7..d27db1a5 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs @@ -141,6 +141,12 @@ namespace XCEngine.Rendering.Universal rendererFeature.ConfigureCameraFramePlan( context); } + + if (renderer != null) + { + renderer.FinalizeCameraFramePlanInstance( + context); + } } protected virtual ScriptableRenderer CreateRenderer() diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs index bf6b8974..2d62e909 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs @@ -39,6 +39,17 @@ namespace XCEngine.Rendering.Universal } ConfigureShadowCasterStage(context); + } + + protected override void FinalizeCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + if (context == null) + { + return; + } + + base.FinalizeCameraFramePlan(context); ConfigureFinalOutputStage(context); } @@ -117,13 +128,14 @@ namespace XCEngine.Rendering.Universal private static void ConfigureFinalOutputStage( ScriptableRenderPipelinePlanningContext context) { - context.ClearFullscreenStage( - CameraFrameStage.FinalOutput); if (!context.HasFinalColorProcessing()) { return; } + context.ClearFullscreenStage( + CameraFrameStage.FinalOutput); + CameraFrameColorSource finalOutputSource = CameraFrameColorSource.MainSceneColor; if (context.IsStageRequested(