Compare commits
109 Commits
f917040e9a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bd375cd24 | |||
| c0d62dc749 | |||
| c98b41f6f4 | |||
| 72d09a1c49 | |||
| ba91e0f5dd | |||
| 9064c2f5f2 | |||
| 5797a75619 | |||
| e83f911aef | |||
| dd2299c8b0 | |||
| b8d29e39f6 | |||
| 72914b3865 | |||
| e6950fa704 | |||
| 21b0530f7b | |||
| c3d443eb85 | |||
| d705cc839b | |||
| 0d6b8bf7d8 | |||
| 4362008b39 | |||
| 712f99e723 | |||
| 48daaa1bd0 | |||
| e462f7d6f7 | |||
| 5b89c2bb76 | |||
| f3fc34898a | |||
| d2140bf5cc | |||
| a0d5e84516 | |||
| 8ba05216fb | |||
| 0cc3d6da46 | |||
| adb6fe4659 | |||
| 95edf0435f | |||
| 82c55b3999 | |||
| 00875e0c90 | |||
| b7428b0ef1 | |||
| 1d6f2e290d | |||
| 2ee74e7761 | |||
| 64212a53c7 | |||
| 6f876678f5 | |||
| 2326857a43 | |||
| 0f60f0f657 | |||
| dd3731ba66 | |||
| 941034b387 | |||
| 89590242bd | |||
| a660fc489a | |||
| e86d260d64 | |||
| 347d08463b | |||
| 7ee28a7969 | |||
| b7ce8618d2 | |||
| 7ad4bfbb1c | |||
| 838f676fa6 | |||
| 0ff02150c0 | |||
| 8848cfd958 | |||
| 3e55f8c204 | |||
| 0a015b52ca | |||
| 030230eb1f | |||
| 8f71f99de4 | |||
| 443c56ed08 | |||
| 2958dcc491 | |||
| 35d3d6328b | |||
| c03c7379c8 | |||
| 0a2bdedc59 | |||
| 2fb6eca854 | |||
| c543ccf79c | |||
| 88a71a5426 | |||
| ff4e3f639a | |||
| 92d5cc61cf | |||
| b3acb5afc2 | |||
| 785377bc9b | |||
| 5200fca82f | |||
| 39632e1a04 | |||
| 3622bf3aa2 | |||
| fac6e588a8 | |||
| 5191bb1149 | |||
| d9bc0f1457 | |||
| 4080b2e5fe | |||
| be5dabd820 | |||
| 107b320aa7 | |||
| 15b42c248f | |||
| 977a4cf2a4 | |||
| b187c8970b | |||
| 1119af2e38 | |||
| 2338e306bf | |||
| 87ad489bfd | |||
| 503e6408ed | |||
| 8f5c342799 | |||
| 84faa585d5 | |||
| ac9388445c | |||
| 3561bf22bb | |||
| e6ac43b454 | |||
| 2f3a28ec3e | |||
| 737ccd2e0c | |||
| bdf0b9a16b | |||
| 0c52e0f640 | |||
| bb9a4d5ef4 | |||
| a990553ade | |||
| de2fc8be43 | |||
| effca78771 | |||
| 0602b34652 | |||
| 85b8b3e583 | |||
| cba3823ea2 | |||
| 89bbad2786 | |||
| 00cf3850d5 | |||
| 81d0d92aed | |||
| 2d8ada03a1 | |||
| 7fc7bb0a22 | |||
| 447977214e | |||
| 46fac8a215 | |||
| e2e4e08479 | |||
| e69240db49 | |||
| dd467d2468 | |||
| 71a27d7c9c | |||
| f401a54806 |
55
AGENT.md
55
AGENT.md
@@ -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. 适合当前仓库的工作方式
|
||||
|
||||
|
||||
158
MVS/3DGS-D3D12/CMakeLists.txt
Normal file
158
MVS/3DGS-D3D12/CMakeLists.txt
Normal 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"
|
||||
)
|
||||
162
MVS/3DGS-D3D12/include/XC3DGSD3D12/App.h
Normal file
162
MVS/3DGS-D3D12/include/XC3DGSD3D12/App.h
Normal 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
|
||||
46
MVS/3DGS-D3D12/include/XC3DGSD3D12/GaussianPlyLoader.h
Normal file
46
MVS/3DGS-D3D12/include/XC3DGSD3D12/GaussianPlyLoader.h
Normal 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
|
||||
43
MVS/3DGS-D3D12/shaders/BuildSortKeysCS.hlsl
Normal file
43
MVS/3DGS-D3D12/shaders/BuildSortKeysCS.hlsl
Normal 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);
|
||||
}
|
||||
12
MVS/3DGS-D3D12/shaders/CompositePS.hlsl
Normal file
12
MVS/3DGS-D3D12/shaders/CompositePS.hlsl
Normal 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);
|
||||
}
|
||||
12
MVS/3DGS-D3D12/shaders/CompositeVS.hlsl
Normal file
12
MVS/3DGS-D3D12/shaders/CompositeVS.hlsl
Normal 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;
|
||||
}
|
||||
19
MVS/3DGS-D3D12/shaders/DebugPointsPS.hlsl
Normal file
19
MVS/3DGS-D3D12/shaders/DebugPointsPS.hlsl
Normal 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);
|
||||
}
|
||||
48
MVS/3DGS-D3D12/shaders/DebugPointsVS.hlsl
Normal file
48
MVS/3DGS-D3D12/shaders/DebugPointsVS.hlsl
Normal 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;
|
||||
}
|
||||
477
MVS/3DGS-D3D12/shaders/DeviceRadixSort.hlsl
Normal file
477
MVS/3DGS-D3D12/shaders/DeviceRadixSort.hlsl
Normal 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
|
||||
}
|
||||
}
|
||||
272
MVS/3DGS-D3D12/shaders/PrepareGaussiansCS.hlsl
Normal file
272
MVS/3DGS-D3D12/shaders/PrepareGaussiansCS.hlsl
Normal 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;
|
||||
}
|
||||
22
MVS/3DGS-D3D12/shaders/PreparedSplatView.hlsli
Normal file
22
MVS/3DGS-D3D12/shaders/PreparedSplatView.hlsli
Normal 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
|
||||
959
MVS/3DGS-D3D12/shaders/SortCommon.hlsl
Normal file
959
MVS/3DGS-D3D12/shaders/SortCommon.hlsl
Normal 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
2193
MVS/3DGS-D3D12/src/App.cpp
Normal file
File diff suppressed because it is too large
Load Diff
617
MVS/3DGS-D3D12/src/GaussianPlyLoader.cpp
Normal file
617
MVS/3DGS-D3D12/src/GaussianPlyLoader.cpp
Normal 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
|
||||
42
MVS/3DGS-D3D12/src/main.cpp
Normal file
42
MVS/3DGS-D3D12/src/main.cpp
Normal 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();
|
||||
}
|
||||
@@ -670,4 +670,4 @@ ID3D12Resource* CreateTexture2D(ID3D12GraphicsCommandList* inCommandList,
|
||||
}
|
||||
ID3D12Device* GetD3DDevice() {
|
||||
return gD3D12Device;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,4 +305,4 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLi
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
251
README.md
251
README.md
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -160,5 +160,5 @@
|
||||
|
||||
- [当前模块](../Components.md)
|
||||
- [MeshRendererComponent](../MeshRendererComponent/MeshRendererComponent.md)
|
||||
- [RenderSceneExtractor](../../Rendering/RenderSceneExtractor/RenderSceneExtractor.md)
|
||||
- [RenderSceneExtractor](../../Rendering/Extraction/RenderSceneExtractor/RenderSceneExtractor.md)
|
||||
- [API 总索引](../../../main.md)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()` 当前会优先查本地 snapshot,cache miss 时再回退到 `AssetImportService`;必要时会先 `Refresh()` 数据库再整体重建 snapshot。
|
||||
- `ProjectAssetIndex::TryResolveAssetPath()` 当前同样优先查 snapshot,但 miss 时只回退到 `AssetImportService::TryGetPrimaryAssetPath()`,不会主动刷新整份 snapshot。
|
||||
- `engine/src/Resources/Material/MaterialLoader.cpp` 现在已经把材质纹理路径解析、`.xcmat` 中的 texture `AssetRef` 回读,以及首次访问时的懒加载纳入真实行为范围,因此 `AssetDatabase` 的材质依赖快照不再只是预留设计。
|
||||
- `.xcmat` 当前已经是 material artifact v4:texture 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
|
||||
|
||||
## 相关指南
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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` 因重复覆盖而触发额外锁冲突
|
||||
|
||||
从商业级工具链视角看,这属于“先把本地开发和编辑器热重建跑稳,再逐步演进成更完整脚本构建系统”的典型路线。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -86,5 +86,5 @@ ApplySceneViewportRenderRequestSetup(
|
||||
- [HasOverlayPasses](HasOverlayPasses.md)
|
||||
- [ApplySceneViewportRenderRequestSetup](../ViewportHostRenderFlowUtils/ApplySceneViewportRenderRequestSetup.md)
|
||||
- [ViewportHostRenderFlowUtils](../ViewportHostRenderFlowUtils/ViewportHostRenderFlowUtils.md)
|
||||
- [CameraRenderRequest](../../../Rendering/CameraRenderRequest/CameraRenderRequest.md)
|
||||
- [CameraRenderRequest](../../../Rendering/Planning/CameraRenderRequest/CameraRenderRequest.md)
|
||||
- [Scene View Render Plan And Failure Flow](../../../../_guides/Editor/Scene-Viewport-Render-Plan-And-Failure-Flow.md)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
`SceneViewportRenderPlan.h` 处在 [ViewportHostService](../ViewportHostService/ViewportHostService.md) 的 Scene View 主路径中段。
|
||||
|
||||
它不负责决定“场景里有哪些相机需要渲染”,那是运行时 [SceneRenderer](../../../Rendering/SceneRenderer/SceneRenderer.md) 与 [SceneRenderRequestPlanner](../../../Rendering/SceneRenderRequestPlanner/SceneRenderRequestPlanner.md) 的职责。
|
||||
它不负责决定“场景里有哪些相机需要渲染”,那是运行时 [SceneRenderer](../../../Rendering/Execution/SceneRenderer/SceneRenderer.md) 与 [SceneRenderRequestPlanner](../../../Rendering/Planning/SceneRenderRequestPlanner/SceneRenderRequestPlanner.md) 的职责。
|
||||
|
||||
它负责的是 Scene View 这一层独有的编辑器附加项:
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ void ApplySceneViewportRenderRequestSetup(
|
||||
|
||||
## 作用
|
||||
|
||||
把 Scene View 这一帧最基础的附加渲染设置写回 [CameraRenderRequest](../../../Rendering/CameraRenderRequest/CameraRenderRequest.md)。
|
||||
把 Scene View 这一帧最基础的附加渲染设置写回 [CameraRenderRequest](../../../Rendering/Planning/CameraRenderRequest/CameraRenderRequest.md)。
|
||||
|
||||
它负责的是“最低层 request 装配”:
|
||||
|
||||
@@ -80,5 +80,5 @@ void ApplySceneViewportRenderRequestSetup(
|
||||
- [SceneViewportRenderPlan](../SceneViewportRenderPlan/SceneViewportRenderPlan.md)
|
||||
- [ApplySceneViewportRenderPlan](../SceneViewportRenderPlan/ApplySceneViewportRenderPlan.md)
|
||||
- [ViewportHostRenderTargets](../ViewportHostRenderTargets/ViewportHostRenderTargets.md)
|
||||
- [CameraRenderRequest](../../../Rendering/CameraRenderRequest/CameraRenderRequest.md)
|
||||
- [ObjectIdRenderRequest](../../../Rendering/CameraRenderRequest/ObjectIdRenderRequest/ObjectIdRenderRequest.md)
|
||||
- [CameraRenderRequest](../../../Rendering/Planning/CameraRenderRequest/CameraRenderRequest.md)
|
||||
- [ObjectIdRenderRequest](../../../Rendering/Planning/CameraRenderRequest/ObjectIdRenderRequest/ObjectIdRenderRequest.md)
|
||||
|
||||
@@ -183,4 +183,4 @@ Scene View 在构建 render plan 时,可能已经得到了更精确但非致
|
||||
- [ViewportHostRenderTargets](../ViewportHostRenderTargets/ViewportHostRenderTargets.md)
|
||||
- [SceneViewportRenderPlan](../SceneViewportRenderPlan/SceneViewportRenderPlan.md)
|
||||
- [SceneViewportEditorOverlayData](../SceneViewportEditorOverlayData/SceneViewportEditorOverlayData.md)
|
||||
- [CameraRenderRequest](../../../Rendering/CameraRenderRequest/CameraRenderRequest.md)
|
||||
- [CameraRenderRequest](../../../Rendering/Planning/CameraRenderRequest/CameraRenderRequest.md)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -6,12 +6,13 @@ void SetSampleCount(uint32_t count) override;
|
||||
|
||||
## 作用
|
||||
|
||||
为统一 RHI 接口预留采样数设置入口。
|
||||
为统一 RHI 接口预留 sample count 设置入口;`quality` 维度在 [SetSampleQuality](SetSampleQuality.md) 中单独暴露。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
- 空实现
|
||||
- `count` 当前不会被缓存,也不会驱动 `GL_MULTISAMPLE` 之外的任何行为
|
||||
- OpenGL 后端当前也不会在这里处理 sample quality
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -14,11 +14,12 @@ virtual void SetSampleCount(uint32_t count) = 0;
|
||||
|
||||
## 作用
|
||||
|
||||
纯虚接口。
|
||||
设置多采样的 sample count 维度;当前接口已经把 sample quality 拆到独立的 [SetSampleQuality](SetSampleQuality.md)。
|
||||
|
||||
## 当前实现
|
||||
|
||||
- 该声明是纯虚接口,基类不提供实现。
|
||||
- 该声明仍是纯虚接口,基类不提供实现。
|
||||
- 当前不要把它理解成“多采样配置的唯一入口”,因为 `quality` 已经是单独方法。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
49
docs/api/XCEngine/RHI/RHIPipelineState/SetSampleQuality.md
Normal file
49
docs/api/XCEngine/RHI/RHIPipelineState/SetSampleQuality.md
Normal 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)
|
||||
@@ -6,12 +6,13 @@ void SetSampleCount(uint32_t count) override;
|
||||
|
||||
## 作用
|
||||
|
||||
更新缓存的采样数。
|
||||
更新缓存的 sample count;`quality` 维度由 [SetSampleQuality](SetSampleQuality.md) 单独暴露。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
- `count > 0` 时保存原值
|
||||
- 否则回退为 `1`
|
||||
- Vulkan 后端当前不会在这里处理 sample quality
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -21,11 +21,14 @@
|
||||
## 当前公开入口
|
||||
|
||||
- [BuiltinPassContract](BuiltinPassContract/BuiltinPassContract.md)
|
||||
- [BuiltinMaterialPass](../RenderMaterialUtility/BuiltinMaterialPass.md)
|
||||
- [BuiltinPassResourceSemantic](../RenderMaterialUtility/BuiltinPassResourceSemantic.md)
|
||||
- [BuiltinPassResourceBindingPlan](../RenderMaterialUtility/BuiltinPassResourceBindingPlan.md)
|
||||
- [NormalizeBuiltinPassMetadataValue](../RenderMaterialUtility/NormalizeBuiltinPassMetadataValue.md)
|
||||
- [TryBuildBuiltinPassResourceBindingPlan](../RenderMaterialUtility/TryBuildBuiltinPassResourceBindingPlan.md)
|
||||
- [BuiltinPassTypes](BuiltinPassTypes/BuiltinPassTypes.md)
|
||||
- [BuiltinPassMetadataUtils](BuiltinPassMetadataUtils/BuiltinPassMetadataUtils.md)
|
||||
- [BuiltinPassLayoutUtils](BuiltinPassLayoutUtils/BuiltinPassLayoutUtils.md)
|
||||
|
||||
兼容说明:
|
||||
|
||||
- [RenderMaterialUtility](../RenderMaterialUtility/RenderMaterialUtility.md)
|
||||
保留旧专题入口,用来解释原先归在 `RenderMaterialUtility` 名下的契约页现在分别落在哪些真实头文件里。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -17,13 +17,15 @@
|
||||
- [RenderMaterialResolve](RenderMaterialResolve/RenderMaterialResolve.md)
|
||||
- [RenderMaterialStateUtils](RenderMaterialStateUtils/RenderMaterialStateUtils.md)
|
||||
- [RenderMaterialUtility](../RenderMaterialUtility/RenderMaterialUtility.md)
|
||||
兼容主题页,用来承接旧入口并指向新的 `Builtin/*` + `Materials/*` 分层。
|
||||
|
||||
## 当前职责
|
||||
|
||||
- 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 解析
|
||||
|
||||
## 相关文档
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,20 @@
|
||||
# BuiltinColorScalePostProcessPass::GetName
|
||||
|
||||
返回当前 pass 的固定名称。
|
||||
```cpp
|
||||
const char* GetName() const override;
|
||||
```
|
||||
|
||||
## 返回值
|
||||
|
||||
- 固定返回字符串 `BuiltinColorScalePostProcessPass`。
|
||||
|
||||
## 当前语义
|
||||
|
||||
- 返回值不依赖 `colorScale`、backend 或 shader 路径。
|
||||
- 它主要用于调试、日志和统一的 pass 标识。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [BuiltinColorScalePostProcessPass](BuiltinColorScalePostProcessPass.md)
|
||||
- [Execute](Execute.md)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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。
|
||||
|
||||
@@ -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()`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,20 @@
|
||||
# BuiltinFinalColorPass::GetName
|
||||
|
||||
返回当前 pass 的固定名称。
|
||||
```cpp
|
||||
const char* GetName() const override;
|
||||
```
|
||||
|
||||
## 返回值
|
||||
|
||||
- 固定返回字符串 `BuiltinFinalColorPass`。
|
||||
|
||||
## 当前语义
|
||||
|
||||
- 返回值不依赖 `FinalColorSettings`、backend 或 shader 路径。
|
||||
- 它主要用于调试、日志和统一的 pass 标识。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [BuiltinFinalColorPass](BuiltinFinalColorPass.md)
|
||||
- [Execute](Execute.md)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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 本身不做资源状态切换。
|
||||
- viewport 与 scissor 总是覆盖整张 surface,不读取 `surface.GetRenderArea()`。
|
||||
- 当前只写第一个颜色附件。
|
||||
- 当前路径不再把 `backendType == D3D12` 作为固定失败条件;真正是否能执行,取决于当前 backend 上是否能解析到兼容的 grid shader variant。
|
||||
- `EnsureInitialized(...)` 现在显式接收 `surface`,并按 backend、render target format、depth format 与 `sampleCount` 重新建资源。
|
||||
- viewport、scissor 和 grid 投影用到的宽高比都会遵循 `surface.GetRenderArea()`,而不是整张 surface 的尺寸。
|
||||
- 当前仍只写第一个颜色附件。
|
||||
|
||||
## 相关文档
|
||||
|
||||
@@ -42,3 +42,4 @@ bool Render(
|
||||
- [SetShaderPath](SetShaderPath.md)
|
||||
- [InfiniteGridPassData](InfiniteGridPassData.md)
|
||||
- [BuildInfiniteGridParameters](BuildInfiniteGridParameters.md)
|
||||
- [RenderSurface](../../RenderSurface/RenderSurface.md)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 状态。
|
||||
|
||||
## 关键实现细节
|
||||
|
||||
|
||||
@@ -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 状态。
|
||||
|
||||
## 返回值
|
||||
|
||||
|
||||
@@ -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 资源。 |
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -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 资源。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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 本身不管理整条阶段链。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -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 资源。 |
|
||||
|
||||
@@ -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。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -19,11 +19,22 @@ std::vector<CameraRenderRequest> BuildRequests(
|
||||
|
||||
这两个计数会传给 `ResolveClearFlags()`,用于推导 `Auto` clear mode 的最终清屏行为。
|
||||
|
||||
在基础 request 构建成功后,当前实现还会尝试补方向光阴影规划:
|
||||
|
||||
- 先判断这台相机当前是否应该进入自动方向光阴影路径。
|
||||
- 再按相机 `cullingMask` 查找主方向光,并要求该光源开启投影阴影。
|
||||
- 满足条件时,通过内部 `DirectionalShadowPlanning` helper 构建 `request.directionalShadow`。
|
||||
- 如果 plan 有效,还会同步写入:
|
||||
- `request.shadowCaster.clearFlags = RenderClearFlags::Depth`
|
||||
- `request.shadowCaster.hasCameraDataOverride = true`
|
||||
- `request.shadowCaster.cameraDataOverride = request.directionalShadow.cameraData`
|
||||
|
||||
## 当前过滤规则
|
||||
|
||||
- 如果某台相机最终生成的 request render area 宽高为 `0`,该请求会被跳过。
|
||||
- 只有真正成功加入结果数组的 base camera,才会推进 `renderedBaseCameraCount`。
|
||||
- 方法不会抛出失败原因;调用方只会拿到最终保留下来的请求数组。
|
||||
- 如果方向光阴影 plan 构建失败,主 request 仍然会保留,只是 `directionalShadow` 维持无效状态。
|
||||
|
||||
## 返回值
|
||||
|
||||
@@ -34,5 +45,6 @@ std::vector<CameraRenderRequest> BuildRequests(
|
||||
|
||||
- [SceneRenderRequestPlanner](SceneRenderRequestPlanner.md)
|
||||
- [CollectCameras](CollectCameras.md)
|
||||
- [CameraRenderRequest](../CameraRenderRequest/CameraRenderRequest.md)
|
||||
- [SceneRenderRequestUtils](../SceneRenderRequestUtils/SceneRenderRequestUtils.md)
|
||||
- [SceneRenderer::BuildRenderRequests](../../Execution/SceneRenderer/BuildRenderRequests.md)
|
||||
|
||||
@@ -20,10 +20,19 @@
|
||||
|
||||
这样 Editor、离屏渲染和手工 request 注入都能复用同一套规划规则,而不用直接碰 `CameraRenderer` 内部执行链。
|
||||
|
||||
## 公开方法
|
||||
## 公开类型与方法
|
||||
|
||||
当前头文件除了 planner 本体,还公开了:
|
||||
|
||||
- `DirectionalShadowPlanningSettings`
|
||||
负责控制自动方向光阴影规划的 map 尺寸、focus 距离、padding 和深度范围下限。
|
||||
|
||||
公开方法包括:
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `SetDirectionalShadowPlanningSettings(...)` | 写入一份会先经过 sanitize 的阴影规划参数。 |
|
||||
| `GetDirectionalShadowPlanningSettings()` | 返回当前生效的阴影规划参数。 |
|
||||
| [CollectCameras](CollectCameras.md) | 收集本次应参与渲染的相机列表。 |
|
||||
| [BuildRequests](BuildRequests.md) | 把相机列表展开成可执行的 `CameraRenderRequest` 数组。 |
|
||||
|
||||
@@ -48,6 +57,13 @@
|
||||
这两个计数会影响自动 clear flag 的推导。
|
||||
如果某个相机最终解析出的 render area 宽高为 `0`,该请求会被直接丢弃,而且不会错误地推进 base camera 计数。
|
||||
|
||||
如果当前相机满足方向光阴影规划条件,planner 还会:
|
||||
|
||||
- 查找当前 `cullingMask` 下可投影阴影的主方向光
|
||||
- 用 `DirectionalShadowPlanningSettings` 生成 `request.directionalShadow`
|
||||
- 在 plan 有效时,把 `shadowCaster.clearFlags` 固定为 `RenderClearFlags::Depth`
|
||||
- 同时把 `shadowCaster.hasCameraDataOverride` 和 `cameraDataOverride` 写成阴影相机数据
|
||||
|
||||
## 设计取舍
|
||||
|
||||
当前 planner 故意保持“只做一件事”:
|
||||
@@ -72,6 +88,7 @@
|
||||
- 它只规划相机请求,不处理 `objectId` 或各类 `RenderPassSequence` 的具体填充;那部分通常由更上层调用方补充。
|
||||
- 当前没有 camera stacking 的更复杂依赖分析,只有 base / overlay 与 depth 的线性排序。
|
||||
- `BuildRequests()` 只返回成功构建的请求,不单独报告被过滤掉的相机原因。
|
||||
- 自动方向光阴影规划目前只产出单张 shadow map 方案,不在这里处理级联阴影或多光源阴影编排。
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user