refactor(srp): formalize renderer block recording
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# SRP / URP Renderer Block Formalization Plan
|
||||
|
||||
时间:2026-04-22
|
||||
|
||||
## 背景
|
||||
|
||||
当前渲染主线已经完成了两件更底层的事情:
|
||||
|
||||
1. C++ 侧的 RenderGraph / runtime resource / stage contract 已经能稳定承接 managed SRP。
|
||||
2. managed 侧也已经有 `ScriptableRenderer / RendererFeature / RenderPass` 这套骨架,并且 post-process / final output 的 graph 绑定已经打通。
|
||||
|
||||
但 renderer 这一层还有一个明显的问题没有收口:
|
||||
|
||||
- `ScriptableRenderer` 现在还是把所有 pass 丢进一个总队列,然后在录制时按 `CameraFrameStage` 逐个扫描、过滤。
|
||||
- 这意味着 renderer 的“组织单位”依然是 stage,而不是 renderer 自己拥有的 block。
|
||||
- 对后续把阴影、体积、Gaussian、更多 scene feature 逐步迁到 URP 层不够友好,因为缺少稳定的 block 落点。
|
||||
|
||||
这一步的目标不是继续扩 RenderGraph 功能,也不是开始 deferred,而是先把 managed renderer 的组织方式收紧。
|
||||
|
||||
## 本阶段目标
|
||||
|
||||
把 managed `ScriptableRenderer` 从“单队列 + stage 过滤”重构成“显式 renderer block 录制”,并保持当前 C++ stage 边界不变。
|
||||
|
||||
目标结果:
|
||||
|
||||
1. `ScriptableRenderer` 内部显式识别 `ShadowCaster / DepthPrepass / MainOpaque / MainSkybox / MainTransparent / PostProcess / FinalOutput`。
|
||||
2. stage 仍然只是 native 执行边界,renderer 内部真正按 block 组织与录制。
|
||||
3. `UniversalRenderer` 后续迁移阴影、体积、Gaussian 等逻辑时,有稳定的 block 入口可以承接。
|
||||
4. 不引入新的兼容层,不保留“旧逻辑先留着”的临时路线。
|
||||
|
||||
## 范围
|
||||
|
||||
本阶段只做 managed SRP / URP 组织重构,不做:
|
||||
|
||||
- deferred rendering
|
||||
- shadow 算法升级
|
||||
- editor 侧面板与资源工作流
|
||||
- ObjectId 这类 editor-only 特性迁移
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 1. 补齐 renderer block 基础类型
|
||||
|
||||
新增 renderer block 枚举与辅助工具,统一维护:
|
||||
|
||||
- pass event -> renderer block
|
||||
- renderer block -> camera frame stage
|
||||
- renderer block 的运行时范围描述
|
||||
|
||||
### 2. 重构 ScriptableRenderer 的录制入口
|
||||
|
||||
保留 active pass queue 作为排序输入,但录制逻辑改为:
|
||||
|
||||
1. 先按 `RenderPassEvent` 排序构建 active pass queue
|
||||
2. 再根据 event range 构建 renderer block range
|
||||
3. 按当前 stage 对应的 block 顺序执行
|
||||
|
||||
也就是说:
|
||||
|
||||
- `MainScene` 不再是“扫完整队列找属于 MainScene 的 pass”
|
||||
- 而是显式录制 `MainOpaque -> MainSkybox -> MainTransparent`
|
||||
|
||||
### 3. 清理 stage-driven 痕迹
|
||||
|
||||
重点清理:
|
||||
|
||||
- `ScriptableRenderer` 内部 `renderPass.SupportsStage(...)` 式的全队列过滤
|
||||
- 把 `SupportsRendererRecording / RecordRenderer` 改成基于 block 判断
|
||||
|
||||
保留:
|
||||
|
||||
- native C++ 仍然通过 `CameraFrameStage` 调用 managed
|
||||
- `ScriptableRenderPass.SupportsStage(...)` 作为公共 API 兼容入口,但内部改为基于 block 推导
|
||||
|
||||
### 4. 验证主线不回退
|
||||
|
||||
要求:
|
||||
|
||||
1. `XCEditor` Debug 构建通过
|
||||
2. old editor 冒烟运行至少 10 秒
|
||||
3. `editor.log` 出现 `SceneReady`
|
||||
|
||||
## 完成标准
|
||||
|
||||
满足以下条件才算本阶段收口:
|
||||
|
||||
1. `ScriptableRenderer` 不再通过“单队列 + stage 过滤”驱动录制。
|
||||
2. renderer block 成为 managed renderer 内部的正式组织单位。
|
||||
3. `UniversalRenderer` 当前默认路径行为不回退。
|
||||
4. `XCEditor` 构建与 old editor 冒烟通过。
|
||||
@@ -235,8 +235,10 @@ set(XCENGINE_RENDER_PIPELINES_UNIVERSAL_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderEnvironmentMode.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderPassEvent.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsRendererFeature.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBlock.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipeline.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBlocks.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererCameraRequestContext.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererDrivenRenderPipeline.cs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererRecordingContext.cs
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using XCEngine.Rendering;
|
||||
|
||||
namespace XCEngine.Rendering.Universal
|
||||
{
|
||||
internal enum RendererBlock
|
||||
{
|
||||
ShadowCaster = 0,
|
||||
DepthPrepass = 1,
|
||||
MainOpaque = 2,
|
||||
MainSkybox = 3,
|
||||
MainTransparent = 4,
|
||||
PostProcess = 5,
|
||||
FinalOutput = 6,
|
||||
Count = 7
|
||||
}
|
||||
|
||||
internal static class RendererBlockUtility
|
||||
{
|
||||
public static CameraFrameStage GetStage(
|
||||
RendererBlock block)
|
||||
{
|
||||
switch (block)
|
||||
{
|
||||
case RendererBlock.ShadowCaster:
|
||||
return CameraFrameStage.ShadowCaster;
|
||||
case RendererBlock.DepthPrepass:
|
||||
return CameraFrameStage.DepthOnly;
|
||||
case RendererBlock.MainOpaque:
|
||||
case RendererBlock.MainSkybox:
|
||||
case RendererBlock.MainTransparent:
|
||||
return CameraFrameStage.MainScene;
|
||||
case RendererBlock.PostProcess:
|
||||
return CameraFrameStage.PostProcess;
|
||||
case RendererBlock.FinalOutput:
|
||||
return CameraFrameStage.FinalOutput;
|
||||
default:
|
||||
return CameraFrameStage.MainScene;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XCEngine.Rendering.Universal
|
||||
{
|
||||
internal sealed class RendererBlocks
|
||||
{
|
||||
private struct BlockRange
|
||||
{
|
||||
public int firstPassIndex;
|
||||
public int lastPassIndex;
|
||||
}
|
||||
|
||||
private readonly BlockRange[] m_ranges =
|
||||
new BlockRange[(int)RendererBlock.Count];
|
||||
|
||||
public RendererBlocks()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < m_ranges.Length; ++i)
|
||||
{
|
||||
m_ranges[i].firstPassIndex = -1;
|
||||
m_ranges[i].lastPassIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void Build(
|
||||
IList<ScriptableRenderPass> activePassQueue)
|
||||
{
|
||||
Clear();
|
||||
|
||||
if (activePassQueue == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < activePassQueue.Count; ++i)
|
||||
{
|
||||
ScriptableRenderPass renderPass =
|
||||
activePassQueue[i];
|
||||
if (renderPass == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RendererBlock block;
|
||||
if (!ScriptableRenderPass.TryResolveRendererBlock(
|
||||
renderPass.renderPassEvent,
|
||||
out block))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int blockIndex = (int)block;
|
||||
BlockRange range = m_ranges[blockIndex];
|
||||
if (range.firstPassIndex < 0)
|
||||
{
|
||||
range.firstPassIndex = i;
|
||||
}
|
||||
|
||||
range.lastPassIndex = i;
|
||||
m_ranges[blockIndex] = range;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPasses(
|
||||
RendererBlock block)
|
||||
{
|
||||
BlockRange range = m_ranges[(int)block];
|
||||
return range.firstPassIndex >= 0 &&
|
||||
range.lastPassIndex >= range.firstPassIndex;
|
||||
}
|
||||
|
||||
public bool TryGetPassRange(
|
||||
RendererBlock block,
|
||||
out int firstPassIndex,
|
||||
out int lastPassIndex)
|
||||
{
|
||||
BlockRange range = m_ranges[(int)block];
|
||||
firstPassIndex = range.firstPassIndex;
|
||||
lastPassIndex = range.lastPassIndex;
|
||||
return range.firstPassIndex >= 0 &&
|
||||
range.lastPassIndex >= range.firstPassIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,21 @@ namespace XCEngine.Rendering.Universal
|
||||
public virtual bool SupportsStage(
|
||||
CameraFrameStage stage)
|
||||
{
|
||||
CameraFrameStage resolvedStage;
|
||||
return TryResolveStage(
|
||||
RendererBlock block;
|
||||
return TryResolveRendererBlock(
|
||||
renderPassEvent,
|
||||
out resolvedStage) &&
|
||||
resolvedStage == stage;
|
||||
out block) &&
|
||||
RendererBlockUtility.GetStage(block) == stage;
|
||||
}
|
||||
|
||||
internal bool SupportsRendererBlock(
|
||||
RendererBlock block)
|
||||
{
|
||||
RendererBlock resolvedBlock;
|
||||
return TryResolveRendererBlock(
|
||||
renderPassEvent,
|
||||
out resolvedBlock) &&
|
||||
resolvedBlock == block;
|
||||
}
|
||||
|
||||
internal bool Record(
|
||||
@@ -209,38 +219,60 @@ namespace XCEngine.Rendering.Universal
|
||||
internal static bool TryResolveStage(
|
||||
RenderPassEvent passEvent,
|
||||
out CameraFrameStage stage)
|
||||
{
|
||||
RendererBlock block;
|
||||
if (TryResolveRendererBlock(
|
||||
passEvent,
|
||||
out block))
|
||||
{
|
||||
stage =
|
||||
RendererBlockUtility.GetStage(block);
|
||||
return true;
|
||||
}
|
||||
|
||||
stage = CameraFrameStage.MainScene;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryResolveRendererBlock(
|
||||
RenderPassEvent passEvent,
|
||||
out RendererBlock block)
|
||||
{
|
||||
switch (passEvent)
|
||||
{
|
||||
case RenderPassEvent.BeforeRenderingShadows:
|
||||
case RenderPassEvent.AfterRenderingShadows:
|
||||
stage = CameraFrameStage.ShadowCaster;
|
||||
block = RendererBlock.ShadowCaster;
|
||||
return true;
|
||||
case RenderPassEvent.BeforeRenderingPrePasses:
|
||||
case RenderPassEvent.AfterRenderingPrePasses:
|
||||
stage = CameraFrameStage.DepthOnly;
|
||||
block = RendererBlock.DepthPrepass;
|
||||
return true;
|
||||
case RenderPassEvent.BeforeRenderingOpaques:
|
||||
case RenderPassEvent.RenderOpaques:
|
||||
case RenderPassEvent.AfterRenderingOpaques:
|
||||
block = RendererBlock.MainOpaque;
|
||||
return true;
|
||||
case RenderPassEvent.BeforeRenderingSkybox:
|
||||
case RenderPassEvent.RenderSkybox:
|
||||
case RenderPassEvent.AfterRenderingSkybox:
|
||||
block = RendererBlock.MainSkybox;
|
||||
return true;
|
||||
case RenderPassEvent.BeforeRenderingTransparents:
|
||||
case RenderPassEvent.RenderTransparents:
|
||||
case RenderPassEvent.AfterRenderingTransparents:
|
||||
stage = CameraFrameStage.MainScene;
|
||||
block = RendererBlock.MainTransparent;
|
||||
return true;
|
||||
case RenderPassEvent.BeforeRenderingPostProcessing:
|
||||
case RenderPassEvent.AfterRenderingPostProcessing:
|
||||
stage = CameraFrameStage.PostProcess;
|
||||
block = RendererBlock.PostProcess;
|
||||
return true;
|
||||
case RenderPassEvent.BeforeRenderingFinalOutput:
|
||||
case RenderPassEvent.AfterRenderingFinalOutput:
|
||||
stage = CameraFrameStage.FinalOutput;
|
||||
block = RendererBlock.FinalOutput;
|
||||
return true;
|
||||
default:
|
||||
stage = CameraFrameStage.MainScene;
|
||||
block = RendererBlock.MainOpaque;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace XCEngine.Rendering.Universal
|
||||
new List<ScriptableRendererFeature>();
|
||||
private readonly List<ScriptableRenderPass> m_activePassQueue =
|
||||
new List<ScriptableRenderPass>();
|
||||
private readonly RendererBlocks m_rendererBlocks =
|
||||
new RendererBlocks();
|
||||
private bool m_disposed;
|
||||
|
||||
protected ScriptableRenderer()
|
||||
@@ -36,6 +38,7 @@ namespace XCEngine.Rendering.Universal
|
||||
|
||||
m_features.Clear();
|
||||
m_activePassQueue.Clear();
|
||||
m_rendererBlocks.Clear();
|
||||
m_disposed = true;
|
||||
}
|
||||
|
||||
@@ -130,17 +133,7 @@ namespace XCEngine.Rendering.Universal
|
||||
RenderingData renderingData =
|
||||
context.renderingData;
|
||||
BuildPassQueue(renderingData);
|
||||
for (int i = 0; i < m_activePassQueue.Count; ++i)
|
||||
{
|
||||
ScriptableRenderPass renderPass = m_activePassQueue[i];
|
||||
if (renderPass != null &&
|
||||
renderPass.SupportsStage(context.stage))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return SupportsRendererStage(context);
|
||||
}
|
||||
|
||||
protected virtual bool RecordRenderer(
|
||||
@@ -155,28 +148,7 @@ namespace XCEngine.Rendering.Universal
|
||||
RenderingData renderingData =
|
||||
context.renderingData;
|
||||
BuildPassQueue(renderingData);
|
||||
|
||||
bool recordedAnyPass = false;
|
||||
for (int i = 0; i < m_activePassQueue.Count; ++i)
|
||||
{
|
||||
ScriptableRenderPass renderPass = m_activePassQueue[i];
|
||||
if (renderPass == null ||
|
||||
!renderPass.SupportsStage(renderingData.stage))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!renderPass.Record(
|
||||
context.renderContext,
|
||||
renderingData))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
recordedAnyPass = true;
|
||||
}
|
||||
|
||||
return recordedAnyPass;
|
||||
return RecordRendererStage(context);
|
||||
}
|
||||
|
||||
protected internal virtual bool SupportsStageRenderGraph(
|
||||
@@ -214,6 +186,147 @@ namespace XCEngine.Rendering.Universal
|
||||
{
|
||||
}
|
||||
|
||||
private bool HasRendererBlock(
|
||||
RendererBlock block)
|
||||
{
|
||||
return m_rendererBlocks.HasPasses(block);
|
||||
}
|
||||
|
||||
protected virtual bool SupportsRendererStage(
|
||||
RendererRecordingContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (context.stage)
|
||||
{
|
||||
case CameraFrameStage.ShadowCaster:
|
||||
return HasRendererBlock(
|
||||
RendererBlock.ShadowCaster);
|
||||
case CameraFrameStage.DepthOnly:
|
||||
return HasRendererBlock(
|
||||
RendererBlock.DepthPrepass);
|
||||
case CameraFrameStage.MainScene:
|
||||
return HasRendererBlock(
|
||||
RendererBlock.MainOpaque) ||
|
||||
HasRendererBlock(
|
||||
RendererBlock.MainSkybox) ||
|
||||
HasRendererBlock(
|
||||
RendererBlock.MainTransparent);
|
||||
case CameraFrameStage.PostProcess:
|
||||
return HasRendererBlock(
|
||||
RendererBlock.PostProcess);
|
||||
case CameraFrameStage.FinalOutput:
|
||||
return HasRendererBlock(
|
||||
RendererBlock.FinalOutput);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool RecordRendererStage(
|
||||
RendererRecordingContext context)
|
||||
{
|
||||
if (context == null ||
|
||||
context.renderContext == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool recordedAnyPass = false;
|
||||
switch (context.stage)
|
||||
{
|
||||
case CameraFrameStage.ShadowCaster:
|
||||
return RecordRendererBlock(
|
||||
RendererBlock.ShadowCaster,
|
||||
context,
|
||||
ref recordedAnyPass) &&
|
||||
recordedAnyPass;
|
||||
case CameraFrameStage.DepthOnly:
|
||||
return RecordRendererBlock(
|
||||
RendererBlock.DepthPrepass,
|
||||
context,
|
||||
ref recordedAnyPass) &&
|
||||
recordedAnyPass;
|
||||
case CameraFrameStage.MainScene:
|
||||
return RecordRendererBlock(
|
||||
RendererBlock.MainOpaque,
|
||||
context,
|
||||
ref recordedAnyPass) &&
|
||||
RecordRendererBlock(
|
||||
RendererBlock.MainSkybox,
|
||||
context,
|
||||
ref recordedAnyPass) &&
|
||||
RecordRendererBlock(
|
||||
RendererBlock.MainTransparent,
|
||||
context,
|
||||
ref recordedAnyPass) &&
|
||||
recordedAnyPass;
|
||||
case CameraFrameStage.PostProcess:
|
||||
return RecordRendererBlock(
|
||||
RendererBlock.PostProcess,
|
||||
context,
|
||||
ref recordedAnyPass) &&
|
||||
recordedAnyPass;
|
||||
case CameraFrameStage.FinalOutput:
|
||||
return RecordRendererBlock(
|
||||
RendererBlock.FinalOutput,
|
||||
context,
|
||||
ref recordedAnyPass) &&
|
||||
recordedAnyPass;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool RecordRendererBlock(
|
||||
RendererBlock block,
|
||||
RendererRecordingContext context,
|
||||
ref bool recordedAnyPass)
|
||||
{
|
||||
if (context == null ||
|
||||
context.renderContext == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int firstPassIndex;
|
||||
int lastPassIndex;
|
||||
if (!m_rendererBlocks.TryGetPassRange(
|
||||
block,
|
||||
out firstPassIndex,
|
||||
out lastPassIndex))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = firstPassIndex;
|
||||
i <= lastPassIndex;
|
||||
++i)
|
||||
{
|
||||
ScriptableRenderPass renderPass =
|
||||
m_activePassQueue[i];
|
||||
if (renderPass == null ||
|
||||
!renderPass.SupportsRendererBlock(block))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!renderPass.Record(
|
||||
context.renderContext,
|
||||
context.renderingData))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
recordedAnyPass = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void BuildPassQueue(
|
||||
RenderingData renderingData)
|
||||
{
|
||||
@@ -233,6 +346,7 @@ namespace XCEngine.Rendering.Universal
|
||||
}
|
||||
|
||||
AddRenderPasses(renderingData);
|
||||
m_rendererBlocks.Build(m_activePassQueue);
|
||||
}
|
||||
|
||||
protected virtual void ReleaseRuntimeResources()
|
||||
|
||||
Reference in New Issue
Block a user