docs: sync api and planning docs

This commit is contained in:
2026-04-08 16:07:03 +08:00
parent 08c3278e10
commit 31756847ab
1826 changed files with 44502 additions and 29645 deletions

View File

@@ -1,248 +0,0 @@
# API 文档实时同步任务池2026-04-03第二轮
## 文档定位
这份任务池接替已归档的第一轮计划:
- `docs/plan/used/API文档实时同步任务池_2026-04-03_第一轮归档.md`
第一轮已经解决的重点是:
- canonical 目录结构收口
- 历史缺页补齐
- 第一轮大规模内容重写
本轮重点不再是“补结构”,而是:
- 重新对照当前工作树源码与测试
- 清理最近重构后重新出现的内容级失配
- 继续维护一份适合多人并行认领的增量同步清单
## 当前复核快照
- 最近一次结构审计时间:`2026-04-03 15:07:08`
- 审计命令:`python -B docs/api/_tools/audit_api_docs.py`
- 当前结果:
- `Public headers: 244`
- `Editor source headers: 124`
- `Invalid header refs: 0`
- `Invalid source refs: 0`
- `Broken .md links: 0`
- `Stale canonical doc tokens: 0`
- `Stale editor doc tokens: 0`
- `Stale editor canonical pages: 0`
这说明当前结构层面已经恢复全绿;本轮后续工作以内容级持续同步为主。
## 认领规则
- 一次只认领 `1` 个任务块。
- 先把 `状态` 改成 `DOING`,再写 `认领人`
- 只能改自己任务块的 `写入范围`
- 所有改动都必须以“当前源码 + 当前测试 + 当前真实调用链”为依据,不允许按旧文档续写旧行为。
- 如果清理了过期 API 页面,必须同时清理交叉链接。
## 任务池
## T01 Editor / Viewport 渲染计划与宿主流程内容同步
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P0`
- 写入范围:
- `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/**`
- `docs/api/XCEngine/Editor/Viewport/ViewportHostRenderFlowUtils/**`
- `docs/api/XCEngine/Editor/Viewport/ViewportHostService/**`
- 必要时 `docs/api/XCEngine/Editor/Viewport/IViewportHostService/**`
- 主要源码依据:
- `editor/src/Viewport/SceneViewportRenderPlan.h`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h`
- `editor/src/Viewport/ViewportHostService.h`
- `tests/editor/test_viewport_render_flow_utils.cpp`
- 已关闭问题:
- 旧文档仍把 Scene View 当前主路径写成 builtin post-process 主导
- 没写清 grid / selection outline 现在已转为显式 `postScenePasses`
- 没写清 overlay pass 是 `editorOverlayFrameData + transientOverlayFrameData` 合并后再创建
- 没写清 `Scene object id shader view is unavailable` 是局部降级警告,不是整帧失败
- 完成记录:
- 已重写 `SceneViewportRenderPlan.md``BuildSceneViewportRenderPlan.md``ApplySceneViewportRenderPlan.md`
- 已同步 `ViewportHostRenderFlowUtils.md``ViewportHostService.md``RenderRequestedViewports.md`
- 已清理 `IViewportHostService` 中残留的旧表述
## T02 Core / Asset 缓存接口改名与语义重构同步
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P0`
- 写入范围:
- `docs/api/XCEngine/Core/Asset/ResourceManager/**`
- `docs/api/XCEngine/Core/Asset/AssetImportService/**`
- `docs/api/XCEngine/Core/Asset/ProjectAssetIndex/**`
- 必要时 `docs/api/XCEngine/Core/Asset/Asset.md`
- 必要时 `docs/api/XCEngine/Core/Asset/ArtifactFormats/**`
- 主要源码依据:
- `engine/include/XCEngine/Core/Asset/ResourceManager.h`
- `engine/src/Core/Asset/ResourceManager.cpp`
- `engine/include/XCEngine/Core/Asset/AssetImportService.h`
- `engine/src/Core/Asset/AssetImportService.cpp`
- `engine/include/XCEngine/Core/Asset/ProjectAssetIndex.h`
- `engine/src/Core/Asset/ProjectAssetIndex.cpp`
- `tests/core/Asset/test_resource_manager.cpp`
- 已关闭问题:
- `RefreshAssetDatabase` 已经不存在,但旧文档仍在沿用
- `RefreshProjectAssets` / `RebuildProjectAssetCache` / `GetProjectLibraryRoot` 缺页或未写清
- `AssetImportService::EnsureArtifact()` 旧文档仍把输出写成 `ResolvedAsset`
- `BuildLookupSnapshot()` 旧文档仍按“双 map 出参”描述,而不是 `LookupSnapshot`
- `ImportedAsset::runtimeLoadPath` 语义未同步到上层文档
- 完成记录:
- 已删除过期页 `ResourceManager/RefreshAssetDatabase.md`
- 已补齐 `RefreshProjectAssets.md``RebuildProjectAssetCache.md``GetProjectLibraryRoot.md`
- 已补齐 `LookupSnapshot.md``ImportedAsset.md``GetLibraryRoot.md``RebuildLibraryCache.md`
- 已同步 `Asset.md``ArtifactFormats.md``ResourceManager/Load.md``runtimeLoadPath` 口径
## T03 Scripting / Mono 托管销毁入口同步
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P1`
- 写入范围:
- `docs/api/XCEngine/Scripting/Mono/MonoScriptRuntime/**`
- 主要源码依据:
- `engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h`
- `engine/src/Scripting/Mono/MonoScriptRuntime.cpp`
- `tests/scripting/test_mono_script_runtime.cpp`
- 已关闭问题:
- `DestroyManagedObject(MonoObject*)` 已进入头文件与测试,但文档树没有对应页面
- `MonoScriptRuntime.md` 没写清 `Object.Destroy(...)` 会回落到原生对象 / 组件销毁
- 完成记录:
- 已新增 `DestroyManagedObject.md`
- 已更新 `MonoScriptRuntime.md` 的 internal call 说明与方法总表
## T04 Cross-Module / 教程层与模块总览口径持续复核
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P2`
- 写入范围:
- `docs/api/_guides/**`
- `docs/api/XCEngine/*/*.md`
- 仅限与本轮已确认 API 变更直接相关的总览页
- 任务目标:
- 继续检查教程页、模块总览页是否仍在传播旧心智模型
- 尤其关注:
- Scene View 仍被写成 builtin post-process 主导
- 资源导入链仍被写成 `artifactMainPath` / `RefreshAssetDatabase` 时代的口径
- 托管对象销毁路径未在教程层被解释
- 产出要求:
- 只修正已确认失配的 guide / overview 页面
- 不做与当前源码无关的泛化扩写
## T05 增量变更监控 / 新一轮差异发现
- 状态: `OPEN`
- 认领人: ``
- 优先级: `P2`
- 写入范围:
- 只读检查 `engine/include/**``engine/src/**``editor/src/**``tests/**`
- 必要时只向本任务池追加新任务块
- 任务目标:
- 继续结合工作树最新改动,找出新的“源码已变但文档还没跟上”的内容级失配
- 优先检查:
- 最近改动过的 public headers
- 最近改动过的 Editor source headers
- 最近新增或更新过的测试
- 产出要求:
- 只记录已确认的问题
- 每条新任务都要写明:
- 受影响文档
- 主要源码依据
- 真实失配点
- 建议写入范围
## T06 Rendering / Camera request、Passes 与执行链旧口径清理
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P1`
- 写入范围:
- `docs/api/XCEngine/Rendering/CameraRenderRequest/**`
- `docs/api/XCEngine/Rendering/CameraRenderer/**`
- `docs/api/XCEngine/Rendering/Passes/**`
- `docs/api/XCEngine/Rendering/ObjectIdPass/**`
- `docs/api/XCEngine/Rendering/RenderPipeline/**`
- `docs/api/XCEngine/Rendering/SceneRenderer/**`
- `docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/**`
- 必要时 `docs/api/XCEngine/XCEngine.md`
- 必要时 `docs/api/_tools/audit_api_docs.py`
- 主要源码依据:
- `engine/include/XCEngine/Rendering/CameraRenderRequest.h`
- `engine/include/XCEngine/Rendering/CameraRenderer.h`
- `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h`
- `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h`
- `engine/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h`
- `engine/include/XCEngine/Rendering/ObjectIdPass.h`
- `engine/include/XCEngine/Rendering/RenderPipeline.h`
- `engine/include/XCEngine/Rendering/SceneRenderer.h`
- `engine/src/Rendering/CameraRenderer.cpp`
- `engine/src/Rendering/SceneRenderer.cpp`
- `tests/Rendering/unit/test_camera_scene_renderer.cpp`
- `editor/src/Viewport/SceneViewportRenderPlan.h`
- `editor/src/Viewport/Passes/SceneViewportGridPass.cpp`
- `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h`
- `tests/Editor/test_viewport_render_flow_utils.cpp`
- `tests/Editor/test_scene_viewport_overlay_renderer.cpp`
- 已关闭问题:
- `CameraRenderRequest.md` 仍描述已删除的 `builtinPostProcess` 子请求
- 页面仍链接到不存在的 `BuiltinPostProcessRequest/BuiltinPostProcessRequest.md`
- Scene View 已改为通过 `postScenePasses` / `overlayPasses` 写回 request但该页口径未同步
- `CameraRenderer.md` 仍保留已删除的 `m_builtinPostProcessBuilder` 历史口径
- `Passes.md` 的典型链路只写了 `postScenePasses`,遗漏当前 `overlayPasses` 组装路径
- `Rendering/Passes` 下仍残留已删除的 `BuiltinPostProcessPassPlan` / `BuiltinPostProcessPassSequenceBuilder` 页面
- `BuiltinObjectIdPass``BuiltinInfiniteGridPass` 多个公开入口缺页
- `ObjectIdPass.md``RenderPipeline.md``SceneRenderer.md` 与顶层 `XCEngine.md` 仍在传播 builtin-post-process 心智模型
- 完成记录:
- 已重写 `CameraRenderRequest.md`
- 已清理 `2` 个失效 `.md` 链接
- 已同步 `CameraRenderer.md``Passes.md` 的当前执行链路表述
- 已删除 `BuiltinPostProcessPassPlan.md``BuiltinPostProcessPassSequenceBuilder.md`
- 已补齐 `BuiltinObjectIdPass` / `BuiltinInfiniteGridPass` 缺失页面,并补充 `SceneViewportRenderPlan` 下 grid / selection outline pass factory 页面
- 已同步 `ObjectIdPass.md``RenderPipeline.md``SceneRenderer.md``XCEngine.md` 的当前口径
- 已补充 Rendering guide、`CameraRenderer::Render``ViewportHostService::RenderRequestedViewports` 的 request 级 pass 注入说明
- 已为 `builtinPostProcess` / `BuiltinPostProcessRequest` / `m_builtinPostProcessBuilder` 增加 canonical 过期符号审计
- 已复跑结构审计并确认 `Broken .md links: 0`
## T07 Editor / Core `EditorConsoleSink` 生命周期说明同步
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P1`
- 写入范围:
- `docs/api/XCEngine/Editor/Core/EditorConsoleSink/**`
- 必要时 `docs/api/XCEngine/Editor/Managers/Managers.md`
- 必要时 `docs/api/_tools/audit_api_docs.py`
- 主要源码依据:
- `editor/src/Core/EditorConsoleSink.h`
- `editor/src/Core/EditorConsoleSink.cpp`
- `editor/src/panels/ConsolePanel.cpp`
- `editor/src/Core/EditorLoggingSetup.h`
- `tests/Editor/test_editor_console_sink.cpp`
- 已关闭问题:
- `EditorConsoleSink::GetInstance()` 旧文档仍把它写成会返回 fallback 实例
- 相关 overview 页面没有写清活动 sink 销毁后会返回 `nullptr`
- 审计脚本此前无法自动拦截这类旧生命周期表述
- 完成记录:
- 已同步 `EditorConsoleSink.md``GetInstance.md``Destructor.md`
- 已同步 `Managers.md` 中对控制台 sink 生命周期的引用口径
- 已为 `fallback 实例` 与“不会返回空指针”旧表述增加定向审计
## 当前结论
- 本轮已经关掉五组明确的内容级失配:
- `Viewport` 渲染计划与宿主流程
- `Core/Asset` 缓存接口与导入服务语义
- `MonoScriptRuntime` 托管销毁入口
- `Rendering` 相机请求、Passes 与单相机执行链旧口径
- `EditorConsoleSink` 生命周期与空指针返回语义
- 当前结构审计为全绿。
- 当前仍建议保留 `T04``T05` 作为持续性并行入口,用来承接你后续重构带来的新文档漂移。

View File

@@ -1,117 +0,0 @@
# API 文档并行更新任务池2026-04-02
## 目的
基于 `2026-04-02` 当前工作树,这份清单用于把 API 文档更新任务拆成可并行认领的独立块,供多个会话同时推进。
## 认领规则
- 一次只认领 `1` 个任务块,先改 `状态``认领人`
- 只修改自己任务块的 `写入范围`,不要跨任务顺手改别的模块页。
-`T09` 之外,其他任务不要更新 `docs/api/_meta/rebuild-status.md`,避免多人冲突。
- 每个任务都要以源码、实现、测试、真实调用点为依据,不允许只按命名猜测行为。
- 如果任务执行中发现需要新增 guide统一放到 `docs/api/_guides/<Module>/` 下。
## 首批并行推荐
- 优先并行启动: `T01``T03``T04``T05``T07`
- 第二批跟进: `T02``T06``T08`
- 收尾整合: `T09`
## 任务池
## T01 Editor / Viewport 子模块补齐与重写
- 状态: `IN_PROGRESS`
- 认领人: `Codex-Viewport`
- 优先级: `P0`
- 写入范围: `docs/api/XCEngine/Editor/Viewport/**``docs/api/XCEngine/Editor/panels/SceneViewPanel/**``docs/api/XCEngine/Editor/panels/ViewportPanelContent/**`
- 主要源码依据: `editor/src/Viewport/**``editor/src/panels/SceneViewPanel.*``editor/src/panels/ViewportPanelContent.h``tests/editor/test_scene_viewport_camera_controller.cpp`
- 当前缺口: `Viewport` 整个 canonical 树尚未建立;以下页面当前缺失: `SceneViewportCameraController``SceneViewportMoveGizmo``SceneViewportRotateGizmo``SceneViewportScaleGizmo``SceneViewportOverlayRenderer``ViewportHostService``ViewportHostRenderFlowUtils``SceneViewportEditorOverlayData``SceneViewportOverlayBuilder``ViewportPanelContent`
- 完成标准: 补齐 `Viewport/Viewport.md` 与所有类型页;`SceneViewPanel` 文档重写到当前 gizmo / overlay / host flow 实现;写清楚生命周期、交互链路、渲染路径和测试覆盖
## T02 Editor / ScriptComponentEditor 补齐
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P0`
- 写入范围: `docs/api/XCEngine/Editor/ComponentEditors/ScriptComponentEditor/**``docs/api/XCEngine/Editor/ComponentEditors/ScriptComponentEditorUtils/**``docs/api/XCEngine/Editor/ComponentEditors/ComponentEditors.md``docs/api/XCEngine/Editor/ComponentEditors/ComponentEditorRegistry/**`
- 主要源码依据: `editor/src/ComponentEditors/ScriptComponentEditor.h``editor/src/ComponentEditors/ScriptComponentEditorUtils.h``editor/src/ComponentEditors/ComponentEditorRegistry.cpp`
- 当前缺口: `ScriptComponentEditor``ScriptComponentEditorUtils` 还没有 canonical 页面;组件编辑器总览也需要纳入脚本组件编辑器
- 完成标准: 补齐缺页;说明 Inspector 侧脚本字段绘制、字段元数据来源、与 `ScriptEngine` / `ScriptComponent` 的关系
## T03 Core / AssetDatabase 新建与资产数据库链路说明
- 状态: `IN_PROGRESS`
- 认领人: `Codex-Asset`
- 优先级: `P0`
- 写入范围: `docs/api/XCEngine/Core/Asset/AssetDatabase/**``docs/api/XCEngine/Core/Asset/Asset.md`
- 主要源码依据: `engine/include/XCEngine/Core/Asset/AssetDatabase.h`、相关 `.cpp` 实现、项目目录下新增的 `.meta``Library` 资产缓存变化
- 当前缺口: `AssetDatabase` 对应的 canonical 类型页完全缺失;`Core/Asset` 模块总览需要反映新的数据库/导入缓存方向
- 完成标准: 建立 `AssetDatabase` 页面,明确 GUID、path、meta、导入缓存、查询职责以及它和 `ProjectPanel` / `ResourceManager` / 资源导入流程的关系
## T04 Rendering / Passes 子模块与 BuiltinObjectIdOutlinePass 补齐
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P0`
- 写入范围: `docs/api/XCEngine/Rendering/Passes/**`
- 主要源码依据: `engine/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h``engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp`
- 当前缺口: `Rendering/Passes` 目录当前没有 canonical 文档树;`BuiltinObjectIdOutlinePass` 页面缺失
- 完成标准: 新建 `Passes/Passes.md``BuiltinObjectIdOutlinePass` 类型页;写清楚对象 ID / 轮廓高亮的输入输出、依赖资源、典型使用位置和当前限制
## T05 Scripting 模块内容重构
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P1`
- 写入范围: `docs/api/XCEngine/Scripting/**``docs/api/_guides/Scripting/**`
- 主要源码依据: `engine/include/XCEngine/Scripting/IScriptRuntime.h``Mono/MonoScriptRuntime.h``NullScriptRuntime.h``ScriptComponent.h``ScriptEngine.h` 及对应 `.cpp``tests/scripting/**`
- 当前缺口: 结构存在,但脚本运行时、字段同步、项目脚本程序集、空运行时回退等内容需要按当前实现重写
- 完成标准: 明确运行时抽象、Mono 后端、Null 后端、字段存储与组件生命周期;必要时补一篇项目脚本程序集 / 字段同步 guide
## T06 Editor 运行时胶水层与面板内容更新
- 状态: `TODO`
- 认领人: `未认领`
- 优先级: `P1`
- 写入范围: `docs/api/XCEngine/Editor/Application/**``docs/api/XCEngine/Editor/Core/EventBus/**``docs/api/XCEngine/Editor/panels/InspectorPanel/**``docs/api/XCEngine/Editor/panels/ProjectPanel/**``docs/api/XCEngine/Editor/UI/Widgets/**``docs/api/XCEngine/Editor/Actions/HierarchyActionRouter/**``docs/api/XCEngine/Editor/Commands/EntityCommands/**`
- 主要源码依据: `editor/src/Application.*``editor/src/Core/EventBus.h``editor/src/panels/InspectorPanel.*``editor/src/panels/ProjectPanel.*``editor/src/UI/Widgets.h``editor/src/Actions/HierarchyActionRouter.h``editor/src/Commands/EntityCommands.h`
- 当前缺口: 这些页面虽然大多存在,但内容容易落后于当前交互链路;`ProjectPanel` 虽已较新,仍要根据这轮源码变化做二次核对
- 完成标准: 把“Editor 主循环 -> EventBus -> 面板 -> Action/Command”这条链路写清楚Inspector/Project/Hierarchy 相关页内容与当前实现严格对齐
## T07 Rendering 相机请求与对象 ID 渲染链路更新
- 状态: `DONE`
- 认领人: `Codex`
- 优先级: `P1`
- 写入范围: `docs/api/XCEngine/Rendering/CameraRenderRequest/**``docs/api/XCEngine/Rendering/CameraRenderer/**``docs/api/XCEngine/Rendering/RenderMaterialUtility/**``docs/api/XCEngine/Rendering/Rendering.md`
- 主要源码依据: `engine/include/XCEngine/Rendering/CameraRenderRequest.h``engine/src/Rendering/CameraRenderer.cpp``engine/src/Resources/Material/MaterialLoader.cpp``engine/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp`
- 当前缺口: 文档需要反映这轮 renderer 里对象 ID、outline、camera request、材质 render state 的新关系
- 完成标准: 写清楚 camera request 的职责边界、camera renderer 的主流程、object-id/outline 的接入点,以及材质 render state 对渲染路径的影响
## T08 Components / MeshFilterComponent 与资源绑定链路更新
- 状态: `IN_PROGRESS`
- 认领人: `Codex`
- 优先级: `P1`
- 写入范围: `docs/api/XCEngine/Components/MeshFilterComponent/**``docs/api/XCEngine/Components/Components.md`
- 主要源码依据: `engine/include/XCEngine/Components/MeshFilterComponent.h`、相关 `.cpp``tests/Resources/Mesh/test_mesh_loader.cpp``tests/Resources/Material/test_material_loader.cpp`
- 当前缺口: `MeshFilterComponent` 页面存在,但需要重新核对 mesh handle / path / 资源解析链路;模块总览也应补充 MeshFilter 在渲染和资产导入链路中的定位
- 完成标准: 说明 `MeshFilterComponent` 如何保存 mesh 引用、如何与资源系统和渲染提取流程衔接,以及当前限制
## T09 根总览与最终审计
- 状态: `TODO`
- 认领人: `未认领`
- 优先级: `P2`
- 写入范围: `docs/api/XCEngine/XCEngine.md`、受影响的模块总览页、`docs/api/_meta/rebuild-status.md`
- 主要源码依据: 前面所有任务的完成结果
- 当前缺口: 根总览和模块总览需要在前面任务落地后统一收口;审计状态文件只能由一个会话最终更新
- 完成标准: 统一调整总览页导航;执行 `audit_api_docs.py`、覆盖校验与链接校验;写回新的 `rebuild-status.md`
## 备注
- 如果只有 `2-3` 个会话,优先拿 `T01``T05``T07`
- 如果有 `4-6` 个会话,推荐并行拿 `T01``T03``T04``T05``T06``T07`
- `T09` 必须最后做

View File

@@ -1,756 +0,0 @@
# C#脚本模块的设计与实现
## 1. 背景
XCEngine 的整体方向是模仿传统 Unity 引擎架构,而不是 DOTS/ECS-first 路线。
在这一目标下,脚本系统应当满足以下基本预期:
- 脚本语言使用 `C#`
- 脚本以“挂载到 `GameObject` 上的组件”形式工作
- 脚本与引擎核心解耦,支持独立编译和运行时加载
- 脚本可以逐步扩展到 Inspector、场景序列化、Play/Simulate 工作流
当前 `editor` 仍处于基础阶段,因此脚本系统第一阶段不应依赖 editor 完整落地。
第一阶段的目标应当收敛为:
- 先完成原生运行时与托管运行时之间的桥接
- 先完成 `ScriptComponent + C# MonoBehaviour` 的基本执行链路
- 先完成单元测试和最小场景级验证
- 将 editor 集成需求单独列为 issue后续补齐
---
## 2. 设计目标
### 2.1 总体目标
脚本系统应当提供一条接近 Unity 的开发路径:
1. 用户在独立的 C# 项目中编写脚本
2. C# 脚本编译为程序集
3. Engine Core 在运行时加载程序集
4. `GameObject` 上挂载 `ScriptComponent`
5. `ScriptComponent` 驱动一个对应的 C# `MonoBehaviour` 实例
6. 脚本通过引擎暴露的 API 调用原生功能
### 2.2 第一阶段目标
第一阶段只覆盖以下内容:
- `engine` 内部的脚本运行时抽象
- 第一套 C# 运行时实现
- `ScriptComponent` 原生组件
- `ScriptCore` 托管基础库
- 最小可用的 `InternalCall` 绑定
- 单元测试与最小运行时测试
### 2.3 第一阶段非目标
第一阶段明确不做以下内容:
- editor Inspector 脚本字段编辑
- editor 中的脚本类选择器
- editor 的 Play/Simulate 集成
- 自动编译、文件监听、热重载
- 调试器接入
- 发布态 AOT / IL2CPP
- 大而全的引擎 API 暴露
---
## 3. 三方对比结论
### 3.1 Unity
Unity 的典型脚本模型有几个关键特征:
- 用户脚本通常继承 `MonoBehaviour`
- 一段脚本本质上就是一个组件实例
- 脚本字段可序列化、可在 Inspector 中编辑
- 生命周期完整,且与 `GameObject active` / `Component enabled` 语义一致
- Play 模式运行的是运行时场景副本,而不是直接修改编辑场景
### 3.2 参考项目 Fermion
参考项目已经实现了一套 C# 脚本模块,优点主要在于:
- 已经证明了 `Mono embedding + InternalCall + C# 程序集加载` 这条路线可行
- 已经具备脚本类发现、脚本实例创建、字段反射、运行时调用
- 已经有托管侧 API 包装层和原生侧 `ScriptGlue`
但它的对象模型并不是 Unity 风格:
- 托管脚本类继承的是 `Entity`
- 原生挂载结构是 `ScriptContainerComponent`
- 更像“一个实体挂多个脚本类名”,而不是“每个脚本本身就是一个组件”
- 生命周期目前主要聚焦 `OnCreate/OnUpdate`
结论:
- Fermion 适合借鉴运行时技术路线
- Fermion 不适合作为最终 API 形态的直接模板
### 3.3 XCEngine 当前情况
XCEngine 当前已经具备以下基础:
- 已有 `GameObject + Component + Scene` 模型
- `Component` 已定义 Unity 风格生命周期接口
- `Scene``GameObject` 已有序列化入口
- `ComponentFactoryRegistry` 已支持按类型名恢复组件
但当前也存在会直接影响脚本系统设计的现实约束:
- `Scene::Update/FixedUpdate/LateUpdate` 目前只遍历根对象
- `GameObject::Update/FixedUpdate/LateUpdate` 目前不递归子对象
- `Start` 的场景级一次性调度路径还未完整建立
- `SetActive` 尚未真正驱动 `OnEnable/OnDisable`
- editor 的 Play/Simulate 工作流仍未落地
- `GameObject UUID` 当前未进入场景序列化主路径
结论:
- XCEngine 适合走 Unity 风格脚本模型
- 但脚本系统第一阶段必须连同一部分运行时地基一起建设
---
## 4. 总体设计结论
XCEngine 的 C# 脚本模块采用以下路线:
- **脚本语言**C#
- **脚本挂载模型**:原生 `ScriptComponent` 对应托管 `MonoBehaviour`
- **程序集模型**`ScriptCore``GameScripts` 分离
- **运行时加载模式**:独立编译,运行时加载
- **桥接方式**`InternalCall`
- **运行时抽象策略**:先抽象接口,再优先落 Mono 实现
- **第一阶段验证方式**:单元测试优先,不依赖 editor
这条路线有两个核心原则:
1. API 形态尽量接近 Unity
2. 第一阶段严格控制范围,只做运行时闭环和测试闭环
---
## 5. 运行时选型
### 5.1 第一阶段选型Mono
第一阶段建议使用 `Mono` 作为第一套 C# 运行时实现,原因如下:
- 与现有规划文档保持一致
- 参考项目已经证明这条技术路线可落地
- `InternalCall` 路线成熟,适合快速建立最小可用系统
- 便于在 Windows 环境中先做出可运行结果
### 5.2 选型边界
本设计不把 `Mono` 写死为脚本系统唯一实现,而是将其作为第一实现:
- 对外暴露 `IScriptRuntime` / `ScriptEngine` 抽象
- `MonoScriptRuntime` 作为第一套后端
- 后续如有需要,可以演进到 `CoreCLR` 或其他运行时
### 5.3 第一阶段不做的运行时能力
Mono 相关的以下复杂能力不进入第一阶段:
- 域热重载
- 编辑器内自动重编译后重载
- 托管调试器接入
- 发布态 AOT
第一阶段仅要求:
- 初始化运行时
- 加载核心程序集
- 加载用户程序集
- 发现脚本类
- 实例化对象
- 调用生命周期
- 读写托管字段
---
## 6. 模块划分
### 6.1 原生侧模块
建议在 `engine` 内新增 `Scripting` 模块:
```text
engine/
├── include/XCEngine/Scripting/
│ ├── ScriptEngine.h
│ ├── IScriptRuntime.h
│ ├── ScriptAssembly.h
│ ├── ScriptClass.h
│ ├── ScriptInstance.h
│ ├── ScriptField.h
│ ├── ScriptFieldStorage.h
│ ├── ScriptGlue.h
│ └── ScriptComponent.h
└── src/Scripting/
├── ScriptEngine.cpp
├── ScriptGlue.cpp
├── ScriptComponent.cpp
└── Mono/
├── MonoScriptRuntime.cpp
├── MonoScriptClass.cpp
└── MonoScriptAssembly.cpp
```
### 6.2 托管侧模块
建议新增托管核心库 `ScriptCore`
```text
managed/
├── XCEngine.ScriptCore/
│ ├── XCEngine.ScriptCore.csproj
│ ├── Object.cs
│ ├── Component.cs
│ ├── Behaviour.cs
│ ├── MonoBehaviour.cs
│ ├── GameObject.cs
│ ├── Transform.cs
│ ├── Debug.cs
│ ├── Time.cs
│ └── InternalCalls.cs
└── GameScripts/
├── GameScripts.csproj
└── Scripts/*.cs
```
### 6.3 程序集分层
程序集分为两层:
- `XCEngine.ScriptCore.dll`
- 由引擎维护
- 提供托管基类和引擎 API 包装
- `GameScripts.dll`
- 由项目侧维护
- 编写具体游戏脚本
关系如下:
```text
GameScripts.dll
└── 引用 XCEngine.ScriptCore.dll
Engine Core
├── 先加载 XCEngine.ScriptCore.dll
└── 再加载 GameScripts.dll
```
---
## 7. 对象模型
### 7.1 托管侧模型
托管侧应当采用接近 Unity 的对象层次:
```text
Object
└── Component
└── Behaviour
└── MonoBehaviour
```
其中:
- `Object`:基础托管对象
- `Component`:挂载到 `GameObject` 上的托管组件基类
- `Behaviour`:带 `enabled` 语义的组件
- `MonoBehaviour`:用户脚本直接继承的基类
### 7.2 原生挂载模型
原生侧脚本挂载使用 `ScriptComponent`,而不是 `ScriptContainerComponent`
原因如下:
- 更符合 Unity 认知模型
- 更容易与现有 `GameObject::AddComponent<T>` 思路对齐
- 更容易在未来做 Inspector 级脚本组件显示
- 更容易把字段序列化与组件实例对应起来
建议 `ScriptComponent` 至少包含:
- `scriptComponentUUID`
- `assemblyName`
- `namespaceName`
- `className`
- `enabled`
- `fieldStorage`
推荐接口示意:
```cpp
class ScriptComponent : public Component {
public:
std::string GetName() const override { return "Script"; }
const std::string& GetAssemblyName() const;
const std::string& GetNamespaceName() const;
const std::string& GetClassName() const;
std::string GetFullClassName() const;
uint64_t GetScriptComponentUUID() const;
bool IsRuntimeValid() const;
void Serialize(std::ostream& os) const override;
void Deserialize(std::istream& is) override;
private:
uint64_t m_scriptComponentUUID = 0;
std::string m_assemblyName = "GameScripts";
std::string m_namespaceName;
std::string m_className;
ScriptFieldStorage m_fieldStorage;
};
```
### 7.3 多脚本挂载
一个 `GameObject` 应允许挂载多个 `ScriptComponent`
这与 Unity 保持一致:
- 同一个对象可以挂多个不同脚本
- 同一个脚本是否允许重复挂载,由后续属性或规则控制
- 第一阶段不做“禁止重复挂载”的复杂策略
---
## 8. 身份模型与序列化要求
### 8.1 必须使用 UUID而不是运行时 ID
脚本系统中,托管实例与原生对象的稳定绑定必须建立在 `UUID` 之上,而不是当前的自增 `ID`
原因如下:
- 运行时场景复制不能依赖自增 ID 稳定
- 脚本字段里的对象引用必须有稳定键
- editor 与 runtime 之间的对象映射必须有稳定键
- 单元测试和场景恢复也需要稳定身份
因此需要补齐以下要求:
- `GameObject UUID` 进入场景序列化
- 场景反序列化时恢复 `UUID`
- `ScriptComponent` 自身也应有持久化 UUID
### 8.2 第一阶段字段序列化策略
第一阶段的脚本字段序列化原则如下:
- 只序列化 `ScriptComponent` 的字段缓存
- 只序列化脚本作者显式设置的字段值
- 运行时脚本执行过程中修改的值,不自动回写场景
这与 Unity 的 Play 模式行为一致:
- Play 中的运行时改动不应直接污染编辑数据
### 8.3 第一阶段字段支持范围
建议第一阶段先支持:
- `float`
- `double`
- `bool`
- `int32`
- `uint64`
- `string`
- `Vector2`
- `Vector3`
- `Vector4`
- `GameObject` 引用
第一阶段不要求支持:
- `List<T>`
- 自定义托管结构体
- 嵌套对象图
- 泛型容器
- 资源引用对象选择器
---
## 9. 生命周期设计
### 9.1 目标生命周期
脚本系统最终应支持以下 Unity 风格生命周期:
- `Awake`
- `OnEnable`
- `Start`
- `FixedUpdate`
- `Update`
- `LateUpdate`
- `OnDisable`
- `OnDestroy`
### 9.2 第一阶段生命周期闭环
第一阶段就应当把上述生命周期的原生调度链路设计好,哪怕 editor 尚未接入。
建议运行时流程如下:
#### `Scene` 运行时启动
1. `ScriptEngine::OnRuntimeStart(scene)`
2. 遍历场景中全部激活对象
3. 找到所有 `ScriptComponent`
4. 为每个组件创建托管 `MonoBehaviour` 实例
5. 写入原生对象 UUID / 组件 UUID / 基础上下文
6. 应用序列化字段缓存
7. 调用 `Awake`
8. 若对象激活且组件启用,调用 `OnEnable`
9. 标记“等待 Start”
#### 每帧执行
- 物理阶段:`FixedUpdate`
- 普通阶段:`Update`
- 后处理阶段:`LateUpdate`
- 对于尚未执行 `Start` 的脚本,在第一次普通帧前先调用 `Start`
#### 运行时停止
1. 对仍处于启用状态的脚本调用 `OnDisable`
2. 对所有脚本调用 `OnDestroy`
3. 清理托管实例表
4. 清理运行时场景上下文
### 9.3 对现有引擎的前置要求
为了让脚本生命周期符合预期,现有引擎需要补齐以下地基:
- `Scene` 更新必须递归整个层级,而不是只更新根对象
- `Start` 必须具备“一次且仅一次”语义
- `SetActive` 必须驱动 `OnEnable/OnDisable`
- 运行时场景启动与停止必须显式化
- 创建对象时不应直接把“编辑态创建”与“运行态 Awake”混为一谈
这些工作虽然不都属于脚本模块,但它们是脚本模块的直接运行前提。
---
## 10. 原生与托管之间的桥接
### 10.1 桥接方式
第一阶段使用 `InternalCall`
- 托管侧通过 `MethodImplOptions.InternalCall` 声明方法
- 原生侧通过 `mono_add_internal_call` 注册
### 10.2 第一阶段最小 API 集
第一阶段建议只暴露最小必需 API
- `Debug.Log / LogWarning / LogError`
- `Time.deltaTime`
- `GameObject.GetName / SetName`
- `GameObject.GetTransform`
- `Component.GetGameObject`
- `GameObject.HasComponent<T>`
- `GameObject.GetComponent<T>`
- `Transform` 的本地位置 / 旋转 / 缩放
这套 API 足够覆盖以下测试与最小演示:
- 变换脚本
- 旋转/移动脚本
- 生命周期日志验证
- 组件访问验证
第一阶段不建议优先暴露:
- 物理 API
- 渲染 API
- 音频 API
- 输入系统
- 资源系统
原因很简单:
- 第一阶段以单元测试闭环为主
- 暴露面越大,绑定维护成本越高
- 当前这些系统本身仍在演进
---
## 11. 类发现与实例管理
### 11.1 脚本类发现规则
用户脚本类应满足以下条件才被视为可挂载脚本:
- 定义在 `GameScripts.dll`
- 非抽象类
- 继承 `XCEngine.MonoBehaviour`
### 11.2 缓存结构
原生运行时需要缓存以下信息:
- 程序集表
- 脚本类表
- 方法句柄表
- 字段元数据表
- 运行时实例表
建议实例表键使用:
- `GameObjectUUID + ScriptComponentUUID`
而不是:
- 内存地址
- 组件在容器中的索引
- 自增 ID
### 11.3 方法缓存
每个脚本类应缓存常用生命周期方法句柄:
- `Awake`
- `OnEnable`
- `Start`
- `FixedUpdate`
- `Update`
- `LateUpdate`
- `OnDisable`
- `OnDestroy`
这样可以避免每帧按字符串查找方法。
---
## 12. 单元测试优先策略
### 12.1 原则
第一阶段脚本模块不依赖 editor因此验证策略以单元测试和最小运行时测试为主。
### 12.2 测试目录建议
```text
tests/
└── Scripting/
├── unit/
│ ├── test_script_runtime.cpp
│ ├── test_script_metadata.cpp
│ ├── test_script_component.cpp
│ ├── test_script_fields.cpp
│ └── CMakeLists.txt
└── managed/
├── XCEngine.ScriptCore/
└── TestScripts/
```
### 12.3 第一阶段必须覆盖的测试
#### 运行时初始化
- 能初始化脚本运行时
- 能加载 `ScriptCore`
- 能加载测试脚本程序集
#### 类发现
- 只发现继承 `MonoBehaviour` 的类
- 忽略抽象类
- 忽略普通工具类
#### 生命周期
- 能创建托管实例
- 能按顺序触发 `Awake -> OnEnable -> Start -> Update`
- 能在停止时触发 `OnDisable -> OnDestroy`
#### 组件桥接
- 脚本能访问 `GameObject`
- 脚本能访问 `Transform`
- 脚本能访问最小组件 API
#### 字段系统
- 能发现公共字段
- 能读写字段
- 字段缓存可回填到托管实例
- 场景序列化后字段值不丢失
#### 多脚本对象
- 同一 `GameObject` 上多个 `ScriptComponent` 都可实例化
- 不同脚本实例之间不会串字段
#### UUID 绑定
- 运行时复制或反序列化后仍可稳定恢复脚本绑定键
### 12.4 第一阶段不要求的测试
- editor Inspector UI
- 热重载
- 编译器输出面板
- 文件监听
- 资源拖拽
---
## 13. 建议的实现顺序
### 阶段 A补运行时地基
先补以下基础能力:
- `GameObject UUID` 序列化
- 层级递归更新
- `Start` 一次性语义
- `SetActive` 对生命周期的影响
- `Scene` 运行时启动/停止接口
### 阶段 B脚本最小闭环
完成:
- `Scripting` 模块骨架
- `ScriptComponent`
- `MonoScriptRuntime`
- `XCEngine.ScriptCore.dll`
- `GameScripts.dll`
- 最小 `InternalCall`
### 阶段 C字段与序列化
完成:
- 脚本字段元数据
- 字段缓存
- `ScriptComponent` 序列化/反序列化
- 字段相关单元测试
### 阶段 D最小运行时演示
在不依赖 editor 的前提下完成:
- 纯运行时测试场景
- 一个或两个最小脚本示例
- Play 级别运行验证
### 阶段 Eeditor 集成
后续再做:
- 脚本组件 Inspector
- 类选择器
- 编译按钮
- 错误输出
- 重载流程
---
## 14. 与 editor 的关系
第一阶段文档明确规定:
- C# 脚本模块**不依赖 editor 完整落地**
- editor 相关工作全部后置
- editor 相关缺口统一记录在 `docs/issues`
第一阶段唯一允许与 editor 共用的内容是:
- 场景序列化格式
- `GameObject UUID` 语义
- 运行时场景副本的总体设计方向
除此之外,不应把脚本模块第一阶段实现建立在 editor 已具备以下能力的假设上:
- Play/Simulate 控制条
- Inspector 自定义字段绘制
- 项目内脚本自动编译
- 脚本异常面板
---
## 15. 风险与权衡
### 15.1 Mono 依赖
风险:
- Windows 环境需要显式安装或打包 Mono
- CMake 配置与发布路径管理会变复杂
权衡:
- 第一阶段优先解决“能跑起来”的问题
- 运行时抽象保留未来替换空间
### 15.2 生命周期与现有引擎实现的偏差
风险:
- 如果继续保留当前“创建对象就立即 `Awake`”的行为,脚本生命周期会混乱
权衡:
- 脚本系统建设必须带动运行时生命周期整理
### 15.3 字段系统范围
风险:
- 一开始就追求完整序列化会让范围失控
权衡:
- 第一阶段只做基础类型与少量引用类型
- 复杂容器和高级序列化后置
### 15.4 editor 后置
风险:
- 第一阶段用户体验不完整
权衡:
- 可以显著降低实现风险
- 可以先通过单测和运行时样例把底层做稳
---
## 16. 最终结论
XCEngine 的 C# 脚本系统应当采用:
- `ScriptComponent + MonoBehaviour`
- `ScriptCore + GameScripts` 双程序集结构
- `InternalCall` 桥接
- `Mono` 作为第一套运行时实现
- `UUID` 作为绑定与序列化的稳定身份
- 第一阶段只做运行时与单元测试,不依赖 editor
这条路线既保留了 Unity 风格的一致性,也能适配当前工程实际进度。
第一阶段的成功标准不是“Inspector 能编辑脚本字段”,而是:
- 能加载脚本程序集
- 能发现脚本类
- 能在场景运行时驱动 `MonoBehaviour`
- 能通过单元测试验证生命周期、字段和绑定语义
达到这一点后,再进入 editor 集成阶段,工程风险会显著更低。

View File

@@ -1,128 +0,0 @@
# Editor Viewport 宿主渲染收口总结 4.1
## 当前判断
截至 2026-04-01这一阶段的主线应视为
-`Editor -> ViewportHost -> Renderer -> RHI` 这条链路接通
- 把 editor viewport 的宿主层从 panel 内部逻辑中收出来
- 把这层宿主代码压到“可继续演进,但先停止扩张”的状态
这一阶段的目标不是继续往 editor viewport 里塞更多功能,也不是提前做完未来 renderer 的全部能力。
如果继续在本阶段里混入 gizmo 完整体系、GPU picking 正式方案、多 pass 大改、后处理、光照系统等内容,只会让阶段边界再次失控。
## 已完成的收口结果
### 1. editor viewport 已经不再是空壳
当前 `SceneView``GameView` 已经能通过统一的 viewport host 路径请求离屏 render target并把 renderer 输出展示到 editor 面板中。
当前已实际接通:
- Scene viewport 渲染
- Game viewport 渲染
- viewport resize 资源重建
- backpack 等真实模型内容在 editor 中显示
- grid / 选中 / 描边 / object id 读取链路
### 2. viewport host 的职责边界已经成型
当前 viewport 宿主层已经被拆成几块相对明确的职责:
- [editor/src/Viewport/ViewportHostSurfaceUtils.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostSurfaceUtils.h)
- viewport surface / texture / reuse 相关纯工具
- [editor/src/Viewport/ViewportHostRenderTargets.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostRenderTargets.h)
- viewport render target 生命周期与创建销毁
- [editor/src/Viewport/ViewportObjectIdPicker.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportObjectIdPicker.h)
- viewport object id 读取与像素映射
- [editor/src/Viewport/ViewportHostRenderFlowUtils.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostRenderFlowUtils.h)
- scene/game viewport 的失败回退策略、request 组装、成功态迁移
- [editor/src/Viewport/ViewportHostService.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostService.h)
- 保留真正的 orchestration、scene view camera、post pass 组装与 clear 执行
这意味着 viewport host 已经不再是“一个面板私有的大杂烩类”,而是一个有明确宿主边界的 editor 侧接入层。
### 3. 现阶段测试闭环已经补上
当前 editor 侧已经有明确的宿主层单测覆盖:
- [tests/editor/test_viewport_host_surface_utils.cpp](D:/Xuanchi/Main/XCEngine/tests/editor/test_viewport_host_surface_utils.cpp)
- [tests/editor/test_viewport_render_targets.cpp](D:/Xuanchi/Main/XCEngine/tests/editor/test_viewport_render_targets.cpp)
- [tests/editor/test_viewport_object_id_picker.cpp](D:/Xuanchi/Main/XCEngine/tests/editor/test_viewport_object_id_picker.cpp)
- [tests/editor/test_viewport_render_flow_utils.cpp](D:/Xuanchi/Main/XCEngine/tests/editor/test_viewport_render_flow_utils.cpp)
本轮收口完成后,`editor_tests` 已覆盖 viewport host 中最容易回归的纯策略与资源管理逻辑。
## 为什么到这里应当停止继续拆
当前 [editor/src/Viewport/ViewportHostService.h](D:/Xuanchi/Main/XCEngine/editor/src/Viewport/ViewportHostService.h) 剩下的主要内容,是以下几类“真正属于宿主编排”的职责:
- scene view camera 的创建与驱动
- focus / orientation axis 等 editor 专属视图控制
- scene view post pass 的装配
- scene/game viewport 的 render dispatch
- clear 执行
这些逻辑如果继续硬拆,很容易为了“文件更小”而把真正依赖 editor 运行时状态的 orchestration 人为打散,收益已经明显下降。
所以这一阶段的明确结论是:
- `ViewportHostService` 继续保持为宿主编排入口是合理的
- 不再以“继续拆文件”为本阶段目标
- 下一阶段应把重点转移到 renderer 本身的演进,而不是继续挤压 editor host
## 本阶段的结束标准
这一阶段以以下标准视为完成:
1. `editor_tests` 全部通过
2. `XCEditor` 能正常编译
3. viewport host 的主要纯逻辑都有独立单测
4. `ViewportHostService` 不再继续承载资源创建、object id 读取、失败策略等细碎职责
5. 明确哪些问题属于后续 renderer 阶段,而不是继续留在当前阶段消耗
## 明确不属于本阶段的内容
以下内容不再计入“editor viewport 宿主渲染收口”阶段:
- GPU object id 正式拾取方案
- renderer 内部通用多 pass / render graph 体系
- 更正式的 editor outline 方案升级
- gizmo 全量产品化
- 光照、阴影、后处理
- runtime 渲染管线的完整 SRP 式抽象
这些内容应进入 renderer 后续阶段,而不是继续塞回当前 viewport host 收口任务。
## 下一阶段建议
下一阶段主线应转为:
### 1. renderer 能力继续上移
把目前 editor 层为了接通 viewport 而保留的一些临时职责,逐步让 renderer 吸收为正式能力,例如:
- 多 pass 组织能力
- 正式的 object id / editor helper pass 接口
- editor 与 runtime 共享的 camera render path
### 2. editor 只消费 renderer 提供的正式输出
editor viewport 后续应更多扮演:
- render target 宿主
- 输入转发
- overlay / gizmo 宿主
- editor 专属交互入口
而不是继续承载 renderer 内部演进本体。
## 阶段性结论
这一阶段现在可以正式收口:
- editor viewport 已经从“空面板”进入“真实宿主层”
- renderer 与 RHI 已经能稳定把离屏结果送进 editor
- viewport host 的边界已经明确
- 后续不应继续在本阶段内部无限拆分,而应切换到 renderer 下一阶段

View File

@@ -6,15 +6,19 @@
- 把视觉样式、交互路由、编辑命令、dock 布局、面板壳层拆开。
-`Hierarchy / Project / Inspector / Console / MenuBar` 统一到同一套 shared UI 和 action 语义上。
- 保持 `Scene / Game` 先作为空壳 panel等待后续 `Viewport / RHI` 回归
- `Scene / Game` viewport 保持在当前真实主链上:`ViewportHostService -> Rendering + RHI -> ImGui panel`
-`Assets + .meta + Library` 项目工作流、脚本程序集构建与运行时状态纳入 editor 正式分层,而不是继续当外围脚本。
这意味着当前 editor 的重点是“编辑器外壳架构完整”,不是“运行时视口功能完整”。
这意味着当前 editor 的重点已经不是“先把外壳搭出来”,而是:
- 在现有外壳分层上继续收口真实可运行的 viewport / project / scripting 主链。
- 让 panel、viewport helper、commands、managers 和引擎侧 `Rendering / Resources / Scene / Scripting` 的边界继续清晰化。
## 2. 总体分层
当前 editor 推荐按下面的依赖方向理解:
`Application -> EditorLayer -> EditorWorkspace -> Panels -> Actions -> Commands -> Managers/Core`
`Application -> EditorLayer -> EditorWorkspace -> Panels/Viewport -> Actions -> Commands -> Managers/Core`
同时还有一条横向的共享 UI 层:
@@ -24,6 +28,10 @@
`InspectorPanel -> ComponentEditorRegistry -> IComponentEditor`
以及一条已经落地的 editor 到引擎主链:
`Viewport / Managers / Scripting -> Rendering / Resources / Scene / Scripting / RHI`
允许依赖的基本原则:
- `UI` 只负责样式 token、共享控件、popup/property-grid 等表现层能力,不承担业务语义。
@@ -32,6 +40,7 @@
- `Panels` 只保留最小的渲染壳层和少量局部瞬时状态,不直接堆业务逻辑。
- `Layout` 只负责 dockspace 和布局持久化,不处理 scene/project 业务。
- `Core/Managers` 提供 editor 运行时共享状态与数据入口。
- `Viewport` 是 editor 宿主和引擎渲染主链之间的正式桥接层,不是 panel 内随手拼的辅助代码。
不允许继续扩散的方向:
@@ -272,6 +281,60 @@
3.`ComponentEditorRegistry.cpp` 注册。
4. 如果涉及复杂交互,优先复用 `UI::PropertyGrid` 和 undo interactive change 机制。
### 3.10 Viewport
关键文件:
- `editor/src/Viewport/ViewportHostService.h`
- `editor/src/Viewport/IViewportHostService.h`
- `editor/src/Viewport/SceneViewportChrome.h`
- `editor/src/Viewport/SceneViewportInteractionFrame.h`
- `editor/src/Viewport/SceneViewportNavigation.h`
- `editor/src/Viewport/SceneViewportTransformGizmoCoordinator.h`
- `editor/src/Viewport/SceneViewportOverlayBuilder.h`
- `editor/src/Viewport/SceneViewportOverlayFrameCache.h`
- `editor/src/Viewport/SceneViewportOverlaySpriteResources.h`
- `editor/src/Viewport/SceneViewportResourcePaths.h`
- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h`
职责:
- 作为 editor 宿主与引擎 `Rendering + RHI` 的桥接层,维护 Scene / Game viewport 的离屏渲染接线。
- 维护当前 Scene View helper 主链:
- `SceneViewportChrome`
- `SceneViewportInteractionFrame`
- `SceneViewportNavigation`
- `SceneViewportTransformGizmoCoordinator`
- `ViewportHostService`
- 负责 overlay builder、overlay frame cache、sprite 资源准备、object-id picking、grid、outline 和 gizmo overlay state。
边界:
- `Viewport` 是正式子系统,不再是“等待未来回归”的空位。
- panel 不应自己拼接一套独立渲染路径;新的 viewport 行为优先落到 helper、host service 或 overlay pass。
- `SceneViewportShaderPaths.h` 当前主要是兼容 include资源路径真实 owner 已经转到 `SceneViewportResourcePaths.h`
### 3.11 Scripting / Project Workflow
关键文件:
- `editor/src/Scripting/EditorScriptAssemblyBuilder.h`
- `editor/src/Scripting/EditorScriptRuntimeStatus.h`
- `editor/src/Managers/ProjectManager.h`
- `editor/src/Managers/SceneManager.h`
- `editor/src/Utils/ProjectFileUtils.h`
职责:
- 解析项目根目录、读写 `Project.xcproject`,并把仓库内 `project/``--project <path>` 解析为当前工程。
- 驱动 `Assets + .meta + Library` 风格项目 workflow包括项目资源浏览、脚本程序集重建入口和运行时状态反馈。
- 把项目脚本程序集与 editor/runtime 实际消费的 `Library/ScriptAssemblies/` 目录连接起来。
边界:
- 与项目资产、`.meta``Library`、脚本程序集相关的问题不应再按“editor 仍处于无工程状态”理解。
- `ProjectManager``SceneManager``EditorScriptAssemblyBuilder` 与引擎侧 `ResourceManager / AssetImportService / ScriptEngine` 是协作关系,不应在 panel 里各自复制一层状态机。
## 4. EventBus 使用规则
`EventBus` 现在主要承担两类职责:
@@ -334,16 +397,19 @@
- dock layout controller 与 editor workspace 装配。
- inspector 的 component editor registry 接入。
- editor 级回归测试基础框架与关键命令测试。
- `Scene / Game` viewport 已经重新接回当前正式主链:引擎 `Rendering + RHI` 离屏输出 -> `ViewportHostService` -> ImGui panel。
- `Assets + .meta + Library` 项目工作流、`Project.xcproject`、脚本程序集重建与脚本运行时状态已经进入 editor 正式工作流。
- viewport helper 已按 `Chrome -> InteractionFrame -> Navigation -> TransformGizmoCoordinator -> ViewportHostService` 显式拆层。
当前明确暂缓:
- `Scene` panel 内容
- `Game` panel 内容
- `Viewport` 与 RHI 重新接入
- 把 editor 主线程上残留的同步资源兜底点继续清理到更严格的异步消费模型
- `Library bootstrap / scene streaming / explicit import` 的状态模型继续正式化到 UI
- 继续减少 panel 内局部瞬时状态与 helper 间重复规则
原因很明确:
- 这三部分依赖渲染壳层与后续 renderer/RHI 重构,暂时不适合和 editor UI 架构收尾混在一起
- 前三项已经不是“没接回来的未来工作”,而是已经落地、但还要继续收口的当前主线
## 8. 后续新增功能时的落点原则
@@ -355,6 +421,7 @@
- 新 inspector 组件面板:放 `ComponentEditors`
- 新 dock/window 布局控制:放 `Layout`
- 新 editor 全局状态:放 `Core/Managers`
- 新 viewport host / overlay / interaction helper`Viewport`
- 只是把已有能力拼进某个窗口:放 `Panels`
如果一个功能不知道放哪,一般先问自己:
@@ -371,8 +438,9 @@
- 继续压缩少量 panel 本地瞬时状态,能下沉的继续下沉。
- 继续补命令/路由回归测试,尤其是 inspector interactive undo 边界。
- 为将来的 viewport 回归预留稳定接入口,但暂不提前接入渲染逻辑
- 继续补 viewport helper、project workflow、script assembly builder 与 scene streaming 相关回归测试
-`Library bootstrap`、显式导入、后台 scene asset streaming 这三类状态在 editor UI 上分开表达。
结论:
editor 当前已经形成稳定分层,后面再做功能迭代时,应坚持“先看边界,再落代码”,不要回到 panel 内部堆逻辑的旧路线。
editor 当前已经形成稳定分层,而且这套分层已经承接了真实的 viewport / project / scripting 工作流。后面再做功能迭代时,应坚持“先看边界,再落代码”,不要回到 panel 内部堆逻辑的旧路线。

View File

@@ -1,165 +0,0 @@
# Editor 重构 3.26
## 当前判断
截至 2026-03-27如果只看 editor 的 UI 架构与编辑器壳层整理,不把 `Viewport / RHI``Scene / Game` 的真实内容算进去,这一轮重构大约已经完成 **96%**
如果把“编辑器整体可用度”一起算进去,则仍然没有结束,因为:
- `Scene` panel 仍然是空壳。
- `Game` panel 仍然是空壳。
- `Viewport` 尚未跟随 RHI 重构完成回归。
所以现在的真实状态是:
- **UI 架构已经基本收稳**
- **编辑器产品功能还没有做完**
## 已经完成的核心重构
### 1. 分层已经基本建立
当前 editor 已经形成比较稳定的职责划分:
- `UI` 负责主题、token、共享控件、popup、property-grid。
- `Actions` 负责菜单、快捷键、右键菜单、按钮动作的共享路由。
- `Commands` 负责 scene/project/entity/component 的编辑行为。
- `Layout` 负责 dock host、默认布局、布局重置与持久化。
- `Panels` 逐步退化为窗口壳层。
- `Core / Managers` 负责 editor context、selection、undo、scene、project 等共享状态。
- `ComponentEditors` 负责 inspector 中各组件的编辑器与注册体系。
这说明 editor 已经从“每个 panel 自己堆逻辑”的结构,切换到“共享层驱动”的结构。
### 2. 共享 UI 基础层已经落稳
已经把大量原先散落在 panel 内部的视觉和控件逻辑收口到 shared UI 层,包括:
- 主题和样式 token
- panel chrome
- toolbar/search/button
- popup / modal state
- empty state
- asset tile
- hierarchy tree node
- component section
- property grid / scalar / vector 编辑控件
这部分的意义是:后面再调 editor 外观,不应该回到 panel 内部逐个修补。
### 3. Action 路由已经成型
当前已形成两条主路由:
- `MainMenuActionRouter`
- `EditActionRouter`
并且 `Hierarchy / Project / Inspector / Console / MenuBar` 的主要菜单和快捷键语义已经接入共享 action/router。
当前已经做到:
- `Edit` 菜单不再只是一套写死逻辑,而是跟随 active action route 切换。
- menu / shortcut / context menu / toolbar 的动作开始共用同一套 action 定义。
- rename、popup、about、exit、reset layout 等交互已不再零散写在 panel 内部。
### 4. Commands 层已经承担主要编辑语义
当前主要编辑行为已进入 command 层:
- 新建 / 打开 / 保存场景
- 脏场景 fallback 保存
- 创建 / 删除 / 复制 / 粘贴 / duplicate / rename / reparent entity
- 创建文件夹 / 删除资源 / 打开资源 / 移动资源
- 添加 / 删除组件
同时undo / dirty / selection reset 等关键边界,已经尽量不再散落在 panel。
### 5. Dock / Workspace / Application 壳层已收口
当前已经完成:
- `EditorWorkspace` 统一 panel 装配与生命周期调度
- `DockLayoutController` 统一 dockspace 和 layout reset
- ImGui layout 持久化到项目 `.xceditor/imgui_layout.ini`
- `Application.cpp` 中的窗口、renderer、ImGui session、layer attach/detach 已完成明显拆分
这说明 editor 顶层壳层已经不再像之前那样把 UI、layout、backend、panel 生命周期混在一起。
### 6. Inspector 的 ComponentEditor 注册体系已稳定
当前 inspector 不再直接硬编码全部组件逻辑,而是通过:
- `IComponentEditor`
- `ComponentEditorRegistry`
- 各具体 `Transform / Camera / Light` component editor
来统一:
- 组件显示
- Add Component 菜单构建
- 组件可添加性 / 可删除性判断
这部分已经是后续扩展自定义组件 inspector 的正确落点。
### 7. 回归测试基础已补齐
当前已新增 `editor_tests`,并已覆盖关键 editor 行为,包括:
- hierarchy edit route 的 copy / paste / duplicate / delete / rename request
- project edit route 的 open / back / delete
- scene dirty save + load 后的 selection / undo reset
- reparent 时的 parent 切换、cycle 拦截与 world transform 保持
- main menu 的 exit / reset layout / about popup request
- hierarchy rename helper
- project create-folder / move-asset / open-folder helper
本轮收尾继续补充后,测试会进一步覆盖:
- clean scene 下的新建场景重置行为
- clean scene 下 fallback save 的 no-op 路径
- project move-asset 的非法输入保护
## 当前仍然剩下什么
### 架构收尾项
1. 继续压缩少量 panel 本地瞬时状态
目标是把还能共享的 popup / router / state 再往 shared 层收一点。
2. 继续补 editor 回归测试
重点补 command/router 边界,而不是 UI 像素测试。
3. 为 viewport 回归保留 editor shell 接口
但暂时不把 RHI/renderer 接进来。
### 暂缓项
以下内容不计入当前这一轮 UI 架构收尾:
- `Scene` panel 真正内容
- `Game` panel 真正内容
- `Viewport` 渲染接入
原因不是不做,而是这些工作和 renderer / RHI 重构直接绑定,应该放到后续阶段。
## 本轮新增文档
本次已补正式架构说明:
- `docs/plan/Editor架构说明.md`
这个文档用于明确:
- 各层职责边界
- 允许依赖方向
- panel / action / command / manager / component editor 的落点规则
- event bus、undo、dirty、selection 的统一约定
## 阶段性结论
当前 editor 可以明确地说:
- **UI 架构层面已经基本重构完成**
- **剩余主要是封口、验证和后续 viewport 接入准备**
也就是说,后面不应该再回到“看到一个 panel 问题就地补一段特殊逻辑”的方式,而应该继续沿着现有分层做增量完善。

View File

@@ -1,567 +0,0 @@
# Material Inspector与Shader属性面板收口计划
日期2026-04-07
## 1. 背景
当前 Rendering / Shader / Material 主线已经基本建立起正式架构:
- Shader 侧已经具备统一的 authoring/schema 能力。
- Material 运行时与 artifact 路径已经逐步从旧兼容逻辑中收口。
- Editor 中的 Material Inspector 仍然没有完全切换到“由 Shader schema 驱动”的正式工作流。
当前最突出的实际问题有两类:
1. Material 选择 Shader 后Shader 中声明的属性没有被正式暴露到 Inspector 面板上。
2. Material 面板上还残留了一些历史字段或临时 UI用户无法清晰判断哪些是正式能力哪些只是过渡产物。
此外,当前工程条件已经允许做更彻底的清理:
- `project/` 中旧材质资产可直接编辑。
- 当前不存在必须长期兼容的历史材质资产包袱。
因此,这一阶段的目标不是“继续兼容旧面板”,而是把 Material 编辑链路彻底切换到正式路径,并为后续 Shader/Material 编辑器扩展打好基础。
## 2. 本阶段目标
本阶段要完成以下目标:
1. 让 Material Inspector 成为 Shader schema 驱动的正式编辑入口。
2. 让材质面板只暴露当前正式架构中的概念,不再混入旧字段和误导性配置。
3. 保证 Editor 面板、运行时 `Material`、源资产、artifact、reimport 之间的数据一致性。
4. 用单测和必要的编辑器验证把这条链路收口,而不是停留在“能显示”层面。
## 3. 不在本阶段处理的内容
以下内容不属于本阶段主目标,避免范围失控:
- 自定义 Material Drawer 系统。
- Shader Graph / 节点式材质编辑器。
- 面向美术工作流的复杂 Material 预设库。
- SRP 层级的材质检查器定制机制。
- 完整的 Shader GUI 仿 Unity 高级扩展体系。
这些能力后续可以做,但必须建立在当前正式链路已经稳定收口的前提上。
## 4. 当前已确认的问题
### 4.1 Inspector 没有正式反射 Shader 属性
当前 Material Inspector 还没有完整读取 Shader schema 并按类型生成属性控件,导致:
- Shader 中声明的属性无法完整暴露。
- 用户无法直接编辑当前材质真正生效的参数。
- 材质默认值、覆盖值、纹理槽与资源引用状态之间关系不清晰。
### 4.2 材质面板仍存在历史路径残留
之前材质层面曾承担过一些本不应由材质承担的职责,例如:
- 材质自己选择 builtin pass。
- Inspector 里出现与当前正式架构不一致的旧字段。
这类逻辑已经开始清理,但 Editor 面板仍需同步彻底收口。
### 4.3 Editor 与运行时 Material 状态可能脱节
如果面板的编辑状态不是直接围绕正式 `Material` 数据模型建立,就容易出现:
- Inspector 显示值与运行时实际值不一致。
- 资源重载后 UI 状态失真。
- artifact / reimport 后材质参数丢失或回退异常。
### 4.4 缺少围绕正式工作流的测试闭环
仅靠手工点 Inspector 验证不足以支撑后续迭代。必须补足以下覆盖:
- Shader 切换后属性集合重建。
- 默认值与显式覆盖值的切换。
- Texture 属性与资源引用链路。
- Keyword 与 Render State 的持久化。
- artifact/reimport 后数据一致性。
## 5. 设计原则
本阶段严格遵循以下原则:
### 5.1 Shader 定义什么Material 就暴露什么
Material 不是独立定义属性结构的地方。属性结构必须由 Shader schema 决定Inspector 只是将其可视化并允许编辑。
### 5.2 Material 只承载实例数据,不承载管线选择策略
材质应承载:
- Shader 引用
- Shader schema 对应的属性值
- Keyword 状态
- 合法的 render state 覆盖
材质不应再承载:
- 独立指定 builtin pass 的旧路径
- 与 Shader metadata 冲突的临时策略字段
### 5.3 Editor 不发明第二套数据模型
Inspector 的中间态必须服务于正式数据模型,而不是绕开 `Material` 自己维护一套平行逻辑。
### 5.4 面板必须可验证
每一步改动都必须能通过测试或明确的编辑器验证闭环证明正确,不接受只靠目测“看起来差不多”。
## 6. 分阶段执行计划
## Phase 1现状审查与残留路径清点
### 目标
彻底梳理当前 Material Inspector、Material 资源、Shader schema、artifact 序列化之间的实际数据流。
### 任务
- 审查 `InspectorPanel` 当前 Material 面板的数据来源和写回路径。
- 审查 `Material` 当前正式字段与历史遗留字段。
- 审查 `MaterialLoader``AssetDatabase`、artifact schema 当前是否仍保留不必要兼容路径。
- 识别 Material 面板中哪些字段是正式路径,哪些属于旧方案残留。
### 完成标准
- 列清楚当前正式数据流。
- 列清楚必须删除的旧字段/旧 UI。
- 列清楚缺失的 schema 反射入口。
## Phase 2Material Inspector 数据模型收口
### 目标
让 Material 面板的数据模型只围绕正式架构组织。
### 任务
- 移除 Material 层面的旧 pass 选择 UI 与残余兼容路径。
- 整理 Inspector 内部状态结构,只保留:
- Shader 资源引用
- Shader schema 对应属性
- Keywords
- Render Queue / Render State
- Tags仅保留当前正式允许暴露的部分
- 避免 Inspector 保存与运行时 `Material` 不一致的冗余字段。
### 完成标准
- 面板字段与正式 `Material` 模型一一对应。
- 旧字段彻底不再出现在材质编辑界面和保存链路中。
## Phase 3Shader schema 驱动的属性面板生成
### 目标
Material Inspector 能基于 Shader schema 动态生成属性编辑 UI。
### 任务
- 读取 Shader 声明的属性定义与默认值。
- 按属性类型渲染控件:
- `float`
- `int`
- `bool`
- `float2/3/4`
- `texture`
- 正确显示每个属性的:
- 属性名
- 显示名
- 默认值
- 当前覆盖值
- 纹理槽绑定状态
- 资源引用路径/AssetRef 状态
- 明确“未覆盖时使用 Shader 默认值”的表现形式。
### 完成标准
- 选定 Shader 后Inspector 中能稳定暴露该 Shader 的正式属性集合。
- 面板能正确编辑并持久化这些属性。
## Phase 4Editor 与运行时一致性收口
### 目标
保证 Material Inspector 编辑结果与运行时 `Material`、artifact、reimport 行为一致。
### 任务
- 对齐 Inspector 写回逻辑与 `Material` 正式 API。
- 验证 Shader 切换时属性重建、默认值回退、无效属性清理。
- 验证纹理属性在源资产、artifact、异步资源加载中的一致性。
- 验证关键词与 render state 在 reload/reimport 后不丢失。
### 完成标准
- Inspector 改动能被运行时正确读取。
- Reload / Reimport / Artifact Round Trip 后材质数据不漂移。
## Phase 5测试补齐与阶段验收
### 目标
为正式工作流建立可持续回归验证。
### 任务
- 补充或更新 `material_tests`
- 补充或更新 `asset_tests`
- 若材质解析或 builtin pass 匹配受影响,补充必要的 `rendering_unit_tests`
- 重新编译 `XCEditor`,执行人工冒烟验证。
### 验收项
- `material_tests` 全绿。
- `asset_tests` 全绿。
- 必要的 `rendering_unit_tests` 全绿。
- `XCEditor` 编译通过。
- Material Inspector 中 Shader 属性暴露、编辑、保存、重载行为正确。
## 7. 预期交付结果
本阶段完成后,工程应达到以下状态:
1. Material 面板正式切换为 Shader schema 驱动。
2. 材质不再承担旧 pass 选择职责。
3. Inspector 中只剩下当前正式架构允许存在的字段。
4. 材质属性、纹理槽、关键词、render state 都能稳定编辑并正确持久化。
5. Shader / Material 后续扩展拥有清晰入口,不再建立在旧兼容逻辑之上。
## 8. 风险与注意事项
### 8.1 Shader 切换会触发属性重建
必须定义清楚以下规则:
- 与新 Shader schema 匹配的属性如何保留。
- 不匹配的旧属性如何移除。
- 默认值何时回退,何时保留用户覆盖。
### 8.2 Texture 属性不仅是 UI 问题
Texture 属性同时涉及:
- Inspector 显示
- AssetRef
- 资源路径
- artifact 存储
- 延迟加载
因此不能只改面板显示,必须连同资源路径一起验证。
### 8.3 Render State 需要坚持“正式最小集”
Material 面板不应再次演化成随意堆字段的临时入口。所有 render state 暴露都必须建立在当前正式支持的能力之上。
## 9. 建议执行顺序
建议严格按以下顺序落地:
1. 先完成旧字段与旧路径清点。
2. 再重构 Inspector 数据模型。
3. 再接 Shader schema 驱动属性 UI。
4. 再收口 Editor 与运行时一致性。
5. 最后统一补测与验收。
不能反过来先堆 UI再回头补数据模型否则很容易再次生成新的临时方案。
## 10. 阶段结论
当前最应该推进的不是继续增加 Material 编辑功能花样,而是把 Material Inspector 这条正式链路做对、做稳、做干净。
只要这一阶段收口完成,后续无论是:
- 更完整的 Shader authoring
- Material 默认面板
- 针对 Renderer/SRP 的材质体系扩展
- 更接近 Unity 的 Shader/Material 编辑工作流
都会建立在清晰、稳定、可验证的基础之上。
## 11. Phase 1 审查结果
状态:已完成
本阶段已对当前 Material Inspector、运行时 `Material`、Shader schema、material source/artifact 路径进行了实际代码审查,结论如下。
### 11.1 当前 Inspector 数据模型先天不完整
当前 `InspectorPanel::MaterialAssetState` 只维护了以下内容:
- `shaderPath`
- `renderQueue`
- `renderState`
- `tags`
它没有正式承载以下关键数据:
- Shader schema 属性列表
- 材质属性当前值
- 纹理槽与贴图引用
- 关键词状态
- Render State Override 开关本身
这意味着当前 Inspector 不是“少画了几个控件”,而是其内部状态模型本身就无法表示正式的材质编辑数据。
### 11.2 当前 Inspector 保存链路会丢失材质的正式内容
当前 `BuildMaterialAssetFileText()` 仅写出:
- `shader`
- `renderQueue`
- `tags`
- `renderState`
它不会写出:
- `properties`
- `textures`
- `keywords`
因此,只要一个材质已经拥有 Shader 属性、纹理槽或关键词,若用户通过当前 Inspector 保存一次,该材质源文件就可能被覆盖成一个严重简化后的版本,导致正式材质数据丢失。
### 11.3 当前 Inspector 的实时写回同样不完整
当前 `ApplyResolvedMaterialStateToResource()` 仅把面板状态写回到运行时 `Material` 的以下部分:
- `SetShader`
- `SetRenderQueue`
- `SetRenderState`
- `ClearTags` / `SetTag`
它不会写回:
- Shader schema 属性值
- 纹理绑定
- 关键词
因此 Inspector 当前即使支持继续加控件,如果不先重构数据模型,运行时同步仍然会不完整。
### 11.4 Render State Override 当前没有被正式建模
运行时 `Material::SetRenderState()` 会自动把 `HasRenderStateOverride` 置为 `true`
但当前 Inspector
- 没有暴露 “是否启用 Render State Override”
- 保存时始终写出完整 `renderState`
- 应用时始终调用 `SetRenderState`
这意味着只要用户通过当前材质面板保存,材质就会被强制推进到显式 Render State Override 路径。这个行为不符合正式设计,必须在下一阶段一起修正。
### 11.5 运行时底层能力其实已经基本齐备
当前 runtime/resource 层已经具备正式所需的大部分能力:
- `ShaderPropertyDesc` 已包含 `name / displayName / type / defaultValue / semantic`
- `Shader` 已提供 `GetProperties()` / `FindProperty()` / keyword declaration 能力
- `Material` 已提供完整的 `SetFloat/SetInt/SetBool/SetTexture...`
- `Material` 已支持 Shader schema 默认值同步
- `MaterialLoader` 已支持解析 `properties / textures / keywords / tags / renderState`
- material artifact 当前已支持这些正式数据的持久化
也就是说,当前问题的主要矛盾不在资源系统,而在 Editor 侧没有接入正式模型。
### 11.6 当前阶段的根本性结论
下一阶段不能直接在现有材质面板上继续堆控件。
必须先完成:
1. `MaterialAssetState` 的正式重构
2. Save/Reload/Apply 链路对 `properties / textures / keywords / renderState override` 的建模
3. Inspector 与运行时 `Material` 之间的一致性重建
只有先做完这些,后续的 Shader schema 驱动属性 UI 才不会继续建立在错误基础上。
### 11.7 Phase 2 的直接执行范围
基于本阶段审查结果,下一阶段将直接进入以下工作:
1. 扩展 `MaterialAssetState`,让其正式承载属性、纹理槽、关键词与 render state override。
2. 重构 `Populate / Apply / Save / Reload` 这四条关键链路。
3. 彻底消除当前“保存一次就丢属性”的结构性问题。
## 12. Phase 2 执行结果
状态:已完成
本阶段已经完成 Material Inspector 数据模型的第一轮正式收口,重点是先把状态模型与保存链路做正确,而不是提前堆属性 UI。
### 12.1 已完成内容
- `MaterialAssetState` 已扩展为正式承载:
- `keywords`
- `properties`
- `texture bindings`
- `renderState override`
- `PopulateMaterialAssetStateFromResource()` 已从运行时 `Material` 同步:
- Shader 路径
- Render Queue
- Render State
- Render State Override 标志
- Tags
- Keywords
- Properties / Texture Bindings
- `ApplyResolvedMaterialStateToResource()` 已开始完整写回:
- Shader
- Render Queue
- Render State
- Render State Override
- Tags
- Keywords
- Properties / Texture Bindings
- `BuildMaterialAssetFileText()` 已开始正式写出:
- `keywords`
- `properties`
- `textures`
- 仅在启用 override 时才写 `renderState`
- Inspector 已增加 `Render State Override` 开关,避免默认把材质强行推进到显式 override 路径。
### 12.2 本阶段解决的核心问题
本阶段已经解决了最危险的结构性问题:
- 当前 Inspector 再保存材质时,不会像之前那样天然丢掉 `properties / textures / keywords`
- Render State 是否为显式 override已经不再是隐藏副作用而成为正式状态的一部分。
### 12.3 本阶段仍未完成的部分
以下能力还没有在本阶段完成,这是下一阶段的工作重点:
- 基于 Shader schema 的属性 UI 自动生成
- 各属性类型的可视化编辑控件
- 默认值/覆盖值的明确表现
- 纹理槽的正式资源选择 UI
- 关键词与属性在 Inspector 中的可视化编辑
因此Phase 2 的性质是“先把材质状态模型与保存链路做对”,而不是“材质面板功能已经完整”。
## 13. Phase 3 执行结果
状态:已完成
本阶段已经开始把正确的材质状态模型真正暴露到 Inspector 上,重点是基于 Shader schema 生成属性面板,并处理 Shader 切换时的状态重建。
### 13.1 已完成内容
- Inspector 已新增 `Properties` 区块。
- 属性区会基于当前 Shader 的 schema 动态生成,而不是写死字段。
- 当前已接入的属性类型包括:
- `Float / Range`
- `Int`
- `Color`
- `Vector`
- `Texture2D / TextureCube`
- Texture 类型已接入资源选择控件,不再只是文本占位。
- 每个属性当前会直接显示 Shader 中声明的默认值文本,作为当前参数的基线提示。
### 13.2 Shader 切换行为已收口
本阶段同时处理了一个关键一致性问题:
- 当用户切换 Shader 时Inspector 会先基于新 Shader schema 重建 `MaterialAssetState`
- 能与新 Shader 对齐的同名属性会尽量保留原值
- 已不被新 Shader 声明的旧属性不会继续残留在保存结果里
- 与新 Shader 不匹配的旧关键词也会被清理
这样可以避免旧材质属性被继续写回到新 Shader 材质文件中,从而减少生成无效材质源文件的风险。
### 13.3 本阶段仍未完成的部分
以下内容还需要后续阶段继续收口:
- 属性默认值与显式覆盖值的正式“重置/回退”交互
- 关键词的可视化编辑 UI
- 更完整的属性类型与显示策略细化
- 针对 Inspector 材质链路的专门自动化测试
因此Phase 3 的完成标准是“Shader schema 驱动的属性面板已经建立起来”,但还不是最终形态。
## 14. Phase 4 执行结果
状态:已完成
本阶段重点处理的是 authoring-state 与 runtime-state 的一致性问题,避免 Inspector 因为单纯从运行时对象反推而破坏材质源文件的语义。
### 14.1 已完成内容
- Inspector 现在会回读材质源文件中实际 authored 的:
- `properties`
- `textures`
- `keywords`
- `renderState`
- 材质状态中已加入“是否需要序列化回源文件”的 authored 标记。
- 保存材质时,不再无条件把所有运行时属性都写回源文件。
- 对于未在源文件中显式 authored 的属性,当前会继续保持“继承 Shader 默认值”的语义。
- 当用户在 Inspector 中修改属性或贴图后,对应项才会被标记为显式 authored 并写回源文件。
### 14.2 本阶段解决的核心问题
本阶段解决的是一个架构层面的隐患:
- 如果只从运行时 `Material` 反推回材质源文件,打开并保存一次材质,就会把 Shader 默认值全部固化进 `.mat`
- 一旦默认值被固化,后续 Shader 默认值再调整,材质就不再继承新的默认值。
当前这条链路已经收口到更合理的状态:
- 只有显式 authored 的 override 才会写回
- 默认值仍然可以继续作为 Shader 侧的基线被继承
### 14.3 本阶段仍未完成的部分
以下内容仍然需要下一阶段继续完成:
- 针对 Inspector 材质链路的专门自动化测试
- 属性“重置到默认值”的正式交互
- 关键词的可视化编辑 UI
- 更完整的属性类型/显示策略覆盖
因此Phase 4 的性质是“先把 authoring 语义做正确”,为最后的测试与收口创造条件。
## 15. Phase 5 执行结果
状态:已完成
本阶段重点不是继续扩材质面板功能,而是把已经形成的正式链路变成“可持续回归验证”的状态,并把测试面收口到 Inspector 实际依赖的核心逻辑上。
### 15.1 已完成内容
- 新增 `MaterialInspectorMaterialState` / `MaterialInspectorMaterialStateIO` 辅助模块,承载:
- Material Inspector 状态结构
- source authored presence 解析
- Shader 切换时的属性/关键词重同步
- 材质源文件序列化文本生成
- 新增 `tests/editor/test_material_inspector_material_state_io.cpp`,覆盖:
- authored 属性/纹理/关键词标记识别
- Shader 切换时保留兼容 override、清理陈旧字段
- 非 authored 默认值不写回源文件
- 纹理 override 与 render state override 的正式序列化
- `editor_tests` 构建链路已接入上述 helper 与新测试文件。
### 15.2 已执行验证
- `cmake --build build --config Debug --target XCEditor -j 8`
- `cmake --build build --config Debug --target editor_tests -- /v:minimal`
- `build/tests/Resources/Material/Debug/material_tests.exe`
- `build/tests/Core/Asset/Debug/asset_tests.exe`
- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=MaterialInspectorMaterialStateIOTest.*`
验证结果:
- `XCEditor` 编译通过
- `material_tests`61 / 61 通过
- `asset_tests`56 / 56 通过
- `MaterialInspectorMaterialStateIOTest`4 / 4 通过
### 15.3 当前剩余风险
- 全量 `editor_tests.exe` 在顺序执行 `EditorActionRoutingTest.*` 时仍存在既有的共享状态级别挂起现象。
- 该挂起并非本次新增的材质面板测试本身触发:
- 新增 `MaterialInspectorMaterialStateIOTest` 单独执行通过
- `EditorActionRoutingTest.PlayModeAllowsRuntimeSceneUndoRedoButKeepsSceneDocumentCommandsBlocked` 单独执行通过
- 因此,本阶段围绕 Material Inspector / Shader 属性面板的测试收口已经完成,但 Editor 其余历史测试链路仍需单独排查。

View File

@@ -0,0 +1,765 @@
# NanoVDB 稀疏体积渲染正式集成计划Unity 对齐版)
文档日期2026-04-08
适用范围:当前 `XCEngine``Resources / Shader / RHI / Rendering / Components / Editor` 体系,以及未来要对齐 Unity 的 C#、Shader、SRP 工作流。
文档目标:把 `MVS/VolumeRenderer` 中已经验证过的 `NanoVDB + GPU Buffer + Ray Marching` 原型,整理成一套能正式进入引擎主线、且从一开始就严格兼容 Unity 风格 shader/C# 语义的落地方案。
---
## 1. 先给结论
这条主线的正式方向不是:
1. 把体积数据退化成 `Texture3D`
2. 给当前引擎临时塞一套 volume 专用 shader 语法
3. 只在某个后端里私下打通一条 demo 路径
这条主线的正式方向必须是:
1. 保留 `NanoVDB` 的稀疏体积本质
2. 把 buffer 资源能力补齐到引擎正式架构里
3. 让高层 authoring 语义严格对齐 Unity
4. 让低层 RHI 分类只存在于引擎内部
5. 让 volumetric 成为 renderer / future SRP 中的正式 pass而不是特例后处理
因此,这份计划默认坚持一条红线:
`StructuredBuffer / RawBuffer` 可以作为引擎内部资源分类存在,但作者可见语义、未来 C# API 语义、shader 写法语义,都必须对齐 Unity 既有做法,而不是引擎自创。
---
## 2. 设计总原则
### 2.1 Unity 对齐原则
后续所有设计都必须同时满足下面这些条件:
1. shader 文件继续朝 Unity 风格 `.shader + HLSLPROGRAM` 组织收口。
2. 作者侧 buffer 语义必须沿用 HLSL/Unity 现成写法,例如:
- `StructuredBuffer<T>`
- `ByteAddressBuffer`
- `RWStructuredBuffer<T>`
- `RWByteAddressBuffer`
3. 不允许为了体积渲染单独发明新的高层 buffer 关键字、声明块或材质 schema。
4. `Properties` 块只用于可序列化作者参数,不用于声明或承载 GPU buffer。
5. 未来 C# 层的 buffer 接口方向,必须对齐 Unity 的:
- `ComputeBuffer`
- `GraphicsBuffer`
- `Material.SetBuffer`
- `Shader.SetGlobalBuffer`
- `CommandBuffer.SetGlobalBuffer`
6. renderer 侧对 buffer 的真正绑定,必须是运行时 per-pass / per-object / global resource binding而不是材质资产直接持有底层 view。
7. `StructuredBuffer / RawBuffer` 在引擎里首先是底层编译和绑定分类,不是高层 authoring 概念。
### 2.2 稀疏体积原则
1. `NanoVDB` 必须按稀疏体积方案正式接入。
2. 不允许把正式路线退化成 `Texture3D` 替代方案。
3. `Texture3D` 可以作为引擎未来的独立能力预留,但不是这条主线的收口路径。
### 2.3 SRP 兼容原则
1. volumetric 必须被设计成正式 scene pass。
2. 它的输入和生命周期要能自然映射到未来 SRP 的 renderer feature / render pass event。
3. 不允许把它设计成只能在 builtin pipeline 中硬编码存在的特例逻辑。
---
## 3. Unity 参考基线
`参考/Unity NanoVDB` 已经给出了一条对本引擎非常重要的参考方向:
1. C# 侧使用 `ComputeBuffer`
2. 运行时通过 `Material.SetBuffer("buf", gpuBuffer)` 绑定
3. shader 侧通过 `StructuredBuffer<uint>` 读取
4. `ComputeBufferType.Default` 对应的正是通用 GPU buffer 资源路径,而不是材质属性系统
这说明后续正式集成时,高层语义应该对齐的是:
1. Unity 的 buffer authoring / binding 模式
2. Unity 的 shader 资源声明模式
3. Unity/HDRP 中把体积渲染插入 render pass 的方式
而不是:
1. 直接照抄 `MVS` 的 D3D12 私有代码
2. 用引擎内部 `RawBuffer` 这个名词去污染高层 shader/C# 接口
3. 为了先出图,临时开 volume 专用旁路
---
## 4. 本轮明确不做什么
为了防止架构被 demo 反向拖偏,本轮明确不做下面这些事:
1. 不把 `NanoVDB` 退化成正式 `Texture3D` 方案。
2. 不把 `MVS/VolumeRenderer` 的 D3D12 私有 helper、私有绑定逻辑直接搬进引擎主线。
3. 不先做全局体积雾、froxel fog、clustered volumetrics。
4. 不先做复杂编辑器 authoring 工具,例如体积笔刷和节点编辑器。
5. 不先做 DXR / path tracing 体渲染。
6. 不为体积渲染另开一套与 Unity 不一致的 shader/material/C# 表层语法。
---
## 5. 当前真正缺的不是“算法”,而是正式能力
当前原型已经证明了下面几件事:
1. `NanoVDB` 数据可以读。
2. `NanoVDB` 数据可以上传到 GPU buffer。
3. shader 可以基于 `pnanovdb` 做树遍历、HDDA 跳空和 ray marching。
4. D3D12 原型能完成从数据到画面的闭环。
但引擎主线真正还缺下面这些正式能力:
1. `.nvdb` 如何进入资产与 artifact 体系。
2. shader 如何用 Unity 风格语义正式声明 buffer 资源。
3. 材质系统如何和运行时 buffer 绑定做严格职责分离。
4. RHI 三后端如何正式支持 buffer SRV/UAV。
5. renderer 如何把体积渲染作为正式 pass 插入 frame。
6. 未来 C# 层如何以 Unity 风格接口绑定 buffer。
7. editor 和测试如何验证整条链路不会回归。
所以本计划的本质不是“移植 demo”而是“先补正式能力再把 demo 算法放到正式能力之上”。
---
## 6. 正式架构目标
### 6.1 作者可见层:严格对齐 Unity 风格
这一层是未来引擎用户、shader 作者、C# 脚本作者看到的语义表面。
要求如下:
1. `.shader` 文件语法继续按 Unity 风格推进。
2. buffer 在 shader 中的声明使用 HLSL 现成资源类型,不使用自定义资源关键字。
3. 材质面板暴露的是作者参数,而不是底层 GPU buffer 本体。
4. 如果未来提供脚本绑定接口,脚本看到的应该是 `ComputeBuffer / GraphicsBuffer` 风格对象和 `SetBuffer` 风格 API。
这层明确禁止:
1.`Properties` 中定义 `StructuredBuffer``RawBuffer`
2.`.shader` 外层再发明 `BufferResources { ... }` 之类自定义块
3. 让作者直接接触 `RHIResourceView` 或后端 view descriptor
### 6.2 引擎中层:运行时绑定契约
这一层负责把“作者声明的 shader 资源”和“运行时真实绑定的 GPU 资源”接起来。
正式边界应该是:
1. `Material` 只持有:
- shader 引用
- keyword / render state
- 序列化参数
- 常规纹理/采样器类作者资源
2. `VolumeField` 或其他 runtime producer 提供真实 GPU buffer 资源
3. `Renderer` / `CommandBuffer` / future script binding API 在渲染阶段把 buffer 绑定进 pass
也就是说:
1. `NanoVDB` 主数据不是普通 material property
2. 它是运行时资源绑定
3. 材质只负责“怎么渲染”
4. `VolumeField` 负责“渲染的数据是什么”
### 6.3 引擎底层RHI 资源分类
在 RHI 与编译反射层,可以存在正式的内部分类,例如:
1. `StructuredBuffer`
2. `RawBuffer`
3. 后续可扩展:
- `ByteAddressBuffer` 的内部映射
- `RWStructuredBuffer`
- `RWByteAddressBuffer`
但要强调:
1. 这些是内部分类,不是高层 authoring 语法。
2. 高层写的是 Unity/HLSL 资源声明。
3. 引擎负责把这些声明反射并落到底层分类。
### 6.4 资源层:正式 VolumeField 资产
新增正式资源抽象:
1. `ResourceType::VolumeField`
2. `Resources::VolumeField`
3. `VolumeFieldLoader`
4. `NanoVDBVolumeImporter`
导入链路建议:
1. 源资产:`Assets/.../*.nvdb`
2. artifact`Library/Artifacts/.../main.xcvol`
`main.xcvol` 建议包含:
1. schema version
2. storage kind
3. grid type / grid class
4. world bbox
5. voxel size
6. active voxel statistics
7. payload byte size
8. 原始 `NanoVDB` blob
原则:
1. 第一阶段不做有损转换。
2. artifact 中保留原始稀疏表示。
3. metadata 单独结构化,供 loader / renderer 快速读取。
### 6.5 渲染层:正式体积 pass
正式新增:
1. `VolumeRendererComponent`
2. `VisibleVolumeItem`
3. `RenderSceneData.visibleVolumes`
4. `BuiltinVolumetricPass`
该 pass 的职责:
1. 消费 `VisibleVolumeItem`
2. 绑定 camera / depth / lighting / shadow / volume buffer / material 参数
3. 执行 ray marching
4. 合成到 scene color
推荐阶段位置:
1. `ShadowCaster`
2. `Depth / Opaque`
3. `Skybox`
4. `Volumetrics`
5. `Transparent`
6. `PostProcess`
7. `FinalColor`
这保证它天然兼容未来 SRP 的 renderer feature / pass event 方向。
### 6.6 C# 层预留目标
即使当前 C# 模块还没完全落地,这条计划也必须先把接口方向冻结。
后续需要预留的高层语义包括:
1. `ComputeBuffer`/`GraphicsBuffer` 风格资源对象
2. `Material.SetBuffer`
3. `Shader.SetGlobalBuffer`
4. `CommandBuffer.SetGlobalBuffer`
这一层的原则是:
1. 先冻结语义,再决定托管层具体封装细节
2. 不允许等到以后 C# 接入时再发现底层 buffer 路径与 Unity 语义冲突
---
## 7. 关键命名与职责冻结
为了避免后续反复推翻命名,这一轮先冻结:
1. 正式资源抽象:`VolumeField`
2. 存储种类:`VolumeStorageKind::NanoVDB`
3. 导入器:`NanoVDBVolumeImporter`
4. 运行时加载器:`VolumeFieldLoader`
5. 组件:`VolumeRendererComponent`
6. frame data`VisibleVolumeItem`
7. pass`BuiltinVolumetricPass`
命名原则:
1. 引擎对外抽象是 `VolumeField`
2. `NanoVDB` 是第一种正式存储后端
3. 不把具体文件格式命名泄露到整个 renderer 表面
---
## 8. Shader 与 Buffer 的正式契约
这是整个计划最关键的部分。
### 8.1 shader 作者看到的语义
作者应该写的是:
1. `StructuredBuffer<uint>` 这类 Unity/HLSL 现成声明
2. 未来必要时的 `ByteAddressBuffer`
3. 如果进入写路径,再扩到 `RWStructuredBuffer<T>` / `RWByteAddressBuffer`
作者不应该写的是:
1. `RawBuffer`
2. `StructuredRawBuffer`
3. 任何引擎自创的高层 buffer 类型关键字
### 8.2 材质系统的职责边界
材质系统只负责:
1. 密度、吸收、散射、步长、相位函数参数
2. tint / emission
3. shader variant / keyword
4. render state
材质系统不负责:
1. 序列化 `NanoVDB` payload
2. 序列化 GPU buffer handle
3. 序列化底层 SRV/UAV view
### 8.3 运行时绑定职责
运行时绑定应该由以下层级之一完成:
1. `Renderer`
2. `CommandBuffer`
3. future script API
典型语义应接近:
1. `material.SetBuffer("buf", volumeBuffer)`
2. `commandBuffer.SetGlobalBuffer(name, buffer)`
### 8.4 内部映射规则
建议的内部映射是:
1. `StructuredBuffer<T>` -> `ShaderResourceType::StructuredBuffer`
2. `ByteAddressBuffer` -> `ShaderResourceType::RawBuffer`
3. `RWStructuredBuffer<T>` -> UAV + structured 分类
4. `RWByteAddressBuffer` -> UAV + raw 分类
这里的重点不是名字,而是边界:
1. 高层保持 Unity/HLSL 语义
2. 中层做反射和 metadata
3. 低层做真正 view 创建与 descriptor 绑定
### 8.5 一个硬性前置条件
如果当前 shader authoring 体系还不能正式表达 Unity 风格 buffer 声明,那么这个缺口的优先级高于体积渲染本身。
也就是说:
1. 不允许为了先做 volumetric单独给它走私有 shader 语法
2. 必须先把正式 shader authoring / reflection / runtime binding 路径打通
---
## 9. RHI 正式能力目标
RHI 侧至少需要补齐:
1. `RHIDevice::CreateShaderResourceView(RHIBuffer*, const ResourceViewDesc&)`
2. `RHIDevice::CreateUnorderedAccessView(RHIBuffer*, const ResourceViewDesc&)`
3. `ResourceViewDesc` 的 buffer 字段:
- firstElement
- elementCount
- structureByteStride
- raw / structured / formatted 标记
4. `RHIResourceView` 对 buffer SRV/UAV 的统一表达
5. descriptor set / bind group 更新路径对 buffer view 的正式支持
三后端预期映射:
1. D3D12
- buffer SRV
- buffer UAV
2. Vulkan
- storage buffer
- 需要时使用 texel buffer 路径
3. OpenGL
- SSBO 优先
- 明确 GLSL 转译和绑定规则
这里必须强调:
1. 这些是底层实现能力
2. 它们服务于高层 Unity 风格语义
3. 它们不能反过来决定高层作者接口长什么样
---
## 10. 正式实施阶段
## 阶段 0冻结 Unity 对齐边界
### 目标
在改代码前,先冻结高层语义、中层契约、低层分类三层边界。
### 任务
1. 冻结 `VolumeField` / `VolumeRendererComponent` / `VisibleVolumeItem` / `BuiltinVolumetricPass` 命名
2. 冻结体积渲染在 scene pass 中的阶段位置
3. 冻结高层 buffer 语义:
- 只接受 Unity/HLSL 写法
4. 冻结材质边界:
- buffer 不进 `Properties`
5. 冻结未来 C# 方向:
- `ComputeBuffer / GraphicsBuffer + SetBuffer` 语义
6. 冻结内部映射:
- `StructuredBuffer<T>` -> internal structured
- `ByteAddressBuffer` -> internal raw
### 验收标准
1. 后续实现阶段不再反复摇摆高层语义
---
## 阶段 1先补底层 buffer 资源视图能力
### 目标
补齐 renderer 目前缺失的“buffer 作为正式 shader 资源”的底层能力。
### 任务
1. 完成 `RHIBuffer` 的 SRV/UAV 创建接口
2. 补齐 `ResourceViewDesc` 的 buffer 视图字段
3. 让 descriptor set 更新路径正式支持 buffer 类 view
4. D3D12 / Vulkan / OpenGL 三后端同时按正式接口设计
5. 明确 backend capability 与错误回报,不允许 silent wrong image
### 测试
1. `tests/RHI/unit` 增加 buffer SRV/UAV 创建测试
2. descriptor set buffer binding 测试
3. 三后端分别验证 buffer view 不再被误当成 texture view
### 验收标准
1. 引擎能在不依赖 texture hack 的前提下,正式把 GPU buffer 绑定给 shader
---
## 阶段 2打通 Unity 风格 shader buffer 语义
### 目标
让 shader authoring / parser / reflection / artifact / runtime binding 能正式表达 Unity/HLSL 风格 buffer 资源。
### 任务
1. 扩展 shader 反射模型以支持 structured/raw buffer 资源
2. 明确 `StructuredBuffer<T>``ByteAddressBuffer` 的内部映射
3. 扩展 runtime build、pass layout、descriptor metadata 对新资源类型的支持
4. 明确 OpenGL 的 GLSL/SSBO 转译规则
5. 严格禁止 volume 专属私有 shader 语法旁路
### 强约束
1. 不新增高层 `RawBuffer` 关键字
2. 不新增 `MaterialPropertyType::StructuredBuffer / RawBuffer`
3. 如果 `Properties` 中尝试声明 buffer必须报错或拒绝导入
### 测试
1. `tests/Resources/Shader`
2. `tests/Resources/Material`
3. `tests/Rendering/unit` 中的 pass layout / metadata 测试
4. authoring 约束测试:
- Unity/HLSL 风格 buffer 声明可以被正确反射
- 非法把 buffer 塞进 `Properties` 会被拒绝
### 验收标准
1. shader 资源描述层正式支持 buffer 资源
2. 高层 authoring 语义仍保持 Unity 风格,不被底层分类污染
---
## 阶段 3冻结 future C# buffer 绑定契约
### 目标
在 C# 模块尚未完全落地前,先把与本主线相关的 buffer 绑定语义冻结。
### 任务
1. 定义 native 侧可对应 future C# 的 buffer 绑定入口
2. 约束 API 形状向 Unity 对齐:
- `Material.SetBuffer`
- `Shader.SetGlobalBuffer`
- `CommandBuffer.SetGlobalBuffer`
3. 明确 `VolumeField` 与 runtime buffer producer 的关系
4. 保证未来托管层不会直接看到 RHI view 对象
### 测试
1. native 侧 binding metadata 测试
2. material/runtime/global 三类 buffer 绑定路径测试
### 验收标准
1. 当前实现方向已经为 future C# 留好 Unity 风格接口落点
---
## 阶段 4接入正式 VolumeField 资产链路
### 目标
`.nvdb` 成为项目里的正式资源,而不是 demo 私有文件。
### 任务
1. 新增 `ResourceType::VolumeField`
2. 新增 `Resources::VolumeField`
3. 新增 `VolumeFieldLoader`
4. 新增 `NanoVDBVolumeImporter`
5. `AssetDatabase` 识别 `.nvdb`
6. 生成 `main.xcvol`
7. 接入 reimport / dependency / cache / runtime load
### 测试
1. `tests/Resources/Volume`
2. `tests/Core/Asset`
3. `.nvdb` 改动后的 reimport 测试
### 验收标准
1. `.nvdb` 可以像 mesh / material / shader 一样进入正式资产体系
---
## 阶段 5接入组件、提取与 frame data
### 目标
让场景系统知道“什么是可渲染的体积对象”。
### 任务
1. 新增 `VolumeRendererComponent`
2. 组件持有:
- `VolumeField`
- `Material`
- enable/disable
- 局部参数 override
- bounds / transform
3. `RenderSceneExtractor` 增加体积对象提取
4. 新增 `VisibleVolumeItem`
5. `RenderSceneData.visibleVolumes` 接入
### 测试
1. 组件序列化 / 反序列化
2. `VisibleVolumeItem` 提取测试
3. frustum / bounds culling 与稳定顺序测试
### 验收标准
1. renderer 已经能从正式场景数据中拿到体积对象输入
---
## 阶段 6BuiltinVolumetricPass 首次正式点亮
### 目标
在当前 renderer 正式链路中点亮第一版 `NanoVDB` 稀疏体积渲染。
### 任务
1. 新增 `BuiltinVolumetricPass`
2. pass 输入包括:
- camera
- depth
- scene color
- light / shadow
- volume buffer
- material parameters
3. D3D12 首次点亮
4. 允许保留一条集成测试级别的 fullscreen debug path
5. 正式运行时路径收口到 proxy box / unit cube volume renderer
### 强约束
1. fullscreen path 只能作为 bring-up / integration debug 路径
2. 正式场景运行时不能停留在 fullscreen 方案
3. 不得保留 `MVS` 风格直接文件加载或私有绑定路径
### 测试
1. `tests/Rendering/integration/volume_nanovdb_minimal`
2. GT 对比
3. 中间调试图输出:
- `volume_mask_debug`
- `depth_debug`
- 必要时的 shadow debug
### 验收标准
1. 正式 renderer 已经能稳定渲染一个来自 `.nvdb` 的体积对象
---
## 阶段 7深度、阴影、合成规则收口
### 目标
把“能出图”收口成“行为正确”。
### 任务
1. 深度遮挡规则确定
2. scene color 合成规则确定
3. 主方向光与阴影采样接入
4. transform 与 bbox 变换一致
5. 步长、阴影采样步数、质量参数稳定化
### 测试
1. `volume_nanovdb_occlusion`
2. `volume_nanovdb_transform`
3. 光照方向变化的回归测试
### 验收标准
1. 画面行为能解释、能回归、能维护,不再只是 demo level
---
## 阶段 8多后端 rollout
### 目标
把已在 D3D12 验证的正式能力推进到 Vulkan 与 OpenGL。
### 任务
1. Vulkan storage buffer 路线点亮
2. OpenGL SSBO 路线点亮
3. 明确 capability 检测与 fallback / disable 行为
4. 补齐 backend-specific shader compile / runtime binding 回归
### 测试
1. 各后端统一 GT 对比
2. backend-specific shader compile 测试
3. buffer binding 回归测试
### 验收标准
1. 不是只有 D3D12 demo 可用,而是正式进入多后端 renderer 能力集合
---
## 阶段 9Editor 最小工作流接入
### 目标
让这项能力进入项目工作流,而不是只能靠测试程序看图。
### 任务
1. `ProjectPanel` 识别 `.nvdb`
2. `Inspector` 显示 `VolumeField` metadata
3. `VolumeRendererComponent` inspector 编辑
4. scene view / game view 中体积对象可见
5. 资源缺失、编译失败、后端不支持时给出明确状态
### 验收标准
1. 用户可以从资产、组件、场景三个层面完整使用这项能力
---
## 11. 测试体系规划
### 11.1 单元测试
需要新增或扩展:
1. `tests/RHI/unit`
- buffer SRV/UAV 创建
- descriptor set buffer binding
2. `tests/Resources/Shader`
- Unity/HLSL 风格 buffer 声明解析
- 非法 `Properties` buffer 声明拒绝
3. `tests/Resources/Material`
- 材质参数 schema 与 runtime buffer 绑定边界
4. `tests/Resources/Volume`
- `.nvdb -> .xcvol`
- metadata roundtrip
- reimport
5. `tests/Rendering/unit`
- `VisibleVolumeItem` 提取
- `BuiltinVolumetricPass` 输入校验
### 11.2 集成测试
建议新增:
1. `tests/Rendering/integration/volume_nanovdb_minimal`
2. `tests/Rendering/integration/volume_nanovdb_occlusion`
3. `tests/Rendering/integration/volume_nanovdb_transform`
GT 规则:
1. 使用单一 `GT.ppm`
2. 各后端输出都对同一张 `GT.ppm` 做对比
3. 必要时保留中间调试图,但不把调试图当正式输出
### 11.3 性能与稳定性验收
至少记录:
1. 单体积对象 frame time
2. 多体积对象 frame time
3. `.nvdb` 首次导入时长
4. artifact 命中后的加载时长
---
## 12. 关键风险
### 12.1 最大风险不是算法,而是语义边界被做脏
如果一开始把 `StructuredBuffer / RawBuffer` 直接暴露成高层 authoring 概念,后面整个 shader/material/C# 体系都会被污染。
### 12.2 OpenGL 路线风险最高
原因:
1. `StructuredBuffer` 到 GLSL/SSBO 的映射最容易出边角问题
2. `PNanoVDB` 这种大 include 在 GLSL 转译路径上更容易暴露兼容性问题
### 12.3 fullscreen debug path 容易反客为主
它只能用于 bring-up 和集成测试,不能变成正式场景架构。
### 12.4 如果当前 shader 体系还没正式支持 Unity 风格 buffer 语义,这会成为主阻塞项
这不是额外问题,而是本主线的前置依赖。
---
## 13. 本阶段收口标准
当下面条件同时满足时,可以认为“稀疏体积渲染第一阶段正式完成”:
1. `.nvdb` 已成为正式项目资源,可导入、可 reimport、可 artifact 化。
2. 引擎 shader / runtime binding / RHI 已正式支持 Unity 风格 buffer 资源链路。
3. 高层 authoring 没有引入任何体积专用自定义 buffer 语法。
4. `VolumeRendererComponent` 能把 `VolumeField` 放进场景。
5. `BuiltinVolumetricPass` 已作为正式 scene pass 存在。
6. D3D12 至少已经在正式链路中点亮。
7. 集成测试能稳定出图并做 GT 对比。
8. future C# 的 `SetBuffer` 风格接口已经有明确落点,不会和当前底层设计冲突。
9. 代码中不再依赖 `MVS/VolumeRenderer` 那套 D3D12 私有 demo 实现。
---
## 14. 一句话结论
这条主线的正确做法不是“先做出体积渲染再说”,而是“从第一天开始就按 Unity 风格的 shader/C# 语义设计 buffer 路径,把 `NanoVDB` 作为正式稀疏体积能力接入 renderer”这样后面无论是 C# 层还是 SRP都不会被今天的底层实现反噬。

View File

@@ -1,127 +0,0 @@
# Renderer 下一阶段: Camera PostProcess 描述层正式化计划
日期: `2026-04-06`
## 1. 阶段目标
在已经完成 runtime `SceneColor -> PostProcess -> FinalOutput` 主链收口的基础上,
把相机上的后处理配置从临时的 `ColorScale` 专用字段,提升为正式的描述层。
这一阶段只解决一件事:
- `CameraComponent` 持有正式的 post-process pass 描述
- `SceneRenderer` 通过工厂把描述翻译成 runtime pass sequence
- 旧的 `ColorScale` 接口继续兼容,旧场景序列化继续可读
## 2. 已完成内容
### 2.1 相机后处理描述层
已新增:
- `CameraPostProcessPassType`
- `CameraPostProcessPassDesc`
- `CameraPostProcessStack`
当前首个正式 builtin effect 仍然是:
- `ColorScale`
但它已经不再通过 `CameraComponent -> vector<Vector4> -> SceneRenderer if/for` 这种硬编码链路传递。
### 2.2 CameraComponent 正式化
已完成:
- `CameraComponent` 内部存储迁移到 `CameraPostProcessStack`
- 新增通用接口:
- `GetPostProcessPasses`
- `SetPostProcessPasses`
- `AddPostProcessPass`
- `ClearPostProcessPasses`
- 旧接口继续保留:
- `SetColorScalePostProcessEnabled`
- `SetColorScalePostProcessScale`
- `SetColorScalePostProcessPasses`
- `GetColorScalePostProcessPasses`
兼容规则:
- 旧接口继续映射到 `ColorScale` 类型的描述
- 新格式序列化写出正式 `postProcessPassN*` 字段
- 反序列化同时兼容旧的 `colorScalePostProcess*` 字段
### 2.3 SceneRenderer 工厂翻译层
已完成:
-`SceneRenderer` 中移除 `ColorScale` 专用硬编码构造
- 新增 `BuildCameraPostProcessPassSequence(...)`
- `SceneRenderer` 现在只关心:
- 读取相机的 `CameraPostProcessStack`
- 为 post-process 分配 source surface
- 调用工厂生成 `RenderPassSequence`
这意味着后续增加新的 builtin camera post-process 时,
修改点已经收缩到描述层和工厂,而不是继续把分支堆进 `SceneRenderer`
## 3. 验证结果
### 3.1 构建
已通过:
- `cmake --build --preset debug --target components_tests rendering_unit_tests rendering_integration_camera_post_process_scene rendering_integration_post_process_scene -- /m:1`
### 3.2 单元测试
已通过:
- `components_tests --gtest_filter=CameraComponent_Test.*`
- `rendering_unit_tests --gtest_filter=SceneRenderer_Test.*:CameraRenderer_Test.*`
覆盖点包括:
- 新描述层 round-trip
-`ColorScale` 字段反序列化兼容
- `SceneRenderer` 从 camera post-process stack 构建正式 request
### 3.3 集成测试
已通过:
- `rendering_integration_camera_post_process_scene`
- D3D12
- OpenGL
- Vulkan
- `rendering_integration_post_process_scene`
- D3D12
- OpenGL
- Vulkan
说明:
- `camera_post_process_scene` 验证 formal scene path
- `post_process_scene` 保留为 manual / low-level multi-pass 覆盖
## 4. 当前结论
这一阶段已经完成。
当前 renderer 在 camera post-process 这一层,已经从“单个效果的临时接线”升级为“正式描述 + 工厂翻译”的结构。
但现阶段仍然有两个边界:
- 描述层虽然正式化了,但当前 builtin effect 仍只有 `ColorScale`
- editor / asset / material 侧还没有把 camera post-process 做成完整的资产化与配置面板体系
## 5. 下一步建议
下一步不要回头继续往 `SceneRenderer` 里塞特判,而应沿着下面的方向推进:
1. 继续扩展 `CameraPostProcessPassDesc`
- 例如 tone mapping / exposure / gamma / final color transform
2. 让新的 builtin camera effect 继续只经过“描述层 + 工厂”
3. 等 shader/material 主线更稳定后,再决定 camera post-process 的资产化形式和 editor 配置入口
本阶段的核心收口标准已经满足,可以提交归档。

View File

@@ -1,841 +0,0 @@
# Renderer 下一阶段Unity 风格 Shader 体系正式化计划
日期:`2026-04-06`
## 1. 阶段结论
当前 renderer 主线已经可以阶段性收口,但 `Shader / Material / Pass` 体系还没有真正统一。
现在仓库里的 shader 体系处于一个过渡态:
- 逻辑上已经有 `Shader -> Pass -> Variant` 模型
- authoring 外观上已经接近 Unity ShaderLab
- 运行时已经能按 pass 和 backend variant 选择 shader
- 三后端已经能稳定跑通 builtin shader
但它仍然不是 Unity 风格的正式体系,因为当前 authoring 仍然暴露了太多 backend 与 binding 细节:
- `.shader` 文件里仍然显式写 `#pragma backend D3D12/OpenGL/Vulkan`
- `.shader` 文件里仍然显式写 `Resources { name(type, set, binding) }`
- material 仍然可以显式指定 `shaderPass`
- shader 关键字、变体、include、pass state、SubShader 选择还没有真正成体系
所以这一阶段的主线不是继续做新渲染效果,而是把 shader 体系升级成:
- **authoring 层完全按 Unity 风格书写**
- **三后端编译路径统一收敛到 importer / compiler 层**
- **runtime 只消费正式 shader artifact / material contract**
这一步不是 SRP 本身,但它是后续 SRP 能否成立的硬地基。
---
## 2. 阶段目标
本阶段要达成的核心目标只有一件事:
**把当前“伪 ShaderLab + 后端直连”的过渡体系升级为“Unity 风格 authoring + 引擎内部统一 shader IR + 三后端编译产物”的正式体系。**
完成后应达到:
1. shader authoring 采用 Unity 风格 `.shader` 语法,不再要求作者显式写 backend variant 分发表。
2. 新语法不再要求作者手写 `set/binding` 级资源绑定表。
3. HLSL 成为 raster shader 的单一 authoring 语言源。
4. D3D12 / Vulkan / OpenGL 的差异退到 importer / compiler / artifact 层。
5. material 只管 property / keyword / texture / render state不再负责点名 pass。
6. renderer 按 `LightMode` / pass contract 选 pass而不是靠 material 的临时字符串兜底。
7. builtin shader 全部迁移到新体系并保持三后端回归稳定。
8. 保留 legacy shader 兼容层,避免一次性炸掉现有工程内容。
---
## 3. 非目标
本阶段明确不做:
- SRP 本体
- render graph
- deferred / clustered
- Shader Graph
- Surface Shader
- 完整 Unity ShaderLab 100% 全语法一次性覆盖
- volume 系统
- 更多新渲染效果
本阶段也不追求一口气把 Unity 的所有 authoring 特性补完,例如:
- `Fallback`
- `UsePass`
- `CustomEditor`
- `GrabPass`
- tessellation / geometry / ray tracing authoring
这些可以后续补,但不应阻塞当前阶段把“统一 shader 体系”先做正确。
---
## 4. 当前真实状态与根本问题
### 4.1 当前已经具备的能力
当前工程已经具备:
- `.shader` 文件解析能力
- `Shader` 的 pass / property / resource / variant 数据模型
- builtin shader 资产化
- material property / texture / render state / tag 载体
- renderer 中按 builtin pass metadata 选择可用 shader pass
这意味着当前不是从零开始设计。
### 4.2 当前最根本的问题
当前最大的问题不是“没有 shader 体系”而是“authoring 层和 runtime 层的边界还没收干净”。
具体表现为:
1. **authoring 层暴露后端差异**
- `.shader` 文件直接写 `#pragma backend D3D12/OpenGL/Vulkan`
- shader 作者必须知道后端与源码文件映射
2. **authoring 层暴露 RHI binding 细节**
- `.shader` 文件显式写 `Resources { ConstantBuffer / Texture2D / Sampler, set, binding }`
- 这更像 Vulkan/D3D12 binding 清单,不是 Unity 风格 shader authoring
3. **material 还带着临时 pass 选择职责**
- `Material::SetShaderPass()` 仍然存在
- renderer 仍然优先吃 material 显式指定的 pass
- 这会阻碍未来 RendererFeature / SRP 规范化
4. **关键字与变体体系缺失**
- 没有正式的 `multi_compile / shader_feature`
- 没有变体剥离与编译缓存策略
5. **include 与共享库体系缺失**
- 没有正式的 shader include 搜索路径、预处理、公共库组织
6. **pass state 仍然不在 shader authoring 的统一语义内**
- 诸如 `Cull / ZWrite / ZTest / Blend / ColorMask / Stencil` 还没有完整进入 shader authoring contract
7. **三后端仍然是物理三套源码直连**
- 当前虽然“逻辑上一个 shader asset”
- 但作者本质上还在维护三套 shader stage 文件
---
## 5. 核心设计结论
### 5.1 目标不是“看起来像 Unity”而是“真正采用 Unity 风格 authoring 模型”
最终目标应当是:
- 一个 `.shader` 文件描述一个逻辑 shader
- shader 内部有 `Properties / SubShader / Pass / Tags / State / Program`
- renderer 消费的是 import 后的统一 IR / artifact
- backend 差异不暴露给 shader 作者
### 5.2 HLSL 作为单一 authoring 语言源
本阶段必须明确:
- **新体系下 raster shader 统一使用 HLSL authoring**
- D3D12 直接编 HLSL
- Vulkan 由 HLSL 编到 SPIR-V
- OpenGL 由 HLSL 编到 SPIR-V再转 GLSL 430
原因:
- 如果 authoring 仍然保留 GLSL/HLSL 三套并行,永远不可能真正统一写法
- 只有 single-source authoring才能接近 Unity 的真实体验
### 5.3 backend 差异必须退到 importer / compiler 层
新 authoring 文件中不应再出现:
- `#pragma backend ...`
- backend 专属 stage 文件路径表
这些内容应由 importer 根据 target backend 生成产物。
### 5.4 resource binding 不再由 shader 作者手写 `Resources(set,binding)`
Unity 风格 shader authoring 不要求作者手写 descriptor set / binding。
因此新体系下应改为:
- material 暴露属性来自 `Properties`
- engine 内建 constant buffer / texture / sampler 来自约定与 reflection
- importer 通过 HLSL reflection + 约定库推导 runtime resource layout
也就是说:
- authoring 层写“语义”
- importer 层生成“绑定布局”
- runtime 层消费“绑定布局”
### 5.5 pass 选择必须回归 renderer而不是 material
新体系中:
- material 只绑定 shader 与 property / keyword / texture
- renderer 按 `LightMode` 选 pass
- `Material::shaderPass` 进入弃用与最终移除路径
这与 Unity 的 `ShaderTagId / LightMode` 思路对齐,也更利于未来 SRP。
### 5.6 必须保留 legacy 兼容层
当前仓库已经有一批 builtin shader 和测试资产。
因此不能激进地“一刀切重做”,而应:
- legacy `.shader` 继续可加载
- 新 Unity 风格 `.shader` 进入新 importer 路径
- builtin shader 分批迁移
- runtime 统一落在同一套 `Shader` / artifact / variant 模型上
---
## 6. 目标架构
建议把 shader 体系正式分成 5 层。
### 6.1 Authoring 层
职责:
- 让开发者以 Unity 风格书写 shader
建议语法子集:
- `Shader`
- `Properties`
- `SubShader`
- `Pass`
- `Tags`
- `LOD`
- `HLSLINCLUDE`
- `HLSLPROGRAM`
- `ENDHLSL`
- `#pragma vertex`
- `#pragma fragment`
- `#pragma target`
- `#pragma multi_compile`
- `#pragma shader_feature`
- `#pragma shader_feature_local`
- `Cull`
- `ZWrite`
- `ZTest`
- `Blend`
- `ColorMask`
- `Stencil`
- `Offset`
### 6.2 Importer / Parser 层
职责:
- 解析 Unity 风格 `.shader`
- 生成统一的内部 `ShaderIR`
建议引入:
- `ShaderAuthoringParser`
- `ShaderIR`
- `ShaderSubShaderIR`
- `ShaderPassIR`
- `ShaderKeywordDecl`
- `ShaderPassStateDesc`
- `ShaderProgramIR`
### 6.3 Compiler / Reflection 层
职责:
- 编译 authoring 中的 HLSL
- 为不同 backend 生成最终编译产物
- 生成 resource layout / constant layout / keyword variant metadata
建议技术路径:
- D3D12`DXC -> DXIL/DXBC`
- Vulkan`DXC -> SPIR-V`
- OpenGL`DXC -> SPIR-V -> SPIRV-Cross -> GLSL 430`
输出:
- 每个 pass / stage / keyword-set / backend 的编译产物
- 反射出的 constant buffer / texture / sampler 布局
### 6.4 Artifact 层
职责:
- 保存运行时真正消费的 shader 产物
建议引入新版 artifact
- `xcshader2` 或继续升级现有 `xcshader`
artifact 内容至少包含:
- shader 名称 / guid
- properties
- subshader / pass tags
- pass state
- keyword declarations
- keyword variant table
- backend binaries / backend source payload
- reflected resource layout
- include 依赖与 hash
### 6.5 Runtime 层
职责:
- renderer 根据 pass contract、keywords、backend 选择最终 variant
- material 根据 property/texture 生成常量与资源绑定
- pipeline cache 根据 shader variant + render state 建 key
---
## 7. Unity 风格 authoring 范围定义
### 7.1 第一阶段必须支持的语法
第一阶段建议正式支持:
```shaderlab
Shader "XCEngine/Example/Lit"
{
Properties
{
_BaseColor ("Base Color", Color) = (1,1,1,1)
_BaseMap ("Base Map", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0,1)) = 0.5
}
HLSLINCLUDE
#include "ShaderLibrary/Core.hlsl"
ENDHLSL
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
LOD 200
Pass
{
Name "ForwardLit"
Tags { "LightMode"="ForwardLit" }
Cull Back
ZWrite On
ZTest LEqual
Blend One Zero
HLSLPROGRAM
#pragma target 4.5
#pragma vertex Vert
#pragma fragment Frag
#pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS
#pragma shader_feature_local _ XC_ALPHA_TEST
ENDHLSL
}
}
}
```
### 7.2 第一阶段暂不支持的语法
第一阶段可暂缓:
- `Fallback`
- `UsePass`
- `GrabPass`
- `CustomEditor`
- `Category`
- `Dependency`
- Surface Shader
- CG fixed-function 时代遗留语义
这些需要列入兼容性说明,但不应阻塞首版落地。
---
## 8. 统一的 shader library 与 include 体系
这一层是“写法统一”能否成立的关键。
### 8.1 必须引入正式的 include 库
建议新增:
- `engine/assets/shaderlib/ShaderLibrary/Core.hlsl`
- `engine/assets/shaderlib/ShaderLibrary/Common.hlsl`
- `engine/assets/shaderlib/ShaderLibrary/SpaceTransforms.hlsl`
- `engine/assets/shaderlib/ShaderLibrary/Lighting.hlsl`
- `engine/assets/shaderlib/ShaderLibrary/MaterialInput.hlsl`
- `engine/assets/shaderlib/ShaderLibrary/Shadow.hlsl`
目标:
- builtin shader 共用统一宏与公共函数
- authoring 层不再重复声明一堆 per-object / lighting 结构
### 8.2 统一的内建常量组
建议统一为接近 Unity 的内建分组:
- `UnityPerFrame``XCPerFrame`
- `UnityPerCamera``XCPerCamera`
- `UnityPerDraw``XCPerDraw`
- `UnityPerMaterial``XCPerMaterial`
建议引擎内部最终保留 `XC*` 前缀实现名,但 authoring 宏层提供 Unity 风格别名。
### 8.3 内建纹理/采样器由 include 库与 reflection 管理
例如:
- 主纹理
- 阴影图
- 环境图
- sampler state
这些都不再由 `.shader` 手填 `Resources(set,binding)`
---
## 9. Material 体系同步改造
shader 统一如果不带 material 一起改,最后会停在半路。
### 9.1 material 的职责边界
新体系里 material 负责:
- 选择 shader
- 保存 property override
- 保存 texture override
- 保存 keyword 开关
- 保存材质级 render queue / tag override如保留
新体系里 material 不再负责:
- 指定 `shaderPass`
- 硬编码 backend 资源名
- 猜测 shader 内的 descriptor set / binding
### 9.2 material constant buffer 正式化
必须建立:
- `Properties` -> `UnityPerMaterial/XCPerMaterial` 布局
- importer 生成 property layout
- material 按 layout 打包 GPU 常量
- layout/hash 进入 pipeline/material cache key
### 9.3 keyword 体系正式化
建议引入:
- `global keywords`
- `local keywords`
- material keyword set
- variant lookup key
material 应持有:
- `ShaderKeywordSet`
renderer 运行时根据:
- shader
- pass
- keyword set
- backend
选择最终 shader variant。
---
## 10. Renderer 运行时契约调整
### 10.1 pass 选择规则统一为 `LightMode`
renderer 选 pass 时只看:
- 当前 pipeline 阶段需要的 `LightMode`
- shader/subshader/pass 是否匹配
- backend 是否有有效 variant
例如:
- 主几何:`ForwardLit` / `Unlit`
- 阴影:`ShadowCaster`
- 深度:`DepthOnly`
- ObjectId`ObjectId`
- FinalOutput`FinalColor`
### 10.2 material 显式 `shaderPass` 进入弃用路径
建议执行顺序:
1. 第一阶段保留字段,但标记为 legacy
2. runtime 优先按 pass contract / LightMode 选 pass
3. 只有 legacy 资产才允许 fallback 到 `shaderPass`
4. migration 完成后移除 `shaderPass` 主路径职责
### 10.3 builtin pass metadata 继续保留,但收进 importer/runtime
当前基于 semantic 的 builtin pass binding 解析仍然有价值,但应改成:
- authoring/IR 层表达语义
- compiler/reflection 层生成 binding plan
- runtime 只消费 binding plan
而不是继续靠散落的名字匹配和 fallback。
---
## 11. 三后端统一策略
### 11.1 D3D12
目标:
- 直接消费 HLSL 编译产物
- 反射得到 root signature / resource layout 所需元数据
第一阶段可继续沿用 `ps_5_0 / vs_5_0`,但建议同时规划升级:
- 后续逐步转 `SM 6.x`
### 11.2 Vulkan
目标:
- 统一吃由 HLSL 编到 SPIR-V 的产物
- 摆脱独立 `.vk.glsl` 长期维护
### 11.3 OpenGL
目标:
- 不再长期维护独立 `.glsl` authoring 文件
- importer 自动生成 OpenGL 目标 GLSL 430
这一步会是整个阶段最大的工程风险之一,但它是“写法统一”绕不开的核心点。
### 11.4 迁移期间的策略
在新体系落稳前,允许:
- legacy backend-specific variant 继续存在
- 新 Unity 风格 shader 走统一 HLSL single-source 路径
两套 importer 并行一段时间,最终再逐步淘汰 legacy path。
---
## 12. 分阶段实施计划
### Phase A冻结目标与建立兼容边界
目标:
- 明确“什么叫 Unity 风格 shader”
- 明确 legacy 与新 authoring 的兼容边界
工作项:
1. 写清 Unity 风格支持子集。
2. 明确旧 `.shader` 的 legacy 模式规则。
3. 明确新 authoring 中禁止出现:
- `#pragma backend`
- `Resources(set,binding)`
4. 明确 material 中 `shaderPass` 的弃用策略。
完成标准:
- 文档、命名、兼容边界全部写死
### Phase B建立新的 Shader Authoring Parser 与 IR
目标:
-`.shader` authoring 能导入到统一 `ShaderIR`
工作项:
1. 新增 parser支持
- `Shader / Properties / SubShader / Pass / Tags`
- `HLSLINCLUDE / HLSLPROGRAM`
- `#pragma vertex / fragment / target / multi_compile / shader_feature`
- pass state DSL
2. 生成 `ShaderIR`
3. 支持 include 依赖收集
4. 保留 legacy importer
完成标准:
- authoring parser 单测齐全
- 可以把一个 Unity 风格 shader 解析成稳定 IR
### Phase C建立单一 HLSL 编译链
目标:
- 打通 `HLSL -> D3D12/Vulkan/OpenGL` 编译管线
工作项:
1. 接入 DXC 编译 HLSL。
2. Vulkan 产出 SPIR-V。
3. OpenGL 产出 GLSL 430。
4. 建立 reflection 数据抽取:
- cbuffer
- texture
- sampler
- entry point
- keywords
5. 缓存编译产物与依赖 hash。
完成标准:
- 一份 HLSL authoring 能生成三后端产物
- OpenGL 不再依赖手写 `.glsl` 作为新体系长期主路径
### Phase DMaterial 与 property/keyword/runtime binding 正式化
目标:
- material 能正式驱动新 shader artifact
工作项:
1. 引入正式 property layout。
2. 引入 material keyword set。
3. 生成 `PerMaterial` 常量缓冲布局。
4. texture/sampler 绑定从 reflection/约定生成。
5. 让 material 运行时不再关心 `set/binding`
完成标准:
- material property / texture / keyword 真正接入 GPU 绑定链
### Phase Erenderer pass 选择与 pipeline cache 收口
目标:
- renderer 完全按 pass contract 驱动 shader
工作项:
1.`LightMode` 选 pass。
2. `shaderPass` 降级为 legacy fallback。
3. pipeline cache key 引入:
- shader artifact id
- pass id
- keyword variant id
- render state
4. builtin pass 的 runtime contract 全部切到新 artifact。
完成标准:
- renderer 主路径不再依赖 material 显式 pass 指定
### Phase F分批迁移 builtin shader
建议迁移顺序:
1. `unlit`
2. `forward-lit`
3. `depth-only`
4. `shadow-caster`
5. `object-id`
6. `skybox`
7. `color-scale-post-process`
8. `final-color`
完成标准:
- builtin shader 全部有新 authoring 版本
- 旧版 backend 分发文件不再是长期主定义来源
### Phase G文档、测试、旧路径收口
目标:
- 让新旧体系的边界最终收口
工作项:
1. 更新 `tests/TEST_SPEC.md` 中 shader/material 测试矩阵。
2. 增加 authoring parser / compiler / runtime 回归测试。
3. 更新开发文档。
4. 标记 legacy 路径弃用阶段。
完成标准:
- 新体系文档、测试、builtin 迁移都完成
---
## 13. 测试策略
### 13.1 Parser / Importer 单测
必须覆盖:
- Properties 解析
- SubShader / Pass / Tags 解析
- HLSLINCLUDE / HLSLPROGRAM 解析
- pragma 解析
- pass state 解析
- include 依赖收集
- legacy / 新 authoring 双路径兼容
### 13.2 Compiler 单测
必须覆盖:
- 单一 HLSL 源能生成三后端产物
- reflection 结果稳定
- keyword variant 正确展开
- 编译错误日志可读、可定位到 authoring 源文件
### 13.3 Material 单测
必须覆盖:
- property 默认值
- property override
- texture binding
- keyword set
- 常量缓冲布局打包
### 13.4 Rendering 单测
必须覆盖:
- renderer 按 `LightMode` 选 pass
- legacy `shaderPass` fallback 行为
- keyword variant 参与 pipeline cache key
- final color / post-process / shadow / object-id 不回退
### 13.5 集成测试
至少回归:
- `material_state_scene`
- `transparent_material_scene`
- `camera_stack_scene`
- `directional_shadow_scene`
- `multi_light_scene`
- `skybox_scene`
- `post_process_scene`
- `final_color_scene`
- `object_id_scene`
要求:
- 三后端全部跑通
- GT 不回退
---
## 14. 风险与控制策略
### 风险 1OpenGL 是统一写法最难的一环
原因:
- OpenGL 当前直接吃 GLSL
- 统一 authoring 要求它改为编译链生成目标 GLSL
控制策略:
- 先把 importer/IR 做好
- OpenGL 先走“生成 GLSL 文本产物”路径
- legacy OpenGL GLSL 文件在迁移期保留 fallback
### 风险 2一次性追 full Unity 语法会把阶段拖爆
控制策略:
- 明确 first-class 子集
- 先做 SRP 真正依赖的 authoring 基础
- 非关键语法延后
### 风险 3material / pass 迁移会破坏当前 builtin renderer
控制策略:
- legacy runtime path 保留一段时间
- builtin shader 分批迁移
- 每迁移一个 shader 就跑对应 integration
### 风险 4编译错误如果不可读会极大拖慢落地
控制策略:
- 必须做 authoring 源到 backend 编译日志的映射
- 错误日志要带 shader 名、pass 名、stage、backend、源文件行号
---
## 15. 收口判定
满足下面条件时,本阶段可视为完成:
1.`.shader` authoring 采用 Unity 风格子集。
2. 新体系 shader authoring 中不再出现 `#pragma backend`
3. 新体系 shader authoring 中不再出现 `Resources(set,binding)`
4. HLSL single-source 能生成 D3D12 / Vulkan / OpenGL 三后端产物。
5. material 已正式接入 property / keyword / texture binding runtime。
6. renderer 按 `LightMode` 正式选择 pass。
7. `shaderPass` 只剩 legacy fallback不再是主路径。
8. builtin shader 已完成新体系迁移。
9. 三后端关键集成测试全部通过。
---
## 16. 与后续 SRP 的承接关系
这一阶段完成后,才能真正自然地承接:
- `RenderPipelineAsset`
- `RenderPipeline`
- `RendererFeature`
- `ScriptableRenderPass`
- C# 层对 shader/material/keyword 的控制
承接关系应当是:
```text
Unity-style Shader Authoring
-> Shader Importer / IR / Artifact
-> Native Material & Pass Runtime
-> Native Renderer Pass Contract
-> C# SRP / RendererFeature
```
也就是说:
- 这一阶段不是 SRP
- 但这是 SRP 成立前必须先做完的最后一层底座
---
## 17. 一句话总结
当前 shader 体系不是没有,而是还停在“过渡态”。
下一阶段的正确方向不是继续堆更多 shader 功能,而是:
- **把 `.shader` 的 authoring 真正统一成 Unity 风格**
- **把 backend 差异与 binding 细节收回 importer / compiler 层**
- **把 material / pass / variant/runtime contract 一次性做正式**
只有这样,后面的 SRP 才不会建立在一层伪统一的 shader 体系上。

View File

@@ -1,556 +0,0 @@
# Renderer模块设计与实现
## 1. 背景
XCEngine 当前已经完成了较为可用的 RHI 抽象层,且已经具备:
- `Scene + GameObject + Component` 基础场景模型
- `CameraComponent` / `LightComponent` 等基础组件
- `Mesh` / `Material` / `Texture` / `Shader` 等资源类型
- D3D12 / OpenGL 双后端 RHI 抽象与测试体系
下一阶段不应该继续封闭式打磨 RHI而应该在 RHI 之上正式建立 **Renderer 模块**
这里的 Renderer 模块不是“最终形态的 SRP”而是
- 先建立一层 **原生渲染运行时**
- 先让场景对象能够以正式渲染链路被绘制
- 同时在设计上预留未来 **C# Scriptable Render PipelineSRP** 的接入点
也就是说,当前阶段的正确目标不是直接实现 Unity 的 URP/HDRP而是先建立一套 **与 Unity 渲染架构方向一致的原生基础层**,后续让 C# SRP 驱动它。
---
## 2. 设计目标
Renderer 模块的目标分为两层:
### 2.1 当前阶段目标
先完成一套最小但完整的原生渲染链路:
-`Scene` 中提取可渲染对象
- 通过 `Camera` 构建视图与投影数据
- 通过 `Material` / `Mesh` / `Texture` 构建 GPU 绘制数据
- 在 RHI 之上完成正式的 frame 渲染
- 支持 swapchain 输出与离屏输出
- 建立独立于 editor 的渲染宿主模型
### 2.2 面向未来 C# SRP 的目标
当前阶段的实现必须为后续演进预留稳定边界:
- 未来允许用 C# 定义 `RenderPipelineAsset` / `RenderPipeline`
- 未来允许用 C# 组织 render pass
- 未来允许 editor `SceneView` / `GameView` 通过同一套 renderer 输出
- 未来允许 C# 脚本控制 camera 渲染、pass 排序、目标输出与 command buffer
因此,当前阶段的原生 Renderer 不能做成一个写死的“大一统内建渲染函数”,而应该一开始就具备“可被上层 pipeline 驱动”的结构。
---
## 3. 与 Unity 渲染架构的对应关系
当前建议的路线与 Unity 的总体方向是对齐的,但要注意分层位置。
### 3.1 推荐分层
```text
Scene / Components / Resources
Renderer 模块(原生渲染运行时)
未来 C# SRP 层(脚本化渲染管线)
RHI 抽象层
D3D12 / OpenGL / Vulkan 后端
```
### 3.2 各层职责
#### Scene / Components / Resources
负责描述“要渲染什么”,例如:
- 场景对象
- 相机
- 灯光
- 网格
- 材质
- 贴图
这一层不应该直接持有后端 API 对象。
#### Renderer 模块(本阶段核心)
负责描述“如何从场景变成 draw call”例如
- 渲染对象抽取
- 可见性裁剪
- GPU 资源缓存
- camera frame 数据组织
- render target / depth target 管理
- render pass 调度
- 内建前向管线
这一层是未来 C# SRP 的原生支撑层。
#### 未来 C# SRP 层
负责描述“以脚本方式控制渲染流程”,例如:
- `RenderPipelineAsset`
- `RenderPipeline`
- `ScriptableRenderContext`
- `CommandBuffer`
- `RenderPassEvent`
- pass 注入与重排
这一层不应该直接绕过 Renderer 模块去操作后端 API。
#### RHI 抽象层
负责统一 GPU 接口与资源对象,是渲染系统的执行后端,而不是场景渲染逻辑本身。
### 3.3 与 Unity 的概念映射
| Unity 概念 | XCEngine 当前/规划对应 |
|---|---|
| `Camera` | `CameraComponent` |
| `Light` | `LightComponent` |
| `MeshFilter` | 计划新增 `MeshFilterComponent` |
| `MeshRenderer` | 计划新增 `MeshRendererComponent` |
| `RenderPipelineAsset` | 未来 Renderer 模块上的 pipeline asset 抽象 |
| `RenderPipeline` | 未来 Renderer 模块上的 pipeline 实例抽象 |
| `ScriptableRenderContext` | 未来 Renderer 模块对脚本暴露的原生 render context |
| `CommandBuffer` | 未来 Renderer 模块对脚本暴露的命令缓冲抽象 |
| `GraphicsDevice` / native render backend | 当前 RHI + 后端实现 |
结论是:
- **方向上符合 Unity 渲染架构**
- **当前阶段实现的应是 Unity 渲染体系中的原生底座**
- **而不是直接跳到最终的脚本化 SRP**
---
## 4. 核心设计原则
### 4.1 先建立原生渲染运行时,再开放脚本化管线
如果现在直接做 C# SRP而原生 Renderer 边界还不存在,后续会出现:
- C# API 直接耦合 RHI
- editor viewport 与 runtime camera 逻辑混杂
- 资源对象与 GPU 对象生命周期混乱
因此必须先收敛原生 Renderer 模块。
### 4.2 Scene 层只描述逻辑对象,不持有后端对象
`GameObject``Component``Mesh``Material` 等对象只能描述逻辑与资源,不应该直接持有 D3D12/OpenGL 私有对象。
GPU 对象应由 Renderer 内部缓存层负责创建和复用。
### 4.3 editor 只是渲染宿主,不是渲染逻辑本体
`GameView` / `SceneView` 最终只是 Renderer 的输出宿主。
Renderer 本身必须先支持:
- 输出到 swapchain
- 输出到离屏纹理
然后 editor 再把离屏纹理接进 ImGui 面板。
### 4.4 为未来 SRP 预留 pipeline 抽象
即使第一阶段先做内建前向渲染,也不应该把逻辑写死成单一 `SceneRenderer::DrawEverything()`
应该从一开始就保留:
- `RenderPipeline`
- `RenderPipelineAsset`
- `RenderContext`
- camera 列表驱动
- pass 分阶段执行
这样未来 C# 只是在这个原生结构上做绑定,而不是重做一遍架构。
### 4.5 测试体系与渲染层分离
`tests/RHI/` 继续只验证 RHI。
Renderer 模块应建立自己的测试体系:
- `tests/Rendering/unit/`
- `tests/Rendering/integration/`
这样职责边界才清晰。
---
## 5. 模块划分建议
建议新增 `Rendering` 模块,作为场景与 RHI 之间的正式中间层。
### 5.1 推荐目录结构
```text
engine/
├── include/XCEngine/Rendering/
│ ├── RenderSurface.h
│ ├── RenderContext.h
│ ├── RenderPipeline.h
│ ├── RenderPipelineAsset.h
│ ├── SceneRenderer.h
│ ├── RenderSceneExtractor.h
│ ├── RenderCameraData.h
│ ├── VisibleRenderObject.h
│ ├── RenderResourceCache.h
│ └── Pipelines/
│ └── BuiltinForwardPipeline.h
└── src/Rendering/
├── RenderSurface.cpp
├── SceneRenderer.cpp
├── RenderSceneExtractor.cpp
├── RenderResourceCache.cpp
└── Pipelines/
└── BuiltinForwardPipeline.cpp
```
### 5.2 组件层建议
为了尽可能对齐 Unity而不是做一个临时过渡方案建议直接采用
- `MeshFilterComponent`
- `MeshRendererComponent`
其中:
#### `MeshFilterComponent`
负责“这个对象使用哪一个 mesh”
- `ResourceHandle<Mesh>`
#### `MeshRendererComponent`
负责“这个对象如何被渲染”:
- 材质数组
- cast shadow / receive shadow
- render queue / layer / enable 状态
- 未来可扩展 light probe / motion vector / static batching 标记
这样做的好处是:
- 更贴近 Unity 的对象模型
- 更容易映射未来 C# API
- 更容易在 editor Inspector 中呈现
- 更容易为 `SkinnedMeshRenderer``SpriteRenderer` 等后续组件扩展留位置
### 5.3 Renderer 内部运行时对象
#### `RenderSurface`
统一表示渲染输出目标:
- 交换链输出
- 离屏 color/depth 输出
- editor viewport 输出
#### `RenderSceneExtractor`
负责从 `Scene` 中提取本帧可渲染对象:
- mesh
- material
- transform
- bounds
- render state
#### `RenderResourceCache`
负责把资源模块对象转成 GPU 可用对象:
- mesh -> vertex/index buffer
- texture -> RHI texture / resource view
- material -> descriptor set / uniform buffer / pipeline key
- shader pass -> pipeline state
#### `RenderContext`
作为原生渲染执行上下文,未来用于承接脚本化 pipeline 的调度。
它应封装:
- 当前 frame 的 command list
- render target 设置
- clear / draw / submit
- camera 相关渲染上下文
#### `RenderPipeline`
用于抽象具体渲染流程。
第一阶段只有一个原生内建实现:
- `BuiltinForwardPipeline`
未来再开放:
- native 可切换 pipeline
- C# 绑定的 scriptable pipeline
---
## 6. 第一阶段实现边界
第一阶段只做最小可用链路,不做“大而全”。
### 6.1 第一阶段要做
- `Rendering` 模块骨架
- `MeshFilterComponent` / `MeshRendererComponent`
- `RenderSurface`
- `RenderSceneExtractor`
- `RenderResourceCache`
- `SceneRenderer`
- `BuiltinForwardPipeline`
- 单 camera
- 单方向的 opaque forward 渲染
- 深度测试与深度写入
- 交换链输出
- 离屏输出
### 6.2 第一阶段先不做
- 阴影
- 后处理
- 延迟渲染
- 完整 PBR
- render graph
- C# SRP 真正落地
- editor viewport 完整交互
- 多 camera 叠加
### 6.3 第一阶段材质能力建议
建议先支持两档:
1. `UnlitTexture`
2. `SimpleLit`
其中:
- `UnlitTexture` 用于先打通最小链路
- `SimpleLit` 用于验证灯光、法线与材质基础通路
---
## 7. 面向未来 C# SRP 的预留设计
虽然第一阶段先做原生内建渲染,但必须提前约束下面这些方向。
### 7.1 先定义 pipeline 边界,再定义内建实现
正确顺序应当是:
1. 先定义 `RenderPipeline` 抽象
2. 再实现 `BuiltinForwardPipeline`
3. 后续 C# SRP 只是在这个边界上做脚本绑定
而不是:
1. 先写死一个 `SceneRenderer`
2. 以后再强行拆成 pipeline
第二种方式后续返工会很大。
### 7.2 Renderer 模块应向未来脚本层暴露的概念
当前阶段不一定全部实现,但结构上要留位置:
- `RenderPipelineAsset`
- `RenderPipeline`
- `RenderContext`
- `CullingResults`
- `DrawingSettings`
- `FilteringSettings`
- `ShaderTag`
- `CommandBuffer`
- `RendererList`
这些概念不一定要立刻与 Unity 一字不差,但应该在职责上能对应上。
### 7.3 材质与 Shader 资产模型不能停留在“单 shader 文件 + 属性包”
未来做 SRP 时shader pass 选择、render queue、tag、render state 都是必要能力。
因此当前阶段即使先不全量实现,也不能把资产模型彻底锁死在过于简单的结构上。
这一点单独列为 issue。
---
## 8. 分阶段推进建议
### 阶段 ARenderer v0 骨架
目标:
- 建立 `Rendering` 模块
- 建立 `MeshFilterComponent` / `MeshRendererComponent`
- 建立 `RenderSurface`
- 建立 `BuiltinForwardPipeline`
验收:
- 可以通过 Renderer 正式绘制一个 textured quad 场景
- 输出到 swapchain
- 输出到离屏 RT
### 阶段 B真实资源场景接入
目标:
- 接入 mesh / texture / material 资源模块
- 跑通 `backpack` 这样的真实模型场景
验收:
- 真实 obj 资源经资源模块导入后可通过 Renderer 正式绘制
- D3D12 / OpenGL 双后端结果一致
### 阶段 C基础光照
目标:
- 接入 `LightComponent`
- 跑通最基础的单方向光前向渲染
验收:
- `sphere` / `backpack` 存在正确基础明暗
- 材质参数与法线链路可验证
### 阶段 Dpipeline 抽象显式化
目标:
- 把内建前向渲染切到 `RenderPipeline` 抽象之下
- 支持 camera 列表驱动
- 为未来 C# SRP 绑定准备原生接口
验收:
- 原生内建 pipeline 通过统一接口驱动
- renderer 不再依赖单一路径写死执行
### 阶段 Eeditor viewport 接入
目标:
- `SceneView` / `GameView` 使用 Renderer 的离屏输出
验收:
- editor 面板只是渲染宿主
- 不额外复制一套渲染逻辑
### 阶段 FC# SRP 桥接
目标:
- 在既有 Renderer 模块基础上绑定脚本化 pipeline
验收:
- C# 可以控制 camera 渲染流程
- 原生 Renderer 继续负责底层资源、上下文与执行
---
## 9. 测试体系建议
Renderer 模块需要独立测试体系。
### 9.1 单元测试
建议放在:
- `tests/Rendering/unit/`
测试内容:
- render object 抽取
- material 参数打包
- pipeline key 构建
- GPU cache 命中与失效
- render surface 创建与 resize
### 9.2 集成测试
建议放在:
- `tests/Rendering/integration/`
建议场景:
1. `textured_quad_scene`
2. `backpack_scene`
3. `lit_sphere_scene`
仍然维持当前 RHI 抽象测试的好习惯:
- 一场景一张 `GT.ppm`
- D3D12 / OpenGL 都与同一张 GT 比对
### 9.3 与 RHI 测试的关系
`tests/RHI/` 继续用于验证:
- API 抽象正确性
- 后端行为一致性
- 资源 / 命令 /格式映射等底层问题
`tests/Rendering/` 则验证:
- 场景渲染链路
- 组件与资源到渲染结果的闭环
---
## 10. 当前已识别的不适配问题
以下问题不适合直接塞进本设计文档正文实现里,而应该独立跟踪:
1. `Scene / Components` 层还没有 `MeshFilter / MeshRenderer` 抽象
2. `Editor` 还没有 viewport 的离屏渲染宿主接入层
3. `Material / Shader` 资产模型还不足以支撑未来 SRP 的 pass/tag 语义
对应 issue
- `docs/issues/Renderer模块_Scene层缺少MeshFilter与MeshRenderer抽象.md`
- `docs/issues/Renderer模块_EditorViewport缺少RenderSurface接入层.md`
- `docs/issues/Renderer模块_Material与Shader资产模型暂不满足SRP演进需求.md`
---
## 11. 结论
当前 Renderer 阶段的正确方向是:
- 在 RHI 之上建立 **原生渲染运行时**
- 用它先承接基础前向渲染
- 同时提前为未来 **C# SRP** 留出清晰接口
因此,下一阶段的 Renderer 规划如果按本文执行,是与 Unity 渲染架构方向相容的,而且比“先做一个临时内建 renderer后面再拆”更稳。
一句话概括:
- **现在做的是 Unity 式渲染体系的原生底座**
- **以后在这个底座之上接 C# SRP**

View File

@@ -1,415 +0,0 @@
# 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 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. 明确拆成三层语义:
- `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 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. `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 承接
否则就是在结构债未清的情况下继续加层,后面只会越收越难。

View File

@@ -1,523 +0,0 @@
# Renderer阶段收口旧兼容路径清理与正式化计划
日期:`2026-04-08`
## 1. 背景
当前 `Rendering` 模块的主执行架构已经基本成型:
- `RenderSceneExtractor`
- `SceneRenderRequestPlanner`
- `SceneRenderer / CameraRenderer`
- built-in forward / shadow / object-id / outline / final-color / skybox
这些主链路已经能稳定支撑:
- runtime 场景渲染
- editor scene/game viewport
- 多光源、阴影、object-id、outline、skybox 等现有能力
因此,当前 Rendering 的主要问题已经不再是“能不能画出来”,而是:
- 还残留一些旧路线兼容代码
- 一些 built-in 运行契约仍然依赖隐式推断
- 少量路径仍然带有明显的过渡期实现痕迹
如果这些问题不在当前阶段彻底收口,后续继续推进:
- Renderer 模块扩展
- Material / Shader editor
- Unity 风格 SRP 底层承接
就会持续建立在一层“虽然能跑,但不是正式规则”的兼容逻辑之上。
这不符合当前阶段的目标。
当前阶段的正确方向不是新增更多渲染功能,而是:
- 清理旧兼容路径
- 去掉运行时语义猜测
- 把 built-in shader / material / pass contract 进一步正式化
---
## 2. 当前已确认的问题
基于本轮对 `engine/include/XCEngine/Rendering``engine/src/Rendering``engine/src/Resources/Shader``engine/src/Resources/Mesh` 的代码审查,当前确认存在以下问题。
### 2.1 Mesh 导入仍可生成“无 shader / 无 schema”的旧材质路线
当前 `MeshLoader` 导入子材质时,仍然直接写入:
- `baseColor`
- `baseColorTexture`
- `opacity`
- `twoSided`
而不是直接落到正式 shader schema 对应的属性名与纹理槽位。
这导致 runtime 渲染阶段仍然需要兜底兼容这些旧名字。
典型位置:
- `engine/src/Resources/Mesh/MeshLoader.cpp`
- `engine/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h`
### 2.2 Rendering 仍通过属性别名表推断 built-in 材质语义
当前 `RenderMaterialResolve.h` 中,仍然保留了大量 builtin 属性/纹理别名表,例如:
- `baseColor`
- `_BaseColor`
- `color`
- `_Color`
- `baseColorTexture`
- `_BaseColorTexture`
- `_MainTex`
- `texture`
这意味着 runtime 当前并不是“按 shader schema 正式解析”,而是:
- 优先找 semantic
- 找不到就继续按一批旧属性名字猜
这属于典型过渡兼容逻辑,不应成为正式长期实现。
### 2.3 BuiltinForward / Depth / Shadow 仍存在 per-material fallback constant 路线
当前如果材质没有正式 schema constant layout管线仍会临时构造
- `FallbackPerMaterialConstants`
并继续提交 draw。
这说明 runtime 仍允许“非正式材质实例”继续进入正式绘制链路。
这条路径虽然提高了兼容性,但本质上绕开了已经建立的 shader/material 正式模型。
### 2.4 Built-in pass resource binding 仍依赖隐式硬编码
当前 builtin shader pass 如果未显式声明 `resources`,运行时仍会通过:
- `TryBuildImplicitBuiltinPassResourceBindings`
自动补一套绑定布局。
这意味着资源绑定契约并不完全存在于 shader 资产中,而是仍有一部分硬编码在 C++ 中。
这会带来两个问题:
1. shader 资产与 runtime 存在双份真相
2. 后续继续演进 shader/material/editor 时,容易再次产生隐式规则
### 2.5 HLSL register 重写仍保留 legacy alias
当前 `ShaderVariantUtils.h` 仍保留:
- `ResolveLegacyHlslBindingDeclarationAlias`
以及基于 `gBaseColorTexture` / `gLinearSampler` 一类旧命名的重写逻辑。
这说明 shader runtime 编译阶段仍在兼容旧命名风格。
这属于典型“过渡兼容层”,应在 built-in shader 显式资源契约完成后清掉。
### 2.6 Built-in pass 选择仍存在隐式默认规则
当前如果 shader 没有显式 builtin metadata`MatchesBuiltinPass(...)` 仍会把它默认当成:
- `ForwardLit`
这意味着 shader 即使没有明确声明自己属于哪个 built-in pass也有可能继续进入主几何管线。
这不利于长期正式化。
### 2.7 Shader artifact 仍兼容多代旧 schema
当前 shader artifact loader 仍兼容:
- `XCSHD01`
- `XCSHD02`
- `XCSHD03`
- `XCSHD04`
- 当前 schema
但 shader artifact 本质上是 `Library` 中的可重建中间产物,不属于必须长期 runtime 兼容的用户资产格式。
如果继续保留多代 schema 分支,会让 shader 资源链路长期背着历史包袱。
---
## 3. 本阶段设计原则
本计划执行时,必须严格遵守以下原则。
### 3.1 正式路径只能有一条
对 built-in shader / material / pass 来说,正式路径必须是:
`导入/authoring -> shader schema -> material instance -> explicit pass contract -> render pipeline`
不能继续允许 runtime 依赖旧命名、旧别名、旧格式去自动猜测。
### 3.2 兼容应尽量前移到导入/重建阶段,而不是留在 runtime
如果确实存在历史资产问题,应优先采用:
- 重新导入
- 重新生成 artifact
- 一次性迁移
而不是继续在 runtime loader / renderer 中保留长期兼容分支。
### 3.3 Built-in shader 契约必须显式写进 shader 资产
以下内容必须属于 shader/pass 资产本身,而不是 runtime 猜出来:
- pass 类型
- pass metadata
- resource binding
- property semantic
### 3.4 Rendering 不再为“无正式 shader/schema 的材质”兜底渲染
当前阶段的目标是“收口”,不是“继续最大化兼容”。
因此:
- 非正式材质应尽快在导入层修正
- runtime 应逐步拒绝无 schema 的正式绘制路径
### 3.5 每一步都必须可验证
每个阶段完成后必须配套:
- unit test
- 必要的 integration test
- editor 编译/回归
不能只凭画面“看起来没问题”判断完成。
---
## 4. 本阶段目标
本阶段完成后Rendering 模块应达到以下状态:
1. Mesh 导入出来的材质直接走正式 shader/material 体系
2. runtime 不再依赖 `baseColor` / `_MainTex` 等别名表去维持 built-in 主链
3. built-in pass resource binding 由 shader 资产显式声明,不再依赖隐式硬编码补全
4. built-in pass 分类必须显式声明,不再存在“默认 ForwardLit”
5. shader artifact runtime loader 不再长期兼容多代旧 schema
6. 对应测试体系同步升级,保证收口后功能不回退
---
## 5. 明确不在本阶段处理的内容
以下内容不属于本阶段目标:
- render graph
- deferred renderer
- 新一轮后处理功能扩展
- C# SRP 脚本侧 API
- ShaderGraph
- 高级材质编辑器功能扩展
这些方向都依赖本阶段先把底层 contract 收紧。
---
## 6. 分阶段执行计划
## Phase 1建立基线与目标测试
### 目标
先把当前遗留兼容路径的行为边界用测试钉住,并同步写出“目标行为”的新测试。
### 任务
- 审查并整理当前覆盖以下行为的测试:
- `RenderMaterialResolve`
- builtin forward pipeline resource binding
- mesh material import
- shader artifact load
- 新增/调整测试,使其明确区分:
- 当前历史兼容行为
- 本阶段目标正式行为
- 对以下目标先写失败测试或待切换测试:
- imported mesh material 必须绑定正式 builtin shader
- imported material property 必须落到正式 schema 名称
- builtin pass 若无显式 metadata不得进入主 pipeline
- builtin shader 若无显式 resources不得依赖隐式 binding 补全
### 验收标准
- 能清楚列出哪些测试在保护旧行为,哪些测试在保护目标行为
- 后续每个阶段都能基于这些测试判断是否真正收口
---
## Phase 2收口 Mesh 导入材质到正式 shader/material 路径
### 目标
彻底去掉 imported mesh material 的“无 shader / 裸属性名”旧路线。
### 任务
- 调整 `MeshLoader` 导入逻辑:
- imported material 直接绑定正式 builtin shader
- 默认按现有主线落到 builtin lit/forward 合同
- 导入属性与纹理时,直接写正式 property name / texture slot
- 例如 `_BaseColor`
- `_MainTex`
- `_Cutoff`
- 其他已正式声明的 builtin 属性
- 不再向 imported material 写入仅靠 runtime 别名识别的裸字段:
- `baseColor`
- `baseColorTexture`
- `color`
- `texture`
- 更新 mesh import 相关测试、render extractor 测试、相关 integration 资源测试
### 验收标准
- mesh import 结果中的材质都带有正式 shader 引用
- mesh import 结果中的属性/纹理绑定名称与 shader schema 对齐
- 不再需要 runtime 靠旧别名才能让导入材质正常渲染
---
## Phase 3移除 runtime builtin 材质语义别名与 fallback 常量路径
### 目标
让 built-in pipeline 只吃正式 schema 材质,不再继续兼容旧材质命名。
### 任务
- 清理 `RenderMaterialResolve.h` 中的旧别名解析表:
- base color property alias
- base texture alias
- skybox texture alias
- alpha cutoff alias
- 保留并强化基于 `shader property semantic` 的正式解析路径
- 移除 `FallbackPerMaterialConstants` 路线
- 当材质未携带正式 schema constant layout 时:
- 显式报错 / 记录诊断
- 拒绝进入需要正式材质常量的绘制路径
- 调整 forward / depth / shadow / skybox 相关单测
### 验收标准
- builtin pipeline 不再依赖属性别名表维持主链
- builtin pipeline 不再手工构造 per-material fallback constant 继续绘制
- runtime 只接受正式 shader/material 契约
---
## Phase 4显式化 builtin pass resource binding contract
### 目标
让 builtin shader pass 的资源绑定契约完全存在于 shader 资产中,而不是藏在 runtime 硬编码里。
### 任务
- 为所有 builtin shader pass 补齐显式 `resources` 描述
- 覆盖至少以下 shader
- `forward-lit.shader`
- `depth-only.shader`
- `shadow-caster.shader`
- `object-id.shader`
- `skybox.shader`
- `final-color.shader`
- 其他当前仍在主链中的 builtin shader
- 清理 `TryBuildImplicitBuiltinPassResourceBindings`
- 清理 `ShaderVariantUtils.h` 中围绕 implicit/legacy binding 的兼容逻辑:
- legacy alias register rewrite
- 依赖 `gXxx` 名称重写的分支
- 调整 shader loader / rendering pipeline / builtin pass 单测
### 验收标准
- builtin shader pass 缺少显式资源绑定时,构建或运行应明确失败
- runtime 不再替 shader 资产自动补 binding layout
- HLSL runtime 编译不再依赖 legacy alias register 重写
---
## Phase 5显式化 builtin pass metadata 与 pass 选择规则
### 目标
去掉“默认 ForwardLit”一类隐式 pass 归类规则。
### 任务
- 收紧 `BuiltinPassMetadataUtils`
- built-in pass 匹配必须依赖显式 pass name / tag
- 删除“无 metadata 默认归 ForwardLit”的逻辑
- 审查并统一 builtin shader 的 pass metadata
- `Name`
- `LightMode`
- 其它当前正式要求的 tag
- 对进入 builtin 主线的 shader 建立硬约束:
- 没有显式 builtin metadata 的 shader不得继续被当作主几何 shader 使用
- 更新 pass 匹配测试和 shader authoring 测试
### 验收标准
- builtin pass 选择全部基于显式 metadata
- 不存在 runtime 默认猜一个 pass 类型的行为
---
## Phase 6清理旧 shader artifact schema 兼容
### 目标
让 shader artifact runtime loader 与 material artifact 一样,收口到 current schema。
### 任务
- 清理 `ShaderArtifactLoader.cpp` 中对旧 schema 的分支兼容:
- `XCSHD01`
- `XCSHD02`
- `XCSHD03`
- `XCSHD04`
- 将旧 `Library` artifact 的处理方式改为:
- 识别为过期
- 触发重新导入 / 重新生成
- 或直接报错要求重建 `Library`
- 更新 asset database / shader load 相关测试
- 明确记录此阶段会带来的影响:
-`Library` 无法直接沿用
- 需要一次性刷新或重建
### 验收标准
- shader artifact loader 只接受 current schema
- 对旧 artifact 的处理边界清晰且可测试
---
## Phase 7全量验证与阶段收口
### 目标
确认 Rendering 在去掉旧兼容层之后没有破坏现有功能。
### 任务
- 编译并运行:
- `material_tests`
- `rendering_unit_tests`
- `asset_tests`
- `editor_tests`
- 受影响的 mesh/shader 资源测试
- 重新编译 `XCEditor`
- 重点回归:
- scene viewport
- game viewport
- object-id picking
- selection outline
- skybox
- 阴影
- 多光源
- backpack / sphere / quad 等 integration scene
- 形成阶段收口报告
### 验收标准
- 所有直接相关测试通过
- editor 编译通过
- 关键 integration scene 渲染行为不回退
- 能明确宣告 runtime 旧兼容路径已移除
---
## 7. 风险与注意事项
### 7.1 这是一次“切正式路径”的收口,不是小修小补
本计划一旦执行,就会主动删除一部分兼容逻辑。
因此不能以“尽量少改代码”为目标,而应以:
- 正式路径唯一
- contract 清晰
- 后续 SRP 可承接
为目标。
### 7.2 `Library` 重建属于预期影响
一旦收掉旧 shader artifact schema 兼容,旧 `Library` 里的 shader artifact 失效是正常现象。
这不应被视为回归,而应被视为阶段性收口的合理代价。
### 7.3 必须避免引入新的“临时兼容层”
执行过程中需要特别警惕以下错误做法:
- 新加一层 alias 表,试图“先兼容一下”
- 把 runtime fallback 换个名字继续保留
- 在 editor 或 import 层再次引入一套过渡数据模型
如果遇到结构性问题,正确做法是:
- 直接改到正式模型
- 同步补测试
而不是再加一层短期兜底。
---
## 8. 建议执行顺序
建议严格按以下顺序推进:
1. `Phase 1` 测试基线整理
2. `Phase 2` mesh 导入材质正式化
3. `Phase 3` runtime 材质别名与 fallback 常量清理
4. `Phase 4` builtin pass 显式资源绑定
5. `Phase 5` builtin pass metadata 显式化
6. `Phase 6` shader artifact schema 收口
7. `Phase 7` 全量验证
原因是:
- 如果不先把 imported material 拉回正式路径
- 后面的 runtime alias / fallback 清理就一定会打断现有资源链路
---
## 9. 本阶段完成后的预期状态
本计划完成后Rendering 模块应达到以下状态:
1. built-in shader/material/pass contract 全部走正式显式路径
2. runtime 不再依赖旧命名猜测材质语义
3. runtime 不再替非正式材质拼接 fallback 常量布局
4. builtin shader 资源绑定契约完全由 shader 资产声明
5. builtin pass 类型选择完全依赖显式 metadata
6. shader artifact runtime loader 不再背负旧 schema 包袱
7. 整个 Rendering 模块更适合作为后续 Unity 风格 SRP 的底层承接
---
## 10. 一句话总结
当前 Rendering 真正需要的不是继续加功能,而是把残留的旧兼容路径彻底拔干净。
这一阶段的本质,是把:
- imported material
- built-in shader binding
- pass metadata
- shader artifact
全部拉回到同一套正式 contract 上,为后续 Renderer / Material / Shader / SRP 的继续推进打地基。

View File

@@ -1,71 +0,0 @@
# Renderer 阶段收口补充Object ID Picking 正式化
日期:`2026-04-02`
## 1. 这次补充收口解决什么
本次补充只收一件事:
- `SceneView` 选中主链路正式切到 `GPU object-id`
本次明确不做:
- render graph
- renderer 内更完整的多 pass 调度
- game/runtime 通用 picking 服务
原因很简单:这些属于下一阶段架构演进,不应该继续污染当前阶段的收口边界。
## 2. 本次收口后的正式行为
当前 `SceneView` 选中行为统一定义为:
1. 场景渲染时生成 `object-id` 纹理
2. 鼠标点击时读取对应像素
3. 颜色解码为实体 ID
4. `0` 视为“未选中任何对象”,但这仍然是一次成功的 GPU 采样
关键变化:
- editor 不再把 `CPU ray picking` 作为 `SceneView` 点击选中的静默回退主链路
- `CPU ray picking` 继续保留为独立几何工具能力,不再承担当前正式选中流程
- `object-id` 读取失败会被显式标记为 readback failure而不是与“没有有效帧”混在一起
## 3. 为什么这才算收口
之前的问题不是没有 `object-id pass`,而是“主路径”和“兜底路径”的语义不够硬:
- 成功采样
- 无有效 object-id 帧
- GPU 读回失败
这三种状态以前没有被清晰区分。
现在已经收紧为显式结果类型:
- `Unavailable`
- `Success`
- `ReadbackFailed`
这意味着:
- renderer/editor 的 `object-id` 交互已经形成可测试契约
- `0 id` 与“采样失败”不再混淆
- 后续若要继续升级成异步 readback、共享 picking 服务,也有稳定边界可接
## 4. 当前阶段完成后的边界
到这里,当前阶段可以正式视为完成:
- editor viewport 宿主链路已打通
- renderer 的 builtin post-process 已形成稳定接口
- `SceneView` 选中正式以 GPU object-id 为主链路
- 回归测试已覆盖 object-id 读回状态语义
下一阶段真正该做的是:
- renderer 内正式 render graph / pass graph
- 更完整的 renderer-owned picking 服务
- editor / runtime shared picking contract
而不是继续在这个阶段里反复修补 viewport host。

View File

@@ -1,164 +0,0 @@
# Renderer阶段收口说明
## 1. 目标
本文用于正式收口当前 Renderer 阶段,明确:
- 本阶段已经完成什么
- 哪些能力已经进入稳定边界
- 哪些事项明确延期到下一阶段
- 后续开发不应再继续把新功能塞回本阶段
当前收口日期:`2026-04-02`
---
## 2. 本阶段已完成能力
### 2.1 Renderer 主体边界
当前已经形成稳定分层:
- `RHI` 负责后端抽象与资源/命令执行
- `Rendering` 负责场景提取、camera request、pipeline、builtin pass
- `Editor` 负责 viewport 宿主、输入、overlay、编辑态请求装配
关键点:
- `CameraRenderer` 已经承担统一 camera 渲染执行职责
- `SceneRenderer` 已经承担 scene -> camera request 的组织职责
- editor scene viewport 不再自己拼装 renderer 执行逻辑
### 2.2 内建后处理边界
本阶段内建编辑态后处理已经收敛为 renderer 自己的通用请求能力:
- `BuiltinPostProcessRequest`
- `BuiltinPostProcessPassPlan`
- `BuiltinPostProcessPassSequenceBuilder`
这意味着:
- renderer 公共接口不再暴露 `SceneView` 专有命名
- grid / selection outline / debug mask 已归入 renderer 侧 builtin post-process 能力
- editor 只负责“是否启用、传什么数据、把哪些 render target 绑定进 request”
### 2.3 Editor Scene Viewport 接入
当前 editor scene viewport 已具备:
- renderer 离屏输出接入
- object-id 帧输出接入
- CPU picking 回退链路
- selection outline
- infinite grid
- built-in post-process 请求装配
其中:
- grid 和 outline 仍然服务于 editor scene viewport
- 但执行入口已经下沉到 renderer
- editor 只保留宿主与编辑器语义
### 2.4 自动化测试体系
当前已经具备稳定回归闸门:
- `tests/Rendering/unit`
- `tests/Rendering/integration`
- `tests/Editor`
- `rendering_phase_regression`
当前阶段收口依赖的关键验证包括:
- renderer unit tests
- editor tests
- 全 rendering integration 场景
- `XCEditor` smoke launch
---
## 3. 本阶段稳定边界
以下内容从现在开始视为本阶段稳定边界:
1. renderer 公共请求以 `CameraRenderRequest` 为核心,而不是 editor 自定义执行入口。
2. editor scene viewport 的内建后处理数据由 editor 组装,但 pass 执行由 renderer 负责。
3. builtin post-process 的公共语义是 renderer 语义,不是 `SceneView` 语义。
4. rendering regression 失败时,优先视为阶段回归,而不是“可接受的小问题”。
---
## 4. 本阶段明确延期项
以下事项明确不再继续塞入本阶段,转入下一阶段:
### 4.1 真正的多 pass / render graph 框架
当前已有 pass sequence 与 builtin post-process但这还不是完整的 renderer 多 pass 架构。
延期内容:
- renderer 级 render graph
- 更正式的 pass phase / event 模型
- 更通用的资源读写依赖管理
### 4.2 GPU Object ID 正式方案
当前 editor selection 相关链路已经能工作,但还不是最终方案。
延期内容:
- renderer 内正式 object-id pass/attachment 规范化
- editor picking 从 CPU fallback 继续向 GPU object-id 正式方案收敛
- editor/game shared picking contract
### 4.3 Gizmo 最终渲染体系
当前 gizmo 与 scene viewport 已经能工作,但不属于本阶段 renderer 收口范围。
延期内容:
- 更成熟的 gizmo 渲染架构
- 更统一的 gizmo draw pass / picking / overlay 体系
- 与后续 renderer 多 pass 的正式对接
### 4.4 C# SRP 对接
当前 renderer 的职责边界已经为 SRP 预留好了方向,但本阶段不做真正脚本化 pipeline 落地。
延期内容:
- `RenderPipelineAsset` / `RenderPipeline` 脚本绑定
- `ScriptableRenderContext`
- `CommandBuffer`
- renderer 与脚本侧的正式桥接层
---
## 5. 阶段退出标准
当前阶段只有在以下条件全部满足时才视为完成:
1. renderer/editor 边界中不再存在新的 `SceneView` 语义向 renderer 公共接口泄漏。
2. scene viewport 的 builtin post-process 组合链路具备稳定自动化回归覆盖。
3. `rendering_phase_regression` 保持通过。
4. 新功能开发转入下一阶段,不再回头污染本阶段边界。
截至本文落地时,这些退出标准已经满足。
---
## 6. 下一阶段入口
Renderer 下一阶段应当正式转向:
- renderer 内更完整的多 pass / phase 模型
- editor/game shared render feature 契约
- object-id 正式化
- 为后续 C# SRP 搭建真正可扩展的 renderer 接口
一句话总结:
- 当前阶段已经把“Renderer 从 RHI 之上独立出来,并接通 editor scene viewport”这件事做完
- 下一阶段不该继续修补这一层,而应开始建设更正式的 renderer 扩展框架

View File

@@ -0,0 +1,291 @@
# Scene 选中描边彻底修复计划
日期2026-04-08
## 1. 文档定位
本文档只解决一个问题:
- `Scene` 面板里,选中对象的描边效果需要达到稳定、可扩展、接近商业编辑器的质量。
本文档不讨论:
- 层级面板右键菜单
- 灯光 gizmo 图标
- 运行时高亮
- 游戏内选中反馈
## 2. 当前问题结论
当前实现不是“可见外轮廓提取”,而是“基于 object-id 的屏幕空间膨胀”。
现状链路如下:
1. 主场景正常渲染。
2. `BuiltinObjectIdPass` 把屏幕上最前面的物体 id 写入 `objectIdTexture`
3. `BuiltinObjectIdOutlinePass` 在全屏 shader 中检查当前像素周围是否存在“选中对象的 object-id”。
4. 只要邻域中存在选中 object-id当前像素就会被涂成 outline。
这个算法的根本缺陷是:
- 它只知道“邻域里有没有选中对象”。
- 它不知道“当前像素前面是不是别的物体挡住了选中对象”。
- 它也不知道“这条边是可见外轮廓,还是选中大平面与前景遮挡物之间的内部接触边界”。
所以当选中一个大平面时,平面上方的 cube、sphere、capsule 这些前景物体边缘也会被错误描边。
这不是数据污染,也不是拾取错误,而是算法层面的必然结果。
## 3. 根因
根因可以归纳为两条:
### 3.1 描边输入选错了
当前描边 pass 直接消费 `objectIdTexture`
`objectIdTexture` 的职责本质上是:
- 告诉编辑器“当前屏幕像素最前面的对象是谁”,用于 picking。
它不适合作为“选中描边”的唯一依据,因为它不包含:
- 选中对象的可见 mask
- 选中对象自己的深度
- 当前场景深度与选中对象深度之间的遮挡关系
### 3.2 描边算法缺少深度裁决
当前 outline shader 的判断规则是:
- 当前像素不是选中对象
- 但周围像素里存在选中对象
满足这两个条件就画 outline。
这套规则没有使用场景深度,也没有使用选中对象的可见性信息,因此无法区分:
- 真正应该画的外轮廓
- 不应该画的前景遮挡边界
## 4. 修复目标
本次彻底修复后的目标是:
1. 选中大平面时,前景物体边缘不再被错误染成 outline。
2. 选中普通 mesh 时outline 仍然能稳定出现在可见外轮廓处。
3. 被遮挡区域不画“伪外轮廓”。
4. picking 与 selection outline 两套能力彻底解耦。
5. 后续如果要做 Unity 风格的 x-ray 选中、被遮挡高亮、hover outline可以在当前结构上继续扩展而不是推倒重来。
## 5. 非目标
这次修复不做以下内容:
1. 不做运行时游戏内高亮系统。
2. 不做完整的“透视遮挡时仍显示淡色轮廓”的 x-ray 选中风格。
3. 不借这次机会重写整个 editor render pipeline。
4. 不继续在现有 `object-id-outline.shader` 上做只针对单个 case 的临时补丁。
## 6. 总体方案
正确方案是把“拾取”和“描边”拆开,改成:
- `object id for picking`
- `visible selection mask for outline`
- `depth-aware outline composite`
### 6.1 picking 继续走 object-id
现有 `objectIdTexture` 继续只用于:
- 场景点击选中
- object-id readback
它不再直接决定 outline 的视觉结果。
### 6.2 新增 selection mask pass
新增一张仅服务于 outline 的 `selectionMaskTexture`
它的规则是:
1. 只渲染当前选中的对象。
2. 渲染时绑定主场景已有的深度缓冲。
3. 依赖深度测试,只把“真正可见的 selected fragment”写进 `selectionMaskTexture`
这样得到的不是 picking id 图,而是“选中对象可见区域 mask”。
### 6.3 scene viewport 暴露深度 SRV
为了做真正的 depth-aware composite`Scene` viewport 需要同时持有:
1. `depthView`
2. `depthShaderView`
也就是说Scene viewport 的深度纹理既要能作为 DSV 使用,也要能在全屏 composite shader 中作为 SRV 读取。
底层 RHI 已经具备这条能力,阴影缓存已有相同用法,因此这不是架构冒险,而是 viewport 资源层尚未接线。
### 6.4 重写 outline composite pass
新的 outline shader 不再直接从 `objectIdTexture` 做邻域膨胀,而是读取:
1. `selectionMaskTexture`
2. `sceneDepthTexture`
必要时预留:
3. `selectionDepthTexture`
新的裁决规则应为:
1. 中心像素若属于 selected mask本像素不直接着色。
2. 若邻域存在 selected mask则该像素可能位于 selected 的边界附近。
3. 只有在深度关系证明“当前像素不是更近的前景遮挡物”时,才允许输出 outline。
这一步的本质是:
- 只画可见 selected 外轮廓。
- 不把前景遮挡物边界误判为 selected outline。
## 7. 具体实施步骤
### Phase 1补齐 viewport 资源
目标:
- Scene viewport 持有可采样深度。
- Scene viewport 持有单独的 selection mask render target。
需要修改:
1. `editor/src/Viewport/ViewportHostRenderTargets.h`
2. 相关 viewport resource reuse / destroy / create 流程
新增资源建议:
1. `depthShaderView`
2. `selectionMaskTexture`
3. `selectionMaskView`
4. `selectionMaskShaderView`
5. `selectionMaskState`
验收标准:
- Scene viewport 创建/销毁/复用逻辑能完整覆盖新资源。
- 编译通过。
- 不影响现有 Scene 视图展示。
### Phase 2新增 selection mask pass
目标:
- 只把当前选中对象的可见区域写入 mask。
实现建议:
1. 新建 editor 专用 `SelectionMaskPass`
2. 输入为选中对象 id 列表。
3. 输出到 `selectionMaskTexture`
4. 渲染时绑定主场景同一套深度。
实现关键点:
1. 这个 pass 的语义不是 picking。
2. 它只关心“选中对象哪些像素当前可见”。
3. 它不需要写 object id只需要写统一 mask 值或选中组 id。
验收标准:
- 选中对象时可以输出稳定的可见 mask。
- 未选中时 pass 可被跳过。
### Phase 3重写 outline composite
目标:
- outline 只出现在选中对象的可见外轮廓。
实现建议:
1. 保留全屏 pass 形式。
2. 输入从 `objectIdTexture` 改为 `selectionMaskTexture + depthShaderView`
3. 邻域检查继续保留,但结果必须经过深度裁决。
最低正确规则:
1. 中心像素若是别的前景几何,且深度显著近于 selected 边界,不画 outline。
2. 中心像素若是背景或更远表面,可以画 outline。
验收标准:
1. 选中大平面时,前景 cube/sphere/capsule 边缘不再被错误描边。
2. 选中单个 mesh 时,外轮廓仍连续稳定。
### Phase 4移除旧耦合
目标:
- 彻底消除 “outline 直接依赖 object-id picking 纹理” 这一旧逻辑。
需要完成:
1.`object-id-outline.shader` 逻辑下线或重命名。
2. `SceneViewportSelectionOutlinePass` 的输入契约改为新资源。
3. 保证 `objectIdTexture` 仅服务于 picking。
验收标准:
- 代码层不再存在“outline 直接读 object id 邻域并膨胀”的核心路径。
## 8. 测试计划
至少补以下回归验证:
### 8.1 功能场景
1. 选中大平面,前面摆放 cube / sphere / capsule
- 前景物体边缘不能被错误描边。
2. 选中单个 cube背景是地面
- outline 能稳定显示在 cube 可见外轮廓上。
3. 选中被部分遮挡的物体:
- 只允许可见区域出现 outline。
4. 多选相邻物体:
- outline 不串边,不污染第三方前景物体。
### 8.2 回归类型
1. shader 资源加载测试
2. render plan 接线测试
3. pass 构建与资源存在性测试
4. 视口级人工验证截图
## 9. 风险与边界
### 9.1 风险
1. 仅使用 `sceneDepth + selectionMask` 可能仍有少数边界 case 需要 `selectionDepth` 才能更稳。
2. Scene viewport 新增 render target 后,资源管理和状态切换复杂度会上升。
3. 若后续要兼容 Vulkan / OpenGL需要同步确认深度 SRV 路径与资源状态语义。
### 9.2 边界
本计划的第一目标不是“做最炫的 outline”而是
- 先把语义做对
- 再把视觉做稳
只要还在复用 picking 的 `objectIdTexture` 直接做 outline问题就不可能彻底解决。
## 10. 最终结论
这次 bug 不能靠继续修补当前 `object-id-outline.shader` 解决。
彻底方案必须满足三点:
1. picking 与 outline 解耦
2. 选中对象生成独立可见 mask
3. outline composite 引入深度裁决
只有这样Scene 视图选中描边才会从“屏幕空间膨胀特效”升级为“真正可用的编辑器轮廓系统”。

View File

@@ -1,62 +0,0 @@
# Unity式 Tick 系统与 Play Mode 运行时方案阶段进展
日期2026-04-02
## 已完成
### 阶段 A
- 已接入 `RuntimeLoop`,统一承载 `FixedUpdate / Update / LateUpdate`
- 已接入 `PlaySessionController`
- 已实现 `Play / Stop`
- Play 时运行 runtime scene clone
- Stop 时恢复 editor scene snapshot
- `Run` 菜单与 `F5` 已可切换 `Play / Stop`
### 阶段 B 当前收口
- 明确区分“文档级编辑”和“运行态场景对象编辑”
- `New/Open/Save Scene``New/Open/Save Project` 仍只允许在 `Edit` 下执行
- `Play / Paused` 下允许对 runtime scene 进行对象级编辑与 `Undo / Redo`
- runtime scene 的对象改动默认不再污染场景文档 dirty 状态
### 阶段 C 当前收口
- 已补全 `Pause / Resume / Step` 的完整请求与状态切换
- `Run` 菜单现在区分 `Play/Stop``Pause/Resume``Step`
- `Error Pause` 已接入正式 Pause 请求通道
- `Paused` 下维持 runtime world不回退到 editor scene
- `Step` 现在只在 `Paused` 下有效,并保持 `Paused` 状态不变
### 阶段 D 当前收口
- 已在软件顶部增加独立运行栏
- 运行栏已接入 `Play / Pause / Step` 三个图标按钮
- 顶部按钮直接复用现有 PlayMode 请求通道,不额外分叉状态机
- `Play` 按钮在运行中会保持高亮,再次点击即 `Stop`
- `Pause``Play / Paused` 下可用,并沿用现有 `F6`
- `Step` 仍只在 `Paused` 下可用
- 顶部栏已改为参与 dockspace 布局,不再覆盖 Scene / Hierarchy / Inspector 面板标题区
## 本轮验证
- 已重新执行 `cmake -S . -B build`
- 已通过 `cmake --build build --config Debug --target scene_tests`
- 已通过 `cmake --build build --config Debug --target editor_tests -- /m:1 /v:minimal`
- 已通过 `cmake --build build --config Debug --target XCEditor -- /m:1 /v:minimal`
- 已通过聚焦测试:
`ctest --test-dir build -C Debug --output-on-failure -j1 -R "RuntimeLoopTest|PlaySessionControllerTest|EditorActionRoutingTest.*PlayMode|EditorActionRoutingTest.*MainMenuRouterRequestsPlayPauseResumeAndStepEvents"`
## 当前语义
- `editor tick` 负责托管运行时会话
- `engine tick` 负责推进 runtime world
- Play 时 `Hierarchy / Inspector / SceneView / GameView` 面对的是同一份 runtime world
- Play 中对对象的改动默认是临时运行态改动Stop 后回滚
- Play 中禁止的是文档切换与文档保存,不是禁止观察或编辑 runtime clone
## 下一阶段建议
- 明确 `Paused` 下的 `Undo / Redo / Gizmo / Inspector` 更细粒度交互边界
- 将 GameView 输入正式接入 runtime input 通道
- 继续补 `Simulate` 与更完整的 Time 语义

File diff suppressed because it is too large Load Diff

View File

@@ -1,631 +0,0 @@
# Scene Viewport Overlay 与 Gizmo 正规化重构方案
日期:`2026-04-02`
## 0. 当前进度 Checkpoint
截至 `2026-04-02`,本方案已有以下落地结果:
- `Phase 1` 已完成:
- `CameraRenderRequest` 已新增 `overlayPasses`
- `CameraRenderer` 已在 builtin postprocess 之后执行 `overlayPasses`
- `ViewportHostService` 已接入 editor overlay pass sequence
- `Phase 2` 已完成首批迁移:
- `camera frustum`
- `directional light gizmo`
- `camera/light scene icon`
- 上述内容已不再走 ImGui world draw而是走 renderer overlay pass
- `scene icon` 的命中数据已开始收口:
- `SceneViewPanel` 不再自己扫描 scene 构建 icon draw data
- icon hit test 已改为消费 `SceneViewportOverlayBuilder::Build()` 产出的同类 frame data
- `transform gizmo` 的统一命中已开始接线:
- `SceneViewportEditorOverlayData.h` 已扩展为通用 `handleRecords`
- `SceneViewportOverlayHandleBuilder.h` 已可把 move/rotate/scale gizmo draw data 转为 canonical handle records
- `SceneViewPanel` 中 gizmo 的 hover / click-begin 已开始走统一 `HitTestSceneViewportOverlayHandles(...)`
- `transform gizmo` 的绘制迁移已开始接线:
- `SceneViewportEditorOverlayPass` 已支持 screen-space triangle primitive
- `ViewportHostService` 已可在 host 侧根据 `SceneViewPanel` 提交的 overlay 与 gizmo handle build inputs 构建 transient transform overlay frame data
- transform gizmo 的 handle build inputs 组装 helper 已开始从 `SceneViewPanel``SceneViewportOverlayHandleBuilder.h` 收口
- transform gizmo 的 selection/context/refresh/cancel helper 已开始从 `SceneViewPanel``SceneViewportTransformGizmoFrameBuilder.h` 收口
- move / rotate / scale gizmo 已不再直接依赖 `DrawSceneViewportOverlay()` 的 ImGui gizmo 绘制分支出图
- `SceneViewportOverlayRenderer.cpp` 已收缩回 HUD/orientation 责任,不再承担 transform gizmo / scene icon / scene line 的 ImGui world draw
- `SceneViewPanel` 内部交互前命中与交互后绘制的 gizmo 刷新链路已开始复用同一套 helper重复的 context/update/submit 逻辑已明显收缩
- interaction overlay frame 已改为 host 按传入的 transform gizmo inputs 现场组合,`SceneViewPanel` 不再为 hit test 预先写入 transient overlay 缓存
- render 阶段使用的 transient transform gizmo frame data 也已改为 host 基于缓存的原始 overlay + inputs 现场构建
当前仍未完成的关键点:
- `transform gizmo` 的 drag solver / 变换求解仍然保留在各自 gizmo 类中
- `SceneViewPanel` 里仍保留 transform gizmo 的 draw data 生成、交互仲裁与 transient overlay 提交逻辑
- `SceneViewPanel` 仍直接控制 transform gizmo 的最终绘制 overlay 提交时机host 尚未完全接管这一层 frame orchestration
- `ViewportHostService` 的 canonical overlay frame data 仍未直接承载 transform gizmo尚未收敛到单帧单份 canonical overlay 数据
当前阶段结论:
**方案方向已经验证正确,下一步不应该回头继续扩写 ImGui world overlay而应该继续推进 canonical overlay data 与统一命中系统。**
## 1. 方案结论
当前 `Scene Viewport` 的问题,不是某一个 `Directional Light Gizmo` 画丑了,而是整条 editor overlay 链路本身没有收口:
- `grid` 已经是正规 renderer pass
- `transform gizmo / camera icon / light icon / camera frustum / directional light gizmo` 仍然是 ImGui overlay
- 输入命中和绘制几何不是同一份数据
- `SceneViewPanel.cpp` 同时承担 panel UI、输入调度、世界转屏幕、overlay 构建、命中仲裁,职责已经失控
结论只有一个:
**不能再继续往 `SceneViewPanel.cpp` 和 ImGui world overlay 上堆功能。必须把场景中的 editor 可视化正式收口成一套 renderer 级 overlay pass 和统一 handle 数据。**
---
## 2. 当前链路梳理
### 2.1 正规链路Grid
当前 `grid` 的路径是正规的 renderer pass
`ViewportHostService -> SceneRenderer -> CameraRenderer -> BuiltinPostProcessPassSequenceBuilder -> BuiltinInfiniteGridPass`
关键文件:
- `editor/src/Viewport/ViewportHostService.h`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h`
- `engine/src/Rendering/CameraRenderer.cpp`
- `engine/src/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder.cpp`
- `engine/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp`
这条链路的特征是:
- 在 scene 几何渲染完成后,由 GPU pass 正式叠加
- 有明确的 render request 输入
- 有独立 pass 边界
- 不依赖 ImGui draw list
### 2.2 非正规链路Editor World Overlay
当前绝大多数 editor 可视化不是 renderer pass而是
1. `ViewportHostService` 渲出 scene viewport 纹理
2. `RenderViewportPanelContent()` 在 ImGui 面板中显示这张纹理
3. `SceneViewPanel.cpp` 在这张纹理之上继续用 ImGui draw list 手搓 world overlay
关键文件:
- `editor/src/panels/ViewportPanelContent.h`
- `editor/src/panels/SceneViewPanel.cpp`
- `editor/src/Viewport/SceneViewportOverlayRenderer.cpp`
当前放在这条链上的内容包括:
- move gizmo
- rotate gizmo
- scale gizmo
- camera icon / light icon
- camera frustum
- directional light gizmo
- orientation gizmo
其中 `orientation gizmo` 本质上是固定在右上角的 HUD放在 ImGui 问题不大。真正失控的是那些锚定在世界空间中的 overlay。
---
## 3. 当前架构的核心问题
### 3.1 绘制职责放错层
`SceneViewPanel.cpp` 不应该知道:
- camera frustum 怎么构造几何
- directional light gizmo 怎么构造几何
- icon 如何转屏幕矩形
- 各种 gizmo 如何排序、如何遮挡
这些本质上都属于 viewport overlay 系统,不属于 panel UI。
### 3.2 命中和绘制分裂
当前很多交互是:
- 一套代码负责画
- 另一套代码负责 hover / click / drag
这会导致:
- 看见的和能点的不是同一个东西
- 改样式时经常忘改命中
- 优先级只能靠条件链硬拼
### 3.3 世界空间对象被当成 2D UI 处理
camera/light icon、frustum、light gizmo、transform gizmo 本质上都是世界空间 editor overlay。
但它们现在被塞进 ImGui draw list 后,就天然失去:
- 正规的渲染顺序语义
- 稳定的深度/遮挡策略
- 统一的 primitive 渲染方式
- GPU 级别的扩展能力
### 3.4 `SceneViewPanel.cpp` 已经过胖
当前它同时负责:
- tools/top bar UI
- tool mode 切换
- gizmo context 组装
- gizmo hover/click 仲裁
- icon hit test
- overlay 世界几何构建
- scene picking
- scene camera 输入
这已经不再是“面板”,而是一个巨型调度器加半个渲染系统。
### 3.5 视觉风格无法稳定收敛
Directional Light 这次暴露得最明显:
- 需求是“圆形底盘上的光线分布”
- 当前实现却是在 panel 里临时拼几根线
这不是调几个参数能根治的问题,而是底层 primitive 表达和系统边界不对。
---
## 4. 重构目标
这次重构的目标不是“顺手把几个 gizmo 再修漂亮一点”,而是把 Scene Viewport overlay 彻底正规化。
最终目标如下:
### 4.1 分离两类 overlay
#### A. HUD 类 overlay
固定在面板坐标系的内容继续留在 ImGui
- 顶部工具栏
- 左侧 tools 按钮
- 右上角 orientation gizmo
- 状态提示文字
#### B. World Anchored Overlay
锚定在世界空间里的 editor 可视化统一进入 renderer overlay pass
- move / rotate / scale gizmo
- camera / light scene icon
- camera frustum
- directional light gizmo
- 后续 collider bounds / helper shapes / volume gizmo
### 4.2 统一绘制数据与命中数据
所有可交互 gizmo handle 必须来自同一份 canonical data
- 画什么
- 颜色是什么
- 层级优先级是什么
- handle id 是什么
- 哪里可以点
都不能再分散在不同类里各算一套。
### 4.3 建立 renderer 级 editor overlay pass
目标顺序应为:
`Scene Geometry -> ObjectId -> Builtin Post Process(Grid/Outline) -> Editor Overlay Pass -> ImGui HUD`
也就是说editor 世界 overlay 必须成为正式 render stage而不是纹理上的二次手绘。
### 4.4 让 gizmo 类回归“控制器/求解器”角色
`Move / Rotate / Scale Gizmo` 类应主要负责:
- drag 状态机
- 轴向约束
- plane 约束
- 变换求解
- 交互反馈求解
不再继续兼任:
- 实际几何绘制器
- 实际命中主仲裁器
---
## 5. 推荐的目标架构
## 5.1 Render Request 层新增 `overlayPasses`
当前 `CameraRenderRequest` 里有:
- `preScenePasses`
- `postScenePasses`
- `builtinPostProcess`
但没有真正适合 editor world overlay 的最后一层。
建议新增:
- `overlayPasses`
执行顺序调整为:
1. `preScenePasses`
2. scene geometry
3. object id
4. `postScenePasses`
5. `builtinPostProcess`
6. `overlayPasses`
这样 world overlay 才能稳定压在 grid 和 outline 之上,再由 ImGui 负责最后的 HUD。
### 5.2 新建 `SceneViewportOverlayFrameData`
建议新增一个独立的 frame data 结构,承载这一帧 Scene Viewport 的 editor overlay 数据。
建议字段:
- `linePrimitives`
- `trianglePrimitives`
- `billboardSprites`
- `handleRecords`
- `renderLayer`
- `depthMode`
- `screenSpaceThickness`
其中:
- primitive 用于绘制
- handle record 用于命中
- 两者共享相同的 `handleId`
### 5.3 新建 `SceneViewportOverlayBuilder`
职责:
- 接收 scene overlay context
- 收集 selected objects
- 构建 camera frustum
- 构建 directional light 圆形底盘 gizmo
- 构建 scene icons
- 构建 transform gizmo handles
- 输出统一的 `SceneViewportOverlayFrameData`
建议位置:
- `editor/src/Viewport/SceneViewportOverlayBuilder.h`
- `editor/src/Viewport/SceneViewportOverlayBuilder.cpp`
### 5.4 新建 `SceneViewportOverlayHitTester`
职责:
- 基于 `SceneViewportOverlayFrameData::handleRecords` 做统一命中
- 输出唯一的 hovered handle
- 同一份数据同时服务 hover / click / drag begin
建议位置:
- `editor/src/Viewport/SceneViewportOverlayHitTester.h`
- `editor/src/Viewport/SceneViewportOverlayHitTester.cpp`
### 5.5 新建 `SceneViewportEditorOverlayPass`
职责:
- 读取 `SceneViewportOverlayFrameData`
- 用 GPU 绘制 line / fill / billboard
- 负责世界空间 editor overlay 的正式渲染
建议位置:
- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.h`
- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp`
### 5.6 `SceneViewPanel` 的目标职责
重构后 `SceneViewPanel` 只负责:
- 顶部栏和 tools UI
- tool mode / pivot / local-global 状态
- 输入汇总
- 与 viewport host service 交互
- 显示固定 HUD
不再负责:
- world overlay 几何构建
- icon 转屏幕
- frustum / light gizmo 线框拼装
- world overlay 主渲染
---
## 6. 模块职责重新划分
### 6.1 `SceneViewPanel`
保留:
- 面板 UI
- 工具模式切换
- 快捷键
- 鼠标/键盘输入汇总
- orientation gizmo
移出:
- world overlay primitive 构建
- scene icon world/screen 几何生成
- camera/light 线框绘制逻辑
### 6.2 `ViewportHostService`
新增职责:
- 组装 `SceneViewportOverlayFrameData`
- 把 overlay pass 接到 render request
保留职责:
- scene view camera
- viewport render target 管理
- scene render request 提交
- object id picking
### 6.3 Gizmo 求解器
`SceneViewportMoveGizmo / RotateGizmo / ScaleGizmo` 保留:
- 拖拽求解
- 激活态
- 交互反馈数据
逐步移除:
- 直接操作 ImGui draw data 的职责
- 各自内部封闭的 hit test 决策权
### 6.4 Overlay Pass
只负责 GPU 绘制,不处理业务判断。
输入必须已经是“可直接画”的 primitive 数据,避免 pass 内部再知道 camera/light/gizmo 业务语义。
---
## 7. 推荐执行阶段
这次重构不能一次性大爆炸改完,但必须每一步都朝最终架构收敛,不能做过渡性屎层。
### Phase 0冻结错误扩展方向
目标:
- 停止继续向 `SceneViewPanel.cpp` 添加新的 world overlay 绘制逻辑
- 停止继续扩写 `SceneViewportOverlayRenderer.cpp` 为 ImGui world renderer
产出:
- 文档确认
- 后续新增 world gizmo 一律走 overlay builder / pass 方向
### Phase 1打通 `overlayPasses` 通道
目标:
-`CameraRenderRequest` 新增 `overlayPasses`
-`CameraRenderer` 中调整执行顺序
-`ViewportHostService` 中接入 editor overlay pass sequence
产出:
- renderer 支持 scene 之后再画 editor overlay
- 这一步允许先画空 pass不做功能迁移
验收:
- 不影响现有 scene / grid / selection outline
- `overlayPasses` 具备独立初始化、执行、释放边界
### Phase 2迁移纯显示型 world overlay
优先迁移:
- camera frustum
- camera/light scene icon
- directional light gizmo
原因:
- 这些内容交互复杂度低
- 最容易先把 `SceneViewPanel.cpp` 中世界几何拼装代码减掉
Directional Light 本阶段的目标形态:
- 圆形底盘
- 光线分布在圆环或圆盘采样点上
- 明确的世界朝向
- 稳定屏幕线宽
验收:
- 这些 overlay 不再由 ImGui draw list 直接绘制
- `SceneViewPanel.cpp` 不再负责生成对应线框
### Phase 3迁移 Transform Gizmo 的绘制
目标:
- move / rotate / scale gizmo 的显示改为 overlay pass primitive
- gizmo 类转为 handle builder + drag solver
当前进度:
- transform gizmo 已开始转成 transient overlay frame data
- overlay pass 已可消费一批 screen-space triangle primitive 来绘制 gizmo 屏幕几何
- 但 transient gizmo overlay 仍然由 `SceneViewPanel` 在帧末提交,尚未完全收口到 host / builder 的 canonical 路径
说明:
- 这一阶段只迁移“怎么画”
- 可以暂时保留现有 drag 求解逻辑
验收:
- transform gizmo 已不依赖 ImGui world draw
- gizmo 视觉反馈仍然可用
### Phase 4统一命中系统
目标:
- 所有 world overlay 的 hover / click / drag begin 统一使用 `handleRecords`
- 彻底删除 panel 里的多套 hit test 拼接逻辑
当前进度:
- `scene icon` 已完成
- `transform gizmo` 的 hover / click-begin 已经开始接入统一 `handleRecords`
- `drag begin` 之后的求解与 active 状态仍然保留在 gizmo 类与 `SceneViewPanel` 现有链路中
涵盖对象:
- transform gizmo
- scene icon
- 后续 camera/light/world helper handles
验收:
- 命中结果只由一份 canonical handle 数据决定
- 不再依赖大量互相屏蔽的布尔条件
### Phase 5删除旧世界 overlay 路径
目标:
- 删除 `SceneViewPanel.cpp` 中遗留的 world overlay 构建逻辑
- 缩减 `SceneViewportOverlayRenderer.cpp` 到只保留 HUD 类渲染或直接拆分
最终保留:
- ImGui HUD
- renderer world overlay
最终移除:
- ImGui world overlay
---
## 8. 第一阶段建议改动范围
如果按最小风险起步,第一轮只做下面这些:
- `engine/include/XCEngine/Rendering/CameraRenderRequest.h`
- `engine/src/Rendering/CameraRenderer.cpp`
- `editor/src/Viewport/ViewportHostService.h`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h`
- 新增 `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.*`
第一轮不碰:
- move/rotate/scale 的求解逻辑
- Scene icon 的交互优先级逻辑
- SceneViewPanel 的大面积交互重写
这样可以先把 renderer 通道立住,再分批迁移业务内容。
---
## 9. 方案边界
这次重构明确不做的内容:
- 不顺手做 local mode 新行为
- 不顺手做 runtime 通用 debug draw 系统
- 不顺手把所有 editor widget 一次性改成 pass
- 不在本轮引入 render graph
这次只做一件事:
**把 Scene Viewport 中锚定世界空间的 editor overlay从 ImGui 手搓模式收口为正式 renderer overlay 系统。**
---
## 10. 风险与控制
### 10.1 风险:一次性迁移过大
控制方式:
- 先打通 pass 通道
- 再迁静态 overlay
- 最后迁 transform gizmo 与命中
### 10.2 风险:命中系统重写影响交互稳定性
控制方式:
- handle record 和旧逻辑短期并存
- 先让新系统服务 hover
- 再切 click / drag begin
### 10.3 风险D3D12-only pass 与后端扩展
控制方式:
- 第一阶段允许 editor overlay pass 先只支持 D3D12
- 但数据结构和 pass 边界必须 backend-neutral
### 10.4 风险:继续出现“画的是一个,点的是另一个”
控制方式:
- 明确规定 overlay primitive 和 hit proxy 必须同源
- 不能再允许绘制与命中分别各算一套几何
---
## 11. 最终验收标准
当以下条件全部满足时,说明收口完成:
- `SceneViewPanel.cpp` 不再承担 world overlay 几何构建职责
- world anchored editor overlay 全部进入 renderer pass
- transform gizmo / scene icon / camera frustum / light gizmo 使用统一 overlay frame data
- hover / click / drag begin 使用统一 handle record
- ImGui 只负责 HUD不再负责世界空间 gizmo 主绘制
- 新增一种 world gizmo 时,不需要再把世界转屏幕逻辑写回 panel
---
## 12. 对本次 Directional Light Gizmo 的直接指导
在该重构方案下Directional Light Gizmo 的正确实现方式应是:
- 它属于 `World Anchored Overlay`
- 它的几何由 `SceneViewportOverlayBuilder` 负责生成
- 它的线段/圆环由 `SceneViewportEditorOverlayPass` 负责绘制
- 它的样式使用“圆形底盘 + 圆周分布光线”,不再在 panel 中临时拼矩形列线
也就是说Directional Light Gizmo 不应该被当成一个局部修补任务继续留在 `SceneViewPanel.cpp`
---
## 13. 本文后的执行原则
在本方案审核通过之前:
- 允许修 bug
- 不建议继续扩写新的 ImGui world overlay 逻辑
在本方案审核通过之后:
- 新增 world overlay 功能,优先接入 overlay builder / overlay pass
- `SceneViewPanel.cpp` 只减不增

View File

@@ -1,506 +0,0 @@
# Shader 与 Material 系统下一阶段计划
日期:`2026-04-03`
## 1. 阶段结论
当前 Renderer 这一轮主线已经完成收口,但完成的是“基础运行时闭环”,不是“最终的 Unity 风格 shader/material 体系”。
已经完成并应视为当前基线的内容:
- `Shader` 运行时契约已具备 `properties / passes / resources / backend variants`
- builtin shader 资源已经外置,运行时不再依赖 C++ 内嵌 fallback
- `Material` 对 builtin forward 的基础属性解析已经优先走 shader semantic
- `BuiltinForwardPipeline` 已按 shader pass `resources` 合约生成 pipeline layout 与 descriptor binding
- `Shader` 已接入 `AssetDatabase / Library` 流程shader import 会生成 `main.xcshader`
- `ShaderLoader` 已支持加载 `.xcshader` artifact
- shader manifest 依赖与 `Material -> Shader` 依赖已进入资产追踪链
- `rendering_unit_tests`
- `rendering_integration_textured_quad_scene`
- `rendering_integration_backpack_lit_scene`
- `shader_tests`
- `material_tests`
三后端验证当前是稳定的:
- D3D12通过
- OpenGL通过
- Vulkan通过
但这仍然只是“Shader / Material Runtime 的第一层骨架”。
当前真正还没有完成的是:
- Unity 风格的 shader authoring 入口
- 正式的 material schema / instance contract
- 从 shader property layout 到 GPU material layout 的通用映射
- 从 builtin forward 扩展到更通用 pass 的正式执行模型
## 2. 旧计划归档说明
以下两份计划文档已经完成历史使命,应归入 `docs/used/`
- `Renderer模块设计与实现.md`
- `Renderer下一阶段_ShaderMaterial与Pass体系设计.md`
原因不是它们“写错了”,而是:
- 第一份解决的是 Renderer 模块从无到有的问题,当前骨架已经落地
- 第二份解决的是“为什么下一阶段先做 shader/material/pass contract而不是 render graph”的阶段判断这份判断已经部分兑现整体已过期
本文件接手它们之后的主线,只保留对当前 checkout 仍然有效的目标。
## 3. 当前真实问题
### 3.1 Shader 运行时有了,但 authoring 还没有正式化
现在的运行时已经能消费:
- shader property
- pass tag
- pass resource binding
- backend variant
但作者侧仍然不是最终形态。
当前还缺:
- 面向用户的 Unity 风格 `.shader` 语法入口
- import 阶段把 authoring 语法转换为 runtime contract 的正式流程
- shader 资产与 Library artifact 的稳定产物边界
### 3.2 Material 仍偏“资源容器”,还不是正式材质实例系统
当前 `Material` 已有:
- shader 引用
- render state
- property / texture 覆盖
- tag / queue
但它还缺少真正用于 Renderer 执行的正式约束:
- 基于 shader property schema 的类型验证
- property 默认值与 override 的统一解析
- per-pass material constant layout
- texture / sampler / buffer 到 pass resource 的正式映射
- renderer 侧可缓存、可失效、可复用的 material binding plan
### 3.3 现有 pass contract 仍偏 builtin-forward 视角
当前 forward 主链已经能跑,但完整的 pass contract 还没有正式化为可扩展系统。
后续至少要能稳定承接:
- `ForwardLit`
- `Unlit`
- `DepthOnly`
- `ShadowCaster`
- `ObjectId`
否则后面一旦开始做阴影、深度预通道、更多 editor/runtime helper pass就会重新退化回 pipeline 内部的条件分支拼装。
### 3.4 三后端问题的本质不是“语法不同”,而是“资产如何统一”
当前真正要解决的,不是简单回答“到底用 GLSL 还是 HLSL”而是明确三层边界
1. 对外 authoring 语法是什么
2. import 后的内部运行时资产是什么
3. 每个 backend 最终执行的 variant 是什么
这三层不分开,后面一定会把 authoring、runtime、backend 编译链搅在一起。
## 4. 下一阶段的目标
下一阶段只做一件事:把 `Shader``Material` 从“能支撑当前 builtin forward 的运行时拼装”升级为“能长期承接 Unity 风格渲染架构的正式系统”。
### 4.1 对外 authoring 语法目标:严格向 Unity 对齐
最终对外公开的 shader 语法目标,必须与 Unity 的使用方式保持一致。
目标形态应当是:
- 单个 `.shader` 文件作为逻辑 shader 入口
- `Shader / Properties / SubShader / Pass` 的层级结构
- pass 内通过 `HLSLPROGRAM ... ENDHLSL` 或等价块组织代码
- 通过 `#pragma vertex` / `#pragma fragment` 指定 stage 入口
也就是说:
- 对外 authoring 视角应当是“Unity 风格的一体化 shader 文件”
- 不是要求作者直接去维护一堆 runtime JSON manifest
- 也不是让上层逻辑直接感知 D3D12/OpenGL/Vulkan 各自的底层差异
### 4.2 对内 runtime 资产目标:继续保留 contract 模型
虽然 authoring 目标要严格向 Unity 靠拢,但 runtime 不应直接拿 authoring AST 当执行数据。
运行时仍应落到清晰的 contract
```text
Shader Asset
-> Properties
-> Passes
-> Tags
-> Resource Bindings
-> Backend Variants
```
原因很直接:
- Renderer 需要稳定、扁平、可缓存的数据结构
- 三后端最终执行的仍然是 backend variant
- material schema 与 pass binding 都需要基于 import 结果,而不是原始文本
结论是:
- 外部写法要像 Unity
- 内部执行模型继续使用现在这套 runtime contract并进一步完善
### 4.3 Material 目标:从资源对象升级为正式材质实例
Material 下一阶段的核心不是“多支持几个 SetFloat”而是建立正式实例语义。
Material 至少要明确:
- 引用哪个 shader
- 使用哪个 pass 或 pass 策略
- 每个 property 的默认值、覆盖值、类型与序列化规则
- 每个 pass 对应的 material constant block 如何布局
- 每个 texture / sampler / buffer 如何映射到 pass resource
Renderer 侧则必须能把这些内容稳定编译成:
- material binding key
- material constant payload
- descriptor update plan
- pipeline compatibility key
## 5. 推荐架构
### 5.1 分成三层,不混写
推荐明确拆成三层:
```text
Unity-like Shader Authoring (.shader)
-> Shader Importer
-> Runtime Shader Contract
-> Backend Variants / Material Binding Plan
```
三层职责分别是:
- authoring 层:给人写、给 editor 看
- importer 层:把 authoring 语法转成稳定运行时资产
- runtime 层:给 renderer 执行、缓存、绑定、测试
### 5.2 Shader 的 public contract 与 backend contract 分离
下一阶段不建议把“Unity 风格语法”直接等同于“单源码自动跨平台编译已完全成熟”。
更务实的路线是:
- public contract 先统一成 Unity 风格 `.shader`
- importer 先产出统一 runtime contract
- backend variant 暂时仍允许按 backend 持有各自的编译输入或中间产物
这意味着:
- 作者看到的是统一 shader
- Renderer 消费的是统一 runtime contract
- backend 最终执行的仍然可以是不同 variant
这个分层既不违背 Unity 风格目标,也不会过早把工程拖进复杂的全平台 shader 编译链泥潭。
### 5.3 Vertex / Fragment 的外部写法按 Unity 组织,内部可拆分
对外语义上vertex / fragment 应当属于同一个 pass。
也就是说public authoring 角度要符合 Unity
- 一个 shader
- 一个或多个 subshader
- 一个或多个 pass
- 每个 pass 里通过 pragma 指定 vertex / fragment
但内部 import/runtime 完全可以把它们拆成:
- pass descriptor
- vertex stage variant
- fragment stage variant
外部合一,内部拆开,这是最稳妥的做法。
## 6. 下一阶段实施顺序
### 阶段 D0先打通 Shader Import / Artifact 基础设施(已完成)
这是当前 checkout 在 `2026-04-03` 已经完成的新增里程碑。
完成内容:
- `ShaderImporter` 已接管 `.shader / .hlsl / .glsl / .vert / .frag / .geom / .comp`
- shader import 结果会写入 `Library/Artifacts/.../main.xcshader`
- `ShaderLoader` 已支持直接读取 `.xcshader`
- shader manifest 中声明的 stage 源文件会进入依赖追踪
- `Material` 对引用 shader 的直接依赖也已进入依赖追踪
- `shader_tests``material_tests` 已覆盖 shader artifact 生成、加载与重导场景
这一步的意义不是“最终方案已完成”,而是先把 shader 纳入和 texture/material/mesh 一致的资产闭环。
没有这一步,后续不管做 Unity 风格 frontend还是做 material schema都会一直建立在“运行时临时解析源码”的不稳定基础上。
### 阶段 0当前基线确认
这部分已经完成,不再作为待办:
- runtime shader contract 已建立
- builtin forward 已按 pass resources 驱动
- 三后端渲染回归通过
### 阶段 A建立 Unity 风格 Shader Authoring Frontend
目标:
- 新增 Unity 风格 `.shader` authoring 入口
- importer 能解析最小闭环子集
第一批建议支持的子集:
- `Shader`
- `Properties`
- `SubShader`
- `Tags`
- `Pass`
- `HLSLPROGRAM / ENDHLSL`
- `#pragma vertex`
- `#pragma fragment`
交付标准:
- builtin `forward-lit`
- builtin `unlit`
- builtin `object-id`
- builtin `infinite-grid`
至少迁移其中一条主线并成功跑通三后端测试。
### 阶段 B建立正式的 Material Schema 与 Instance Contract
目标:
- shader importer 输出可供 material 消费的 property schema
- material 对 property override 做类型校验与默认值回退
- 为 pass 生成 material constant layout 与 resource mapping
交付标准:
- material 不再只靠 builtin alias 名字兜底
- shader property semantic 变成正式主路径,而不是兼容性补丁
- renderer 能从 shader schema 生成 material binding payload
当前建议把这一阶段作为下一步主线。
原因:
- shader artifact 与依赖追踪已经到位shader 现在可以作为稳定 schema 来源
- material 仍然缺少基于 shader property 的正式类型校验、默认值回退和资源映射
- renderer 目前虽然能消费 pass resources但 material binding 仍偏 builtin-forward 特判
当前进展(`2026-04-03`
- 已完成shader schema 驱动的 property 类型校验与默认值回退
- 已完成source `.material``properties` authoring 入口
- 已完成material constant layout runtime contract
- `Material` 现在会生成正式的 constant layout 元数据
- layout 字段包含 `name / type / offset / size / alignedSize`
- renderer 读取的已不再只是裸字节 payload而是 `layout + payload` 组合
- 已完成builtin pass resource mapping / material binding plan runtime contract
- `RenderMaterialUtility` 现在统一提供 `PassResourceBindingLocation / BuiltinPassResourceBindingPlan`
- 显式 shader pass `resources` 与 legacy builtin forward fallback 已走同一套解析与校验路径
- `BuiltinForwardPipeline` 已改为消费通用 binding plan而不是继续内联 forward 特判绑定逻辑
- 已验证:`rendering_unit_tests` 57/57`material_tests` 51/51
- 下一步:把这套 pass binding plan 继续推到 `Unlit / ObjectId` 等 pass收敛成真正共享的 shader/material 执行边界
### 阶段 C把 Pass Binding 扩展为正式材质执行链路
目标:
- 不只是 builtin forward 能吃 pass resources
- `Unlit``ObjectId` 也逐步切到同一套 shader/material contract
- pipeline state key 与 descriptor binding plan 从“按功能写逻辑”升级到“按 shader pass contract 解析”
交付标准:
- 至少 `ForwardLit + Unlit + ObjectId` 共用同一套 shader/material 执行边界
- 新增 pass 不再默认要求先写一套新的硬编码 binding 路径
当前进展(`2026-04-04`
- 已完成builtin `ObjectId` pass 接入通用 pass binding plan
- builtin object-id shader 已显式声明 `PerObject` 资源合约
- `BuiltinObjectIdPass` 已改为消费通用 binding plan不再硬编码 `set0/binding0` 常量布局
- 显式 shader `resources` 与 legacy object-id fallback 现在走同一套解析与校验路径
- 已完成builtin `Unlit` shader / pipeline 主线接入共享执行边界
- 新增 builtin `unlit` shader 资产与 `BuiltinResources` 入口
- `BuiltinForwardPipeline` 现在会在 `ForwardLit + Unlit` 之间按 material/shader metadata 解析目标 pass
- `Unlit``ForwardLit` 现在共用同一套 input layout、material schema、binding plan 与 descriptor 组装路径
- 已完成:抽出 `ForwardLit / Unlit / ObjectId` 共用的 pass layout / descriptor set 组装骨架
- `RenderMaterialUtility` 现在统一提供 `BuiltinPassSetLayoutMetadata``TryBuildBuiltinPassSetLayouts(...)`
- `BuiltinForwardPipeline``BuiltinObjectIdPass` 现在都复用同一套 `binding plan -> set layout -> pipeline layout` 组装路径
- forward 侧只保留 set0 compatibility fallback 与 draw/update 逻辑object-id 侧只保留自身约束校验与 per-object 常量写入
- 已完成builtin `DepthOnly / ShadowCaster` shader 与独立 pass skeleton 落地
- 新增 builtin `depth-only``shadow-caster` shader 资产,以及 `BuiltinResources` 对应入口
- 新增 `BuiltinDepthOnlyPass / BuiltinShadowCasterPass`,作为独立 `RenderPass` 复用同一套 shared pass-layout skeleton
- 两个 pass 当前先收敛到 `PerObject` 常量路径opaque 物体走 builtin fallback`ShadowCaster` 额外尊重 `MeshRenderer.castShadows`
- 这一步解决的是“pass contract 与执行骨架缺失”,还没有把 shadow map / light-space request flow 一次性做完
- 已完成:`CameraRenderRequest + CameraRenderer` 正式接入 `DepthOnly / ShadowCaster` request 流程
- 新增 `depthOnly / shadowCaster` request支持独立 surface、clear flags / clear color以及可选 `RenderCameraData` override
- `CameraRenderer` 现在按 `ShadowCaster -> DepthOnly -> Forward -> ObjectId` 的顺序执行这些请求,`BuiltinDepthStylePassBase` 也会按 request clear flags 清理目标
- 已验证:`rendering_unit_tests` 73/73
- 已验证:`rendering_integration_textured_quad_scene` 3/3D3D12 / OpenGL / Vulkan
- 已验证:`rendering_integration_unlit_scene` 3/3D3D12 / OpenGL / Vulkan
- 已验证:`rendering_integration_object_id_scene` 3/3D3D12 / OpenGL / Vulkan
- 下一步:如果继续沿 renderer 主线收口,优先补 `DepthOnly / ShadowCaster` 的真实 cross-backend integration coverage并决定是否把 shadow-map 结果继续接回 forward lighting 消费链如果先控制范围request 流程这一块已经收口
### 阶段 D扩展 AssetDatabase / Library Artifact 能力
目标:
- 在已完成的 shader artifact 基础上,继续扩展 import 产物边界
- backend variant 的编译输入、中间产物或缓存策略进入 `Library/Artifacts`
- 为后续 Unity 风格 `.shader` frontend 预留稳定 importer 输出层
交付标准:
- 资源改动能够稳定触发 shader/material 重新导入
- editor/runtime 读取的是 import 后资产,不是源码临时解析
- shader importer 不再只服务当前 JSON manifest 兼容路径,也能承接下一步 authoring frontend
### 阶段 E测试与回归收口
目标:
- 给 shader importer、material schema、binding plan 增加 unit tests
- 对 forward/unlit/object-id 增加 integration coverage
- 保持 D3D12 / OpenGL / Vulkan 一致回归
最低验证集:
- `shader_tests`
- `material_tests`
- `rendering_unit_tests`
- `rendering_integration_textured_quad_scene`
- `rendering_integration_backpack_lit_scene`
必要时新增:
- `rendering_integration_unlit_scene`
- `rendering_integration_object_id_scene`
当前进展(`2026-04-04`
- 已完成:`rendering_integration_unlit_scene`
- 显式使用 builtin `unlit` shader + `Unlit` pass覆盖 `BuiltinForwardPipeline``ForwardLit / Unlit` pass 选择路径
- 复用 `textured_quad_scene` 构图做最小差异回归,验证 shader/material 新 contract 不改变既有画面输出
- 已验证:`rendering_integration_unlit_scene`
- D3D12通过
- OpenGL通过
- Vulkan通过
- 已完成:`rendering_integration_object_id_scene`
- 新增独立的 object-id integration scene直接验证 object-id 输出采样点,而不是再维护一张新的大尺寸 GT 图片
- 覆盖 `CameraRenderer + BuiltinObjectIdPass` 路径,以及 `Forward -> ObjectId -> Copy/Screenshot` 的跨 pass 回归
- 修正了 `BuiltinObjectIdPass` 在 Vulkan 下的顶点输入步长问题,并让 object-id pass 自己清理/写入 depth避免依赖前一 pass 的 depth 复用状态
- 已验证:`rendering_integration_object_id_scene`
- D3D12通过
- OpenGL通过
- Vulkan通过
- 阶段 E 当前状态:`unlit_scene``object_id_scene` 均已完成并通过三后端验证,本阶段可以收口
## 7. 当前阶段明确不做
下一阶段不应把范围扩散到下面这些方向:
- render graph
- shader graph
- 全平台单源码自动转译一次性做完
- 完整 SRP scripting API
- 大规模后处理框架
这些都依赖 shader/material 正式体系先稳定下来。
## 8. 成功标准
这个阶段完成时,应该满足下面几个判断:
- 作者侧已经可以写 Unity 风格的 `.shader`
- runtime 已不再依赖手写 JSON manifest 才能描述 pass contract
- material 能基于 shader schema 做正式绑定,而不是 builtin 特判兜底
- 至少 `ForwardLit / Unlit / ObjectId` 三类 pass 共用统一 shader/material 执行边界
- 三后端回归测试仍稳定通过
## 9. 一句话总结
下一阶段不是继续给 builtin forward 打补丁,而是把 `Shader``Material` 正式提升为 Unity 风格渲染架构中的稳定中层资产与执行契约。
## 10. 快速收口策略(`2026-04-04`
目标收窄为只处理 `shader / material` 核心主线不继续扩散到完整阴影功能、render graph、shader graph 或 editor 外围能力。
按下面顺序收口:
1. 先完成 Unity-like `.shader` authoring MVP importer
- 允许最小子集:`Shader / Properties / SubShader / Tags / Pass / HLSLPROGRAM / #pragma vertex / #pragma fragment`
- importer 输出继续复用当前 runtime shader contract`properties / passes / resources / backend variants`
- 这一阶段不追求完整 Unity ShaderLab只做 builtin 与主线材质系统需要的最小闭环
2. 再迁移 builtin shader 到新 authoring 入口
- 优先 `ForwardLit / Unlit / ObjectId / DepthOnly / ShadowCaster`
- 要求 importer 产出的 runtime contract 与当前 renderer 消费路径保持一致
3. 然后收紧 material 主路径
- 以 imported shader schema 为主路径完成 property 类型校验、默认值回退、constant layout 与 resource mapping
- builtin alias / canonical-name fallback 只保留兼容兜底,不再作为主执行路径
4. 最后做最小回归集收口
- `shader_tests`
- `material_tests`
- `rendering_unit_tests`
- 必要的 rendering integration smoke
当前进展(`2026-04-04`
- 已完成Step 1 `Unity-like .shader authoring MVP importer`
- `ShaderLoader` 新增 Unity-like `.shader` authoring 识别与解析入口,但保留现有 JSON manifest `.shader` 兼容路径不动
- importer 继续落到现有 runtime shader contract`properties / passes / resources / backend variants`
- `CollectSourceDependencies` 已覆盖新 authoring 路径,`AssetDatabase` 会继续追踪各 backend stage 文件依赖并参与重导入
- 已完成Step 2 `builtin shader 迁到新 authoring 入口`
- `forward-lit / unlit / object-id / depth-only / shadow-caster` 五个 builtin `.shader` 入口已全部切到 Unity-like authoring
- stage 源文件、builtin shader 路径与 renderer 消费 contract 保持不变,迁移只发生在 authoring 入口层
- 补充 `DepthOnly / ShadowCaster` builtin shader loader 覆盖,确保五类 builtin pass 都经过新 authoring 路径验证
- 已完成Step 3 `material 主路径收紧到 imported shader schema`
- `MaterialLoader` 在存在 shader schema 时,`properties / textures` 中的旧 key 会先解析到 shader property semantic再落到 shader 正式属性名
- 旧的 `baseColor / baseColorTexture` 等兼容 key 仍可导入,但只作为兼容输入存在,不再直接污染 material 主执行路径
- 当 material 绑定 shader schema 后,未知 texture binding 现在会被显式拒绝,不再静默忽略
- 已验证:`shader_tests` 中新增 authoring 直载与 artifact/reimport 覆盖
- 已验证:`shader_tests` 31/31 通过builtin `ForwardLit / Unlit / ObjectId / DepthOnly / ShadowCaster` 全部通过加载与 backend variant 覆盖
- 已验证:`material_tests` 53/53 通过schema 驱动的 property/texture 映射与未知 binding 拒绝路径都已覆盖
- 已完成Step 4 `最小回归集收口`
- `rendering_unit_tests` 73/73 通过builtin pass contract 与 renderer 侧消费路径在当前重构后保持稳定
- 本轮最小回归集结果:`shader_tests` 31/31`material_tests` 53/53`rendering_unit_tests` 73/73
- 以当前目标范围看,`shader / material` 核心主线已经可以阶段性收口
- 补充验收(`2026-04-04`
- 补跑 integration smoke 时发现并修复了一处真实回归Unity-like `.shader` authoring 将 HLSL 的 `MainVS / MainPS` 入口错误套到了 GLSL/Vulkan variant上层表现为 Vulkan `BuiltinForwardPipeline` 建 pipeline 失败
- 修复后补跑 `rendering_integration_textured_quad_scene``rendering_integration_unlit_scene``rendering_integration_object_id_scene`,三者在 `D3D12 / OpenGL / Vulkan` 下均通过
- 额外补跑 `rendering_integration_backpack_lit_scene`暴露出一条相邻但不同范围的问题direct `MeshLoader` 导入的 mesh/material/texture 生命周期路径存在异常退出/不干净收尾,已超出本次 `shader / material contract` 收口范围
- 因此本计划按“核心主线已收口,附带一条相邻 residual risk”归档若后续继续补强优先单独开一轮处理 `MeshLoader + imported subresource lifetime`
- 下一步:从当前目标范围出发,本计划可归档;后续如继续推进,请以新计划承接 `backpack_lit_scene` 这条相邻问题
当前阶段明确不做:
- 完整阴影贴图消费链
- render graph
- shader graph
- 完整 Unity ShaderLab 全语法
- 与 shader/material 主线无关的 editor/ui 扩展

View File

@@ -1,140 +0,0 @@
# XCUI Input / Focus / Shortcut Subplan 完成归档
日期:`2026-04-04`
## 1. 归档结论
`Subplan-04` 在其原始定义边界内已经完成,可以归档。
这里的“完成”指的是:
- 已建立 XCUI 输入事件基础模型
- 已建立焦点、激活路径、指针捕获路径三套状态管理
- 已建立 capture / target / bubble 三阶段输入路由
- 已建立 shortcut scope 与命令匹配机制
- 已建立统一的 `UIInputDispatcher`
- 已补齐最小单元测试并完成通过验证
这里的“完成”不包括:
- Win32 原生消息采集
- ImGui / Editor 侧的输入桥接
- 文本输入控件级别的 IME 细节
这些内容本来就不在 `Subplan-04` 的负责边界里,后续应由 `Subplan-05``Subplan-08``Subplan-09` 等继续接手。
## 2. 本次落地内容
### 2.1 输入事件模型
已扩展 `UIInputEvent`
- `PointerEnter`
- `PointerLeave`
- `pointerId`
- `timestampNanoseconds`
- `repeat`
- `synthetic`
相关文件:
- `engine/include/XCEngine/UI/Types.h`
### 2.2 焦点与路径模型
已补齐:
- `UIElementId`
- `UIInputPath`
- `UIFocusController`
- `UIFocusChange`
相关文件:
- `engine/include/XCEngine/UI/Input/UIInputPath.h`
- `engine/include/XCEngine/UI/Input/UIFocusController.h`
- `engine/src/UI/Input/UIInputPath.cpp`
- `engine/src/UI/Input/UIFocusController.cpp`
### 2.3 Shortcut 系统
已补齐:
- `UIShortcutScope`
- `UIShortcutChord`
- `UIShortcutBinding`
- `UIShortcutRegistry`
- shortcut scope 优先级匹配规则
相关文件:
- `engine/include/XCEngine/UI/Input/UIShortcutRegistry.h`
- `engine/src/UI/Input/UIShortcutRegistry.cpp`
### 2.4 输入路由与统一分发器
已补齐:
- `UIInputRouter`
- `UIInputRoutingPlan`
- `UIInputRoutingStep`
- `UIInputDispatcher`
相关文件:
- `engine/include/XCEngine/UI/Input/UIInputRouter.h`
- `engine/include/XCEngine/UI/Input/UIInputDispatcher.h`
- `engine/src/UI/Input/UIInputRouter.cpp`
- `engine/src/UI/Input/UIInputDispatcher.cpp`
## 3. 测试与验证
新增测试:
- `tests/Input/test_xcui_input_dispatcher.cpp`
已完成验证:
- `cmake --build build --config Debug --target input_tests`
- `ctest -C Debug -R "XCUI.*" --output-on-failure`
验证结果:
- `6 / 6` 通过
覆盖点包括:
- focus path 切换
- capture path 优先级
- keyboard routed path 顺序
- shortcut scope 匹配优先级
- pointer down / pointer up 对 active path 的影响
- shortcut 命中后优先消费、跳过普通 routing
## 4. 对后续 subplan 的可复用输出
当前已经可以被后续直接依赖的稳定入口:
- `XCEngine::UI::UIInputPath`
- `XCEngine::UI::UIFocusController`
- `XCEngine::UI::UIShortcutRegistry`
- `XCEngine::UI::UIInputRouter`
- `XCEngine::UI::UIInputDispatcher`
后续建议对接方式:
- `Subplan-05`:负责把 ImGui/平台输入桥接进这套 dispatcher
- `Subplan-08`:负责把 menu / dock / panel shell 的 shortcut scope 接进 registry
- `Subplan-09`:负责把 viewport shell 的 pointer / focus / capture 接进 routing
## 5. 原 subplan 文件
原始 subplan 文件保留在:
- `docs/plan/xcui-subplans/Subplan-04_XCUI-Input-Focus-Shortcut.md`
其状态应视为:
- 已完成
- 已归档
- 不再作为活跃开发计划继续扩写

View File

@@ -1,93 +0,0 @@
# XCUI Subplan 01Core Tree / State / Invalidation
归档日期:
- `2026-04-04`
状态:
- 已完成
本次实际完成内容:
- 新增 XCUI core 基础契约:`UIElementId``UIDirtyFlags``IUIViewModel``RevisionedViewModelBase`
- 新增 retained-mode build 层:`UIBuildElementDesc``UIBuildList``UIBuildContext`
- 新增 retained-mode tree 层:`UIElementTree``UIElementNode``UIElementTreeRebuildResult`
- 新增统一入口:`UIContext`
- 实现最小闭环tree rebuild、dirty flag 标记、layout dirty 向祖先传播、dirty root 收集
- 新增并验证 UI core 测试tree 创建、unchanged rebuild、local state invalidation、view model invalidation、structure change、未闭合 build scope 失败
本次涉及文件:
- `engine/include/XCEngine/UI/Types.h`
- `engine/include/XCEngine/UI/Core/UIInvalidation.h`
- `engine/include/XCEngine/UI/Core/UIViewModel.h`
- `engine/include/XCEngine/UI/Core/UIBuildContext.h`
- `engine/include/XCEngine/UI/Core/UIElementTree.h`
- `engine/include/XCEngine/UI/Core/UIContext.h`
- `engine/src/UI/Core/UIBuildContext.cpp`
- `engine/src/UI/Core/UIElementTree.cpp`
- `tests/core/UI/CMakeLists.txt`
- `tests/core/UI/test_ui_core.cpp`
验证结果:
- `cmake --build . --config Debug --target core_ui_tests`
- `ctest -C Debug --output-on-failure -R UICoreTest --test-dir .`
- 结果:`6/6` 通过
当前结论:
- `Subplan 01` 的最小 retained-mode core 已经可用
- 后续 `Subplan 03/05/06/07/08/09` 可以基于这套 core 继续推进
原始 subplan 内容归档如下:
# Subplan 01XCUI Core Tree / State / Invalidation
目标:
- 搭出 XCUI 的 retained-mode 核心骨架。
- 明确 `ElementTree``NodeId``View``ViewModel``dirty flag``rebuild``lifecycle` 的最小闭环。
负责人边界:
- 负责 `engine/include/XCEngine/UI/``engine/src/UI/Core/` 的核心树模型。
- 不负责具体布局算法。
- 不负责 ImGui 适配绘制。
建议目录:
- `engine/include/XCEngine/UI/Core/`
- `engine/src/UI/Core/`
- `tests` 中对应 XCUI core 测试文件
前置依赖:
- 依赖主线完成 `Phase 0` 的基础类型和 UI 生命周期边界清理。
现在就可以先做的内容:
- 设计 `UIElementId` / `UIElement` / `UIContext` / `UIBuildContext`
- 设计 dirty 标记与增量重建规则
- 设计 ViewModel 读写边界和 command 回调入口
- 写最小 tree rebuild 测试
明确不做:
- 不接入 `.xcui` 文件
- 不接入 editor 面板
- 不写具体 widget 大库
交付物:
- XCUI core 基础类与生命周期定义
- tree rebuild / invalidation / state propagation 单元测试
- 一个最小 demo代码构建 UI tree 并触发一次增量更新
验收标准:
- 可以构建一棵稳定的 UI tree
- 局部状态变化时只标脏必要节点
- 重建逻辑与布局/渲染解耦
- 其他 subplan 可以基于该模块定义控件树和状态更新

View File

@@ -1,50 +0,0 @@
# XCUI Subplan 02Layout Engine 完成归档
归档日期:
- `2026-04-04`
原始来源:
- [../XCUI完整架构设计与执行计划.md](../XCUI完整架构设计与执行计划.md)
本次完成范围:
- 落地 XCUI 纯算法布局基础类型:
- `UILayoutLength`
- `UILayoutConstraints`
- `UILayoutThickness`
- `UILayoutItem`
- `UIStackLayoutOptions`
- `UIOverlayLayoutOptions`
- 落地 measure / arrange 双阶段布局算法
- 实现 `Horizontal Stack` / `Vertical Stack` / `Overlay` 三类 MVP 容器
- 支持 `px / content / stretch`
- 支持 `padding / spacing / margin / min / max / alignment`
- 建立独立 `ui_tests` 测试目标并通过验证
实际代码落点:
- [engine/include/XCEngine/UI/Types.h](D:/Xuanchi/Main/XCEngine/engine/include/XCEngine/UI/Types.h)
- [engine/include/XCEngine/UI/Layout/LayoutTypes.h](D:/Xuanchi/Main/XCEngine/engine/include/XCEngine/UI/Layout/LayoutTypes.h)
- [engine/include/XCEngine/UI/Layout/LayoutEngine.h](D:/Xuanchi/Main/XCEngine/engine/include/XCEngine/UI/Layout/LayoutEngine.h)
- [tests/Core/Math/CMakeLists.txt](D:/Xuanchi/Main/XCEngine/tests/Core/Math/CMakeLists.txt)
- [tests/Core/Math/test_ui_layout.cpp](D:/Xuanchi/Main/XCEngine/tests/Core/Math/test_ui_layout.cpp)
验证结果:
- `cmake --build . --config Debug --target math_tests -- /m:1`
- `ctest -C Debug --test-dir D:\\Xuanchi\\Main\\XCEngine\\build\\tests\\Core\\Math --output-on-failure -R UI_Layout`
- 结果:`5/5 UI_Layout tests passed`
与原子计划相比,当前仍未覆盖:
- `Scroll` 容器
- 更复杂的主轴分布策略
- 与 XCUI tree/state 的正式对接
- 文本测量与真实控件树集成
建议后续承接:
-`Subplan-01` 提供 tree / node / invalidation 契约后,把当前布局算法接入正式 UI tree
- 后续再补 `Scroll`、更完整容器族、文本测量桥接

View File

@@ -1,83 +0,0 @@
# XCUI Subplan 05: ImGui Transition Backend
归档日期:
- `2026-04-04`
状态:
- 已完成
本次实际完成内容:
- 新增 XCUI 绘制数据契约:`UIColor``UIDrawCommandType``UIDrawCommand``UIDrawList``UIDrawData`
- 新增 `ImGuiTransitionBackend` 过渡后端,支持 `FilledRect``RectOutline``Text``Image``PushClipRect``PopClipRect`
- 新增最小 editor 接入样例 `XCUIDemoPanel`,用于在现有编辑器壳层中演示 XCUI draw data 到 ImGui draw call 的过渡链路
- 将 demo panel 接入 editor workspace并补齐 editor/tests 构建入口
- 新增 Subplan-05 配套测试,覆盖 draw data 聚合与 backend flush 行为
本次涉及文件:
- `editor/CMakeLists.txt`
- `editor/src/Core/EditorWorkspace.h`
- `editor/src/XCUIBackend/ImGuiTransitionBackend.h`
- `editor/src/panels/XCUIDemoPanel.cpp`
- `editor/src/panels/XCUIDemoPanel.h`
- `engine/include/XCEngine/UI/DrawData.h`
- `tests/editor/CMakeLists.txt`
- `tests/editor/test_xcui_draw_data.cpp`
- `tests/editor/test_xcui_imgui_transition_backend.cpp`
验证结果:
- `cmake --build . --config Debug --target editor_tests -- /m:1 /p:CL_MPCount=1`
- `ctest -C Debug -R "XCUIDrawDataTest|XCUIImGuiTransitionBackendTest" --output-on-failure`
- 结果:`4/4` 通过
- `cmake --build . --config Debug --target XCEditor -- /m:1 /p:UseMultiToolTask=false /p:CL_MPCount=1`
- 结果:通过
提交记录:
- `75ded6f` `Add XCUI ImGui transition backend MVP`
当前结论:
- `Subplan-05` 的最小过渡后端已经可用,可以作为 XCUI 在 editor 中落地的第一层渲染适配桥
- XCUI 逻辑层仍然不直接依赖 ImGui APIImGui 仅存在于过渡 backend 和 editor 接入层
- 后续 `Subplan-08``Subplan-09` 可以直接基于这套 draw data 和 transition backend 继续推进
原始 subplan 内容归档如下:
# Subplan 05XCUI ImGui Transition Backend
目标:
- 在过渡阶段,让 ImGui 只承担宿主窗口和 draw submission 容器的职责
- 由 XCUI 自己生成 draw list再交给 ImGui backend 落屏
负责人边界:
- 负责 `editor/src/XCUIBackend/` 或等价新目录
- 负责 XCUI draw primitive 到 ImGui draw call 的映射
- 不负责 XCUI tree、布局、样式的内部规则
建议目录:
- `editor/src/XCUIBackend/`
- `editor/src/UI/` 中与 XCUI backend 直接相关的桥接代码
- `tests/Editor` 中 backend 相关测试
前置依赖:
- 需要 `Subplan-01` 给出稳定 draw data 和 frame submission 契约
- 需要 `Subplan-03` 提供样式查询结果
现在就可以先做的内容:
- 定义 `UIDrawList` / `UIDrawCommand` / `UIDrawText` / `UIDrawImage` / `UIDrawClip`
- 先做矩形、边框、文字、图片四类 primitive
- 设计 frame begin / submit / end 的 adapter 流程
- 写一个最小 demo panel用 XCUI draw list 通过 ImGui 显示
明确不做:
- 不做 RHI native backend
- 不做 docking 逻辑
交付物:
- XCUI 到 ImGui 的过渡 backend
- primitive 转换测试或快照测试
- 最小 editor 接入样例
验收标准:
- XCUI 逻辑层不直接依赖 ImGui API
- ImGui 只出现在 backend 适配层
- 可以渲染基础控件和文本

View File

@@ -1,38 +0,0 @@
# XCUI Parallel Subplans
基于 [XCUI完整架构设计与执行计划](../XCUI完整架构设计与执行计划.md) 的并行拆分版本。
当前建议:
- `Phase 0` 由主线继续推进,目标是把 ImGui 从 `engine/editor` 公共边界剥离出来。
- 其他人不要再去碰 `Phase 0` 正在修改的边界文件,优先认领下面的独立 subplan。
- 每个人只领一个 subplan按“自己负责的目录”做增量开发避免跨 subplan 修改核心契约。
推荐并行顺序:
- 可以立即开始:`03` `06`
- 建议在 Core/Backend 契约初步稳定后启动:`07` `08` `09`
已完成归档:
- `Subplan-01`:已于 `2026-04-04` 归档到 [../used/XCUI_Subplan-01_Core_Tree_State_完成归档_2026-04-04.md](../used/XCUI_Subplan-01_Core_Tree_State_完成归档_2026-04-04.md)
- `Subplan-02`:已于 `2026-04-04` 归档到 [../used/XCUI_Subplan-02_LayoutEngine_完成归档_2026-04-04.md](../used/XCUI_Subplan-02_LayoutEngine_完成归档_2026-04-04.md)
- `Subplan-04`:已于 `2026-04-04` 归档到 [../used/XCUI_Input_Focus_Shortcut_Subplan_完成归档_2026-04-04.md](../used/XCUI_Input_Focus_Shortcut_Subplan_完成归档_2026-04-04.md)
- `Subplan-05`:已于 `2026-04-04` 归档到 [../used/XCUI_Subplan-05_ImGui_Transition_Backend_完成归档_2026-04-04.md](../used/XCUI_Subplan-05_ImGui_Transition_Backend_完成归档_2026-04-04.md)
统一协作约束:
- 共享契约文件尽量只由主线或对应 owner 修改。
- 新模块优先放到新目录,不要把 XCUI 新逻辑继续塞进旧的 ImGui helper。
- 每个 subplan 都要自带最小测试或样例,不接受只落抽象不落验证。
- 每个 subplan 完成后,至少产出一个可被其他 subplan 直接依赖的稳定入口。
共享高风险边界:
- `engine/include/XCEngine/UI/`
- `engine/include/XCEngine/Core/Layer.h`
- `engine/include/XCEngine/Core/LayerStack.h`
- `editor/src/Application.cpp`
- `editor/src/Viewport/IViewportHostService.h`
Subplan 列表:
- `Subplan-03`XCUI Style / Theme / Token
- `Subplan-06`XCUI Markup / Import / Hot Reload
- `Subplan-07`XCUI Schema Inspector / PropertyGrid
- `Subplan-08`XCUI DockHost / Menu / Panel Shell
- `Subplan-09`XCUI ViewportSlot / Editor Integration

View File

@@ -1,46 +0,0 @@
# Subplan 03XCUI Style / Theme / Token
目标:
- 建立 XCUI 的样式模型、token 体系与主题覆盖规则。
- 让控件视觉不再散落在代码硬编码常量里。
负责人边界:
- 负责 `Style` / `Theme` / `Token` 数据模型和解析规则。
- 不负责 `.xcui` 导入器。
- 不负责最终 renderer 具体绘制。
建议目录:
- `engine/include/XCEngine/UI/Style/`
- `engine/src/UI/Style/`
- `tests` 中 style/theme 测试
前置依赖:
- 依赖 `Subplan 01` 的节点属性注入点。
现在就可以先做的内容:
- 设计 style 层级:默认样式、类型样式、命名样式、局部覆盖
- 设计 token颜色、圆角、边距、字号、线宽
- 设计主题切换与 token 查询接口
- 写冲突优先级测试
明确不做:
- 不做具体面板视觉重构
- 不做字体资源导入
交付物:
- 统一样式查询入口
- `.xctheme` 对应的数据结构
- 样式优先级与 token 解析测试
验收标准:
- 样式优先级可预测
- 主题替换不需要改控件逻辑
- 其他 subplan 能通过统一 API 获取视觉参数

View File

@@ -1,53 +0,0 @@
# Subplan 04XCUI Input / Focus / Shortcut
状态:
- 已于 `2026-04-04` 完成当前 subplan 定义边界内的实现。
- 已归档到:
[../used/XCUI_Input_Focus_Shortcut_Subplan_完成归档_2026-04-04.md](../used/XCUI_Input_Focus_Shortcut_Subplan_完成归档_2026-04-04.md)
目标:
- 建立 XCUI 的输入事件、焦点流转和快捷键分发模型。
- 让输入不再直接写死在 ImGui 调用点里。
负责人边界:
- 负责 `engine/src/UI/Input/`
- 负责 pointer / keyboard / focus / command dispatch 的抽象。
- 不负责平台消息采集本身。
建议目录:
- `engine/include/XCEngine/UI/Input/`
- `engine/src/UI/Input/`
- `tests` 中 input/focus 测试
前置依赖:
- 依赖 `Subplan 01` 的 tree 和 hit-test 基础契约。
- 需要和 `Subplan 05` 对齐 adapter 输入桥接接口。
现在就可以先做的内容:
- 设计 `UIInputEvent` 丰富版本
- 设计 focus path / active path / capture path
- 设计 shortcut scopeglobal / window / panel / widget
- 写 focus 切换和冒泡/捕获测试
明确不做:
- 不做 Win32 原生消息处理细节
- 不做具体文本输入 widget
交付物:
- XCUI 输入分发器
- 焦点管理器
- 快捷键绑定与分发机制
验收标准:
- 可以确定事件从哪里来、往哪里走、谁消费
- 焦点切换规则稳定可测
- 快捷键系统可与 editor shell 直接对接

View File

@@ -1,46 +0,0 @@
# Subplan 06XCUI Markup / Import / Hot Reload
目标:
-`.xcui` / `.xctheme` / `.xcschema` 拉进资源系统。
- 建立导入、编译产物、热重载、诊断输出的第一版链路。
负责人边界:
- 负责资源类型、导入器、artifact、诊断日志。
- 不负责 widget 运行时逻辑本身。
建议目录:
- `engine/include/XCEngine/Resources/UI/`
- `engine/src/Resources/UI/`
- `editor/src` 中与导入面板、诊断输出相关的接入口
前置依赖:
- 需要主计划中的资源类型命名拍板。
-`Subplan 03``Subplan 07` 协调格式字段。
现在就可以先做的内容:
- 定义三类资源描述结构
- 设计导入错误诊断格式
- 设计热重载触发和缓存失效策略
- 先做一个最小 parser可以把简单 `.xcui` 编成中间结构
明确不做:
- 不做完整 markup 语法大全
- 不做 inspector 的最终渲染
交付物:
- UI 资源类型定义
- 导入器与 artifact 结构
- 热重载与错误输出最小闭环
验收标准:
- UI 资源可被 ResourceManager 识别
- 导入失败时有可读诊断
- 改动文件后可触发重新加载