refactor(srp): infer fullscreen stages from renderer pass queue

This commit is contained in:
2026-04-21 15:54:26 +08:00
parent f2be5627be
commit 2c49ac58d9
5 changed files with 239 additions and 18 deletions

View File

@@ -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 风格扩展点做准备。

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -141,6 +141,12 @@ namespace XCEngine.Rendering.Universal
rendererFeature.ConfigureCameraFramePlan(
context);
}
if (renderer != null)
{
renderer.FinalizeCameraFramePlanInstance(
context);
}
}
protected virtual ScriptableRenderer CreateRenderer()

View File

@@ -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(