diff --git a/docs/used/SRP_URP_StageScopedPassCollectionPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_StageScopedPassCollectionPlan_2026-04-22_完成归档.md new file mode 100644 index 00000000..caaff87b --- /dev/null +++ b/docs/used/SRP_URP_StageScopedPassCollectionPlan_2026-04-22_完成归档.md @@ -0,0 +1,85 @@ +# SRP / URP Stage-Scoped Pass Collection Plan + +时间:2026-04-22 + +## 背景 + +上一阶段已经把 managed renderer 的执行组织收成了 renderer block: + +- `ShadowCaster` +- `DepthPrepass` +- `MainOpaque` +- `MainSkybox` +- `MainTransparent` +- `PostProcess` +- `FinalOutput` + +但当前还有一个没有完全收口的问题: + +1. native C++ 这一层仍然按 `CameraFrameStage` 分阶段调用 managed renderer。 +2. `ScriptableRenderer.BuildPassQueue(...)` 也是每个 stage 都会执行一次。 +3. 可现在 renderer / feature 仍然可能在这个阶段里把所有 stage 的 pass 都先塞进总队列,再交给后面的 block 过滤。 + +这意味着: + +- 组织方式虽然已经比之前清晰,但 pass collection 仍然混着跨 stage 的内容。 +- `UniversalRenderer` 自己对 block 的 ownership 也还不够直接。 +- 后续把 shadow / volumetric / gaussian 等能力继续往 URP 层迁时,边界会继续发虚。 + +## 本阶段目标 + +把 managed renderer 的 pass collection 也收成 stage-scoped,并同步把 `UniversalRenderer` 的内建 pass 入口拆成更清晰的 block-owned 结构。 + +目标结果: + +1. 当前 stage 构建 pass queue 时,不再混入明显属于其他 stage 的 pass。 +2. `UniversalRenderer` 明确区分 shadow / depth / main scene / final output 这些 block-owned 入口。 +3. 继续保留现在的 `RenderPassEvent + RenderBlocks` 路线,不引入额外兼容层。 + +## 范围 + +本阶段只处理 managed SRP / URP 的 pass collection 与 block ownership,不做: + +- deferred rendering +- 阴影算法迁移 +- editor-only 渲染能力迁移 +- C++ RenderGraph 功能扩展 + +## 实施步骤 + +### 1. ScriptableRenderer pass collection stage-scoped 化 + +重构 `ScriptableRenderer`: + +- 在 build active pass queue 时记录当前 `RenderingData.stage` +- `EnqueuePass(...)` 在 pass queue build 期间只接受与当前 stage 匹配的 pass +- 非 build 期间保持现有安全行为 + +这样可以让当前 stage 的 queue 从源头上变干净,而不是先混入、后过滤。 + +### 2. UniversalRenderer 显式按 block 组织内建 pass + +重构 `UniversalRenderer.AddRenderPasses(...)`: + +- `ShadowCaster` 只处理 shadow block +- `DepthOnly` 只处理 depth prepass block +- `MainScene` 只处理 opaque / skybox / transparent +- `FinalOutput` 只处理 final output block + +必要时同步清理内建 feature 的 stage 边界判断,避免继续依赖下游过滤。 + +### 3. 验证主线不回退 + +要求: + +1. `XCEditor` Debug 构建通过 +2. old editor 冒烟至少 10 秒 +3. `editor.log` 出现 `SceneReady` + +## 完成标准 + +满足以下条件才算本阶段收口: + +1. active pass queue 的 collection 已经是 stage-scoped。 +2. `UniversalRenderer` 内建 pass ownership 更直接,不再在单个入口里混着所有 stage。 +3. `XCEditor` 编译与 old editor 冒烟通过。 diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs index 46dc72b9..6ede0104 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs @@ -34,7 +34,9 @@ namespace XCEngine.Rendering.Universal ScriptableRenderer renderer, RenderingData renderingData) { - if (renderer == null) + if (renderer == null || + renderingData == null || + !renderingData.isMainSceneStage) { return; } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs index 51aac959..439aff89 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs @@ -34,7 +34,9 @@ namespace XCEngine.Rendering.Universal ScriptableRenderer renderer, RenderingData renderingData) { - if (renderer == null) + if (renderer == null || + renderingData == null || + !renderingData.isMainSceneStage) { return; } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs index 36342433..45e8dbae 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs @@ -113,7 +113,8 @@ namespace XCEngine.Rendering.Universal RenderingData renderingData) { if (renderer == null || - renderingData == null) + renderingData == null || + !renderingData.isPostProcessStage) { return; } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsRendererFeature.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsRendererFeature.cs index 0a88be0c..d7f2a705 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsRendererFeature.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsRendererFeature.cs @@ -136,6 +136,12 @@ namespace XCEngine.Rendering.Universal scenePhase, BuildRendererListDesc(), BuildDrawingSettings()); + if (!m_pass.SupportsStage( + renderingData.stage)) + { + return; + } + renderer.EnqueuePass(m_pass); } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs index f0c2bccb..18a305cf 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs @@ -12,6 +12,9 @@ namespace XCEngine.Rendering.Universal new List(); private readonly RendererBlocks m_rendererBlocks = new RendererBlocks(); + private bool m_isBuildingPassQueue; + private CameraFrameStage m_passQueueStage = + CameraFrameStage.MainScene; private bool m_disposed; protected ScriptableRenderer() @@ -50,6 +53,19 @@ namespace XCEngine.Rendering.Universal return; } + if (m_isBuildingPassQueue && + !renderPass.SupportsStage(m_passQueueStage)) + { + return; + } + + InsertActivePass( + renderPass); + } + + private void InsertActivePass( + ScriptableRenderPass renderPass) + { int insertIndex = m_activePassQueue.Count; while (insertIndex > 0 && m_activePassQueue[insertIndex - 1].renderPassEvent > @@ -331,22 +347,37 @@ namespace XCEngine.Rendering.Universal RenderingData renderingData) { m_activePassQueue.Clear(); + m_rendererBlocks.Clear(); + m_isBuildingPassQueue = true; + m_passQueueStage = + renderingData != null + ? renderingData.stage + : CameraFrameStage.MainScene; - for (int i = 0; i < m_features.Count; ++i) + try { - ScriptableRendererFeature feature = m_features[i]; - if (feature == null || !feature.isActive) + for (int i = 0; i < m_features.Count; ++i) { - continue; + ScriptableRendererFeature feature = m_features[i]; + if (feature == null || !feature.isActive) + { + continue; + } + + feature.AddRenderPasses( + this, + renderingData); } - feature.AddRenderPasses( - this, - renderingData); + AddRenderPasses(renderingData); + m_rendererBlocks.Build(m_activePassQueue); + } + finally + { + m_isBuildingPassQueue = false; + m_passQueueStage = + CameraFrameStage.MainScene; } - - AddRenderPasses(renderingData); - m_rendererBlocks.Build(m_activePassQueue); } protected virtual void ReleaseRuntimeResources() diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs index 424c53ba..4e3f6305 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs @@ -145,37 +145,36 @@ namespace XCEngine.Rendering.Universal DepthPrepassBlockData depthPrepass = m_rendererData.GetDepthPrepassBlockInstance(); - if (shadowCaster.enabled) + switch (renderingData.stage) { - EnqueueShadowCasterPasses(shadowCaster); + case CameraFrameStage.ShadowCaster: + EnqueueShadowCasterBlockPasses( + shadowCaster); + break; + case CameraFrameStage.DepthOnly: + EnqueueDepthPrepassBlockPasses( + depthPrepass); + break; + case CameraFrameStage.MainScene: + EnqueueMainSceneBlockPasses( + mainScene); + break; + case CameraFrameStage.FinalOutput: + EnqueueFinalOutputBlockPasses( + renderingData); + break; } - - if (depthPrepass.enabled) - { - EnqueueDepthPrepass(depthPrepass); - } - - if (mainScene.renderOpaque) - { - EnqueueOpaquePasses(mainScene); - } - - if (mainScene.renderSkybox) - { - EnqueueSkyboxPasses(mainScene); - } - - if (mainScene.renderTransparent) - { - EnqueueTransparentPasses(mainScene); - } - - EnqueueFinalOutputPasses(renderingData); } - private void EnqueueShadowCasterPasses( + private void EnqueueShadowCasterBlockPasses( ShadowCasterBlockData shadowCaster) { + if (shadowCaster == null || + !shadowCaster.enabled) + { + return; + } + m_drawShadowCasterPass.Configure( shadowCaster.passEvent, SceneRenderPhase.Opaque, @@ -184,9 +183,15 @@ namespace XCEngine.Rendering.Universal EnqueuePass(m_drawShadowCasterPass); } - private void EnqueueDepthPrepass( + private void EnqueueDepthPrepassBlockPasses( DepthPrepassBlockData depthPrepass) { + if (depthPrepass == null || + !depthPrepass.enabled) + { + return; + } + m_drawDepthPrepass.Configure( depthPrepass.passEvent, SceneRenderPhase.Opaque, @@ -195,7 +200,31 @@ namespace XCEngine.Rendering.Universal EnqueuePass(m_drawDepthPrepass); } - private void EnqueueOpaquePasses( + private void EnqueueMainSceneBlockPasses( + UniversalMainSceneData mainScene) + { + if (mainScene == null) + { + return; + } + + if (mainScene.renderOpaque) + { + EnqueueMainOpaquePasses(mainScene); + } + + if (mainScene.renderSkybox) + { + EnqueueMainSkyboxPasses(mainScene); + } + + if (mainScene.renderTransparent) + { + EnqueueMainTransparentPasses(mainScene); + } + } + + private void EnqueueMainOpaquePasses( UniversalMainSceneData mainScene) { m_drawOpaqueObjectsPass.Configure( @@ -206,7 +235,7 @@ namespace XCEngine.Rendering.Universal EnqueuePass(m_drawOpaqueObjectsPass); } - private void EnqueueSkyboxPasses( + private void EnqueueMainSkyboxPasses( UniversalMainSceneData mainScene) { m_drawSkyboxPass.Configure( @@ -214,7 +243,7 @@ namespace XCEngine.Rendering.Universal EnqueuePass(m_drawSkyboxPass); } - private void EnqueueTransparentPasses( + private void EnqueueMainTransparentPasses( UniversalMainSceneData mainScene) { m_drawTransparentObjectsPass.Configure( @@ -313,7 +342,7 @@ namespace XCEngine.Rendering.Universal CameraProjectionType.Perspective; } - private void EnqueueFinalOutputPasses( + private void EnqueueFinalOutputBlockPasses( RenderingData renderingData) { if (renderingData == null)