Compare commits

...

107 Commits

Author SHA1 Message Date
8bd375cd24 Add render graph texture transition plans 2026-04-14 04:45:39 +08:00
c0d62dc749 Support graph-owned imported texture transitions 2026-04-14 04:41:58 +08:00
c98b41f6f4 Implement render graph compiler and transient fullscreen execution 2026-04-14 04:37:07 +08:00
72d09a1c49 Fix editor host resize and dock splitter behavior 2026-04-14 03:34:31 +08:00
ba91e0f5dd Fix borderless host resize presentation ordering 2026-04-14 01:42:44 +08:00
9064c2f5f2 Add borderless editor host chrome 2026-04-14 01:14:45 +08:00
5797a75619 Extract frame-plan fullscreen stage builder 2026-04-14 01:01:45 +08:00
e83f911aef Add borderless host migration plan 2026-04-14 00:56:23 +08:00
dd2299c8b0 Clarify frame plan compatibility adapters 2026-04-14 00:54:47 +08:00
b8d29e39f6 Migrate scene renderer callers to frame plans 2026-04-14 00:52:43 +08:00
72914b3865 Keep shadow execution state out of scene planner 2026-04-14 00:43:55 +08:00
e6950fa704 Switch scene viewport flow to frame plans 2026-04-14 00:40:11 +08:00
21b0530f7b Separate request and frame-stage execution contracts 2026-04-14 00:30:15 +08:00
c3d443eb85 Reduce host swap chain latency 2026-04-14 00:25:57 +08:00
d705cc839b Reduce redundant resize repaint passes 2026-04-13 23:38:45 +08:00
0d6b8bf7d8 Formalize directional shadow runtime contracts 2026-04-13 23:11:28 +08:00
4362008b39 Refactor new editor host resize pipeline 2026-04-13 23:09:02 +08:00
712f99e723 Refactor rendering frame execution contracts 2026-04-13 22:16:04 +08:00
48daaa1bd0 Fix Nahida toon binding and test assets 2026-04-13 21:09:40 +08:00
e462f7d6f7 Add 3DGS D3D12 composite debug checkpoint 2026-04-13 20:17:13 +08:00
5b89c2bb76 Separate viewport target and descriptor ownership 2026-04-13 19:57:25 +08:00
f3fc34898a Refactor new editor host orchestration 2026-04-13 19:37:10 +08:00
d2140bf5cc Checkpoint current new editor host iteration 2026-04-13 18:52:30 +08:00
a0d5e84516 Render 3DGS debug splats as quads 2026-04-13 13:38:41 +08:00
8ba05216fb Establish 3DGS D3D12 sorted baseline 2026-04-13 13:16:57 +08:00
0cc3d6da46 Fix new editor window resize presentation 2026-04-13 12:20:25 +08:00
adb6fe4659 rendering: improve main light shadow receiver filtering 2026-04-13 03:14:06 +08:00
95edf0435f rendering: stabilize single-map directional shadow fitting 2026-04-13 03:13:30 +08:00
82c55b3999 docs: rebuild main light shadow repair plan 2026-04-13 02:24:30 +08:00
00875e0c90 rendering: tune main light shadow bias defaults 2026-04-13 02:24:14 +08:00
b7428b0ef1 Stabilize 3DGS D3D12 phase 3 and sort key setup 2026-04-13 02:23:39 +08:00
1d6f2e290d rendering: formalize main light shadow bias settings 2026-04-13 01:40:29 +08:00
2ee74e7761 rendering: formalize main light shadow params 2026-04-13 01:06:09 +08:00
64212a53c7 Add 3DGS D3D12 MVS bootstrap and PLY loader 2026-04-13 00:36:50 +08:00
6f876678f5 editor: prefer bundled mono runtime discovery 2026-04-13 00:27:11 +08:00
2326857a43 build: remove forced MSVC multi-tool disable 2026-04-13 00:24:49 +08:00
0f60f0f657 Carry backing resource in UI texture handles 2026-04-12 23:25:18 +08:00
dd3731ba66 Execute viewport offscreen frames through D3D12 host 2026-04-12 23:21:59 +08:00
941034b387 Wire viewport shell host chain and move host under app 2026-04-12 23:13:00 +08:00
89590242bd docs(editor): sync script assembly builder api docs 2026-04-12 22:50:50 +08:00
a660fc489a Plan new editor viewport render host migration 2026-04-12 22:35:23 +08:00
e86d260d64 Fix Nahida unlit baseline isolation 2026-04-12 12:48:38 +08:00
347d08463b Fix D3D12 descriptor set staging for shader tables 2026-04-12 11:49:12 +08:00
7ee28a7969 Add gaussian splat asset caching groundwork 2026-04-12 11:15:59 +08:00
b7ce8618d2 Advance new editor hosted panels and state flow 2026-04-12 11:12:27 +08:00
7ad4bfbb1c Extract new editor host command session bridge 2026-04-12 01:49:08 +08:00
838f676fa6 Refactor new editor app context and workspace shell 2026-04-12 01:29:00 +08:00
0ff02150c0 Refine editor tree alignment and project panel events 2026-04-11 22:31:14 +08:00
8848cfd958 chore: checkpoint current workspace changes 2026-04-11 22:14:02 +08:00
3e55f8c204 fix(editor_ui): resolve splitter and tab drag gesture conflict 2026-04-11 20:33:53 +08:00
0a015b52ca feat(new_editor): add project panel and polish dock chrome 2026-04-11 20:20:30 +08:00
030230eb1f Add Nahida model import and preview pipeline 2026-04-11 20:16:49 +08:00
8f71f99de4 Fix FBX winding for Nahida preview 2026-04-11 18:45:49 +08:00
443c56ed08 Center tab labels and unify dock cursor resolution 2026-04-11 17:37:13 +08:00
2958dcc491 Refine XCEditor docking and DPI rendering 2026-04-11 17:07:37 +08:00
35d3d6328b Fix gaussian splat integration GT baseline 2026-04-11 17:07:00 +08:00
c03c7379c8 Precompute gaussian splat chunk visibility 2026-04-11 16:32:40 +08:00
0a2bdedc59 Archive outdated Library cache plans 2026-04-11 15:57:29 +08:00
2fb6eca854 Cull invisible gaussian splat chunks in prepare pass 2026-04-11 14:22:51 +08:00
c543ccf79c Preserve chunk metadata in gaussian splat subset test 2026-04-11 14:02:09 +08:00
88a71a5426 Bind gaussian splat chunk metadata in prepare pass 2026-04-11 13:55:39 +08:00
ff4e3f639a Generate gaussian splat chunks during PLY import 2026-04-11 07:13:32 +08:00
92d5cc61cf Define gaussian splat chunk data contract 2026-04-11 07:07:21 +08:00
b3acb5afc2 Derive gaussian splat SH order from resource layout 2026-04-11 06:57:47 +08:00
785377bc9b Add SH shading to gaussian splat renderer 2026-04-11 06:32:38 +08:00
5200fca82f Add GPU sorting for gaussian splat rendering 2026-04-11 06:09:53 +08:00
39632e1a04 Add gaussian splat integration baseline 2026-04-11 05:37:31 +08:00
3622bf3aa2 Fix builtin pass layout metadata lifetime 2026-04-11 03:24:32 +08:00
fac6e588a8 Formalize gaussian splat prepare-order pass 2026-04-11 03:02:30 +08:00
5191bb1149 Add formal compute pipeline creation API 2026-04-11 02:27:33 +08:00
d9bc0f1457 Add gaussian splat compute shader contracts 2026-04-11 01:30:59 +08:00
4080b2e5fe Fix D3D12 NanoVDB volume load stalls 2026-04-11 00:27:23 +08:00
be5dabd820 Support compute-only shader authoring variants 2026-04-11 00:24:55 +08:00
107b320aa7 Add builtin GaussianSplat forward pass baseline 2026-04-10 23:11:11 +08:00
15b42c248f Formalize GaussianSplat transient pass resources 2026-04-10 22:15:05 +08:00
977a4cf2a4 Unify dock leaves around single-tab stacks 2026-04-10 21:50:31 +08:00
b187c8970b Formalize GaussianSplat scene extraction 2026-04-10 21:49:53 +08:00
1119af2e38 Upgrade project asset watcher to native Win32 notifications 2026-04-10 21:36:53 +08:00
2338e306bf Add editor Assets watcher refresh loop 2026-04-10 21:16:17 +08:00
87ad489bfd Tighten new editor shell chrome and add dock convergence plan 2026-04-10 21:05:07 +08:00
503e6408ed Add model and GaussianSplat asset pipelines 2026-04-10 20:55:48 +08:00
8f5c342799 Formalize GaussianSplat render cache 2026-04-10 20:44:24 +08:00
84faa585d5 docs(plan): add incremental API doc task board 2026-04-10 18:49:37 +08:00
ac9388445c docs(plan): sync api task board completion state 2026-04-10 18:45:07 +08:00
3561bf22bb docs(ui): refresh selection model and resources umbrella docs 2026-04-10 18:42:11 +08:00
e6ac43b454 docs(rendering): refine selection and object id pass docs 2026-04-10 18:36:17 +08:00
2f3a28ec3e docs(rendering): reconcile infinite grid docs 2026-04-10 18:35:23 +08:00
737ccd2e0c docs(rendering): expand render pass foundation docs 2026-04-10 18:32:41 +08:00
bdf0b9a16b docs(rendering): sync infinite grid and depth style pass docs 2026-04-10 18:31:05 +08:00
0c52e0f640 docs(rendering): refine outline and volumetric pass docs 2026-04-10 18:28:20 +08:00
bb9a4d5ef4 docs(rendering): document color scale post-process API 2026-04-10 18:25:06 +08:00
a990553ade docs(rendering): sync outline inputs and volumetric prewarm docs 2026-04-10 18:23:40 +08:00
de2fc8be43 docs(rendering): document builtin final color pass API 2026-04-10 18:18:41 +08:00
effca78771 docs(asset): sync model and volume api docs 2026-04-10 18:17:19 +08:00
0602b34652 docs(rendering): sync render surface state and sample docs 2026-04-10 18:08:37 +08:00
85b8b3e583 docs(rhi): sync buffer init and sample quality docs 2026-04-10 17:59:59 +08:00
cba3823ea2 docs(rhi): document buffer init overload and sample quality 2026-04-10 17:51:45 +08:00
89bbad2786 docs(xceditor): deepen foundation api pages 2026-04-10 17:43:30 +08:00
00cf3850d5 docs(api): align render material resolve docs 2026-04-10 17:37:57 +08:00
81d0d92aed docs(plan): refresh API restructuring status 2026-04-10 17:36:25 +08:00
2d8ada03a1 docs(api): sync archive links and task board status 2026-04-10 17:34:19 +08:00
7fc7bb0a22 docs(api): sync archive and command guidance 2026-04-10 17:33:51 +08:00
447977214e docs(api): sync entry guidance for dual api roots 2026-04-10 17:32:04 +08:00
46fac8a215 docs(api): sync active and archived API entry docs 2026-04-10 17:27:29 +08:00
e2e4e08479 docs(api): refresh audit sync snapshot 2026-04-10 17:26:05 +08:00
e69240db49 docs(components): fix extractor links in component api docs 2026-04-10 17:21:52 +08:00
dd467d2468 docs: drop audit report from commit history 2026-04-10 17:19:45 +08:00
673 changed files with 91778 additions and 6309 deletions

View File

@@ -20,9 +20,11 @@
- [docs/plan/Library启动预热与运行时异步加载混合重构计划_2026-04-04.md](docs/plan/Library启动预热与运行时异步加载混合重构计划_2026-04-04.md)
- [docs/plan/Library启动预热与运行时异步加载混合重构计划_进度更新_2026-04-04.md](docs/plan/Library启动预热与运行时异步加载混合重构计划_进度更新_2026-04-04.md)
- [docs/plan/Editor架构说明.md](docs/plan/Editor架构说明.md)
- [docs/plan/Renderer下一阶段_Unity风格Shader体系正式化计划_2026-04-06.md](docs/plan/Renderer下一阶段_Unity风格Shader体系正式化计划_2026-04-06.md)
- [docs/plan/Unity风格模型导入与Model资产架构重构计划_2026-04-10.md](docs/plan/Unity风格模型导入与Model资产架构重构计划_2026-04-10.md)
- [docs/plan/NanoVDB体积云加载阻塞与Runtime上传修复计划_2026-04-10.md](docs/plan/NanoVDB体积云加载阻塞与Runtime上传修复计划_2026-04-10.md)
- [docs/plan/3DGS专用PLY导入器与GaussianSplat资源缓存正式化计划_2026-04-10.md](docs/plan/3DGS专用PLY导入器与GaussianSplat资源缓存正式化计划_2026-04-10.md)
- [docs/plan/XCUI_NewEditor主线重建计划_2026-04-07.md](docs/plan/XCUI_NewEditor主线重建计划_2026-04-07.md)
- [docs/plan/XCUI完整架构设计与执行计划.md](docs/plan/XCUI完整架构设计与执行计划.md)
- [docs/plan/XCUI_Phase_Status_2026-04-05.md](docs/plan/XCUI_Phase_Status_2026-04-05.md)
- [docs/plan/C#脚本模块下一阶段计划.md](docs/plan/C%23脚本模块下一阶段计划.md)
- [tests/TEST_SPEC.md](tests/TEST_SPEC.md)
- [tests/UI/TEST_SPEC.md](tests/UI/TEST_SPEC.md)
@@ -30,22 +32,35 @@
已归档但当前仍常用的背景文档:
- [Library资产导入与缓存系统收口计划归档](docs/used/Library资产导入与缓存系统收口计划_完成归档_2026-04-03.md)
- [API 文档第三轮任务池(归档基线)](docs/used/API文档实时同步任务池_2026-04-03.md)
- [XCUI Phase Status 2026-04-05归档](docs/used/XCUI_Phase_Status_2026-04-05.md)
- [Shader与Material系统下一阶段计划归档](docs/used/Shader与Material系统下一阶段计划_完成归档_2026-04-04.md)
- [Unity 风格 Shader 体系正式化计划(归档)](docs/used/Renderer下一阶段_Unity风格Shader体系正式化计划_完成归档_2026-04-07.md)
- [Renderer 当前阶段正式收口(阶段归档)](docs/used/Renderer当前阶段正式收口计划_阶段归档_2026-04-10.md)
- [NanoVDB 后续正式化(阶段归档)](docs/used/NanoVDB稀疏体积渲染后续正式化计划_阶段归档_2026-04-10.md)
- [SceneViewport Overlay / Gizmo 重构计划(归档)](docs/used/SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md)
- [Unity式 SceneView Gizmo 正式化方案(归档)](docs/used/Unity式SceneView_Gizmo系统完整审查与正式化重构方案_完成归档_2026-04-06.md)
- [NanoVDB 第一阶段完成归档](docs/used/NanoVDB稀疏体积渲染正式集成计划_第一阶段完成归档_2026-04-09.md)
如果任务落在 API 文档:
1. 先检查 `docs/plan/`有没有日期更晚的 API 相关计划或归档;当前活跃任务池是 [docs/plan/API文档实时同步任务池_2026-04-03.md](docs/plan/API文档实时同步任务池_2026-04-03.md)。这份任务池目前已经推进到第三轮 `T01-T20` 全部完成,结构审计保持全绿,但开始新一轮前仍要先确认是否又追加了新任务块。
2. 再看 [docs/api-skill.md](docs/api-skill.md)
3. 再看 `docs/api/_meta/rebuild-status.md`
4. 一次只认领一个任务块,先改状态为 `DOING`,只写自己任务块允许的范围。
1. 先检查 `docs/plan/`最新的 API 相关计划或并行任务板。当前工作树里已经存在多份 2026-04-09 的活跃文件,例如:
- [docs/plan/API文档目录重构计划_2026-04-09.md](docs/plan/API文档目录重构计划_2026-04-09.md)
- [docs/plan/API文档目录结构重大重构并行任务板_2026-04-09.md](docs/plan/API文档目录结构重大重构并行任务板_2026-04-09.md)
- [docs/plan/API文档目录结构第二轮重构计划_2026-04-09.md](docs/plan/API文档目录结构第二轮重构计划_2026-04-09.md)
- [docs/plan/API文档目录结构第二轮并行任务板_2026-04-09.md](docs/plan/API文档目录结构第二轮并行任务板_2026-04-09.md)
- [docs/plan/API文档目录结构重构并行任务板_2026-04-09_第二轮.md](docs/plan/API文档目录结构重构并行任务板_2026-04-09_第二轮.md)
2. 如果这些活跃文件都不覆盖当前问题,再回看 [docs/used/API文档实时同步任务池_2026-04-03.md](docs/used/API文档实时同步任务池_2026-04-03.md) 作为最近一轮完成基线。
3. 再看 [docs/api-skill.md](docs/api-skill.md)。
4. 再看 `docs/api/main.md``docs/api/_meta/rebuild-status.md`,确认当前问题落在 `XCEngine` 还是 `XCEditor` 根树。
5. 一次只认领一个任务块,先改状态为 `DOING`,只写自己任务块允许的范围。
## 2. 当前工程事实
- 顶层 `CMakeLists.txt` 当前纳入 `engine/``editor/``new_editor/``managed/``mvs/RenderDoc/``tests/`
- `engine/` 构建静态库 `XCEngine``editor/` 构建 `XCEditor`,但输出文件名仍是 `editor/bin/<Config>/XCEngine.exe`
- `new_editor/` 当前构建 `XCUIEditorLib``XCUIEditorHost`;启用 `XCENGINE_BUILD_XCUI_EDITOR_APP` 时会输出 `new_editor/bin/<Config>/XCUIEditor.exe`
- `editor/` 目前继续保留为当前正式编辑器、行为对照和视觉基线来源
- `new_editor/` 当前构建 `XCUIEditorLib``XCUIEditorHost`;启用 `XCENGINE_BUILD_XCUI_EDITOR_APP` 时会输出 `new_editor/bin/<Config>/XCUIEditor.exe`,并被视为未来正式编辑器主线,而不再只是临时 sandbox。
- editor 默认把仓库内的 `project/` 识别为工程根目录,也支持 `--project <path>` 覆盖。
- 当前工程真实使用 `Assets/ + .meta + Library/` 的项目布局;`project/Library/` 是当前 workflow 的一部分,不是可随手忽略的垃圾目录。
- Mono 运行时与 editor 的脚本类发现都从 `<project>/Library/ScriptAssemblies/` 加载程序集。
@@ -81,8 +96,13 @@
- `ObjectId` 渲染与 editor picking
- `BuiltinInfiniteGridPass`
- `BuiltinObjectIdOutlinePass`
- `directional shadow`
- `skybox`
- `CameraRenderRequest::postScenePasses`
- `CameraRenderRequest::overlayPasses`
- `post-process / final color` 当前处于正式化收口阶段,不再是纯预留接口。
- `NanoVDB` 体积渲染已进入当前正式运行链路,但 Vulkan / OpenGL 的 rollout 和多后端能力边界仍在继续收口。
- 当前资源与导入主线正在继续向 `Model` 资产架构与 `3DGS GaussianSplat` 资源链扩展,不要再把 `.obj/.fbx/.ply` 简化理解为“文件直读后立刻渲染”的旧 sample 流程。
- 当前主线不是 render graph而是 shader / material contract、builtin pass contract 和 renderer-owned feature contract。
### 3.3 Editor
@@ -126,13 +146,13 @@
### 3.4 XCUI / New Editor
- `new_editor/`当前 `XCUI` editor sandbox 主线;旧 `editor/` 的整体替换仍处于延后状态,不要把 `XCUI` 计划误读成“已经整体替换现有 editor”
- `new_editor/`未来正式编辑器主线,不再只是 sandbox`editor/` 当前继续保留为正式编辑器、行为对照和视觉基线
- 当前宿主分层是:
- `XCUIEditorLib`
- `XCUIEditorHost`
- `XCUIEditorApp`(可选应用壳)
- 共享 UI core、runtime screen host 与 widget 基础能力主要沉淀在 `engine/include/XCEngine/UI/``engine/src/UI/``new_editor/` 负责 XCUI editor 壳、宿主与 widget sandbox
- XCUI / new_editor 的测试规范以 [tests/UI/TEST_SPEC.md](tests/UI/TEST_SPEC.md) 为准;根目录虽然有 `tests/NewEditor/`,但当前具体测试实现主要放在 `tests/UI/`
- 共享 UI core、runtime screen host 与 widget 基础能力主要沉淀在 `engine/include/XCEngine/UI/``engine/src/UI/``new_editor/` 负责 XCUI editor 壳、宿主与产品装配
- `tests/UI/` 是当前 XCUI `Core / Editor / Runtime` 三层的唯一正式基础层验证入口;`new_editor/` 不承担测试堆场职责
### 3.5 Scripting
@@ -262,15 +282,21 @@
只要任务涉及 `docs/api/`
1.检查 `docs/plan/` 下有没有更新日期更晚的 API 计划或归档
2. 以最新任务池和 [docs/api-skill.md](docs/api-skill.md) 为执行规范
3. 改完必须重新执行:
1. `docs/plan/API文档目录*.md` 里日期最新的 API 计划 / 并行任务板
2. 再看 [docs/api-skill.md](docs/api-skill.md) `docs/api/_meta/rebuild-status.md`
3. 如果活跃计划没有覆盖当前问题,再回看 [docs/used/API文档实时同步任务池_2026-04-03.md](docs/used/API文档实时同步任务池_2026-04-03.md) 作为最近一轮归档基线。
4. 改完必须重新执行:
```powershell
python -B docs/api/_tools/audit_api_docs.py
```
如果审计没回绿,不算完成。
当前审计口径已经同时覆盖:
- `engine/include/XCEngine/**`
- `new_editor/include/XCEditor/**`
- `editor/src/**`
## 5. 推荐构建与验证入口
@@ -321,9 +347,10 @@ ctest --test-dir build -C Debug --output-on-failure
- 资源导入与工程布局:`engine/include/XCEngine/Core/Asset/``engine/src/Core/Asset/``editor/src/Managers/ProjectManager.cpp``project/Assets/``project/Library/`
- Material / shader / artifact`engine/include/XCEngine/Resources/Material/``engine/src/Resources/Material/``engine/include/XCEngine/Core/Asset/ArtifactFormats.h``tests/Resources/Material/`
- Editor viewport / gizmo / picking`editor/src/Viewport/``editor/src/panels/SceneViewPanel.cpp``tests/Editor/`
- XCUI / new_editor`engine/include/XCEngine/UI/``engine/src/UI/``new_editor/include/XCEditor/``new_editor/src/``tests/UI/`
- Editor actions / project routing`editor/src/Actions/``editor/src/Commands/``editor/src/Core/``editor/src/Managers/``tests/Editor/test_action_routing.cpp`
- 脚本运行时与程序集:`engine/include/XCEngine/Scripting/``engine/src/Scripting/``managed/``project/Assets/Scripts/``tests/Scripting/`
- API 文档:`docs/api/XCEngine/``docs/api/_guides/``docs/api/_tools/audit_api_docs.py``docs/plan/API文档实时同步任务池_2026-04-03.md`
- API 文档:`docs/api/main.md``docs/api/XCEngine/``docs/api/XCEditor/``docs/api/_guides/``docs/api/_tools/audit_api_docs.py``docs/plan/API文档目录*.md``docs/used/API文档实时同步任务池_2026-04-03.md`
## 7. 适合当前仓库的工作方式

View File

@@ -0,0 +1,158 @@
cmake_minimum_required(VERSION 3.20)
project(XC3DGSD3D12MVS LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_program(XC_DXC_EXECUTABLE NAMES dxc)
if(NOT XC_DXC_EXECUTABLE)
message(FATAL_ERROR "dxc is required to build the 3DGS D3D12 MVS sort shaders.")
endif()
get_filename_component(XCENGINE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
set(XCENGINE_BUILD_DIR "${XCENGINE_ROOT}/build")
set(XCENGINE_INCLUDE_DIR "${XCENGINE_ROOT}/engine/include")
set(XCENGINE_LIBRARY_DEBUG "${XCENGINE_BUILD_DIR}/engine/Debug/XCEngine.lib")
if(NOT EXISTS "${XCENGINE_LIBRARY_DEBUG}")
message(FATAL_ERROR "Prebuilt XCEngine library was not found: ${XCENGINE_LIBRARY_DEBUG}")
endif()
add_library(XCEngine STATIC IMPORTED GLOBAL)
set_target_properties(XCEngine PROPERTIES
IMPORTED_CONFIGURATIONS "Debug;Release;RelWithDebInfo;MinSizeRel"
IMPORTED_LOCATION_DEBUG "${XCENGINE_LIBRARY_DEBUG}"
IMPORTED_LOCATION_RELEASE "${XCENGINE_LIBRARY_DEBUG}"
IMPORTED_LOCATION_RELWITHDEBINFO "${XCENGINE_LIBRARY_DEBUG}"
IMPORTED_LOCATION_MINSIZEREL "${XCENGINE_LIBRARY_DEBUG}"
)
add_executable(xc_3dgs_d3d12_mvs
WIN32
src/main.cpp
src/App.cpp
src/GaussianPlyLoader.cpp
include/XC3DGSD3D12/App.h
include/XC3DGSD3D12/GaussianPlyLoader.h
shaders/PreparedSplatView.hlsli
shaders/PrepareGaussiansCS.hlsl
shaders/BuildSortKeysCS.hlsl
shaders/SortCommon.hlsl
shaders/DeviceRadixSort.hlsl
shaders/DebugPointsVS.hlsl
shaders/DebugPointsPS.hlsl
shaders/CompositeVS.hlsl
shaders/CompositePS.hlsl
)
set_source_files_properties(
shaders/PreparedSplatView.hlsli
shaders/PrepareGaussiansCS.hlsl
shaders/BuildSortKeysCS.hlsl
shaders/SortCommon.hlsl
shaders/DeviceRadixSort.hlsl
shaders/DebugPointsVS.hlsl
shaders/DebugPointsPS.hlsl
shaders/CompositeVS.hlsl
shaders/CompositePS.hlsl
PROPERTIES
HEADER_FILE_ONLY TRUE
)
target_include_directories(xc_3dgs_d3d12_mvs PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${XCENGINE_INCLUDE_DIR}
)
target_compile_definitions(xc_3dgs_d3d12_mvs PRIVATE
UNICODE
_UNICODE
NOMINMAX
WIN32_LEAN_AND_MEAN
)
if(MSVC)
target_compile_options(xc_3dgs_d3d12_mvs PRIVATE /utf-8)
endif()
target_link_libraries(xc_3dgs_d3d12_mvs PRIVATE
XCEngine
d3d12
dxgi
dxguid
d3dcompiler
winmm
delayimp
bcrypt
opengl32
)
set_target_properties(xc_3dgs_d3d12_mvs PROPERTIES
VS_DEBUGGER_WORKING_DIRECTORY "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>"
)
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_SOURCE_DIR}/room.ply"
"$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/room.ply"
)
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/shaders"
"$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders"
)
add_custom_command(TARGET xc_3dgs_d3d12_mvs POST_BUILD
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E MainCS
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/BuildSortKeysCS.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/BuildSortKeysCS.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E InitDeviceRadixSort
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixInit.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E Upsweep
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixUpsweep.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E BuildGlobalHistogram
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixGlobalHistogram.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E Scan
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixScan.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
COMMAND "${XC_DXC_EXECUTABLE}"
-T cs_6_6
-E Downsweep
-D KEY_UINT=1
-D PAYLOAD_UINT=1
-D SORT_PAIRS=1
-D SHOULD_ASCEND=1
-Fo "$<TARGET_FILE_DIR:xc_3dgs_d3d12_mvs>/shaders/RadixDownsweep.dxil"
"${CMAKE_CURRENT_SOURCE_DIR}/shaders/DeviceRadixSort.hlsl"
)

View File

@@ -0,0 +1,162 @@
#pragma once
#include <windows.h>
#include <memory>
#include <string>
#include <vector>
#include <wrl/client.h>
#include "XC3DGSD3D12/GaussianPlyLoader.h"
#include "XCEngine/RHI/RHIEnums.h"
#include "XCEngine/RHI/RHITypes.h"
#include "XCEngine/RHI/D3D12/D3D12CommandAllocator.h"
#include "XCEngine/RHI/D3D12/D3D12Buffer.h"
#include "XCEngine/RHI/D3D12/D3D12CommandList.h"
#include "XCEngine/RHI/D3D12/D3D12CommandQueue.h"
#include "XCEngine/RHI/D3D12/D3D12DescriptorHeap.h"
#include "XCEngine/RHI/D3D12/D3D12Device.h"
#include "XCEngine/RHI/D3D12/D3D12ResourceView.h"
#include "XCEngine/RHI/D3D12/D3D12SwapChain.h"
#include "XCEngine/RHI/D3D12/D3D12Texture.h"
namespace XCEngine {
namespace RHI {
class RHIDescriptorPool;
class RHIDescriptorSet;
class RHIPipelineLayout;
class RHIPipelineState;
} // namespace RHI
} // namespace XCEngine
namespace XC3DGSD3D12 {
struct PreparedSplatView {
float clipPosition[4] = {};
float axis1[2] = {};
float axis2[2] = {};
uint32_t packedColor[2] = {};
};
class App {
public:
App();
~App();
bool Initialize(HINSTANCE instance, int showCommand);
int Run();
void SetFrameLimit(unsigned int frameLimit);
void SetGaussianScenePath(std::wstring scenePath);
void SetSummaryPath(std::wstring summaryPath);
void SetScreenshotPath(std::wstring screenshotPath);
const std::wstring& GetLastErrorMessage() const;
private:
static constexpr int kBackBufferCount = 2;
static constexpr int kDefaultWidth = 1280;
static constexpr int kDefaultHeight = 720;
static LRESULT CALLBACK StaticWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
bool RegisterWindowClass(HINSTANCE instance);
bool CreateMainWindow(HINSTANCE instance, int showCommand);
bool LoadGaussianScene();
bool InitializeRhi();
bool InitializeGaussianGpuResources();
bool InitializePreparePassResources();
bool InitializeSortResources();
bool InitializeDebugDrawResources();
bool InitializeCompositeResources();
void ShutdownGaussianGpuResources();
void ShutdownPreparePassResources();
void ShutdownSortResources();
void ShutdownDebugDrawResources();
void ShutdownCompositeResources();
void Shutdown();
bool CaptureSortSnapshot();
bool CapturePass3HistogramDebug();
void RenderFrame(bool captureScreenshot);
HWND m_hwnd = nullptr;
HINSTANCE m_instance = nullptr;
int m_width = kDefaultWidth;
int m_height = kDefaultHeight;
bool m_running = false;
bool m_isInitialized = false;
bool m_hasRenderedAtLeastOneFrame = false;
unsigned int m_frameLimit = 0;
unsigned int m_renderedFrameCount = 0;
std::wstring m_gaussianScenePath = L"room.ply";
std::wstring m_summaryPath;
std::wstring m_screenshotPath = L"phase3_debug_points.ppm";
std::wstring m_sortKeySnapshotPath = L"phase3_sortkeys.txt";
std::wstring m_lastErrorMessage;
GaussianSplatRuntimeData m_gaussianSceneData;
XCEngine::RHI::D3D12Buffer m_gaussianPositionBuffer;
XCEngine::RHI::D3D12Buffer m_gaussianOtherBuffer;
XCEngine::RHI::D3D12Buffer m_gaussianShBuffer;
XCEngine::RHI::D3D12Texture m_gaussianColorTexture;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianPositionView;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianOtherView;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianShView;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_gaussianColorView;
std::vector<Microsoft::WRL::ComPtr<ID3D12Resource>> m_gaussianUploadBuffers;
XCEngine::RHI::D3D12Buffer* m_preparedViewBuffer = nullptr;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_preparedViewSrv;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_preparedViewUav;
XCEngine::RHI::RHIPipelineLayout* m_preparePipelineLayout = nullptr;
XCEngine::RHI::RHIPipelineState* m_preparePipelineState = nullptr;
XCEngine::RHI::RHIDescriptorPool* m_prepareDescriptorPool = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_prepareDescriptorSet = nullptr;
XCEngine::RHI::D3D12Buffer* m_sortKeyBuffer = nullptr;
XCEngine::RHI::D3D12Buffer* m_sortKeyScratchBuffer = nullptr;
XCEngine::RHI::D3D12Buffer* m_orderBuffer = nullptr;
XCEngine::RHI::D3D12Buffer* m_orderScratchBuffer = nullptr;
XCEngine::RHI::D3D12Buffer* m_passHistogramBuffer = nullptr;
XCEngine::RHI::D3D12Buffer* m_globalHistogramBuffer = nullptr;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_sortKeyUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_sortKeyScratchUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_orderBufferSrv;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_orderBufferUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_orderScratchUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_passHistogramUav;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_globalHistogramUav;
XCEngine::RHI::RHIPipelineLayout* m_buildSortKeyPipelineLayout = nullptr;
XCEngine::RHI::RHIPipelineState* m_buildSortKeyPipelineState = nullptr;
XCEngine::RHI::RHIDescriptorPool* m_buildSortKeyDescriptorPool = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_buildSortKeyDescriptorSet = nullptr;
XCEngine::RHI::RHIPipelineLayout* m_radixSortPipelineLayout = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortInitPipelineState = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortUpsweepPipelineState = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortGlobalHistogramPipelineState = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortScanPipelineState = nullptr;
XCEngine::RHI::RHIPipelineState* m_radixSortDownsweepPipelineState = nullptr;
XCEngine::RHI::RHIDescriptorPool* m_radixSortDescriptorPool = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_radixSortDescriptorSetPrimaryToScratch = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_radixSortDescriptorSetScratchToPrimary = nullptr;
XCEngine::RHI::RHIPipelineLayout* m_debugPipelineLayout = nullptr;
XCEngine::RHI::RHIPipelineState* m_debugPipelineState = nullptr;
XCEngine::RHI::RHIDescriptorPool* m_debugDescriptorPool = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_debugDescriptorSet = nullptr;
XCEngine::RHI::D3D12Texture m_splatRenderTargetTexture;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_splatRenderTargetRtv;
std::unique_ptr<XCEngine::RHI::D3D12ResourceView> m_splatRenderTargetSrv;
XCEngine::RHI::RHIPipelineLayout* m_compositePipelineLayout = nullptr;
XCEngine::RHI::RHIPipelineState* m_compositePipelineState = nullptr;
XCEngine::RHI::RHIDescriptorPool* m_compositeDescriptorPool = nullptr;
XCEngine::RHI::RHIDescriptorSet* m_compositeDescriptorSet = nullptr;
XCEngine::RHI::D3D12Device m_device;
XCEngine::RHI::D3D12CommandQueue m_commandQueue;
XCEngine::RHI::D3D12SwapChain m_swapChain;
XCEngine::RHI::D3D12CommandAllocator m_commandAllocator;
XCEngine::RHI::D3D12CommandList m_commandList;
XCEngine::RHI::D3D12Texture m_depthStencil;
XCEngine::RHI::D3D12DescriptorHeap m_rtvHeap;
XCEngine::RHI::D3D12DescriptorHeap m_dsvHeap;
XCEngine::RHI::D3D12ResourceView m_rtvs[kBackBufferCount];
XCEngine::RHI::D3D12ResourceView m_dsv;
};
} // namespace XC3DGSD3D12

View File

@@ -0,0 +1,46 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <string>
#include <vector>
namespace XC3DGSD3D12 {
struct Float3 {
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
};
struct GaussianSplatRuntimeData {
static constexpr uint32_t kColorTextureWidth = 2048;
static constexpr uint32_t kPositionStride = sizeof(float) * 3;
static constexpr uint32_t kOtherStride = sizeof(uint32_t) + sizeof(float) * 3;
static constexpr uint32_t kColorStride = sizeof(float) * 4;
static constexpr uint32_t kShCoefficientCount = 15;
static constexpr uint32_t kShStride = sizeof(float) * 3 * 16;
uint32_t splatCount = 0;
uint32_t colorTextureWidth = kColorTextureWidth;
uint32_t colorTextureHeight = 0;
Float3 boundsMin = {};
Float3 boundsMax = {};
std::vector<std::byte> positionData;
std::vector<std::byte> otherData;
std::vector<std::byte> colorData;
std::vector<std::byte> shData;
};
bool LoadGaussianSceneFromPly(
const std::filesystem::path& filePath,
GaussianSplatRuntimeData& outData,
std::string& outErrorMessage);
bool WriteGaussianSceneSummary(
const std::filesystem::path& filePath,
const GaussianSplatRuntimeData& data,
std::string& outErrorMessage);
} // namespace XC3DGSD3D12

View File

@@ -0,0 +1,43 @@
#define GROUP_SIZE 64
cbuffer FrameConstants : register(b0)
{
float4x4 gViewProjection;
float4x4 gView;
float4x4 gProjection;
float4 gCameraWorldPos;
float4 gScreenParams;
float4 gSettings;
};
ByteAddressBuffer gPositions : register(t0);
StructuredBuffer<uint> gOrderBuffer : register(t1);
RWStructuredBuffer<uint> gSortKeys : register(u0);
float3 LoadFloat3(ByteAddressBuffer buffer, uint byteOffset)
{
return asfloat(buffer.Load3(byteOffset));
}
uint FloatToSortableUint(float value)
{
uint bits = asuint(value);
uint mask = (0u - (bits >> 31)) | 0x80000000u;
return bits ^ mask;
}
[numthreads(GROUP_SIZE, 1, 1)]
void MainCS(uint3 dispatchThreadId : SV_DispatchThreadID)
{
uint index = dispatchThreadId.x;
uint splatCount = (uint)gSettings.x;
if (index >= splatCount)
{
return;
}
uint splatIndex = gOrderBuffer[index];
float3 position = LoadFloat3(gPositions, splatIndex * 12);
float3 viewPosition = mul(float4(position, 1.0), gView).xyz;
gSortKeys[index] = FloatToSortableUint(viewPosition.z);
}

View File

@@ -0,0 +1,12 @@
Texture2D<float4> gSplatTexture : register(t0);
struct PixelInput
{
float4 position : SV_Position;
};
float4 MainPS(PixelInput input) : SV_Target0
{
float4 color = gSplatTexture.Load(int3(int2(input.position.xy), 0));
return float4(color.rgb, color.a);
}

View File

@@ -0,0 +1,12 @@
struct VertexOutput
{
float4 position : SV_Position;
};
VertexOutput MainVS(uint vertexId : SV_VertexID)
{
VertexOutput output = (VertexOutput)0;
float2 quadPosition = float2(vertexId & 1, (vertexId >> 1) & 1) * 4.0 - 1.0;
output.position = float4(quadPosition, 1.0, 1.0);
return output;
}

View File

@@ -0,0 +1,19 @@
struct PixelInput
{
float4 position : SV_Position;
float4 color : COLOR0;
float2 localPosition : TEXCOORD0;
};
float4 MainPS(PixelInput input) : SV_Target0
{
float alpha = exp(-dot(input.localPosition, input.localPosition));
alpha = saturate(alpha * input.color.a);
if (alpha < (1.0 / 255.0))
{
discard;
}
return float4(input.color.rgb * alpha, alpha);
}

View File

@@ -0,0 +1,48 @@
#include "PreparedSplatView.hlsli"
cbuffer FrameConstants : register(b0)
{
float4x4 gViewProjection;
float4x4 gView;
float4x4 gProjection;
float4 gCameraWorldPos;
float4 gScreenParams;
float4 gSettings;
};
StructuredBuffer<PreparedSplatView> gPreparedViews : register(t0);
StructuredBuffer<uint> gOrderBuffer : register(t1);
struct VertexOutput
{
float4 position : SV_Position;
float4 color : COLOR0;
float2 localPosition : TEXCOORD0;
};
VertexOutput MainVS(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
{
VertexOutput output = (VertexOutput)0;
uint splatIndex = gOrderBuffer[instanceId];
PreparedSplatView view = gPreparedViews[splatIndex];
float4 color = UnpackPreparedColor(view);
if (view.clipPosition.w <= 0.0)
{
const float nanValue = asfloat(0x7fc00000);
output.position = float4(nanValue, nanValue, nanValue, nanValue);
return output;
}
float2 quadPosition = float2(vertexId & 1, (vertexId >> 1) & 1) * 2.0 - 1.0;
quadPosition *= 2.0;
float2 deltaScreenPosition =
(quadPosition.x * view.axis1 + quadPosition.y * view.axis2) * 2.0 / gScreenParams.xy;
output.position = view.clipPosition;
output.position.xy += deltaScreenPosition * view.clipPosition.w;
output.color = color;
output.localPosition = quadPosition;
return output;
}

View File

@@ -0,0 +1,477 @@
/******************************************************************************
* DeviceRadixSort
* Device Level 8-bit LSD Radix Sort using reduce then scan
*
* SPDX-License-Identifier: MIT
* Copyright Thomas Smith 5/17/2024
* https://github.com/b0nes164/GPUSorting
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
#include "SortCommon.hlsl"
#define US_DIM 128U //The number of threads in a Upsweep threadblock
#define SCAN_DIM 128U //The number of threads in a Scan threadblock
RWStructuredBuffer<uint> b_globalHist : register(u5); //buffer holding device level offsets for each binning pass
RWStructuredBuffer<uint> b_passHist : register(u4); //buffer used to store reduced sums of partition tiles
groupshared uint g_us[RADIX * 2]; //Shared memory for upsweep
groupshared uint g_scan[SCAN_DIM]; //Shared memory for the scan
//*****************************************************************************
//INIT KERNEL
//*****************************************************************************
//Clear the global histogram, as we will be adding to it atomically
[numthreads(1024, 1, 1)]
void InitDeviceRadixSort(int3 id : SV_DispatchThreadID)
{
b_globalHist[id.x] = 0;
}
//*****************************************************************************
//UPSWEEP KERNEL
//*****************************************************************************
//histogram, 64 threads to a histogram
inline void HistogramDigitCounts(uint gtid, uint gid)
{
const uint histOffset = gtid / 64 * RADIX;
const uint partitionEnd = gid == e_threadBlocks - 1 ?
e_numKeys : (gid + 1) * PART_SIZE;
for (uint i = gtid + gid * PART_SIZE; i < partitionEnd; i += US_DIM)
{
#if defined(KEY_UINT)
InterlockedAdd(g_us[ExtractDigit(b_sort[i]) + histOffset], 1);
#elif defined(KEY_INT)
InterlockedAdd(g_us[ExtractDigit(IntToUint(b_sort[i])) + histOffset], 1);
#elif defined(KEY_FLOAT)
InterlockedAdd(g_us[ExtractDigit(FloatToUint(b_sort[i])) + histOffset], 1);
#endif
}
}
//reduce and pass to tile histogram
inline void ReduceWriteDigitCounts(uint gtid, uint gid)
{
for (uint i = gtid; i < RADIX; i += US_DIM)
{
g_us[i] += g_us[i + RADIX];
b_passHist[i * e_threadBlocks + gid] = g_us[i];
}
}
//Build the per-pass 256-bin exclusive prefix from the reduced pass histogram.
inline void BuildGlobalHistogramExclusive(uint gtid)
{
uint digitIndices[2];
uint digitTotals[2];
uint digitCount = 0;
for (uint i = gtid; i < RADIX; i += US_DIM)
{
uint total = 0u;
const uint baseOffset = i * e_threadBlocks;
for (uint blockIndex = 0; blockIndex < e_threadBlocks; ++blockIndex)
{
total += b_passHist[baseOffset + blockIndex];
}
g_us[i] = total;
digitIndices[digitCount] = i;
digitTotals[digitCount] = total;
++digitCount;
}
GroupMemoryBarrierWithGroupSync();
for (uint offset = 1; offset < RADIX; offset <<= 1)
{
for (uint i = gtid; i < RADIX; i += US_DIM)
{
g_us[i + RADIX] = g_us[i] + (i >= offset ? g_us[i - offset] : 0u);
}
GroupMemoryBarrierWithGroupSync();
for (uint i = gtid; i < RADIX; i += US_DIM)
{
g_us[i] = g_us[i + RADIX];
}
GroupMemoryBarrierWithGroupSync();
}
const uint globalHistOffset = GlobalHistOffset();
for (uint localIndex = 0; localIndex < digitCount; ++localIndex)
{
const uint digitIndex = digitIndices[localIndex];
b_globalHist[digitIndex + globalHistOffset] = g_us[digitIndex] - digitTotals[localIndex];
}
}
[numthreads(US_DIM, 1, 1)]
void Upsweep(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
{
//get the wave size
const uint waveSize = getWaveSize();
//clear shared memory
const uint histsEnd = RADIX * 2;
for (uint i = gtid.x; i < histsEnd; i += US_DIM)
g_us[i] = 0;
GroupMemoryBarrierWithGroupSync();
HistogramDigitCounts(gtid.x, gid.x);
GroupMemoryBarrierWithGroupSync();
ReduceWriteDigitCounts(gtid.x, gid.x);
}
[numthreads(US_DIM, 1, 1)]
void BuildGlobalHistogram(uint3 gtid : SV_GroupThreadID)
{
const uint histsEnd = RADIX * 2;
for (uint i = gtid.x; i < histsEnd; i += US_DIM)
g_us[i] = 0;
GroupMemoryBarrierWithGroupSync();
BuildGlobalHistogramExclusive(gtid.x);
}
//*****************************************************************************
//SCAN KERNEL
//*****************************************************************************
inline void ExclusiveThreadBlockScanFullWGE16(
uint gtid,
uint laneMask,
uint circularLaneShift,
uint partEnd,
uint deviceOffset,
uint waveSize,
inout uint reduction)
{
for (uint i = gtid; i < partEnd; i += SCAN_DIM)
{
g_scan[gtid] = b_passHist[i + deviceOffset];
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
GroupMemoryBarrierWithGroupSync();
if (gtid < SCAN_DIM / waveSize)
{
g_scan[(gtid + 1) * waveSize - 1] +=
WavePrefixSum(g_scan[(gtid + 1) * waveSize - 1]);
}
GroupMemoryBarrierWithGroupSync();
uint t = (WaveGetLaneIndex() != laneMask ? g_scan[gtid] : 0) + reduction;
if (gtid >= waveSize)
t += WaveReadLaneAt(g_scan[gtid - 1], 0);
b_passHist[circularLaneShift + (i & ~laneMask) + deviceOffset] = t;
reduction += g_scan[SCAN_DIM - 1];
GroupMemoryBarrierWithGroupSync();
}
}
inline void ExclusiveThreadBlockScanPartialWGE16(
uint gtid,
uint laneMask,
uint circularLaneShift,
uint partEnd,
uint deviceOffset,
uint waveSize,
uint reduction)
{
uint i = gtid + partEnd;
if (i < e_threadBlocks)
g_scan[gtid] = b_passHist[deviceOffset + i];
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
GroupMemoryBarrierWithGroupSync();
if (gtid < SCAN_DIM / waveSize)
{
g_scan[(gtid + 1) * waveSize - 1] +=
WavePrefixSum(g_scan[(gtid + 1) * waveSize - 1]);
}
GroupMemoryBarrierWithGroupSync();
const uint index = circularLaneShift + (i & ~laneMask);
if (index < e_threadBlocks)
{
uint t = (WaveGetLaneIndex() != laneMask ? g_scan[gtid] : 0) + reduction;
if (gtid >= waveSize)
t += g_scan[(gtid & ~laneMask) - 1];
b_passHist[index + deviceOffset] = t;
}
}
inline void ExclusiveThreadBlockScanWGE16(uint gtid, uint gid, uint waveSize)
{
uint reduction = 0;
const uint laneMask = waveSize - 1;
const uint circularLaneShift = WaveGetLaneIndex() + 1 & laneMask;
const uint partionsEnd = e_threadBlocks / SCAN_DIM * SCAN_DIM;
const uint deviceOffset = gid * e_threadBlocks;
ExclusiveThreadBlockScanFullWGE16(
gtid,
laneMask,
circularLaneShift,
partionsEnd,
deviceOffset,
waveSize,
reduction);
ExclusiveThreadBlockScanPartialWGE16(
gtid,
laneMask,
circularLaneShift,
partionsEnd,
deviceOffset,
waveSize,
reduction);
}
inline void ExclusiveThreadBlockScanFullWLT16(
uint gtid,
uint partitions,
uint deviceOffset,
uint laneLog,
uint circularLaneShift,
uint waveSize,
inout uint reduction)
{
for (uint k = 0; k < partitions; ++k)
{
g_scan[gtid] = b_passHist[gtid + k * SCAN_DIM + deviceOffset];
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
GroupMemoryBarrierWithGroupSync();
if (gtid < waveSize)
{
b_passHist[circularLaneShift + k * SCAN_DIM + deviceOffset] =
(circularLaneShift ? g_scan[gtid] : 0) + reduction;
}
uint offset = laneLog;
uint j = waveSize;
for (; j < (SCAN_DIM >> 1); j <<= laneLog)
{
if (gtid < (SCAN_DIM >> offset))
{
g_scan[((gtid + 1) << offset) - 1] +=
WavePrefixSum(g_scan[((gtid + 1) << offset) - 1]);
}
GroupMemoryBarrierWithGroupSync();
if ((gtid & ((j << laneLog) - 1)) >= j)
{
if (gtid < (j << laneLog))
{
b_passHist[gtid + k * SCAN_DIM + deviceOffset] =
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0) +
((gtid & (j - 1)) ? g_scan[gtid - 1] : 0) + reduction;
}
else
{
if ((gtid + 1) & (j - 1))
{
g_scan[gtid] +=
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0);
}
}
}
offset += laneLog;
}
GroupMemoryBarrierWithGroupSync();
//If SCAN_DIM is not a power of lanecount
for (uint i = gtid + j; i < SCAN_DIM; i += SCAN_DIM)
{
b_passHist[i + k * SCAN_DIM + deviceOffset] =
WaveReadLaneAt(g_scan[((i >> offset) << offset) - 1], 0) +
((i & (j - 1)) ? g_scan[i - 1] : 0) + reduction;
}
reduction += WaveReadLaneAt(g_scan[SCAN_DIM - 1], 0) +
WaveReadLaneAt(g_scan[(((SCAN_DIM - 1) >> offset) << offset) - 1], 0);
GroupMemoryBarrierWithGroupSync();
}
}
inline void ExclusiveThreadBlockScanParitalWLT16(
uint gtid,
uint partitions,
uint deviceOffset,
uint laneLog,
uint circularLaneShift,
uint waveSize,
uint reduction)
{
const uint finalPartSize = e_threadBlocks - partitions * SCAN_DIM;
if (gtid < finalPartSize)
{
g_scan[gtid] = b_passHist[gtid + partitions * SCAN_DIM + deviceOffset];
g_scan[gtid] += WavePrefixSum(g_scan[gtid]);
}
GroupMemoryBarrierWithGroupSync();
if (gtid < waveSize && circularLaneShift < finalPartSize)
{
b_passHist[circularLaneShift + partitions * SCAN_DIM + deviceOffset] =
(circularLaneShift ? g_scan[gtid] : 0) + reduction;
}
uint offset = laneLog;
for (uint j = waveSize; j < finalPartSize; j <<= laneLog)
{
if (gtid < (finalPartSize >> offset))
{
g_scan[((gtid + 1) << offset) - 1] +=
WavePrefixSum(g_scan[((gtid + 1) << offset) - 1]);
}
GroupMemoryBarrierWithGroupSync();
if ((gtid & ((j << laneLog) - 1)) >= j && gtid < finalPartSize)
{
if (gtid < (j << laneLog))
{
b_passHist[gtid + partitions * SCAN_DIM + deviceOffset] =
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0) +
((gtid & (j - 1)) ? g_scan[gtid - 1] : 0) + reduction;
}
else
{
if ((gtid + 1) & (j - 1))
{
g_scan[gtid] +=
WaveReadLaneAt(g_scan[((gtid >> offset) << offset) - 1], 0);
}
}
}
offset += laneLog;
}
}
inline void ExclusiveThreadBlockScanWLT16(uint gtid, uint gid, uint waveSize)
{
uint reduction = 0;
const uint partitions = e_threadBlocks / SCAN_DIM;
const uint deviceOffset = gid * e_threadBlocks;
const uint laneLog = countbits(waveSize - 1);
const uint circularLaneShift = WaveGetLaneIndex() + 1 & waveSize - 1;
ExclusiveThreadBlockScanFullWLT16(
gtid,
partitions,
deviceOffset,
laneLog,
circularLaneShift,
waveSize,
reduction);
ExclusiveThreadBlockScanParitalWLT16(
gtid,
partitions,
deviceOffset,
laneLog,
circularLaneShift,
waveSize,
reduction);
}
//Scan does not need flattening of gids
[numthreads(SCAN_DIM, 1, 1)]
void Scan(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
{
if (gtid.x != 0u)
{
return;
}
const uint deviceOffset = gid.x * e_threadBlocks;
uint runningOffset = 0u;
for (uint blockIndex = 0u; blockIndex < e_threadBlocks; ++blockIndex)
{
const uint index = deviceOffset + blockIndex;
const uint count = b_passHist[index];
b_passHist[index] = runningOffset;
runningOffset += count;
}
}
//*****************************************************************************
//DOWNSWEEP KERNEL
//*****************************************************************************
inline void LoadThreadBlockReductions(uint gtid, uint gid, uint exclusiveHistReduction)
{
if (gtid < RADIX)
{
g_d[gtid + PART_SIZE] = b_globalHist[gtid + GlobalHistOffset()] +
b_passHist[gtid * e_threadBlocks + gid] - exclusiveHistReduction;
}
}
[numthreads(D_DIM, 1, 1)]
void Downsweep(uint3 gtid : SV_GroupThreadID, uint3 gid : SV_GroupID)
{
if (gtid.x != 0u)
{
return;
}
const uint partitionStart = gid.x * PART_SIZE;
const uint partitionEnd = min(partitionStart + PART_SIZE, e_numKeys);
uint digitOffsets[RADIX];
const uint globalHistOffset = GlobalHistOffset();
for (uint digit = 0u; digit < RADIX; ++digit)
{
digitOffsets[digit] =
b_globalHist[globalHistOffset + digit] +
b_passHist[digit * e_threadBlocks + gid.x];
}
for (uint index = partitionStart; index < partitionEnd; ++index)
{
uint key;
#if defined(KEY_UINT)
key = b_sort[index];
#elif defined(KEY_INT)
key = IntToUint(b_sort[index]);
#elif defined(KEY_FLOAT)
key = FloatToUint(b_sort[index]);
#endif
const uint digit = ExtractDigit(key);
const uint destinationIndex = digitOffsets[digit]++;
#if defined(KEY_UINT)
b_alt[destinationIndex] = key;
#elif defined(KEY_INT)
b_alt[destinationIndex] = UintToInt(key);
#elif defined(KEY_FLOAT)
b_alt[destinationIndex] = UintToFloat(key);
#endif
#if defined(SORT_PAIRS)
#if defined(PAYLOAD_UINT)
b_altPayload[destinationIndex] = b_sortPayload[index];
#elif defined(PAYLOAD_INT)
b_altPayload[destinationIndex] = b_sortPayload[index];
#elif defined(PAYLOAD_FLOAT)
b_altPayload[destinationIndex] = b_sortPayload[index];
#endif
#endif
}
}

View File

@@ -0,0 +1,272 @@
#include "PreparedSplatView.hlsli"
#define GROUP_SIZE 64
cbuffer FrameConstants : register(b0)
{
float4x4 gViewProjection;
float4x4 gView;
float4x4 gProjection;
float4 gCameraWorldPos;
float4 gScreenParams;
float4 gSettings;
};
ByteAddressBuffer gPositions : register(t0);
ByteAddressBuffer gOther : register(t1);
Texture2D<float4> gColor : register(t2);
ByteAddressBuffer gSh : register(t3);
RWStructuredBuffer<PreparedSplatView> gPreparedViews : register(u0);
static const float SH_C1 = 0.4886025;
static const float SH_C2[] = { 1.0925484, -1.0925484, 0.3153916, -1.0925484, 0.5462742 };
static const float SH_C3[] = { -0.5900436, 2.8906114, -0.4570458, 0.3731763, -0.4570458, 1.4453057, -0.5900436 };
static const uint kColorTextureWidth = 2048;
static const uint kOtherStride = 16;
static const uint kShStride = 192;
struct SplatSHData
{
float3 col;
float3 sh[15];
};
float3 LoadFloat3(ByteAddressBuffer buffer, uint byteOffset)
{
return asfloat(buffer.Load3(byteOffset));
}
uint EncodeMorton2D_16x16(uint2 c)
{
uint t = ((c.y & 0xF) << 8) | (c.x & 0xF);
t = (t ^ (t << 2)) & 0x3333;
t = (t ^ (t << 1)) & 0x5555;
return (t | (t >> 7)) & 0xFF;
}
uint2 DecodeMorton2D_16x16(uint t)
{
t = (t & 0xFF) | ((t & 0xFE) << 7);
t &= 0x5555;
t = (t ^ (t >> 1)) & 0x3333;
t = (t ^ (t >> 2)) & 0x0F0F;
return uint2(t & 0xF, t >> 8);
}
uint3 SplatIndexToPixelIndex(uint index)
{
uint2 xy = DecodeMorton2D_16x16(index);
uint tileWidth = kColorTextureWidth / 16;
index >>= 8;
uint3 result;
result.x = (index % tileWidth) * 16 + xy.x;
result.y = (index / tileWidth) * 16 + xy.y;
result.z = 0;
return result;
}
float4 DecodePacked_10_10_10_2(uint encoded)
{
return float4(
(encoded & 1023) / 1023.0,
((encoded >> 10) & 1023) / 1023.0,
((encoded >> 20) & 1023) / 1023.0,
((encoded >> 30) & 3) / 3.0);
}
float4 DecodeRotation(float4 packedRotation)
{
uint droppedIndex = (uint)round(packedRotation.w * 3.0);
float4 rotation;
rotation.xyz = packedRotation.xyz * sqrt(2.0) - (1.0 / sqrt(2.0));
rotation.w = sqrt(1.0 - saturate(dot(rotation.xyz, rotation.xyz)));
if (droppedIndex == 0)
{
rotation = rotation.wxyz;
}
if (droppedIndex == 1)
{
rotation = rotation.xwyz;
}
if (droppedIndex == 2)
{
rotation = rotation.xywz;
}
return rotation;
}
float3x3 CalcMatrixFromRotationScale(float4 rotation, float3 scale)
{
float3x3 scaleMatrix = float3x3(
scale.x, 0, 0,
0, scale.y, 0,
0, 0, scale.z);
float x = rotation.x;
float y = rotation.y;
float z = rotation.z;
float w = rotation.w;
float3x3 rotationMatrix = float3x3(
1 - 2 * (y * y + z * z), 2 * (x * y - w * z), 2 * (x * z + w * y),
2 * (x * y + w * z), 1 - 2 * (x * x + z * z), 2 * (y * z - w * x),
2 * (x * z - w * y), 2 * (y * z + w * x), 1 - 2 * (x * x + y * y));
return mul(rotationMatrix, scaleMatrix);
}
void CalcCovariance3D(float3x3 rotationScaleMatrix, out float3 sigma0, out float3 sigma1)
{
float3x3 sigma = mul(rotationScaleMatrix, transpose(rotationScaleMatrix));
sigma0 = float3(sigma._m00, sigma._m01, sigma._m02);
sigma1 = float3(sigma._m11, sigma._m12, sigma._m22);
}
float3 CalcCovariance2D(float3 worldPosition, float3 covariance0, float3 covariance1)
{
float3 viewPosition = mul(float4(worldPosition, 1.0), gView).xyz;
float aspect = gProjection._m00 / gProjection._m11;
float tanFovX = rcp(gProjection._m00);
float tanFovY = rcp(gProjection._m11 * aspect);
float clampX = 1.3 * tanFovX;
float clampY = 1.3 * tanFovY;
viewPosition.x = clamp(viewPosition.x / viewPosition.z, -clampX, clampX) * viewPosition.z;
viewPosition.y = clamp(viewPosition.y / viewPosition.z, -clampY, clampY) * viewPosition.z;
float focal = gScreenParams.x * gProjection._m00 * 0.5;
float3x3 jacobian = float3x3(
focal / viewPosition.z, 0, -(focal * viewPosition.x) / (viewPosition.z * viewPosition.z),
0, focal / viewPosition.z, -(focal * viewPosition.y) / (viewPosition.z * viewPosition.z),
0, 0, 0);
float3x3 worldToView = transpose((float3x3)gView);
float3x3 transform = mul(jacobian, worldToView);
float3x3 covariance = float3x3(
covariance0.x, covariance0.y, covariance0.z,
covariance0.y, covariance1.x, covariance1.y,
covariance0.z, covariance1.y, covariance1.z);
float3x3 projected = mul(transform, mul(covariance, transpose(transform)));
projected._m00 += 0.3;
projected._m11 += 0.3;
return float3(projected._m00, projected._m01, projected._m11);
}
void DecomposeCovariance(float3 covariance2D, out float2 axis1, out float2 axis2)
{
float diagonal0 = covariance2D.x;
float diagonal1 = covariance2D.z;
float offDiagonal = covariance2D.y;
float mid = 0.5 * (diagonal0 + diagonal1);
float radius = length(float2((diagonal0 - diagonal1) * 0.5, offDiagonal));
float lambda0 = mid + radius;
float lambda1 = max(mid - radius, 0.1);
float2 diagonalVector = normalize(float2(offDiagonal, lambda0 - diagonal0));
diagonalVector.y = -diagonalVector.y;
const float maxSize = 4096.0;
axis1 = min(sqrt(2.0 * lambda0), maxSize) * diagonalVector;
axis2 = min(sqrt(2.0 * lambda1), maxSize) * float2(diagonalVector.y, -diagonalVector.x);
}
SplatSHData LoadSplatSH(uint index)
{
SplatSHData sh;
const uint shBaseOffset = index * kShStride;
sh.col = gColor.Load(int3(SplatIndexToPixelIndex(index).xy, 0)).rgb;
[unroll]
for (uint coefficientIndex = 0; coefficientIndex < 15; ++coefficientIndex)
{
sh.sh[coefficientIndex] = LoadFloat3(gSh, shBaseOffset + coefficientIndex * 12);
}
return sh;
}
float3 ShadeSH(SplatSHData sh, float3 direction, int shOrder)
{
direction *= -1.0;
float x = direction.x;
float y = direction.y;
float z = direction.z;
float3 result = sh.col;
if (shOrder >= 1)
{
result += SH_C1 * (-sh.sh[0] * y + sh.sh[1] * z - sh.sh[2] * x);
if (shOrder >= 2)
{
float xx = x * x;
float yy = y * y;
float zz = z * z;
float xy = x * y;
float yz = y * z;
float xz = x * z;
result +=
(SH_C2[0] * xy) * sh.sh[3] +
(SH_C2[1] * yz) * sh.sh[4] +
(SH_C2[2] * (2 * zz - xx - yy)) * sh.sh[5] +
(SH_C2[3] * xz) * sh.sh[6] +
(SH_C2[4] * (xx - yy)) * sh.sh[7];
if (shOrder >= 3)
{
result +=
(SH_C3[0] * y * (3 * xx - yy)) * sh.sh[8] +
(SH_C3[1] * xy * z) * sh.sh[9] +
(SH_C3[2] * y * (4 * zz - xx - yy)) * sh.sh[10] +
(SH_C3[3] * z * (2 * zz - 3 * xx - 3 * yy)) * sh.sh[11] +
(SH_C3[4] * x * (4 * zz - xx - yy)) * sh.sh[12] +
(SH_C3[5] * z * (xx - yy)) * sh.sh[13] +
(SH_C3[6] * x * (xx - 3 * yy)) * sh.sh[14];
}
}
}
return max(result, 0.0);
}
[numthreads(GROUP_SIZE, 1, 1)]
void MainCS(uint3 dispatchThreadId : SV_DispatchThreadID)
{
uint index = dispatchThreadId.x;
uint splatCount = (uint)gSettings.x;
if (index >= splatCount)
{
return;
}
PreparedSplatView view = (PreparedSplatView)0;
float3 position = LoadFloat3(gPositions, index * 12);
uint packedRotation = gOther.Load(index * kOtherStride);
float4 rotation = DecodeRotation(DecodePacked_10_10_10_2(packedRotation));
float3 scale = LoadFloat3(gOther, index * kOtherStride + 4);
float4 colorOpacity = gColor.Load(int3(SplatIndexToPixelIndex(index).xy, 0));
view.clipPosition = mul(float4(position, 1.0), gViewProjection);
if (view.clipPosition.w > 0.0)
{
float3x3 rotationScale = CalcMatrixFromRotationScale(rotation, scale);
float3 covariance0;
float3 covariance1;
CalcCovariance3D(rotationScale, covariance0, covariance1);
float splatScaleSquared = gSettings.w * gSettings.w;
covariance0 *= splatScaleSquared;
covariance1 *= splatScaleSquared;
float3 covariance2D = CalcCovariance2D(position, covariance0, covariance1);
DecomposeCovariance(covariance2D, view.axis1, view.axis2);
SplatSHData sh = LoadSplatSH(index);
float3 viewDirection = normalize(gCameraWorldPos.xyz - position);
float3 shadedColor = ShadeSH(sh, viewDirection, (int)gSettings.z);
float opacity = saturate(colorOpacity.a * gSettings.y);
view.packedColor.x = (f32tof16(shadedColor.r) << 16) | f32tof16(shadedColor.g);
view.packedColor.y = (f32tof16(shadedColor.b) << 16) | f32tof16(opacity);
}
gPreparedViews[index] = view;
}

View File

@@ -0,0 +1,22 @@
#ifndef PREPARED_SPLAT_VIEW_HLSLI
#define PREPARED_SPLAT_VIEW_HLSLI
struct PreparedSplatView
{
float4 clipPosition;
float2 axis1;
float2 axis2;
uint2 packedColor;
};
float4 UnpackPreparedColor(PreparedSplatView view)
{
float4 color;
color.r = f16tof32((view.packedColor.x >> 16) & 0xFFFF);
color.g = f16tof32(view.packedColor.x & 0xFFFF);
color.b = f16tof32((view.packedColor.y >> 16) & 0xFFFF);
color.a = f16tof32(view.packedColor.y & 0xFFFF);
return color;
}
#endif

View File

@@ -0,0 +1,959 @@
/******************************************************************************
* SortCommon
* Common functions for GPUSorting
*
* SPDX-License-Identifier: MIT
* Copyright Thomas Smith 5/17/2024
* https://github.com/b0nes164/GPUSorting
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
******************************************************************************/
#define KEYS_PER_THREAD 15U
#define D_DIM 256U
#define PART_SIZE 3840U
#define D_TOTAL_SMEM 4096U
#define RADIX 256U //Number of digit bins
#define RADIX_MASK 255U //Mask of digit bins
#define HALF_RADIX 128U //For smaller waves where bit packing is necessary
#define HALF_MASK 127U // ''
#define RADIX_LOG 8U //log2(RADIX)
#define RADIX_PASSES 4U //(Key width) / RADIX_LOG
cbuffer cbGpuSorting : register(b0)
{
uint e_numKeys;
uint e_radixShift;
uint e_threadBlocks;
uint padding;
};
#if defined(KEY_UINT)
RWStructuredBuffer<uint> b_sort : register(u0);
RWStructuredBuffer<uint> b_alt : register(u1);
#elif defined(KEY_INT)
RWStructuredBuffer<int> b_sort : register(u0);
RWStructuredBuffer<int> b_alt : register(u1);
#elif defined(KEY_FLOAT)
RWStructuredBuffer<float> b_sort : register(u0);
RWStructuredBuffer<float> b_alt : register(u1);
#endif
#if defined(PAYLOAD_UINT)
RWStructuredBuffer<uint> b_sortPayload : register(u2);
RWStructuredBuffer<uint> b_altPayload : register(u3);
#elif defined(PAYLOAD_INT)
RWStructuredBuffer<int> b_sortPayload : register(u2);
RWStructuredBuffer<int> b_altPayload : register(u3);
#elif defined(PAYLOAD_FLOAT)
RWStructuredBuffer<float> b_sortPayload : register(u2);
RWStructuredBuffer<float> b_altPayload : register(u3);
#endif
groupshared uint g_d[D_TOTAL_SMEM]; //Shared memory for DigitBinningPass and DownSweep kernels
struct KeyStruct
{
uint k[KEYS_PER_THREAD];
};
struct OffsetStruct
{
#if defined(ENABLE_16_BIT)
uint16_t o[KEYS_PER_THREAD];
#else
uint o[KEYS_PER_THREAD];
#endif
};
struct DigitStruct
{
#if defined(ENABLE_16_BIT)
uint16_t d[KEYS_PER_THREAD];
#else
uint d[KEYS_PER_THREAD];
#endif
};
//*****************************************************************************
//HELPER FUNCTIONS
//*****************************************************************************
//Due to a bug with SPIRV pre 1.6, we cannot use WaveGetLaneCount() to get the currently active wavesize
inline uint getWaveSize()
{
#if defined(VULKAN)
GroupMemoryBarrierWithGroupSync(); //Make absolutely sure the wave is not diverged here
return dot(countbits(WaveActiveBallot(true)), uint4(1, 1, 1, 1));
#else
return WaveGetLaneCount();
#endif
}
inline uint getWaveIndex(uint gtid, uint waveSize)
{
return gtid / waveSize;
}
//Radix Tricks by Michael Herf
//http://stereopsis.com/radix.html
inline uint FloatToUint(float f)
{
uint mask = -((int) (asuint(f) >> 31)) | 0x80000000;
return asuint(f) ^ mask;
}
inline float UintToFloat(uint u)
{
uint mask = ((u >> 31) - 1) | 0x80000000;
return asfloat(u ^ mask);
}
inline uint IntToUint(int i)
{
return asuint(i ^ 0x80000000);
}
inline int UintToInt(uint u)
{
return asint(u ^ 0x80000000);
}
inline uint getWaveCountPass(uint waveSize)
{
return D_DIM / waveSize;
}
inline uint ExtractDigit(uint key)
{
return key >> e_radixShift & RADIX_MASK;
}
inline uint ExtractDigit(uint key, uint shift)
{
return key >> shift & RADIX_MASK;
}
inline uint ExtractPackedIndex(uint key)
{
return key >> (e_radixShift + 1) & HALF_MASK;
}
inline uint ExtractPackedShift(uint key)
{
return (key >> e_radixShift & 1) ? 16 : 0;
}
inline uint ExtractPackedValue(uint packed, uint key)
{
return packed >> ExtractPackedShift(key) & 0xffff;
}
inline uint SubPartSizeWGE16(uint waveSize)
{
return KEYS_PER_THREAD * waveSize;
}
inline uint SharedOffsetWGE16(uint gtid, uint waveSize)
{
return WaveGetLaneIndex() + getWaveIndex(gtid, waveSize) * SubPartSizeWGE16(waveSize);
}
inline uint SubPartSizeWLT16(uint waveSize, uint _serialIterations)
{
return KEYS_PER_THREAD * waveSize * _serialIterations;
}
inline uint SharedOffsetWLT16(uint gtid, uint waveSize, uint _serialIterations)
{
return WaveGetLaneIndex() +
(getWaveIndex(gtid, waveSize) / _serialIterations * SubPartSizeWLT16(waveSize, _serialIterations)) +
(getWaveIndex(gtid, waveSize) % _serialIterations * waveSize);
}
inline uint DeviceOffsetWGE16(uint gtid, uint waveSize, uint partIndex)
{
return SharedOffsetWGE16(gtid, waveSize) + partIndex * PART_SIZE;
}
inline uint DeviceOffsetWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
{
return SharedOffsetWLT16(gtid, waveSize, serialIterations) + partIndex * PART_SIZE;
}
inline uint GlobalHistOffset()
{
return e_radixShift << 5;
}
inline uint WaveHistsSizeWGE16(uint waveSize)
{
return D_DIM / waveSize * RADIX;
}
inline uint WaveHistsSizeWLT16()
{
return D_TOTAL_SMEM;
}
//*****************************************************************************
//FUNCTIONS COMMON TO THE DOWNSWEEP / DIGIT BINNING PASS
//*****************************************************************************
//If the size of a wave is too small, we do not have enough space in
//shared memory to assign a histogram to each wave, so instead,
//some operations are peformed serially.
inline uint SerialIterations(uint waveSize)
{
return (D_DIM / waveSize + 31) >> 5;
}
inline void ClearWaveHists(uint gtid, uint waveSize)
{
const uint histsEnd = waveSize >= 16 ?
WaveHistsSizeWGE16(waveSize) : WaveHistsSizeWLT16();
for (uint i = gtid; i < histsEnd; i += D_DIM)
g_d[i] = 0;
}
inline void LoadKey(inout uint key, uint index)
{
#if defined(KEY_UINT)
key = b_sort[index];
#elif defined(KEY_INT)
key = UintToInt(b_sort[index]);
#elif defined(KEY_FLOAT)
key = FloatToUint(b_sort[index]);
#endif
}
inline void LoadDummyKey(inout uint key)
{
key = 0xffffffff;
}
inline KeyStruct LoadKeysWGE16(uint gtid, uint waveSize, uint partIndex)
{
KeyStruct keys;
[unroll]
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
i < KEYS_PER_THREAD;
++i, t += waveSize)
{
LoadKey(keys.k[i], t);
}
return keys;
}
inline KeyStruct LoadKeysWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
{
KeyStruct keys;
[unroll]
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
i < KEYS_PER_THREAD;
++i, t += waveSize * serialIterations)
{
LoadKey(keys.k[i], t);
}
return keys;
}
inline KeyStruct LoadKeysPartialWGE16(uint gtid, uint waveSize, uint partIndex)
{
KeyStruct keys;
[unroll]
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
i < KEYS_PER_THREAD;
++i, t += waveSize)
{
if (t < e_numKeys)
LoadKey(keys.k[i], t);
else
LoadDummyKey(keys.k[i]);
}
return keys;
}
inline KeyStruct LoadKeysPartialWLT16(uint gtid, uint waveSize, uint partIndex, uint serialIterations)
{
KeyStruct keys;
[unroll]
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
i < KEYS_PER_THREAD;
++i, t += waveSize * serialIterations)
{
if (t < e_numKeys)
LoadKey(keys.k[i], t);
else
LoadDummyKey(keys.k[i]);
}
return keys;
}
inline uint WaveFlagsWGE16(uint waveSize)
{
return (waveSize & 31) ? (1U << waveSize) - 1 : 0xffffffff;
}
inline uint WaveFlagsWLT16(uint waveSize)
{
return (1U << waveSize) - 1;;
}
inline void WarpLevelMultiSplitWGE16(uint key, inout uint4 waveFlags)
{
[unroll]
for (uint k = 0; k < RADIX_LOG; ++k)
{
const uint currentBit = 1U << (k + e_radixShift);
const bool t = (key & currentBit) != 0;
GroupMemoryBarrierWithGroupSync(); //Play on the safe side, throw in a barrier for convergence
const uint4 ballot = WaveActiveBallot(t);
if(t)
waveFlags &= ballot;
else
waveFlags &= (~ballot);
}
}
inline uint2 CountBitsWGE16(uint waveSize, uint ltMask, uint4 waveFlags)
{
uint2 count = uint2(0, 0);
for(uint wavePart = 0; wavePart < waveSize; wavePart += 32)
{
uint t = countbits(waveFlags[wavePart >> 5]);
if (WaveGetLaneIndex() >= wavePart)
{
if (WaveGetLaneIndex() >= wavePart + 32)
count.x += t;
else
count.x += countbits(waveFlags[wavePart >> 5] & ltMask);
}
count.y += t;
}
return count;
}
inline void WarpLevelMultiSplitWLT16(uint key, inout uint waveFlags)
{
[unroll]
for (uint k = 0; k < RADIX_LOG; ++k)
{
const bool t = key >> (k + e_radixShift) & 1;
waveFlags &= (t ? 0 : 0xffffffff) ^ (uint) WaveActiveBallot(t);
}
}
inline OffsetStruct RankKeysWGE16(
uint waveSize,
uint waveOffset,
KeyStruct keys)
{
OffsetStruct offsets;
const uint initialFlags = WaveFlagsWGE16(waveSize);
const uint ltMask = (1U << (WaveGetLaneIndex() & 31)) - 1;
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
{
uint4 waveFlags = initialFlags;
WarpLevelMultiSplitWGE16(keys.k[i], waveFlags);
const uint index = ExtractDigit(keys.k[i]) + waveOffset;
const uint2 bitCount = CountBitsWGE16(waveSize, ltMask, waveFlags);
offsets.o[i] = g_d[index] + bitCount.x;
GroupMemoryBarrierWithGroupSync();
if (bitCount.x == 0)
g_d[index] += bitCount.y;
GroupMemoryBarrierWithGroupSync();
}
return offsets;
}
inline OffsetStruct RankKeysWLT16(uint waveSize, uint waveIndex, KeyStruct keys, uint serialIterations)
{
OffsetStruct offsets;
const uint ltMask = (1U << WaveGetLaneIndex()) - 1;
const uint initialFlags = WaveFlagsWLT16(waveSize);
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
{
uint waveFlags = initialFlags;
WarpLevelMultiSplitWLT16(keys.k[i], waveFlags);
const uint index = ExtractPackedIndex(keys.k[i]) +
(waveIndex / serialIterations * HALF_RADIX);
const uint peerBits = countbits(waveFlags & ltMask);
for (uint k = 0; k < serialIterations; ++k)
{
if (waveIndex % serialIterations == k)
offsets.o[i] = ExtractPackedValue(g_d[index], keys.k[i]) + peerBits;
GroupMemoryBarrierWithGroupSync();
if (waveIndex % serialIterations == k && peerBits == 0)
{
InterlockedAdd(g_d[index],
countbits(waveFlags) << ExtractPackedShift(keys.k[i]));
}
GroupMemoryBarrierWithGroupSync();
}
}
return offsets;
}
inline uint WaveHistInclusiveScanCircularShiftWGE16(uint gtid, uint waveSize)
{
uint histReduction = g_d[gtid];
for (uint i = gtid + RADIX; i < WaveHistsSizeWGE16(waveSize); i += RADIX)
{
histReduction += g_d[i];
g_d[i] = histReduction - g_d[i];
}
return histReduction;
}
inline uint WaveHistInclusiveScanCircularShiftWLT16(uint gtid)
{
uint histReduction = g_d[gtid];
for (uint i = gtid + HALF_RADIX; i < WaveHistsSizeWLT16(); i += HALF_RADIX)
{
histReduction += g_d[i];
g_d[i] = histReduction - g_d[i];
}
return histReduction;
}
inline void WaveHistReductionExclusiveScanWGE16(uint gtid, uint waveSize, uint histReduction)
{
if (gtid < RADIX)
{
const uint laneMask = waveSize - 1;
g_d[((WaveGetLaneIndex() + 1) & laneMask) + (gtid & ~laneMask)] = histReduction;
}
GroupMemoryBarrierWithGroupSync();
if (gtid < RADIX / waveSize)
{
g_d[gtid * waveSize] =
WavePrefixSum(g_d[gtid * waveSize]);
}
GroupMemoryBarrierWithGroupSync();
uint t = WaveReadLaneAt(g_d[gtid], 0);
if (gtid < RADIX && WaveGetLaneIndex())
g_d[gtid] += t;
}
//inclusive/exclusive prefix sum up the histograms,
//use a blelloch scan for in place packed exclusive
inline void WaveHistReductionExclusiveScanWLT16(uint gtid)
{
uint shift = 1;
for (uint j = RADIX >> 2; j > 0; j >>= 1)
{
GroupMemoryBarrierWithGroupSync();
if (gtid < j)
{
g_d[((((gtid << 1) + 2) << shift) - 1) >> 1] +=
g_d[((((gtid << 1) + 1) << shift) - 1) >> 1] & 0xffff0000;
}
shift++;
}
GroupMemoryBarrierWithGroupSync();
if (gtid == 0)
g_d[HALF_RADIX - 1] &= 0xffff;
for (uint j = 1; j < RADIX >> 1; j <<= 1)
{
--shift;
GroupMemoryBarrierWithGroupSync();
if (gtid < j)
{
const uint t = ((((gtid << 1) + 1) << shift) - 1) >> 1;
const uint t2 = ((((gtid << 1) + 2) << shift) - 1) >> 1;
const uint t3 = g_d[t];
g_d[t] = (g_d[t] & 0xffff) | (g_d[t2] & 0xffff0000);
g_d[t2] += t3 & 0xffff0000;
}
}
GroupMemoryBarrierWithGroupSync();
if (gtid < HALF_RADIX)
{
const uint t = g_d[gtid];
g_d[gtid] = (t >> 16) + (t << 16) + (t & 0xffff0000);
}
}
inline void UpdateOffsetsWGE16(
uint gtid,
uint waveSize,
inout OffsetStruct offsets,
KeyStruct keys)
{
if (gtid >= waveSize)
{
const uint t = getWaveIndex(gtid, waveSize) * RADIX;
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
{
const uint t2 = ExtractDigit(keys.k[i]);
offsets.o[i] += g_d[t2 + t] + g_d[t2];
}
}
else
{
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
offsets.o[i] += g_d[ExtractDigit(keys.k[i])];
}
}
inline void UpdateOffsetsWLT16(
uint gtid,
uint waveSize,
uint serialIterations,
inout OffsetStruct offsets,
KeyStruct keys)
{
if (gtid >= waveSize * serialIterations)
{
const uint t = getWaveIndex(gtid, waveSize) / serialIterations * HALF_RADIX;
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
{
const uint t2 = ExtractPackedIndex(keys.k[i]);
offsets.o[i] += ExtractPackedValue(g_d[t2 + t] + g_d[t2], keys.k[i]);
}
}
else
{
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
offsets.o[i] += ExtractPackedValue(g_d[ExtractPackedIndex(keys.k[i])], keys.k[i]);
}
}
inline void ScatterKeysShared(OffsetStruct offsets, KeyStruct keys)
{
[unroll]
for (uint i = 0; i < KEYS_PER_THREAD; ++i)
g_d[offsets.o[i]] = keys.k[i];
}
inline uint DescendingIndex(uint deviceIndex)
{
return e_numKeys - deviceIndex - 1;
}
inline void WriteKey(uint deviceIndex, uint groupSharedIndex)
{
#if defined(KEY_UINT)
b_alt[deviceIndex] = g_d[groupSharedIndex];
#elif defined(KEY_INT)
b_alt[deviceIndex] = UintToInt(g_d[groupSharedIndex]);
#elif defined(KEY_FLOAT)
b_alt[deviceIndex] = UintToFloat(g_d[groupSharedIndex]);
#endif
}
inline void LoadPayload(inout uint payload, uint deviceIndex)
{
#if defined(PAYLOAD_UINT)
payload = b_sortPayload[deviceIndex];
#elif defined(PAYLOAD_INT) || defined(PAYLOAD_FLOAT)
payload = asuint(b_sortPayload[deviceIndex]);
#endif
}
inline void ScatterPayloadsShared(OffsetStruct offsets, KeyStruct payloads)
{
ScatterKeysShared(offsets, payloads);
}
inline void WritePayload(uint deviceIndex, uint groupSharedIndex)
{
#if defined(PAYLOAD_UINT)
b_altPayload[deviceIndex] = g_d[groupSharedIndex];
#elif defined(PAYLOAD_INT)
b_altPayload[deviceIndex] = asint(g_d[groupSharedIndex]);
#elif defined(PAYLOAD_FLOAT)
b_altPayload[deviceIndex] = asfloat(g_d[groupSharedIndex]);
#endif
}
//*****************************************************************************
//SCATTERING: FULL PARTITIONS
//*****************************************************************************
//KEYS ONLY
inline void ScatterKeysOnlyDeviceAscending(uint gtid)
{
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
WriteKey(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i, i);
}
inline void ScatterKeysOnlyDeviceDescending(uint gtid)
{
if (e_radixShift == 24)
{
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
WriteKey(DescendingIndex(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i), i);
}
else
{
ScatterKeysOnlyDeviceAscending(gtid);
}
}
inline void ScatterKeysOnlyDevice(uint gtid)
{
#if defined(SHOULD_ASCEND)
ScatterKeysOnlyDeviceAscending(gtid);
#else
ScatterKeysOnlyDeviceDescending(gtid);
#endif
}
//KEY VALUE PAIRS
inline void ScatterPairsKeyPhaseAscending(
uint gtid,
inout DigitStruct digits)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
digits.d[i] = ExtractDigit(g_d[t]);
WriteKey(g_d[digits.d[i] + PART_SIZE] + t, t);
}
}
inline void ScatterPairsKeyPhaseDescending(
uint gtid,
inout DigitStruct digits)
{
if (e_radixShift == 24)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
digits.d[i] = ExtractDigit(g_d[t]);
WriteKey(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
}
}
else
{
ScatterPairsKeyPhaseAscending(gtid, digits);
}
}
inline void LoadPayloadsWGE16(
uint gtid,
uint waveSize,
uint partIndex,
inout KeyStruct payloads)
{
[unroll]
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
i < KEYS_PER_THREAD;
++i, t += waveSize)
{
LoadPayload(payloads.k[i], t);
}
}
inline void LoadPayloadsWLT16(
uint gtid,
uint waveSize,
uint partIndex,
uint serialIterations,
inout KeyStruct payloads)
{
[unroll]
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
i < KEYS_PER_THREAD;
++i, t += waveSize * serialIterations)
{
LoadPayload(payloads.k[i], t);
}
}
inline void ScatterPayloadsAscending(uint gtid, DigitStruct digits)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
WritePayload(g_d[digits.d[i] + PART_SIZE] + t, t);
}
inline void ScatterPayloadsDescending(uint gtid, DigitStruct digits)
{
if (e_radixShift == 24)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
WritePayload(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
}
else
{
ScatterPayloadsAscending(gtid, digits);
}
}
inline void ScatterPairsDevice(
uint gtid,
uint waveSize,
uint partIndex,
OffsetStruct offsets)
{
DigitStruct digits;
#if defined(SHOULD_ASCEND)
ScatterPairsKeyPhaseAscending(gtid, digits);
#else
ScatterPairsKeyPhaseDescending(gtid, digits);
#endif
GroupMemoryBarrierWithGroupSync();
KeyStruct payloads;
if (waveSize >= 16)
LoadPayloadsWGE16(gtid, waveSize, partIndex, payloads);
else
LoadPayloadsWLT16(gtid, waveSize, partIndex, SerialIterations(waveSize), payloads);
ScatterPayloadsShared(offsets, payloads);
GroupMemoryBarrierWithGroupSync();
#if defined(SHOULD_ASCEND)
ScatterPayloadsAscending(gtid, digits);
#else
ScatterPayloadsDescending(gtid, digits);
#endif
}
inline void ScatterDevice(
uint gtid,
uint waveSize,
uint partIndex,
OffsetStruct offsets)
{
#if defined(SORT_PAIRS)
ScatterPairsDevice(
gtid,
waveSize,
partIndex,
offsets);
#else
ScatterKeysOnlyDevice(gtid);
#endif
}
//*****************************************************************************
//SCATTERING: PARTIAL PARTITIONS
//*****************************************************************************
//KEYS ONLY
inline void ScatterKeysOnlyDevicePartialAscending(uint gtid, uint finalPartSize)
{
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
{
if (i < finalPartSize)
WriteKey(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i, i);
}
}
inline void ScatterKeysOnlyDevicePartialDescending(uint gtid, uint finalPartSize)
{
if (e_radixShift == 24)
{
for (uint i = gtid; i < PART_SIZE; i += D_DIM)
{
if (i < finalPartSize)
WriteKey(DescendingIndex(g_d[ExtractDigit(g_d[i]) + PART_SIZE] + i), i);
}
}
else
{
ScatterKeysOnlyDevicePartialAscending(gtid, finalPartSize);
}
}
inline void ScatterKeysOnlyDevicePartial(uint gtid, uint partIndex)
{
const uint finalPartSize = e_numKeys - partIndex * PART_SIZE;
#if defined(SHOULD_ASCEND)
ScatterKeysOnlyDevicePartialAscending(gtid, finalPartSize);
#else
ScatterKeysOnlyDevicePartialDescending(gtid, finalPartSize);
#endif
}
//KEY VALUE PAIRS
inline void ScatterPairsKeyPhaseAscendingPartial(
uint gtid,
uint finalPartSize,
inout DigitStruct digits)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
if (t < finalPartSize)
{
digits.d[i] = ExtractDigit(g_d[t]);
WriteKey(g_d[digits.d[i] + PART_SIZE] + t, t);
}
}
}
inline void ScatterPairsKeyPhaseDescendingPartial(
uint gtid,
uint finalPartSize,
inout DigitStruct digits)
{
if (e_radixShift == 24)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
if (t < finalPartSize)
{
digits.d[i] = ExtractDigit(g_d[t]);
WriteKey(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
}
}
}
else
{
ScatterPairsKeyPhaseAscendingPartial(gtid, finalPartSize, digits);
}
}
inline void LoadPayloadsPartialWGE16(
uint gtid,
uint waveSize,
uint partIndex,
inout KeyStruct payloads)
{
[unroll]
for (uint i = 0, t = DeviceOffsetWGE16(gtid, waveSize, partIndex);
i < KEYS_PER_THREAD;
++i, t += waveSize)
{
if (t < e_numKeys)
LoadPayload(payloads.k[i], t);
}
}
inline void LoadPayloadsPartialWLT16(
uint gtid,
uint waveSize,
uint partIndex,
uint serialIterations,
inout KeyStruct payloads)
{
[unroll]
for (uint i = 0, t = DeviceOffsetWLT16(gtid, waveSize, partIndex, serialIterations);
i < KEYS_PER_THREAD;
++i, t += waveSize * serialIterations)
{
if (t < e_numKeys)
LoadPayload(payloads.k[i], t);
}
}
inline void ScatterPayloadsAscendingPartial(
uint gtid,
uint finalPartSize,
DigitStruct digits)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
if (t < finalPartSize)
WritePayload(g_d[digits.d[i] + PART_SIZE] + t, t);
}
}
inline void ScatterPayloadsDescendingPartial(
uint gtid,
uint finalPartSize,
DigitStruct digits)
{
if (e_radixShift == 24)
{
[unroll]
for (uint i = 0, t = gtid; i < KEYS_PER_THREAD; ++i, t += D_DIM)
{
if (t < finalPartSize)
WritePayload(DescendingIndex(g_d[digits.d[i] + PART_SIZE] + t), t);
}
}
else
{
ScatterPayloadsAscendingPartial(gtid, finalPartSize, digits);
}
}
inline void ScatterPairsDevicePartial(
uint gtid,
uint waveSize,
uint partIndex,
OffsetStruct offsets)
{
DigitStruct digits;
const uint finalPartSize = e_numKeys - partIndex * PART_SIZE;
#if defined(SHOULD_ASCEND)
ScatterPairsKeyPhaseAscendingPartial(gtid, finalPartSize, digits);
#else
ScatterPairsKeyPhaseDescendingPartial(gtid, finalPartSize, digits);
#endif
GroupMemoryBarrierWithGroupSync();
KeyStruct payloads;
if (waveSize >= 16)
LoadPayloadsPartialWGE16(gtid, waveSize, partIndex, payloads);
else
LoadPayloadsPartialWLT16(gtid, waveSize, partIndex, SerialIterations(waveSize), payloads);
ScatterPayloadsShared(offsets, payloads);
GroupMemoryBarrierWithGroupSync();
#if defined(SHOULD_ASCEND)
ScatterPayloadsAscendingPartial(gtid, finalPartSize, digits);
#else
ScatterPayloadsDescendingPartial(gtid, finalPartSize, digits);
#endif
}
inline void ScatterDevicePartial(
uint gtid,
uint waveSize,
uint partIndex,
OffsetStruct offsets)
{
#if defined(SORT_PAIRS)
ScatterPairsDevicePartial(
gtid,
waveSize,
partIndex,
offsets);
#else
ScatterKeysOnlyDevicePartial(gtid, partIndex);
#endif
}

2193
MVS/3DGS-D3D12/src/App.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,617 @@
#include "XC3DGSD3D12/GaussianPlyLoader.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <fstream>
#include <limits>
#include <sstream>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace XC3DGSD3D12 {
namespace {
constexpr float kSHC0 = 0.2820948f;
enum class PlyPropertyType {
None,
Float32,
Float64,
UInt8,
};
struct PlyProperty {
std::string name;
PlyPropertyType type = PlyPropertyType::None;
uint32_t offset = 0;
uint32_t size = 0;
};
struct PlyHeader {
uint32_t vertexCount = 0;
uint32_t vertexStride = 0;
std::vector<PlyProperty> properties;
};
struct Float4 {
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float w = 0.0f;
};
struct RawGaussianSplat {
Float3 position = {};
Float3 dc0 = {};
std::array<Float3, GaussianSplatRuntimeData::kShCoefficientCount> sh = {};
float opacity = 0.0f;
Float3 scale = {};
Float4 rotation = {};
};
struct GaussianPlyPropertyLayout {
const PlyProperty* position[3] = {};
const PlyProperty* dc0[3] = {};
const PlyProperty* opacity = nullptr;
const PlyProperty* scale[3] = {};
const PlyProperty* rotation[4] = {};
std::array<const PlyProperty*, GaussianSplatRuntimeData::kShCoefficientCount * 3> sh = {};
};
std::string TrimTrailingCarriageReturn(std::string line) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
return line;
}
uint32_t PropertyTypeSize(PlyPropertyType type) {
switch (type) {
case PlyPropertyType::Float32:
return 4;
case PlyPropertyType::Float64:
return 8;
case PlyPropertyType::UInt8:
return 1;
default:
return 0;
}
}
bool ParsePropertyType(const std::string& token, PlyPropertyType& outType) {
if (token == "float") {
outType = PlyPropertyType::Float32;
return true;
}
if (token == "double") {
outType = PlyPropertyType::Float64;
return true;
}
if (token == "uchar") {
outType = PlyPropertyType::UInt8;
return true;
}
outType = PlyPropertyType::None;
return false;
}
bool ParsePlyHeader(std::ifstream& input, PlyHeader& outHeader, std::string& outErrorMessage) {
std::string line;
if (!std::getline(input, line)) {
outErrorMessage = "Failed to read PLY magic line.";
return false;
}
if (TrimTrailingCarriageReturn(line) != "ply") {
outErrorMessage = "Input file is not a valid PLY file.";
return false;
}
bool sawFormat = false;
std::string currentElement;
while (std::getline(input, line)) {
line = TrimTrailingCarriageReturn(line);
if (line == "end_header") {
break;
}
if (line.empty()) {
continue;
}
std::istringstream stream(line);
std::string token;
stream >> token;
if (token == "comment") {
continue;
}
if (token == "format") {
std::string formatName;
std::string version;
stream >> formatName >> version;
if (formatName != "binary_little_endian") {
outErrorMessage = "Only binary_little_endian PLY files are supported.";
return false;
}
sawFormat = true;
continue;
}
if (token == "element") {
stream >> currentElement;
if (currentElement == "vertex") {
stream >> outHeader.vertexCount;
}
continue;
}
if (token == "property" && currentElement == "vertex") {
std::string typeToken;
std::string name;
stream >> typeToken >> name;
PlyPropertyType propertyType = PlyPropertyType::None;
if (!ParsePropertyType(typeToken, propertyType)) {
outErrorMessage = "Unsupported PLY vertex property type: " + typeToken;
return false;
}
PlyProperty property;
property.name = name;
property.type = propertyType;
property.offset = outHeader.vertexStride;
property.size = PropertyTypeSize(propertyType);
outHeader.vertexStride += property.size;
outHeader.properties.push_back(property);
}
}
if (!sawFormat) {
outErrorMessage = "PLY header is missing a valid format declaration.";
return false;
}
if (outHeader.vertexCount == 0) {
outErrorMessage = "PLY file does not contain any vertex data.";
return false;
}
if (outHeader.vertexStride == 0 || outHeader.properties.empty()) {
outErrorMessage = "PLY vertex layout is empty.";
return false;
}
return true;
}
bool ReadPropertyAsFloat(
const std::byte* vertexBytes,
const PlyProperty& property,
float& outValue) {
const std::byte* propertyPtr = vertexBytes + property.offset;
switch (property.type) {
case PlyPropertyType::Float32: {
std::memcpy(&outValue, propertyPtr, sizeof(float));
return true;
}
case PlyPropertyType::Float64: {
double value = 0.0;
std::memcpy(&value, propertyPtr, sizeof(double));
outValue = static_cast<float>(value);
return true;
}
case PlyPropertyType::UInt8: {
uint8_t value = 0;
std::memcpy(&value, propertyPtr, sizeof(uint8_t));
outValue = static_cast<float>(value);
return true;
}
default:
return false;
}
}
bool BuildPropertyMap(
const PlyHeader& header,
std::unordered_map<std::string_view, const PlyProperty*>& outMap,
std::string& outErrorMessage) {
outMap.clear();
outMap.reserve(header.properties.size());
for (const PlyProperty& property : header.properties) {
const auto [it, inserted] = outMap.emplace(property.name, &property);
if (!inserted) {
outErrorMessage = "Duplicate PLY vertex property found: " + property.name;
return false;
}
}
return true;
}
bool RequireProperty(
const std::unordered_map<std::string_view, const PlyProperty*>& propertyMap,
std::string_view name,
const PlyProperty*& outProperty,
std::string& outErrorMessage) {
const auto iterator = propertyMap.find(name);
if (iterator == propertyMap.end()) {
outErrorMessage = "Missing required PLY property: " + std::string(name);
return false;
}
outProperty = iterator->second;
return true;
}
bool BuildGaussianPlyPropertyLayout(
const std::unordered_map<std::string_view, const PlyProperty*>& propertyMap,
GaussianPlyPropertyLayout& outLayout,
std::string& outErrorMessage) {
outLayout = {};
if (!RequireProperty(propertyMap, "x", outLayout.position[0], outErrorMessage) ||
!RequireProperty(propertyMap, "y", outLayout.position[1], outErrorMessage) ||
!RequireProperty(propertyMap, "z", outLayout.position[2], outErrorMessage) ||
!RequireProperty(propertyMap, "f_dc_0", outLayout.dc0[0], outErrorMessage) ||
!RequireProperty(propertyMap, "f_dc_1", outLayout.dc0[1], outErrorMessage) ||
!RequireProperty(propertyMap, "f_dc_2", outLayout.dc0[2], outErrorMessage) ||
!RequireProperty(propertyMap, "opacity", outLayout.opacity, outErrorMessage) ||
!RequireProperty(propertyMap, "scale_0", outLayout.scale[0], outErrorMessage) ||
!RequireProperty(propertyMap, "scale_1", outLayout.scale[1], outErrorMessage) ||
!RequireProperty(propertyMap, "scale_2", outLayout.scale[2], outErrorMessage) ||
!RequireProperty(propertyMap, "rot_0", outLayout.rotation[0], outErrorMessage) ||
!RequireProperty(propertyMap, "rot_1", outLayout.rotation[1], outErrorMessage) ||
!RequireProperty(propertyMap, "rot_2", outLayout.rotation[2], outErrorMessage) ||
!RequireProperty(propertyMap, "rot_3", outLayout.rotation[3], outErrorMessage)) {
return false;
}
for (uint32_t index = 0; index < outLayout.sh.size(); ++index) {
const std::string propertyName = "f_rest_" + std::to_string(index);
if (!RequireProperty(propertyMap, propertyName, outLayout.sh[index], outErrorMessage)) {
return false;
}
}
return true;
}
Float3 Min(const Float3& a, const Float3& b) {
return {
std::min(a.x, b.x),
std::min(a.y, b.y),
std::min(a.z, b.z),
};
}
Float3 Max(const Float3& a, const Float3& b) {
return {
std::max(a.x, b.x),
std::max(a.y, b.y),
std::max(a.z, b.z),
};
}
float Dot(const Float4& a, const Float4& b) {
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
Float4 NormalizeSwizzleRotation(const Float4& wxyz) {
const float lengthSquared = Dot(wxyz, wxyz);
if (lengthSquared <= std::numeric_limits<float>::epsilon()) {
return { 0.0f, 0.0f, 0.0f, 1.0f };
}
const float inverseLength = 1.0f / std::sqrt(lengthSquared);
return {
wxyz.y * inverseLength,
wxyz.z * inverseLength,
wxyz.w * inverseLength,
wxyz.x * inverseLength,
};
}
Float4 PackSmallest3Rotation(Float4 rotation) {
const Float4 absoluteRotation = {
std::fabs(rotation.x),
std::fabs(rotation.y),
std::fabs(rotation.z),
std::fabs(rotation.w),
};
int largestIndex = 0;
float largestValue = absoluteRotation.x;
if (absoluteRotation.y > largestValue) {
largestIndex = 1;
largestValue = absoluteRotation.y;
}
if (absoluteRotation.z > largestValue) {
largestIndex = 2;
largestValue = absoluteRotation.z;
}
if (absoluteRotation.w > largestValue) {
largestIndex = 3;
largestValue = absoluteRotation.w;
}
if (largestIndex == 0) {
rotation = { rotation.y, rotation.z, rotation.w, rotation.x };
} else if (largestIndex == 1) {
rotation = { rotation.x, rotation.z, rotation.w, rotation.y };
} else if (largestIndex == 2) {
rotation = { rotation.x, rotation.y, rotation.w, rotation.z };
}
const float sign = rotation.w >= 0.0f ? 1.0f : -1.0f;
const float invSqrt2 = std::sqrt(2.0f) * 0.5f;
const Float3 encoded = {
(rotation.x * sign * std::sqrt(2.0f)) * 0.5f + 0.5f,
(rotation.y * sign * std::sqrt(2.0f)) * 0.5f + 0.5f,
(rotation.z * sign * std::sqrt(2.0f)) * 0.5f + 0.5f,
};
(void)invSqrt2;
return { encoded.x, encoded.y, encoded.z, static_cast<float>(largestIndex) / 3.0f };
}
uint32_t EncodeQuatToNorm10(const Float4& packedRotation) {
const auto saturate = [](float value) {
return std::clamp(value, 0.0f, 1.0f);
};
const uint32_t x = static_cast<uint32_t>(saturate(packedRotation.x) * 1023.5f);
const uint32_t y = static_cast<uint32_t>(saturate(packedRotation.y) * 1023.5f);
const uint32_t z = static_cast<uint32_t>(saturate(packedRotation.z) * 1023.5f);
const uint32_t w = static_cast<uint32_t>(saturate(packedRotation.w) * 3.5f);
return x | (y << 10) | (z << 20) | (w << 30);
}
Float3 LinearScale(const Float3& logarithmicScale) {
return {
std::fabs(std::exp(logarithmicScale.x)),
std::fabs(std::exp(logarithmicScale.y)),
std::fabs(std::exp(logarithmicScale.z)),
};
}
Float3 SH0ToColor(const Float3& dc0) {
return {
dc0.x * kSHC0 + 0.5f,
dc0.y * kSHC0 + 0.5f,
dc0.z * kSHC0 + 0.5f,
};
}
float Sigmoid(float value) {
return 1.0f / (1.0f + std::exp(-value));
}
std::array<uint32_t, 2> DecodeMorton2D16x16(uint32_t value) {
value = (value & 0xFFu) | ((value & 0xFEu) << 7u);
value &= 0x5555u;
value = (value ^ (value >> 1u)) & 0x3333u;
value = (value ^ (value >> 2u)) & 0x0F0Fu;
return { value & 0xFu, value >> 8u };
}
uint32_t SplatIndexToTextureIndex(uint32_t index) {
const std::array<uint32_t, 2> morton = DecodeMorton2D16x16(index);
const uint32_t widthInBlocks = GaussianSplatRuntimeData::kColorTextureWidth / 16u;
index >>= 8u;
const uint32_t x = (index % widthInBlocks) * 16u + morton[0];
const uint32_t y = (index / widthInBlocks) * 16u + morton[1];
return y * GaussianSplatRuntimeData::kColorTextureWidth + x;
}
template <typename T>
void WriteValue(std::vector<std::byte>& bytes, size_t offset, const T& value) {
std::memcpy(bytes.data() + offset, &value, sizeof(T));
}
void WriteFloat3(std::vector<std::byte>& bytes, size_t offset, const Float3& value) {
WriteValue(bytes, offset + 0, value.x);
WriteValue(bytes, offset + 4, value.y);
WriteValue(bytes, offset + 8, value.z);
}
void WriteFloat4(std::vector<std::byte>& bytes, size_t offset, float x, float y, float z, float w) {
WriteValue(bytes, offset + 0, x);
WriteValue(bytes, offset + 4, y);
WriteValue(bytes, offset + 8, z);
WriteValue(bytes, offset + 12, w);
}
bool ReadGaussianSplat(
const std::byte* vertexBytes,
const GaussianPlyPropertyLayout& propertyLayout,
RawGaussianSplat& outSplat,
std::string& outErrorMessage) {
auto readFloat = [&](const PlyProperty* property, float& outValue) -> bool {
if (property == nullptr) {
outErrorMessage = "Gaussian PLY property layout is incomplete.";
return false;
}
return ReadPropertyAsFloat(vertexBytes, *property, outValue);
};
if (!readFloat(propertyLayout.position[0], outSplat.position.x) ||
!readFloat(propertyLayout.position[1], outSplat.position.y) ||
!readFloat(propertyLayout.position[2], outSplat.position.z) ||
!readFloat(propertyLayout.dc0[0], outSplat.dc0.x) ||
!readFloat(propertyLayout.dc0[1], outSplat.dc0.y) ||
!readFloat(propertyLayout.dc0[2], outSplat.dc0.z) ||
!readFloat(propertyLayout.opacity, outSplat.opacity) ||
!readFloat(propertyLayout.scale[0], outSplat.scale.x) ||
!readFloat(propertyLayout.scale[1], outSplat.scale.y) ||
!readFloat(propertyLayout.scale[2], outSplat.scale.z) ||
!readFloat(propertyLayout.rotation[0], outSplat.rotation.x) ||
!readFloat(propertyLayout.rotation[1], outSplat.rotation.y) ||
!readFloat(propertyLayout.rotation[2], outSplat.rotation.z) ||
!readFloat(propertyLayout.rotation[3], outSplat.rotation.w)) {
if (outErrorMessage.empty()) {
outErrorMessage = "Failed to read required Gaussian splat PLY properties.";
}
return false;
}
std::array<float, GaussianSplatRuntimeData::kShCoefficientCount * 3> shRaw = {};
for (uint32_t index = 0; index < shRaw.size(); ++index) {
if (!readFloat(propertyLayout.sh[index], shRaw[index])) {
if (outErrorMessage.empty()) {
outErrorMessage = "Failed to read SH rest coefficients from PLY.";
}
return false;
}
}
for (uint32_t coefficientIndex = 0; coefficientIndex < GaussianSplatRuntimeData::kShCoefficientCount; ++coefficientIndex) {
outSplat.sh[coefficientIndex] = {
shRaw[coefficientIndex + 0],
shRaw[coefficientIndex + GaussianSplatRuntimeData::kShCoefficientCount],
shRaw[coefficientIndex + GaussianSplatRuntimeData::kShCoefficientCount * 2],
};
}
return true;
}
void LinearizeGaussianSplat(RawGaussianSplat& splat) {
const Float4 normalizedQuaternion = NormalizeSwizzleRotation(splat.rotation);
const Float4 packedQuaternion = PackSmallest3Rotation(normalizedQuaternion);
splat.rotation = packedQuaternion;
splat.scale = LinearScale(splat.scale);
splat.dc0 = SH0ToColor(splat.dc0);
splat.opacity = Sigmoid(splat.opacity);
}
} // namespace
bool LoadGaussianSceneFromPly(
const std::filesystem::path& filePath,
GaussianSplatRuntimeData& outData,
std::string& outErrorMessage) {
outData = {};
outErrorMessage.clear();
std::ifstream input(filePath, std::ios::binary);
if (!input.is_open()) {
outErrorMessage = "Failed to open PLY file: " + filePath.string();
return false;
}
PlyHeader header;
if (!ParsePlyHeader(input, header, outErrorMessage)) {
return false;
}
std::unordered_map<std::string_view, const PlyProperty*> propertyMap;
if (!BuildPropertyMap(header, propertyMap, outErrorMessage)) {
return false;
}
GaussianPlyPropertyLayout propertyLayout;
if (!BuildGaussianPlyPropertyLayout(propertyMap, propertyLayout, outErrorMessage)) {
return false;
}
outData.splatCount = header.vertexCount;
outData.colorTextureWidth = GaussianSplatRuntimeData::kColorTextureWidth;
outData.colorTextureHeight =
std::max<uint32_t>(1u, (header.vertexCount + outData.colorTextureWidth - 1u) / outData.colorTextureWidth);
outData.colorTextureHeight = (outData.colorTextureHeight + 15u) / 16u * 16u;
outData.positionData.resize(static_cast<size_t>(header.vertexCount) * GaussianSplatRuntimeData::kPositionStride);
outData.otherData.resize(static_cast<size_t>(header.vertexCount) * GaussianSplatRuntimeData::kOtherStride);
outData.colorData.resize(
static_cast<size_t>(outData.colorTextureWidth) *
static_cast<size_t>(outData.colorTextureHeight) *
GaussianSplatRuntimeData::kColorStride);
outData.shData.resize(static_cast<size_t>(header.vertexCount) * GaussianSplatRuntimeData::kShStride);
outData.boundsMin = {
std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::infinity(),
};
outData.boundsMax = {
-std::numeric_limits<float>::infinity(),
-std::numeric_limits<float>::infinity(),
-std::numeric_limits<float>::infinity(),
};
std::vector<std::byte> vertexBytes(header.vertexStride);
for (uint32_t splatIndex = 0; splatIndex < header.vertexCount; ++splatIndex) {
input.read(reinterpret_cast<char*>(vertexBytes.data()), static_cast<std::streamsize>(vertexBytes.size()));
if (input.gcount() != static_cast<std::streamsize>(vertexBytes.size())) {
outErrorMessage =
"Unexpected end of file while reading Gaussian splat vertex " + std::to_string(splatIndex) + ".";
return false;
}
RawGaussianSplat splat;
if (!ReadGaussianSplat(vertexBytes.data(), propertyLayout, splat, outErrorMessage)) {
return false;
}
LinearizeGaussianSplat(splat);
outData.boundsMin = Min(outData.boundsMin, splat.position);
outData.boundsMax = Max(outData.boundsMax, splat.position);
const size_t positionOffset = static_cast<size_t>(splatIndex) * GaussianSplatRuntimeData::kPositionStride;
WriteFloat3(outData.positionData, positionOffset, splat.position);
const size_t otherOffset = static_cast<size_t>(splatIndex) * GaussianSplatRuntimeData::kOtherStride;
const uint32_t packedRotation = EncodeQuatToNorm10(splat.rotation);
WriteValue(outData.otherData, otherOffset, packedRotation);
WriteFloat3(outData.otherData, otherOffset + sizeof(uint32_t), splat.scale);
const size_t shOffset = static_cast<size_t>(splatIndex) * GaussianSplatRuntimeData::kShStride;
for (uint32_t coefficientIndex = 0; coefficientIndex < GaussianSplatRuntimeData::kShCoefficientCount; ++coefficientIndex) {
const size_t coefficientOffset = shOffset + static_cast<size_t>(coefficientIndex) * sizeof(float) * 3u;
WriteFloat3(outData.shData, coefficientOffset, splat.sh[coefficientIndex]);
}
const uint32_t textureIndex = SplatIndexToTextureIndex(splatIndex);
const size_t colorOffset = static_cast<size_t>(textureIndex) * GaussianSplatRuntimeData::kColorStride;
WriteFloat4(outData.colorData, colorOffset, splat.dc0.x, splat.dc0.y, splat.dc0.z, splat.opacity);
}
return true;
}
bool WriteGaussianSceneSummary(
const std::filesystem::path& filePath,
const GaussianSplatRuntimeData& data,
std::string& outErrorMessage) {
outErrorMessage.clear();
std::ofstream output(filePath, std::ios::binary | std::ios::trunc);
if (!output.is_open()) {
outErrorMessage = "Failed to open summary output file: " + filePath.string();
return false;
}
output << "splat_count=" << data.splatCount << '\n';
output << "color_texture_width=" << data.colorTextureWidth << '\n';
output << "color_texture_height=" << data.colorTextureHeight << '\n';
output << "bounds_min=" << data.boundsMin.x << "," << data.boundsMin.y << "," << data.boundsMin.z << '\n';
output << "bounds_max=" << data.boundsMax.x << "," << data.boundsMax.y << "," << data.boundsMax.z << '\n';
output << "position_bytes=" << data.positionData.size() << '\n';
output << "other_bytes=" << data.otherData.size() << '\n';
output << "color_bytes=" << data.colorData.size() << '\n';
output << "sh_bytes=" << data.shData.size() << '\n';
return output.good();
}
} // namespace XC3DGSD3D12

View File

@@ -0,0 +1,42 @@
#include <windows.h>
#include <shellapi.h>
#include <string>
#include "XC3DGSD3D12/App.h"
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int showCommand) {
XC3DGSD3D12::App app;
int argumentCount = 0;
LPWSTR* arguments = CommandLineToArgvW(GetCommandLineW(), &argumentCount);
for (int index = 1; index + 1 < argumentCount; ++index) {
if (std::wstring(arguments[index]) == L"--frame-limit") {
app.SetFrameLimit(static_cast<unsigned int>(_wtoi(arguments[index + 1])));
++index;
} else if (std::wstring(arguments[index]) == L"--scene") {
app.SetGaussianScenePath(arguments[index + 1]);
++index;
} else if (std::wstring(arguments[index]) == L"--summary-file") {
app.SetSummaryPath(arguments[index + 1]);
++index;
} else if (std::wstring(arguments[index]) == L"--screenshot-file") {
app.SetScreenshotPath(arguments[index + 1]);
++index;
}
}
if (arguments != nullptr) {
LocalFree(arguments);
}
if (!app.Initialize(instance, showCommand)) {
const std::wstring message =
app.GetLastErrorMessage().empty()
? L"Failed to initialize XC 3DGS D3D12 MVS."
: app.GetLastErrorMessage();
MessageBoxW(nullptr, message.c_str(), L"Initialization Error", MB_OK | MB_ICONERROR);
return -1;
}
return app.Run();
}

View File

@@ -670,4 +670,4 @@ ID3D12Resource* CreateTexture2D(ID3D12GraphicsCommandList* inCommandList,
}
ID3D12Device* GetD3DDevice() {
return gD3D12Device;
}
}

View File

@@ -305,4 +305,4 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLi
}
}
return 0;
}
}

251
README.md
View File

@@ -1,6 +1,6 @@
# XCEngine
`XCEngine` 是一个基于 `C++20` 游戏引擎。当前代码中已经包含 `RHI``Rendering``Scene``Resources``Editor``Mono C# Scripting``XCUI` 相关模块;本 README 只描述当前已经实现已经接入的能力。
`XCEngine` 是一个 Windows-first、editor-first 的 `C++20` 游戏引擎工作区。当前代码中已经形成 `RHI``Rendering``Scene``Resources``Editor``Mono C# Scripting``XCUI` 相关主线;本 README 只描述当前已经实现已经接入或已经成为正式工作流一部分的能力。
## 核心特性
@@ -91,7 +91,8 @@
- `UIScreenDocumentHost`
- `UIScreenPlayer`
- `UISystem`
- 新编辑器路线基于 `XCUIEditorLib` 推进当前已经包含树视图、列表视图、菜单、标签条、属性网格、字段控件、workspace / dock / viewport shell 等基础组件
- `tests/UI/` 是当前 XCUI `Core / Editor / Runtime` 三层的正式基础层验证入口
- `new_editor/` 是未来正式编辑器主线不再只是临时沙盒当前已经包含树视图、列表视图、菜单、标签条、属性网格、字段控件、workspace / dock / viewport shell 等基础组件
### 音频
@@ -122,7 +123,7 @@
- 当前 `RHI` 后端为 `D3D12``OpenGL``Vulkan`
- 当前项目资产工作流已经接入 `AssetDatabase + Artifact + Library`
- 当前 `Mono C#` 脚本程序集与运行时链路已经接入
- 当前 `XCUI` 新编辑器路线仍在继续实现中
- 当前 `XCUI` 新编辑器路线`new_editor/` 为正式主线继续推进
## 快速开始
@@ -170,6 +171,12 @@ cmake --build build --config Debug --target xcengine_project_managed_assemblies
cmake --build build --config Debug --target XCUIEditorApp
```
运行 XCUI 新编辑器宿主:
```powershell
.\new_editor\bin\Debug\XCUIEditor.exe
```
## 测试入口
```powershell
@@ -189,10 +196,248 @@ cmake --build build --config Debug --target editor_ui_all_tests
cmake --build build --config Debug --target runtime_ui_all_tests
```
## 完整目录结构
以下目录树按当前工作树整理,保留了真实使用的生成目录与关键子树;省略了 `.git/``build/_deps/`、部分重复资源文件,以及 `docs/used/` 中大量历史归档的长尾条目。
```text
XCEngine/
|- .gitattributes
|- .gitignore
|- AGENT.md
|- CMakeLists.txt
|- README.md
|- build/ # 本地 CMake 构建输出
|- docs/
| |- api/
| | |- XCEngine/
| | |- _guides/
| | |- _meta/
| | |- _tools/
| | `- main.md
| |- issues/
| | `- Editor模块_Console面板错误绑定fallback sink导致运行时日志不显示4.3.md
| |- plan/
| | |- end/
| | | |- RHI模块设计与实现/
| | | | |- RHIFence.md
| | | | `- RHI模块总览.md
| | | `- 编辑器与运行时分层架构设计.md
| | |- 开题报告和任务书/
| | |- 旧版题目/
| | |- 3DGS专用PLY导入器与GaussianSplat资源缓存正式化计划_2026-04-10.md
| | |- API文档目录结构第二轮并行任务板_2026-04-09.md
| | |- API文档目录结构第二轮重构计划_2026-04-09.md
| | |- API文档目录结构重大重构并行任务板_2026-04-09.md
| | |- API文档目录结构重构并行任务板_2026-04-09_第二轮.md
| | |- API文档目录重构计划_2026-04-09.md
| | |- C#脚本模块下一阶段计划.md
| | |- Editor架构说明.md
| | |- Library启动预热与运行时异步加载混合重构计划_2026-04-04.md
| | |- Library启动预热与运行时异步加载混合重构计划_进度更新_2026-04-04.md
| | |- NanoVDB体积云加载阻塞与Runtime上传修复计划_2026-04-10.md
| | |- Unity SRP API参考文档.md
| | |- Unity绝区零开发文档还原版.md
| | |- Unity风格模型导入与Model资产架构重构计划_2026-04-10.md
| | |- XCUI_NewEditor主线重建计划_2026-04-07.md
| | `- XCUI完整架构设计与执行计划.md
| |- used/ # 历史材料、阶段归档和旧计划背景
| | |- API文档实时同步任务池_2026-04-03.md
| | |- Library资产导入与缓存系统收口计划_完成归档_2026-04-03.md
| | |- NanoVDB稀疏体积渲染后续正式化计划_阶段归档_2026-04-10.md
| | |- NanoVDB稀疏体积渲染正式集成计划_第一阶段完成归档_2026-04-09.md
| | |- Renderer当前阶段正式收口计划_阶段归档_2026-04-10.md
| | |- Renderer剩余收口与体积渲染多后端正式化计划_完成归档_2026-04-10.md
| | |- Renderer下一阶段_Unity风格Shader体系正式化计划_完成归档_2026-04-07.md
| | |- SceneViewport_Overlay_Gizmo_Rework_Plan_完成归档_2026-04-04.md
| | |- Unity式SceneView_Gizmo系统完整审查与正式化重构方案_完成归档_2026-04-06.md
| | `- XCUI_Phase_Status_2026-04-05.md
| |- api-skill.md
| |- blueprint-skill.md
| `- blueprint.md
|- editor/
| |- CMakeLists.txt
| |- README.md
| |- bin/
| |- resources/
| | `- Icons/
| `- src/
| |- Actions/
| |- Commands/
| |- ComponentEditors/
| |- Core/
| |- Layers/
| |- Layout/
| |- Managers/
| |- panels/
| |- Platform/
| |- Scripting/
| |- UI/
| |- Utils/
| |- Viewport/
| | |- Passes/
| | |- SceneViewportChrome.*
| | |- SceneViewportInteractionFrame.h
| | |- SceneViewportNavigation.h
| | |- SceneViewportOverlayBuilder.*
| | |- SceneViewportOverlayFrameCache.*
| | |- SceneViewportOverlaySpriteResources.*
| | |- SceneViewportPassSpecs.h
| | |- SceneViewportPicker.*
| | |- SceneViewportResourcePaths.h
| | |- SceneViewportTransformGizmoCoordinator.*
| | `- ViewportHostService.h
| |- Application.cpp
| |- Application.h
| |- Theme.cpp
| `- main.cpp
|- engine/
| |- CMakeLists.txt
| |- include/
| | `- XCEngine/
| | |- Audio/
| | |- Components/
| | |- Core/
| | |- Debug/
| | |- Input/
| | |- Memory/
| | |- Platform/
| | |- Rendering/
| | | |- Caches/
| | | |- Execution/
| | | |- Extraction/
| | | |- FrameData/
| | | |- Materials/
| | | |- Passes/
| | | |- Picking/
| | | |- Pipelines/
| | | `- Planning/
| | |- Resources/
| | | |- AudioClip/
| | | |- Material/
| | | |- Mesh/
| | | |- Shader/
| | | |- Texture/
| | | `- Volume/
| | |- RHI/
| | | |- D3D12/
| | | |- OpenGL/
| | | `- Vulkan/
| | |- Scene/
| | |- Scripting/
| | |- Threading/
| | `- UI/
| |- src/
| |- third_party/
| `- tools/
|- managed/
| |- CMakeLists.txt
| |- GameScripts/
| `- XCEngine.ScriptCore/
|- mvs/
| |- 3DGS-Unity/
| |- D3D12/
| |- OpenGL/
| |- RenderDoc/
| |- ui/
| `- VolumeRenderer/
|- new_editor/
| |- app/
| | |- Host/
| | |- Application.cpp
| | |- Application.h
| | `- main.cpp
| |- bin/
| |- include/
| | `- XCEditor/
| | |- Collections/
| | |- Fields/
| | |- Foundation/
| | |- Shell/
| | `- Widgets/
| |- src/
| | |- Collections/
| | |- Fields/
| | |- Foundation/
| | |- Shell/
| | `- Widgets/
| |- ui/
| | |- themes/
| | `- views/
| `- CMakeLists.txt
|- project/
| |- .xceditor/
| | |- imgui_layout.ini
| | `- thumbs/
| |- Assets/
| | |- Materials/
| | |- Models/
| | |- Scenes/
| | `- Scripts/
| |- Library/
| | |- ArtifactDB/
| | |- Artifacts/
| | |- ScriptAssemblies/
| | `- SourceAssetDB/
| |- Assets.meta
| `- Project.xcproject
|- scripts/
| `- Run-RendererPhaseRegression.ps1
|- tests/
| |- CMakeLists.txt
| |- TEST_SPEC.md
| |- Components/
| |- Core/
| |- Debug/
| |- Editor/
| |- Fixtures/
| |- Input/
| |- Memory/
| |- NewEditor/ # 当前为空的预留测试根目录
| |- Rendering/
| | |- integration/
| | | |- alpha_cutout_scene/
| | | |- camera_post_process_scene/
| | | |- directional_shadow_scene/
| | | |- final_color_scene/
| | | |- multi_light_scene/
| | | |- skybox_scene/
| | | |- volume_occlusion_scene/
| | | |- volume_scene/
| | | `- volume_transform_scene/
| | `- unit/
| |- Resources/
| |- RHI/
| |- Scene/
| |- Scripting/
| |- Threading/
| `- UI/
| |- Core/
| |- Editor/
| |- Runtime/
| `- TEST_SPEC.md
|- 参考/
| |- Fermion/
| |- TransformGizmo/
| |- unity editor/
| |- unity-editor-icons/
| |- unity-icons/
| `- UnityRuntimeSceneGizmo-master/
`- .vscode/
```
## 文档入口
- `docs/api/main.md`
- `editor/README.md`
- `AGENT.md`
- `docs/blueprint.md`
- `tests/TEST_SPEC.md`
- `tests/UI/TEST_SPEC.md`
- `docs/plan/Editor架构说明.md`
- `docs/plan/Library启动预热与运行时异步加载混合重构计划_2026-04-04.md`
- `docs/plan/XCUI_NewEditor主线重建计划_2026-04-07.md`
- `docs/plan/XCUI完整架构设计与执行计划.md`
- `docs/plan/NanoVDB体积云加载阻塞与Runtime上传修复计划_2026-04-10.md`
- `docs/plan/Unity风格模型导入与Model资产架构重构计划_2026-04-10.md`
- `docs/plan/3DGS专用PLY导入器与GaussianSplat资源缓存正式化计划_2026-04-10.md`

View File

@@ -1,8 +1,9 @@
# XCEngine API Documentation Skill
# XCEngine / XCEditor API Documentation Skill
## 目标
这份规范面向维护 `XCEngine` API 文档的 coding agent。它的目标不是“批量生成一套看起来完整的文档”而是持续把当前源码、测试和真实调用链路同步到唯一的 canonical API 文档树里。
这份规范面向维护 `XCEngine` / `XCEditor` API 文档的 coding agent。它的目标不是“批量生成一套看起来完整的文档”而是持续把当前源码、测试和真实调用链路同步到当前活跃的 canonical API 文档树里。
当前仓库已经进入“增量同步”阶段。重点不再是补骨架,而是:
@@ -12,11 +13,14 @@
## 当前范围
API 文档的正式范围包含部分:
API 文档的正式范围包含部分:
1. 引擎 public API
- 对齐 `engine/include/XCEngine/**`
2. Editor source-backed API
2. Editor public API
- 对齐 `new_editor/include/XCEditor/**`
- canonical 文档入口放在 `docs/api/XCEditor/**`
3. Editor source-backed API
- 对齐 `editor/src/**`
- canonical 文档入口仍放在 `docs/api/XCEngine/Editor/**`
@@ -28,35 +32,46 @@ API 文档的正式范围包含两部分:
- 审计结果、阶段性状态
- `docs/api/_tools/**`
- 审计、生成、修链脚本
- `docs/plan/API文档实时同步任务池_2026-04-03.md`
- 当前多任务并行同步池
- `docs/plan/API文档目录*.md`
- 当前活跃 API 计划、重构计划与并行任务板
- `docs/plan/API文档目录结构阶段进度_XCEditor与Model收口_2026-04-10.md`
- 最近一轮 `XCEditor / Model / GaussianSplat` 收口记录
- `docs/used/API文档实时同步任务池_2026-04-03.md`
- 最近一轮已完成的归档基线
- `README.md` / `editor/README.md` / `AGENT.md`
- 这些不是 canonical API 页,但一旦它们引用 API 模块结构、测试目录或 Editor helper 分层,就属于必须同步的活跃协作文档
## 硬约束
1. `docs/api/XCEngine/**` 是唯一 canonical API 树
1. `docs/api/XCEngine/**` `docs/api/XCEditor/**` 是当前两棵 canonical API 树;`docs/api/XCEngine/Editor/**` 继续承载 `editor/src/**` 的 source-backed 文档入口
2. 文档必须以“当前 header + 当前实现 + 当前测试 + 当前真实调用点”为依据,不能按旧文档或命名猜行为。
3. 删除的 API 页面要一起删除,相关交叉链接也必须一起清理。
4. 方法页优先使用源码中的原始函数名;不要擅自改成 kebab-case 或小写别名。
5. 不要把“设计意图”写成“当前实现行为”。
6. 不要为已经删除的 API 保留默认兼容入口页,除非任务明确要求。
7. `rebuild-status.md` 以审计脚本输出为准并发场景下stdout 比旧文件内容更可信。
8. 收口活跃文档;`docs/plan/used/**``docs/used/**` 等归档材料默认保留历史写法,不要为了“顺手统一”去重写归档
8. 默认先收口活跃文档;只有当任务明确涉及归档链、入口路径或历史基线说明时,才修改 `docs/used/**`
9. Windows 工作树里的路径大小写按真实目录名写,例如 `tests/Editor/``tests/Core/``tests/Scripting/`,不要继续传播 `tests/editor/``tests/core/` 之类的历史噪音。
## 工作流
### 1. 开工前先看两文件
### 1. 开工前先看两文件
- 任务池
- `docs/plan/API文档实时同步任务池_2026-04-03.md`
- 最新活跃计划
- `docs/plan/API文档目录*.md`
- `docs/plan/API文档目录结构阶段进度_XCEditor与Model收口_2026-04-10.md`
- 最新审计:
- `docs/api/_meta/rebuild-status.md`
如果任务池和工作树不一致,以当前源码和重新执行审计后的结果为准。
如果活跃计划和工作树不一致,以当前源码和重新执行审计后的结果为准。
如果 `docs/plan/` 下出现日期更晚的 API 相关计划或归档文件,优先读取更新日期更晚的文件,再判断当前任务池是否已经转入 `docs/plan/used/`
如果当前活跃计划没有覆盖你的问题,再回看:
- `docs/used/API文档实时同步任务池_2026-04-03.md`
作为最近一轮完成归档的基线。
如果 `docs/plan/` 下出现日期更晚的 API 相关计划,优先读取日期更晚的活跃文件;当前仓库里的归档根目录是 `docs/used/**`,不要再假设存在 `docs/plan/used/**`
如果工作内容会改到 `README.md``editor/README.md``AGENT.md`
@@ -121,10 +136,13 @@ python -B docs/api/_tools/audit_api_docs.py
- `docs/api/main.md`
- API 根页:
- `docs/api/XCEngine/XCEngine.md`
- `docs/api/XCEditor/XCEditor.md`
- 模块页:
- `docs/api/XCEngine/{ModuleName}/{ModuleName}.md`
- `docs/api/XCEditor/{ModuleName}/{ModuleName}.md`
- 子模块页:
- `docs/api/XCEngine/{ModuleName}/{SubmoduleName}/{SubmoduleName}.md`
- `docs/api/XCEditor/{ModuleName}/{SubmoduleName}/{SubmoduleName}.md`
示例:
@@ -132,6 +150,8 @@ python -B docs/api/_tools/audit_api_docs.py
- `docs/api/XCEngine/Core/Asset/Asset.md`
- `docs/api/XCEngine/Rendering/Passes/Passes.md`
- `docs/api/XCEngine/Editor/Viewport/Viewport.md`
- `docs/api/XCEditor/Foundation/Foundation.md`
- `docs/api/XCEditor/Shell/Shell.md`
### 2. Header / source-backed 目录
@@ -324,7 +344,7 @@ docs/api/XCEngine/Editor/Viewport/SceneViewportRenderPlan/
## 推荐命令
```powershell
rg --files docs/api/XCEngine
rg --files docs/api/XCEngine docs/api/XCEditor
rg --files tests/Editor
rg -n "SymbolName" engine/include engine/src editor/src tests docs/api
python -B docs/api/_tools/audit_api_docs.py

View File

@@ -2,39 +2,49 @@
**命名空间**: `XCEngine::UI::Editor`
**类型**: `submodule`
**类型**: `module`
**描述**: 编辑器基础设施子模块,覆盖命令注册、命令派发、快捷键管理以及主题度量解析入口。
**描述**: `XCEditor` 的基础协议层,覆盖命令注册、命令派发、快捷键管理以及主题度量解析入口。
## 概
## 概
`Foundation` 负责把 shell 壳层的静态配置转换成运行时可执行的命令与快捷键系统。按当前头文件分工:
`Foundation` 解决的是“shell 和控件真正运行前,需要先有一套什么样的 editor 级基础协议”。
按当前代码,职责主要分成四块:
- `UIEditorCommandRegistry.h`
- 声明命令描述、workspace 命令描述注册表校验规则
- 定义 editor 命令描述、workspace 命令描述以及注册表校验规则
- `UIEditorCommandDispatcher.h`
-命令 id 分发到 workspace controller 或 host command handler
- `commandId` 解析为 workspace command 或 host command,并执行 preview / dispatch。
- `UIEditorShortcutManager.h`
- `UIShortcutRegistry` 之上管理绑定、校验冲突并执行调度
- 复用 `XCEngine::UI::UIShortcutRegistry` 完成快捷键绑定、冲突校验与调度
- `UIEditorTheme.h`
- 汇总 Collections / Fields / Shell 各控件的 metrics palette 解析入口
- `Collections / Fields / Shell` 汇总默认 metrics / palette 解析入口
`new_editor/app/Application.cpp` 当前会构造 `UIEditorWorkspaceController`,再通过 `BuildEditorShellShortcutManager(...)` 接入这一层
这层不直接渲染 widget也不直接拥有 workspace 状态;它更像 `XCEditor` 的“命令 + 输入 + 主题”底座
## 当前调用链
- `new_editor/src/Shell/UIEditorShellAsset.cpp` 会从 shell asset 构造命令注册表和快捷键管理器。
- `new_editor/src/Shell/UIEditorMenuModel.cpp` 通过命令注册表、命令派发器和快捷键管理器生成 menu item 的启用态与 shortcut 文案。
- 多组 `tests/UI/Editor/unit/**``tests/UI/Editor/integration/**` 当前直接把这层当作 editor shell 的最小可验证基础设施。
## 公开头文件
- `UIEditorCommandDispatcher.h`
- `UIEditorCommandRegistry.h`
- `UIEditorShortcutManager.h`
- `UIEditorTheme.h`
- [UIEditorCommandRegistry](UIEditorCommandRegistry/UIEditorCommandRegistry.md) - editor 命令描述与注册表校验规则。
- [UIEditorCommandDispatcher](UIEditorCommandDispatcher/UIEditorCommandDispatcher.md) - `commandId -> workspace/host dispatch` 入口。
- [UIEditorShortcutManager](UIEditorShortcutManager/UIEditorShortcutManager.md) - editor 快捷键绑定、校验和调度入口。
- [UIEditorTheme](UIEditorTheme/UIEditorTheme.md) - 默认 metrics / palette 解析入口。
## 当前实现边界
- 当前这里只建立目录索引页,具体头文件页仍需后续补齐
- 该模块不直接渲染 UI而是为 `Shell` 与各控件模块提供命令、快捷键、主题和度量解析能力
- 该模块不直接渲染 UI而是为 `Shell``Collections``Fields` 提供命令、快捷键和主题基础能力
- 当前主题解析函数统一返回静态默认值,还没有引入动态主题切换或 style 覆盖链
- host command 仍然通过 `UIEditorHostCommandHandler` 抽象对外,不把宿主实现细节泄漏回 public header。
## 相关文档
- [XCEditor](../XCEditor.md)
- [Shell](../Shell/Shell.md)
- [Widgets](../Widgets/Widgets.md)
- [Collections](../Collections/Collections.md)
- [Fields](../Fields/Fields.md)

View File

@@ -1,43 +1,60 @@
# UIEditorCommandDispatcher
**命名空间**: `XCEngine`
**命名空间**: `XCEngine::UI::Editor`
**类型**: `class`
**头文件**: `XCEditor/Foundation/UIEditorCommandDispatcher.h`
**描述**: 定义 `XCEditor/Foundation` 子目录中的 `UIEditorCommandDispatcher` public API
**描述**: editor 命令执行入口,负责把 `commandId` 解析为 workspace command 或 host command并执行 preview / dispatch
## 概
## 概
`UIEditorCommandDispatcher.h` `XCEditor/Foundation` 子目录 下的 public header当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。
`UIEditorCommandDispatcher` 位于命令描述层和 shell 工作区控制器之间。它当前做两步:
## 声明概览
1. `Evaluate(...)`
- 校验注册表,查找 `commandId`,把 `ActivePanel` / `FixedPanelId` 等静态描述解析成当前可执行的具体命令。
2. `Dispatch(...)`
-`Evaluate(...)` 成功后,把 workspace 命令交给 `UIEditorWorkspaceController`,或者把 host 命令交给 `UIEditorHostCommandHandler`
| 声明 | 类型 | 说明 |
|------|------|------|
| `UIEditorHostCommandEvaluationResult` | `struct` | 头文件中的公开声明。 |
| `UIEditorHostCommandDispatchResult` | `struct` | 头文件中的公开声明。 |
| `UIEditorHostCommandHandler` | `class` | 头文件中的公开声明。 |
| `UIEditorCommandEvaluationCode` | `enum class` | 头文件中的公开声明。 |
| `UIEditorCommandEvaluationResult` | `struct` | 头文件中的公开声明。 |
| `UIEditorCommandDispatchStatus` | `enum class` | 头文件中的公开声明。 |
| `UIEditorCommandDispatchResult` | `struct` | 头文件中的公开声明。 |
| `UIEditorCommandDispatcher` | `class` | 头文件中的公开声明。 |
这让 menu、shortcut 和 shell interaction 都可以共享同一套命令求值与分发逻辑。
## 公共方法
## 主要声明
| 方法 | 描述 |
| 声明 | 角色 |
|------|------|
| [UIEditorCommandDispatcher()](Constructor.md) | 构造对象。 |
| [GetCommandRegistry](GetCommandRegistry.md) | 获取相关状态或对象。 |
| [SetHostCommandHandler](SetHostCommandHandler.md) | 设置相关状态或配置。 |
| [GetHostCommandHandler](GetHostCommandHandler.md) | 获取相关状态或对象。 |
| [ValidateConfiguration](ValidateConfiguration.md) | 公开方法,详见头文件声明。 |
| [Evaluate](Evaluate.md) | 公开方法,详见头文件声明。 |
| [Dispatch](Dispatch.md) | 公开方法,详见头文件声明。 |
| `UIEditorHostCommandHandler` | 宿主命令执行抽象,允许 shell 外部接管 host 命令。 |
| `UIEditorCommandEvaluationCode` | 命令求值失败或成功的细分原因。 |
| `UIEditorCommandEvaluationResult` | `Evaluate(...)` 的结果,包含解析出的 workspace command 与 preview 结果。 |
| `UIEditorCommandDispatchStatus` | 最终 dispatch 状态。 |
| `UIEditorCommandDispatchResult` | `Dispatch(...)` 的结果。 |
| `UIEditorCommandDispatcher` | 命令求值与分发器本体。 |
## 当前实现行为
- `ValidateConfiguration()` 直接复用 `ValidateUIEditorCommandRegistry(...)`
- `Evaluate(...)` 当前会:
- 在注册表无效时返回 `InvalidCommandRegistry`
- 在命令不存在时返回 `UnknownCommandId`
-`Host` 命令转发给 `UIEditorHostCommandHandler::EvaluateHostCommand(...)`
-`Workspace` 命令解析 `panelSource`
-`ActivePanel` 但当前 workspace 没有 active panel 时返回 `MissingActivePanel`
- 复制一份 controller 做 preview dispatch`previewResult` 预测命令是否会被拒绝
- `Dispatch(...)` 当前先调用 `Evaluate(...)`,只有可执行时才真正分发。
- host 命令和 workspace 命令共享统一结果结构,但最终执行路径不同。
## 测试与调用链
- `tests/UI/Editor/unit/test_ui_editor_command_dispatcher.cpp`
- 当前覆盖 active panel 解析、缺少 active panel 的拒绝路径,以及 dispatch 后 workspace 状态变更。
- `tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp`
- 当前通过 shell interaction 间接消费命令派发器。
- `tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp`
- 当前把命令派发器挂进 menu bar / context menu 的真实交互路径。
## 相关文档
- [当前目录](../Foundation.md) - 返回 `Foundation` 平行目录
- [API 总索引](../../../main.md) - 返回顶层索引
- [Foundation](../Foundation.md)
- [UIEditorCommandRegistry](../UIEditorCommandRegistry/UIEditorCommandRegistry.md)
- [UIEditorShortcutManager](../UIEditorShortcutManager/UIEditorShortcutManager.md)
- [UIEditorWorkspaceController](../../Shell/UIEditorWorkspaceController/UIEditorWorkspaceController.md)

View File

@@ -1,36 +1,59 @@
# UIEditorCommandRegistry
**命名空间**: `XCEngine`
**命名空间**: `XCEngine::UI::Editor`
**类型**: `struct`
**类型**: `enums + structs + functions`
**头文件**: `XCEditor/Foundation/UIEditorCommandRegistry.h`
**描述**: 定义 `XCEditor/Foundation` 子目录中的 `UIEditorCommandRegistry` public API
**描述**: editor 命令描述协议与注册表校验入口,负责定义 workspace/host 命令的静态声明格式
## 概
## 概
`UIEditorCommandRegistry.h``XCEditor/Foundation` 子目录 下的 public header当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。
`UIEditorCommandRegistry.h``XCEditor` 命令系统的静态描述层。它不执行命令,而是先回答这些问题:
## 声明概览
- 一个 editor 命令的 `commandId``displayName` 和命令种类是什么。
- 这个命令对应的是 workspace command 还是 host command。
- 如果是 workspace command它的 panel 路由来自固定 panel、当前 active panel还是根本不需要 panel。
- 当前整张命令表是否合法,是否存在重复 id、空 displayName 或错误的 panel source 组合。
| 声明 | 类型 | 说明 |
|------|------|------|
| `UIEditorCommandKind` | `enum class` | 头文件中的公开声明。 |
| `UIEditorCommandPanelSource` | `enum class` | 头文件中的公开声明。 |
| `UIEditorWorkspaceCommandDescriptor` | `struct` | 头文件中的公开声明。 |
| `UIEditorCommandDescriptor` | `struct` | 头文件中的公开声明。 |
| `UIEditorCommandRegistry` | `struct` | 头文件中的公开声明。 |
| `UIEditorCommandRegistryValidationCode` | `enum class` | 头文件中的公开声明。 |
| `UIEditorCommandRegistryValidationResult` | `struct` | 头文件中的公开声明。 |
## 主要声明
## 结构体成员
| 声明 | 角色 |
|------|------|
| `UIEditorCommandKind` | 区分 `Workspace``Host` 命令。 |
| `UIEditorCommandPanelSource` | 描述 panel 路由来自 `None / FixedPanelId / ActivePanel`。 |
| `UIEditorWorkspaceCommandDescriptor` | 把高层命令映射到 `UIEditorWorkspaceCommandKind + panel routing`。 |
| `UIEditorCommandDescriptor` | 单个 editor 命令的完整静态描述。 |
| `UIEditorCommandRegistry` | 命令描述表。 |
| `UIEditorCommandRegistryValidationCode` | 注册表校验失败原因。 |
| `FindUIEditorCommandDescriptor(...)` | 通过 `commandId` 查找描述。 |
| `ValidateUIEditorCommandRegistry(...)` | 校验命令表的完整性与路由合法性。 |
| 成员 | 类型 | 描述 | 默认值 |
|------|------|------|--------|
| `commands` | `std::vector<UIEditorCommandDescriptor>` | 结构体公开字段。 | `{}` |
## 当前实现行为
- `ValidateUIEditorCommandRegistry(...)` 会拒绝:
-`commandId`
-`displayName`
- 重复 `commandId`
- host 命令错误携带 workspace panel routing
- 需要 panel 的 workspace 命令却没有 panel source
- 不需要 panel 的命令却错误携带 `FixedPanelId` / `ActivePanel`
- `FindUIEditorCommandDescriptor(...)` 当前按线性遍历 `registry.commands` 查找。
- `CommandKindRequiresPanelId(...)` 当前把 `OpenPanel / ClosePanel / ShowPanel / HidePanel / ActivatePanel` 视为需要 panel 路由;`ResetWorkspace` 不需要。
## 测试与调用链
- `tests/UI/Editor/unit/test_ui_editor_command_registry.cpp`
- 当前覆盖 descriptor 查找、重复 id、空 displayName 和 panel source 校验。
- `new_editor/src/Foundation/UIEditorCommandDispatcher.cpp`
- 直接依赖这份注册表做命令解析和配置校验。
- `new_editor/src/Shell/UIEditorMenuModel.cpp`
- 当前用它决定 menu item 的 command 元数据与启用态来源。
## 相关文档
- [当前目录](../Foundation.md) - 返回 `Foundation` 平行目录
- [API 总索引](../../../main.md) - 返回顶层索引
- [Foundation](../Foundation.md)
- [UIEditorCommandDispatcher](../UIEditorCommandDispatcher/UIEditorCommandDispatcher.md)
- [UIEditorShortcutManager](../UIEditorShortcutManager/UIEditorShortcutManager.md)
- [UIEditorWorkspaceController](../../Shell/UIEditorWorkspaceController/UIEditorWorkspaceController.md)

View File

@@ -1,45 +1,64 @@
# UIEditorShortcutManager
**命名空间**: `XCEngine`
**命名空间**: `XCEngine::UI::Editor`
**类型**: `class`
**头文件**: `XCEditor/Foundation/UIEditorShortcutManager.h`
**描述**: 定义 `XCEditor/Foundation` 子目录中的 `UIEditorShortcutManager` public API
**描述**: editor 快捷键绑定与调度入口,负责把 `UIShortcutRegistry` 的匹配结果连接到 editor 命令系统
## 概
## 概
`UIEditorShortcutManager.h` `XCEditor/Foundation` 子目录 下的 public header当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明。
`UIEditorShortcutManager``UIShortcutRegistry` `UIEditorCommandDispatcher` 之间增加了一层 editor 语义:
## 声明概览
- 先校验 command registry 和 shortcut binding 是否自洽
- 再根据输入事件和 `UIShortcutContext` 找到最佳匹配 binding
- 最后把匹配结果转换成 editor command dispatch
| 声明 | 类型 | 说明 |
|------|------|------|
| `UIEditorShortcutManagerValidationCode` | `enum class` | 头文件中的公开声明。 |
| `UIEditorShortcutManagerValidationResult` | `struct` | 头文件中的公开声明。 |
| `UIEditorShortcutDispatchStatus` | `enum class` | 头文件中的公开声明。 |
| `UIEditorShortcutDispatchResult` | `struct` | 头文件中的公开声明。 |
| `UIEditorShortcutManager` | `class` | 头文件中的公开声明。 |
它同时还承担 shortcut 文案格式化,所以 menu bar / context menu 可以直接显示“当前推荐快捷键文本”。
## 公共方法
## 主要声明
| 方法 | 描述 |
| 声明 | 角色 |
|------|------|
| [UIEditorShortcutManager()](Constructor.md) | 构造对象。 |
| [GetCommandDispatcher](GetCommandDispatcher.md) | 获取相关状态或对象。 |
| [SetHostCommandHandler](SetHostCommandHandler.md) | 设置相关状态或配置。 |
| [GetHostCommandHandler](GetHostCommandHandler.md) | 获取相关状态或对象。 |
| [GetCommandRegistry](GetCommandRegistry.md) | 获取相关状态或对象。 |
| [GetShortcutRegistry](GetShortcutRegistry.md) | 获取相关状态或对象。 |
| [RegisterBinding](RegisterBinding.md) | 注册对象、回调或映射。 |
| [UnregisterBinding](UnregisterBinding.md) | 取消注册对象、回调或映射。 |
| [ClearBindings](ClearBindings.md) | 清空内部数据。 |
| [ValidateConfiguration](ValidateConfiguration.md) | 公开方法,详见头文件声明。 |
| [GetPreferredShortcutText](GetPreferredShortcutText.md) | 获取相关状态或对象。 |
| [Dispatch](Dispatch.md) | 公开方法,详见头文件声明。 |
| `UIEditorShortcutManagerValidationCode` | 快捷键配置校验失败原因。 |
| `UIEditorShortcutManagerValidationResult` | 快捷键配置校验结果。 |
| `UIEditorShortcutDispatchStatus` | `NoMatch / Suppressed / Dispatched / Rejected` 四类结果。 |
| `UIEditorShortcutDispatchResult` | dispatch 后返回的命令 id、scope、owner 和 workspace command 结果。 |
| `UIEditorShortcutManager` | 快捷键绑定、校验和调度器本体。 |
## 当前实现行为
- `ValidateConfiguration()` 当前会拒绝:
- 无效 command registry
- binding 缺少 `commandId`
- binding 指向未知命令
- `keyCode == 0`
-`Global` scope 却没有 `ownerId`
- chord/scope/owner 完全冲突的重复 binding
- `GetPreferredShortcutText(...)` 会按 `Widget > Panel > Window > Global` 的展示优先级挑选 binding并把 chord 格式化成 `Ctrl+H` 这类文本。
- `Dispatch(...)` 当前流程是:
- 配置校验
- `m_shortcutRegistry.Match(...)`
-`textInputActive == true`,返回 `Suppressed`
- 调用 `m_commandDispatcher.Dispatch(...)`
- 把匹配到的 scope / owner 和 command result 一并回填到结果结构
## 测试与调用链
- `tests/UI/Editor/unit/test_ui_editor_shortcut_manager.cpp`
- 当前覆盖未知命令、冲突 chord、active-panel 路由、panel scope 优先于 global以及 text-input suppression。
- `tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp`
- 当前验证 shortcut manager 在 editor shell 状态路径上的实际行为。
- `new_editor/src/Shell/UIEditorShellAsset.cpp`
- 当前用 asset 中的 command registry + bindings 构建 editor 快捷键管理器。
- `new_editor/src/Shell/UIEditorMenuModel.cpp`
- 当前通过它读取 shortcut 文案并参与 menu item 启用态生成。
## 相关文档
- [当前目录](../Foundation.md) - 返回 `Foundation` 平行目录
- [API 总索引](../../../main.md) - 返回顶层索引
- [Foundation](../Foundation.md)
- [UIEditorCommandRegistry](../UIEditorCommandRegistry/UIEditorCommandRegistry.md)
- [UIEditorCommandDispatcher](../UIEditorCommandDispatcher/UIEditorCommandDispatcher.md)
- [UIShortcutRegistry](../../../XCEngine/UI/Input/UIShortcutRegistry/UIShortcutRegistry.md)

View File

@@ -1,18 +1,44 @@
# UIEditorTheme
**命名空间**: `XCEngine`
**命名空间**: `XCEngine::UI::Editor`
**类型**: `header`
**类型**: `theme-accessor header`
**头文件**: `XCEditor/Foundation/UIEditorTheme.h`
**描述**: 定义 `XCEditor/Foundation` 子目录中的 `UIEditorTheme` public API
**描述**: `XCEditor` 默认主题解析入口,向 `Collections / Fields / Shell` 各类控件暴露统一的 metrics 与 palette 访问函数
## 概
## 概
`UIEditorTheme.h` `XCEditor/Foundation` 子目录 下的 public header当前页面作为平行目录中的 canonical 总览,用于汇总该头文件暴露的主要声明
`UIEditorTheme.h` 当前不是“主题对象 + 可编辑主题树”,而是一组默认值解析函数
这组函数把下游控件需要的 theme 数据集中暴露出来,包括:
- `Fields`
- bool / number / text / vector / enum / color / object / asset / property-grid
- `Collections`
- list-view / tree-view / scroll-view / tab-strip
- `Shell`
- menu-bar / menu-popup / status-bar / panel-frame / dock-host / viewport-slot / shell-compose / shell-interaction
## 当前实现行为
- `new_editor/src/Foundation/UIEditorTheme.cpp` 通过 `GetDefaultValue<T>()` + 一组宏生成所有 `ResolveUIEditor*` 默认访问器。
- 每个访问器当前都返回静态默认构造值,生命周期稳定,可安全被多处重复读取。
- 当前还没有:
- 主题资源加载
- runtime 主题切换
- 层叠式 style override
- 按 workspace / panel 定制局部 palette
## 当前调用链
- 多组 `tests/UI/Editor/integration/shell/*/main.cpp` 当前直接依赖这些默认 theme 访问器来构造演示场景。
- `Collections``Fields``Shell` 的控件页当前都把这里视为默认 metrics / palette 的公共来源。
## 相关文档
- [当前目录](../Foundation.md) - 返回 `Foundation` 平行目录
- [API 总索引](../../../main.md) - 返回顶层索引
- [Foundation](../Foundation.md)
- [Fields](../../Fields/Fields.md)
- [Collections](../../Collections/Collections.md)
- [Shell](../../Shell/Shell.md)

View File

@@ -10,7 +10,7 @@
## 角色概述
`CameraComponent` 是当前渲染管线里的相机配置组件。它本身不持有 view/projection 矩阵,也不直接执行渲染;真正的消费方是 [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md)。
`CameraComponent` 是当前渲染管线里的相机配置组件。它本身不持有 view/projection 矩阵,也不直接执行渲染;真正的消费方是 [RenderSceneExtractor](../../Rendering/Extraction/RenderSceneExtractor/RenderSceneExtractor.md)。
从职责上看,它更像 Unity 里“挂在对象上的 Camera 配置数据”,而不是一个自带完整渲染流程的重型对象。
@@ -39,7 +39,7 @@
### 3. 相机选择规则由渲染场景提取器决定
当前 [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md) 的选择顺序是:
当前 [RenderSceneExtractor](../../Rendering/Extraction/RenderSceneExtractor/RenderSceneExtractor.md) 的选择顺序是:
1. 先用可用的 override camera
2. 否则在所有可用主相机里挑 `Depth` 最大的那个
@@ -116,5 +116,5 @@
- [当前模块](../Components.md)
- [GameObject](../GameObject/GameObject.md)
- [TransformComponent](../TransformComponent/TransformComponent.md)
- [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md)
- [RenderSceneExtractor](../../Rendering/Extraction/RenderSceneExtractor/RenderSceneExtractor.md)
- [API 总索引](../../../main.md)

View File

@@ -160,5 +160,5 @@
- [当前模块](../Components.md)
- [MeshRendererComponent](../MeshRendererComponent/MeshRendererComponent.md)
- [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md)
- [RenderSceneExtractor](../../Rendering/Extraction/RenderSceneExtractor/RenderSceneExtractor.md)
- [API 总索引](../../../main.md)

View File

@@ -182,6 +182,6 @@ renderLayer=0;
- [当前模块](../Components.md)
- [MeshFilterComponent](../MeshFilterComponent/MeshFilterComponent.md)
- [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md)
- [RenderSceneExtractor](../../Rendering/Extraction/RenderSceneExtractor/RenderSceneExtractor.md)
- [BuiltinForwardPipeline](../../Rendering/Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md)
- [API 总索引](../../../main.md)

View File

@@ -6,301 +6,147 @@
**头文件**: `XCEngine/Core/Asset/ArtifactFormats.h`
**描述**: 定义 `Texture``Material``Mesh``Shader` artifact 的二进制头结构schema 常量,供 `AssetDatabase` 写出、各 loader 回读共享
**描述**: 定义项目 artifact 的二进制头结构schema 常量与记录布局,供 `AssetDatabase` 写出、各 loader 回读以及资源系统共享使用
## 角色概述
`ArtifactFormats.h`项目资产导入链路的磁盘格式契约层。
`ArtifactFormats.h` 是资产导入链路的磁盘格式契约层。它回答的是:
它回答的是:
- 某类 source asset 导入后会写出什么主 artifact
- 二进制文件头里有哪些 schema 常量和 magic
- loader 回读时应如何解释结构化头信息
- `AssetDatabase` 把 source asset 导入到 `Library/Artifacts/...` 时,头结构和 magic 是什么
- `TextureLoader``MaterialLoader``MeshLoader``ShaderLoader` 回读时,二进制布局该怎样解释
它不负责:
- 扫描 `Assets`
- 生成 GUID
- 维护 source / artifact 数据库
- 直接触发运行时异步加载
它本身不负责扫描项目、生成 GUID 或执行导入;它只定义“写什么”和“读什么”。
## 当前 schema 常量
- `kTextureArtifactSchemaVersion = 1`
- `kMaterialArtifactSchemaVersion = 4`
- `kMeshArtifactSchemaVersion = 2`
- `kShaderArtifactSchemaVersion = 4`
- `kShaderArtifactSchemaVersion = 5`
- `kUIDocumentArtifactSchemaVersion = 2`
- `kVolumeFieldArtifactSchemaVersion = 2`
- `kModelArtifactSchemaVersion = 1`
- `kGaussianSplatArtifactSchemaVersion = 1`
## Texture artifact: `.xctex`
这些常量说明 artifact 契约已经不再只覆盖传统的贴图、材质、网格和 shader模型层级资源与 Gaussian Splat 资源也已经进入了格式层。
- 头结构: `TextureArtifactHeader`
## 当前格式覆盖
### Texture artifact: `.xctex`
- 文件头: `TextureArtifactHeader`
- magic: `XCTEX01`
- 记录纹理维度、格式、mip 层数、数组大小与像素 payload 大小
`TextureArtifactHeader` 当前记录:
- `textureType`
- `textureFormat`
- `width`
- `height`
- `depth`
- `mipLevels`
- `arraySize`
- `pixelDataSize`
写入顺序当前是:
1. `TextureArtifactHeader`
2. 原始像素 payload
## Material artifact: `.xcmat`
### Material artifact: `.xcmat`
- 文件头: `MaterialArtifactFileHeader`
- 主头结构: `MaterialArtifactHeader`
- 属性结构: `MaterialPropertyArtifact`
- 主头: `MaterialArtifactHeader`
- 属性记录: `MaterialPropertyArtifact`
- schema: `4`
- magic: `XCMAT04`
- 支持 render-state override、keyword、property 与 texture binding
### 当前正文布局
### Mesh artifact: `.xcmesh`
`.xcmat` 当前不是“一个大 struct 一次性写完”,而是“文件头 + 变长字符串段 + 固定头 + 变长数组”的布局:
1. `MaterialArtifactFileHeader`
2. 材质名字符串
3. source 材质路径字符串
4. shader 路径字符串
5. shader pass 字符串
6. `MaterialArtifactHeader`
7. `tagCount` 对 tag 名/值字符串
8. `keywordCount` 个 keyword 字符串
9. `propertyCount` 个属性名字符串 + `MaterialPropertyArtifact`
10. `textureBindingCount` 组 texture binding 三元组:
- binding name
- 编码后的 `AssetRef` 字符串
- 可选 texture path 字符串
### `MaterialArtifactHeader`
当前记录:
- `renderQueue`
- `renderState`
- `tagCount`
- `hasRenderStateOverride`
- `keywordCount`
- `propertyCount`
- `textureBindingCount`
### `MaterialPropertyArtifact`
只记录:
- `propertyType`
- `MaterialProperty::Value`
当前 writer 只把非 texture 属性写进 property 段texture / cubemap 绑定统一走 texture binding 段。
### render-state override 语义
`MaterialArtifactHeader::hasRenderStateOverride` 当前显式区分两种情况:
- 材质真的声明了 `renderState`
- 材质只是保留了一份默认 `MaterialRenderState` 缓存
回读侧 `MaterialLoader` 当前会据此恢复 `HasRenderStateOverride()`
### texture binding 语义
texture binding 当前不再只保存 path。
写入侧 `AssetDatabase::WriteMaterialArtifactFile(...)` 会尽量为每个 binding 同时保存:
- 编码 `AssetRef`
格式是 `guid,localID,resourceTypeInt`
- 可选 path
`AssetRef` 的求值优先级当前是:
1. binding 自己显式持有的 `AssetRef`
2. 已加载纹理句柄映射出的 `AssetRef`
3. 纹理 path 反查得到的 `AssetRef`
path 的求值优先级当前是:
1. 已加载纹理映射出的 artifact / source path
2. binding 自己保存的 `texturePath`
回读侧 `MaterialLoader` 当前会:
- 先解码 `AssetRef`
- 再解析 path
- 有效 `AssetRef` 优先走 `Material::SetTextureAssetRef(...)`
- 否则退到 `Material::SetTexturePath(...)`
这意味着当前 `.xcmat` 版本可以只靠 `AssetRef` 恢复稳定引用path 只作为可选辅助信息存在。
## Mesh artifact: `.xcmesh`
- 头结构: `MeshArtifactHeader`
- 文件头: `MeshArtifactHeader`
- schema: `2`
- magic: `XCMESH2`
- 记录顶点流、索引流、section、bounds 与材质引用路径
`MeshArtifactHeader` 当前记录:
### Model artifact: `.xcmodel`
- `vertexCount`
- `vertexStride`
- `vertexAttributes`
- `indexCount`
- `use32BitIndex`
- `sectionCount`
- `materialCount`
- `boundsMin`
- `boundsMax`
- `vertexDataSize`
- `indexDataSize`
这是本轮最关键的新内容之一。当前模型 artifact 由以下结构组成:
`.xcmesh` 当前写入顺序是:
- `ModelArtifactFileHeader`
- `ModelArtifactHeader`
- `ModelNodeArtifactHeader`
- `ModelMeshBindingArtifact`
- `ModelMaterialBindingArtifact`
1. `MeshArtifactHeader`
2. `sectionCount``MeshSection`
3. 顶点数据 blob
4. 索引数据 blob
5. `materialCount` 个材质 artifact 路径字符串
它表达的不是单个 mesh payload而是一个结构化模型资源
## Shader artifact
- 节点层级
- 根节点索引
- 每个节点的局部位移、旋转与缩放
- mesh 绑定范围
- 材质槽位绑定范围
真实的 mesh 数据仍然写在同目录的 `mesh_<n>.xcmesh` 中;`main.xcmodel` 更像“模型装配说明书”负责把层级、mesh 和材质绑定组织起来。
### Shader artifact: `.xcshader`
- 文件头: `ShaderArtifactFileHeader`
- 主头结构: `ShaderArtifactHeader`
- pass 头结构: `ShaderPassArtifactHeader`
- 属性结构: `ShaderPropertyArtifact`
- 资源结构: `ShaderResourceArtifact`
- variant 头结构: `ShaderVariantArtifactHeader`
- schema: `4`
- magic: `XCSHD04`
- 主头: `ShaderArtifactHeader`
- pass 头: `ShaderPassArtifactHeaderV4`
- 变体头: `ShaderVariantArtifactHeader`
- schema: `5`
### 当前正文布局
它已经不是简单的 shader 源码缓存,而是包含 property、resource、keyword declaration、variant 与可选编译产物的完整 artifact 格式。
`.xcshader` 当前采用“文件头 + 名称/路径字符串 + 逻辑 shader 头 + property 段 + pass 段”的顺序写出:
### VolumeField artifact: `.xcvol`
1. `ShaderArtifactFileHeader`
2. shader 名称字符串
3. source shader 路径字符串
4. fallback 字符串
5. `ShaderArtifactHeader`
6. `propertyCount` 组 property 数据:
- property 名称字符串
- display name 字符串
- default value 字符串
- semantic 字符串
- `ShaderPropertyArtifact`
7. `passCount` 组 pass 数据:
- pass 名称字符串
- `ShaderPassArtifactHeaderV4`
- `tagCount` 对 tag 名/值字符串
- `resourceCount` 组资源绑定:
- resource 名称字符串
- semantic 字符串
- `ShaderResourceArtifact`
- `keywordDeclarationCount` 组 keyword 声明:
- `ShaderKeywordDeclarationArtifactHeader`
- `optionCount` 个 option 字符串
- `variantCount` 组 stage variant
- `ShaderVariantArtifactHeader`
- entry point 字符串
- profile 字符串
- source code 字符串
- `keywordCount` 个 required keyword 字符串
- 可选 `compiledBinary` payload长度由 `compiledBinarySize` 指定)
- 文件头: `VolumeFieldArtifactHeader`
- schema: `2`
- 记录存储类型、bounds、voxel size、index bounds、grid metadata 与 payload 大小
### 各头结构当前记录
### GaussianSplat artifact
- `ShaderArtifactHeader`
- `propertyCount`
- `passCount`
- `ShaderPassArtifactHeader`
- `tagCount`
- `resourceCount`
- `keywordDeclarationCount`
- `variantCount`
- `ShaderPassArtifactHeaderV4`
- `tagCount`
- `resourceCount`
- `keywordDeclarationCount`
- `variantCount`
- `hasFixedFunctionState`
- `fixedFunctionState`
- `ShaderPropertyArtifact`
- `propertyType`
- `ShaderResourceArtifact`
- `resourceType`
- `set`
- `binding`
- `ShaderVariantArtifactHeader`
- `stage`
- `language`
- `backend`
- `keywordCount`
- `compiledBinarySize`
- `ShaderKeywordDeclarationArtifactHeader`
- `declarationType`
- `optionCount`
当前头文件已经定义了完整的 Gaussian Splat artifact 契约:
## 导入与消费链路
- `GaussianSplatArtifactFileHeader`
- `GaussianSplatArtifactHeader`
- `GaussianSplatArtifactSectionRecord`
### 写入侧
头信息覆盖了:
- `AssetDatabase::ImportTextureAsset()` 产出 `main.xctex`
- `AssetDatabase::ImportMaterialAsset()` 产出 `main.xcmat`
- `AssetDatabase::ImportModelAsset()` 产出 `main.xcmesh`
- `AssetDatabase::ImportShaderAsset()` 产出 `main.xcshader`
- splat / chunk / camera 数量
- 包围盒
- 各 section 的格式声明
- section 数量与总 payload 大小
- 每个 section 的类型、格式、偏移、字节大小、元素个数与步长
模型导入还会在同一个 artifact 目录里附带生成:
这说明 Gaussian Splat 至少在磁盘格式层已经被认真建模,而不是临时塞进某个通用二进制资源。
- `texture_<n>.xctex`
## 当前导入链路中的真实产物
按本轮审查到的 `AssetDatabase` 实现,当前明确写出的主 artifact 包括:
- 纹理: `main.xctex`
- 材质: `main.xcmat`
- 模型: `main.xcmodel`
- Shader: `main.xcshader`
- 体积: `main.xcvol`
其中模型导入还会在同一 artifact 目录下生成:
- `mesh_<n>.xcmesh`
- `material_<n>.xcmat`
- `texture_<n>.xctex`
### 读取侧
这证明 `ArtifactFormats.h` 现在服务的是“模型主 artifact + 子资源 artifact”这种组合式导入结果而不是只有单文件资产。
- `TextureLoader` 回读 `.xctex`
- `MaterialLoader` 回读 `.xcmat`
- `MeshLoader` 回读 `.xcmesh`
- `ShaderLoader` 回读 `.xcshader`
## 设计理解
`ResourceManager::LoadResource()` 在项目模式下会先经 `AssetImportService::EnsureArtifact()` 准备 `ImportedAsset`,再把 `runtimeLoadPath` 交给具体 loader。
把 artifact 契约集中定义在一个头文件里,有几个非常现实的好处:
## 兼容性与边界
- `AssetDatabase` 与 loader 可以共享完全一致的格式定义
- 新资源类型可以先把磁盘契约建起来,再逐步补 importer、loader、编辑器与运行时渲染链路
- schema 版本升级点集中,后续做兼容迁移时不必在多个模块里分散维护
### 当前版本语义
这类集中式格式契约,在商业引擎的资产导入层非常常见。
- loader 目前显式检查的仍主要是 magic
- `schemaVersion` 已经写进文件头,但当前代码没有基于它做多版本分支读取
## 边界与注意事项
因此当前兼容策略更接近“magic + 整套代码同步升级”,而不是长期维护多代 reader。
### 二进制稳定性边界
当前 artifact 文件仍直接写入/读回这些 C++ 原生布局:
- `TextureArtifactHeader`
- `MaterialArtifactFileHeader`
- `MaterialArtifactHeader`
- `MaterialPropertyArtifact`
- `MeshArtifactHeader`
- `ShaderArtifactFileHeader`
- `ShaderArtifactHeader`
- `ShaderPassArtifactHeader`
- `ShaderPropertyArtifact`
- `ShaderResourceArtifact`
- `ShaderVariantArtifactHeader`
这意味着它本质上仍是引擎内部缓存格式,不是长期稳定的外部交换格式。
- 这些结构本质上仍然是引擎内部 artifact 契约,不是长期稳定的外部交换格式
- `GaussianSplat` 虽然已经有格式契约,但在本轮代码里还没有看到和 `AssetDatabase` 同等级的公开 importer 入口
- schema 常量已就位,但多版本 reader/upgrade 路径是否完整,仍要结合具体 loader 再看
## 相关文档
- [当前模块](../Asset.md)
- [AssetDatabase](../AssetDatabase/AssetDatabase.md)
- [AssetRef](../AssetRef/AssetRef.md)
- [ResourceManager](../ResourceManager/ResourceManager.md)
- [Material](../../../Resources/Material/Material/Material.md)
- [MaterialLoader](../../../Resources/Material/MaterialLoader/MaterialLoader.md)
- [ResourceTypes](../ResourceTypes/ResourceTypes.md)
- [Model](../../../Resources/Model/Model.md)
- [GaussianSplat](../../../Resources/GaussianSplat/GaussianSplat.md)
- [VolumeField](../../../Resources/Volume/VolumeField/VolumeField.md)
- [API 总索引](../../../../main.md)

View File

@@ -4,20 +4,20 @@
**类型**: `submodule`
**描述**: 定义项目资产数据库、artifact 缓存、运行时资源加载、句柄、缓存与项目资产查询快照这一整套资源基础设施。
**描述**: 项目资产身份、artifact 缓存、导入服务、热路径索引与运行时资源加载共同组成的资源基础设施模块
## 概述
## 模块概述
当前 `Core/Asset` 已经不只是“运行时按路径加载资源”的薄层接口,而是可以分成三层来看:
当前 `Core/Asset` 已经不是单纯的“按路径加载资源”接口,而是可以分成三层来看:
1. [AssetDatabase](AssetDatabase/AssetDatabase.md)
负责扫描项目 `Assets`、维护 `.meta`、保存 source/artifact 索引,并在需要时把源资产导入 `Library/Artifacts/...` 下的 artifact。
负责扫描项目 `Assets`、维护 `.meta` source/artifact 快照,并在需要时把 source asset 导入 `Library/Artifacts/...`
2. [AssetImportService](AssetImportService/AssetImportService.md) + [ProjectAssetIndex](ProjectAssetIndex/ProjectAssetIndex.md)
负责把 `AssetDatabase` 包装成线程安全服务,并导出供热路径查询使用的 path/GUID snapshot。
负责把 `AssetDatabase` 包装成线程安全服务,并导出供热路径使用的 path/GUID 查询快照
3. [ResourceManager](ResourceManager/ResourceManager.md)
负责运行时加载、缓存、句柄与 loader 分发;当设置了项目根目录后,它会先经 `AssetImportService` 准备 artifact通过 `ProjectAssetIndex``AssetRef` / 路径查询。
负责运行时加载、缓存、句柄与 loader 分发;当设置了项目根目录后,它会先经 `AssetImportService` 准备 artifact把可直接加载的路径交给具体 loader
这意味着当前资源系统的真实链路是
真实链路大致如下
```text
Assets/... source files
@@ -28,32 +28,48 @@ Assets/... source files
-> concrete loader / runtime resource
```
其中,[AssetGUID](AssetGUID/AssetGUID.md)、[AssetRef](AssetRef/AssetRef.md) 和 [ArtifactFormats](ArtifactFormats/ArtifactFormats.md) 分别补上了“资产身份”“资产引用”和“artifact 磁盘格式”这三块基础协议。
## 当前导入与 artifact 覆盖
## 当前实现关系
按当前 `AssetDatabase::GetImporterNameForPath()` 与导入实现,已经明确落地的项目资产类型包括:
- `editor/src/Application.cpp` 初始化编辑器时会调用 `ResourceManager::SetResourceRoot(projectRoot)`,从而绑定项目根、初始化 `ResourceFileSystem`,并刷新 `ProjectAssetIndex` 快照。
- `ResourceManager::LoadResource()` 在加载项目资产时,会先调用 `AssetImportService::EnsureArtifact()`;只有 artifact 就绪后,才把 `ImportedAsset::runtimeLoadPath` 交给具体 loader。
- `ProjectAssetIndex::TryGetAssetRef()` 当前会优先查本地 snapshotcache miss 时再回退到 `AssetImportService`;必要时会先 `Refresh()` 数据库再整体重建 snapshot。
- `ProjectAssetIndex::TryResolveAssetPath()` 当前同样优先查 snapshot但 miss 时只回退到 `AssetImportService::TryGetPrimaryAssetPath()`,不会主动刷新整份 snapshot。
- `engine/src/Resources/Material/MaterialLoader.cpp` 现在已经把材质纹理路径解析、`.xcmat` 中的 texture `AssetRef` 回读,以及首次访问时的懒加载纳入真实行为范围,因此 `AssetDatabase` 的材质依赖快照不再只是预留设计。
- `.xcmat` 当前已经是 material artifact v4texture binding 会同时写出编码后的 `AssetRef` 和可选路径字符串,并额外保存 `hasRenderStateOverride` 与 keywords后续由 `MaterialLoader``Material` 协作懒解析成真正贴图句柄。
- `TextureImporter` -> `Texture` -> `main.xctex`
- `MaterialImporter` -> `Material` -> `main.xcmat`
- `ModelImporter` -> `Model` -> `main.xcmodel`
- `ShaderImporter` -> `Shader` -> `main.xcshader`
- `VolumeFieldImporter` -> `VolumeField` -> `main.xcvol`
其中模型导入已经不再把“模型文件 = 单个 mesh artifact”简单等同处理。当前实现会
- 产出一个主模型 artifact `main.xcmodel`
- 额外生成 `mesh_<n>.xcmesh`
- 额外生成 `material_<n>.xcmat`
- 必要时生成 `texture_<n>.xctex`
这说明模型系统已经从“把导入模型直接压扁成一个 Mesh”演进到“保留层级、mesh 绑定和材质绑定”的结构化资源。
## 资源类型与磁盘契约的新变化
最近这轮源码改动里,资产基础层又向前走了一步:
- [ResourceTypes](ResourceTypes/ResourceTypes.md) 新增了 `Model``GaussianSplat`
- [ArtifactFormats](ArtifactFormats/ArtifactFormats.md) 新增了 `Model``GaussianSplat` 的 artifact 结构定义
- [VolumeField](../../Resources/Volume/VolumeField/VolumeField.md) 新增了 [CreateOwned](../../Resources/Volume/VolumeField/CreateOwned.md),让 loader 可以把已读入的 payload 直接转移给运行时资源
需要注意的是,`GaussianSplat` 目前已经有资源类型和 artifact 结构契约,但我在本轮审查到的 `AssetDatabase` 路径里还没有看到公开的 GaussianSplat importer 入口。也就是说,契约层已经先建立,导入链路仍在继续建设。
## 设计要点
- 把“项目资产身份与导入缓存”和“运行时资源实例加载”拆成两层,避免 `ResourceManager` 直接承载 GUID、`.meta` artifact 细节
- 再引入 `AssetImportService + ProjectAssetIndex` 作为服务层和只读快照层,把线程同步、项目根切换和高频查询从 `AssetDatabase``ResourceManager` 本体里拆出来。
- `AssetDatabase` 通过 `guid + importer + source/meta/dependency snapshot` 生成 `artifactKey`,让重导入条件可复现。
- `ResourceManager` 仍保留 `builtin://` 这类虚拟路径直载能力;只有能解析到项目 `Assets` 的路径才会进入项目资产链路。
- `ResourceHandle``ResourceCache``AsyncLoader` 继续围绕运行时 `IResource` 实例运转,而不是直接把 source asset 当作运行时对象。
- 把“项目资产身份与导入缓存”和“运行时资源实例”拆层,避免 `ResourceManager` 直接承`.meta`、GUID 和磁盘 artifact 细节
- `AssetImportService + ProjectAssetIndex` 分离线程同步和热路径查询
- `ArtifactFormats` `ResourceTypes` 作为稳定契约层,支持上层先扩展资源类型,再逐步补齐 importer、loader 与编辑器流程
## 当前实现边界
这种先稳定契约、再逐步铺满各条生产链路的做法,是商业级引擎重构里很典型的推进方式。
- `AssetDatabase` 当前只有 `TextureImporter``MaterialImporter``ModelImporter` 会产出 artifact`FolderImporter``DefaultImporter` 只保留 source 记录。
- `.meta` 文件当前只保存 `guid``folderAsset``importer``importerVersion`,还没有持久化 importer 专属设置。
- `ProjectAssetIndex` 当前只缓存主资产路径,生成的 `AssetRef` 仍默认使用 `kMainAssetLocalID`
- `ResourceManager` 会在同步加载路径里触发 artifact 生成,但当前没有后台导入队列或文件监听闭环。
- `LoadAsync()` / `AsyncLoader` 当前已经具备真实的工作线程与完成队列闭环,但回调仍依赖 `Update()` 手动轮询分发,取消与配置所有权语义也还比较原始。
## 当前边界
- 并不是所有 `ResourceType` 都已经有完整的项目导入器
- `GaussianSplat` 已进入类型与 artifact 契约层,但公开导入路径仍未在这轮代码里完全接起
- `ResourceManager` 仍会在同步加载路径里触发按需导入,后台导入队列与文件监听闭环还不是这一层的重点
## 头文件
@@ -74,11 +90,11 @@ Assets/... source files
## 推荐阅读顺序
1. 先读 [AssetGUID](AssetGUID/AssetGUID.md) [AssetRef](AssetRef/AssetRef.md),理解项目资产的身份与引用契约
2. 再读 [AssetDatabase](AssetDatabase/AssetDatabase.md) [ArtifactFormats](ArtifactFormats/ArtifactFormats.md),理解 source asset 如何导入为 `Library/Artifacts` 下的 artifact。
3. 然后读 [AssetImportService](AssetImportService/AssetImportService.md) 与 [ProjectAssetIndex](ProjectAssetIndex/ProjectAssetIndex.md),理解数据库如何被包装成线程安全服务和热路径查询缓存。
4. 接着读 [ResourceManager](ResourceManager/ResourceManager.md) [ResourceHandle](ResourceHandle/ResourceHandle.md),理解运行时如何消费这些 artifact 和 snapshot。
5. 最后再看 [ResourceCache](ResourceCache/ResourceCache.md)、[AsyncLoader](AsyncLoader/AsyncLoader.md) 和 [ResourceDependencyGraph](ResourceDependencyGraph/ResourceDependencyGraph.md),理解缓存、异步与依赖管理的现状。
1. 先读 [AssetGUID](AssetGUID/AssetGUID.md) [AssetRef](AssetRef/AssetRef.md),理解项目资产的身份与引用契约
2. 再读 [ResourceTypes](ResourceTypes/ResourceTypes.md) [ArtifactFormats](ArtifactFormats/ArtifactFormats.md),理解资源类型与磁盘格式
3. 接着读 [AssetDatabase](AssetDatabase/AssetDatabase.md),理解 source asset 如何变成 artifact
4. 然后读 [AssetImportService](AssetImportService/AssetImportService.md) [ProjectAssetIndex](ProjectAssetIndex/ProjectAssetIndex.md),理解服务层和热路径索引
5. 最后 [ResourceManager](ResourceManager/ResourceManager.md) 与相关 loader理解运行时如何消费这些 artifact
## 相关指南

View File

@@ -6,190 +6,124 @@
**头文件**: `XCEngine/Core/Asset/AssetDatabase.h`
**描述**: 管理项目 `Assets` 目录的 source asset、`.meta` sidecar、source/artifact 快照以及 artifact 导入结果的项目资产数据库。
**描述**: 管理项目 `Assets` 目录的 source asset、`.meta` sidecar、source/artifact 快照以及导入结果的项目资产数据库。
## 概览
## 角色概述
`AssetDatabase` 处理的是“项目资产”和“artifact 索引”,而不是已经加载进内存的运行时资源实例。
它当前的真实职责是:
`AssetDatabase` 处理的是“项目资产及其导入缓存”,而不是已经加载进内存的运行时资源实例。它的核心职责是:
1. 扫描项目 `Assets`
2. 为每个 source asset 或文件夹维护稳定 `.meta` 与 GUID
3. 维护 source 快照 artifact 快照
4. 在按需导入或显式重导时,把 source asset 导入到 `Library/Artifacts/<shard>/<artifactKey>/...`
5. 向上提供路径解析、GUID 查询、可导入类型探测、显式重导与 lookup snapshot 导出能力
当前它并不直接暴露给 `ResourceManager`;运行时与编辑器通常都通过 [AssetImportService](../AssetImportService/AssetImportService.md) 间接访问。
2. 为每个 source asset 维护稳定 `.meta``GUID`
3. 维护 source 快照 artifact 快照
4. 在按需导入或显式重导时,把 source asset 写成 `Library/Artifacts/...` 下的 artifact
5. 向上提供路径解析、GUID 查询、类型探测与 artifact 准备能力
## 生命周期
- [Initialize](Initialize.md)
设置项目根、`Assets` `Library` 根,确保目录结构存在,然后加载 source/artifact 数据库并扫描当前资产树
设置项目根、`Assets` `Library` 根,确保目录结构存在,然后加载快照并扫描资产树
- [Refresh](Refresh.md)
重新扫描 `Assets`、维护 source 快照并清理孤儿 artifact;当前返回 [MaintenanceStats](MaintenanceStats.md)。
重新扫描 `Assets`、维护 source 快照并清理孤儿 artifact
- [Shutdown](Shutdown.md)
写回 source/artifact 数据库,然后清空内存状态
写回 source/artifact 数据库并清理内存状态
`AssetDatabase` 自身没有内部锁。线程同步当前由外层 [AssetImportService](../AssetImportService/AssetImportService.md) 的 `recursive_mutex` 提供。
## 持久化布局
## 持久化与目录布局
### Source 侧
- 每个 source asset 或文件夹都对应一个 `.meta` sidecar
- `.meta` 当前记录:
- `guid`
- `folderAsset`
- `importer`
- `importerVersion`
- 每个 source asset 或目录都有对应 `.meta`
- `Library/SourceAssetDB/assets.db` 保存 [SourceAssetRecord](SourceAssetRecord.md) 快照
`assets.db` 当前是制表符分隔的文本快照,而不是二进制数据库。
### Artifact 侧
- `Library/ArtifactDB/artifacts.db` 保存 [ArtifactRecord](ArtifactRecord.md) 快照
- `Library/ArtifactDB/artifacts.db` 保存 [ArtifactRecord](ArtifactRecord.md)
- artifact 目录按 `artifactKey` 前两位分片:
- `Library/Artifacts/<2-char-shard>/<artifactKey>/...`
- 主 artifact 文件当前约定:
- 纹理: `main.xctex`
- 材质: `main.xcmat`
- 模型: `main.xcmesh`
- Shader: `main.xcshader`
模型 artifact 还会在同一目录下写出内嵌材质和贴图 artifact例如 `material_0.xcmat``texture_0.xctex`
当前明确的主 artifact 命名为:
## 查询与快照链路
- 纹理: `main.xctex`
- 材质: `main.xcmat`
- 模型: `main.xcmodel`
- Shader: `main.xcshader`
- 体积: `main.xcvol`
### 路径解析
## 当前 importer 映射
- [ResolvePath](ResolvePath.md) 只把项目内 `Assets/...`,或能还原成 `Assets/...` 的绝对路径视为正式项目资产路径。
-`://` 的虚拟路径会直接失败,不进入项目资产数据库。
- 普通相对路径虽然可以被展开成绝对路径,但如果无法得到 `Assets/...` 相对路径,就不会被当作正式项目资产。
按当前 `GetImporterNameForPath()``GetPrimaryResourceTypeForImporter()`,公开可见的路径映射如下:
### GUID、类型探测与 `AssetRef`
- [TryGetAssetGuid](TryGetAssetGuid.md)
通过规范化、大小写无关的 `Assets/...` path key 查询 GUID。
- [TryGetImportableResourceType](TryGetImportableResourceType.md)
返回当前 importer 的 primary `ResourceType`;目录、项目外路径和 `Unknown` importer 都直接失败。
- [TryGetAssetRef](TryGetAssetRef.md)
只是把 GUID 包装成主资产 `AssetRef`,并原样写入调用方指定的 `ResourceType`
- [TryGetPrimaryAssetPath](TryGetPrimaryAssetPath.md)
做 GUID 到主 source 路径的反查。
### Lookup snapshot
[BuildLookupSnapshot](BuildLookupSnapshot.md) 会从当前 source 快照导出两张表:
- `pathKey -> AssetGUID`
- `AssetGUID -> relativePath`
它不扫描磁盘,也不触发导入。当前真实链路是:
```text
AssetDatabase::BuildLookupSnapshot
-> AssetImportService::BuildLookupSnapshot
-> ProjectAssetIndex::RefreshFrom
-> ResourceManager hot-path queries
```
## 导入与重导链路
### 当前 importer 映射
| importer | 扩展名/来源 | 主资源类型 | 当前主 artifact |
| importer | 典型扩展名 | primary resource type | 主 artifact |
|------|------|------|------|
| `TextureImporter` | `.png` `.jpg` `.jpeg` `.bmp` `.tga` `.gif` `.hdr` | `Texture` | `main.xctex` |
| `MaterialImporter` | `.mat` `.material` `.json` | `Material` | `main.xcmat` |
| `ModelImporter` | `.obj` `.fbx` `.gltf` `.glb` `.dae` `.stl` | `Mesh` | `main.xcmesh` + 内嵌材质/贴图 artifact |
| `ShaderImporter` | `.shader` `.hlsl` `.glsl` `.vert` `.frag` `.geom` `.comp` | `Shader` | `main.xcshader` |
| `ModelImporter` | `.obj` `.fbx` `.gltf` `.glb` `.dae` `.stl` | `Model` | `main.xcmodel` |
| `ShaderImporter` | `.shader` | `Shader` | `main.xcshader` |
| `VolumeFieldImporter` | `.nvdb` | `VolumeField` | `main.xcvol` |
| `FolderImporter` | 文件夹 | `Unknown` | 无 |
| `DefaultImporter` | 其他扩展名 | `Unknown` | 无 |
[SourceAssetRecord](SourceAssetRecord.md) 的 `importerVersion` 当前统一取头文件里的 `kCurrentImporterVersion`,现在是 `5`
这张表里最重要的新变化是:`ModelImporter` 的 primary type 已经从 `Mesh` 升级为 `Model`
### 重导判定
## Model 导入链路的新语义
`ShouldReimport()` 当前会在以下任一条件满足时要求重导
`ImportModelAsset()` 当前不再通过 `MeshLoader` 直接把文件导成一个单体 mesh artifact而是走更完整的模型导入链
- 没有现存 `ArtifactRecord`
- `artifactKey``mainArtifactPath` 为空
- 主 artifact 文件已经不存在
- importer 版本变化
- source 内容哈希变化
- `.meta` 哈希变化
- source 文件大小或写时间变化
- 任一依赖文件快照不再匹配
- 使用 Assimp 模型导入数据
- 收集导入出的贴图依赖,而不是从单个 mesh 对象反推
- 生成 `main.xcmodel`
- 为每个子 mesh 生成 `mesh_<n>.xcmesh`
- 为每个材质生成 `material_<n>.xcmat`
- 必要时生成 `texture_<n>.xctex`
因此当前 artifact key 不是唯一 gate真正的重导判定是一组 source / meta / dependency snapshot 联合判断
这说明 `AssetDatabase` 现在已经把“模型”视为有层级、有绑定关系的主资源,而不是把导入结果压扁为单个 `Mesh`
### 公开导入入口
## EnsureArtifact 的真实职责
- [EnsureArtifact](EnsureArtifact.md)
按需保证单个 source asset 的主 artifact 可用;只有命中 `ShouldReimport()` 时才真正重建。
- [ReimportAsset](ReimportAsset.md)
强制重导单个 source asset并返回最新主 artifact 的 [ResolvedAsset](ResolvedAsset.md)。
- [ReimportAllAssets](ReimportAllAssets.md)
强制重导当前 source 快照里的全部可导入记录;单个条目失败不会中断整轮循环。
[EnsureArtifact](EnsureArtifact.md) 仍然是最关键的公开入口之一。它会:
### `Refresh()` 与显式重导的区别
1. 解析请求路径
2. 确保 source 记录与 `.meta` 有效
3. 根据 importer 推导 primary `ResourceType`
4. 校验请求类型与 primary type 一致
5. 查询或重建 artifact
6. 返回可直接交给 loader 的 [ResolvedAsset](ResolvedAsset.md)
- [Refresh](Refresh.md) 是“扫描 + 清理”入口,返回 [MaintenanceStats](MaintenanceStats.md),但当前不主动重建 artifact。
- [ReimportAsset](ReimportAsset.md) 和 [ReimportAllAssets](ReimportAllAssets.md) 才是显式重导入口,它们会真正调用 importer 重写 artifact。
所以对运行时系统来说,`AssetDatabase` 的价值不是“告诉你有没有这个文件”,而是“把 source asset 准备成当前可用、类型正确的 artifact
## 公开数据结构
## 设计理解
- [MaintenanceStats](MaintenanceStats.md) - 一次维护操作的汇总统计
- [ArtifactDependencyRecord](ArtifactDependencyRecord.md) - 单个依赖文件的快照记录
- [SourceAssetRecord](SourceAssetRecord.md) - source 资产记录
- [ArtifactRecord](ArtifactRecord.md) - artifact 数据库记录
- [ResolvedAsset](ResolvedAsset.md) - `EnsureArtifact()``ReimportAsset()` 返回给调用方的解析结果
这套设计体现的是典型的商业引擎资产流水线思路:
## 公开方法
- source asset 和 runtime resource 严格分层
- artifact 作为可缓存、可重建的中间产物
- importer 的 primary type 决定外部看到的主资源身份
- 子资源 artifact 可以作为主资源导入链的一部分自动生成
`Model -> main.xcmodel + mesh/material/texture artifacts` 就是这种设计成熟度提升的直接信号。
## 重点公共方法
| 方法 | 说明 |
|------|------|
| [Initialize](Initialize.md) | 初始化项目根与数据库状态。 |
| [Shutdown](Shutdown.md) | 写回数据库并清理内存状态。 |
| [Refresh](Refresh.md) | 重新扫描 `Assets` 并返回维护统计。 |
| [ResolvePath](ResolvePath.md) | 解析请求路径到绝对/相对项目路径。 |
| [ResolvePath](ResolvePath.md) | 请求路径解析为项目资产路径。 |
| [TryGetAssetGuid](TryGetAssetGuid.md) | 通过路径查询 GUID。 |
| [TryGetImportableResourceType](TryGetImportableResourceType.md) | 探测当前 importer 的主资源类型。 |
| [TryGetAssetRef](TryGetAssetRef.md) | 通过路径组装主资 `AssetRef`。 |
| [TryGetImportableResourceType](TryGetImportableResourceType.md) | 探测 importer 对应的 primary `ResourceType`。 |
| [TryGetAssetRef](TryGetAssetRef.md) | 通过路径组装主资 `AssetRef`。 |
| [ReimportAsset](ReimportAsset.md) | 强制重导单个 project asset。 |
| [ReimportAllAssets](ReimportAllAssets.md) | 强制重导当前快照的全部可导入资产。 |
| [ReimportAllAssets](ReimportAllAssets.md) | 强制重导当前快照的全部可导入资产。 |
| [EnsureArtifact](EnsureArtifact.md) | 确保 source asset 对应的主 artifact 可用。 |
| [TryGetPrimaryAssetPath](TryGetPrimaryAssetPath.md) | 通过 GUID 反查主 source 路径。 |
| [BuildLookupSnapshot](BuildLookupSnapshot.md) | 导出 path/GUID lookup snapshot。 |
| [GetProjectRoot](GetProjectRoot.md) | 获取项目根目录。 |
| [GetAssetsRoot](GetAssetsRoot.md) | 获取 `Assets` 根目录。 |
| [GetLibraryRoot](GetLibraryRoot.md) | 获取 `Library` 根目录。 |
## 真实使用位置
- [AssetImportService](../AssetImportService/AssetImportService.md) 当前持有 `AssetDatabase`,并负责所有外部访问的加锁与项目根切换。
- `AssetImportService` 的显式工具入口当前会:
-`Refresh()`,再调用 [ReimportAsset](ReimportAsset.md)
-`Refresh()`,再调用 [ReimportAllAssets](ReimportAllAssets.md)
- 在持锁且项目根有效时转发 [TryGetImportableResourceType](TryGetImportableResourceType.md)
- [ProjectAssetIndex](../ProjectAssetIndex/ProjectAssetIndex.md) 通过 [BuildLookupSnapshot](BuildLookupSnapshot.md) 构建热路径 snapshot。
- `ResourceManager::LoadResource()` 通过 `AssetImportService::EnsureArtifact()` 准备 artifact再把结果交给具体 loader。
## 当前实现边界
- 当前只处理项目 `Assets` 下的资产,`builtin://` 等虚拟路径不会进入这里。
- `.meta` 只保存 importer 名称和版本,不保存更细的 importer 设置。
- `FolderImporter` / `DefaultImporter` 不会产出 artifact。
- 当前没有文件监听器也没有后台导入队列artifact 通常在首次加载、显式重导或按需导入时同步生成。
- [TryGetAssetRef](TryGetAssetRef.md) 只保证“路径有 GUID”不保证请求的 `ResourceType` 与真实 importer 一致;真正的类型匹配要到 [EnsureArtifact](EnsureArtifact.md) 阶段才检查。
## 相关文档
- [当前模块](../Asset.md)
- [ArtifactFormats](../ArtifactFormats/ArtifactFormats.md)
- [ResourceTypes](../ResourceTypes/ResourceTypes.md)
- [AssetImportService](../AssetImportService/AssetImportService.md)
- [ProjectAssetIndex](../ProjectAssetIndex/ProjectAssetIndex.md)
- [ResourceManager](../ResourceManager/ResourceManager.md)
- [ImportSettings](../ImportSettings/ImportSettings.md)
- [API 总索引](../../../../main.md)

View File

@@ -6,23 +6,21 @@
**头文件**: `XCEngine/Core/Asset/ResourceTypes.h`
**描述**: 定义资源系统的基础类型,包括资源类别枚举、GUID 规则和类型映射模板。
**描述**: 定义资源系统的基础类型,包括 `ResourceType` 枚举、`ResourceGUID` 以及 `GetResourceType<T>()` 类型映射模板。
## 角色概述
`ResourceTypes.h` 是整个资源模块最底层的身份定义文件。它回答的是三个基础问题
`ResourceTypes.h` 是整个资源系统最底层的身份定义文件。它决定了三件事
- 这是什么类别的资源
- 个资源的稳定标识是什么
- 给定一个 C++ 资源类型,应该映射到哪个 `ResourceType`
1. 一个运行时资源属于哪种 `ResourceType`
2. 个资源路径如何生成稳定的 `ResourceGUID`
3. 给定 C++ 资源类型时,模板系统如何映射回对应的 `ResourceType`
这类头文件在商业引擎里通常非常核心,因为 loader、缓存、依赖图、序列化和调试输出都依赖同一套身份系统
loader、缓存、artifact 数据库、热路径索引和调试输出都依赖这套定义
## 当前定义
## 当前 ResourceType 枚举
### ResourceType
`ResourceType` 当前包含:
当前枚举值包括:
- `Unknown`
- `Texture`
@@ -37,12 +35,22 @@
- `ParticleSystem`
- `Scene`
- `Prefab`
- `UIView`
- `UITheme`
- `UISchema`
- `VolumeField`
- `Model`
- `GaussianSplat`
`GetResourceTypeName()` 是对应的 constexpr 名称映射,用于日志和调试输出
最近新增的 `Model``GaussianSplat` 很关键,因为它们意味着引擎不再只把“模型”理解成 `Mesh`,也已经为 3D Gaussian Splat 这类新型资源准备好了正式身份
### ResourceGUID
## GetResourceTypeName()
`ResourceGUID` 本质上是一个 `uint64` 包装类型,提供:
`GetResourceTypeName(ResourceType)` 提供了与枚举保持同步的字符串映射。文档、日志、调试面板和 artifact 元数据输出通常都会依赖这个函数保持名字一致。
## ResourceGUID
`ResourceGUID` 本质上是 `uint64` 包装类型,提供:
- `IsValid()`
- 相等 / 不等比较
@@ -50,11 +58,11 @@
- `Generate(const Containers::String&)`
- `ToString()`
当前 `Generate()` 的实现使用 64 位 FNV-1a 风格字符串哈希,对输入路径逐字节哈希
它的设计目标是轻量、可复制、易于做哈希键,而不是引入额外的注册中心
### 类型映射模板
## 类型映射模板
`GetResourceType<T>()` 当前只有少数显式特化:
`GetResourceType<T>()` 现在已经有这些显式特化:
- `Texture`
- `Mesh`
@@ -62,51 +70,45 @@
- `Shader`
- `AudioClip`
- `BinaryResource`
- `UIView`
- `UITheme`
- `UISchema`
- `VolumeField`
- `Model`
- `GaussianSplat`
这正是 `ResourceManager::Load<T>()` 能正常分派到 loader 的基础
其中 `Model``GaussianSplat` 的加入很重要,因为这代表模板层已经认可它们是正式的一等资源类型,而不是临时附属数据
## GUID 语义
## 为什么 Model 不是 Mesh
当前 `ResourceGUID::Generate(path)` 的行为非常直接:
这是本轮改动里最值得说清楚的设计点之一。
- 只对传入字符串本身做哈希
- 不做路径规范化
- 不统一大小写
- 不消除相对路径和绝对路径差异
`Mesh` 只表达一段可绘制的几何数据;`Model` 则表达:
因此:
- 节点层级
- 局部变换
- mesh 绑定
- 材质绑定
- `"textures/a.png"``"./textures/a.png"` 会生成不同 GUID
- 路径大小写不同,也会生成不同 GUID
- 调用方必须自己保证路径规范一致,否则缓存命中和资源去重都会受影响
把二者拆开,才能让资源系统正确表达一个真正的导入模型,而不是把层级资产粗暴压平为单网格资源。这也是商业引擎里非常标准的做法。
`ToString()` 当前输出 16 位十六进制字符串,便于日志和调试面板展示。
## 为什么要把 GaussianSplat 提前放进 ResourceType
## 设计取向
即使某些导入与运行时链路还在建设中,先把 `GaussianSplat` 放进正式 `ResourceType` 仍然有明确价值:
用路径派生 GUID 而不是维护中心注册表,是一种很轻量的资源身份策略。它的优点是:
- artifact、资源缓存和调试输出可以先统一识别这种资源
- 上层系统不必把它伪装成 `Binary` 或其他临时类型
- 后续补 importer、loader 和渲染器时,不需要再重做身份契约
- 不需要额外数据库
- 同一路径天然得到稳定结果
- 非常适合当前这种运行时轻量资源系统
代价也很明确:
- 身份稳定性完全依赖路径字符串规范
- 理论上存在哈希碰撞可能
- 没有单独的 GUID 到路径反查系统保证
## 当前实现限制
- `GetResourceType<T>()` 没有通用默认实现,只能用于已显式特化的资源类型。
- `ResourceGUID` 不做路径规范化,调用方必须自己统一路径形式。
- 当前没有更强的 GUID 持久化或冲突检测机制。
- `ResourceType` 已经枚举了不少类别,但并不是每一类当前都有成熟 loader 或完整资源实现。
这是一种典型的“先确立系统边界,再逐步补实现”的工程推进方式。
## 相关文档
- [当前模块](../Asset.md)
- [ResourceManager](../ResourceManager/ResourceManager.md)
- [ResourceHandle](../ResourceHandle/ResourceHandle.md)
- [IResource](../IResource/IResource.md)
- [AssetGUID](../AssetGUID/AssetGUID.md)
- [AssetRef](../AssetRef/AssetRef.md)
- [ArtifactFormats](../ArtifactFormats/ArtifactFormats.md)
- [Model](../../../Resources/Model/Model.md)
- [GaussianSplat](../../../Resources/GaussianSplat/GaussianSplat.md)
- [VolumeField](../../../Resources/Volume/VolumeField/VolumeField.md)
- [API 总索引](../../../../main.md)

View File

@@ -8,65 +8,100 @@
**描述**: 负责把 Editor 项目里的 C# 源码编译成 `Library/ScriptAssemblies` 下的脚本程序集。
## 概述
## 角色概述
`EditorScriptAssemblyBuilder` 是当前 Editor 脚本工作流里真正执行“编译”的那一层。
它会把两类源码分别编成:
`EditorScriptAssemblyBuilder` 是当前 Editor 托管脚本工作流里真正执行“编译”的那一层。它会把两组 C# 源码分别编成:
- `XCEngine.ScriptCore.dll`
- `GameScripts.dll`
并把结果统一落到:
并把输出统一落到:
- `<project>/Library/ScriptAssemblies/`
## 为什么这层设计重要
编辑器脚本系统要稳定工作,关键不只是“能调用 Roslyn”而是要把几件事情一起稳定下来
- 仓库根目录如何解析
- Mono 运行时依赖从哪里找
- `dotnet` / Roslyn / `.NET Framework 4.7.2` 引用程序集如何就位
- 输出目录里的 `mscorlib.dll` 如何避免被活动 Mono runtime 锁住后反复覆盖
`EditorScriptAssemblyBuilder` 正是在做这层工程收口。
## 公开方法
| 方法 | 说明 |
|------|------|
| [RebuildProjectAssemblies](RebuildProjectAssemblies.md) | 全量重建项目脚本程序集并返回结果消息。 |
这是当前唯一入口。返回 `EditorScriptAssemblyBuildResult`
当前这就是它唯一的公开入口。返回 `EditorScriptAssemblyBuildResult` 包含
- `succeeded`
- `message`
## 当前重建流程
`EditorScriptAssemblyBuilder.cpp` 当前实现,主流程大致
`EditorScriptAssemblyBuilder.cpp` 当前实现,主流程大致如下
1. 校验项目路径非空。
2. 解析仓库根与 Mono 根目录。
3. 创建 `<project>/Library/ScriptAssemblies`
4. `PATH` 上查找 `dotnet.exe`
5. 运行 `dotnet --list-sdks`,取最后一条 SDK 版本
6. 定位该 SDK 下的 `Roslyn/bincore/csc.dll`
7. 校验 `.NET Framework 4.7.2` 参考程序集存在
8. 收集 `managed/XCEngine.ScriptCore/**/*.cs`
9. 收集 `<project>/Assets/**/*.cs`
10. 若项目脚本为空,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件
11. 先编译 `XCEngine.ScriptCore.dll`
12. 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用旧文件,避免在增量重建时覆盖仍可能被 Mono 映射的 corlib
13. 再引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`
2. 解析仓库根目录。
3. 解析 Mono 根目录
4. 创建 `<project>/Library/ScriptAssemblies`
5. `PATH` 上查找 `dotnet.exe`
6. 运行 `dotnet --list-sdks`,取最后一条 SDK 版本
7. 定位该 SDK 下的 `Roslyn/bincore/csc.dll`
8. 校验 `.NET Framework 4.7.2` 引用程序集存在
9. 收集 `managed/XCEngine.ScriptCore/**/*.cs`
10. 收集 `<project>/Assets/**/*.cs`
11. 若项目脚本为空,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件
12. 先编译 `XCEngine.ScriptCore.dll`
13. 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用
14. 再引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`
## Mono 根目录解析顺序
这是本轮必须同步进文档的重点变化。
当前 `GetMonoRootDirectory()` 的真实优先级是:
1. 先从仓库根目录下找 bundled Mono
- 优先检查 `engine/third_party/mono/binary/mscorlib.dll`
- 若不存在,再扫描仓库一级子目录里的 `Fermion/Fermion/external/mono/binary/mscorlib.dll`
2. 如果没找到 bundled Mono再检查编译期配置的 `XCENGINE_EDITOR_MONO_ROOT_DIR`
3. 如果配置路径也无效,最后回退到 `<repositoryRoot>/managed/mono`
这意味着当前编辑器会优先使用项目内随仓库提供的 Mono而不是先依赖外部配置路径。这是一个非常实际的工程改进
- 减少对开发机环境的隐式依赖
- 让构建行为更可复现
- 更适合团队协作和 CI
## 仓库根目录解析
仓库根目录也不是写死的。当前顺序是:
1. 若定义了 `XCENGINE_EDITOR_REPO_ROOT`,直接使用它
2. 否则以可执行文件目录为起点,向上回退三级,得到 fallback repository root
这让同一套构建器既能支持配置化构建,也能支持开发环境里的相对部署。
## 真实使用位置
- `Application.cpp` 会在重建脚本程序集时调用它。
- `tests/Editor/test_editor_script_assembly_builder.cpp` 当前覆盖了两类关键语义
- 成功路径:`XCEngine.ScriptCore.dll``GameScripts.dll` 与项目本地 `mscorlib.dll` 都会落到 `Library/ScriptAssemblies`
- 锁语义:如果活动 Mono runtime 仍持有已加载的 `GameScripts.dll`,直接重建会失败;释放 runtime 后再重建则可以成功并看到新增脚本类型
- `Application.cpp` 会在编辑器触发脚本重建时调用它。
- `tests/Editor/test_editor_script_assembly_builder.cpp` 当前覆盖了成功路径与“活动 Mono runtime 持有程序集导致重建失败”的锁语义
## 当前实现边界
- 当前是全量重建,不是增量编译。
- 编译链明显依赖 Windows 路径和本机安装的 `dotnet` SDK。
- 参考程序集路径当前写死在 `.NET Framework 4.7.2` 目录
- 构建器本身不会主动卸载当前脚本运行时;如果调用方没有先释放 Mono app domain`GameScripts.dll` 仍可能因文件锁而建失败。
- 若外部环境缺少 `dotnet.exe`、Roslyn 或参考程序集,返回的只是一条失败消息,而不是更复杂的恢复策略。
- 编译链明显依赖 Windows本机安装的 `dotnet` SDK`.NET Framework 4.7.2` 引用程序集
- 它会优先消费仓库内 bundled Mono但不会主动管理 Mono runtime 生命周期
- 如果调用方没有先释放活动脚本运行时`GameScripts.dll` 仍可能因文件锁而建失败。
## 相关文档
- [Scripting](../Scripting.md)
- [RebuildProjectAssemblies](RebuildProjectAssemblies.md)
- [EditorScriptAssemblyBuilderUtils](../EditorScriptAssemblyBuilderUtils/EditorScriptAssemblyBuilderUtils.md)
- [EditorScriptRuntimeStatus](../EditorScriptRuntimeStatus/EditorScriptRuntimeStatus.md)

View File

@@ -14,53 +14,123 @@ static EditorScriptAssemblyBuildResult RebuildProjectAssemblies(const std::strin
## 作用
把指定项目下的托管脚本源码全量编译到 `Library/ScriptAssemblies` 目录,并返回成功/失败消息
把指定项目下的托管脚本源码全量编译到 `Library/ScriptAssemblies` 目录,并返回成功/失败结果
## 当前实现行为
## 当前实现流程
### 1. 解析路径与外部工具
### 1. 解析路径与基础环境
- 要求 `projectPath` 非空,否则直接返回失败结果
- 解析
- 仓库根目录
- Mono 根目录
- `managed/XCEngine.ScriptCore`
- `<project>/Library/ScriptAssemblies`
- 当前依赖系统 `PATH` 上可找到 `dotnet.exe`,并通过 `dotnet --list-sdks` 解析最新 SDK 版本,再定位对应 `Roslyn/bincore/csc.dll`
- 要求 `projectPath` 非空,否则直接返回失败。
- 解析 `projectRoot``repositoryRoot``monoRoot``managedRoot` 与输出目录。
- 创建 `<project>/Library/ScriptAssemblies`
### 2. 校验引用与源文件
### 2. 解析仓库根目录
- 校验 `.NET Framework 4.7.2` 参考程序集存在
- `mscorlib.dll`
- `System.dll`
- `System.Core.dll`
- 校验 Mono 自带 `binary/mscorlib.dll` 存在。
- 收集两组 C# 源文件:
- `managed/XCEngine.ScriptCore/**/*.cs`
- `<project>/Assets/**/*.cs`
- 若项目脚本为空,会生成 `Generated/EmptyProjectGameScripts.cs` 占位源码。
仓库根目录顺序如下
### 3. 编译输出
1.`XCENGINE_EDITOR_REPO_ROOT` 非空,优先使用该配置。
2. 否则以可执行文件目录为起点向上回退三级,得到 fallback repository root。
- 先编译 `XCEngine.ScriptCore.dll`
- 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用旧文件。
- 然后引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`
- 成功时返回:
- `succeeded = true`
- `message = "Rebuilt script assemblies in ..."`
### 3. 解析 Mono 根目录
### 4. 失败语义
当前 Mono 根目录的真实查找顺序是:
- 任意路径校验、进程启动、编译失败、首次 `mscorlib.dll` 复制失败,或目标程序集仍被活动 Mono runtime 持有时,都会返回 `succeeded = false`
- 这个函数本身不负责关闭现有脚本运行时;如果调用方在同一进程里仍持有已加载的 `GameScripts.dll`,重建可能因为文件锁失败。
- 函数内部还包了一层 `try/catch`,用于把标准异常和未知异常转成失败消息。
1. 优先查找仓库内 bundled Mono
- `<repositoryRoot>/engine/third_party/mono`
- 若未命中,再扫描仓库一级子目录下的 `Fermion/Fermion/external/mono`
2. 若未找到 bundled Mono则尝试 `XCENGINE_EDITOR_MONO_ROOT_DIR`
3. 若配置路径也无效,则回退到 `<repositoryRoot>/managed/mono`
## 当前实现边界
判定依据都是目标目录下是否存在 `binary/mscorlib.dll`
- 当前是全量重建,不做增量分析
- 平台和工具链路径明显偏向 Windows + 本机安装 `.NET SDK`
- 参考程序集版本当前固定为 `.NET Framework 4.7.2`
- 输出目录里的 `mscorlib.dll` 当前采用“首次复制、后续复用”的策略,不会在每次重建时强制覆盖。
这条顺序很关键,因为它决定了编辑器现在优先消费项目内置的 Mono 依赖,而不是先依赖开发机外部配置
### 4. 解析外部编译工具
-`PATH` 上查找 `dotnet.exe`
- 运行 `dotnet --list-sdks`
- 取输出中的最后一条 SDK 版本
- 组装 `C:\Program Files\dotnet\sdk\<sdkVersion>\Roslyn\bincore\csc.dll`
如果任一步失败,直接返回失败结果。
### 5. 校验引用程序集
当前固定要求 `.NET Framework 4.7.2` 引用程序集存在:
- `mscorlib.dll`
- `System.dll`
- `System.Core.dll`
同时还会校验 `monoRoot/binary/mscorlib.dll` 存在。
### 6. 收集源文件
- 收集 `managed/XCEngine.ScriptCore/**/*.cs`
- 收集 `<project>/Assets/**/*.cs`
- 若项目侧没有任何脚本,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件,确保仍能产出 `GameScripts.dll`
### 7. 执行两阶段编译
先编译:
- `XCEngine.ScriptCore.dll`
再编译:
- `GameScripts.dll`
第二阶段会把 `XCEngine.ScriptCore.dll` 放进引用列表。
### 8. 处理项目本地 mscorlib.dll
这是当前实现里一个很关键的工程细节:
- 输出目录里的 `mscorlib.dll` 只会在“第一次不存在”时,从 `monoRoot/binary/mscorlib.dll` 复制进来
- 如果该文件已经存在,则直接复用,不再每次重建都覆盖
源码里的注释已经说明原因Mono 可能在进程生命周期内持续映射项目本地 corlib。如果每次重建都强行覆盖文件锁会让流程更加脆弱。
## 返回值语义
成功时:
- `succeeded = true`
- `message` 类似 `Rebuilt script assemblies in ...`
失败时:
- `succeeded = false`
- `message` 会包含更具体的失败原因,例如缺少 `dotnet.exe`、Roslyn、引用程序集、Mono corlib或编译器进程本身失败
函数内部还包了一层 `try/catch`,会把标准异常与未知异常也转换成失败消息。
## 失败边界
以下情况都会导致失败:
- 项目路径为空
- 输出目录创建失败
- `dotnet.exe` 不在 `PATH`
- `dotnet --list-sdks` 无法执行或无法解析出 SDK 版本
- `csc.dll` 不存在
- `.NET Framework 4.7.2` 引用程序集缺失
- Mono corlib 缺失
- `ScriptCore` 源码为空
- 占位脚本生成失败
- 任意一次 C# 编译失败
- 目标程序集仍被活动 Mono runtime 持有导致写入失败
## 设计理解
这个函数不是一个通用 C# 构建系统,而是一个面向编辑器脚本热重建的工程化入口。它的目标是:
- 把编辑器脚本依赖解析收拢到一处
- 让项目内 bundled Mono 可以直接工作
- 让脚本重建失败时返回可读的诊断信息
- 尽量避免 `mscorlib.dll` 因重复覆盖而触发额外锁冲突
从商业级工具链视角看,这属于“先把本地开发和编辑器热重建跑稳,再逐步演进成更完整脚本构建系统”的典型路线。
## 相关文档

View File

@@ -10,19 +10,66 @@
```cpp
RHIBuffer* CreateBuffer(const BufferDesc& desc) override;
RHIBuffer* CreateBuffer(
const BufferDesc& desc,
const void* initialData,
size_t initialDataSize,
ResourceStates finalState = ResourceStates::GenericRead) override;
```
## 作用
更新 `m_device`
在 D3D12 后端创建 `RHIBuffer`。基础重载负责分配资源本体;带初始数据的重载额外负责把初始内容上传到默认堆缓冲,并把资源切换到目标状态
## 当前实现
## 基础重载的当前实现
- 会更新 `m_device`
- 当前实现会调用 `D3D12Buffer``Initialize``SetStride``SetBufferType`
- 包含条件分支,并可能提前返回。
- 包含 `nullptr` 相关分支。
`CreateBuffer(const BufferDesc&)` 主要按 `BufferType` 决定堆类型:
- `ReadBack` 使用 `D3D12_HEAP_TYPE_READBACK`
- `Constant``Vertex``Index` 使用 `D3D12_HEAP_TYPE_UPLOAD`
- 其他情况默认使用 `D3D12_HEAP_TYPE_DEFAULT`
这条路径更像“只把资源壳创建出来”,后续是否还要做数据上传、状态切换,由调用方继续安排。
## 带初始数据重载的当前实现
带初始数据的重载是一个真实的 D3D12 上传路径,而不是简单转调基类默认实现。它会:
1. 对输入做前置校验:
- `initialData` 不能为空
- `initialDataSize` 不能为 `0`
- `initialDataSize` 不能超过 `desc.size`
- `desc.size` 不能超出 `size_t` 可表达范围
- `ReadBack` 缓冲直接拒绝
2. 创建默认堆目标缓冲,初始状态固定为 `CopyDst`
3. 临时创建 direct queue、allocator 和 command list
4. 创建 upload heap 缓冲并映射
5. 先把整个目标尺寸补零,再把传入字节流复制进去
6. 录制 `CopyBufferRegion`
7. 按需插入从 `CopyDst``finalState` 的 transition barrier
8. 提交命令并 `WaitForIdle()`
9. 返回已经处于 `finalState` 的目标缓冲
## 为什么这么实现
对 D3D12 来说,“默认堆资源不能直接 CPU 写入”是基本现实,因此如果想在 `CreateBuffer` 阶段就得到一个可用于 GPU 访问、且已经带初始内容的缓冲,最自然的做法就是:
- 默认堆承载最终资源
- 上传堆承载 CPU 可写的 staging 数据
- 命令列表执行 copy
- barrier 完成状态收尾
这也是商业引擎里最常见的 D3D12 初始化资源路径。
## 使用建议
- 适合创建静态顶点缓冲、索引缓冲、常量缓冲和其他启动期 GPU 资源。
- 不适合大规模批量流式上传,因为它每次调用都会创建一套临时上传上下文并阻塞等待完成。
- `ReadBack` 缓冲不支持这条重载,因为这种资源方向和上传初始化目标相反。
## 相关文档
- [D3D12Device](D3D12Device.md)
- [RHIDevice::CreateBuffer](../../RHIDevice/CreateBuffer.md)
- [D3D12Buffer](../D3D12Buffer/D3D12Buffer.md)

View File

@@ -6,88 +6,95 @@
**头文件**: `XCEngine/RHI/D3D12/D3D12Device.h`
**描述**: D3D12 后端的设备根对象,负责创建 DXGI factory、选择适配器、创建 `ID3D12Device`,并作为所有 D3D12 资源工厂的统一入口
**描述**: `RHIDevice` 在 Direct3D 12 后端上的具体实现,负责创建设备、查询能力,并把统一的 `RHI` 创建请求翻译成 D3D12 资源与管线对象
## 概览
## 角色概述
如果把抽象层里的 [`RHIDevice`](../../RHIDevice/RHIDevice.md) 理解成“跨后端设备契约,那么 `D3D12Device` 就是契约在 Direct3D 12 上的落地实现
它承担三类职责:
- 初始化 D3D12 运行时根对象:`IDXGIFactory4``IDXGIAdapter1``ID3D12Device`
- 缓存设备信息与能力信息,供测试、日志和上层系统查询
- 把 RHI 的资源创建请求翻译成 D3D12 后端对象,例如 buffer、texture、command queue、command list、pipeline state、descriptor heap 与 descriptor set
如果把 [RHIDevice](../../RHIDevice/RHIDevice.md) 理解跨后端契约,那么 `D3D12Device` 就是这套契约在 D3D12 上的落地层。它既是设备持有者,也是后端资源工厂
## 生命周期
推荐顺序
推荐顺序如下
1. 构造 `D3D12Device`
2. 调用 [`Initialize`](Initialize.md)
3. 通过设备创建队列、命令列表、资源和状态对象
4. 先销毁设备创建出来的对象
2. 调用 [Initialize](Initialize.md)
3. 通过设备创建命令队列、命令列表、资源、描述符和管线状态
4. 先销毁设备创建出来的对象
5. 最后调用 `Shutdown()`
析构函数会调用 `Shutdown()`,但工程上仍应把显式关闭视为标准用法
析构函数会兜底调用 `Shutdown()`,但工程上的正确用法仍然是显式关闭
## 当前实现的真实行为
## 当前实现要点
### 适配器与调试层
- `Initialize()` 会先创建 DXGI factory然后从 `adapterIndex` 开始枚举适配器。
- `Initialize()` 会先创建 DXGI factory再按 `adapterIndex` 起点枚举适配器。
- 软件适配器会被跳过。
- 当前实现选择“第一个能以 `D3D_FEATURE_LEVEL_11_0` 创建设备的硬件适配器”,而不是做更复杂的打分排序
- `enableDebugLayer` 会尝试启用 `ID3D12Debug``DXGI_CREATE_FACTORY_DEBUG`,这是真正接线了的;但它仍然属于基础调试层支持,不等同于完整 GPU validation 工作流。
- `enableDebugLayer` 会尝试启用 D3D12 调试层与 DXGI 调试 factory
### 资源工厂策略
这说明当前实现更强调“稳定创建设备并接起调试能力”,而不是复杂的适配器评分系统。
- [`CreateBuffer`](CreateBuffer.md) 会按 `BufferType` 粗粒度选择 heap
### 缓冲创建策略
[CreateBuffer](CreateBuffer.md) 现在有两条路径:
- 基础重载按 `BufferType` 选择堆类型:
- `ReadBack` -> `D3D12_HEAP_TYPE_READBACK`
- `Constant / Vertex / Index` -> `D3D12_HEAP_TYPE_UPLOAD`
- 其他 -> `D3D12_HEAP_TYPE_DEFAULT`
- [`CreateTexture`](CreateTexture.md) 对深度格式自动补 `ALLOW_DEPTH_STENCIL`,对常规 2D 纹理自动补 `ALLOW_RENDER_TARGET`
- 带初始数据的 `CreateTexture()` 会临时创建 upload queue、allocator、command list同步上传后再 `WaitForIdle()`,因此这是一个阻塞式初始化路径。
- [`CreateCommandList`](CreateCommandList.md) 会先创建专用 `D3D12CommandAllocator`,再创建 `D3D12CommandList`;命令列表内部通过 `ComPtr` 持有 allocator。
- [`CreateDescriptorSet`](CreateDescriptorSet.md) 实际上把分配工作转交给 `D3D12DescriptorHeap::AllocateSet()`
- `Constant` / `Vertex` / `Index` -> `D3D12_HEAP_TYPE_UPLOAD`
- 其他默认走 `D3D12_HEAP_TYPE_DEFAULT`
- 带初始数据的重载会专门建立上传上下文,把数据复制进默认堆缓冲,然后切换到目标状态
### 能力与设备信息
第二条路径是这次文档必须强调的新行为。它不是简单地调用 `SetData`,而是显式做了一次 D3D12 风格的 upload-copy-transition 流程。
- `QueryAdapterInfo()` 会填充 `AdapterInfo``RHIDeviceInfo`
- 能力查询覆盖了 ray tracing、mesh shader、conservative rasterization、shader model 等字段
- 一部分上限值直接来自 D3D12 常量,而不是运行时逐项探测
### 带初始数据的 CreateBuffer
## 所有权与资源管理
当前实现会:
- 绝大多数 `Create*()` 返回裸指针,调用方负责 `Shutdown()``delete`
- `D3D12Device` 不做集中式对象注册和统一回收
- 这更接近商业引擎底层 backend 的“轻工厂”模式:好处是后端简单直接,代价是上层必须严格控制释放顺序
1. 拒绝 `nullptr`、零大小、越界大小以及 `ReadBack` 缓冲。
2. 创建一个默认堆目标缓冲,初始状态为 `CopyDst`
3. 临时创建 `D3D12CommandQueue``D3D12CommandAllocator``D3D12CommandList` 作为上传上下文。
4. 创建上传堆缓冲,把整个目标大小区域先补零,再拷贝 `initialData`
5. 录制 `CopyBufferRegion`
6. 如果 `finalState` 不是 `CopyDst`,再显式插入一次 transition barrier。
7. `Close()` 命令列表并 `WaitForIdle()`,最后返回已处于目标状态的缓冲。
## 线程语义
这条路径非常适合静态资源引导,但它是阻塞式的、每次调用都临时创建上传上下文,因此不应该被当成批量流式上传系统。
`D3D12Device` 自身没有显式加锁。更稳妥的使用方式是:
### 纹理与管线创建
- 初始化与关闭在单线程串行完成
- 资源创建由调用方保证外部同步
- 不要把它当成天然线程安全的全局服务
- `CreateTexture` 会根据描述决定资源 flag并支持带初始数据的同步上传路径。
- `CreatePipelineState` 会把 `GraphicsPipelineDesc::sampleCount``sampleQuality` 一并下传给 `D3D12PipelineState`
## 当前限制
后一点很关键,因为 `sampleQuality` 已经不再是“只有描述结构里有,后端不消费”的字段了。
- 适配器选择仍是“从起始索引往后取第一个可用硬件设备”
- 带初始数据的纹理创建走同步上传路径,初始化成本偏高
- 某些能力字段是“基础探测 + 常量填充”的组合,不是完整 profile
## 设计理解
`D3D12Device` 的实现风格很典型地体现了现代显式图形 API 后端的工程习惯:
- 上层看到的是统一工厂接口。
- 后端真正承担上传缓冲、命令录制、状态转换和 native 对象创建。
- 为了减少上层复杂度,部分便捷路径允许用同步、阻塞式实现换取简单和确定性。
这种设计在商业引擎里非常常见。真正成熟后,后续通常会继续把“便捷初始化路径”和“高吞吐上传系统”进一步拆分。
## 重点公共方法
- [Initialize](Initialize.md)
- [CreateBuffer](CreateBuffer.md)
- [CreateTexture](CreateTexture.md)
- [CreateCommandQueue](CreateCommandQueue.md)
- [CreateCommandList](CreateCommandList.md)
- [CreatePipelineState](CreatePipelineState.md)
- [CreateDescriptorPool](CreateDescriptorPool.md)
- [CreateDescriptorSet](CreateDescriptorSet.md)
## 相关文档
- [D3D12](../D3D12.md)
- [当前目录](../D3D12.md)
- [RHIDevice](../../RHIDevice/RHIDevice.md)
- [D3D12Buffer](../D3D12Buffer/D3D12Buffer.md)
- [D3D12PipelineState](../D3D12PipelineState/D3D12PipelineState.md)
- [D3D12CommandQueue](../D3D12CommandQueue/D3D12CommandQueue.md)
- [D3D12CommandList](../D3D12CommandList/D3D12CommandList.md)
- [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md)
- [API 总索引](../../../../main.md)

View File

@@ -6,61 +6,69 @@
**头文件**: `XCEngine/RHI/D3D12/D3D12PipelineState.h`
**描述**: D3D12 pipeline state 封装,保存 Unity SRP 风格的管线配置,并在需要时生成 graphics / compute PSO
**描述**: D3D12 后端的 `RHIPipelineState` 实现,负责缓存跨后端管线描述,并在需要时生成 graphics / compute `ID3D12PipelineState`
## 概述
## 角色概述
`D3D12PipelineState` 是 D3D12 后端的 PSO 包装。它不是单纯的原生句柄壳,而是先缓存一整套与引擎 RHI 对齐的状态描述,再在 `EnsureValid()` 或初始化阶段把这些配置固化 D3D12 graphics / compute pipeline state object。头文件里当前可见的职责包括
`D3D12PipelineState` 不是一层薄包装。它会先缓存输入布局、光栅化、混合、深度模板、render target 格式、多重采样配置和 shader bytecode然后在初始化或 `EnsureValid()` 阶段把这些状态固化 D3D12 PSO。
- 存储 rasterizer、blend、depth-stencil、input layout、render target format 等状态
- 接收 graphics / compute shader bytecode
- 管理 root signature 和最终 `ID3D12PipelineState`
- 为命令列表绑定前提供 hash、类型和有效性查询
这和商业级引擎常见的做法一致:高层管线描述统一,后端负责把描述落到 native PSO。
## 声明概览
## 当前实现要点
| 声明 | 类型 | 说明 |
|------|------|------|
| `D3D12PipelineState` | `class` | D3D12 后端的 graphics / compute PSO 封装。 |
### sample quality 已经进入真实后端语义
## 公共方法
这是本轮需要同步到文档的重点变化。`SetSampleQuality` 不再只是接口层占位:
| 方法 | 描述 |
|------|------|
| [D3D12PipelineState()](Constructor.md) | 构造 `D3D12PipelineState` 实例。 |
| [~D3D12PipelineState()](Destructor.md) | 执行 `Shutdown(...)` 相关流程。 |
| [Initialize](Initialize.md) | 执行 `clear``push_back``EnsureValid` 协同流程。 |
| [SetInputLayout](SetInputLayout.md) | 更新 `m_inputLayoutDesc`。 |
| [SetRasterizerState](SetRasterizerState.md) | 写入 `m_rasterizerDesc`。 |
| [SetBlendState](SetBlendState.md) | 写入 `m_blendDesc`。 |
| [SetDepthStencilState](SetDepthStencilState.md) | 写入 `m_depthStencilDesc`。 |
| [SetTopology](SetTopology.md) | 写入 `m_topologyType`。 |
| [SetRenderTargetFormats](SetRenderTargetFormats.md) | 执行该公开方法对应的当前实现。 |
| [SetSampleCount](SetSampleCount.md) | 写入 `m_sampleCount`。 |
| [SetComputeShader](SetComputeShader.md) | 执行 `Reset``IsValid` 协同流程。 |
| [SetRootSignature](SetRootSignature.md) | 写入 `m_rootSignature`。 |
| [GetRasterizerState](GetRasterizerState.md) | 返回 `m_rasterizerDesc` 当前值。 |
| [GetBlendState](GetBlendState.md) | 返回 `m_blendDesc` 当前值。 |
| [GetDepthStencilState](GetDepthStencilState.md) | 返回 `m_depthStencilDesc` 当前值。 |
| [GetInputLayout](GetInputLayout.md) | 返回 `m_inputLayoutDesc` 当前值。 |
| [GetHash](GetHash.md) | 返回 `hash`。 |
| [GetComputeShader](GetComputeShader.md) | 返回 `m_computeShader` 当前值。 |
| [HasComputeShader](HasComputeShader.md) | 返回 `m_csBytecode.pShaderBytecode != nullptr && m_csBytecode.BytecodeLength > 0`。 |
| [IsValid](IsValid.md) | 返回 `m_finalized` 当前值。 |
| [EnsureValid](EnsureValid.md) | 执行 `HasComputeShader``CreateD3D12ComputePSO``CreateD3D12PSO` 协同流程。 |
| [SetShaderBytecodes](SetShaderBytecodes.md) | 执行该公开方法对应的当前实现。 |
| [SetComputeShaderBytecodes](SetComputeShaderBytecodes.md) | 写入 `m_csBytecode`。 |
| [Shutdown](Shutdown.md) | 执行 `Reset` 协同流程。 |
| [GetPipelineState](GetPipelineState.md) | 返回 `m_pipelineState.Get()` 的结果。 |
| [GetComputePipelineState](GetComputePipelineState.md) | 返回 `m_computePipelineState.Get()` 的结果。 |
| [GetRootSignature](GetRootSignature.md) | 返回 `m_rootSignature.Get()` 的结果。 |
| [GetNativeHandle](GetNativeHandle.md) | 返回 `m_pipelineState.Get()` 的结果。 |
| [GetType](GetType.md) | 返回 `HasComputeShader() ? PipelineType::Compute : PipelineType::Graphics`。 |
| [Bind](Bind.md) | 当前实现为空。 |
| [Unbind](Unbind.md) | 当前实现为空。 |
| [CreateInputElement](CreateInputElement.md) | 执行 `ToD3D12(...)` 相关流程。 |
- `D3D12PipelineState` 现在有独立的 `m_sampleQuality`
- `Initialize(...)` 会从 `D3D12_GRAPHICS_PIPELINE_STATE_DESC::SampleDesc.Quality` 回填该值
- `SetSampleQuality(...)` 会更新缓存状态
- `CreateD3D12PSO()` 会把它写回 `desc.SampleDesc.Quality`
- `GetHash()` 现在同时把 `renderTargetCount``depthStencilFormat``sampleCount``sampleQuality` 编进 hash
这意味着对 D3D12 后端来说,不同的 MSAA quality 已经是不同的管线状态,而不只是无关紧要的附属参数。
### 图形与计算路径
- 图形路径依赖输入布局、固定功能状态和 VS/PS bytecode 共同创建 graphics PSO
- 计算路径依赖 compute shader bytecode 创建 compute PSO
- `GetType()` 仍然通过“是否有 compute shader”区分 `Graphics``Compute`
### 有效性
`IsValid()` 表示当前对象是否已经具备可用的 native PSO`EnsureValid()` 则负责在需要时推进到该状态。对 D3D12 来说,这种语义比较接近“真正的 PSO 是否已经可绑定”。
## 设计理解
`sampleQuality` 被正式纳入 D3D12 实现后,这个类更完整地承担了“把统一描述翻译成 D3D12 PSO”的职责。这样做的好处有两个
- 上层渲染器不需要为了 quality 字段单独写 D3D12 分支
- 后端 hash 与 PSO 创建语义保持一致,避免把实际上不同的 MSAA 配置错误地视为同一个管线
这类一致性对商业引擎非常重要,因为 PSO 缓存、材质系统和渲染图规划都依赖稳定而精确的状态键。
## 重点公共方法
- [Initialize](Initialize.md)
- [SetInputLayout](SetInputLayout.md)
- [SetRasterizerState](SetRasterizerState.md)
- [SetBlendState](SetBlendState.md)
- [SetDepthStencilState](SetDepthStencilState.md)
- [SetTopology](SetTopology.md)
- [SetRenderTargetFormats](SetRenderTargetFormats.md)
- [SetSampleCount](SetSampleCount.md)
- [SetSampleQuality](SetSampleQuality.md)
- [SetComputeShader](SetComputeShader.md)
- [SetRootSignature](SetRootSignature.md)
- [GetHash](GetHash.md)
- [EnsureValid](EnsureValid.md)
- [GetPipelineState](GetPipelineState.md)
- [GetComputePipelineState](GetComputePipelineState.md)
## 相关文档
- [当前目录](../D3D12.md) - 返回 `D3D12` 平行目录
- [API 总索引](../../../../main.md) - 返回顶层索引
- [当前目录](../D3D12.md)
- [RHIPipelineState](../../RHIPipelineState/RHIPipelineState.md)
- [D3D12Device](../D3D12Device/D3D12Device.md)
- [D3D12CommandList](../D3D12CommandList/D3D12CommandList.md)
- [API 总索引](../../../../main.md)

View File

@@ -14,11 +14,12 @@ void SetSampleCount(uint32_t count) override;
## 作用
更新 `m_sampleCount`
更新 `m_sampleCount``quality` 维度现在由 [SetSampleQuality](SetSampleQuality.md) 单独负责
## 当前实现
- 会更新 `m_sampleCount`
- `D3D12_SAMPLE_DESC.Quality` 不在这里设置,而是在 `SetSampleQuality(...)` 中写入 `m_sampleQuality`
## 相关文档

View File

@@ -0,0 +1,36 @@
# D3D12PipelineState::SetSampleQuality
**命名空间**: `XCEngine::RHI`
**类型**: `method`
**头文件**: `XCEngine/RHI/D3D12/D3D12PipelineState.h`
## 签名
```cpp
void SetSampleQuality(uint32_t quality) override;
```
## 作用
设置 D3D12 管线状态缓存中的 MSAA quality 级别。
## 当前实现
当前实现会直接把参数写入 `m_sampleQuality`。这个值随后会在两个关键位置被消费:
1. `GetHash()` 会把 `m_sampleQuality` 纳入 hash 计算
2. `CreateD3D12PSO()` 会把它写入 `D3D12_GRAPHICS_PIPELINE_STATE_DESC::SampleDesc.Quality`
因此它已经是影响 PSO 身份和 native 创建结果的真实状态,而不是占位字段。
## 为什么重要
如果只记录 `sampleCount`,却忽略 quality那么不同的 D3D12 多重采样配置可能会被错误地视为同一个管线状态。把 quality 纳入缓存和 hash才能保证高层描述、PSO 创建结果和缓存键保持一致。
## 相关文档
- [D3D12PipelineState](D3D12PipelineState.md)
- [RHIPipelineState::SetSampleQuality](../../RHIPipelineState/SetSampleQuality.md)
- [SetSampleCount](SetSampleCount.md)

View File

@@ -6,56 +6,44 @@
**头文件**: `XCEngine/RHI/OpenGL/OpenGLPipelineState.h`
**描述**: OpenGL 后端的 CPU 侧管线状态容器;它统一跨后端 PSO 概念,但真实行为仍然是“在绑定时把缓存状态写回 OpenGL 上下文”
**描述**: OpenGL 后端的 `RHIPipelineState` 实现。它统一接受跨后端管线描述,但真实行为更接近“绑定时写回状态”的 CPU 侧状态容器,而不是显式 API 意义上的原生 PSO
## 概览
## 角色概述
`OpenGLPipelineState` 和 D3D12/Vulkan 的原生 PSO 有本质区别。显式 API 的 PSO 更像“预编译不可变的 GPU 对象”;当前 OpenGL 实现则是一个运行时状态包,里面保存的是:
OpenGL 没有 D3D12/Vulkan 那样的标准化 native PSO 语义,因此 `OpenGLPipelineState` 的职责不是预编译一个不可变的 GPU 管线对象,而是把高层状态先缓存下来,在 `Bind()` 时按需写回 OpenGL 上下文。
- graphics program 或 compute program 句柄
- 输入布局描述
- 深度/模板状态
- 混合状态
- 光栅化状态
- viewport / scissor / clear color 等附加缓存
这类设计在商业引擎里非常常见:抽象层仍然保持现代 PSO 形状,后端实现则承认旧式状态机 API 的现实。
真正的 OpenGL 调用发生在 [Bind](Bind.md) 和各个 `Apply*` 方法里,而不是在对象创建时一次性固化。
## 当前实现要点
## 当前实现的真实行为
- `Bind()` 会优先绑定 compute program否则绑定 graphics program
- `Apply()` 负责把缓存的深度/模板、混合和光栅化状态写回 OpenGL
- viewport 与 scissor 仍由独立方法处理
- [Bind](Bind.md) 会优先绑定 compute program否则绑定 graphics program
- `Bind()` 之后只会调用 [Apply](Apply.md),即深度/模板、混合、光栅化三类状态
- [ApplyViewport](ApplyViewport.md) 与 [ApplyScissor](ApplyScissor.md) 需要单独调用
- [SetRenderTargetFormats](SetRenderTargetFormats.md)、[SetSampleCount](SetSampleCount.md)、[GetHash](GetHash.md) 目前基本是占位实现
- [IsValid](IsValid.md) 永远返回 `true`
- [EnsureValid](EnsureValid.md) 为空实现
- [GetNativeHandle](GetNativeHandle.md) 只返回 graphics `m_program`
同时需要明确几件事情:
## 设计背景
- [SetRenderTargetFormats](SetRenderTargetFormats.md) 当前基本是占位接口
- [SetSampleCount](SetSampleCount.md) 当前基本是占位接口
- [SetSampleQuality](SetSampleQuality.md) 当前同样是占位接口
- [GetHash](GetHash.md) 目前也不是严格的跨状态稳定键
- [IsValid](IsValid.md) 近似恒为 `true`
- [EnsureValid](EnsureValid.md) 当前为空实现
商业引擎在做 OpenGL 后端时,通常不会试图伪造一套完全等价于 D3D12/Vulkan 的原生 PSO而是会保留一个“高层 PSO 接口 + 低层状态写回器”的折中结构。当前实现就属于这一类设计:
## 为什么仍然保留 SetSampleQuality
- 高层渲染代码仍可依赖统一的 `RHIPipelineState`
- OpenGL 后端把真实成本集中到绑定阶段
- 这样既保留了跨后端 API 一致性,也避免在 OpenGL 上硬造不存在的驱动对象
虽然当前 OpenGL 后端并没有把 sample quality 落到真实状态,但接口仍然保留,原因并不奇怪:
这和 Unity、Unreal 等商业引擎在老式状态机 API 上常用的策略是一致的:抽象层保持现代接口形状,后端实现接受状态机现实。
- 上层渲染器希望使用统一的 `RHIPipelineState` 契约
- 文档需要如实说明“该字段在 OpenGL 后端尚未真正消费”
- 将来如果后端补充更细的多重采样策略,不需要再改上层 API
## 生命周期
这正是重构期商业引擎常见的渐进式契约策略。
- [OpenGLPipelineState()](Constructor.md) 初始化为空容器
- 上层通过 [SetInputLayout](SetInputLayout.md)、[SetBlendState](SetBlendState.md)、[SetDepthStencilState](SetDepthStencilState.md)、[SetRasterizerState](SetRasterizerState.md) 等接口缓存状态
- 设备创建 graphics pipeline 时,会通过 [SetOwnedGraphicsShader](SetOwnedGraphicsShader.md) 把编译好的 `OpenGLShader` 交给它管理
- 命令提交阶段由 [Bind](Bind.md) 把状态真正写入 OpenGL
- [Shutdown](Shutdown.md) 释放自有 graphics shader并重置程序句柄与标记位
## 重点限制
## 重要限制
- 不是原生 OpenGL pipeline object
- 不是不可变对象,状态可以随时被覆盖
- 不自动应用 viewport / scissor
- 部分 RHI 描述字段只被缓存,没有进入真实 OpenGL 调用
- 没有稳定的 hash 和严格的创建期验证
- 它不是原生 OpenGL pipeline object
- 它不是不可变对象,状态可以被持续覆盖
- MSAA 相关接口当前只保留抽象层形状,不代表后端已经完整支持
## 关键方法
@@ -66,9 +54,13 @@
- [SetBlendState](SetBlendState.md)
- [SetDepthStencilState](SetDepthStencilState.md)
- [SetRasterizerState](SetRasterizerState.md)
- [SetSampleCount](SetSampleCount.md)
- [SetSampleQuality](SetSampleQuality.md)
## 相关文档
- [OpenGLShader](../OpenGLShader/OpenGLShader.md)
- [OpenGLCommandList](../OpenGLCommandList/OpenGLCommandList.md)
- [OpenGLDevice](../OpenGLDevice/OpenGLDevice.md)
- [RHIPipelineState](../../RHIPipelineState/RHIPipelineState.md)
- [API 总索引](../../../../main.md)

View File

@@ -6,12 +6,13 @@ void SetSampleCount(uint32_t count) override;
## 作用
为统一 RHI 接口预留采样数设置入口
为统一 RHI 接口预留 sample count 设置入口;`quality` 维度在 [SetSampleQuality](SetSampleQuality.md) 中单独暴露
## 当前实现行为
- 空实现
- `count` 当前不会被缓存,也不会驱动 `GL_MULTISAMPLE` 之外的任何行为
- OpenGL 后端当前也不会在这里处理 sample quality
## 相关文档

View File

@@ -0,0 +1,33 @@
# OpenGLPipelineState::SetSampleQuality
**命名空间**: `XCEngine::RHI`
**类型**: `method`
**头文件**: `XCEngine/RHI/OpenGL/OpenGLPipelineState.h`
## 签名
```cpp
void SetSampleQuality(uint32_t quality) override;
```
## 作用
接受抽象层传入的 MSAA quality 参数。
## 当前实现
当前实现是空函数,`quality` 不会被保存,也不会落到真实 OpenGL 状态。它的主要意义是让 `OpenGLPipelineState` 满足统一的 `RHIPipelineState` 契约。
## 这意味着什么
- 上层可以继续用统一接口描述多重采样配置
- 但不能把该调用理解成“OpenGL 后端已经支持 sample quality 级别控制”
- 当前如果逻辑严格依赖 quality 的差异化行为,应避免在 OpenGL 后端做此类假设
## 相关文档
- [OpenGLPipelineState](OpenGLPipelineState.md)
- [RHIPipelineState::SetSampleQuality](../../RHIPipelineState/SetSampleQuality.md)
- [SetSampleCount](SetSampleCount.md)

View File

@@ -10,16 +10,54 @@
```cpp
virtual RHIBuffer* CreateBuffer(const BufferDesc& desc) = 0;
virtual RHIBuffer* CreateBuffer(
const BufferDesc& desc,
const void* initialData,
size_t initialDataSize,
ResourceStates finalState = ResourceStates::GenericRead);
```
## 作用
纯虚接口
创建 `RHIBuffer`。基础重载只创建资源本体;带初始数据的重载额外负责把给定字节流写入缓冲,并把资源切换到目标状态
## 当前实现
## 默认实现语义
- 该声明是纯虚接口,基类不提供实现。
抽象基类只把第一种重载保留为纯虚接口。第二种重载在基类里已经提供了统一默认实现,流程如下:
1. 如果没有初始数据,直接转发到 `CreateBuffer(desc)`
2. 如果 `initialDataSize > desc.size`,返回 `nullptr`
3. 创建基础缓冲。
4. 通过 `RHIBuffer::SetData` 写入初始数据。
5. 如果缓冲比初始数据更大,则把剩余区域补零。
6. 调用 `SetState(finalState)`,让返回对象带着上层期望的资源状态离开工厂。
这意味着“带初始化数据的创建”在抽象层已经成为正式契约,而不是某个后端的私有便利函数。
## 设计意图
这个重载的价值不是取代完整的上传系统,而是给渲染器、资源加载器和测试代码一个统一、直接的入口:
- 小型静态常量缓冲和顶点数据可以一步创建完成。
- 各后端都能保留自己的最佳实现空间。
- 上层不需要为“先建空缓冲,再手动上传”重复铺设样板逻辑。
商业引擎底层通常都会保留这种“便捷初始化路径”,因为它能降低资源引导阶段的复杂度;真正的大规模流式上传则仍然会走专门的上传器和队列系统。
## 后端实现差异
- `D3D12Device` 已经重写该重载,使用上传缓冲和拷贝命令完成初始化,而不是依赖默认的 `SetData` 路径。
- 其他设备实现可以继续继承默认语义,也可以在需要时重写以获得更好的性能或更严格的状态控制。
## 使用建议
- 适合启动期、测试、工具链或中小型静态数据。
- 不要把它当成高频 streaming API。
- 调用方仍然负责对象生命周期管理:成功返回后需要在合适时机 `Shutdown()` 并销毁缓冲。
## 相关文档
- [RHIDevice](RHIDevice.md)
- [RHIBuffer](../RHIBuffer/RHIBuffer.md)
- [D3D12Device::CreateBuffer](../D3D12/D3D12Device/CreateBuffer.md)

View File

@@ -6,134 +6,80 @@
**头文件**: `XCEngine/RHI/RHIDevice.h`
**描述**: 抽象图形设备接口,负责设备初始化、能力查询,并作为大多数 RHI 对象的统一创建中心
**描述**: `RHI` 设备抽象,负责初始化后端、暴露能力信息,并作为大多数资源、命令与管线对象的统一创建入口
## 角色概述
`RHIDevice` 当前 `RHI` 抽象层的中心对象。它不像某些引擎那样把资源工厂、提交器、能力查询器分拆成多套接口,而是采用一种更集中的“设备即工厂”设计:
`RHIDevice` 当前架构里承担的是“设备即工厂”的职责。渲染器不需要分别依赖 buffer 工厂、texture 工厂、pipeline 工厂和 descriptor 工厂,而是统一通过一个设备对象创建后端资源。这种做法和很多商业级引擎底层渲染架构一致,优点是上层入口集中、后端差异被压缩在实现层,代价是 `RHIDevice` 接口面会比较大。
- 初始化后端上下文或 native device
- 持有能力信息和设备信息
- 创建资源对象、命令对象、pipeline 对象和 descriptor 对象
- 提供必要的 native handle 泄露口
## 为什么这样设计
这和原生图形 API 的心智模型比较接近,也符合很多商业引擎底层渲染后端的设计习惯。好处是统一、直接,代价是接口面比较大
- 上层渲染代码只需要依赖一套 `RHI` 入口,不必按后端分叉创建流程
- `BufferDesc``TextureDesc``GraphicsPipelineDesc` 这些描述结构可以直接作为跨后端契约。
- 同一个创建请求可以在不同后端走不同实现细节,例如直接 CPU 写入、上传缓冲复制、延迟编译或立即创建。
这和 Unity SRP、Unreal RHI 一类设计的核心思路一致:高层描述保持统一,真正的后端差异放在设备和资源实现里消化。
## 生命周期
### 初始化
`Initialize(const RHIDeviceDesc&)` 是创建后的第一步。当前 `RHIDeviceDesc` 只暴露了三个设备级开关:
- `enableDebugLayer`
- `enableGPUValidation`
- `adapterIndex`
这说明当前设备初始化配置还比较克制,没有把所有平台差异都堆进入口参数里。
`Initialize(const RHIDeviceDesc&)` 负责建立后端上下文并填充能力与设备信息。当前设备描述主要控制调试层、GPU 验证和适配器选择,说明引擎仍然把设备创建视为底层、工程化的初始化步骤,而不是面向最终用户的复杂配置界面。
### 关闭
`Shutdown()` 负责销毁设备级状态。当前测试习惯,调用方应在删除设备之前显式调用它,而不是依赖析构函数
`Shutdown()` 负责释放设备级状态。当前 `RHI` 仍然以显式 `Shutdown()` 为主,而不是完全依赖析构自动回收,因此调用方应把“显式关闭后再销毁对象”视为标准用法
### 查询
## 缓冲创建模型
初始化成功后,调用方通常会继续读取
`CreateBuffer` 现在有两种形态,且它们表达的是两种不同层级的需求
- [GetCapabilities](GetCapabilities.md)
- [GetDeviceInfo](GetDeviceInfo.md)
- [GetNativeDevice](GetNativeDevice.md)
```cpp
virtual RHIBuffer* CreateBuffer(const BufferDesc& desc) = 0;
virtual RHIBuffer* CreateBuffer(
const BufferDesc& desc,
const void* initialData,
size_t initialDataSize,
ResourceStates finalState = ResourceStates::GenericRead);
```
其中 `GetNativeDevice()` 明确是一个“打破抽象边界”的出口,供后端专用逻辑或调试逻辑使用。
### 基础重载
## 它负责创建什么
`CreateBuffer(const BufferDesc&)` 只负责创建资源本体,不附带初始化数据。具体放在哪种堆、初始状态是什么、后端如何实现,都由具体设备类决定。
当前接口覆盖了 RHI 里绝大多数对象类别。
### 带初始数据的重载
### 资源与展示
这个重载是最近补进来的便捷契约,目的是让上层可以用一条调用完成“创建 + 上传初始内容 + 进入目标状态”。基类默认实现会:
- [CreateBuffer](CreateBuffer.md)
- [CreateTexture](CreateTexture.md)
- [CreateSwapChain](CreateSwapChain.md)
1.`initialData == nullptr` 或大小为 `0` 时退化到基础重载。
2. 拒绝 `initialDataSize > desc.size` 的非法输入。
3. 先创建基础缓冲。
4. 对前半段调用 `SetData(initialData, initialDataSize)`
5. 如果 `desc.size` 更大,则把剩余区间补零。
6. 最后把资源状态设置为 `finalState`
`CreateTexture()` 还有一个带 `initialData``rowPitch` 的重载,允许直接创建并上传初始数据
这个默认实现强调的是统一语义,而不是最优性能。它适合小型静态数据、启动期资源或测试代码,不适合高频流式上传
### 执行与同步
## 后端差异
- [CreateCommandList](CreateCommandList.md)
- [CreateCommandQueue](CreateCommandQueue.md)
- [CreateFence](CreateFence.md)
- `D3D12Device` 对带初始数据的 `CreateBuffer` 做了专门重写,会显式创建上传缓冲、录制拷贝命令并做状态转换。
- 其他后端当前只需要满足统一契约,不一定都实现了同等级别的上传路径优化。
### shader 与管线
这正是抽象层的价值所在:上层只说“我要一个带初始内容的缓冲”,至于后端是 CPU 直写、上传队列复制还是暂时退化实现,都留给设备实现层。
- [CreateShader](CreateShader.md)
- [CreatePipelineState](CreatePipelineState.md)
- [CreatePipelineLayout](CreatePipelineLayout.md)
- [CreateSampler](CreateSampler.md)
## 所有权与使用约定
### render pass 与 framebuffer
当前 `RHI` 仍然大量返回裸指针,调用方负责:
- [CreateRenderPass](CreateRenderPass.md)
- [CreateFramebuffer](CreateFramebuffer.md)
1. 在使用结束前调用对象自己的 `Shutdown()`
2. 在确认资源不再被引用后显式 `delete`
### descriptor 与资源视图
因此文档和代码都不应把这层接口描述成“天然 RAII 安全”的现代封装。它更接近商业引擎底层后端常见的显式生命周期模型。
- [CreateDescriptorPool](CreateDescriptorPool.md)
- [CreateDescriptorSet](CreateDescriptorSet.md)
- [CreateVertexBufferView](CreateVertexBufferView.md)
- [CreateIndexBufferView](CreateIndexBufferView.md)
- [CreateRenderTargetView](CreateRenderTargetView.md)
- [CreateDepthStencilView](CreateDepthStencilView.md)
- [CreateShaderResourceView](CreateShaderResourceView.md)
- [CreateUnorderedAccessView](CreateUnorderedAccessView.md)
## 公开方法
从接口形状就能看出,当前 `RHIDevice` 同时承担了资源创建器、视图工厂、descriptor 工厂和 pipeline 工厂的职责。
## 所有权约定
这是这页最关键的现实语义。
当前 `RHIDevice` 的所有创建接口都返回裸指针。按 `tests/RHI/unit/test_device.cpp``test_command_list.cpp` 等现有测试来看,推荐使用方式是:
1. 通过 `RHIDevice` 创建对象。
2. 使用对象执行初始化、录制或资源操作。
3. 结束前调用对象自己的 `Shutdown()`
4. 最后 `delete`
这同样适用于 `buffer``texture``queue``command list``fence``sampler``render pass``framebuffer``descriptor pool``descriptor set``resource view` 等对象。
如果带着现代 RAII 预期来使用这层接口,很容易误判释放责任。当前文档必须把这一点写死。
## 当前设计理解
从架构方向看,`RHIDevice` 的定位是合理的:
- 抽象层统一把后端对象创建集中到设备上,方便 renderer 只依赖一套入口。
- 设备负责暴露能力信息,便于上层在初始化阶段建立 feature branch。
- 资源视图和 descriptor 也由设备创建,符合 D3D12 / Vulkan 风格资源管理直觉。
但从当前实现成熟度看,也要认识到几个边界:
- 还没有单独的内存分配器 / residency / heap 管理抽象。
- 资源创建描述符虽然统一,但很多字段仍是 `uint32_t` 承载枚举值,而不是更强类型的 API。
- `GetNativeDevice()` 表明上层仍可能在必要时下钻到后端实现。
这说明当前阶段的目标更偏“稳定支撑后端实现和测试”,而不是已经完成最终抽象收敛。
## 测试中体现出的真实用法
现有测试提供了比模板文档更可靠的使用线索:
- 创建设备后先 `Initialize()`
- 查询 `GetCapabilities()``GetDeviceInfo()`
- 创建 `Buffer``Texture``Fence``CommandQueue``CommandList``Sampler` 等对象。
- 对每个对象做基本验证后 `Shutdown()``delete`
这说明 `RHIDevice` 当前不仅是概念入口,而且已经是单元测试和集成测试里的真实生产入口。
## 公共方法
- [Initialize](Initialize.md) - 初始化设备。
- [Shutdown](Shutdown.md) - 关闭设备并释放设备级状态。
- [Initialize](Initialize.md)
- [Shutdown](Shutdown.md)
- [CreateBuffer](CreateBuffer.md)
- [CreateTexture](CreateTexture.md)
- [CreateSwapChain](CreateSwapChain.md)
@@ -165,6 +111,7 @@
## 相关文档
- [当前模块](../RHI.md)
- [RHIBuffer](../RHIBuffer/RHIBuffer.md)
- [RHIFactory](../RHIFactory/RHIFactory.md)
- [RHICommandQueue](../RHICommandQueue/RHICommandQueue.md)
- [RHICommandList](../RHICommandList/RHICommandList.md)

View File

@@ -6,24 +6,20 @@
**头文件**: `XCEngine/RHI/RHIPipelineState.h`
**描述**: 抽象图形/计算管线状态对象负责聚合输入布局、光栅化、混合、深度模板、render target 格式以及可选计算 shader
**描述**: 图形/计算管线状态的跨后端抽象,负责收敛固定功能状态、着色器与目标格式,并向具体后端暴露统一的配置接口
## 角色概述
`RHIPipelineState` 对应的是当前抽象层里的“可绑定管线状态包”。它的接口明显带有“统一图形状态描述”的设计意图,而不是简单把某个后端的 native PSO 结构原样暴露出去
`RHIPipelineState` 对应的是“上层渲染系统描述管线状态”的统一入口。它不是简单照抄某个 native API 的 PSO 结构,而是把输入布局、光栅化、混合、深度模板、拓扑、目标格式、多重采样信息以及计算着色器统一收敛到一套接口上
头文件注释里直接写了
这是一种典型的商业引擎设计思路
- `Unity SRP style`
- `Shader independent of PSO`
这说明当前设计方向是希望用一套更统一的 pipeline 状态模型去支撑不同后端,而不是要求所有状态都只通过 shader 或 native API 自己的配置路径表达。
- 渲染器先在统一描述层组织状态。
- 具体后端再决定这些状态是立即创建 native PSO、延迟编译还是只作为运行时状态缓存。
## 当前状态模型
`RHIPipelineState` 当前主要包含两类信息:
### 图形状态
### 图形路径
- [SetInputLayout](SetInputLayout.md)
- [SetRasterizerState](SetRasterizerState.md)
@@ -32,76 +28,49 @@
- [SetTopology](SetTopology.md)
- [SetRenderTargetFormats](SetRenderTargetFormats.md)
- [SetSampleCount](SetSampleCount.md)
- [SetSampleQuality](SetSampleQuality.md)
### 计算路径
- [SetComputeShader](SetComputeShader.md)
也就是说,当前同一个抽象接口既能表达 graphics pipeline,也能表达 compute pipeline
其中 `SetSampleCount``SetSampleQuality` 共同表达多重采样配置,目的是让 `GraphicsPipelineDesc` 的采样参数可以完整下传到后端,而不是让上层在描述层就为不同图形 API 分叉
## 类型与有效性
## 为什么要有 SetSampleQuality
这是这页最值得说明的地方。
`sampleQuality` 在显式图形 API 中并不是完全对等的概念:
### `GetType()`
- D3D12 会直接把它映射到 `DXGI_SAMPLE_DESC::Quality`
- Vulkan 当前实现只消费 sample count不消费 quality
- OpenGL 当前实现也没有把 quality 落到真实状态
从现有测试看
但抽象层仍然保留这个接口,原因很明确
- 默认创建出来的 pipeline state 类型是 `PipelineType::Graphics`
- 当设置了 compute shader 后,类型可以变成 `PipelineType::Compute`
- 上层 `GraphicsPipelineDesc` 可以保持完整
- 后端可以渐进式演进,不必修改渲染器对外契约
- 文档能够明确告诉使用者“这个字段在不同后端的成熟度不同”
### `IsValid()` / `EnsureValid()`
这比“为了照顾当前最弱后端,直接把接口删掉”更符合商业级引擎的长期演进方式。
现有测试明确揭示了一个很重要的后端差异:
## 后端语义差异
- D3D12 下,默认或信息不足的 pipeline state 往往不是立即有效的
- 在 OpenGL 路径下,默认 pipeline state 更容易被视为有效
- `D3D12PipelineState` 会真正存储 sample quality并让它进入 PSO 创建与 hash
- `VulkanPipelineState` 暂时把 quality 视为兼容字段,当前实现忽略其值
- `OpenGLPipelineState` 当前同样把它作为占位契约处理。
头文件注释甚至直接写出了当前统一语义:
因此 `RHIPipelineState` 的接口是稳定的,但“字段被消费的深度”仍然是后端相关事实,文档必须把这一点写清楚。
- `D3D12` 需要编译
- `OpenGL` 总是有效
## 生命周期与有效性
这意味着 `RHIPipelineState` 的“有效”不是完全抽象无差异的概念而是和后端实现成熟度、native API 要求直接相关。
`RHIPipelineState` 的“有效”并不保证在所有后端都等价于“native pipeline 已经创建完成”:
## 设计理解
- 有的后端在初始化时就固化 native 对象。
- 有的后端在 `EnsureValid()` 时才推进到真正可绑定状态。
- 还有的后端只是把状态缓存起来,绑定时再逐步写回。
从商用引擎经验看,这样的 pipeline state 抽象是合理的:
这也是为什么文档不能把所有实现都粗暴描述成“标准 PSO”。
- renderer 可以先在统一描述层拼好状态,再交给后端去编译或绑定
- 输入布局、混合、深度模板和 render target 格式等关键状态有明确归属
- 计算路径可以与图形路径复用一部分生命周期和绑定逻辑
但当前实现也有现实边界:
- 还保留了 `RHICommandList::SetShader()` 这种并行路径,所以整个系统并不是“只有 PSO”这一种绑定范式
- `GraphicsPipelineDesc``RHIPipelineState` 的关系更偏工程实现上的状态收敛,而不是已经完全封装成单一材质系统
- 有效性判定明显依赖后端
## 测试体现出的真实使用方式
`tests/RHI/unit/test_pipeline_state.cpp``test_compute.cpp` 给出了当前比较可信的使用路径:
- 可以先创建默认 `GraphicsPipelineDesc` 再逐步设置状态
- 可以读回 `RasterizerDesc``BlendDesc``DepthStencilStateDesc``InputLayoutDesc`
- 可以为 graphics pipeline 直接在 `GraphicsPipelineDesc` 里提供 vertex / fragment shader
- 可以用 `SetComputeShader()` 走 compute 路径
- 在部分后端下 `EnsureValid()` 会触发或推进有效性检查,但并不保证无条件成功
这说明当前 pipeline state 的职责更像“后端可消费的统一状态对象”,而不是文档层面随便说说的概念壳子。
## 生命周期
当前接口提供:
- [Bind](Bind.md)
- [Unbind](Unbind.md)
- [Shutdown](Shutdown.md)
- [GetNativeHandle](GetNativeHandle.md)
通常它由 [RHIDevice](../RHIDevice/RHIDevice.md) 创建,并以裸指针形式返回。使用完成后应显式 `Shutdown()``delete`
## 公共方法
## 公开方法
- [SetInputLayout](SetInputLayout.md)
- [SetRasterizerState](SetRasterizerState.md)
@@ -110,6 +79,7 @@
- [SetTopology](SetTopology.md)
- [SetRenderTargetFormats](SetRenderTargetFormats.md)
- [SetSampleCount](SetSampleCount.md)
- [SetSampleQuality](SetSampleQuality.md)
- [SetComputeShader](SetComputeShader.md)
- [GetRasterizerState](GetRasterizerState.md)
- [GetBlendState](GetBlendState.md)
@@ -129,8 +99,10 @@
## 相关文档
- [当前模块](../RHI.md)
- [RHITypes](../RHITypes/RHITypes.md)
- [RHICommandList](../RHICommandList/RHICommandList.md)
- [RHIDevice](../RHIDevice/RHIDevice.md)
- [RHIPipelineLayout](../RHIPipelineLayout/RHIPipelineLayout.md)
- [RHIShader](../RHIShader/RHIShader.md)
- [D3D12PipelineState](../D3D12/D3D12PipelineState/D3D12PipelineState.md)
- [OpenGLPipelineState](../OpenGL/OpenGLPipelineState/OpenGLPipelineState.md)
- [VulkanPipelineState](../Vulkan/VulkanPipelineState/VulkanPipelineState.md)
- [API 总索引](../../../main.md)

View File

@@ -14,11 +14,12 @@ virtual void SetSampleCount(uint32_t count) = 0;
## 作用
纯虚接口
设置多采样的 sample count 维度;当前接口已经把 sample quality 拆到独立的 [SetSampleQuality](SetSampleQuality.md)
## 当前实现
- 该声明是纯虚接口,基类不提供实现。
- 该声明是纯虚接口,基类不提供实现。
- 当前不要把它理解成“多采样配置的唯一入口”,因为 `quality` 已经是单独方法。
## 相关文档

View File

@@ -0,0 +1,49 @@
# RHIPipelineState::SetSampleQuality
**命名空间**: `XCEngine::RHI`
**类型**: `method`
**头文件**: `XCEngine/RHI/RHIPipelineState.h`
## 签名
```cpp
virtual void SetSampleQuality(uint32_t quality) = 0;
```
## 作用
设置管线状态的多重采样质量级别。它与 [SetSampleCount](SetSampleCount.md) 一起描述最终的 MSAA 配置。
## 设计意图
这个接口的存在不是偶然补丁,而是抽象层契约的一部分:
- `GraphicsPipelineDesc` 可以完整表达 `sampleCount``sampleQuality`
- 后端不需要为了当前实现成熟度不同而迫使上层分叉
- 文档可以明确说明“哪些后端真正消费了该字段,哪些暂时忽略”
这类“先保留稳定契约,再让后端逐步补齐实现”的做法,在商业引擎重构阶段很常见。
## 当前后端语义
- `D3D12PipelineState` 会存储该值,并把它写进 `DXGI_SAMPLE_DESC::Quality`
- `VulkanPipelineState` 当前实现忽略该值
- `OpenGLPipelineState` 当前实现同样忽略该值
因此它不是“所有后端都已有完整效果”的统一语义,而是“所有后端都接受同一描述接口”的统一语义。
## 使用建议
-`SetSampleCount` 配套使用。
- 如果你的上层逻辑需要严格依赖 quality 级别,当前应明确限制在 D3D12 后端验证。
- 不要假设所有后端对该字段都具备同等成熟度。
## 相关文档
- [RHIPipelineState](RHIPipelineState.md)
- [SetSampleCount](SetSampleCount.md)
- [D3D12PipelineState::SetSampleQuality](../D3D12/D3D12PipelineState/SetSampleQuality.md)
- [VulkanPipelineState::SetSampleQuality](../Vulkan/VulkanPipelineState/SetSampleQuality.md)
- [OpenGLPipelineState::SetSampleQuality](../OpenGL/OpenGLPipelineState/SetSampleQuality.md)

View File

@@ -6,12 +6,13 @@ void SetSampleCount(uint32_t count) override;
## 作用
更新缓存的采样数
更新缓存的 sample count`quality` 维度由 [SetSampleQuality](SetSampleQuality.md) 单独暴露
## 当前实现行为
- `count > 0` 时保存原值
- 否则回退为 `1`
- Vulkan 后端当前不会在这里处理 sample quality
## 相关文档

View File

@@ -0,0 +1,38 @@
# VulkanPipelineState::SetSampleQuality
**命名空间**: `XCEngine::RHI`
**类型**: `method`
**头文件**: `XCEngine/RHI/Vulkan/VulkanPipelineState.h`
## 签名
```cpp
void SetSampleQuality(uint32_t quality) override;
```
## 作用
接受抽象层传入的 sample quality 参数。
## 当前实现
当前实现不会保存或消费这个值,函数体只有 `(void)quality`。也就是说Vulkan 后端当前把它视为接口兼容字段,而不是创建 `VkPipeline` 的真实输入。
## 为什么接口仍然保留
- `RHIPipelineState` 需要保持统一的跨后端契约
- 上层 `GraphicsPipelineDesc` 不必因为某个后端暂未使用该字段就裁掉描述能力
- 后续如果 Vulkan 路径需要引入更细的采样策略,不必再改外层 API
## 使用建议
- 需要真实控制 Vulkan 多重采样时,应优先关注 [SetSampleCount](SetSampleCount.md)
- 不要假设 quality 值当前会影响 Vulkan pipeline 创建结果
## 相关文档
- [VulkanPipelineState](VulkanPipelineState.md)
- [RHIPipelineState::SetSampleQuality](../../RHIPipelineState/SetSampleQuality.md)
- [SetSampleCount](SetSampleCount.md)

View File

@@ -6,157 +6,66 @@
**头文件**: `XCEngine/RHI/Vulkan/VulkanPipelineState.h`
**描述**: Vulkan 后端`RHIPipelineState` 实现,负责创建 graphics / compute pipeline并保存与之相关的 layout、render target 格式和固定功能状态
**描述**: Vulkan 后端`RHIPipelineState` 实现,负责收敛图形/计算管线描述,并在需要时创建 `VkPipeline`
## 概览
## 角色概述
`VulkanPipelineState` 是当前 Vulkan 后端里 draw / dispatch 能否真正执行的关键对象
`VulkanPipelineState` 是当前 Vulkan 路径里最核心的可绑定管线对象之一。它保存统一的管线状态描述,并在图形或计算路径上创建对应的 native pipeline
它承担两类职责
和显式 API 的常见商业引擎设计一样,它试图把高层的统一描述与底层 Vulkan 创建过程隔离开
- 保存抽象层的 pipeline 描述,例如输入布局、光栅化、混合、深度模板、拓扑和 render target 格式
- 在合适时机创建原生 `VkPipeline`
- 高层只关心输入布局、固定功能状态、shader 与目标格式
- 后端负责决定何时创建 `VkPipeline`、如何组织 pipeline layout以及哪些能力当前已经成熟
但它不是一个完全成熟、完全统一的 PSO 系统。当前 graphics 和 compute 路径的成熟度并不一致,这一点文档必须明确写出来。
## 当前实现要点
## 生命周期
### 图形与计算路径成熟度不同
常见用法有两种:
- `Initialize(...)` 面向图形管线配置
- 计算管线依赖 `SetComputeShader()``EnsureValid()` 延迟创建
- `Bind()` / `Unbind()` 当前仍然是轻量或空语义
### 图形管线
所以它虽然叫 `PipelineState`,但目前仍然是“图形优先、计算补齐”的工程状态。
1. 构造 `VulkanPipelineState`
2. `Initialize(VulkanDevice*, const GraphicsPipelineDesc&)`
3. 录制阶段把它传给 [VulkanCommandList](../VulkanCommandList/VulkanCommandList.md) 的 `SetPipelineState()`
4. 不再使用时 `Shutdown()`
### SetSampleQuality 的当前语义
### 计算管线
`SetSampleQuality` 已经进入接口,但 Vulkan 后端目前显式忽略该值:
1. 初始化 pipeline state让它至少拥有 pipeline layout
2. `SetComputeShader(RHIShader*)`
3. 在录制 `Dispatch()` 前由 `EnsureValid()` 延迟创建 compute pipeline
- `SetSampleCount` 会真正保存并影响后续创建
- `SetSampleQuality` 只是接受参数后 `(void)quality`
## 当前实现的真实行为
这说明抽象层契约已经对齐,但 Vulkan 实现当前只消费多重采样中的 `count`,还没有引入独立的 quality 概念。
### graphics 与 compute 的差异
### 有效性与 native 对象
按当前实现:
`IsValid()` 更接近“对象配置可用”,而不总是严格等价于“`VkPipeline` 一定已经存在”。尤其在计算路径上,真正的 pipeline 可能要等到 `EnsureValid()` 之后才创建。
- `Initialize()` 面向的是 `GraphicsPipelineDesc`
- 如果 `vertexShader``fragmentShader` 都没有有效 payload`Initialize()` 仍可能把对象标记为 `m_isConfigured = true`
- graphics 路径要求:
- `renderTargetCount == 1`
- 第一项 render target format 有效
- vertex shader 和 fragment shader 都存在
- compute 路径不是在 `Initialize()` 里直接创建,而是走 `SetComputeShader()` + `EnsureValid()` + `CreateComputePipeline()`
## 为什么文档必须写清这个差异
因此当前类名虽然叫 `PipelineState`,但本质上是“图形优先、计算补充”的实现。
在商业引擎里,统一接口和后端成熟度不同步是正常现象。真正危险的不是接口暂时不对等,而是文档把它们写成“都一样”。把这种差异明确写出来,调用者才能知道:
### Pipeline Layout
`EnsurePipelineLayout(desc)` 有两种行为:
- 如果 `desc.pipelineLayout` 非空,直接借用外部 `VulkanPipelineLayout`
- 否则创建一个空的 `VkPipelineLayout`
这解释了为什么 compute 路径能先有 pipeline layout再延迟绑定 compute shader。
### Graphics Pipeline 创建
`CreateGraphicsPipeline()` 当前会:
-`CompileVulkanShader(...)``desc.vertexShader` / `desc.fragmentShader` 编译成 SPIR-V
- 临时创建两个 `VkShaderModule`
- 根据 render target / depth format 构造一个内部 `VkRenderPass`
- 再据此创建 graphics pipeline
需要注意的是,当前这个 graphics pipeline 会自己生成一个内部 `VkRenderPass`。这和一些成熟引擎里把 render pass / framebuffer compatibility 单独抽出来统一管理的做法相比,还处于更简单直接的阶段。
### Compute Pipeline 创建
`CreateComputePipeline()` 当前要求:
- `m_device` 有效
- `m_pipelineLayout` 有效
- `m_computeShader` 非空
- `m_computeShader` 实际上是有效的 `VulkanShader`
创建时会从 `VulkanShader` 里拿 `VkShaderModule` 与 entry point然后调用 `vkCreateComputePipelines()`
### `IsValid()` 与真实 native pipeline
这是当前实现里最容易误解的一点:
- `IsValid()` 返回的是 `m_isConfigured`
- 它不等价于“`m_pipeline != VK_NULL_HANDLE` 一定成立”
尤其在 compute 路径里pipeline 可能要等 `EnsureValid()` 之后才真正创建。因此把 `IsValid()` 理解成“配置状态可用”更准确,而不是“原生对象已经存在”。
### 其他行为
- `Bind()` / `Unbind()` 当前都是 no-op
- `GetHash()` 目前只用了拓扑和第一个 render target format 做轻量 hash
- `HasDepthStencilAttachment()` 只按 `m_depthStencilFormat` 是否为 `Unknown` 判断
## 线程语义
当前实现没有内部锁。更安全的工程约束是:
- pipeline state 构建与修改在单线程完成
- 构建完成后再交给录制线程使用
- 不要在录制过程中并发修改同一个 pipeline state
## 所有权与资源管理
- `VulkanPipelineState` 持有 `VkPipeline`
- 可能持有自建的 `VkPipelineLayout`
- graphics 路径还会持有内部创建的 `VkRenderPass`
- 如果借用了外部 `VulkanPipelineLayout`,则不会在自身 `Shutdown()` 中销毁它
## 设计取向
当前实现非常典型地体现了“先让 pipeline 概念在跨后端 RHI 里工作起来,再逐步收敛成更严谨的 Vulkan 语义”:
- 优点是 graphics / compute 基本链路已经能跑
- 代价是状态有效性、render pass 归属和 hash 策略仍较简化
这类设计在商业引擎重构早期很常见。真正成熟后,通常会继续把 pipeline cache、render pass 兼容性、shader reflection 和 descriptor layout 规则再往前推一层。
## 当前限制
- graphics 路径当前只支持一个 render target
- compute pipeline 为延迟创建模型
- `IsValid()` 不严格等价于 native pipeline 已创建
- `Bind()` / `Unbind()` 无实际行为
- `GetHash()` 很简化,不能视为完整 pipeline cache key
- 哪些字段已经是可靠后端语义
- 哪些字段目前只是为了接口稳定性保留
- 何时需要在特定后端做额外验证
## 主要公开方法
- `bool Initialize(VulkanDevice* device, const GraphicsPipelineDesc& desc)`
- `void SetInputLayout(const InputLayoutDesc& layout)`
- `void SetRasterizerState(const RasterizerDesc& state)`
- `void SetBlendState(const BlendDesc& state)`
- `void SetDepthStencilState(const DepthStencilStateDesc& state)`
- `void SetTopology(uint32_t topologyType)`
- `void SetRenderTargetFormats(uint32_t count, const uint32_t* formats, uint32_t depthFormat)`
- `void SetSampleCount(uint32_t count)`
- `void SetComputeShader(RHIShader* shader)`
- `PipelineStateHash GetHash() const`
- `RHIShader* GetComputeShader() const`
- `bool HasComputeShader() const`
- `bool IsValid() const`
- `void EnsureValid()`
- `void Shutdown()`
- `void Bind()`
- `void Unbind()`
- `void* GetNativeHandle()`
- `PipelineType GetType() const`
## 相关测试与使用线索
- `tests/RHI/unit/test_vulkan_graphics.cpp` 覆盖了 Vulkan graphics pipeline 与 compute dispatch 的真实创建路径
- `tests/RHI/unit/test_compute.cpp` 会把 compute shader、pipeline layout 和 descriptor set 接起来验证计算路径
- `tests/RHI/unit/test_command_list.cpp` 会通过抽象命令列表接口驱动 pipeline 绑定与 dispatch
- [Initialize](Initialize.md)
- [SetInputLayout](SetInputLayout.md)
- [SetRasterizerState](SetRasterizerState.md)
- [SetBlendState](SetBlendState.md)
- [SetDepthStencilState](SetDepthStencilState.md)
- [SetTopology](SetTopology.md)
- [SetRenderTargetFormats](SetRenderTargetFormats.md)
- [SetSampleCount](SetSampleCount.md)
- [SetSampleQuality](SetSampleQuality.md)
- [SetComputeShader](SetComputeShader.md)
- [GetHash](GetHash.md)
- [HasComputeShader](HasComputeShader.md)
- [IsValid](IsValid.md)
- [EnsureValid](EnsureValid.md)
- [Shutdown](Shutdown.md)
- [GetNativeHandle](GetNativeHandle.md)
## 相关文档
@@ -164,5 +73,5 @@
- [VulkanShader](../VulkanShader/VulkanShader.md)
- [VulkanPipelineLayout](../VulkanPipelineLayout/VulkanPipelineLayout.md)
- [VulkanCommandList](../VulkanCommandList/VulkanCommandList.md)
- [VulkanRenderPass](../VulkanRenderPass/VulkanRenderPass.md)
- [RHIPipelineState](../../RHIPipelineState/RHIPipelineState.md)
- [API 总索引](../../../../main.md)

View File

@@ -24,7 +24,8 @@
- shader pass / builtin pass 语义匹配
- material render state 转换
- builtin base-color / alpha-cutoff / skybox 材质数据解析
- schema-driven 逐材质常量 payload 暴露
- schema-driven 与 depth-style 逐材质常量 payload 暴露
- structured/raw material buffer binding 视图解析
- `VisibleRenderItem` 最终材质与 render queue 解析
## 相关文档

View File

@@ -6,27 +6,30 @@
**头文件**: `XCEngine/Rendering/Materials/RenderMaterialResolve.h`
**描述**: 渲染材质解析辅助头,负责把 `Material``MeshRendererComponent``VisibleRenderItem` 翻译成 builtin pass 可直接消费的材质、常量、render queue 与 skybox 语义。
**描述**: 渲染材质解析辅助头,负责把 `Material``MeshRendererComponent``VisibleRenderItem` 翻译成 builtin pass 可直接消费的常量、buffer 视图、render queue 与 skybox 语义。
## 头文件中的主要类型
- `BuiltinForwardMaterialData`
- `BuiltinSkyboxTextureMode`
- `BuiltinSkyboxMaterialData`
- `BuiltinDepthStyleMaterialConstants`
- `MaterialConstantLayoutView`
- `MaterialConstantPayloadView`
- `MaterialBufferResourceView`
## 当前公开能力
这份头文件当前主要承接类解析:
这份头文件当前主要承接类解析:
1. builtin pass 匹配
2. base-color / alpha-cutoff / skybox 材质数据解析
3. schema-driven 逐材质常量 payload 暴露
4. `VisibleRenderItem` 最终材质与 render queue 解析
1. semantic-driven 的 shader property / skybox / alpha-cutoff 解析
2. schema-driven 与 depth-style 两类逐材质常量 payload 组装
3. material buffer binding 到 `MaterialBufferResourceView` 的解析
4. `MeshRendererComponent` / `Mesh` / `VisibleRenderItem` 最终材质与 render queue 解析
5. builtin pass 匹配
## 关键 helper
- `IsMaterialBufferResourceType(...)`
- `FindShaderPropertyBySemantic(...)`
- `ResolveBuiltinBaseColorFactor(...)`
- `ResolveBuiltinBaseColorTexture(...)`
@@ -42,14 +45,14 @@
- `ResolveSkyboxRotationDegrees(...)`
- `BuildBuiltinSkyboxMaterialData(...)`
- `ResolveSchemaMaterialConstantPayload(...)`
- `BuildBuiltinDepthStyleMaterialConstants(...)`
- `ResolveBuiltinDepthStyleMaterialConstantPayload(...)`
- `TryResolveMaterialBufferResourceView(...)`
- `ResolveMaterial(...)`
- `TryResolveRenderQueueTagValue(...)`
- `TryResolveShaderPassRenderQueue(...)`
- `ResolveMaterialRenderQueue(...)`
- `IsTransparentRenderQueue(...)`
- `HasLegacyMaterialBuiltinPassHints(...)`
- `LegacyMaterialBuiltinPassHintsMatch(...)`
- `CanUseLegacyMaterialPassFallback(...)`
- `MatchesBuiltinPass(...)`
## 当前实现边界
@@ -57,13 +60,18 @@
- 它只负责材质语义解析,不直接创建 descriptor set 或 pipeline state。
- `ResolveSchemaMaterialConstantPayload(...)` 暴露的是借用视图,不负责重新打包或持有内存;只有常量布局和常量 buffer 都非空时才会返回有效 payload。
- `MaterialConstantPayloadView::IsValid()` 还要求 `layout.size == size`,因此文档使用时应把它视为“布局与字节载荷配对成功”的结果,而不只是 data 指针非空。
- `ResolveBuiltinDepthStyleMaterialConstantPayload(...)` 返回的 view 同样是借用视图;底层数据来自调用方提供的 `outConstants``outLayout`
- `MaterialBufferResourceView` 当前只覆盖 structured/raw buffer 的 SRV / UAV 视图,不负责 texture 或 sampler 资源。
- `TryResolveMaterialBufferResourceView(...)` 只有在 `binding.resourceType` 属于 material buffer 类型、材质中存在同名 buffer binding 且 stride / dimension 合法时才会成功。
- `ResolveMaterial(...)` 当前按“精确材质槽位 -> mesh 默认材质槽位 -> slot 0 回退”的顺序解析最终材质。
- `ResolveMaterialRenderQueue(...)` 当前优先尊重材质自身 `renderQueue` 覆盖;只有它仍等于默认 `Geometry` 队列时,才会继续读取显式 `shaderPass` 或 shader pass `Queue` tag。
- `ResolveSkyboxTextureMode(...)` 先尝试 panoramic再尝试 cubemap如果两者都能解析到当前会优先返回 `Panoramic`
- `MatchesBuiltinPass(...)` 会先看 shader 是否已经声明显式 builtin metadata;只有缺少这层 metadata 时,才会回退到 `Material::GetShaderPass()` / `LightMode` 这套兼容提示,并最终把完全无提示的材质视作 `ForwardLit`
- `MatchesBuiltinPass(...)` 当前只遍历 shader `ShaderPass` 列表,并把真正的匹配规则委托给 [BuiltinPassMetadataUtils](../../Builtin/BuiltinPassMetadataUtils/BuiltinPassMetadataUtils.md) 的 `ShaderPassMatchesBuiltinPass(...)`
## 相关文档
- [Materials](../Materials.md)
- [RenderMaterialStateUtils](../RenderMaterialStateUtils/RenderMaterialStateUtils.md)
- [Builtin](../../Builtin/Builtin.md)
- [BuiltinPassMetadataUtils](../../Builtin/BuiltinPassMetadataUtils/BuiltinPassMetadataUtils.md)
- [RenderMaterialUtility](../../RenderMaterialUtility/RenderMaterialUtility.md)

View File

@@ -6,35 +6,47 @@
**头文件**: `XCEngine/Rendering/Passes/BuiltinColorScalePostProcessPass.h`
**描述**: 基于 `RenderPassContext::sourceColorView`全屏颜色缩放后处理 pass当前由相机 post-process 栈通过 factory 按需创建。
**描述**: 基于 `RenderPassContext::sourceColorView` fullscreen 颜色缩放后处理 pass当前由相机 post-process 栈按需创建。
## 概
## 概
`BuiltinColorScalePostProcessPass` 是当前 post-process 路径里最简单的一类 fullscreen pass
`BuiltinColorScalePostProcessPass` 是当前 post-process 链路里最直接的一类 fullscreen pass
-`sourceColorView` 采样输入颜色
-`m_colorScale` 写入常量
- 输出到 `context.surface`
-`sourceColorView` 采样输入颜色
-`m_colorScale` 写入常量缓冲。
- 输出到 `context.surface` 的唯一颜色附件。
它通常不由调用方手工长期持有,而是通过 [BuildCameraPostProcessPassSequence](../../Planning/CameraPostProcessPassFactory/CameraPostProcessPassFactory.md) 根据 `CameraPostProcessStack` 临时组装。
## 当前公开能力
## 公开类型
- 构造时指定 `colorScale` 和可选 `shaderPath`
- `GetName()` / `Execute(...)` / `Shutdown()`
- `SetColorScale(...)` / `GetColorScale()`
- `SetShaderPath(...)` / `GetShaderPath()`
- [OwnedDescriptorSet](OwnedDescriptorSet.md)
## 公开方法
| 方法 | 说明 |
|------|------|
| [Constructor](Constructor.md) | 构造 post-process color-scale pass并可选指定自定义 shader 路径。 |
| [Destructor](Destructor.md) | 析构时释放内部 GPU 资源。 |
| [GetName](GetName.md) | 返回固定 pass 名称。 |
| [Execute](Execute.md) | 执行 fullscreen 颜色缩放绘制。 |
| [Shutdown](Shutdown.md) | 主动释放当前持有的 GPU 资源。 |
| [SetColorScale](SetColorScale.md) | 更新颜色缩放参数。 |
| [GetColorScale](GetColorScale.md) | 读取当前颜色缩放参数。 |
| [SetShaderPath](SetShaderPath.md) | 切换 post-process shader 路径,并使资源缓存失效。 |
| [GetShaderPath](GetShaderPath.md) | 读取当前 shader 路径。 |
## 当前实现边界
- 只处理单个全屏颜色缩放效果,不负责 tone mapping、output transfer 或 final compositing。
- 资源初始化按 `RenderContext` 后端和 render target format 惰性建立
- 输入颜色必须来自 `RenderPassContext::sourceColorView`;它不会自己分配中间表面
- 它只负责单一的颜色缩放效果,不承担 tone mapping、output transfer 或 final compositing。
- 当前实现要求 source surface 与 destination surface 都是“单颜色附件”形态;不支持 MRT也不使用深度附件
- 构造函数会在传入空路径时自动回退到 builtin color-scale shader但后续若显式调用 `SetShaderPath("")`,资源创建会因为空路径而失败,直到重新设置为非空路径
- GPU 资源缓存会随着 backend、目标格式、sample count、sample quality 和 shader path 变化而失效;[SetColorScale](SetColorScale.md) 本身只更新常量,不会重建 pipeline。
## 真实接入位置
- `SceneRenderer` 先根据相机 `postProcess` 描述构建 `CameraRenderRequest::postProcess`
- `BuildCameraPostProcessPassSequence(...)` 读取 [CameraPostProcessPassDesc](../../Planning/CameraPostProcessDesc/CameraPostProcessDesc.md) 并生成当前 pass。
- `SceneRenderer` 先根据相机 `postProcess` 描述构建 `CameraRenderRequest::postProcess`
- `BuildCameraPostProcessPassSequence(...)` 读取 [CameraPostProcessDesc](../../Planning/CameraPostProcessDesc/CameraPostProcessDesc.md) 并生成当前 pass。
- `CameraRenderer` 在主场景之后、final-output 之前执行这一阶段。
## 相关文档
@@ -43,3 +55,4 @@
- [CameraPostProcessDesc](../../Planning/CameraPostProcessDesc/CameraPostProcessDesc.md)
- [CameraPostProcessPassFactory](../../Planning/CameraPostProcessPassFactory/CameraPostProcessPassFactory.md)
- [BuiltinFinalColorPass](../BuiltinFinalColorPass/BuiltinFinalColorPass.md)
- [RenderPass](../../RenderPass/RenderPass.md)

View File

@@ -0,0 +1,26 @@
# BuiltinColorScalePostProcessPass::BuiltinColorScalePostProcessPass
构造 post-process color-scale pass并初始化颜色缩放参数与 shader 路径。
```cpp
explicit BuiltinColorScalePostProcessPass(
const Math::Vector4& colorScale = Math::Vector4(0.65f, 0.80f, 1.0f, 1.0f),
Containers::String shaderPath = {});
```
## 参数
- `colorScale` - 初始颜色缩放参数。
- `shaderPath` - 可选自定义 shader 路径;传空时会自动回退到 builtin color-scale shader。
## 当前语义
- 构造函数只保存 CPU 侧配置,不会立即创建 GPU 资源。
- 如果 `shaderPath.Empty()`,构造函数会改写成 `Resources::GetBuiltinColorScalePostProcessShaderPath()`
- 真正的 pipeline、sampler、descriptor set 与 shader 资源会在第一次 [Execute](Execute.md) 时按目标 surface 延迟创建。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [Execute](Execute.md)
- [SetShaderPath](SetShaderPath.md)
- [SetColorScale](SetColorScale.md)

View File

@@ -0,0 +1,21 @@
# BuiltinColorScalePostProcessPass::~BuiltinColorScalePostProcessPass
析构函数会兜底释放当前持有的 GPU 资源。
```cpp
~BuiltinColorScalePostProcessPass() override;
```
## 当前语义
- 实现直接调用 [Shutdown](Shutdown.md)。
- 因此即便调用方忘记手动 `Shutdown()`,析构时也会回收 pipeline、pipeline layout、sampler、descriptor pool / set 与 shader 句柄。
## 当前实现边界
- 析构只负责当前 pass 自己持有的 GPU 资源,不管理 source / destination surface 或外部传入的 `RenderPassContext` 资源。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [Shutdown](Shutdown.md)
- [OwnedDescriptorSet](OwnedDescriptorSet.md)

View File

@@ -0,0 +1,39 @@
# BuiltinColorScalePostProcessPass::Execute
执行一次 fullscreen 颜色缩放绘制。
```cpp
bool Execute(const RenderPassContext& context) override;
```
## 参数
- `context` - 当前渲染上下文、source surface / source color view以及目标 `RenderSurface` 的组合描述。
## 返回值
- 成功完成颜色缩放绘制返回 `true`;前置条件不满足或 GPU 资源初始化失败时返回 `false`
## 当前语义
- 要求 `context.renderContext` 有效,且 `context.sourceSurface``context.sourceColorView` 非空。
- 要求 source surface 与 destination surface 都是“恰好一个有效颜色附件”的形态。
- 会校验 destination `RenderSurface::GetRenderArea()` 的宽高必须大于 `0`
- 资源未就绪时会调用内部初始化逻辑;若 backend、目标格式、sample count、sample quality 或 shader path 发生变化,会先销毁旧资源再重建。
- 每次执行都会把 [GetColorScale](GetColorScale.md) 的结果写入常量缓冲source 纹理 descriptor 只有在 `sourceColorView` 变化时才会重新绑定。
- destination surface 开启自动状态切换时,会把目标颜色附件从 `GetColorStateBefore()` 切到 `RenderTarget`,绘制完成后再切回 `GetColorStateAfter()`
- source surface 开启自动状态切换时,会把 `sourceColorView``context.sourceColorState` 切到 `PixelShaderResource`,结束后再切回原状态;若 source 关闭自动切换,则 `context.sourceColorState` 必须已经是 `PixelShaderResource`
- 绘制路径会设置 render target、viewport、scissor、triangle-list 拓扑、pipeline state 和 3 组 descriptor set然后发出一次 `Draw(3, 1, 0, 0)`
## 当前实现边界
- 当前实现不使用深度附件,也不处理多颜色附件输出。
- 是否执行这个 post-process pass本身由上层 planning / factory 决定;这里不负责策略选择。
- 当前仓库里没有直接以 `BuiltinColorScalePostProcessPass` 命名的独立单元测试,更多约束来自 `SceneRenderer` 的 post-process 链路。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [Shutdown](Shutdown.md)
- [SetColorScale](SetColorScale.md)
- [RenderPass](../../RenderPass/RenderPass.md)
- [CameraPostProcessDesc](../../Planning/CameraPostProcessDesc/CameraPostProcessDesc.md)

View File

@@ -0,0 +1,21 @@
# BuiltinColorScalePostProcessPass::GetColorScale
返回当前保存的颜色缩放参数。
```cpp
const Math::Vector4& GetColorScale() const;
```
## 返回值
- 返回内部保存的 `Math::Vector4` 引用。
## 当前语义
- 这里返回的是 CPU 侧配置快照,而不是 shader 常量缓冲的即时读回结果。
- 若调用方先执行了 [SetColorScale](SetColorScale.md) 但尚未调用 [Execute](Execute.md),这里已经能读到最新配置。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [SetColorScale](SetColorScale.md)
- [Execute](Execute.md)

View File

@@ -0,0 +1,20 @@
# BuiltinColorScalePostProcessPass::GetName
返回当前 pass 的固定名称。
```cpp
const char* GetName() const override;
```
## 返回值
- 固定返回字符串 `BuiltinColorScalePostProcessPass`
## 当前语义
- 返回值不依赖 `colorScale`、backend 或 shader 路径。
- 它主要用于调试、日志和统一的 pass 标识。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [Execute](Execute.md)

View File

@@ -0,0 +1,21 @@
# BuiltinColorScalePostProcessPass::GetShaderPath
返回当前保存的 post-process shader 路径。
```cpp
const Containers::String& GetShaderPath() const;
```
## 返回值
- 返回内部保存的 `shaderPath` 引用。
## 当前语义
- 构造函数若收到空路径,会先把它替换成 builtin color-scale shader 路径,因此默认情况下这里返回的是非空 builtin 路径。
- 如果调用方后续显式执行 [SetShaderPath](SetShaderPath.md) 并传入空字符串,这里也会如实返回空路径。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [SetShaderPath](SetShaderPath.md)
- [Constructor](Constructor.md)

View File

@@ -0,0 +1,26 @@
# BuiltinColorScalePostProcessPass::OwnedDescriptorSet
描述 `BuiltinColorScalePostProcessPass` 内部把“descriptor pool + descriptor set”作为一组资源一起持有的轻量结构。
```cpp
struct OwnedDescriptorSet {
RHI::RHIDescriptorPool* pool = nullptr;
RHI::RHIDescriptorSet* set = nullptr;
};
```
## 字段
- `pool` - 用来分配该 descriptor set 的 pool。
- `set` - 实际绑定到图形管线的 descriptor set。
## 当前语义
- `BuiltinColorScalePostProcessPass` 用它分别管理常量、纹理和采样器三组 descriptor 资源。
- 结构本身不提供独立生命周期函数;真正的释放逻辑由 pass 内部的 `DestroyOwnedDescriptorSet(...)` 和 [Shutdown](Shutdown.md) 统一处理。
- 这里暴露的是原始指针打包结果,不附带所有权转移语义。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [Shutdown](Shutdown.md)
- [Execute](Execute.md)

View File

@@ -0,0 +1,22 @@
# BuiltinColorScalePostProcessPass::SetColorScale
更新当前颜色缩放参数。
```cpp
void SetColorScale(const Math::Vector4& colorScale);
```
## 参数
- `colorScale` - 新的 RGBA 颜色缩放参数。
## 当前语义
- 这个 setter 只更新 `m_colorScale`,不会立即创建或重建 GPU 资源。
- 新配置会在下一次 [Execute](Execute.md) 时被写入常量缓冲并立即生效。
- 因为 pipeline 选择不依赖颜色缩放常量,这里不会触发资源失效。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [GetColorScale](GetColorScale.md)
- [Execute](Execute.md)

View File

@@ -0,0 +1,28 @@
# BuiltinColorScalePostProcessPass::SetShaderPath
切换当前 post-process shader 路径,并使已有资源缓存失效。
```cpp
void SetShaderPath(const Containers::String& shaderPath);
```
## 参数
- `shaderPath` - 新的 shader 资源路径。
## 当前语义
- 如果传入路径与当前 `m_shaderPath` 相同,函数直接返回,不做任何事。
- 路径发生变化时,会先销毁现有 GPU 资源,然后再保存新的路径字符串。
- 后续下一次 [Execute](Execute.md) 会按新的路径重新加载 shader 并创建 pipeline。
## 当前实现边界
- 与构造函数不同,这个 setter 不会在空字符串时自动回退到 builtin shader如果显式传入空路径后续资源创建会失败。
- 它只让资源缓存失效,不会立即触发重建。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [GetShaderPath](GetShaderPath.md)
- [Constructor](Constructor.md)
- [Execute](Execute.md)

View File

@@ -0,0 +1,24 @@
# BuiltinColorScalePostProcessPass::Shutdown
主动释放当前持有的 GPU 资源。
```cpp
void Shutdown() override;
```
## 当前语义
- 实现直接调用内部 `DestroyResources()`
- 会回收 pipeline state、pipeline layout、sampler以及常量/纹理/采样器三组 descriptor pool / set。
- 还会清空当前 shader 句柄、device/backend/format/sample 描述缓存与已绑定 source color view。
## 当前实现边界
- `Shutdown()` 不会重置 [GetColorScale](GetColorScale.md) 或 `shaderPath`;后续再次 [Execute](Execute.md) 时仍会基于现有配置重建资源。
- 析构函数也会调用它,因此可以把它视为幂等的显式清理入口。
## 相关文档
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
- [Destructor](Destructor.md)
- [Execute](Execute.md)
- [OwnedDescriptorSet](OwnedDescriptorSet.md)

View File

@@ -10,6 +10,14 @@
## 概览
## 2026-04-10 同步补充
以下内容以当前 `BuiltinDepthStylePassBase.h/.cpp` 为准:
- `PipelineStateKey` 现在显式包含 `sampleCount``sampleQuality`,因此 depth-style pipeline cache 不再只按 render state、shader、format 和 keyword 签名区分。
- 自动状态切换现在同时覆盖 color 与 depth进入 pass 前会读取 `GetColorStateBefore()` / `GetDepthStateBefore()`,结束后恢复到 `GetColorStateAfter()` / `GetDepthStateAfter()`
- depth-style surface 兼容性现在按 `IsDepthStyleCompatibleSurface(...)` 判断,要求的是“零或一个颜色附件 + 已知 depth format + 合法 sample 描述”,而不是早期那种更死板的固定表述。
`BuiltinDepthStylePassBase` 把两类 pass 的公共逻辑集中在一起:
-`BuiltinMaterialPass` 解析可兼容的 shader pass。

View File

@@ -6,6 +6,14 @@
bool Execute(const RenderPassContext& context) override;
```
## 2026-04-10 更新
-`surface.IsAutoTransitionEnabled()``true` 时,当前实现会同时切换 color 与 depth
- 颜色附件从 `GetColorStateBefore()` 切到 `RenderTarget`
- depth 附件从 `GetDepthStateBefore()` 切到 `DepthWrite`
- 结束后分别恢复到各自的 `after` 状态
- `Execute(...)` 依赖 `IsDepthStyleCompatibleSurface(...)` 判断 surface 是否兼容;当前要求的是零或一个颜色附件、已知 depth format以及合法的 sample 描述。
## 当前流程
1. 校验 `renderContext`、第一个颜色附件、深度附件和 `surface.GetRenderArea()`

View File

@@ -17,7 +17,7 @@
- tone mapping
- final color scale
对应参数通过 `FinalColorSettings``ResolvedFinalColorPolicy` 注入。
对应参数通过 `FinalColorSettings``ResolvedFinalColorPolicy` 注入。
## 当前公开能力
@@ -28,9 +28,11 @@
## 当前实现边界
- 它只负责 final-color 阶段,不解析相机 override 或 pipeline 默认值。
- `SceneRenderer` 先在 planning 阶段解析 [ResolvedFinalColorPolicy](../../Planning/FinalColorSettings/FinalColorSettings.md),再通过 factory 创建当前 pass
- 和其他 fullscreen pass 一样,它依赖 `RenderPassContext::sourceColorView`,不会自己管理中间表面生命周期
- 它只负责 final-color 阶段,不解析相机 override 或 pipeline 默认值;这些在 planning 阶段就已经被解出来
- 与其他 fullscreen pass 一样,它依赖 `RenderPassContext::sourceSurface``sourceColorView``sourceColorState`,不会自己管理中间表面生命周期
- 目标 surface 与 source surface 当前都要求“恰好一张有效颜色附件”
- 当 source surface 启用了自动状态切换时pass 会根据 `sourceColorState` 自动把输入切到 `PixelShaderResource` 再恢复;否则要求调用方保证输入已经可读。
- GPU 资源缓存会跟随目标 surface 的 render target format、`sampleCount``sampleQuality` 一起变化。
## 真实接入位置
@@ -41,6 +43,7 @@
## 相关文档
- [Passes](../Passes.md)
- [RenderPass](../../RenderPass/RenderPass.md)
- [FinalColorSettings](../../Planning/FinalColorSettings/FinalColorSettings.md)
- [FinalColorPassFactory](../../Planning/FinalColorPassFactory/FinalColorPassFactory.md)
- [BuiltinColorScalePostProcessPass](../BuiltinColorScalePostProcessPass/BuiltinColorScalePostProcessPass.md)

View File

@@ -0,0 +1,26 @@
# BuiltinFinalColorPass::BuiltinFinalColorPass
构造 final-color pass并初始化设置与 shader 路径。
```cpp
explicit BuiltinFinalColorPass(
const FinalColorSettings& settings = {},
Containers::String shaderPath = {});
```
## 参数
- `settings` - 初始的 final-color 配置。
- `shaderPath` - 可选自定义 shader 路径;传空时会自动回退到 builtin final-color shader。
## 当前语义
- 构造函数只保存 CPU 侧配置,不会立即创建 GPU 资源。
- 如果 `shaderPath.Empty()`,构造函数会改写成 `Resources::GetBuiltinFinalColorShaderPath()`
- 真正的 pipeline、sampler、descriptor set 与 shader 资源会在第一次 [Execute](Execute.md) 时按目标 surface 延迟创建。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [Execute](Execute.md)
- [SetShaderPath](SetShaderPath.md)
- [SetSettings](SetSettings.md)

View File

@@ -0,0 +1,21 @@
# BuiltinFinalColorPass::~BuiltinFinalColorPass
析构函数会兜底释放当前持有的 GPU 资源。
```cpp
~BuiltinFinalColorPass() override;
```
## 当前语义
- 实现直接调用 [Shutdown](Shutdown.md)。
- 因此即便调用方忘记手动 `Shutdown()`,析构时也会回收 pipeline、pipeline layout、sampler、descriptor pool / set 与 shader 句柄。
## 当前实现边界
- 析构只负责当前 pass 自己持有的 GPU 资源,不管理 source / destination surface 或外部传入的 `RenderPassContext` 资源。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [Shutdown](Shutdown.md)
- [OwnedDescriptorSet](OwnedDescriptorSet.md)

View File

@@ -0,0 +1,39 @@
# BuiltinFinalColorPass::Execute
执行一次 fullscreen final-color 绘制。
```cpp
bool Execute(const RenderPassContext& context) override;
```
## 参数
- `context` - 当前渲染上下文、source surface / source color view以及目标 `RenderSurface` 的组合描述。
## 返回值
- 成功完成 final-color 绘制返回 `true`;前置条件不满足或 GPU 资源初始化失败时返回 `false`
## 当前语义
- 要求 `context.renderContext` 有效,且 `context.sourceSurface``context.sourceColorView` 非空。
- 要求 source surface 与 destination surface 都是“恰好一个有效颜色附件”的形态。
- 会校验 destination `RenderSurface::GetRenderArea()` 的宽高必须大于 `0`
- 资源未就绪时会调用内部初始化逻辑;若 backend、目标格式、sample count、sample quality 或 shader path 发生变化,会先销毁旧资源再重建。
- 每次执行都会把 [FinalColorSettings](GetSettings.md) 写入常量缓冲source 纹理 descriptor 只有在 `sourceColorView` 变化时才会重新绑定。
- destination surface 开启自动状态切换时,会把目标颜色附件从 `GetColorStateBefore()` 切到 `RenderTarget`,绘制完成后再切回 `GetColorStateAfter()`
- source surface 开启自动状态切换时,会把 `sourceColorView``context.sourceColorState` 切到 `PixelShaderResource`,结束后再切回原状态;若 source 关闭自动切换,则 `context.sourceColorState` 必须已经是 `PixelShaderResource`
- 绘制路径会设置 render target、viewport、scissor、triangle-list 拓扑、pipeline state 和 3 组 descriptor set然后发出一次 `Draw(3, 1, 0, 0)`
## 当前实现边界
- 当前实现不使用深度附件,也不处理多颜色附件输出。
- 是否执行 final-color pass本身由上层 planning / factory 决定;这里不负责策略选择。
- 当前仓库里没有直接以 `BuiltinFinalColorPass` 命名的独立单元测试,更多约束来自 `SceneRenderer` 的 final-output 链路。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [Shutdown](Shutdown.md)
- [SetSettings](SetSettings.md)
- [RenderPass](../../RenderPass/RenderPass.md)
- [FinalColorSettings](../../Planning/FinalColorSettings/FinalColorSettings.md)

View File

@@ -0,0 +1,20 @@
# BuiltinFinalColorPass::GetName
返回当前 pass 的固定名称。
```cpp
const char* GetName() const override;
```
## 返回值
- 固定返回字符串 `BuiltinFinalColorPass`
## 当前语义
- 返回值不依赖 `FinalColorSettings`、backend 或 shader 路径。
- 它主要用于调试、日志和统一的 pass 标识。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [Execute](Execute.md)

View File

@@ -0,0 +1,21 @@
# BuiltinFinalColorPass::GetSettings
返回当前保存的 final-color 配置。
```cpp
const FinalColorSettings& GetSettings() const;
```
## 返回值
- 返回内部保存的 `FinalColorSettings` 引用。
## 当前语义
- 这里返回的是 CPU 侧配置快照,而不是 shader 常量缓冲的即时读回结果。
- 若调用方先执行了 [SetSettings](SetSettings.md) 但尚未调用 [Execute](Execute.md),这里已经能读到最新配置。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [SetSettings](SetSettings.md)
- [FinalColorSettings](../../Planning/FinalColorSettings/FinalColorSettings.md)

View File

@@ -0,0 +1,21 @@
# BuiltinFinalColorPass::GetShaderPath
返回当前保存的 final-color shader 路径。
```cpp
const Containers::String& GetShaderPath() const;
```
## 返回值
- 返回内部保存的 `shaderPath` 引用。
## 当前语义
- 构造函数若收到空路径,会先把它替换成 builtin final-color shader 路径,因此默认情况下这里返回的是非空 builtin 路径。
- 如果调用方后续显式执行 [SetShaderPath](SetShaderPath.md) 并传入空字符串,这里也会如实返回空路径。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [SetShaderPath](SetShaderPath.md)
- [Constructor](Constructor.md)

View File

@@ -0,0 +1,26 @@
# BuiltinFinalColorPass::OwnedDescriptorSet
描述 `BuiltinFinalColorPass` 内部把“descriptor pool + descriptor set”作为一组资源一起持有的轻量结构。
```cpp
struct OwnedDescriptorSet {
RHI::RHIDescriptorPool* pool = nullptr;
RHI::RHIDescriptorSet* set = nullptr;
};
```
## 字段
- `pool` - 用来分配该 descriptor set 的 pool。
- `set` - 实际绑定到图形管线的 descriptor set。
## 当前语义
- `BuiltinFinalColorPass` 用它分别管理常量、纹理和采样器三组 descriptor 资源。
- 结构本身不提供独立生命周期函数;真正的释放逻辑由 pass 内部的 `DestroyOwnedDescriptorSet(...)` 和 [Shutdown](Shutdown.md) 统一处理。
- 这里暴露的是原始指针打包结果,不附带所有权转移语义。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [Shutdown](Shutdown.md)
- [Execute](Execute.md)

View File

@@ -0,0 +1,23 @@
# BuiltinFinalColorPass::SetSettings
更新当前 final-color 配置。
```cpp
void SetSettings(const FinalColorSettings& settings);
```
## 参数
- `settings` - 新的曝光、tone mapping、output transfer 与最终颜色缩放配置。
## 当前语义
- 这个 setter 只更新 `m_settings`,不会立即创建或重建 GPU 资源。
- 新配置会在下一次 [Execute](Execute.md) 时被写入常量缓冲并立即生效。
- 因为 pipeline 选择不依赖 `FinalColorSettings`,这里只改配置不会触发资源失效。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [GetSettings](GetSettings.md)
- [Execute](Execute.md)
- [FinalColorSettings](../../Planning/FinalColorSettings/FinalColorSettings.md)

View File

@@ -0,0 +1,28 @@
# BuiltinFinalColorPass::SetShaderPath
切换当前 final-color shader 路径,并使已有资源缓存失效。
```cpp
void SetShaderPath(const Containers::String& shaderPath);
```
## 参数
- `shaderPath` - 新的 shader 资源路径。
## 当前语义
- 如果传入路径与当前 `m_shaderPath` 相同,函数直接返回,不做任何事。
- 路径发生变化时,会先销毁现有 GPU 资源,然后再保存新的路径字符串。
- 后续下一次 [Execute](Execute.md) 会按新的路径重新加载 shader 并创建 pipeline。
## 当前实现边界
- 与构造函数不同,这个 setter 不会在空字符串时自动回退到 builtin shader如果显式传入空路径后续资源创建会失败。
- 它只让资源缓存失效,不会立即触发重建。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [GetShaderPath](GetShaderPath.md)
- [Constructor](Constructor.md)
- [Execute](Execute.md)

View File

@@ -0,0 +1,24 @@
# BuiltinFinalColorPass::Shutdown
主动释放当前持有的 GPU 资源。
```cpp
void Shutdown() override;
```
## 当前语义
- 实现直接调用内部 `DestroyResources()`
- 会回收 pipeline state、pipeline layout、sampler以及常量/纹理/采样器三组 descriptor pool / set。
- 还会清空当前 shader 句柄、device/backend/format/sample 描述缓存与已绑定 source color view。
## 当前实现边界
- `Shutdown()` 不会重置 [FinalColorSettings](GetSettings.md) 或 `shaderPath`;后续再次 [Execute](Execute.md) 时仍会基于现有配置重建资源。
- 析构函数也会调用它,因此可以把它视为幂等的显式清理入口。
## 相关文档
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
- [Destructor](Destructor.md)
- [Execute](Execute.md)
- [OwnedDescriptorSet](OwnedDescriptorSet.md)

View File

@@ -6,53 +6,50 @@
**头文件**: `XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h`
**描述**: 在场景颜色目标上叠加无限地面网格的底层全屏 pass真正使用哪份 shader 由调用方通过 `shaderPath` 注入。
**描述**: 在场景颜色目标上叠加无限地面网格的全屏 pass真正使用哪份 shader 由调用方通过 `shaderPath` 注入。
## 概
## 概
`BuiltinInfiniteGridPass` 是当前 Scene View 里“地面参考网格”效果的实际执行者。
它消费一份 [InfiniteGridPassData](InfiniteGridPassData.md),在运行时先推导
[InfiniteGridParameters](InfiniteGridParameters.md),再用当前配置的 shader 资源把网格直接混合到目标颜色附件上。
`BuiltinInfiniteGridPass` 是当前 Scene View 里“地面参考网格”效果的实际执行者。它消费一份 [InfiniteGridPassData](InfiniteGridPassData.md),先推导 [InfiniteGridParameters](InfiniteGridParameters.md),再用当前配置的 shader 资源把网格直接混合到目标颜色附件上。
这个类当前不再自己决定“Scene View grid shader 到底是哪一份资源”。源码里的职责边界已经变成:
- `BuiltinInfiniteGridPass`
只负责资源创建、常量写入和一次全屏三角形执行
- editor 调用方
通过 [SetShaderPath](SetShaderPath.md) 或构造函数参数注入具体 shader 路径
只负责资源创建、常量写入和一次全屏三角形执行
- editor / 调用方
通过 [SetShaderPath](SetShaderPath.md) 或构造函数参数注入具体 shader 路径
- `SceneViewportGridPass`
负责把 [SceneViewportResourcePaths](../../../Editor/Viewport/SceneViewportResourcePaths/SceneViewportResourcePaths.md)
生成的 editor 资源路径传进来;`SceneViewportShaderPaths` 现在只是兼容 include 层。
负责把 editor 资源路径和 Scene View 运行时参数组织好,再调用这里执行
## 关键输入
- [InfiniteGridPassData](InfiniteGridPassData.md) 决定相机位置、朝向、FOV、裁剪面和轨道距离
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md) 会把这些数据折算成 `baseScale``transitionBlend``fadeDistance`
- `RenderSurface` 需要同时提供颜色附件深度附件;当前 pass 会开启深度测试但关闭深度写入。
- [InfiniteGridPassData](InfiniteGridPassData.md) 决定相机位置、朝向、FOV、裁剪面和轨道距离
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md) 会把这些数据折算成 `baseScale``transitionBlend``fadeDistance`
- `RenderSurface` 需要同时提供单个有效颜色附件深度附件和有效 render area
## 当前实现流程
1. 先校验 `data.valid``RenderContext::IsValid()``backendType == D3D12`
2. 通过 `EnsureInitialized()` 惰性创建 pipeline layout、pipeline state 和常量描述符;这一步要求 `shaderPath` 非空且可成功加载。
3. 从相机姿态推导网格参数,并构建 view-projection 常量。
1. 先校验 `data.valid``RenderContext::IsValid()`、目标颜色附件、深度附件和 `surface.GetRenderArea()`
2. 通过 `EnsureInitialized(renderContext, surface)` 懒创建或重建 pipeline layout、pipeline state 和常量描述符;这一步要求 `shaderPath` 非空且可成功加载。
3. 从相机姿态推导网格参数,并基于 `surface.GetRenderArea()` 的宽高构建 view-projection 常量。
4. 把第一个颜色附件和深度附件绑定为渲染目标。
5. 以全屏三角形方式发出一次 `Draw(3, 1, 0, 0)`
## 当前实现边界
- 当前只支持 `D3D12` shader variant
- 在第一次创建资源之前必须先提供有效 `shaderPath`;空路径会记录错误并返回 `false`
- 只写入 `surface` 的第一个颜色附件
- 视口和裁剪矩形使用 `surface.GetWidth()` / `GetHeight()`,不是自定义 render area
- 网格参数完全由当前相机高度与朝向启发式推导,还没有暴露成更完整的编辑器样式配置
- 当前实现不再把“`backendType == D3D12`”写成硬编码前置条件;只要配置 shader 在当前 backend 上存在兼容 graphics variant就可以创建并执行
- `EnsureInitialized(...)` / `CreateResources(...)` 会按 backend、目标颜色格式、深度格式和目标 `sampleCount` 重新建资源surface 配置变化时会销毁旧的 pipeline / descriptor 再创建
- viewport、scissor、宽高比与投影视口尺寸都会按 `surface.GetRenderArea()` 计算,不再固定覆盖整张 surface
- `surface.IsAutoTransitionEnabled()` `true`pass 会把颜色附件从 `surface.GetColorStateAfter()` 切到 `RenderTarget`,并把 depth 附件从 `surface.GetDepthStateAfter()` 切到 `DepthWrite`,结束后再恢复
- 当前仍只写第一个颜色附件;不是多 render target pass
- 网格参数仍主要由当前相机高度与朝向启发式推导,还没有暴露成更完整的编辑器样式配置对象。
## 公开方法与相关类型
| 成员 | 说明 |
|------|------|
| [Constructor](Constructor.md) | 创建 pass并可选地预置 shader 路径。 |
| [Destructor](Destructor.md) | 默认析构;不会自动代替 `Shutdown()` 释放内部 RHI 资源。 |
| [Destructor](Destructor.md) | 默认析构;不会替代 [Shutdown](Shutdown.md) 释放内部 RHI 资源。 |
| [SetShaderPath](SetShaderPath.md) | 更新当前要加载的 shader 路径,并清空已创建资源。 |
| [GetShaderPath](GetShaderPath.md) | 返回当前记录的 shader 路径。 |
| [InfiniteGridPassData](InfiniteGridPassData.md) | 输入相机数据。 |
@@ -63,16 +60,13 @@
## 真实使用位置
- `editor/src/Viewport/Passes/SceneViewportGridPass.cpp` `SceneViewportGridPassRenderer` 包装它,
并通过 `GetSceneViewportInfiniteGridShaderPath()` 注入 editor-owned shader 路径,再把它挂进 Scene View`postScenePasses`
- [SceneViewportRenderPlan](../../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 决定当前帧是否需要创建这条 pass。
- `tests/Editor/test_scene_viewport_overlay_renderer.cpp` 固定了网格参数推导规则。
- `editor/src/Viewport/Passes/SceneViewportGridPass.cpp` 当前会包装它,并通过 `GetSceneViewportInfiniteGridShaderPath()` 注入 editor-owned shader 路径,再把它挂进 Scene View 的 `postScenePasses`
- [SceneViewportRenderPlan](../../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 决定当前帧是否需要创建这条 pass
- `tests/Editor/test_scene_viewport_overlay_renderer.cpp` 覆盖了网格参数推导规则
## 相关文档
- [Passes](../Passes.md)
- [Constructor](Constructor.md)
- [Destructor](Destructor.md)
- [SetShaderPath](SetShaderPath.md)
- [GetShaderPath](GetShaderPath.md)
- [InfiniteGridPassData](InfiniteGridPassData.md)

View File

@@ -21,20 +21,20 @@ bool Render(
## 当前实现流程
1. 如果 `data.valid == false``renderContext` 无效或后端不是 `D3D12`,直接返回 `false`
2. 调用 `EnsureInitialized(renderContext)`如果当前 `shaderPath` 为空,或配置路径下的 shader 无法加载,也会在这里失败。
3. 检查颜色附件和深度附件,要求 grid pass 具备完整的颜色与深度目标
4. 调用 [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md) 生成网格参数,并构建 view-projection 常量
5. 把常量写入 descriptor set
6. 绑定第一个颜色附件和深度附件。
7. 以全屏三角形方式执行一次 `Draw(3, 1, 0, 0)`
1. 如果 `data.valid == false``renderContext` 无效、目标不具备单颜色附件 / 深度附件,或 `surface.GetRenderArea()` 非法,直接返回 `false`
2. 调用 `EnsureInitialized(renderContext, surface)`当前 `shaderPath` 为空,或配置路径下的 shader 无法在当前 backend 上解析到兼容 graphics variant,也会在这里失败。
3. 调用 [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md) 生成网格参数,并基于 `surface.GetRenderArea()` 的宽高构建 view-projection 常量
4. 把常量写入 descriptor set
5. `surface.IsAutoTransitionEnabled()``true`,则把颜色附件从 `surface.GetColorStateAfter()` 切到 `RenderTarget`,并把深度附件从 `surface.GetDepthStateAfter()` 切到 `DepthWrite`
6. 绑定第一个颜色附件和深度附件,按 `surface.GetRenderArea()` 设置 viewport 与 scissor
7. 以全屏三角形方式执行一次 `Draw(3, 1, 0, 0)`,并在结束后按需恢复资源状态
## 关键语义
- 这条路径不再隐式依赖 engine builtin shader调用方必须先通过构造函数或 [SetShaderPath](SetShaderPath.md) 提供有效 shader 路径
- pass 本身不做资源状态切换
- viewportscissor 总是覆盖整张 surface不读取 `surface.GetRenderArea()`
- 当前只写第一个颜色附件。
- 当前路径不再`backendType == D3D12` 作为固定失败条件;真正是否能执行,取决于当前 backend 上是否能解析到兼容的 grid shader variant
- `EnsureInitialized(...)` 现在显式接收 `surface`,并按 backend、render target format、depth format 与 `sampleCount` 重新建资源
- viewportscissor 和 grid 投影用到的宽高比都会遵循 `surface.GetRenderArea()`,而不是整张 surface 的尺寸
- 当前只写第一个颜色附件。
## 相关文档
@@ -42,3 +42,4 @@ bool Render(
- [SetShaderPath](SetShaderPath.md)
- [InfiniteGridPassData](InfiniteGridPassData.md)
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md)
- [RenderSurface](../../RenderSurface/RenderSurface.md)

View File

@@ -6,65 +6,64 @@
**头文件**: `XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h`
**描述**: 内建的全屏后处理 pass读取 object-id 纹理并把选中对象轮廓或调试 selection mask 合成到当前颜色目标;真正使用哪份 shader 由调用方通过 `shaderPath` 注入。
**描述**: 内建的全屏轮廓合成 pass读取 object-id 纹理并把选中对象轮廓或调试 selection mask 叠加到当前颜色目标;真正使用哪份 shader 由调用方通过 `shaderPath` 注入。
## 概览
`BuiltinObjectIdOutlinePass` 处在“object-id 已经生成,但主场景也已经画完”的阶段。它本身不负责提取可见物体,也不负责生成 object-id 纹理,而是消费这些上游结果:
- object-id 纹理通常来自 `BuiltinObjectIdPass`
- 选中对象列表和 style 当前通常由 editor 侧 Scene View render plan 提供
- 调用时机当前`editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp` 这类包装 pass 决定,并最终通过 [CameraRenderer](../../Execution/CameraRenderer/CameraRenderer.md) 的 `postScenePasses` 提交
- object-id 纹理通常来自 `BuiltinObjectIdPass`
- 选中对象列表和样式通常由 editor 侧 Scene View render plan 提供
- 调用时机通常`SceneViewportSelectionOutlinePass` 这类包装逻辑决定,并最终通过 [CameraRenderer](../../Execution/CameraRenderer/CameraRenderer.md) 的 `postScenePasses` 提交
因此它更接近一个“builtin selection feedback compositor”而不是一个通用 post-process 框架。
因此它更接近一个“selection feedback compositor”而不是通用 post-process 框架。
和早期实现不同,当前类已经不再把 outline shader 写死为 engine builtin 资源。源码里的职责边界
和早期实现不同,当前类不再把 outline shader 写死成某个单一后端资源,也不再直接接受裸 `objectIdTextureView` 参数。源码里的职责边界已经变成
- `BuiltinObjectIdOutlinePass`
只负责资源创建、object-id 采样、常量编码和一次全屏三角形执行
- editor 调用方
通过 [SetShaderPath](SetShaderPath.md) 或构造函数参数注入具体 shader 路径。
- `SceneViewportSelectionOutlinePass`
当前用 [SceneViewportResourcePaths](../../../Editor/Viewport/SceneViewportResourcePaths/SceneViewportResourcePaths.md)
生成 editor-owned object-id outline shader 路径,再交给这里执行;`SceneViewportShaderPaths` 只是兼容 include 层。
只负责资源创建、选中对象编码、object-id 采样和一次全屏三角形执行
- [ObjectIdOutlinePassInputs](../ObjectIdOutlinePassInputs/ObjectIdOutlinePassInputs.md)
描述 object-id 纹理视图及其进入 pass 前的资源状态
- editor / 调用方
通过 [SetShaderPath](SetShaderPath.md) 或构造函数参数注入具体 shader 路径,并负责准备选中对象列表
## 输入与输出契约
| 输入 / 输出 | 当前要求 |
|------|------|
| `renderContext` | 必须有效,且后端类型必须是 `D3D12`。 |
| `surface` | 至少要有一个有效颜色附件pass 只写 `colorAttachments[0]`。 |
| `objectIdTextureView` | 不能为空,并且调用方要保证它已经处于可作为 SRV 读取的状态。 |
| `selectedObjectIds` | 不能为空;超过 `256` 个时只会取前 `256`。 |
| `renderContext` | 必须有效。 |
| `surface` | 必须具备恰好一个有效颜色附件,且 render area 有效pass 只写 `colorAttachments[0]`。 |
| `inputs` | 由 [ObjectIdOutlinePassInputs](../ObjectIdOutlinePassInputs/ObjectIdOutlinePassInputs.md) 描述;`objectIdTextureView` 不能为空。 |
| `selectedObjectIds` | 运行时对象 ID 列表;最多编码前 `256` 个,且至少要有一个能成功转换成 render object id。 |
| `style` | 决定轮廓颜色、宽度和是否改为输出 debug mask。 |
| 输出 | 在主颜色目标上 alpha 混合轮廓,或在 debug 模式下输出黑白 selection mask。 |
| 输出 | 在主颜色目标上合轮廓,或在 debug 模式下输出 selection mask 可视化结果。 |
## 当前渲染算法
## 当前渲染流程
`BuiltinObjectIdOutlinePass.cpp` 与当前配置 shader 的实现,流程是:
1. 校验上下文、后端、object-id SRV 和选中对象列表。
2. 懒初始化内部资源,必要时重新创建 pipeline layout、pipeline state 和两个 descriptor set;这一步要求 `shaderPath` 非空且可成功加载。
3. `surface.GetWidth()` / `GetHeight()` 计算视口尺寸与 texel size。
4. `EncodeObjectIdToColor()` 把选中对象 ID 编码进常量缓冲,最多写入 `kMaxSelectedObjectCount = 256` 项。
5. 绑定一个 CBV set 和一个 SRV set设置目标为 `surface` 的第一个颜色附件。
6. 以全屏三角形方式执行 `Draw(3, 1, 0, 0)`
1. 校验 `renderContext``inputs.objectIdTextureView``selectedObjectIds` 和目标 `surface`
2. 通过 `EnsureInitialized(renderContext, surface)` 懒创建或重建 pipeline layout、pipeline state 和两个 descriptor set
3. 把运行时对象 ID 转成 render object id并最多写入 `kMaxSelectedObjectCount = 256` 项编码颜色
4. 把常量缓冲写到 `m_constantSet`,把 `inputs.objectIdTextureView` 写到 `m_textureSet`
5. `surface` 开启自动状态切换,则把输出颜色附件和 object-id 纹理切到本 pass 需要的状态,并在结束后恢复
6. `surface.GetRenderArea()` 作为 viewport/scissor执行一次全屏三角形 `Draw(3, 1, 0, 0)`
## 生命周期
- [Constructor](Constructor.md) 只做状态清零,不会立刻创建 GPU 资源
- [SetShaderPath](SetShaderPath.md) 更新路径时,会立刻销毁当前缓存的 RHI 资源,等待下一次 `Render()` 重新初始化
- 第一次 [Render](Render.md) 成功进入初始化路径时,才会真正创建 descriptor 和 PSO
- [Shutdown](Shutdown.md) 会显式销毁所有内部缓存对象
- [Destructor](Destructor.md) 当前是默认析构,因此如果把它作为长期 renderer 成员使用,调用方仍在销毁前显式执行 `Shutdown()`
- [Constructor](Constructor.md) 只初始化状态,不会立刻创建 GPU 资源
- [SetShaderPath](SetShaderPath.md) 更新路径时,会销毁当前缓存的 RHI 资源,等待下一次 [Render](Render.md) 重新初始化
- 第一次成功进入初始化路径时,才会真正创建 descriptor set 和 PSO
- [Shutdown](Shutdown.md) 会显式销毁所有内部缓存对象
- [Destructor](Destructor.md) 当前是默认析构,因此把它作为长期 renderer 成员使用,调用方仍在销毁前显式执行 `Shutdown()`
## 当前实现边界
- 只接受 `D3D12`,其它后端直接返回 `false`
- 在第一次创建资源前必须先提供有效 `shaderPath`;空路径会记录错误并返回 `false`
- 处理资源状态切换,也不负责清屏;这些由调用方保证。
-前完全忽略 `surface.GetRenderArea()`viewport 和 scissor 都直接取整张 surface 的宽高。
- 只使用第一个颜色附件;多 render target 场景不会同步写其它附件。
- 当前不再硬编码限定 `D3D12`;只要配置 shader 在当前 backend 上存在兼容 graphics variant即可建立资源并执行
- 在第一次创建资源前必须先提供有效 `shaderPath`
- 处理单颜色附件输出,不写深度
-`surface.IsAutoTransitionEnabled()``false`pass 不会替调用方检查或修正目标与输入纹理的资源状态
- pipeline 资源会按 backend、render target format 和 sample count 重新建立;当前未把 sample quality 纳入该类自身的缓存键
## 公开方法
@@ -75,21 +74,19 @@
| [SetShaderPath](SetShaderPath.md) | 更新当前要加载的 shader 路径,并清空已创建资源。 |
| [GetShaderPath](GetShaderPath.md) | 返回当前记录的 shader 路径。 |
| [Shutdown](Shutdown.md) | 销毁已创建的 pipeline、descriptor 和 shader handle。 |
| [Render](Render.md) | 读取 object-id SRV,并把轮廓或 debug mask 写入颜色目标。 |
| [Render](Render.md) | 读取 object-id 输入,并把轮廓或 debug mask 写入颜色目标。 |
## 真实使用位置
- `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp` `SceneViewportSelectionOutlinePassRenderer` 包装它,
并通过 `GetSceneViewportObjectIdOutlineShaderPath()` 注入 editor-owned shader 路径。
- [SceneViewportRenderPlan](../../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 决定当前帧是否要创建 selection outline pass。
- [CameraRenderer](../../Execution/CameraRenderer/CameraRenderer.md) 在主场景与 object-id pass 之后执行这些 `postScenePasses`
- `editor/src/Viewport/ViewportHostRenderFlowUtils.h` 当前为 Scene View 组装默认橙色、`2px` 的 outline style。
- `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp` 当前会包装它,并通过 `GetSceneViewportObjectIdOutlineShaderPath()` 注入 editor-owned shader 路径
- [SceneViewportRenderPlan](../../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 决定当前帧是否创建 object-id outline 链路
- [CameraRenderer](../../Execution/CameraRenderer/CameraRenderer.md) 在主场景与 object-id pass 之后执行这些 `postScenePasses`
## 相关文档
- [Passes](../Passes.md)
- [SetShaderPath](SetShaderPath.md)
- [GetShaderPath](GetShaderPath.md)
- [Render](Render.md)
- [ObjectIdOutlinePassInputs](../ObjectIdOutlinePassInputs/ObjectIdOutlinePassInputs.md)
- [ObjectIdOutlineStyle](../ObjectIdOutlineStyle/ObjectIdOutlineStyle.md)
- [CameraRenderer](../../Execution/CameraRenderer/CameraRenderer.md)
- [RenderSurface](../../RenderSurface/RenderSurface.md)

View File

@@ -12,7 +12,7 @@
bool Render(
const RenderContext& renderContext,
const RenderSurface& surface,
RHI::RHIResourceView* objectIdTextureView,
const ObjectIdOutlinePassInputs& inputs,
const std::vector<uint64_t>& selectedObjectIds,
const ObjectIdOutlineStyle& style = {});
```
@@ -23,20 +23,22 @@ bool Render(
## 当前实现流程
1. 如果 `renderContext` 无效、后端不是 `D3D12``objectIdTextureView == nullptr``selectedObjectIds` 为空,直接返回 `false`
2. 调用 `EnsureInitialized(renderContext)`;如果当前 device/backend 变化,会先销毁旧资源再重建。若 `shaderPath` 为空或配置路径下的 shader 无法加载,也会在这里失败
3. 检查 `surface.GetColorAttachments()`,要求至少存在一个有效颜色附件
4. 构建 `OutlineConstants`,写入 viewport 尺寸、轮廓颜色、调试开关、像素宽度和最多 `256` 个选中对象编码颜色
5. 把常量缓冲写到 `m_constantSet`,把 `objectIdTextureView` 绑定到 `m_textureSet`
6. 设置渲染目标为 `surface` 的第一个颜色附件,并提交一次全屏三角形 `Draw(3, 1, 0, 0)`
1. 如果 `renderContext` 无效、`inputs.objectIdTextureView == nullptr``selectedObjectIds` 为空,直接返回 `false`
2. 检查 `surface` 是否具备单个有效颜色附件,以及有效的 `renderArea`
3. 调用 `EnsureInitialized(renderContext, surface)`;如果当前 device/backend、render target format 或 sample count 变化,会先销毁旧资源再重建
4. `selectedObjectIds` 逐个转换成 render object id最多编码 `256` 个,转换失败的条目会被跳过;若最终一个都没留下,返回 `false`
5. 构建 `OutlineConstants`,写入 viewport 尺寸、轮廓颜色、调试开关、像素宽度和选中对象编码颜色
6. 把常量缓冲写到 `m_constantSet`,把 `inputs.objectIdTextureView` 绑定到 `m_textureSet`
7.`surface.IsAutoTransitionEnabled()``true`,则把输出颜色附件切到 `RenderTarget`、把 object-id 输入从 `inputs.objectIdTextureState` 切到 `PixelShaderResource`,并在结束后恢复。
8. 使用 `surface.GetRenderArea()` 设置 viewport 与 scissor提交一次全屏三角形 `Draw(3, 1, 0, 0)`
## 关键语义
- 这条路径不再隐式依赖 engine builtin outline shader调用方必须先通过构造函数或 [SetShaderPath](SetShaderPath.md) 提供有效路径。
- 这条路径不再隐式依赖某个固定后端 outline shader调用方必须先通过构造函数或 [SetShaderPath](SetShaderPath.md) 提供有效路径。
- 当前只使用 `surface` 的第一个颜色附件。
- pass 自己不会做 render target 清空;如果要输出 debug mask 这“整张替换”的结果,需要由调用方先清空目标。
- pass 自己也不负责资源状态切换;调用前应保证颜色目标已经在 `RenderTarget` 状态、object-id SRV 已经可读
- 当前 viewport 和 scissor 始终覆盖整张 surface而不是 `surface.GetRenderArea()`
- pass 自己不会做 render target 清空;如果要输出 debug mask 这“整张替换”的结果,需要由调用方先清空目标。
- 当自动状态切换关闭时pass 不会替调用方检查目标与 object-id 输入是否已经处于正确资源状态
- viewport 和 scissor 当前都会遵循 `surface.GetRenderArea()`
## 典型使用位置
@@ -47,5 +49,6 @@ bool Render(
- [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass.md)
- [SetShaderPath](SetShaderPath.md)
- [ObjectIdOutlinePassInputs](../ObjectIdOutlinePassInputs/ObjectIdOutlinePassInputs.md)
- [ObjectIdOutlineStyle](../ObjectIdOutlineStyle/ObjectIdOutlineStyle.md)
- [RenderSurface](../../RenderSurface/RenderSurface.md)

View File

@@ -12,20 +12,20 @@
`BuiltinObjectIdPass` 可以理解成主场景渲染旁边的一条辅助输出路径:
- 主颜色目标仍由当前 `RenderPipeline` 负责
- object-id 目标由这条 pass 用单独 shader 再画一遍可见物体
- 每个物体的 `GameObject::GetID()` 会被编码成 RGBA 颜色写入目标纹理
- 主颜色目标仍由当前 `RenderPipeline` 负责
- object-id 目标由这条 pass 用单独 shader 再画一遍可见物体
- 每个物体的 `GameObject::GetID()` 会被编码成 RGBA 颜色写入目标纹理
这样后续只要采样这张 object-id 纹理,就能做选中描边、拾取或调试显示,而不必回读主颜色缓冲。
## 当前实现流程
1. 校验 `RenderContext`、颜色附件、深度附件和 `RenderSurface` 的 render area。
2. 惰性创建 pipeline layout、pipeline state builtin object-id shader。
3. 如果 `RenderSurface` 开启自动状态切换,先把颜色附件切到 `RenderTarget`
4. 清空 object-id 颜色目标为全零
1. 校验 `RenderContext`颜色附件、深度附件和 `RenderSurface` 的 render area。
2. 按当前 backend、surface 格式和采样描述惰性创建或重建 pipeline layout、pipeline state builtin object-id shader。
3. 如果 `RenderSurface` 开启自动状态切换,先把颜色附件切到 `RenderTarget`,把深度附件切到 `DepthWrite`
4. 清空当前 render area 范围内的 object-id 颜色目标和深度目标
5. 遍历 `RenderSceneData::visibleItems`,按物体 ID 建立或复用常量缓冲并发出 draw call。
6. 若开启自动状态切换,再把颜色附件切回 `surface.GetColorStateAfter()`
6. 若开启自动状态切换,再把颜色附件和深度附件恢复到 `surface` 记录的 after 状态
## 关键实现细节

View File

@@ -11,18 +11,19 @@ bool Render(
当前实现会:
1. 校验 `context`、颜色附件、深度附件和 `surface.GetRenderArea()`
2. 调用惰性初始化,必要时建 shader、pipeline layout 和 pipeline state。
3. 如果 `surface` 开启自动状态切换,把第一个颜色附件切到 `RenderTarget`
4. 把当前 render area 清成全零 object-id 颜色。
1. 校验 `context`颜色附件、深度附件和 `surface.GetRenderArea()`
2. 调用惰性初始化,必要时按当前 backend、surface 格式与采样描述创建或重建 shader、pipeline layout 和 pipeline state。
3. 如果 `surface` 开启自动状态切换,把第一个颜色附件切到 `RenderTarget`,同时把深度附件切到 `DepthWrite`
4. 把当前 render area 清成全零 object-id 颜色,并把深度清到默认值
5. 遍历 `sceneData.visibleItems`,为每个 `gameObject` 编码 object-id 并发出 draw call。
6. 如果开启自动状态切换,再把颜色附件切回 `surface.GetColorStateAfter()`
6. 如果开启自动状态切换,再把颜色附件切回 `surface.GetColorStateAfter()`,把深度附件切回 `surface.GetDepthStateAfter()`
## 当前细节
- viewport 与 scissor 使用 `surface.GetRenderArea()`,不是整张 surface。
- 每个 object ID 会缓存一套独立常量 descriptor set。
- section mesh 会按 `VisibleRenderItem::sectionIndex` 绘制对应 section否则退回整 mesh。
- 当前自动状态切换会同时消费 `RenderSurface` 的颜色和深度 before/after 状态。
## 返回值

View File

@@ -17,14 +17,16 @@
它不是 `RenderPass` 子类,而是一个自管理资源的独立 helper。
当前接口已经把输入纹理打包为 [SelectionOutlinePassInputs](../SelectionOutlinePassInputs/SelectionOutlinePassInputs.md),并允许调用方显式声明 selection mask 与 depth 在进入本 pass 前的资源状态。
## 当前执行流程
1. [Render](Render.md) 校验 render context、mask view、depth view 和 surface
1. [Render](Render.md) 校验 render context、输入纹理、单颜色目标和有效 render area
2. `EnsureInitialized(...)` 按后端、render target 格式和采样数准备 pipeline 资源
3. 写入 outline 常量
4. 更新 selection mask / depth 的 SRV 绑定
5. 视情况做资源状态切换
6. 绘制一次全屏三角形并恢复资源状态
5. 若目标 `surface` 开启自动状态切换,则按 `SelectionOutlinePassInputs` 提供的状态把输入切到 `PixelShaderResource`,并在结束后恢复
6. 绘制一次全屏三角形并结束 render pass
## 关键资源
@@ -34,6 +36,7 @@
| `m_constantPool` / `m_constantSet` | outline 常量缓冲 |
| `m_texturePool` / `m_textureSet` | selection mask 与 depth SRV |
| `m_shaderPath` | 当前使用的 selection-outline shader 路径 |
| `m_renderTargetFormat` / `m_renderTargetSampleCount` | 当前缓存资源所对应的目标格式与采样数 |
## 当前实现边界
@@ -41,16 +44,18 @@
- 当前不写深度
- 输出样式由 [SelectionOutlineStyle](../SelectionOutlineStyle/SelectionOutlineStyle.md) 控制
- 输入纹理来源由 [SelectionOutlinePassInputs](../SelectionOutlinePassInputs/SelectionOutlinePassInputs.md) 描述
-`surface.IsAutoTransitionEnabled()``false` 时,调用方需要自己保证目标与输入纹理已经处于正确状态
- 当前不再硬编码限制为 `D3D12`;只要配置 shader 在当前 backend 上存在兼容 graphics variant即可创建资源并执行
## 公开方法
| 方法 | 说明 |
|------|------|
| [Constructor](Constructor.md) | 创建 pass可注入 shader 路径 |
| [SetShaderPath](SetShaderPath.md) | 更新 shader 路径并清空已创建资源 |
| [GetShaderPath](GetShaderPath.md) | 返回当前 shader 路径 |
| [Render](Render.md) | 执行全屏描边合成 |
| [Shutdown](Shutdown.md) | 销毁内部 RHI 资源 |
| [Constructor](Constructor.md) | 创建 pass可注入 shader 路径 |
| [SetShaderPath](SetShaderPath.md) | 更新 shader 路径并清空已创建资源 |
| [GetShaderPath](GetShaderPath.md) | 返回当前 shader 路径 |
| [Render](Render.md) | 执行全屏描边合成 |
| [Shutdown](Shutdown.md) | 销毁内部 RHI 资源 |
## 相关文档

View File

@@ -12,13 +12,20 @@ bool Render(
## 当前实现流程
1. 校验 render context、mask/depth 纹理与 surface
2. `EnsureInitialized(...)`
3. 构建 `OutlineConstants`
4. 写入常量并更新两个 SRV
5. 自动状态切换开启时,把目标切到 `RenderTarget`、输入切到 `PixelShaderResource`
6. 绑定两个 descriptor set,执行一次 `Draw(3, 1, 0, 0)`
7. 恢复资源状态
1. 校验 `renderContext``inputs.selectionMaskTextureView``inputs.depthTextureView`
2. 要求 `surface` 具备单个有效颜色附件,以及有效的 `renderArea`
3. 调用 `EnsureInitialized(renderContext, surface)`如果后端、render target format 或 sample count 变化,会重建内部 pipeline 资源。
4. 构建 `OutlineConstants`,并把 selection mask / depth 两个 SRV 写入纹理 descriptor set。
5. `surface.IsAutoTransitionEnabled()``true`,则把目标切到 `RenderTarget`,同时把两个输入纹理按 `SelectionOutlinePassInputs` 提供的状态切到 `PixelShaderResource`
6. 使用 `surface.GetRenderArea()` 设置 viewport / scissor,执行一次 `Draw(3, 1, 0, 0)`
7. 结束 render pass并在自动状态切换开启时恢复目标与输入纹理状态
## 关键语义
- 输入结构显式携带了 selection mask 与 depth 纹理的进入状态,因此本 pass 可以在自动状态切换模式下自行恢复它们。
- 当前只写 `surface` 的第一个颜色附件,不写深度。
- 当自动状态切换关闭时,调用方必须自己保证输出目标和两个输入纹理已经处于正确状态。
- 这条路径当前会按目标格式和采样数缓存/重建 pipeline 资源。
## 相关文档

View File

@@ -15,16 +15,17 @@
- 几何始终来自 builtin cube mesh而不是体积资源自带网格
- 着色器必须声明 `PerObject``VolumeField` 资源绑定
- descriptor set 与 pipeline state 会按 shader pass、材质和 volume SRV 动态缓存
- descriptor set 与 pipeline state 会按 shader pass、材质、surface 格式以及采样参数动态缓存
## 当前执行流程
1. [Initialize](Initialize.md) / `EnsureInitialized(...)` 加载 builtin cube mesh
2. [Execute](Execute.md) 校验 `RenderSurface` 是否具备单颜色附件和深度附件
3. 逐个遍历 `sceneData.visibleVolumes`
4. 为每个体积解析兼容的 volumetric shader pass
5. 构建或复用 pipeline layout、pipeline state 和动态 descriptor set
6. 把体积 bounds、材质常量、主方向光常量与 volume SRV 绑定后提交 draw
1. [Initialize](Initialize.md) / `EnsureInitialized(...)` 加载 builtin cube mesh 和基础资源
2. [PrepareVolumeResources](PrepareVolumeResources.md) 预热当前帧可见体积需要的 mesh / volume GPU 资源
3. [Execute](Execute.md) 校验 `RenderSurface` 是否具备单颜色附件、深度附件和有效 render area
4. 逐个遍历 `sceneData.visibleVolumes`
5. 为每个体积解析兼容的 volumetric shader pass
6. 构建或复用 pipeline layout、pipeline state 和动态 descriptor set
7. 把体积 bounds、材质常量、光照常量与 volume SRV 绑定后提交 draw
## 关键内部状态
@@ -33,15 +34,16 @@
| `m_builtinCubeMesh` | 所有体积绘制共用的 cube proxy mesh |
| `m_resourceCache` | 复用 mesh 与 volume GPU 资源 |
| `m_passResourceLayouts` | 按 shader/pass 缓存资源布局 |
| `m_pipelineStates` | 按 render state、shader、格式与 keyword 签名缓存 pipeline |
| `m_pipelineStates` | 按 render state、shader、格式、sample count / quality 与 keyword 签名缓存 pipeline |
| `m_dynamicDescriptorSets` | 按对象、材质、volume field 缓存动态描述符集 |
## 当前实现边界
- 只处理 `VisibleVolumeItem`
- 只接受 `VolumeStorageKind::NanoVDB`
- 当前只为 `VolumeStorageKind::NanoVDB` 体积准备 GPU 资源并参与绘制
- 当前要求 surface 为“单颜色附件 + 深度附件”模型
- 若 shader pass 未声明 `PerObject``VolumeField` 绑定,会直接失败而不是做 legacy fallback
- 这仍然是 builtin forward 链路中的一个 scene pass不是独立的体渲染框架或 render graph 子系统
## 公开方法
@@ -49,13 +51,20 @@
|------|------|
| [BuildInputLayout](BuildInputLayout.md) | 返回体积 pass 使用的顶点布局 |
| [GetName](GetName.md) | 返回 pass 名称 |
| [Initialize](Initialize.md) | 预热 builtin cube mesh 等资源 |
| [Initialize](Initialize.md) | 预热 builtin cube mesh 等基础资源 |
| [PrepareVolumeResources](PrepareVolumeResources.md) | 为当前帧可见体积预热 mesh 与 volume GPU 资源 |
| [Execute](Execute.md) | 绘制所有可见体积 |
| [Shutdown](Shutdown.md) | 销毁缓存的 RHI 资源 |
## 真实接入位置
- [BuiltinForwardPipeline](../../Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md) 在场景存在 `visibleVolumes` 时,会先调用 [PrepareVolumeResources](PrepareVolumeResources.md) 预热资源,再把当前 pass 放进 scene-pass 执行序列
- `RenderSceneExtractor` 负责把体积对象提取到 `sceneData.visibleVolumes`
## 相关文档
- [Passes](../Passes.md)
- [VisibleVolumeItem](../../FrameData/VisibleVolumeItem/VisibleVolumeItem.md)
- [VolumeField](../../../Resources/Volume/VolumeField/VolumeField.md)
- [RenderResourceCache](../../Caches/RenderResourceCache/RenderResourceCache.md)
- [VolumeRendererComponent](../../../Components/VolumeRendererComponent/VolumeRendererComponent.md)

View File

@@ -8,19 +8,22 @@ bool Execute(const RenderPassContext& context) override;
## 当前实现流程
1. 校验 `renderContext`
2.`visibleVolumes` 为空,直接返回 `true`
3. 要求 surface 具备单颜色附件、深度附件和有效 render area
4. 设置 viewport、scissor 和 triangle-list topology
5. 逐个调用内部 `DrawVisibleVolume(...)`
1. 校验 `renderContext`
2.`visibleVolumes` 为空,直接返回 `true`
3. 要求 surface 具备单颜色附件、深度附件和有效 render area
4. 调用 [PrepareVolumeResources](PrepareVolumeResources.md) 预热当前帧体积资源。
5. 设置 viewport、scissor 和 triangle-list topology。
6. 逐个调用内部 `DrawVisibleVolume(...)`
## 关键语义
- 这里不负责 scene extraction输入必须已经是 `VisibleVolumeItem`
- 体积绘制依赖材质 shader pass 与 volume SRV 都已可用
- 某个体积绘制失败不会自动切换为其他 shader 或 fallback path
- 这里不负责 scene extraction输入必须已经是 `VisibleVolumeItem`
- 体积绘制依赖材质 shader pass 与 volume SRV 都已可用
- pipeline state 会按 surface 的颜色/深度格式与 sample count / quality 建立或复用。
- 某个体积绘制失败不会自动切换为其他 shader 或 fallback path。
## 相关文档
- [BuiltinVolumetricPass](BuiltinVolumetricPass.md)
- [PrepareVolumeResources](PrepareVolumeResources.md)
- [VisibleVolumeItem](../../FrameData/VisibleVolumeItem/VisibleVolumeItem.md)

View File

@@ -8,11 +8,13 @@ bool Initialize(const RenderContext& context) override;
## 当前行为
- 实际入口会转到内部 `EnsureInitialized(...)`
- 当前最关键的初始化工作是加载 builtin cube mesh
- `RenderContext` 无效或 cube mesh 加载失败,返回 `false`
- 实际入口会转到内部 `EnsureInitialized(...)`
- 当前最关键的初始化工作是加载 builtin cube mesh,并准备与 `RenderContext` 绑定的基础缓存。
- 这里不会为某一帧具体的 `visibleVolumes` 预热 GPU 资源;这部分工作已经拆到 [PrepareVolumeResources](PrepareVolumeResources.md)。
-`RenderContext` 无效或 cube mesh 加载失败,返回 `false`
## 相关文档
- [BuiltinVolumetricPass](BuiltinVolumetricPass.md)
- [PrepareVolumeResources](PrepareVolumeResources.md)
- [Execute](Execute.md)

View File

@@ -0,0 +1,34 @@
# BuiltinVolumetricPass::PrepareVolumeResources
```cpp
bool PrepareVolumeResources(
const RenderContext& context,
const RenderSceneData& sceneData);
```
为当前帧可见体积预热 `BuiltinVolumetricPass` 需要的 mesh 与 volume GPU 资源。
## 当前实现流程
1. 先调用内部 `EnsureInitialized(context)`,确保 builtin cube mesh 等基础资源可用。
2. 如果 `visibleVolumes` 非空,则先通过 `RenderResourceCache` 取得 cube proxy mesh 的 GPU 缓存,并要求 `vertexBufferView` 有效。
3. 逐个遍历 `sceneData.visibleVolumes`
4. 对同时满足“存在 `volumeField`、存在 `material`、且存储类型为 `NanoVDB`”的条目,调用 `RenderResourceCache::GetOrCreateVolumeField(...)` 预热 volume SRV。
5. 只要某个必要资源创建失败,就返回 `false`
## 当前语义
- 这是场景相关的资源预热步骤,和 [Initialize](Initialize.md) 的“基础一次性准备”不同。
-`visibleVolumes` 为空时,该接口会返回 `true`
- 对不满足绘制条件的体积条目,当前实现会直接跳过,而不是报错。
## 真实接入位置
- [BuiltinForwardPipeline](../../Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md) 当前会在场景存在体积时提前调用它,避免真正进入 [Execute](Execute.md) 后才发现 volume 资源还没准备好。
## 相关文档
- [BuiltinVolumetricPass](BuiltinVolumetricPass.md)
- [Initialize](Initialize.md)
- [Execute](Execute.md)
- [RenderResourceCache](../../Caches/RenderResourceCache/RenderResourceCache.md)

View File

@@ -0,0 +1,32 @@
# ObjectIdOutlinePassInputs
**命名空间**: `XCEngine::Rendering::Passes`
**类型**: `struct`
**头文件**: `XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h`
**描述**: object-id outline pass 的输入纹理描述,指定 object-id 视图及其进入 pass 前的资源状态。
## 字段
| 字段 | 说明 |
|------|------|
| `objectIdTextureView` | object-id 纹理视图。 |
| `objectIdTextureState` | object-id 纹理在进入本 pass 前的资源状态。 |
## 当前作用
-`BuiltinObjectIdOutlinePass::Render(...)` 提供 object-id 输入 SRV。
- 在目标 `surface` 开启自动状态切换时,提供“进入 pass 前”的原始状态,以便 pass 临时切到 `PixelShaderResource` 并在结束后恢复。
## 当前实现边界
- 这个结构只描述 object-id 输入,不描述输出 surface。
-`surface.IsAutoTransitionEnabled()``false`pass 不会自动验证这里声明的状态是否与真实资源状态一致。
## 相关文档
- [BuiltinObjectIdOutlinePass](../BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md)
- [Render](../BuiltinObjectIdOutlinePass/Render.md)
- [ObjectIdOutlineStyle](../ObjectIdOutlineStyle/ObjectIdOutlineStyle.md)

View File

@@ -10,13 +10,13 @@
`Rendering::Passes` 不是用来替代 [RenderPipeline](../RenderPipeline/RenderPipeline.md) 的第二套主渲染框架,而是承载几类被 `CameraRenderer`、editor 视口流程和 builtin pipeline 复用的 builtin pass
- [BuiltinObjectIdPass](BuiltinObjectIdPass/BuiltinObjectIdPass.md) 把可见物体编码成 object-id 颜色,写到辅助渲染目标
- [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md) 读取 object-id 纹理和选中对象列表,把轮廓或调试 mask 叠加回场景颜色
- [BuiltinObjectIdPass](BuiltinObjectIdPass/BuiltinObjectIdPass.md) 把可见物体编码成 object-id 颜色。
- [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md) 读取 object-id 输入并把选中轮廓叠加回主颜色目标
- [BuiltinSelectionMaskPass](BuiltinSelectionMaskPass/BuiltinSelectionMaskPass.md) 重绘当前选中对象,生成 selection mask。
- [BuiltinSelectionOutlinePass](BuiltinSelectionOutlinePass/BuiltinSelectionOutlinePass.md) 读取 selection-mask 与 depth 纹理,在主颜色目标上合成轮廓。
- [BuiltinSelectionOutlinePass](BuiltinSelectionOutlinePass/BuiltinSelectionOutlinePass.md) 读取 selection-mask 与 depth 输入,在主颜色目标上合成轮廓。
- [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md) 给 Scene View 一类编辑器视口叠加无限网格。
- [BuiltinVolumetricPass](BuiltinVolumetricPass/BuiltinVolumetricPass.md) 消费 `visibleVolumes` `VolumeField` GPU 资源,执行体渲染。
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass/BuiltinColorScalePostProcessPass.md) 执行基于 `sourceColorView` 的全屏颜色缩放后处理。
- [BuiltinVolumetricPass](BuiltinVolumetricPass/BuiltinVolumetricPass.md) 消费 `visibleVolumes` 执行体渲染。
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass/BuiltinColorScalePostProcessPass.md) 执行依赖 `sourceSurface``sourceColorView``sourceColorState` 的全屏颜色缩放后处理。
- [BuiltinFinalColorPass](BuiltinFinalColorPass/BuiltinFinalColorPass.md) 执行 exposure、tone mapping、output transfer 和 final color scale 的最终输出阶段。
- [BuiltinDepthStylePassBase](BuiltinDepthStylePassBase/BuiltinDepthStylePassBase.md) 为深度风格场景重绘 pass 提供共享执行骨架。
- [BuiltinDepthOnlyPass](BuiltinDepthOnlyPass/BuiltinDepthOnlyPass.md) 是 `CameraRenderer` 默认 `depthOnly` request 的 builtin 实现。
@@ -29,21 +29,13 @@
当前 Scene View 相关链路大致是:
1. [CameraRenderer](../Execution/CameraRenderer/CameraRenderer.md) 先执行主 `RenderPipeline`
2. [CameraRenderRequest](../Planning/CameraRenderRequest/CameraRenderRequest.md) 请求了 `objectId.surface`,则额外执行 [BuiltinObjectIdPass](BuiltinObjectIdPass/BuiltinObjectIdPass.md)。
3. editor 侧的 `SceneViewportRenderPlan`构建 `postScenePasses``overlayPasses`
4. 其中 `SceneViewportGridPass` / `SceneViewportSelectionOutlinePass` 当前分别调用 [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md) 与“[BuiltinSelectionMaskPass](BuiltinSelectionMaskPass/BuiltinSelectionMaskPass.md) + [BuiltinSelectionOutlinePass](BuiltinSelectionOutlinePass/BuiltinSelectionOutlinePass.md)”这组 selection outline 组合。
2. 如 [CameraRenderRequest](../Planning/CameraRenderRequest/CameraRenderRequest.md) 请求了 `objectId.surface`,则额外执行 [BuiltinObjectIdPass](BuiltinObjectIdPass/BuiltinObjectIdPass.md)。
3. editor 侧的 `SceneViewportRenderPlan`组装 `postScenePasses``overlayPasses`
4. 其中 `SceneViewportGridPass` / `SceneViewportSelectionOutlinePass` 分别调用 [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md) 与“[BuiltinSelectionMaskPass](BuiltinSelectionMaskPass/BuiltinSelectionMaskPass.md) + [BuiltinSelectionOutlinePass](BuiltinSelectionOutlinePass/BuiltinSelectionOutlinePass.md)”这组 selection outline 组合。
### Fullscreen post-process / final-color
`SceneRenderer` 当前会先在 planning 阶段把:
- 相机 `postProcess` 描述栈
- pipeline 默认 final-color 设置
- 相机 final-color override
解析成 `CameraRenderRequest::postProcess``CameraRenderRequest::finalOutput`
随后 `CameraRenderer` 在主场景之后依次执行:
`SceneRenderer` 会先在 planning 阶段把相机 post-process 描述、pipeline 默认 final-color 设置和相机 final-color override 解析成 `CameraRenderRequest::postProcess``CameraRenderRequest::finalOutput`。随后 `CameraRenderer` 在主场景之后依次执行
1. `postProcess`
2. `finalOutput`
@@ -53,45 +45,19 @@
### Builtin volume scene pass
体渲染链路和前面两类流程不同,它不是 post-process而是 builtin scene pass
体渲染链路不是 post-process而是 builtin scene pass
1. `RenderSceneExtractor` 先把体对象提取到 `visibleVolumes`
1. `RenderSceneExtractor` 先把体对象提取到 `visibleVolumes`
2. [BuiltinForwardPipeline](../Pipelines/BuiltinForwardPipeline/BuiltinForwardPipeline.md) 把 [BuiltinVolumetricPass](BuiltinVolumetricPass/BuiltinVolumetricPass.md) 放进自己的 pass sequence。
3. `BuiltinVolumetricPass` 再按材质 shader pass、volume-field 绑定和 lighting 常量逐项绘制。
## 当前公开概念
| 类型 / 页面 | 角色 |
|------|------|
| [BuiltinObjectIdPass](BuiltinObjectIdPass/BuiltinObjectIdPass.md) | 生成 object-id 颜色缓冲。 |
| [BuiltinObjectIdOutlinePass](BuiltinObjectIdOutlinePass/BuiltinObjectIdOutlinePass.md) | 在主颜色目标上合成选中轮廓。 |
| [BuiltinSelectionMaskPass](BuiltinSelectionMaskPass/BuiltinSelectionMaskPass.md) | 为选中轮廓链路生成 selection mask。 |
| [BuiltinSelectionOutlinePass](BuiltinSelectionOutlinePass/BuiltinSelectionOutlinePass.md) | 基于 selection-mask 与 depth 的全屏轮廓合成器。 |
| [BuiltinInfiniteGridPass](BuiltinInfiniteGridPass/BuiltinInfiniteGridPass.md) | Scene View 网格覆盖层的底层执行 pass。 |
| [BuiltinVolumetricPass](BuiltinVolumetricPass/BuiltinVolumetricPass.md) | builtin forward 链路中的体渲染 scene pass。 |
| [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass/BuiltinColorScalePostProcessPass.md) | 相机 post-process 栈当前的颜色缩放全屏 pass。 |
| [BuiltinFinalColorPass](BuiltinFinalColorPass/BuiltinFinalColorPass.md) | final-color 阶段的最终合成 / 输出变换 pass。 |
| [BuiltinDepthStylePassBase](BuiltinDepthStylePassBase/BuiltinDepthStylePassBase.md) | depth-only / shadow-caster 场景重绘共享的执行骨架。 |
| [BuiltinDepthOnlyPass](BuiltinDepthOnlyPass/BuiltinDepthOnlyPass.md) | `CameraRenderer` 默认的 depth-only scene pass。 |
| [BuiltinShadowCasterPass](BuiltinShadowCasterPass/BuiltinShadowCasterPass.md) | `CameraRenderer` 默认的 shadow-caster scene pass。 |
## 测试与真实调用点
- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 验证了 `CameraRenderer` 里 object-id、post-process、final-output 与可选 pass sequence 的接入时机。
- `engine/src/Rendering/Execution/CameraRenderer.cpp` 当前把 [BuiltinShadowCasterPass](BuiltinShadowCasterPass/BuiltinShadowCasterPass.md) 与 [BuiltinDepthOnlyPass](BuiltinDepthOnlyPass/BuiltinDepthOnlyPass.md) 作为默认 scene pass 挂进 `shadowCaster` / `depthOnly` request。
- `engine/src/Rendering/Execution/SceneRenderer.cpp` 当前负责把 fullscreen 阶段写进 `CameraRenderRequest`
- `editor/src/Viewport/Passes/SceneViewportSelectionOutlinePass.h` 当前组合 [BuiltinSelectionMaskPass](BuiltinSelectionMaskPass/BuiltinSelectionMaskPass.md) 与 [BuiltinSelectionOutlinePass](BuiltinSelectionOutlinePass/BuiltinSelectionOutlinePass.md)。
- `engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp` 当前把 [BuiltinVolumetricPass](BuiltinVolumetricPass/BuiltinVolumetricPass.md) 挂进 builtin forward pass sequence。
- `tests/Rendering/unit/test_builtin_forward_pipeline.cpp` 验证了 `VolumeField` 资源语义与布局元数据。
- `tests/Editor/test_viewport_render_flow_utils.cpp` 验证了 Scene View render plan 如何组装 grid / selection outline / overlay pass。
## 当前实现边界
- 这一层目前是 builtin、轻量、偏 editor/工具链导向的 pass 集合,还不是通用 render graph。
- 这一层目前是 builtin、轻量、偏 editor / 工具链导向的 pass 集合,还不是通用 render graph。
- object-id 相关流程依赖单独的辅助 render target / shader resource view而不是直接从主颜色结果反推。
- selection outline 当前已经拆成“mask 重绘 + fullscreen compositing”两层而不是单个对象列表后处理。
- volumetric pass 当前依赖 `visibleVolumes``VolumeField` 资源绑定和 builtin cube proxy不是独立 volume framework。
- fullscreen pass 依赖调用方准备 `sourceColorView` 和中间表面;单个 pass 本身不管理整条阶段链。
- fullscreen pass 依赖调用方准备 `sourceSurface``sourceColorView``sourceColorState` 和中间表面;单个 pass 本身不管理整条阶段链。
## 相关文档

View File

@@ -6,85 +6,62 @@
**头文件**: `XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h`
**描述**: 当前内建的前向主渲染管线实现。它会按 shader pass 的资源声明动态构建 `PassResourceLayout`、descriptor set 与 `RHIPipelineState`,并通过 `RenderPassSequence` 顺序执行 opaque、skybox、volumetric 和 transparent 阶段
**描述**: 当前内建的前向主渲染管线实现。它会按 shader pass 的资源声明动态构建 `PassResourceLayout`、descriptor set 与 `RHIPipelineState`,并通过 `RenderPassSequence` 顺序执行 builtin scene pass
## 概览
`BuiltinForwardPipeline` 已经不是“固定几套 descriptor set + 单一 pipeline layout”的旧模型。当前实现有三条关键缓存链路
`BuiltinForwardPipeline` 已经不是“固定几套 descriptor set + 单一 pipeline layout”的旧模型。当前实现有三条关键缓存链路
- `m_passResourceLayouts`:以 `(shader*, passName)` 为 key缓存每个 shader pass 的资源布局、pipeline layout、静态 descriptor set 和语义位置。
- `m_pipelineStates`:以 `(shader*, passName, material render state)` 为 key缓存真正的图形 pipeline state。
- `m_dynamicDescriptorSets`:以 `(passLayout, setIndex, objectId, material)` 为 key缓存逐物体或逐材质的 descriptor set。
- `m_passResourceLayouts`:以 `(shader*, passName)` 为 key缓存每个 shader pass 的资源布局、pipeline layout、静态 descriptor set 和语义位置信息
- `m_pipelineStates`:以 `(shader*, passName, material render state, surface properties)` 为 key缓存真正的图形 pipeline state。
- `m_dynamicDescriptorSets`:以 `(passLayout, setIndex, objectId, material)` 为 key缓存逐对象或逐材质的 descriptor set。
当前构造函数会向 `m_passSequence` 依次注册:
构造函数当前会向 `m_passSequence` 顺序注册:
- `BuiltinForwardOpaquePass`
- `BuiltinForwardSkyboxPass`
- [BuiltinVolumetricPass](../../Passes/BuiltinVolumetricPass/BuiltinVolumetricPass.md)
- `BuiltinForwardTransparentPass`
因此它已经不再是“单 opaque pass”的过渡版前向路径而是一条正式的多阶段 builtin forward sequence。
## 当前资源契约
`BuiltinForwardPipeline` 当前要求 resolved shader pass 显式声明 `resources`
`BuiltinForwardPipeline` 当前要求 resolved shader pass 显式声明 `resources`随后会通过 [TryBuildBuiltinPassResourceBindingPlan](../../RenderMaterialUtility/TryBuildBuiltinPassResourceBindingPlan.md) 把资源声明收口成 [BuiltinPassResourceBindingPlan](../../RenderMaterialUtility/BuiltinPassResourceBindingPlan.md)。
如果 `resources.Empty()`,实现会直接报错,而不是自动生成 legacy fallback binding plan。
当前可识别的 forward 语义主要包括:
随后会通过 [TryBuildBuiltinPassResourceBindingPlan](../../RenderMaterialUtility/TryBuildBuiltinPassResourceBindingPlan.md)
把显式资源声明收口成
[BuiltinPassResourceBindingPlan](../../RenderMaterialUtility/BuiltinPassResourceBindingPlan.md)。
- `PerObject`
- `Material`
- `BaseColorTexture`
- `LinearClampSampler`
当前可识别的 forward 语义只有四个:
- `PerObject`:必需,且必须是唯一的 constant buffer。
- `Material`:可选,且必须是唯一的 constant buffer。
- `BaseColorTexture`:可选,且必须是唯一的 `Texture2D``TextureCube`
- `LinearClampSampler`:可选,且必须是唯一的 sampler。
其中 `Material` 语义当前不是固定写死成某个历史布局。渲染时会先通过
`ResolveSchemaMaterialConstantPayload(material)` 取得
`Material::GetConstantBufferData()` 的字节视图;只有拿不到有效 payload 时,
才会回退到内部 `FallbackPerMaterialConstants { baseColorFactor, alphaCutoffParams }`
binding-plan 解析之后,布局构建阶段还会继续拒绝以下情况:
- 未知语义。
- 非法 set index。
- 在同一个 set 中混用 sampler 与非 sampler 绑定。
- 在同一个 set 中出现重复 binding。
- 缺少 `PerObject`
超出这组约定的资源声明不会被当前 builtin forward 路径接受。
## 当前渲染流程
1. [Initialize](Initialize.md) 通过 `m_passSequence.Initialize(context)` 进入 pass 生命周期。
2. [Render](Render.md) 把 `RenderContext``RenderSurface``RenderSceneData` 打包成 `RenderPassContext`,再交给 `m_passSequence.Execute(...)`
2. [Render](Render.md) 把 `RenderContext``RenderSurface``RenderSceneData` 打包成不带上游输入的 `RenderPassContext`,再交给 `m_passSequence.Execute(...)`
3. `BuiltinForwardOpaquePass` 处理主场景 opaque 物体。
4. `BuiltinForwardSkyboxPass` 在环境允许时绘制天空盒。
5. [BuiltinVolumetricPass](../../Passes/BuiltinVolumetricPass/BuiltinVolumetricPass.md) 遍历 `RenderSceneData::visibleVolumes`,绘制当前已接入的体对象
5. [BuiltinVolumetricPass](../../Passes/BuiltinVolumetricPass/BuiltinVolumetricPass.md) 遍历 `RenderSceneData::visibleVolumes` 并执行体渲染
6. `BuiltinForwardTransparentPass` 再处理透明阶段。
## 当前实现细节
- [BuildInputLayout](BuildInputLayout.md) 现在明确使用 `POSITION=float3``NORMAL=float3``TEXCOORD=float2`,与 `StaticMeshVertex` 对齐
- 材质贴图解析通过 `ResolveBuiltinBaseColorTexture(material)` 进入 builtin base-color 语义,不再依赖旧文档里的“按若干名字猜测纹理”说法
- 逐物体常量来自 `PerObjectConstants`
- 逐材质常量优先来自 `ResolveSchemaMaterialConstantPayload(material)` 暴露的 schema-driven payload
如果 view 无效,才回退到内部 `FallbackPerMaterialConstants { baseColorFactor, alphaCutoffParams }`
- 采样器和 1x1 白色 fallback 纹理在初始化后长期复用;具体 `Texture``Mesh` 的 GPU 资源则由 [RenderResourceCache](../../Caches/RenderResourceCache/RenderResourceCache.md) 按需上传。
- 体对象阶段当前已经正式接入 [BuiltinVolumetricPass](../../Passes/BuiltinVolumetricPass/BuiltinVolumetricPass.md),因此 `RenderSceneData` 不再只有 `visibleItems` 一条几何主链。
- pipeline state 缓存已经会跟随目标 surface 的 render target format、depth format、sample count 和 sample quality 一起变化
- 材质常量优先来自 schema-driven payload只有拿不到有效 payload 时才会回退到 builtin fallback 常量
- 采样器和 fallback 纹理可长期复用;具体 mesh / texture / volume 资源由 [RenderResourceCache](../../Caches/RenderResourceCache/RenderResourceCache.md) 按需上传
## 当前限制
- 当前虽然已经有 opaque / skybox / volumetric / transparent 四段 sequence但仍不是延迟渲染、RenderGraph 或更通用的多管线框架。
- 资源语义是白名单模型,超出 `PerObject / Material / BaseColorTexture / LinearClampSampler` 的声明不会被接受
- `Render()` 层面不会因为单个物体 `DrawVisibleItem()` 失败整体返回 `false`;当前调用点仍然以“尽量继续绘制其余物体”为主
- 这仍然不是 deferred、render graph 或更通用的多管线框架。
- 资源语义是白名单模型,不接受任意自定义 binding plan
- `Render()` 层面不会因为单个对象 draw 失败整体返回 `false`,更偏向“尽量继续绘制其余对象”
## 公开方法
| 方法 | 说明 |
|------|------|
| [Constructor](Constructor.md) | 创建管线对象,并注册默认的 opaque pass。 |
| [Constructor](Constructor.md) | 创建管线对象,并注册默认 builtin scene pass。 |
| [Destructor](Destructor.md) | 析构时调用 `Shutdown()`。 |
| [BuildInputLayout](BuildInputLayout.md) | 返回 builtin forward 使用的静态网格输入布局。 |
| [Initialize](Initialize.md) | 初始化 pass sequence并在需要时准备共享 GPU 资源。 |

View File

@@ -12,54 +12,35 @@ bool Render(
当前 `Render()` 本身很薄,只做两件事:
1. 调用 `Initialize(context)`,确保 pass sequence 与共享资源已经可用。
2. 组装 `RenderPassContext { context, surface, sceneData }`,再执行 `m_passSequence.Execute(passContext)`
2. 组装 `RenderPassContext { context, surface, sceneData, nullptr, nullptr, RHI::ResourceStates::Common }`,再执行 `m_passSequence.Execute(passContext)`
由于当前 sequence 里只包含 `BuiltinForwardOpaquePass`,真正的绘制逻辑发生在 `ExecuteForwardOpaquePass()`
由于 forward scene pass 没有上游 fullscreen 输入,所以这里会显式把 `sourceSurface` / `sourceColorView` 置空,并把 `sourceColorState` 置为 `Common`
## 当前 opaque pass 流程
## 当前 opaque / scene pass 流程
1. 校验 `surface.GetColorAttachments()` 非空
2. 如启用了自动状态切换,把颜色附件从 `surface.GetColorStateBefore()` 转成 `RenderTarget`
3. 绑定 render target、depth attachment、viewport scissor。
4. `RenderSurface` clear-color override `sceneData.cameraData.clearFlags` 决定是否清颜色/深度
5. 设置 `TriangleList` 拓扑
6. 遍历 `RenderSceneData::visibleItems`,只处理匹配 `BuiltinMaterialPass::ForwardLit` 的可见项
7. 对每个物体复用或创建匹配的 pipeline state并调用内部 `DrawVisibleItem(...)`
8. 结束时如启用了自动状态切换,再把颜色附件从 `RenderTarget` 切回 `surface.GetColorStateAfter()`
## `DrawVisibleItem()` 当前行为
单个可见项的绘制链路为:
1. 通过 `RenderResourceCache` 取得 mesh 的 GPU 缓存。
2. 写入 `PerObjectConstants`,其中包含投影矩阵、视图矩阵、`localToWorld`、逆矩阵和主方向光数据。
3. 基于材质解析实际要使用的 shader/pass。
4. 获取或创建该 `(shader*, passName)` 对应的 `PassResourceLayout`
5. 先解析逐材质常量 payload
- 优先用 `ResolveSchemaMaterialConstantPayload(material)` 取得 schema-driven 字节视图。
- 若 view 无效,则用 `BuildBuiltinForwardMaterialData(material)` 构造仅含 `baseColorFactor` 的 fallback 常量。
- base-color 纹理视图仍单独通过 `ResolveBuiltinBaseColorTexture(material)` 解析。
6. 逐 set 绑定 descriptor
-`PerObject / Material / Texture` 的 set 走动态 descriptor set 缓存。
- 只含 sampler 的 set 走静态 descriptor set 缓存。
7. 调用 `SetGraphicsDescriptorSets(...)`,并按 section 或整 mesh 发出 `DrawIndexed` / `Draw`
1. 校验 `surface`、render area 与必要附件
2. 按需执行自动状态切换。
3. 绑定 render target、depth attachment、viewport scissor。
4. 根据 clear-color override 与相机 clear flags 决定是否清
5. 顺序执行 builtin forward 的 scene-pass sequence
6. 如启用了自动状态切换,在结束阶段把资源恢复到 `surface` 约定的 after state
## 参数
- `context` - 当前渲染上下文。
- `surface` - 当前输出目标。
- `sceneData` - `RenderSceneExtractor` 产出的相机、光照与 `visibleItems` 数据。
- `sceneData` - `RenderSceneExtractor` 产出的相机、光照与可见对象数据。
## 返回值
- 整个 pass sequence 成功执行时返回 `true`
- 初始化失败、缺少颜色附件、render area 非法,或 pass sequence 中某个 pass 明确失败时返回 `false`
- 初始化失败、缺少必要附件、render area 非法,或 sequence 中某个 pass 明确失败时返回 `false`
## 当前限制
- 当前 sequence 只有一个 opaque pass还没有透明排序、shadow pass 或 deferred path
- 调用点目前不会把单个 `DrawVisibleItem()` 失败汇总成整体失败;它更偏向“跳过当前物体,继续绘制其余物体”
- `Render()` 只处理一个 `RenderSurface` 和一份 `RenderSceneData`,不是 render graph。
- `Render()` 只负责把 `RenderSceneData` 交给 builtin forward 的 scene-pass sequence不直接处理 fullscreen 输入链
- 单个 `DrawVisibleItem()` 失败通常只会跳过当前对象,而不是把整帧渲染整体判定为失败
- 这里仍是单 `RenderSurface` + 单 `RenderSceneData` 的线性入口,不是 render graph。
## 相关文档

View File

@@ -36,8 +36,6 @@
| [CollectCameras](CollectCameras.md) | 收集本次应参与渲染的相机列表。 |
| [BuildRequests](BuildRequests.md) | 把相机列表展开成可执行的 `CameraRenderRequest` 数组。 |
## 公开方法
## 当前规划规则
### `CollectCameras()`

View File

@@ -8,73 +8,57 @@
**描述**: 定义轻量通用 pass 抽象 `RenderPass`、执行上下文 `RenderPassContext` 以及顺序执行容器 `RenderPassSequence`
## 概
## 概
`RenderPass.h` 是当前渲染基础设施里最通用的一层 pass 协议。
`RenderPass.h` 是当前渲染基础设施里最通用的一层 pass 协议。它统一回答三件事:
它统一回答三件事:
- pass 执行时能拿到什么上下文
- 一个 pass 最少需要实现哪些生命周期函数
- 多个 pass 如何按顺序组成一个小型执行序列
- 一个 pass 执行时能拿到什么上下文。
- 一个 pass 至少需要实现哪些生命周期函数。
- 多个 pass 如何按顺序组成一个小型执行序列。
## 当前定义
### `RenderPassContext`
- [RenderPassContext](RenderPassContext.md)
统一描述当前输出 `surface`、源颜色输入和共享 `sceneData`
- `RenderPass`
抽象基类,约定所有渲染 pass 的最小生命周期接口。
- [RenderPassSequence](RenderPassSequence.md)
拥有型顺序容器,负责按固定顺序初始化、执行和关闭多个 pass。
`RenderPassContext` 只是三个引用的聚合
- `renderContext`
- `surface`
- `sceneData`
### `RenderPass`
抽象基类包含四个核心成员:
`RenderPass` 抽象基类当前包含 4 个核心成员
| 成员 | 当前语义 |
|------|------|
| `GetName()` | 返回 pass 的人类可读名称。 |
| `GetName()` | 返回 pass 的可读名称。 |
| `Initialize()` | 默认返回 `true`,需要时准备一次性资源。 |
| `Execute()` | 纯虚函数,执行真正的 pass 逻辑。 |
| `Shutdown()` | 默认空实现,释放 pass 资源。 |
### `RenderPassSequence`
`RenderPassSequence` 是一个拥有型容器,内部以 `std::unique_ptr<RenderPass>` 顺序保存 pass。
当前行为是:
- `AddPass()` 会忽略空指针
- `GetPassCount()` 返回当前持有的 pass 数
- `Initialize()` 按插入顺序调用每个 pass 的 `Initialize()`
- `Execute()` 按插入顺序调用每个 pass 的 `Execute()`
- `Shutdown()` 按逆序调用每个 pass 的 `Shutdown()`
## 失败与回滚语义
- `Initialize()` 遇到失败会立返回 `false`
- `Execute()` 遇到失败也会立即停止后续 pass
- `RenderPassSequence` 自身不会在 `Initialize()` 失败时自动回滚已经初始化过的 pass
- `RenderPassSequence::Initialize()` 遇到失败会立返回 `false`
- `RenderPassSequence::Execute()` 遇到失败也会停止后续 pass
- `RenderPassSequence` 自身不会在 `Initialize()` 失败时自动回滚已经初始化过的 pass
- 更高层调用方,例如 `CameraRenderer`,会在 sequence 初始化失败时显式再做一次 `Shutdown()` 回滚。
当前更高层调用方,例如 `CameraRenderer`,会额外包一层 helper若 sequence 初始化失败,就主动执行一次 `Shutdown()` 回滚。
## 测试与真实行为
## 测试验证的真实行为
`tests/Rendering/unit/test_render_pass.cpp` 当前覆盖了:
`tests/Rendering/unit/test_render_pass.cpp` 已覆盖:
- 初始化和执行按插入顺序发生
- `Shutdown()` 按逆序发生
- 中间某个 pass 执行失败时,后续 pass 不再执行
- 初始化与执行按插入顺序发生。
- `Shutdown()` 按逆序发生。
- 中间某个 pass 执行失败时,后续 pass 不再执行。
## 当前实现边界
- 这套协议仍然是线性顺序执行,不是 render graph也没有显式资源依赖建模。
- `RenderPassSequence` 只管理 pass 对象,不管理共享资源或跨 pass 的状态同步。
- `RenderPassContext` 暂时只暴露一个目标表面和一份 `RenderSceneData`不适合表达更复杂的多目标或多阶段图结构。
- 这套协议仍然是线性顺序执行模型,不是 render graph也没有显式资源依赖建模。
- `RenderPassSequence` 只管理 `RenderPass` 对象,不管理共享资源或跨 pass 的状态同步。
- `RenderPassContext` 虽然已经能表达“一个输出 `surface` + 一路可选上游颜色输入”,但还不适合描述更复杂的多目标或多阶段图结构。
## 相关文档
- [RenderPassContext](RenderPassContext.md)
- [RenderPassSequence](RenderPassSequence.md)
- [当前模块](../Rendering.md)
- [CameraRenderRequest](../Planning/CameraRenderRequest/CameraRenderRequest.md)
- [CameraRenderer](../Execution/CameraRenderer/CameraRenderer.md)

View File

@@ -0,0 +1,43 @@
# RenderPassContext
描述单个 `RenderPass` 执行时可见的共享上下文。
```cpp
struct RenderPassContext {
const RenderContext& renderContext;
const RenderSurface& surface;
const RenderSceneData& sceneData;
const RenderSurface* sourceSurface = nullptr;
RHI::RHIResourceView* sourceColorView = nullptr;
RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common;
};
```
## 字段
| 字段 | 说明 |
|------|------|
| `renderContext` | 当前 backend、device、command list / queue 等运行时上下文。 |
| `surface` | 当前 pass 的输出目标 `RenderSurface`。 |
| `sceneData` | 本帧已提取好的场景数据。 |
| `sourceSurface` | fullscreen / chained pass 的上游 `RenderSurface`;没有上游输入时可为空。 |
| `sourceColorView` | 上游传入的颜色输入视图;没有上游输入时可为空。 |
| `sourceColorState` | `sourceColorView` 当前约定的资源状态,供自动切换或状态校验使用。 |
## 当前语义
- 前向场景 pass 往往只消费 `renderContext``surface``sceneData`
- fullscreen post-process / final-output 链路会继续消费 `sourceSurface``sourceColorView``sourceColorState`
- `surface` 描述的是“这一次 pass 输出到哪里”,`sourceSurface` / `sourceColorView` 描述的是“这一次 pass 从哪里读颜色输入”。
## 当前实现边界
- 当前只支持一路可选的上游颜色输入,不支持多个输入 surface 或多纹理输入槽位的统一建模。
- 深度附件、render area、sample 描述等输出约束不在这里展开,而是继续由 [RenderSurface](../RenderSurface/RenderSurface.md) 表达。
- `RenderPassContext` 只是协议对象,不拥有这些资源的生命周期。
## 相关文档
- [RenderPass](RenderPass.md)
- [RenderPassSequence](RenderPassSequence.md)
- [RenderSurface](../RenderSurface/RenderSurface.md)
- [RenderContext](../RenderContext/RenderContext.md)

View File

@@ -0,0 +1,56 @@
# RenderPassSequence
描述按固定顺序持有并执行多个 `RenderPass` 的拥有型容器。
```cpp
class RenderPassSequence {
public:
void AddPass(std::unique_ptr<RenderPass> pass);
size_t GetPassCount() const;
bool Empty() const;
bool Initialize(const RenderContext& context);
void Shutdown();
bool Execute(const RenderPassContext& context);
bool ExecutePass(size_t index, const RenderPassContext& context);
};
```
## 公开方法
| 方法 | 当前语义 |
|------|------|
| `AddPass()` | 追加一个 pass传入 `nullptr` 时直接忽略。 |
| `GetPassCount()` | 返回当前持有的 pass 数量。 |
| `Empty()` | 判断当前序列是否为空。 |
| `Initialize()` | 按插入顺序调用每个 pass 的 `Initialize()`;遇到失败立即返回 `false`。 |
| `Shutdown()` | 按逆序调用每个 pass 的 `Shutdown()`。 |
| `Execute()` | 按插入顺序调用每个 pass 的 `Execute()`;遇到失败立即停止。 |
| `ExecutePass()` | 只执行指定索引的单个 pass索引越界或该位置为空时返回 `false`。 |
## 失败与回滚语义
- `Initialize()` 失败时,`RenderPassSequence` 不会自动回滚已经初始化过的 pass。
- `Execute()` 失败时,后续 pass 不再执行,但当前序列仍保留,调用方可以选择显式 `Shutdown()`
- `Shutdown()` 总是按逆序发生,以匹配常见的资源依赖释放顺序。
## 测试覆盖
`tests/Rendering/unit/test_render_pass.cpp` 当前已经验证:
- 初始化按插入顺序发生。
- 执行按插入顺序发生。
- 关闭按逆序发生。
- 中间某个 pass 返回失败时,后续 pass 不再执行。
`ExecutePass()` 当前没有独立测试,但它的实现只是对索引和空指针做检查后转发到单个 `RenderPass::Execute()`
## 当前实现边界
- 它是线性容器,不会重排 pass也不做依赖分析。
- 共享资源、跨 pass 过渡和中间 surface 分配都不由 `RenderPassSequence` 统一管理。
- 当前实现用 `std::unique_ptr<RenderPass>` 持有 pass因此所有权在加入序列时转移给容器。
## 相关文档
- [RenderPass](RenderPass.md)
- [RenderPassContext](RenderPassContext.md)
- [Passes](../Passes/Passes.md)

View File

@@ -0,0 +1,28 @@
# RenderSurface::GetDepthStateAfter
返回深度附件在本次渲染结束后期望落到的状态。
```cpp
RHI::ResourceStates GetDepthStateAfter() const;
```
## 返回值
- 返回内部保存的深度附件“结束状态”;默认值是 `RHI::ResourceStates::DepthWrite`
## 当前语义
- 某些支持自动 depth barrier 的路径会把它当作从 `DepthWrite` 或其他中间状态恢复出去的目标。
- 它常与 [GetDepthStateBefore](GetDepthStateBefore.md) 共同描述一次渲染阶段对深度附件的进入/退出约定。
## 当前实现边界
- 这里返回的是 surface 记录值,不是对真实资源状态的反查。
- 不同 pass 是否消费它并不完全一致。
## 相关文档
- [RenderSurface](RenderSurface.md)
- [SetDepthStateAfter](SetDepthStateAfter.md)
- [GetDepthStateBefore](GetDepthStateBefore.md)
- [IsAutoTransitionEnabled](IsAutoTransitionEnabled.md)

View File

@@ -0,0 +1,28 @@
# RenderSurface::GetDepthStateBefore
返回深度附件在本次渲染开始前期望处于的状态。
```cpp
RHI::ResourceStates GetDepthStateBefore() const;
```
## 返回值
- 返回内部保存的深度附件“开始状态”;默认值是 `RHI::ResourceStates::DepthWrite`
## 当前语义
- 当消费方启用了基于 `RenderSurface` 的自动 depth barrier 时,这个值会被当作过渡到 `DepthWrite` 或其他目标状态的起点。
- 即使当前 `surface` 没有深度附件,这个字段也仍然可以被预先设置和保留。
## 当前实现边界
- 该返回值只反映 `RenderSurface` 当前记录的约定,不保证 GPU 资源真实状态与之完全一致。
- 并非所有 renderer / pass 都会读取这个字段。
## 相关文档
- [RenderSurface](RenderSurface.md)
- [SetDepthStateBefore](SetDepthStateBefore.md)
- [GetDepthStateAfter](GetDepthStateAfter.md)
- [IsAutoTransitionEnabled](IsAutoTransitionEnabled.md)

View File

@@ -0,0 +1,27 @@
# RenderSurface::GetSampleCount
返回当前记录的采样数。
```cpp
uint32_t GetSampleCount() const;
```
## 返回值
- 返回 `RenderSurface` 当前保存的 `sampleCount`;默认值是 `1`
## 当前语义
- 这个值来自 [SetSampleDesc](SetSampleDesc.md)。
- 一些 scene pass、fullscreen pass 和 request planning 逻辑会把它用于 pipeline 兼容性判断或中间 surface 规划。
## 当前实现边界
- 该值不保证当前绑定的附件真实具备相同 sample count。
- `1` 表示非 MSAA 目标,不代表调用方显式调用过 `SetSampleDesc(1, 0)`
## 相关文档
- [RenderSurface](RenderSurface.md)
- [SetSampleDesc](SetSampleDesc.md)
- [GetSampleQuality](GetSampleQuality.md)

View File

@@ -0,0 +1,28 @@
# RenderSurface::GetSampleQuality
返回当前记录的采样质量。
```cpp
uint32_t GetSampleQuality() const;
```
## 返回值
- 返回 `RenderSurface` 当前保存的 `sampleQuality`;默认值是 `0`
## 当前语义
- 该值来自 [SetSampleDesc](SetSampleDesc.md)。
- 当有效 `sampleCount <= 1` 时,当前实现会保证这里返回 `0`
- 某些后端或 pipeline 构建路径会把它作为 sample quality 维度继续传递。
## 当前实现边界
- 这只是目标 surface 的元数据,不代表附件或后端一定真正使用该 quality。
- 如果调用方把 surface 切回 1x 采样,这个值会被重置为 `0`
## 相关文档
- [RenderSurface](RenderSurface.md)
- [SetSampleDesc](SetSampleDesc.md)
- [GetSampleCount](GetSampleCount.md)

Some files were not shown because too many files have changed in this diff Show More