diff --git a/docs/plan/Renderer结构收口与代码正式化计划_2026-04-05.md b/docs/plan/Renderer结构收口与代码正式化计划_2026-04-05.md new file mode 100644 index 00000000..5a94b922 --- /dev/null +++ b/docs/plan/Renderer结构收口与代码正式化计划_2026-04-05.md @@ -0,0 +1,415 @@ +# 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` 的底部还直接放着 `BuiltinDepthOnlyPass`、`BuiltinShadowCasterPass` 具体实现,这说明“抽象基类”和“具体 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 A:Camera 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 B:BuiltinForwardPipeline 职责拆分 + +### 目标 + +把 `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 C:RenderMaterialUtility 正式拆层 + +### 目标 + +把 shader/material/pass 的正式 contract 与 legacy/compat/runtime helper 拆开。 + +### 要解决的根因 + +- 正式接口和过渡逻辑混在一起 +- 任何依赖 `RenderMaterialUtility.h` 的代码都会被迫包含大量不相干能力 +- 后续 shader/material 演进会被兼容逻辑长期绑死 + +### 具体工作 + +1. 明确拆成三层语义: + - `contract`:builtin pass 名称、标准 binding 名称、正式解析规则 + - `runtime resolve`:材质/着色器运行时查询、pass 选择、绑定规划 + - `compat`:legacy property 名称、历史 fallback、过渡适配 +2. 避免再把大段实现继续塞在头文件里,能下沉到 `.cpp` 的尽量下沉。 +3. 对外只暴露最小且稳定的正式接口。 +4. 给 compat 层加清晰边界,避免以后继续被当作默认主路径使用。 + +### 验收标准 + +- `RenderMaterialUtility.h` 体量显著下降,职责单一 +- renderer 主链依赖的是正式 contract / runtime resolve,而不是 compat 大杂烩 +- 现有 shader/material 行为与测试结果保持一致 + +### 计划提交点 + +这一阶段完成后立即提交推送一次。 + +## 6.4 Phase D:Runtime 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 会继续引入更多特判 + +控制策略: + +- 第一阶段必须先砍掉这类特殊旁路 + +### 风险 3:compat 逻辑继续侵入正式 contract + +后果: + +- shader/material 体系长期混乱 +- 之后 Unity 风格 shader authoring 很难落地 + +控制策略: + +- compat 层必须显式命名、显式隔离、显式限定使用场景 + +### 风险 4:editor/debug pass 重整时破坏现有 editor 体验 + +后果: + +- 影响当前 SceneView 主线 +- 把结构收口又变成功能回退 + +控制策略: + +- 每一阶段都要做 editor smoke 和既有测试回归 + +## 9. 阶段完成判定 + +满足以下条件时,本阶段才算真正收口: + +1. `CameraRenderer` 已统一成正式 frame composition 执行模型。 +2. `BuiltinForwardPipeline` 与 `RenderMaterialUtility` 已完成职责拆分,核心 god file 问题消除。 +3. runtime pass 与 editor/debug pass 边界清晰,不再混成一团。 +4. 现有 rendering / editor tests 继续稳定通过。 +5. `docs/plan` 与 `docs/used` 的 plan 入口重新清晰,不再保留已过期的执行入口。 + +## 10. 与当前主线的关系 + +这份计划不是替代“Skybox 环境与 Frame Composition 正式化”方向,而是它的前置收口。 + +顺序必须是: + +1. 先做 renderer 结构收口与代码正式化 +2. 再做 skybox / environment / post-process 的正式接入 +3. 最后才考虑更高阶的 renderer feature 与未来 SRP 承接 + +否则就是在结构债未清的情况下继续加层,后面只会越收越难。