15 KiB
Renderer C++层第一阶段重构计划(Render Graph 前)
日期:2026-04-13
1. 文档定位
这份计划只处理 Render Graph 之前的那一步,也就是把当前 Rendering 原生主链先整理成一个可长期演进的正式架构。
这一步的目标不是新增大功能,而是先把下面这些边界收口:
SceneRenderer / CameraRenderer / SceneRenderRequestPlanner的职责边界RenderSceneExtractor / RenderSceneData / VisibleRenderItem的数据边界RenderPipeline / RenderPass / BuiltinForwardPipeline的执行边界- Runtime 主链与 Editor 注入点的边界
这一步做完之后,下一阶段才适合引入真正的 Render Graph。
2. 关于 Render Graph 的结论
结论很明确:
Render Graph应该先做在C++ native层- 它不应该直接作为第一阶段的一部分
- 第一阶段的任务,是先把当前 native renderer 收口到足够清晰,避免后面只是把现有混乱机械搬进 graph
后面的目标结构应该是:
RHI -> Native Render Kernel -> Render Graph -> SRP-like Contract -> Universal Renderer -> C# Custom Pipeline
其中:
Render Graph属于Native Render KernelC#层以后可以编排 pass,但图资源生命周期、资源状态、barrier、跨 pass 读写依赖仍应先由 native graph 掌控
3. 当前代码里的真实主链
当前项目并不是没有架构,而是已经形成了一条手工编排的 mini-SRP 主链:
SceneRenderRequestPlanner- 收集 camera
- 生成
CameraRenderRequest - 负责方向光阴影规划参数
SceneRenderer- 调 planner
- 解析 final color policy
- 为 post-process / final-output 附加 fullscreen stage request
CameraRenderer- 做 shadow request resolve
- 提取
RenderSceneData - 按固定 stage 顺序执行
BuiltinForwardPipeline- 负责主场景绘制
- 同时背着 shader 绑定、PSO 缓存、材质资源解析、skybox、splat、volumetric 等大量职责
对应的当前入口主要是:
engine/include/XCEngine/Rendering/Execution/SceneRenderer.hengine/include/XCEngine/Rendering/Execution/CameraRenderer.hengine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.hengine/include/XCEngine/Rendering/Planning/CameraRenderRequest.hengine/include/XCEngine/Rendering/FrameData/RenderSceneData.hengine/include/XCEngine/Rendering/FrameData/VisibleRenderItem.hengine/include/XCEngine/Rendering/RenderPipeline.hengine/include/XCEngine/Rendering/RenderPipelineAsset.hengine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.hengine/src/Rendering/Execution/SceneRenderer.cppengine/src/Rendering/Execution/CameraRenderer.cppengine/src/Rendering/Extraction/RenderSceneExtractor.cppengine/src/Rendering/Pipelines/BuiltinForwardPipeline.cppengine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp
4. 当前真正的问题
当前主要不是“功能太少”,而是“职责层次还不够正式”。
4.1 request、plan、execution 混在一起
CameraRenderRequest 现在既像外部请求,又像内部执行计划,还夹带了一部分 runtime surface / fullscreen chain 组织信息。
SceneRenderer 也同时承担了 request build、final color resolve、fullscreen intermediate surface ownership 这几类事情。
这样的问题是:
- 后面很难区分“用户想渲染什么”和“引擎决定怎么执行”
- Editor 也会继续直接依赖底层 stage 细节
- Render Graph 以后很难接到一个稳定的 frame plan 输入
4.2 extraction 数据还偏早期
RenderSceneExtractor 现在能工作,但产物仍然偏“把可见对象收集成数组”。
它还没有正式长成后续 SRP / Render Graph 真正需要的那种中间层:
CullingResultsRendererListFilteringSettingsSortingSettingsDrawSettings
如果这层不先正式化,后面 forward、deferred、shadow、object-id、editor overlay 都会继续各自拿 visibleItems 做自己的隐式解释。
4.3 BuiltinForwardPipeline 过重
当前 BuiltinForwardPipeline 不只是一个 renderer,它已经同时包含:
- 主场景 pass 编排
- 材质资源布局解析
- descriptor set 组织
- pipeline state cache
- lighting / shadow 绑定
- skybox 逻辑
- gaussian splat 特殊路径
- volumetric 特殊路径
这会带来两个后果:
- 后面任何主场景新能力都会继续往这个类里堆
- 未来要抽
Universal Renderer或切出Deferred时,拆分成本会很高
4.4 runtime 主线和 editor 扩展点还没正式隔离
现在 Editor 已经深度依赖 request stage 注入点,这个方向本身没错,但 contract 还不够正式。
如果不先收口,后面 graph 化时 editor 特殊路径会变成额外复杂度来源。
4.5 pipeline contract 还不够像未来的 SRP 底座
当前 RenderPipeline 还是:
Render(context, surface, sceneData)
这个接口对当前 builtin forward 足够,但对未来这些东西不够:
- renderer list
- graph resource declaration
- per-frame context
- pass feature 注入
- 多渲染路径共存
第一阶段不要求一步到位,但至少要把它朝这个方向整理。
5. 本阶段明确不做的事
为了保证收口不失控,这一阶段明确不做下面这些:
- 不引入真正运行时
Render Graph - 不做完整
Deferred Renderer - 不做
C# SRP暴露 - 不重写 RHI
- 不新增大型渲染特性来打断架构收口
本阶段只做一件事:
把当前 native rendering layer 整理成 Render Graph 之前的正式形态
6. 第一阶段完成后应该达到什么状态
本阶段完成后,native renderer 至少应满足下面这些条件:
- 外部 request、内部 frame plan、实际 execution 三层语义分开
- extraction / culling / draw organization 有正式中间层,不再只靠裸
visibleItems BuiltinForwardPipeline明显瘦身,不再继续当总垃圾桶- runtime pass 与 editor pass 有明确注入 contract
- 后续
Render Graph能接一个稳定的Frame Plan + Renderer Lists + Resource Needs输入
7. 第一阶段的重构原则
7.1 先 formalize,再 graphize
先把 contract 和边界做清楚,再做 graph。
不要反过来。
7.2 不破坏现有主链闭环
当前 Scene / Game / Editor Viewport 已经能跑通,第一阶段不能为了“好看”把现有可运行链条打散。
7.3 新中间层优先 native internal
这一阶段新补的 Frame Plan、CullingResults、RendererList、DrawSettings 都先是 C++ 内部契约,不急着暴露给 C#。
7.4 先解耦职责,再决定目录长相
不要先做大范围目录搬家。
先把职责拆开,目录只是最后的外显结果。
7.5 editor contract 正式化,但不去特殊化主链
Editor 的 object-id、outline、grid、overlay 依然要保留,但它们应该依附在正式 contract 上,而不是继续共享 runtime 的隐式细节。
8. 工作包拆分
工作包 A:request / frame plan 分层
目标:
把“用户想渲染什么”和“引擎这帧怎么执行”拆开。
建议结果:
- 保留
CameraRenderRequest作为外部请求描述 - 新增内部
CameraFramePlan或等价类型,承载:- 实际启用的 stage
- fullscreen chain
- resolved target/surface
- 阴影计划结果
- editor 注入点
SceneRenderer只负责:- 请求收集
- 请求排序
- request -> frame plan 转换
CameraRenderer只负责:- frame plan 执行
- 调 scene extraction
- 调 pipeline / standalone pass
优先涉及文件:
engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.hengine/include/XCEngine/Rendering/Execution/SceneRenderer.hengine/src/Rendering/Execution/SceneRenderer.cppengine/include/XCEngine/Rendering/Execution/CameraRenderer.hengine/src/Rendering/Execution/CameraRenderer.cpp
完成标准:
- request 不再混入过多 runtime-owned intermediate state
- frame plan 成为 camera 执行的正式输入
- SceneRenderer 和 CameraRenderer 的边界可以一句话说清
工作包 B:scene extraction -> culling results / renderer list
目标:
把当前 RenderSceneExtractor 的数组式输出,推进为后续 SRP/RenderGraph 可用的原生中间层。
建议结果:
- 在 native 层引入等价于以下概念的类型:
CullingResultsRendererListDescRendererListFilteringSettingsSortingSettingsDrawSettings
RenderSceneData不再承担过多“什么都往里塞”的职责VisibleRenderItem继续保留为底层记录,但不再直接成为所有 pass 的唯一公共输入- shadow、main scene、object-id 等路径逐步改为消费
RendererList
优先涉及文件:
engine/include/XCEngine/Rendering/FrameData/RenderSceneData.hengine/include/XCEngine/Rendering/FrameData/VisibleRenderItem.hengine/src/Rendering/Extraction/RenderSceneExtractor.cppengine/include/XCEngine/Rendering/Extraction/engine/include/XCEngine/Rendering/Builtin/
完成标准:
- opaque、transparent、shadow、object-id 至少能共用同一套 draw organization contract
- 后面新增 deferred 时,不需要再发明第二套“可见对象数组解释逻辑”
工作包 C:pipeline contract 收口
目标:
把当前 RenderPipeline / RenderPass / RenderPipelineAsset 收口到更像未来 SRP native kernel 的形态。
建议结果:
- 继续保留
RenderPipelineAsset -> RenderPipeline这条总方向 - 明确区分:
- pipeline asset:配置与默认策略
- pipeline runtime:本帧执行
- standalone pass:独立功能 pass
- 给主场景 pipeline 引入更正式的执行输入,而不是继续只吃
surface + sceneData - 为下一阶段 graph 化预留清晰输入面:
- frame context
- renderer lists
- per-frame resources
优先涉及文件:
engine/include/XCEngine/Rendering/RenderPipeline.hengine/include/XCEngine/Rendering/RenderPipelineAsset.hengine/include/XCEngine/Rendering/RenderPass.hengine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h
完成标准:
- 主 pipeline 输入面能明显映射到未来
Universal Renderer - standalone pass 与 main scene pipeline 的边界清楚
工作包 D:BuiltinForwardPipeline 瘦身
目标:
把 BuiltinForwardPipeline 从“大一统实现类”拆成可长期维护的 renderer 内部模块。
建议结果:
- 按职责切成内部模块或 helper:
- main scene draw
- skybox draw
- lighting binding
- shadow binding
- material binding
- pipeline state cache
- per-frame resource cache
GaussianSplat和Volumetric继续保留,但从“主 pipeline 杂糅逻辑”变成清晰的 feature-style 子模块BuiltinForwardPipelineResources.cpp里和运行时编排无关的资源/绑定组织进一步下沉
优先涉及文件:
engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.hengine/src/Rendering/Pipelines/BuiltinForwardPipeline.cppengine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cppengine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h
完成标准:
BuiltinForwardPipeline主类显著变薄- 新增一种主场景能力时,不需要继续把所有逻辑堆回同一个 cpp
工作包 E:editor 注入点正式化
目标:
保留当前 editor 渲染能力,但把它从“依赖阶段细节”变成“依赖正式 extension contract”。
建议结果:
- 继续保留:
- object-id
- outline
- grid
- overlay
- 把这些能力明确分成:
- runtime stage
- editor-only extension stage
- 收口
RenderPassSequence在 request 上的使用边界,避免后续 graph 化时出现“谁都能往 request 里塞东西”的状态
优先涉及文件:
engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.heditor/src/Viewport/SceneViewportRenderPlan.heditor/src/Viewport/engine/src/Rendering/Execution/CameraRenderer.cpp
完成标准:
- Editor 仍能完整接入当前主链
- 但 editor 不再直接绑死 runtime 内部实现细节
工作包 F:测试与文档同步
目标:
把第一阶段重构做成可回归、可交接的正式收口,而不是一次只靠手工验证的大重排。
建议结果:
- 补
tests/Rendering/unit - 保底回归现有
tests/Rendering/integration - 必要时补 editor viewport 级验证
- 更新 API 文档与后续计划入口
完成标准:
- request / extractor / pipeline 边界都有对应测试覆盖
- 当前 forward、shadow、object-id、editor scene view 不回退
9. 执行顺序
必须按下面的顺序推进:
- 先做工作包 A
- 再做工作包 B
- 再做工作包 C
- 然后做工作包 D
- 接着做工作包 E
- 最后统一做工作包 F
原因:
- 不先分清 request 和 frame plan,后面所有 contract 都会悬空
- 不先把 extraction 推进到
renderer list层,pipeline 收口只是表面整理 - 不先把 pipeline contract 稳住,
BuiltinForwardPipeline的瘦身会缺少稳定目标 - 不先把 native 主线收紧,editor contract 很容易继续沿着旧细节扩张
10. 建议新增或调整的 native 类型
第一阶段不要求名字完全按这个来,但建议至少出现这些等价层:
CameraFramePlanFrameExecutionContextCullingResultsRendererListDescRendererListDrawSettingsFilteringSettingsSortingSettingsEditorRenderExtensions或等价 editor 注入描述
注意:
- 这些类型先是
C++ internal contract - 这一阶段不急着桥接到
managed - 下一阶段
Render Graph会直接消费其中一部分
11. 这一阶段完成的验收标准
当以下条件同时成立时,这一阶段才算完成:
SceneRenderer / CameraRenderer / Planner三者职责边界稳定- native 渲染主链内部已经形成 request、frame plan、execution 三层
RenderSceneExtractor不再只是输出裸数组,而是能支持正式的 draw organizationBuiltinForwardPipeline不再承担明显超载职责- editor 的注入点已经正规化,且没有破坏当前 Scene View / Game View 能力
- 现有 forward、shadow、post-process、final-output、object-id、overlay 主路径回归通过
12. 下一阶段如何接 Render Graph
等本阶段完成后,下一阶段才进入真正的 Render Graph。
那时的接法应该是:
CameraFramePlan提供本帧逻辑阶段与 feature 需求CullingResults / RendererList提供 draw 输入FrameExecutionContext提供本帧统一资源上下文Render Graph负责:- pass declaration
- resource creation/import
- read/write dependency
- barrier / lifetime
- transient resource reuse
也就是说,Render Graph 不是来替代第一阶段,而是建立在第一阶段之上。
13. 一句话结论
当前最佳路线不是立刻补 Deferred,也不是立刻补 Render Graph,而是先把你现有这条 native rendering 主链整理成真正的 Render Kernel v1;这一步做实了,后面的 Render Graph、Universal Renderer、C# SRP 才会接得稳。