Files
XCEngine/docs/used/Renderer结构收口与代码正式化计划_完成归档_2026-04-05.md

15 KiB
Raw Blame History

Renderer 结构收口与代码正式化计划

日期:2026-04-05

1. 阶段定位

当前 Rendering 主线在功能上已经完成了相当多闭环:

  • 三后端统一的 runtime renderer 主链已经建立
  • directional shadow、multi-light、object-id、editor overlay 等能力都已接入
  • SceneView / GameView 基本共用了同一条 runtime 渲染路径

但从代码结构和职责边界上看,这一阶段还没有真正收口。现在的问题已经不再是“某个功能没接上”,而是:

renderer 的核心模块里仍然混有明显的阶段性写法、特殊分支、职责堆叠和 editor/runtime 边界不清的问题。

如果此时直接继续往上叠 skybox、环境、后处理、更多 renderer feature后面会越来越难拆最终重新把已经相对稳定的 renderer 主链拖回到“能跑但很难维护”的状态。

因此,本阶段的唯一目标不是加新功能,而是:

把当前 renderer 这一阶段真正做成可持续演进的正式结构,为后续 Skybox / Environment / PostProcess / 更正式的 SRP 承接清掉结构债。

2. 为什么现在必须先做结构收口

这不是“目录看着乱一点”的表面问题,而是已经影响后续演进的实质性架构问题。

2.1 CameraRenderer 仍然存在特殊通道

当前 CameraRenderer 虽然已经具备请求规划与多阶段执行能力,但 object-id 仍然是单独的一套特殊路径,而不是正式 frame composition 里的统一 pass 节点。

这带来的问题:

  • 相机级执行顺序不是单一模型,而是“主链 + 特判”
  • 后续 skybox / post-process / capture / debug target 更难正规接入
  • 单元测试里被迫维护特殊 mock pass 类型,而不是统一的 pass contract

关键文件:

  • engine/include/XCEngine/Rendering/ObjectIdPass.h
  • engine/include/XCEngine/Rendering/CameraRenderer.h
  • engine/include/XCEngine/Rendering/CameraRenderRequest.h
  • engine/src/Rendering/CameraRenderer.cpp

2.2 BuiltinForwardPipeline.cpp 已经是典型 god file

这个文件里当前同时混着:

  • pass wrapper
  • shader pass resolve
  • graphics pipeline 创建
  • descriptor set layout 规划
  • descriptor set 资源写入
  • lighting 常量打包
  • material fallback
  • draw submission

这已经不是“文件有点长”而是职责拆分失败。后续任何修改都会把高层语义、资源绑定、RHI 细节、draw 级逻辑一起牵动,测试也只能做大颗粒回归,无法精准保护。

关键文件:

  • engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp

2.3 RenderMaterialUtility.h 混合了契约、兼容层和运行时解析

当前这个头里至少混了五类职责:

  • builtin pass contract 定义
  • shader/property/binding 查询
  • descriptor layout 规划
  • legacy/material fallback 兼容
  • runtime material resolve 与绑定辅助

这会导致:

  • “正式 contract” 与 “过渡兼容逻辑” 难以分开演进
  • 很多 renderer 代码只能依赖一个超大工具头
  • 头文件膨胀,职责不可读,接口边界不清

关键文件:

  • engine/include/XCEngine/Rendering/RenderMaterialUtility.h

2.4 editor / debug pass 仍然混在 runtime renderer 核心层里

grid、outline 这些能力现在已经走到了比较正式的 runtime host path但它们在 engine 里的组织方式仍然更接近“把 editor 需求塞进 renderer 核心”。

风险在于:

  • runtime 核心会继续被 editor 语义污染
  • 后续 scene/game/editor 三条宿主路径边界会再次变模糊
  • 玩家运行时和编辑器专用渲染能力的依赖关系难以长期维护

关键文件:

  • engine/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h
  • engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h

2.5 文件拆分层次仍然不干净

例如 BuiltinDepthStylePassBase.cpp 的底部还直接放着 BuiltinDepthOnlyPassBuiltinShadowCasterPass 具体实现,这说明“抽象基类”和“具体 pass”仍然没有彻底分层。

关键文件:

  • engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp

2.6 少量稳定性问题仍然暴露出“临时写法”

比如 scene extractor 里 visible item 的稳定排序仍然用 raw pointer 作为 tie-breaker而 additional light 已经升级成了稳定的 GameObject::ID 语义。

这类问题虽然不大,但非常能说明当前代码里仍混有阶段性临时写法,必须顺手清理干净。

关键文件:

  • engine/src/Rendering/RenderSceneExtractor.cpp

3. 本阶段的核心设计原则

本阶段继续严格遵循当前工程既定设计理念,并与 RHI模块总览 中的核心原则保持一致:

  1. RHI 只负责 GPU 抽象,不感知 object-id、grid、outline、skybox、post-process 等高层语义。
  2. Renderer 负责 scene extraction、frame composition、material/shader contract、runtime pass orchestration。
  3. Editor 只作为 renderer 的宿主和扩展使用方,不把 editor 语义反向污染 RHI。
  4. 兼容层和正式 contract 必须拆开,不能继续把“临时兜底”混在正式主链里。
  5. 不引入 render graph。本阶段先把现有 renderer 结构做正式化,不跳级优化。
  6. 不做“修修补补式”文件搬家,必须同时修职责边界、执行路径和测试结构。

4. 阶段总目标

本阶段收口完成后,应达到以下状态:

  1. CameraRenderer 形成单一、明确、可测试的 frame composition 模型。
  2. runtime pass 与 object-id / editor-debug pass 的边界清晰,接入点正式化。
  3. BuiltinForwardPipeline 不再由一个 god file 承担所有责任。
  4. RenderMaterialUtility 被拆分为“正式 contract 层”和“兼容/运行时辅助层”。
  5. renderer 文件结构与代码结构一致,抽象基类、具体 pass、绑定辅助、compat helper 各归其位。
  6. 当前所有 rendering / editor 相关测试继续通过,不破坏已闭环功能。

5. 非目标

本阶段明确不做:

  • render graph
  • deferred / clustered / tiled lighting
  • 新一轮 editor 视觉特效堆叠
  • 大规模 shader authoring 体系重写
  • point / spot shadow
  • 重新设计 RHI

6. 分阶段执行方案

6.1 Phase ACamera Frame Composition 正式化

目标

消灭 CameraRenderer 里 object-id 的特殊执行通道,把相机级执行模型统一成正式 frame composition。

要解决的根因

  • 现在相机渲染顺序不是单一 contract
  • ObjectIdPass 是旁路抽象,不利于后续继续扩展 composition
  • 测试里存在针对 object-id 的特殊 mock pass 体系

具体工作

  1. 重新审视 CameraRenderRequest 的阶段描述,明确:
    • pre-scene
    • shadow/depth
    • scene pipeline
    • auxiliary offscreen passes
    • post-scene
    • overlay
  2. 去掉 ObjectIdPass 作为并行特例抽象的地位。
  3. 把 object-id 统一纳入正式 pass 执行序列,必要时通过 pass category / target intent 标识语义,而不是再保留独立虚函数族。
  4. 简化 CameraRenderer 执行逻辑让失败传播、目标准备、pass 顺序只走一套主链。
  5. 同步收敛相关单元测试,让测试验证“阶段顺序”和“失败传播”,而不是验证某个特判分支。

验收标准

  • CameraRenderer 不再对 object-id 走特判主逻辑
  • test_camera_scene_renderer 等单测仍覆盖 object-id 顺序与失败传播
  • editor viewport object-id picking 不回退

计划提交点

这一阶段完成后立即提交推送一次。

6.2 Phase BBuiltinForwardPipeline 职责拆分

目标

BuiltinForwardPipeline.cpp 从 god file 拆成正式的职责层次,但不改变现有 forward runtime 的对外行为。

要解决的根因

  • pipeline resolve、resource layout、descriptor write、material resolve、lighting packing、draw submission 全部耦合
  • 任何小修改都会波及整个文件
  • 难以为 skybox / post-process / future pipeline 承接建立稳定接口

具体工作

  1. 先按职责切出独立模块,优先拆成以下几层:
    • shader/pass resolve
    • pipeline cache/build
    • resource binding layout / descriptor planning
    • frame-scoped lighting / pass constants upload
    • draw item submission
  2. BuiltinForwardPipeline 保留 orchestration 职责,而不是继续承载全部细节。
  3. 清理与 RenderMaterialUtility 的交叉依赖,为下一阶段拆 contract 做准备。
  4. 保证 unlit / lit / object-id / depth-only / shadow-caster 的绑定逻辑不被混淆。

验收标准

  • BuiltinForwardPipeline.cpp 明显缩小,核心职责清晰
  • 新拆出的模块命名与职责稳定,不是单纯“工具类化”
  • forward 相关单测、集成测试全部不回退

计划提交点

这一阶段完成后立即提交推送一次。

6.3 Phase CRenderMaterialUtility 正式拆层

目标

把 shader/material/pass 的正式 contract 与 legacy/compat/runtime helper 拆开。

要解决的根因

  • 正式接口和过渡逻辑混在一起
  • 任何依赖 RenderMaterialUtility.h 的代码都会被迫包含大量不相干能力
  • 后续 shader/material 演进会被兼容逻辑长期绑死

具体工作

  1. 明确拆成三层语义:
    • contractbuiltin pass 名称、标准 binding 名称、正式解析规则
    • runtime resolve:材质/着色器运行时查询、pass 选择、绑定规划
    • compatlegacy property 名称、历史 fallback、过渡适配
  2. 避免再把大段实现继续塞在头文件里,能下沉到 .cpp 的尽量下沉。
  3. 对外只暴露最小且稳定的正式接口。
  4. 给 compat 层加清晰边界,避免以后继续被当作默认主路径使用。

验收标准

  • RenderMaterialUtility.h 体量显著下降,职责单一
  • renderer 主链依赖的是正式 contract / runtime resolve而不是 compat 大杂烩
  • 现有 shader/material 行为与测试结果保持一致

计划提交点

这一阶段完成后立即提交推送一次。

6.4 Phase DRuntime Pass 与 Editor/Debug Pass 边界重整

目标

明确 engine runtime rendering core 与 editor/debug-oriented rendering extension 的边界。

要解决的根因

  • grid、outline 等语义虽然已经可用,但组织上仍偏临时
  • engine 核心层里混有 editor 专用概念
  • 后续 camera frame composition 扩展容易再次被 editor 需求污染

具体工作

  1. 明确哪些是 runtime 正式能力,哪些是 editor/debug extension。
  2. 把 editor/debug pass 的注册与宿主接入方式整理成正式 extension seam。
  3. 保持 SceneView / GameView 继续复用 runtime renderer 主链,但 editor overlay / outline / grid 不侵入 runtime scene composition。
  4. 补足必要文档,说明 engine、renderer、editor 三者的责任边界。

验收标准

  • editor/debug pass 不再作为 runtime renderer 核心概念扩散
  • SceneView / GameView 显示、grid、outline、gizmo 宿主路径不回退
  • 新增代码结构能自然承接后续 icon/light gizmo/camera gizmo 等扩展

计划提交点

这一阶段完成后立即提交推送一次。

6.5 Phase E稳定性清扫、文件收口与文档归档

目标

清掉这一阶段剩余的临时写法,让实现、测试、文档口径再次一致。

具体工作

  1. 修正 RenderSceneExtractor 里仍然使用 raw pointer 的稳定排序 tie-breaker。
  2. BuiltinDepthStylePassBase.cpp 中具体 pass 实现拆出到独立文件。
  3. 全面复查 renderer 相关文件命名、目录结构、头源分布是否仍有明显反模式。
  4. 更新 tests/TEST_SPEC.md 与相关 renderer / editor guide。
  5. 阶段完成后,把已过期 plan 归档到 docs/used

验收标准

  • renderer 核心目录结构与职责边界基本一致
  • 没有明显残留的阶段性临时代码入口
  • 文档、测试矩阵、实现状态三者一致

计划提交点

这一阶段完成后立即提交推送一次。

7. 测试策略

本阶段的测试必须是“每阶段落地即验证”,不能到最后一次性回归。

7.1 Unit

重点保护:

  • CameraRenderer 阶段顺序与失败传播
  • RenderSceneExtractor 的稳定输出
  • BuiltinForwardPipeline 的绑定与材质解析
  • material/shader contract 拆层后的接口行为

优先关注:

  • tests/Rendering/unit/test_camera_scene_renderer.cpp
  • tests/Rendering/unit/test_builtin_forward_pipeline.cpp
  • 与 material utility / scene extractor 相关的 unit tests

7.2 Editor / Runtime Integration

重点回归:

  • object-id picking
  • SceneView / GameView runtime 渲染链
  • overlay / outline / grid
  • backpack / shadow / multi-light / camera stack / offscreen 等现有场景

至少覆盖:

  • tests/editor/test_viewport_render_flow_utils.cpp
  • tests/editor/test_scene_viewport_overlay_renderer.cpp
  • tests/editor/test_viewport_object_id_picker.cpp
  • 现有 rendering integration matrix 中与 lighting、object-id、camera flow 相关的场景

7.3 编译与宿主验证

每一阶段至少执行:

  1. 相关 test target 编译
  2. 相关 unit / integration tests
  3. 必要时编译 XCEditor
  4. 对 editor 中 SceneView / GameView 做 smoke 验证

8. 风险与控制

风险 1把“结构重构”做成单纯的文件搬家

后果:

  • 文件名变了,职责没变
  • 代码仍然继续跨层互相依赖

控制策略:

  • 每次拆分都要同时调整接口边界和测试保护点

风险 2为了图省事继续保留 object-id 特判

后果:

  • Camera frame composition 永远无法正式化
  • 后续 skybox / post-process 会继续引入更多特判

控制策略:

  • 第一阶段必须先砍掉这类特殊旁路

风险 3compat 逻辑继续侵入正式 contract

后果:

  • shader/material 体系长期混乱
  • 之后 Unity 风格 shader authoring 很难落地

控制策略:

  • compat 层必须显式命名、显式隔离、显式限定使用场景

风险 4editor/debug pass 重整时破坏现有 editor 体验

后果:

  • 影响当前 SceneView 主线
  • 把结构收口又变成功能回退

控制策略:

  • 每一阶段都要做 editor smoke 和既有测试回归

9. 阶段完成判定

满足以下条件时,本阶段才算真正收口:

  1. CameraRenderer 已统一成正式 frame composition 执行模型。
  2. BuiltinForwardPipelineRenderMaterialUtility 已完成职责拆分,核心 god file 问题消除。
  3. runtime pass 与 editor/debug pass 边界清晰,不再混成一团。
  4. 现有 rendering / editor tests 继续稳定通过。
  5. docs/plandocs/used 的 plan 入口重新清晰,不再保留已过期的执行入口。

10. 与当前主线的关系

这份计划不是替代“Skybox 环境与 Frame Composition 正式化”方向,而是它的前置收口。

顺序必须是:

  1. 先做 renderer 结构收口与代码正式化
  2. 再做 skybox / environment / post-process 的正式接入
  3. 最后才考虑更高阶的 renderer feature 与未来 SRP 承接

否则就是在结构债未清的情况下继续加层,后面只会越收越难。