docs(editor): sync script assembly builder api docs
This commit is contained in:
@@ -8,65 +8,100 @@
|
|||||||
|
|
||||||
**描述**: 负责把 Editor 项目里的 C# 源码编译成 `Library/ScriptAssemblies` 下的脚本程序集。
|
**描述**: 负责把 Editor 项目里的 C# 源码编译成 `Library/ScriptAssemblies` 下的脚本程序集。
|
||||||
|
|
||||||
## 概述
|
## 角色概述
|
||||||
|
|
||||||
`EditorScriptAssemblyBuilder` 是当前 Editor 脚本工作流里真正执行“编译”的那一层。
|
`EditorScriptAssemblyBuilder` 是当前 Editor 托管脚本工作流里真正执行“编译”的那一层。它会把两组 C# 源码分别编成:
|
||||||
|
|
||||||
它会把两类源码分别编成:
|
|
||||||
|
|
||||||
- `XCEngine.ScriptCore.dll`
|
- `XCEngine.ScriptCore.dll`
|
||||||
- `GameScripts.dll`
|
- `GameScripts.dll`
|
||||||
|
|
||||||
并把结果统一落到:
|
并把输出统一落到:
|
||||||
|
|
||||||
- `<project>/Library/ScriptAssemblies/`
|
- `<project>/Library/ScriptAssemblies/`
|
||||||
|
|
||||||
|
## 为什么这层设计重要
|
||||||
|
|
||||||
|
编辑器脚本系统要稳定工作,关键不只是“能调用 Roslyn”,而是要把几件事情一起稳定下来:
|
||||||
|
|
||||||
|
- 仓库根目录如何解析
|
||||||
|
- Mono 运行时依赖从哪里找
|
||||||
|
- `dotnet` / Roslyn / `.NET Framework 4.7.2` 引用程序集如何就位
|
||||||
|
- 输出目录里的 `mscorlib.dll` 如何避免被活动 Mono runtime 锁住后反复覆盖
|
||||||
|
|
||||||
|
`EditorScriptAssemblyBuilder` 正是在做这层工程收口。
|
||||||
|
|
||||||
## 公开方法
|
## 公开方法
|
||||||
|
|
||||||
| 方法 | 说明 |
|
| 方法 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| [RebuildProjectAssemblies](RebuildProjectAssemblies.md) | 全量重建项目脚本程序集并返回结果消息。 |
|
| [RebuildProjectAssemblies](RebuildProjectAssemblies.md) | 全量重建项目脚本程序集并返回结果消息。 |
|
||||||
|
|
||||||
这是当前唯一入口。返回 `EditorScriptAssemblyBuildResult`:
|
当前这就是它唯一的公开入口。返回的 `EditorScriptAssemblyBuildResult` 包含:
|
||||||
|
|
||||||
- `succeeded`
|
- `succeeded`
|
||||||
- `message`
|
- `message`
|
||||||
|
|
||||||
## 当前重建流程
|
## 当前重建流程
|
||||||
|
|
||||||
按 `EditorScriptAssemblyBuilder.cpp` 当前实现,主流程大致是:
|
按 `EditorScriptAssemblyBuilder.cpp` 当前实现,主流程大致如下:
|
||||||
|
|
||||||
1. 校验项目路径非空。
|
1. 校验项目路径非空。
|
||||||
2. 解析仓库根与 Mono 根目录。
|
2. 解析仓库根目录。
|
||||||
3. 创建 `<project>/Library/ScriptAssemblies`。
|
3. 解析 Mono 根目录。
|
||||||
4. 在 `PATH` 上查找 `dotnet.exe`。
|
4. 创建 `<project>/Library/ScriptAssemblies`。
|
||||||
5. 运行 `dotnet --list-sdks`,取最后一条 SDK 版本。
|
5. 在 `PATH` 上查找 `dotnet.exe`。
|
||||||
6. 定位该 SDK 下的 `Roslyn/bincore/csc.dll`。
|
6. 运行 `dotnet --list-sdks`,取最后一条 SDK 版本。
|
||||||
7. 校验 `.NET Framework 4.7.2` 参考程序集存在。
|
7. 定位该 SDK 下的 `Roslyn/bincore/csc.dll`。
|
||||||
8. 收集 `managed/XCEngine.ScriptCore/**/*.cs`。
|
8. 校验 `.NET Framework 4.7.2` 引用程序集存在。
|
||||||
9. 收集 `<project>/Assets/**/*.cs`。
|
9. 收集 `managed/XCEngine.ScriptCore/**/*.cs`。
|
||||||
10. 若项目脚本为空,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件。
|
10. 收集 `<project>/Assets/**/*.cs`。
|
||||||
11. 先编译 `XCEngine.ScriptCore.dll`。
|
11. 若项目脚本为空,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件。
|
||||||
12. 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用旧文件,避免在增量重建时覆盖仍可能被 Mono 映射的 corlib。
|
12. 先编译 `XCEngine.ScriptCore.dll`。
|
||||||
13. 再引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.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` 会在重建脚本程序集时调用它。
|
- `Application.cpp` 会在编辑器触发脚本重建时调用它。
|
||||||
- `tests/Editor/test_editor_script_assembly_builder.cpp` 当前覆盖了两类关键语义:
|
- `tests/Editor/test_editor_script_assembly_builder.cpp` 当前覆盖了成功路径与“活动 Mono runtime 持有程序集导致重建失败”的锁语义。
|
||||||
- 成功路径:`XCEngine.ScriptCore.dll`、`GameScripts.dll` 与项目本地 `mscorlib.dll` 都会落到 `Library/ScriptAssemblies`
|
|
||||||
- 锁语义:如果活动 Mono runtime 仍持有已加载的 `GameScripts.dll`,直接重建会失败;释放 runtime 后再重建则可以成功并看到新增脚本类型
|
|
||||||
|
|
||||||
## 当前实现边界
|
## 当前实现边界
|
||||||
|
|
||||||
- 当前是全量重建,不是增量编译。
|
- 当前是全量重建,不是增量编译。
|
||||||
- 编译链明显依赖 Windows 路径和本机安装的 `dotnet` SDK。
|
- 编译链明显依赖 Windows、本机安装的 `dotnet` SDK 与 `.NET Framework 4.7.2` 引用程序集。
|
||||||
- 参考程序集路径当前写死在 `.NET Framework 4.7.2` 目录。
|
- 它会优先消费仓库内 bundled Mono,但不会主动管理 Mono runtime 生命周期。
|
||||||
- 构建器本身不会主动卸载当前脚本运行时;如果调用方没有先释放 Mono app domain,`GameScripts.dll` 仍可能因为文件锁而构建失败。
|
- 如果调用方没有先释放活动脚本运行时,`GameScripts.dll` 仍可能因文件锁而重建失败。
|
||||||
- 若外部环境缺少 `dotnet.exe`、Roslyn 或参考程序集,返回的只是一条失败消息,而不是更复杂的恢复策略。
|
|
||||||
|
|
||||||
## 相关文档
|
## 相关文档
|
||||||
|
|
||||||
- [Scripting](../Scripting.md)
|
- [Scripting](../Scripting.md)
|
||||||
|
- [RebuildProjectAssemblies](RebuildProjectAssemblies.md)
|
||||||
- [EditorScriptAssemblyBuilderUtils](../EditorScriptAssemblyBuilderUtils/EditorScriptAssemblyBuilderUtils.md)
|
- [EditorScriptAssemblyBuilderUtils](../EditorScriptAssemblyBuilderUtils/EditorScriptAssemblyBuilderUtils.md)
|
||||||
- [EditorScriptRuntimeStatus](../EditorScriptRuntimeStatus/EditorScriptRuntimeStatus.md)
|
- [EditorScriptRuntimeStatus](../EditorScriptRuntimeStatus/EditorScriptRuntimeStatus.md)
|
||||||
|
|||||||
@@ -14,53 +14,123 @@ static EditorScriptAssemblyBuildResult RebuildProjectAssemblies(const std::strin
|
|||||||
|
|
||||||
## 作用
|
## 作用
|
||||||
|
|
||||||
把指定项目下的托管脚本源码全量编译到 `Library/ScriptAssemblies` 目录,并返回成功/失败消息。
|
把指定项目下的托管脚本源码全量编译到 `Library/ScriptAssemblies` 目录,并返回成功/失败结果。
|
||||||
|
|
||||||
## 当前实现行为
|
## 当前实现流程
|
||||||
|
|
||||||
### 1. 解析路径与外部工具
|
### 1. 解析路径与基础环境
|
||||||
|
|
||||||
- 要求 `projectPath` 非空,否则直接返回失败结果。
|
- 要求 `projectPath` 非空,否则直接返回失败。
|
||||||
- 会解析:
|
- 解析 `projectRoot`、`repositoryRoot`、`monoRoot`、`managedRoot` 与输出目录。
|
||||||
- 仓库根目录
|
- 创建 `<project>/Library/ScriptAssemblies`。
|
||||||
- Mono 根目录
|
|
||||||
- `managed/XCEngine.ScriptCore`
|
|
||||||
- `<project>/Library/ScriptAssemblies`
|
|
||||||
- 当前依赖系统 `PATH` 上可找到 `dotnet.exe`,并通过 `dotnet --list-sdks` 解析最新 SDK 版本,再定位对应 `Roslyn/bincore/csc.dll`。
|
|
||||||
|
|
||||||
### 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`。
|
### 3. 解析 Mono 根目录
|
||||||
- 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用旧文件。
|
|
||||||
- 然后引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`。
|
|
||||||
- 成功时返回:
|
|
||||||
- `succeeded = true`
|
|
||||||
- `message = "Rebuilt script assemblies in ..."`
|
|
||||||
|
|
||||||
### 4. 失败语义
|
当前 Mono 根目录的真实查找顺序是:
|
||||||
|
|
||||||
- 任意路径校验、进程启动、编译失败、首次 `mscorlib.dll` 复制失败,或目标程序集仍被活动 Mono runtime 持有时,都会返回 `succeeded = false`。
|
1. 优先查找仓库内 bundled Mono:
|
||||||
- 这个函数本身不负责关闭现有脚本运行时;如果调用方在同一进程里仍持有已加载的 `GameScripts.dll`,重建可能因为文件锁失败。
|
- `<repositoryRoot>/engine/third_party/mono`
|
||||||
- 函数内部还包了一层 `try/catch`,用于把标准异常和未知异常转成失败消息。
|
- 若未命中,再扫描仓库一级子目录下的 `Fermion/Fermion/external/mono`
|
||||||
|
2. 若未找到 bundled Mono,则尝试 `XCENGINE_EDITOR_MONO_ROOT_DIR`
|
||||||
|
3. 若配置路径也无效,则回退到 `<repositoryRoot>/managed/mono`
|
||||||
|
|
||||||
## 当前实现边界
|
判定依据都是目标目录下是否存在 `binary/mscorlib.dll`。
|
||||||
|
|
||||||
- 当前是全量重建,不做增量分析。
|
这条顺序很关键,因为它决定了编辑器现在优先消费项目内置的 Mono 依赖,而不是先依赖开发机外部配置。
|
||||||
- 平台和工具链路径明显偏向 Windows + 本机安装 `.NET SDK`。
|
|
||||||
- 参考程序集版本当前固定为 `.NET Framework 4.7.2`。
|
### 4. 解析外部编译工具
|
||||||
- 输出目录里的 `mscorlib.dll` 当前采用“首次复制、后续复用”的策略,不会在每次重建时强制覆盖。
|
|
||||||
|
- 在 `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` 因重复覆盖而触发额外锁冲突
|
||||||
|
|
||||||
|
从商业级工具链视角看,这属于“先把本地开发和编辑器热重建跑稳,再逐步演进成更完整脚本构建系统”的典型路线。
|
||||||
|
|
||||||
## 相关文档
|
## 相关文档
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# API 文档重构状态
|
# API 文档重构状态
|
||||||
|
|
||||||
**生成时间**: `2026-04-10 18:41:38`
|
**生成时间**: `2026-04-12 22:45:25`
|
||||||
|
|
||||||
**来源**: `docs/api/_tools/audit_api_docs.py`
|
**来源**: `docs/api/_tools/audit_api_docs.py`
|
||||||
|
|
||||||
@@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
- Markdown 页面数(全部): `4006`
|
- Markdown 页面数(全部): `4006`
|
||||||
- Markdown 页面数(canonical): `3978`
|
- Markdown 页面数(canonical): `3978`
|
||||||
- Public headers 数: `384`
|
- Public headers 数: `393`
|
||||||
- `XCEditor` public headers 数: `64`(canonical 已覆盖 `64`)
|
- `XCEditor` public headers 数: `66`(canonical 已覆盖 `64`)
|
||||||
- `XCEngine` public headers 数: `320`(canonical 已覆盖 `320`)
|
- `XCEngine` public headers 数: `327`(canonical 已覆盖 `320`)
|
||||||
- Editor source headers 数: `144`
|
- Editor source headers 数: `145`
|
||||||
- 有效头文件引用数(全部): `384`
|
- 有效头文件引用数(全部): `384`
|
||||||
- 有效头文件引用数(canonical): `384`
|
- 有效头文件引用数(canonical): `384`
|
||||||
- 无效头文件引用数: `0`
|
- 无效头文件引用数: `0`
|
||||||
@@ -41,20 +41,20 @@
|
|||||||
|------|----------------|--------|--------|
|
|------|----------------|--------|--------|
|
||||||
| `XCEditor/Collections` | `9` | `9` | `0` |
|
| `XCEditor/Collections` | `9` | `9` | `0` |
|
||||||
| `XCEditor/Fields` | `23` | `23` | `0` |
|
| `XCEditor/Fields` | `23` | `23` | `0` |
|
||||||
| `XCEditor/Foundation` | `4` | `4` | `0` |
|
| `XCEditor/Foundation` | `6` | `4` | `2` |
|
||||||
| `XCEditor/Shell` | `24` | `24` | `0` |
|
| `XCEditor/Shell` | `24` | `24` | `0` |
|
||||||
| `XCEditor/Widgets` | `4` | `4` | `0` |
|
| `XCEditor/Widgets` | `4` | `4` | `0` |
|
||||||
| `XCEngine/Audio` | `11` | `11` | `0` |
|
| `XCEngine/Audio` | `11` | `11` | `0` |
|
||||||
| `XCEngine/Components` | `11` | `11` | `0` |
|
| `XCEngine/Components` | `12` | `11` | `1` |
|
||||||
| `XCEngine/Core` | `48` | `48` | `0` |
|
| `XCEngine/Core` | `49` | `48` | `1` |
|
||||||
| `XCEngine/Debug` | `10` | `10` | `0` |
|
| `XCEngine/Debug` | `10` | `10` | `0` |
|
||||||
| `XCEngine/Input` | `5` | `5` | `0` |
|
| `XCEngine/Input` | `5` | `5` | `0` |
|
||||||
| `XCEngine/Memory` | `5` | `5` | `0` |
|
| `XCEngine/Memory` | `5` | `5` | `0` |
|
||||||
| `XCEngine/Platform` | `11` | `11` | `0` |
|
| `XCEngine/Platform` | `11` | `11` | `0` |
|
||||||
| `XCEngine/RHI` | `88` | `88` | `0` |
|
| `XCEngine/RHI` | `88` | `88` | `0` |
|
||||||
| `XCEngine/Rendering` | `42` | `42` | `0` |
|
| `XCEngine/Rendering` | `44` | `42` | `2` |
|
||||||
| `XCEngine/Resources` | `29` | `29` | `0` |
|
| `XCEngine/Resources` | `31` | `29` | `2` |
|
||||||
| `XCEngine/Scene` | `4` | `4` | `0` |
|
| `XCEngine/Scene` | `5` | `4` | `1` |
|
||||||
| `XCEngine/Scripting` | `7` | `7` | `0` |
|
| `XCEngine/Scripting` | `7` | `7` | `0` |
|
||||||
| `XCEngine/Threading` | `10` | `10` | `0` |
|
| `XCEngine/Threading` | `10` | `10` | `0` |
|
||||||
| `XCEngine/UI` | `39` | `39` | `0` |
|
| `XCEngine/UI` | `39` | `39` | `0` |
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
| `Actions` | `9` | `9` | `0` |
|
| `Actions` | `9` | `9` | `0` |
|
||||||
| `Commands` | `4` | `4` | `0` |
|
| `Commands` | `4` | `4` | `0` |
|
||||||
| `ComponentEditors` | `11` | `11` | `0` |
|
| `ComponentEditors` | `11` | `11` | `0` |
|
||||||
| `Core` | `20` | `20` | `0` |
|
| `Core` | `21` | `20` | `1` |
|
||||||
| `Layers` | `1` | `1` | `0` |
|
| `Layers` | `1` | `1` | `0` |
|
||||||
| `Layout` | `1` | `1` | `0` |
|
| `Layout` | `1` | `1` | `0` |
|
||||||
| `Managers` | `3` | `3` | `0` |
|
| `Managers` | `3` | `3` | `0` |
|
||||||
@@ -88,3 +88,19 @@
|
|||||||
| `描述` | `618` |
|
| `描述` | `618` |
|
||||||
| `头文件` | `1975` |
|
| `头文件` | `1975` |
|
||||||
| `源文件` | `520` |
|
| `源文件` | `520` |
|
||||||
|
|
||||||
|
## 未覆盖的 public headers
|
||||||
|
|
||||||
|
- `XCEditor/Foundation/UIEditorRuntimeTrace.h`
|
||||||
|
- `XCEditor/Foundation/UIEditorTextMeasurement.h`
|
||||||
|
- `XCEngine/Components/GaussianSplatRendererComponent.h`
|
||||||
|
- `XCEngine/Core/Asset/ArtifactContainer.h`
|
||||||
|
- `XCEngine/Rendering/FrameData/VisibleGaussianSplatItem.h`
|
||||||
|
- `XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h`
|
||||||
|
- `XCEngine/Resources/Shader/ShaderCompilationCache.h`
|
||||||
|
- `XCEngine/Resources/Texture/TextureImportHeuristics.h`
|
||||||
|
- `XCEngine/Scene/ModelSceneInstantiation.h`
|
||||||
|
|
||||||
|
## 未覆盖的 Editor 源文件页
|
||||||
|
|
||||||
|
- `editor/src/Core/ProjectAssetWatcher.h`
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
# Library启动Bootstrap与SourceHash校验解耦修复计划
|
|
||||||
|
|
||||||
日期:2026-04-11
|
|
||||||
|
|
||||||
## 0. 计划定位
|
|
||||||
|
|
||||||
这份计划专门处理当前 `Library` 主线收口时暴露出来的新根因问题:
|
|
||||||
|
|
||||||
1. `SetResourceRoot()` 把 `BootstrapProjectAssets()` 接到了启动同步路径。
|
|
||||||
2. `BootstrapProjectAssets()` 进一步进入 `AssetDatabase::Refresh()`。
|
|
||||||
3. `Refresh()` 在扫描 `Assets/cloud.nvdb` 时同步计算 `sourceHash`。
|
|
||||||
4. `cloud.nvdb` 是超大源文件,导致 editor 启动直接卡在主线程。
|
|
||||||
|
|
||||||
本计划不回退单文件 artifact container,不推翻 `@entry=main`,也不回退现有 `Library` 架构。
|
|
||||||
|
|
||||||
## 1. 当前根因
|
|
||||||
|
|
||||||
已经确认的实际阻塞链路如下:
|
|
||||||
|
|
||||||
1. `ResourceManager::SetResourceRoot()`
|
|
||||||
2. `BootstrapProjectAssets()`
|
|
||||||
3. `AssetImportService::BootstrapProject()`
|
|
||||||
4. `AssetDatabase::Refresh()`
|
|
||||||
5. `EnsureMetaForPath(Assets/cloud.nvdb)`
|
|
||||||
6. `ComputeFileHash(cloud.nvdb)`
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
1. 慢的不是 `ArtifactContainer` 的 `offset / entry` 读取。
|
|
||||||
2. 慢的不是 `VolumeField` artifact payload 本身。
|
|
||||||
3. 真正把时间炸掉的是“启动阶段同步执行大源文件内容级校验”。
|
|
||||||
|
|
||||||
## 2. 修复目标
|
|
||||||
|
|
||||||
本计划的目标不是取消 `Library` 启动检查,而是把检查做对。
|
|
||||||
|
|
||||||
目标如下:
|
|
||||||
|
|
||||||
1. 打开项目时仍然会检查 `Library`。
|
|
||||||
2. 启动检查只做便宜元数据检查,不做大文件内容级哈希。
|
|
||||||
3. `Bootstrap` 与 `EnsureArtifact / Reimport` 的职责彻底拆开。
|
|
||||||
4. `sourceHash` 只在真正需要导入、重导入、显式全量重建时才计算。
|
|
||||||
5. `Volume`、`Shader`、`Model`、`Material` 在现有 container 架构下继续保持功能正确。
|
|
||||||
|
|
||||||
## 3. 核心原则
|
|
||||||
|
|
||||||
### 3.1 启动阶段只做便宜检查
|
|
||||||
|
|
||||||
启动阶段应该只检查:
|
|
||||||
|
|
||||||
1. `assets.db / artifacts.db` 是否存在、是否可读。
|
|
||||||
2. schema 是否匹配。
|
|
||||||
3. 源文件是否存在。
|
|
||||||
4. `fileSize / writeTime / importerVersion / metaHash` 是否变化。
|
|
||||||
5. 哪些资源只是 `DirtyCandidate`。
|
|
||||||
|
|
||||||
启动阶段不应该做:
|
|
||||||
|
|
||||||
1. 对每个源文件重新算 `sourceHash`。
|
|
||||||
2. 对所有资源同步 `ImportAsset`。
|
|
||||||
3. 对大资源执行内容级证明。
|
|
||||||
|
|
||||||
### 3.2 sourceHash 只服务导入正确性
|
|
||||||
|
|
||||||
`sourceHash` 的职责应该下沉到真正需要它的地方:
|
|
||||||
|
|
||||||
1. `EnsureArtifact()`
|
|
||||||
2. `ReimportAsset()`
|
|
||||||
3. `ReimportAllAssets()`
|
|
||||||
4. 用户显式 `RebuildProjectAssetCache()`
|
|
||||||
|
|
||||||
### 3.3 日常启动和冷启动重建要分开
|
|
||||||
|
|
||||||
语义上必须区分:
|
|
||||||
|
|
||||||
1. 日常启动:
|
|
||||||
- 快速检查
|
|
||||||
- 只找出可能脏的资源
|
|
||||||
- 不同步重导所有大资源
|
|
||||||
2. 删除 `Library` 后的首次冷启动:
|
|
||||||
- 允许重建索引和数据库
|
|
||||||
- 但依然不应该把所有超大源文件的内容校验都塞进主线程同步路径
|
|
||||||
|
|
||||||
## 4. 计划拆解
|
|
||||||
|
|
||||||
### 阶段A:冻结当前正确成果
|
|
||||||
|
|
||||||
目的:
|
|
||||||
|
|
||||||
1. 不回退 `ArtifactContainer`
|
|
||||||
2. 不回退 `@entry=main`
|
|
||||||
3. 不破坏 `ArtifactDB schema=2`
|
|
||||||
4. 不破坏 `runtimeLoadPath` 现有语义
|
|
||||||
|
|
||||||
交付标准:
|
|
||||||
|
|
||||||
1. 旧 `Library` 统一容器计划正式转入阶段归档。
|
|
||||||
2. 后续启动链路修复不影响现有 container 主线。
|
|
||||||
|
|
||||||
### 阶段B:拆开 Bootstrap 与导入职责
|
|
||||||
|
|
||||||
动作:
|
|
||||||
|
|
||||||
1. 调整 `AssetImportService::BootstrapProject()` 的语义。
|
|
||||||
2. 让 `AssetDatabase::Refresh()` 只负责 fast refresh。
|
|
||||||
3. 明确 `Refresh()` 不再承担内容级导入校验责任。
|
|
||||||
|
|
||||||
交付标准:
|
|
||||||
|
|
||||||
1. `SetResourceRoot()` 不再把大文件 `sourceHash` 强行拉进启动同步路径。
|
|
||||||
2. `assets.db` 仍能在启动时正确恢复。
|
|
||||||
|
|
||||||
### 阶段C:把脏判断改成元数据优先
|
|
||||||
|
|
||||||
动作:
|
|
||||||
|
|
||||||
1. 在 `EnsureMetaForPath()` 中优先使用:
|
|
||||||
- `sourceFileSize`
|
|
||||||
- `sourceWriteTime`
|
|
||||||
- `metaHash`
|
|
||||||
- `importerVersion`
|
|
||||||
2. 启动扫描阶段只更新这些便宜字段。
|
|
||||||
3. 明确区分“元数据变化”和“必须立刻重导”。
|
|
||||||
|
|
||||||
交付标准:
|
|
||||||
|
|
||||||
1. 打开项目时不会因为 `cloud.nvdb` 被同步全量哈希而长时间阻塞。
|
|
||||||
2. 资源脏状态仍可被发现。
|
|
||||||
|
|
||||||
### 阶段D:把 sourceHash 下沉到真正需要的路径
|
|
||||||
|
|
||||||
动作:
|
|
||||||
|
|
||||||
1. `EnsureArtifact()` 真正需要生成或验证 artifact 时,再决定是否补做 `sourceHash`。
|
|
||||||
2. `ReimportAllAssets()`、`RebuildProjectAssetCache()` 继续保留严格路径。
|
|
||||||
3. 对已有 artifact 且 `size/writeTime/meta/importerVersion` 未变化的资源,优先直接复用。
|
|
||||||
|
|
||||||
交付标准:
|
|
||||||
|
|
||||||
1. 日常启动快。
|
|
||||||
2. 显式重导仍然正确。
|
|
||||||
3. `VolumeFieldImporter` 不再在启动同步阶段把大文件成本炸出来。
|
|
||||||
|
|
||||||
### 阶段E:Volume 与 Editor 回归
|
|
||||||
|
|
||||||
动作:
|
|
||||||
|
|
||||||
1. 回归 `VolumeFieldLoader.AssetDatabaseCreatesVolumeArtifactAndReusesItWithoutReimport`
|
|
||||||
2. 回归 `VolumeFieldLoader.ResourceManagerLoadsVolumeByAssetRefFromProjectAssets`
|
|
||||||
3. 回归 editor 打开 `project` 的启动路径
|
|
||||||
4. 回归 `asset / ui / shader / gaussian_splat`
|
|
||||||
|
|
||||||
交付标准:
|
|
||||||
|
|
||||||
1. `project/Library` 删除后可重新生成。
|
|
||||||
2. editor 打开项目不再出现 30s 级主线程阻塞。
|
|
||||||
3. `cloud.nvdb` 在真正请求时仍能正确导入并渲染。
|
|
||||||
|
|
||||||
## 5. 风险控制
|
|
||||||
|
|
||||||
1. 不能为了消掉启动卡顿,直接把 `sourceHash` 整体删掉。
|
|
||||||
2. 不能回退当前 `Library` 单文件 container 主线。
|
|
||||||
3. 不能把 `Bootstrap` 改成完全不检查 `Library`,否则会把错误拖到运行时爆炸。
|
|
||||||
4. 必须特别盯住 `Volume`,因为它是最能放大启动语义错误的大资源类型。
|
|
||||||
|
|
||||||
## 6. 完成定义
|
|
||||||
|
|
||||||
当以下条件全部满足时,本计划收口:
|
|
||||||
|
|
||||||
1. 打开项目时 `SetResourceRoot()` 不再同步对 `cloud.nvdb` 做内容级哈希。
|
|
||||||
2. `project/Library` 删除后重新打开项目,数据库能够恢复。
|
|
||||||
3. `cloud.nvdb` 在真正被请求时仍能正确生成 volume artifact。
|
|
||||||
4. `Volume / Asset / UI / Shader / GaussianSplat` 关键回归全部通过。
|
|
||||||
5. 启动检查、按需导入、显式全量重建三种语义边界清晰稳定。
|
|
||||||
@@ -1,564 +0,0 @@
|
|||||||
# Rendering 通用 Shader 多 Pass 执行重构计划
|
|
||||||
|
|
||||||
日期: `2026-04-12`
|
|
||||||
|
|
||||||
## 1. 文档定位
|
|
||||||
|
|
||||||
这份计划用于正式解决当前渲染系统里的一个根问题:
|
|
||||||
|
|
||||||
- `Shader` 资源层已经支持定义多个 `Pass`
|
|
||||||
- `UsePass` 也已经能被解析和导入
|
|
||||||
- 但主场景 `BuiltinForwardPipeline` 还没有把“同一个材质的多个 surface pass 按顺序执行”做成正式运行时能力
|
|
||||||
|
|
||||||
这不是 Nahida 特例,也不是某一个卡通 shader 的临时问题,而是当前 `Rendering` 模块在通用材质执行模型上的结构性缺口。
|
|
||||||
|
|
||||||
本计划的目标不是给 Nahida 加一个专用补丁,而是把“Unity 式 shader 多 pass 执行”补成引擎正式能力。
|
|
||||||
|
|
||||||
## 2. 结论摘要
|
|
||||||
|
|
||||||
### 2.1 当前系统到底有没有多 pass
|
|
||||||
|
|
||||||
当前系统是“局部有多 pass,通用 surface 没有多 pass”。
|
|
||||||
|
|
||||||
- 有:
|
|
||||||
- `Shader` 资源对象可以持有多个 `Pass`
|
|
||||||
- `UsePass` 可用
|
|
||||||
- `DepthOnly` / `ShadowCaster` / `ObjectId` / `SelectionMask` 这类专用 pass 会按 pass type 单独解析
|
|
||||||
- `PostProcess` / `FinalOutput` 这类 fullscreen pass sequence 也支持多 pass 串联
|
|
||||||
- 没有:
|
|
||||||
- 主场景里“同一个材质的多个 graphics surface pass 自动执行”
|
|
||||||
- 当前 `BuiltinForwardPipeline` 只会为一个材质挑一个主 surface pass 来画
|
|
||||||
|
|
||||||
### 2.2 当前根因
|
|
||||||
|
|
||||||
根因不是 shader authoring 语法不支持多 pass,而是主场景 surface draw 路径还停留在“单 pass 材质模型”。
|
|
||||||
|
|
||||||
当前主路径的关键限制是:
|
|
||||||
|
|
||||||
- `TryResolveSurfacePassType()` 只认 `Unlit` / `ForwardLit`
|
|
||||||
- `ResolveSurfaceShaderPass()` 只返回一个 pass
|
|
||||||
- `DrawVisibleItems()` 对每个 `VisibleRenderItem` 只执行一次主 surface draw
|
|
||||||
|
|
||||||
所以:
|
|
||||||
|
|
||||||
- 你可以在 shader 里写 `ForwardLit + Outline`
|
|
||||||
- 资源也能读进来
|
|
||||||
- 但运行时不会自动再画第二遍 `Outline`
|
|
||||||
|
|
||||||
### 2.3 是否需要 Render Graph
|
|
||||||
|
|
||||||
这轮不需要 Render Graph,而且不应该先上 Render Graph。
|
|
||||||
|
|
||||||
原因很明确:
|
|
||||||
|
|
||||||
- 当前问题是“主场景通用材质的多 pass 调度缺失”
|
|
||||||
- 不是“跨资源依赖分析 / 瞬态资源分配 / 全帧拓扑求解”问题
|
|
||||||
- 现有架构已经有显式的 `RenderPipeline` + `RenderPassSequence`
|
|
||||||
- 这次只需要把 `BuiltinForwardPipeline` 从“单 surface pass 执行器”升级成“多 surface pass 执行器”
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
- 先不用 Render Graph
|
|
||||||
- 先把主场景通用 multipass 执行能力补齐
|
|
||||||
- 后续如果将来做更复杂的 frame dependency,再考虑 Render Graph
|
|
||||||
|
|
||||||
## 3. 当前现状拆解
|
|
||||||
|
|
||||||
## 3.1 Shader 资源层
|
|
||||||
|
|
||||||
当前 `Shader` 资源层已经具备以下能力:
|
|
||||||
|
|
||||||
- 一个 shader 可以拥有多个 `Pass`
|
|
||||||
- pass 有自己的:
|
|
||||||
- `name`
|
|
||||||
- `tags`
|
|
||||||
- `resources`
|
|
||||||
- `keywordDeclarations`
|
|
||||||
- `variants`
|
|
||||||
- `UsePass` 会在构建时导入引用 pass
|
|
||||||
|
|
||||||
这说明“shader 文件里写多个 pass”本身不是问题。
|
|
||||||
|
|
||||||
## 3.2 专用渲染 pass 层
|
|
||||||
|
|
||||||
当前系统已经有一些“按 pass type 单独拉取并执行”的路径:
|
|
||||||
|
|
||||||
- `DepthOnly`
|
|
||||||
- `ShadowCaster`
|
|
||||||
- `ObjectId`
|
|
||||||
- `SelectionMask`
|
|
||||||
- `Skybox`
|
|
||||||
- `GaussianSplat`
|
|
||||||
- `Volumetric`
|
|
||||||
|
|
||||||
它们说明当前引擎已经具备“识别 pass 元数据并挑一个对应 pass 执行”的机制,但这套机制目前没有扩展到主场景通用材质 surface 路径。
|
|
||||||
|
|
||||||
## 3.3 主场景 surface 路径
|
|
||||||
|
|
||||||
当前主场景 forward 渲染顺序是:
|
|
||||||
|
|
||||||
- `ExecuteForwardOpaquePass`
|
|
||||||
- `ExecuteForwardSkyboxPass`
|
|
||||||
- `BuiltinGaussianSplatPass`
|
|
||||||
- `BuiltinVolumetricPass`
|
|
||||||
- `ExecuteForwardTransparentPass`
|
|
||||||
|
|
||||||
但 `ExecuteForwardOpaquePass/TransparentPass` 内部依然是“每个物体只解析一个主 surface pass”的模型。
|
|
||||||
|
|
||||||
这意味着:
|
|
||||||
|
|
||||||
- 旧的单 pass lit/unlit 材质可以工作
|
|
||||||
- Unity 式 `Forward + Outline` 这类角色 shader 不能完整工作
|
|
||||||
|
|
||||||
## 3.4 当前缺口的精确定义
|
|
||||||
|
|
||||||
缺的不是“多 pass 文件格式”,而是下面这套正式能力:
|
|
||||||
|
|
||||||
1. 主场景 surface pass 的收集
|
|
||||||
2. 主场景 surface pass 的排序
|
|
||||||
3. 同一 `VisibleRenderItem` 的多次 graphics draw
|
|
||||||
4. 主 surface pass 与附加 surface pass 的阶段归属
|
|
||||||
5. 与现有 opaque / transparent / skybox / depth / shadow / objectId 的兼容
|
|
||||||
|
|
||||||
## 4. 本轮设计选择
|
|
||||||
|
|
||||||
## 4.1 选择的正式方案
|
|
||||||
|
|
||||||
本轮选择:
|
|
||||||
|
|
||||||
- 不做 Nahida 特判
|
|
||||||
- 不在某个 shader 上硬编码“再画一遍 outline”
|
|
||||||
- 不重写整个渲染框架为 Render Graph
|
|
||||||
- 直接把 `BuiltinForwardPipeline` 重构为“支持通用 surface multipass”
|
|
||||||
|
|
||||||
## 4.2 明确拒绝的方案
|
|
||||||
|
|
||||||
### 方案 A: Nahida / Toon 专用补丁
|
|
||||||
|
|
||||||
拒绝原因:
|
|
||||||
|
|
||||||
- 只解决一个案例
|
|
||||||
- 会把根因藏在角色特判里
|
|
||||||
- 后续别的 Unity shader 还是一样会坏
|
|
||||||
|
|
||||||
### 方案 B: 先全面 Render Graph 化
|
|
||||||
|
|
||||||
拒绝原因:
|
|
||||||
|
|
||||||
- 工作量过大
|
|
||||||
- 与当前问题不对焦
|
|
||||||
- 会把原本中等规模的结构重构,升级成高风险基础设施重写
|
|
||||||
|
|
||||||
### 方案 C: 继续维持单 surface pass,只在 shader 层绕
|
|
||||||
|
|
||||||
拒绝原因:
|
|
||||||
|
|
||||||
- 不能从根上支持 `ForwardLit + Outline`
|
|
||||||
- 和 Unity 式多 pass 材质模型不一致
|
|
||||||
|
|
||||||
## 5. 目标架构
|
|
||||||
|
|
||||||
## 5.1 目标状态
|
|
||||||
|
|
||||||
主场景渲染的目标状态是:
|
|
||||||
|
|
||||||
- 一个材质可以在 shader 内声明多个 surface pass
|
|
||||||
- 渲染时会先收集这些 pass
|
|
||||||
- 再按引擎定义好的 scene phase 顺序执行
|
|
||||||
- 同一个 mesh / material 在一帧里可以被绘制多次
|
|
||||||
|
|
||||||
最小闭环至少要支持:
|
|
||||||
|
|
||||||
- `Unlit`
|
|
||||||
- `ForwardLit`
|
|
||||||
- `Outline`
|
|
||||||
|
|
||||||
并为后续保留扩展点:
|
|
||||||
|
|
||||||
- 更多角色附加 pass
|
|
||||||
- 深度依赖的 rim pass
|
|
||||||
- 特殊透明角色 pass
|
|
||||||
|
|
||||||
## 5.2 推荐的主场景 surface 阶段模型
|
|
||||||
|
|
||||||
本轮建议把主场景通用 surface pass 明确拆成以下阶段:
|
|
||||||
|
|
||||||
1. `OpaqueBase`
|
|
||||||
2. `Skybox`
|
|
||||||
3. `OpaqueAuxiliary`
|
|
||||||
4. `TransparentBase`
|
|
||||||
5. `TransparentAuxiliary`
|
|
||||||
|
|
||||||
其中:
|
|
||||||
|
|
||||||
- `Unlit` / `ForwardLit` 默认属于 `Base`
|
|
||||||
- `Outline` 默认属于 `Auxiliary`
|
|
||||||
- 本轮重点落地 `OpaqueBase + OpaqueAuxiliary`
|
|
||||||
|
|
||||||
这么拆的原因是:
|
|
||||||
|
|
||||||
- `Outline` 一般要在角色主表面之后绘制
|
|
||||||
- 又通常希望在透明物体之前完成
|
|
||||||
- 这和当前 forward pipeline 的大框架可以自然兼容
|
|
||||||
|
|
||||||
## 5.3 推荐的 pass 类型模型
|
|
||||||
|
|
||||||
当前 `BuiltinMaterialPass` 需要正式扩展,而不是继续只停留在:
|
|
||||||
|
|
||||||
- `ForwardLit`
|
|
||||||
- `Unlit`
|
|
||||||
|
|
||||||
建议新增:
|
|
||||||
|
|
||||||
- `Outline`
|
|
||||||
|
|
||||||
并在后续根据需要继续扩展。
|
|
||||||
|
|
||||||
这里的重点不是“枚举值多一个”本身,而是:
|
|
||||||
|
|
||||||
- 主场景 surface 路径终于承认“一个材质有多个可执行的主 graphics pass”
|
|
||||||
- 不再把 `Outline` 当作特殊插件逻辑,而是当作正式 pass type
|
|
||||||
|
|
||||||
## 6. 核心改造点
|
|
||||||
|
|
||||||
## 6.1 Pass 元数据层
|
|
||||||
|
|
||||||
涉及模块:
|
|
||||||
|
|
||||||
- `engine/include/XCEngine/Rendering/Builtin/BuiltinPassTypes.h`
|
|
||||||
- `engine/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h`
|
|
||||||
- `engine/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h`
|
|
||||||
|
|
||||||
任务:
|
|
||||||
|
|
||||||
- 新增 `BuiltinMaterialPass::Outline`
|
|
||||||
- 增加 `Outline` 的 canonical name 解析
|
|
||||||
- 为 `Outline` 建立默认资源绑定规则
|
|
||||||
- 保持现有 `ForwardLit / Unlit / DepthOnly / ShadowCaster / ObjectId ...` 兼容
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
- shader 中 `Name "Outline"` 或 `Tags { "LightMode" = "Outline" }` 能稳定识别
|
|
||||||
- 不影响现有 builtin pass 匹配行为
|
|
||||||
|
|
||||||
## 6.2 主场景 surface pass 收集层
|
|
||||||
|
|
||||||
涉及模块:
|
|
||||||
|
|
||||||
- `engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h`
|
|
||||||
- `engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp`
|
|
||||||
|
|
||||||
当前问题:
|
|
||||||
|
|
||||||
- `ResolveSurfaceShaderPass()` 是单返回值模型
|
|
||||||
|
|
||||||
本轮需要改成:
|
|
||||||
|
|
||||||
- `CollectSurfaceShaderPasses()` 或等价结构
|
|
||||||
- 返回一组“已解析的 surface pass 列表”
|
|
||||||
|
|
||||||
每个条目至少包含:
|
|
||||||
|
|
||||||
- `passType`
|
|
||||||
- `shader`
|
|
||||||
- `shaderPass`
|
|
||||||
- `passName`
|
|
||||||
- `scenePhase`
|
|
||||||
- `effectiveRenderState`
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
- 同一材质可同时解析出 `ForwardLit + Outline`
|
|
||||||
- 旧的单 pass 材质仍只解析出一个条目
|
|
||||||
|
|
||||||
## 6.3 主场景 surface pass 排序与阶段归属
|
|
||||||
|
|
||||||
涉及模块:
|
|
||||||
|
|
||||||
- `BuiltinForwardPipeline.cpp`
|
|
||||||
- `BuiltinForwardPipelineResources.cpp`
|
|
||||||
|
|
||||||
任务:
|
|
||||||
|
|
||||||
- 不再只按 `opaque/transparent` 二元划分来理解主 surface
|
|
||||||
- 要引入“主场景 surface 子阶段”的概念
|
|
||||||
- 至少实现:
|
|
||||||
- opaque base
|
|
||||||
- opaque auxiliary
|
|
||||||
- transparent base
|
|
||||||
|
|
||||||
本轮建议的执行顺序:
|
|
||||||
|
|
||||||
1. `OpaqueBase`
|
|
||||||
2. `Skybox`
|
|
||||||
3. `OpaqueAuxiliary`
|
|
||||||
4. `GaussianSplat`
|
|
||||||
5. `Volumetric`
|
|
||||||
6. `TransparentBase`
|
|
||||||
7. `TransparentAuxiliary`
|
|
||||||
|
|
||||||
说明:
|
|
||||||
|
|
||||||
- `Outline` 先放在 `OpaqueAuxiliary`
|
|
||||||
- 本轮先不支持“透明物体 outline”的复杂排序
|
|
||||||
- 透明附加 pass 只保留接口,不要求首轮全部打通
|
|
||||||
|
|
||||||
## 6.4 Draw 级执行模型
|
|
||||||
|
|
||||||
当前问题:
|
|
||||||
|
|
||||||
- `DrawVisibleItems()` 对每个 item 只会 draw 一次主 surface
|
|
||||||
|
|
||||||
重构目标:
|
|
||||||
|
|
||||||
- 对每个 `VisibleRenderItem`,先收集其可执行 surface pass
|
|
||||||
- 再按当前 scene phase 过滤
|
|
||||||
- 每个 pass 单独:
|
|
||||||
- 解析 pipeline state
|
|
||||||
- 绑定 descriptor sets
|
|
||||||
- 执行 draw
|
|
||||||
|
|
||||||
这一步是整个重构的真正核心。
|
|
||||||
|
|
||||||
验收:
|
|
||||||
|
|
||||||
- 一个物体在同一帧可发生多次主场景 draw
|
|
||||||
- 每次 draw 都有独立 `passName + renderState`
|
|
||||||
- pipeline cache 仍然以 `shader + passName + renderState + keywordSignature + surface format` 为 key 稳定工作
|
|
||||||
|
|
||||||
## 6.5 资源绑定与 layout 缓存
|
|
||||||
|
|
||||||
当前系统在这方面基础是够的,因为:
|
|
||||||
|
|
||||||
- `PassLayoutKey` 已经包含 `shader + passName`
|
|
||||||
- `PipelineStateKey` 已经包含 `passName`
|
|
||||||
|
|
||||||
所以本轮不需要重写 cache 架构,只需要保证:
|
|
||||||
|
|
||||||
- 多 pass 场景下 cache key 继续区分不同 pass
|
|
||||||
- `Outline` 这种 pass 的资源绑定计划能正确建立
|
|
||||||
- `MaterialTexture` / `MaterialConstants` / `PerObject` 等绑定仍走现有机制
|
|
||||||
|
|
||||||
重点检查:
|
|
||||||
|
|
||||||
- `Outline` 是否需要 `Lighting`
|
|
||||||
- `Outline` 是否需要 `ShadowReceiver`
|
|
||||||
- `Outline` 是否只需 `PerObject + Material + MaterialTextures + Sampler`
|
|
||||||
|
|
||||||
## 6.6 Shader authoring 约束正式化
|
|
||||||
|
|
||||||
这轮需要把“主场景 surface multipass”的 authoring 约束写清楚,而不是默认靠猜。
|
|
||||||
|
|
||||||
建议明确约定:
|
|
||||||
|
|
||||||
- 主场景可执行的 surface pass 必须有明确 `Name` 或 `LightMode`
|
|
||||||
- pass 名称与 builtin canonical name 的映射规则固定
|
|
||||||
- `ForwardLit` / `Unlit` / `Outline` 属于主场景通用 surface pass
|
|
||||||
- `DepthOnly` / `ShadowCaster` / `ObjectId` / `SelectionMask` 继续属于专用路径
|
|
||||||
|
|
||||||
这样做的价值是:
|
|
||||||
|
|
||||||
- shader authoring 规则清晰
|
|
||||||
- 不会再出现“写了 pass 但没人知道该在哪个阶段执行”
|
|
||||||
|
|
||||||
## 6.7 XCCharacterToon.shader 的正式接入方式
|
|
||||||
|
|
||||||
在 multipass 正式能力完成后,`XCCharacterToon.shader` 的正确接法应为:
|
|
||||||
|
|
||||||
- `ForwardLit` 负责角色主表面
|
|
||||||
- `Outline` 负责描边
|
|
||||||
- `DepthOnly` / `ShadowCaster` 继续沿用已有专用 pass
|
|
||||||
|
|
||||||
本轮对 Nahida 的定位是:
|
|
||||||
|
|
||||||
- 不再作为特判对象
|
|
||||||
- 只作为 multipass 正式化后的第一个高价值验证样本
|
|
||||||
|
|
||||||
## 7. 分阶段执行计划
|
|
||||||
|
|
||||||
## Phase 0: 基线确认与测试样本准备
|
|
||||||
|
|
||||||
### 目标
|
|
||||||
|
|
||||||
在改主路径前固定当前行为,防止重构期间把旧材质全带坏。
|
|
||||||
|
|
||||||
### 任务
|
|
||||||
|
|
||||||
- 盘点当前所有依赖 `ForwardLit / Unlit` 的单 pass 集成测试
|
|
||||||
- 新建一个最小 multipass 测试场景:
|
|
||||||
- 一个简单 mesh
|
|
||||||
- 一个 `ForwardLit + Outline` 测试 shader
|
|
||||||
- 明确 Nahida 作为高复杂度回归样本,不作为最小开发起点
|
|
||||||
|
|
||||||
### 完成标准
|
|
||||||
|
|
||||||
- 有一个简单到足以定位多 pass 执行问题的专门测试场景
|
|
||||||
- 现有 lit/unlit 场景回归基线不丢
|
|
||||||
|
|
||||||
## Phase 1: 主场景通用 surface multipass 基础设施
|
|
||||||
|
|
||||||
### 目标
|
|
||||||
|
|
||||||
让 `BuiltinForwardPipeline` 具备“一个物体可执行多个主 surface pass”的正式能力。
|
|
||||||
|
|
||||||
### 任务
|
|
||||||
|
|
||||||
- 扩展 `BuiltinMaterialPass`
|
|
||||||
- 新增 `Outline` pass type
|
|
||||||
- 单 pass 解析模型改成 multi-pass collection 模型
|
|
||||||
- 引入主场景 surface phase
|
|
||||||
- 重写 `DrawVisibleItems()` 执行逻辑
|
|
||||||
|
|
||||||
### 完成标准
|
|
||||||
|
|
||||||
- 最小 multipass 测试 shader 能完成两次 draw
|
|
||||||
- 单 pass shader 行为不回归
|
|
||||||
|
|
||||||
## Phase 2: Outline 正式落地
|
|
||||||
|
|
||||||
### 目标
|
|
||||||
|
|
||||||
让 `Outline` 成为主场景正式 pass,而不是外置补丁。
|
|
||||||
|
|
||||||
### 任务
|
|
||||||
|
|
||||||
- 为 `Outline` 补齐 builtin metadata / layout / binding 规则
|
|
||||||
- 在 `XCCharacterToon.shader` 中加入正式 `Outline` pass
|
|
||||||
- 验证 `Cull Front / ZTest / ZWrite / Blend` 等状态是否符合需求
|
|
||||||
- 首轮先以 static mesh + vertex color alpha 宽度控制闭环
|
|
||||||
|
|
||||||
### 明确暂缓
|
|
||||||
|
|
||||||
- `smoothNormal` 新顶点语义支持
|
|
||||||
- skinned mesh outline
|
|
||||||
- 透明角色 outline 排序
|
|
||||||
|
|
||||||
### 完成标准
|
|
||||||
|
|
||||||
- 最小 multipass 测试场景通过
|
|
||||||
- Nahida 在 `original` 模式里开始出现正确的独立 outline draw
|
|
||||||
|
|
||||||
## Phase 3: Nahida / Unity 风格角色卡通验证
|
|
||||||
|
|
||||||
### 目标
|
|
||||||
|
|
||||||
把 multipass 正式能力用于 Nahida,验证这套方案确实能支撑 Unity 风格角色 shader。
|
|
||||||
|
|
||||||
### 任务
|
|
||||||
|
|
||||||
- 将 `XCCharacterToon.shader` 的 `Outline` 接入正式主场景 multipass
|
|
||||||
- 重新生成 `nahida.png`
|
|
||||||
- 对比 `unlit`、`forward lit`、`original` 三种模式的画面差异
|
|
||||||
- 评估是否可以锁定新的 `GT.ppm`
|
|
||||||
|
|
||||||
### 完成标准
|
|
||||||
|
|
||||||
- Nahida 的描边不再依赖临时逻辑
|
|
||||||
- `original` 渲染链路进入可持续迭代状态
|
|
||||||
|
|
||||||
## Phase 4: 通用化与规则收口
|
|
||||||
|
|
||||||
### 目标
|
|
||||||
|
|
||||||
把这次重构从“够 Nahida 用”收口成“引擎正式通用能力”。
|
|
||||||
|
|
||||||
### 任务
|
|
||||||
|
|
||||||
- 补文档,明确 shader multipass authoring 规范
|
|
||||||
- 视情况支持更多主场景 surface pass type
|
|
||||||
- 清理旧的单 pass 假设与命名
|
|
||||||
- 审查编辑器 / 材质检查器 / shader 资源导入链路是否需要显示 pass 信息
|
|
||||||
|
|
||||||
### 完成标准
|
|
||||||
|
|
||||||
- Multipass 不再是隐式能力
|
|
||||||
- 规则、测试、运行时行为三者一致
|
|
||||||
|
|
||||||
## 8. 测试计划
|
|
||||||
|
|
||||||
## 8.1 单元测试
|
|
||||||
|
|
||||||
重点新增或补强:
|
|
||||||
|
|
||||||
- `BuiltinPassMetadataUtils`
|
|
||||||
- `Outline` canonical name 匹配
|
|
||||||
- `BuiltinPassLayoutUtils`
|
|
||||||
- `Outline` 资源绑定计划
|
|
||||||
- `BuiltinForwardPipeline`
|
|
||||||
- 单材质多 surface pass 收集
|
|
||||||
- scene phase 排序
|
|
||||||
- 单 pass 回归不变
|
|
||||||
|
|
||||||
## 8.2 集成测试
|
|
||||||
|
|
||||||
建议新增:
|
|
||||||
|
|
||||||
- `tests/Rendering/integration/multipass_outline_scene`
|
|
||||||
- 最小 multipass 样例
|
|
||||||
- 继续保留:
|
|
||||||
- `nahida_preview_scene`
|
|
||||||
- 现有 lit/unlit/backpack/material_state 等基础场景
|
|
||||||
|
|
||||||
## 8.3 人工验收
|
|
||||||
|
|
||||||
人工验收重点不只是“有没有画出来”,而是:
|
|
||||||
|
|
||||||
- 是否真的发生了两次 draw
|
|
||||||
- state / cull / depth 是否正确
|
|
||||||
- 单 pass 材质是否回归
|
|
||||||
- Nahida 的 outline 是否来自正式 pass,而不是额外补丁
|
|
||||||
|
|
||||||
## 9. 风险与控制
|
|
||||||
|
|
||||||
## 9.1 最大风险
|
|
||||||
|
|
||||||
最大风险不是代码量,而是“把旧的单 pass 假设改坏”。
|
|
||||||
|
|
||||||
具体风险包括:
|
|
||||||
|
|
||||||
- 单 pass lit/unlit 材质回归
|
|
||||||
- opaque / transparent 分类被打乱
|
|
||||||
- pipeline cache 或 descriptor set 复用逻辑出错
|
|
||||||
- 新增 `Outline` 后错误进入 shadow/depth/objectId 路径
|
|
||||||
|
|
||||||
## 9.2 风险控制策略
|
|
||||||
|
|
||||||
- 先做最小 multipass 场景,不直接拿 Nahida 起步
|
|
||||||
- 先只开放 `Outline` 这一种主场景 auxiliary pass
|
|
||||||
- 暂缓透明 multipass 与 depth-driven rim
|
|
||||||
- 每个阶段都跑现有 forward 基础集成测试
|
|
||||||
|
|
||||||
## 10. 本轮不做的内容
|
|
||||||
|
|
||||||
本计划明确不把以下内容混进首轮 multipass 重构:
|
|
||||||
|
|
||||||
- Render Graph 化
|
|
||||||
- SkinnedMesh / 骨骼动画
|
|
||||||
- GPU skinning
|
|
||||||
- Transparent multipass 完整排序体系
|
|
||||||
- Scene depth texture 的通用相机绑定
|
|
||||||
- Unity 全量角色 shader 语义一次性补齐
|
|
||||||
|
|
||||||
这些都不是当前根因的第一优先级。
|
|
||||||
|
|
||||||
## 11. 完成判定
|
|
||||||
|
|
||||||
当满足以下条件时,才算这次“通用 shader 多 pass 执行重构”完成:
|
|
||||||
|
|
||||||
1. 主场景 surface 路径正式支持一个材质执行多个 pass
|
|
||||||
2. `Outline` 成为正式 builtin surface pass
|
|
||||||
3. 现有单 pass 材质与基础场景不回归
|
|
||||||
4. Nahida 的描边来自正式 multipass 执行,而不是特判
|
|
||||||
5. 文档、测试、实现三者一致
|
|
||||||
|
|
||||||
## 12. 下一步建议
|
|
||||||
|
|
||||||
这份计划写完后的下一步,不是直接去碰 Nahida 复杂 shader 细节,而是:
|
|
||||||
|
|
||||||
1. 先做 `Phase 0`
|
|
||||||
2. 新建最小 multipass outline 场景与测试 shader
|
|
||||||
3. 再开始 `BuiltinForwardPipeline` 的 multipass 基础设施改造
|
|
||||||
|
|
||||||
顺序不能反。
|
|
||||||
|
|
||||||
如果一上来就直接拿 Nahida 开刀,很容易把“结构性问题”和“角色 shader 细节问题”混在一起,最后继续变成补丁式推进。
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
Shader "Builtin Gaussian Splat Composite"
|
|
||||||
{
|
|
||||||
SubShader
|
|
||||||
{
|
|
||||||
Pass
|
|
||||||
{
|
|
||||||
Name "GaussianComposite"
|
|
||||||
Tags { "LightMode" = "GaussianComposite" }
|
|
||||||
Cull Off
|
|
||||||
ZWrite Off
|
|
||||||
ZTest Always
|
|
||||||
Blend One OneMinusSrcAlpha
|
|
||||||
HLSLPROGRAM
|
|
||||||
#pragma target 4.5
|
|
||||||
#pragma vertex MainVS
|
|
||||||
#pragma fragment MainPS
|
|
||||||
|
|
||||||
Texture2D GaussianSplatAccumulationTexture;
|
|
||||||
|
|
||||||
struct VSOutput
|
|
||||||
{
|
|
||||||
float4 position : SV_POSITION;
|
|
||||||
};
|
|
||||||
|
|
||||||
VSOutput MainVS(uint vertexId : SV_VertexID)
|
|
||||||
{
|
|
||||||
const float2 positions[3] = {
|
|
||||||
float2(-1.0f, -1.0f),
|
|
||||||
float2(-1.0f, 3.0f),
|
|
||||||
float2( 3.0f, -1.0f)
|
|
||||||
};
|
|
||||||
|
|
||||||
VSOutput output;
|
|
||||||
output.position = float4(positions[vertexId], 1.0f, 1.0f);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 MainPS(VSOutput input) : SV_TARGET
|
|
||||||
{
|
|
||||||
const int2 pixelCoord = int2(input.position.xy);
|
|
||||||
return GaussianSplatAccumulationTexture.Load(int3(pixelCoord, 0));
|
|
||||||
}
|
|
||||||
ENDHLSL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -229,21 +229,21 @@ Shader "Builtin Gaussian Splat Utilities"
|
|||||||
}
|
}
|
||||||
|
|
||||||
float3 CalcCovariance2D(
|
float3 CalcCovariance2D(
|
||||||
float3 localPosition,
|
float3 viewPosition,
|
||||||
float3 covariance3D0,
|
float3 covariance3D0,
|
||||||
float3 covariance3D1,
|
float3 covariance3D1,
|
||||||
float4x4 modelViewMatrix,
|
float4x4 viewMatrix,
|
||||||
float4x4 projectionMatrix,
|
float4x4 projectionMatrix,
|
||||||
float2 screenSize)
|
float2 screenSize)
|
||||||
{
|
{
|
||||||
const float3 viewPosition = mul(modelViewMatrix, float4(localPosition, 1.0)).xyz;
|
|
||||||
if (abs(viewPosition.z) <= 1.0e-5)
|
if (abs(viewPosition.z) <= 1.0e-5)
|
||||||
{
|
{
|
||||||
return float3(0.3, 0.0, 0.3);
|
return float3(0.3, 0.0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const float aspect = projectionMatrix[0][0] / projectionMatrix[1][1];
|
||||||
const float tanFovX = rcp(projectionMatrix[0][0]);
|
const float tanFovX = rcp(projectionMatrix[0][0]);
|
||||||
const float tanFovY = rcp(projectionMatrix[1][1]);
|
const float tanFovY = rcp(projectionMatrix[1][1] * aspect);
|
||||||
const float limitX = 1.3 * tanFovX;
|
const float limitX = 1.3 * tanFovX;
|
||||||
const float limitY = 1.3 * tanFovY;
|
const float limitY = 1.3 * tanFovY;
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ Shader "Builtin Gaussian Splat Utilities"
|
|||||||
0.0,
|
0.0,
|
||||||
0.0,
|
0.0,
|
||||||
0.0);
|
0.0);
|
||||||
const float3x3 worldToView = (float3x3)modelViewMatrix;
|
const float3x3 worldToView = (float3x3)viewMatrix;
|
||||||
const float3x3 transform = mul(jacobian, worldToView);
|
const float3x3 transform = mul(jacobian, worldToView);
|
||||||
const float3x3 covariance3D = float3x3(
|
const float3x3 covariance3D = float3x3(
|
||||||
covariance3D0.x, covariance3D0.y, covariance3D0.z,
|
covariance3D0.x, covariance3D0.y, covariance3D0.z,
|
||||||
@@ -284,7 +284,7 @@ Shader "Builtin Gaussian Splat Utilities"
|
|||||||
const float offDiagonal = covariance2D.y;
|
const float offDiagonal = covariance2D.y;
|
||||||
const float mid = 0.5 * (diagonal0 + diagonal1);
|
const float mid = 0.5 * (diagonal0 + diagonal1);
|
||||||
const float radius = length(float2((diagonal0 - diagonal1) * 0.5, offDiagonal));
|
const float radius = length(float2((diagonal0 - diagonal1) * 0.5, offDiagonal));
|
||||||
const float lambda0 = mid + radius;
|
const float lambda0 = max(mid + radius, 0.1);
|
||||||
const float lambda1 = max(mid - radius, 0.1);
|
const float lambda1 = max(mid - radius, 0.1);
|
||||||
|
|
||||||
float2 basis = normalize(float2(offDiagonal, lambda0 - diagonal0));
|
float2 basis = normalize(float2(offDiagonal, lambda0 - diagonal0));
|
||||||
@@ -425,22 +425,22 @@ Shader "Builtin Gaussian Splat Utilities"
|
|||||||
GaussianSplatSortDistances[index] = FloatToSortableUint(viewCenter.z);
|
GaussianSplatSortDistances[index] = FloatToSortableUint(viewCenter.z);
|
||||||
|
|
||||||
const float4 clipCenter = mul(gProjectionMatrix, float4(viewCenter, 1.0));
|
const float4 clipCenter = mul(gProjectionMatrix, float4(viewCenter, 1.0));
|
||||||
const float nearClip = max(gCameraWorldPos.w, 1.0e-4);
|
if (clipCenter.w > 0.0)
|
||||||
if (clipCenter.w > 0.0 && viewCenter.z > nearClip)
|
|
||||||
{
|
{
|
||||||
const float4x4 modelViewMatrix = mul(gViewMatrix, gModelMatrix);
|
const float3x3 modelLinear = (float3x3)gModelMatrix;
|
||||||
const float3x3 rotationScaleMatrix =
|
const float3x3 rotationScaleMatrix =
|
||||||
CalcMatrixFromRotationScale(otherData.rotation, otherData.scaleReserved.xyz);
|
CalcMatrixFromRotationScale(otherData.rotation, otherData.scaleReserved.xyz);
|
||||||
|
const float3x3 worldRotationScale = mul(modelLinear, rotationScaleMatrix);
|
||||||
|
|
||||||
float3 covariance3D0 = 0.0;
|
float3 covariance3D0 = 0.0;
|
||||||
float3 covariance3D1 = 0.0;
|
float3 covariance3D1 = 0.0;
|
||||||
CalcCovariance3D(rotationScaleMatrix, covariance3D0, covariance3D1);
|
CalcCovariance3D(worldRotationScale, covariance3D0, covariance3D1);
|
||||||
|
|
||||||
const float3 covariance2D = CalcCovariance2D(
|
const float3 covariance2D = CalcCovariance2D(
|
||||||
localCenter,
|
viewCenter,
|
||||||
covariance3D0,
|
covariance3D0,
|
||||||
covariance3D1,
|
covariance3D1,
|
||||||
modelViewMatrix,
|
gViewMatrix,
|
||||||
gProjectionMatrix,
|
gProjectionMatrix,
|
||||||
gScreenParams.xy);
|
gScreenParams.xy);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ Shader "Builtin Gaussian Splat"
|
|||||||
{
|
{
|
||||||
_PointScale ("Point Scale", Float) = 1.0
|
_PointScale ("Point Scale", Float) = 1.0
|
||||||
_OpacityScale ("Opacity Scale", Float) = 1.0
|
_OpacityScale ("Opacity Scale", Float) = 1.0
|
||||||
_DebugViewMode ("Debug View Mode", Float) = 0.0
|
|
||||||
}
|
}
|
||||||
HLSLINCLUDE
|
HLSLINCLUDE
|
||||||
cbuffer PerObjectConstants
|
cbuffer PerObjectConstants
|
||||||
@@ -24,7 +23,6 @@ Shader "Builtin Gaussian Splat"
|
|||||||
{
|
{
|
||||||
float4 gPointScaleParams;
|
float4 gPointScaleParams;
|
||||||
float4 gOpacityScaleParams;
|
float4 gOpacityScaleParams;
|
||||||
float4 gDebugViewModeParams;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GaussianSplatViewData
|
struct GaussianSplatViewData
|
||||||
@@ -93,14 +91,8 @@ Shader "Builtin Gaussian Splat"
|
|||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gDebugViewModeParams.x >= 0.5)
|
return float4(input.colorOpacity.rgb, alpha);
|
||||||
{
|
|
||||||
return float4(alpha, alpha, alpha, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
return float4(input.colorOpacity.rgb * alpha, alpha);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ENDHLSL
|
ENDHLSL
|
||||||
|
|
||||||
SubShader
|
SubShader
|
||||||
@@ -113,13 +105,12 @@ Shader "Builtin Gaussian Splat"
|
|||||||
Cull Off
|
Cull Off
|
||||||
ZWrite Off
|
ZWrite Off
|
||||||
ZTest LEqual
|
ZTest LEqual
|
||||||
Blend OneMinusDstAlpha One
|
Blend SrcAlpha OneMinusSrcAlpha
|
||||||
HLSLPROGRAM
|
HLSLPROGRAM
|
||||||
#pragma target 4.5
|
#pragma target 4.5
|
||||||
#pragma vertex MainVS
|
#pragma vertex MainVS
|
||||||
#pragma fragment MainPS
|
#pragma fragment MainPS
|
||||||
ENDHLSL
|
ENDHLSL
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,10 +112,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr Core::uint32 kBaseImporterVersion = 7;
|
static constexpr Core::uint32 kBaseImporterVersion = 7;
|
||||||
enum class SourceHashPolicy : Core::uint8 {
|
|
||||||
PreserveOrClear = 0,
|
|
||||||
EnsureCurrent = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
void EnsureProjectLayout();
|
void EnsureProjectLayout();
|
||||||
void LoadSourceAssetDB();
|
void LoadSourceAssetDB();
|
||||||
@@ -130,7 +126,6 @@ private:
|
|||||||
|
|
||||||
bool EnsureMetaForPath(const std::filesystem::path& sourcePath,
|
bool EnsureMetaForPath(const std::filesystem::path& sourcePath,
|
||||||
bool isFolder,
|
bool isFolder,
|
||||||
SourceHashPolicy sourceHashPolicy,
|
|
||||||
SourceAssetRecord& outRecord);
|
SourceAssetRecord& outRecord);
|
||||||
bool ReadMetaFile(const std::filesystem::path& metaPath,
|
bool ReadMetaFile(const std::filesystem::path& metaPath,
|
||||||
SourceAssetRecord& inOutRecord) const;
|
SourceAssetRecord& inOutRecord) const;
|
||||||
|
|||||||
@@ -70,16 +70,6 @@ private:
|
|||||||
RHI::RHIDescriptorSet* set = nullptr;
|
RHI::RHIDescriptorSet* set = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CompositePipelineResources {
|
|
||||||
RHI::RHIPipelineLayout* pipelineLayout = nullptr;
|
|
||||||
RHI::RHIPipelineState* pipelineState = nullptr;
|
|
||||||
OwnedDescriptorSet textureSet = {};
|
|
||||||
RHI::RHIResourceView* boundAccumulationView = nullptr;
|
|
||||||
RHI::Format renderTargetFormat = RHI::Format::Unknown;
|
|
||||||
Core::uint32 sampleCount = 1u;
|
|
||||||
Core::uint32 sampleQuality = 0u;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PassLayoutKey {
|
struct PassLayoutKey {
|
||||||
const Resources::Shader* shader = nullptr;
|
const Resources::Shader* shader = nullptr;
|
||||||
Containers::String passName;
|
Containers::String passName;
|
||||||
@@ -295,21 +285,10 @@ private:
|
|||||||
const RenderSurface& surface,
|
const RenderSurface& surface,
|
||||||
const RenderSceneData& sceneData,
|
const RenderSceneData& sceneData,
|
||||||
const VisibleGaussianSplatItem& visibleGaussianSplat);
|
const VisibleGaussianSplatItem& visibleGaussianSplat);
|
||||||
bool EnsureCompositeResources(
|
|
||||||
const RenderContext& context,
|
|
||||||
const RenderSurface& surface);
|
|
||||||
bool CreateCompositeResources(
|
|
||||||
const RenderContext& context,
|
|
||||||
const RenderSurface& surface);
|
|
||||||
void DestroyCompositeResources();
|
|
||||||
bool CompositeAccumulationSurface(
|
|
||||||
const RenderPassContext& context,
|
|
||||||
RHI::RHIResourceView* accumulationTextureView);
|
|
||||||
|
|
||||||
RHI::RHIDevice* m_device = nullptr;
|
RHI::RHIDevice* m_device = nullptr;
|
||||||
RHI::RHIType m_backendType = RHI::RHIType::D3D12;
|
RHI::RHIType m_backendType = RHI::RHIType::D3D12;
|
||||||
Resources::ResourceHandle<Resources::Shader> m_builtinGaussianSplatShader;
|
Resources::ResourceHandle<Resources::Shader> m_builtinGaussianSplatShader;
|
||||||
Resources::ResourceHandle<Resources::Shader> m_builtinGaussianSplatCompositeShader;
|
|
||||||
Resources::ResourceHandle<Resources::Shader> m_builtinGaussianSplatUtilitiesShader;
|
Resources::ResourceHandle<Resources::Shader> m_builtinGaussianSplatUtilitiesShader;
|
||||||
std::unique_ptr<Resources::Material> m_builtinGaussianSplatMaterial;
|
std::unique_ptr<Resources::Material> m_builtinGaussianSplatMaterial;
|
||||||
RenderResourceCache m_resourceCache;
|
RenderResourceCache m_resourceCache;
|
||||||
@@ -319,7 +298,6 @@ private:
|
|||||||
std::unordered_map<PipelineStateKey, RHI::RHIPipelineState*, PipelineStateKeyHash> m_pipelineStates;
|
std::unordered_map<PipelineStateKey, RHI::RHIPipelineState*, PipelineStateKeyHash> m_pipelineStates;
|
||||||
std::unordered_map<ComputePipelineKey, RHI::RHIPipelineState*, ComputePipelineKeyHash> m_computePipelineStates;
|
std::unordered_map<ComputePipelineKey, RHI::RHIPipelineState*, ComputePipelineKeyHash> m_computePipelineStates;
|
||||||
std::unordered_map<DynamicDescriptorSetKey, CachedDescriptorSet, DynamicDescriptorSetKeyHash> m_dynamicDescriptorSets;
|
std::unordered_map<DynamicDescriptorSetKey, CachedDescriptorSet, DynamicDescriptorSetKeyHash> m_dynamicDescriptorSets;
|
||||||
CompositePipelineResources m_compositeResources = {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Passes
|
} // namespace Passes
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ Containers::String GetBuiltinSelectionMaskShaderPath();
|
|||||||
Containers::String GetBuiltinSelectionOutlineShaderPath();
|
Containers::String GetBuiltinSelectionOutlineShaderPath();
|
||||||
Containers::String GetBuiltinSkyboxShaderPath();
|
Containers::String GetBuiltinSkyboxShaderPath();
|
||||||
Containers::String GetBuiltinGaussianSplatShaderPath();
|
Containers::String GetBuiltinGaussianSplatShaderPath();
|
||||||
Containers::String GetBuiltinGaussianSplatCompositeShaderPath();
|
|
||||||
Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath();
|
Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath();
|
||||||
Containers::String GetBuiltinVolumetricShaderPath();
|
Containers::String GetBuiltinVolumetricShaderPath();
|
||||||
Containers::String GetBuiltinColorScalePostProcessShaderPath();
|
Containers::String GetBuiltinColorScalePostProcessShaderPath();
|
||||||
|
|||||||
@@ -689,7 +689,6 @@ std::vector<fs::path> CollectBuiltinShaderAssetPaths() {
|
|||||||
GetBuiltinSelectionOutlineShaderPath(),
|
GetBuiltinSelectionOutlineShaderPath(),
|
||||||
GetBuiltinSkyboxShaderPath(),
|
GetBuiltinSkyboxShaderPath(),
|
||||||
GetBuiltinGaussianSplatShaderPath(),
|
GetBuiltinGaussianSplatShaderPath(),
|
||||||
GetBuiltinGaussianSplatCompositeShaderPath(),
|
|
||||||
GetBuiltinGaussianSplatUtilitiesShaderPath(),
|
GetBuiltinGaussianSplatUtilitiesShaderPath(),
|
||||||
GetBuiltinVolumetricShaderPath(),
|
GetBuiltinVolumetricShaderPath(),
|
||||||
GetBuiltinColorScalePostProcessShaderPath(),
|
GetBuiltinColorScalePostProcessShaderPath(),
|
||||||
@@ -1735,11 +1734,7 @@ bool AssetDatabase::ReimportAsset(const Containers::String& requestPath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
SourceAssetRecord sourceRecord;
|
SourceAssetRecord sourceRecord;
|
||||||
if (!EnsureMetaForPath(
|
if (!EnsureMetaForPath(absoluteFsPath, false, sourceRecord)) {
|
||||||
absoluteFsPath,
|
|
||||||
false,
|
|
||||||
SourceHashPolicy::EnsureCurrent,
|
|
||||||
sourceRecord)) {
|
|
||||||
SetLastErrorMessage(Containers::String("Failed to prepare asset metadata: ") + absolutePath);
|
SetLastErrorMessage(Containers::String("Failed to prepare asset metadata: ") + absolutePath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1811,35 +1806,18 @@ bool AssetDatabase::ReimportAllAssets(MaintenanceStats* outStats) {
|
|||||||
bool allSucceeded = true;
|
bool allSucceeded = true;
|
||||||
MaintenanceStats localStats;
|
MaintenanceStats localStats;
|
||||||
for (const SourceAssetRecord& record : importableRecords) {
|
for (const SourceAssetRecord& record : importableRecords) {
|
||||||
const fs::path sourcePath = fs::path(m_projectRoot.CStr()) / record.relativePath.CStr();
|
|
||||||
|
|
||||||
SourceAssetRecord currentRecord;
|
|
||||||
if (!EnsureMetaForPath(
|
|
||||||
sourcePath,
|
|
||||||
false,
|
|
||||||
SourceHashPolicy::EnsureCurrent,
|
|
||||||
currentRecord)) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::FileSystem,
|
|
||||||
Containers::String("[AssetDatabase] ReimportAllAssets failed to refresh metadata path=") +
|
|
||||||
record.relativePath);
|
|
||||||
allSucceeded = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArtifactRecord rebuiltRecord;
|
ArtifactRecord rebuiltRecord;
|
||||||
if (!ImportAsset(currentRecord, rebuiltRecord)) {
|
if (!ImportAsset(record, rebuiltRecord)) {
|
||||||
Debug::Logger::Get().Error(
|
Debug::Logger::Get().Error(
|
||||||
Debug::LogCategory::FileSystem,
|
Debug::LogCategory::FileSystem,
|
||||||
Containers::String("[AssetDatabase] ReimportAllAssets failed path=") + currentRecord.relativePath);
|
Containers::String("[AssetDatabase] ReimportAllAssets failed path=") + record.relativePath);
|
||||||
allSucceeded = false;
|
allSucceeded = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_artifactsByGuid[currentRecord.guid] = rebuiltRecord;
|
m_artifactsByGuid[record.guid] = rebuiltRecord;
|
||||||
m_sourcesByGuid[currentRecord.guid].lastKnownArtifactKey = rebuiltRecord.artifactKey;
|
m_sourcesByGuid[record.guid].lastKnownArtifactKey = rebuiltRecord.artifactKey;
|
||||||
m_sourcesByPathKey[ToStdString(MakeKey(currentRecord.relativePath))].lastKnownArtifactKey =
|
m_sourcesByPathKey[ToStdString(MakeKey(record.relativePath))].lastKnownArtifactKey = rebuiltRecord.artifactKey;
|
||||||
rebuiltRecord.artifactKey;
|
|
||||||
++localStats.importedAssetCount;
|
++localStats.importedAssetCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2108,11 +2086,7 @@ void AssetDatabase::ScanAssetPath(const fs::path& path,
|
|||||||
|
|
||||||
const bool isFolder = fs::is_directory(path);
|
const bool isFolder = fs::is_directory(path);
|
||||||
SourceAssetRecord record;
|
SourceAssetRecord record;
|
||||||
if (EnsureMetaForPath(
|
if (EnsureMetaForPath(path, isFolder, record)) {
|
||||||
path,
|
|
||||||
isFolder,
|
|
||||||
SourceHashPolicy::PreserveOrClear,
|
|
||||||
record)) {
|
|
||||||
seenPaths[ToStdString(MakeKey(record.relativePath))] = true;
|
seenPaths[ToStdString(MakeKey(record.relativePath))] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2220,7 +2194,6 @@ Core::uint32 AssetDatabase::CleanupOrphanedArtifacts() const {
|
|||||||
|
|
||||||
bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
||||||
bool isFolder,
|
bool isFolder,
|
||||||
SourceHashPolicy sourceHashPolicy,
|
|
||||||
SourceAssetRecord& outRecord) {
|
SourceAssetRecord& outRecord) {
|
||||||
const Containers::String relativePath = NormalizeRelativePath(sourcePath);
|
const Containers::String relativePath = NormalizeRelativePath(sourcePath);
|
||||||
if (relativePath.Empty()) {
|
if (relativePath.Empty()) {
|
||||||
@@ -2243,35 +2216,6 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
|||||||
outRecord.importerName = GetImporterNameForPath(relativePath, isFolder);
|
outRecord.importerName = GetImporterNameForPath(relativePath, isFolder);
|
||||||
outRecord.importerVersion = GetCurrentImporterVersion(outRecord.importerName);
|
outRecord.importerVersion = GetCurrentImporterVersion(outRecord.importerName);
|
||||||
|
|
||||||
const auto refreshSourceSnapshot = [&]() {
|
|
||||||
if (isFolder) {
|
|
||||||
outRecord.sourceHash.Clear();
|
|
||||||
outRecord.sourceFileSize = 0;
|
|
||||||
outRecord.sourceWriteTime = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Core::uint64 fileSize = GetFileSizeValue(sourcePath);
|
|
||||||
const Core::uint64 writeTime = GetFileWriteTimeValue(sourcePath);
|
|
||||||
const bool canReuseExistingHash =
|
|
||||||
existingIt != m_sourcesByPathKey.end() &&
|
|
||||||
existingIt->second.sourceFileSize == fileSize &&
|
|
||||||
existingIt->second.sourceWriteTime == writeTime &&
|
|
||||||
!existingIt->second.sourceHash.Empty();
|
|
||||||
|
|
||||||
if (sourceHashPolicy == SourceHashPolicy::EnsureCurrent) {
|
|
||||||
outRecord.sourceHash =
|
|
||||||
canReuseExistingHash ? existingIt->second.sourceHash : ComputeFileHash(sourcePath);
|
|
||||||
} else if (canReuseExistingHash) {
|
|
||||||
outRecord.sourceHash = existingIt->second.sourceHash;
|
|
||||||
} else {
|
|
||||||
outRecord.sourceHash.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
outRecord.sourceFileSize = fileSize;
|
|
||||||
outRecord.sourceWriteTime = writeTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (UsesExternalSyntheticSourceRecord(relativePath)) {
|
if (UsesExternalSyntheticSourceRecord(relativePath)) {
|
||||||
if (!outRecord.guid.IsValid()) {
|
if (!outRecord.guid.IsValid()) {
|
||||||
outRecord.guid = HashStringToAssetGUID(NormalizePathString(sourcePath));
|
outRecord.guid = HashStringToAssetGUID(NormalizePathString(sourcePath));
|
||||||
@@ -2289,7 +2233,24 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
|||||||
outRecord.guid = HashStringToAssetGUID(duplicateSignature);
|
outRecord.guid = HashStringToAssetGUID(duplicateSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshSourceSnapshot();
|
if (isFolder) {
|
||||||
|
outRecord.sourceHash.Clear();
|
||||||
|
outRecord.sourceFileSize = 0;
|
||||||
|
outRecord.sourceWriteTime = 0;
|
||||||
|
} else {
|
||||||
|
const Core::uint64 fileSize = GetFileSizeValue(sourcePath);
|
||||||
|
const Core::uint64 writeTime = GetFileWriteTimeValue(sourcePath);
|
||||||
|
if (existingIt != m_sourcesByPathKey.end() &&
|
||||||
|
existingIt->second.sourceFileSize == fileSize &&
|
||||||
|
existingIt->second.sourceWriteTime == writeTime &&
|
||||||
|
!existingIt->second.sourceHash.Empty()) {
|
||||||
|
outRecord.sourceHash = existingIt->second.sourceHash;
|
||||||
|
} else {
|
||||||
|
outRecord.sourceHash = ComputeFileHash(sourcePath);
|
||||||
|
}
|
||||||
|
outRecord.sourceFileSize = fileSize;
|
||||||
|
outRecord.sourceWriteTime = writeTime;
|
||||||
|
}
|
||||||
|
|
||||||
m_sourcesByPathKey[pathKey] = outRecord;
|
m_sourcesByPathKey[pathKey] = outRecord;
|
||||||
m_sourcesByGuid[outRecord.guid] = outRecord;
|
m_sourcesByGuid[outRecord.guid] = outRecord;
|
||||||
@@ -2329,7 +2290,24 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
outRecord.metaHash = HashStringToAssetGUID(ReadWholeFileText(metaPath)).ToString();
|
outRecord.metaHash = HashStringToAssetGUID(ReadWholeFileText(metaPath)).ToString();
|
||||||
refreshSourceSnapshot();
|
if (isFolder) {
|
||||||
|
outRecord.sourceHash.Clear();
|
||||||
|
outRecord.sourceFileSize = 0;
|
||||||
|
outRecord.sourceWriteTime = 0;
|
||||||
|
} else {
|
||||||
|
const Core::uint64 fileSize = GetFileSizeValue(sourcePath);
|
||||||
|
const Core::uint64 writeTime = GetFileWriteTimeValue(sourcePath);
|
||||||
|
if (existingIt != m_sourcesByPathKey.end() &&
|
||||||
|
existingIt->second.sourceFileSize == fileSize &&
|
||||||
|
existingIt->second.sourceWriteTime == writeTime &&
|
||||||
|
!existingIt->second.sourceHash.Empty()) {
|
||||||
|
outRecord.sourceHash = existingIt->second.sourceHash;
|
||||||
|
} else {
|
||||||
|
outRecord.sourceHash = ComputeFileHash(sourcePath);
|
||||||
|
}
|
||||||
|
outRecord.sourceFileSize = fileSize;
|
||||||
|
outRecord.sourceWriteTime = writeTime;
|
||||||
|
}
|
||||||
|
|
||||||
m_sourcesByPathKey[pathKey] = outRecord;
|
m_sourcesByPathKey[pathKey] = outRecord;
|
||||||
m_sourcesByGuid[outRecord.guid] = outRecord;
|
m_sourcesByGuid[outRecord.guid] = outRecord;
|
||||||
@@ -2534,11 +2512,6 @@ bool AssetDatabase::ShouldReimport(const SourceAssetRecord& sourceRecord,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sourceRecord.isFolder &&
|
|
||||||
(sourceRecord.sourceHash.Empty() || artifactRecord->sourceHash.Empty())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifactRecord->importerVersion != sourceRecord.importerVersion ||
|
return artifactRecord->importerVersion != sourceRecord.importerVersion ||
|
||||||
artifactRecord->sourceHash != sourceRecord.sourceHash ||
|
artifactRecord->sourceHash != sourceRecord.sourceHash ||
|
||||||
artifactRecord->metaHash != sourceRecord.metaHash ||
|
artifactRecord->metaHash != sourceRecord.metaHash ||
|
||||||
@@ -2607,14 +2580,8 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool isFolder = fs::is_directory(absoluteFsPath);
|
|
||||||
|
|
||||||
SourceAssetRecord sourceRecord;
|
SourceAssetRecord sourceRecord;
|
||||||
if (!EnsureMetaForPath(
|
if (!EnsureMetaForPath(absoluteFsPath, fs::is_directory(absoluteFsPath), sourceRecord)) {
|
||||||
absoluteFsPath,
|
|
||||||
isFolder,
|
|
||||||
SourceHashPolicy::PreserveOrClear,
|
|
||||||
sourceRecord)) {
|
|
||||||
SetLastErrorMessage(Containers::String("Failed to prepare asset metadata: ") + absolutePath);
|
SetLastErrorMessage(Containers::String("Failed to prepare asset metadata: ") + absolutePath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2661,25 +2628,6 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ShouldReimport(sourceRecord, artifactRecord)) {
|
if (ShouldReimport(sourceRecord, artifactRecord)) {
|
||||||
if (!EnsureMetaForPath(
|
|
||||||
absoluteFsPath,
|
|
||||||
isFolder,
|
|
||||||
SourceHashPolicy::EnsureCurrent,
|
|
||||||
sourceRecord)) {
|
|
||||||
SetLastErrorMessage(Containers::String("Failed to validate asset metadata: ") + absolutePath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
artifactIt = m_artifactsByGuid.find(sourceRecord.guid);
|
|
||||||
artifactRecord = artifactIt != m_artifactsByGuid.end() ? &artifactIt->second : nullptr;
|
|
||||||
|
|
||||||
if (!ShouldReimport(sourceRecord, artifactRecord)) {
|
|
||||||
SaveSourceAssetDB();
|
|
||||||
PopulateResolvedAssetResult(m_projectRoot, sourceRecord, *artifactRecord, false, outAsset);
|
|
||||||
ClearLastErrorMessage();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ShouldTraceAssetPath(requestPath)) {
|
if (ShouldTraceAssetPath(requestPath)) {
|
||||||
Debug::Logger::Get().Info(
|
Debug::Logger::Get().Info(
|
||||||
Debug::LogCategory::FileSystem,
|
Debug::LogCategory::FileSystem,
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ void D3D12CommandList::SetGraphicsDescriptorSets(
|
|||||||
}
|
}
|
||||||
|
|
||||||
D3D12DescriptorHeap* heap = d3d12Set->GetHeap();
|
D3D12DescriptorHeap* heap = d3d12Set->GetHeap();
|
||||||
if (heap == nullptr) {
|
if (heap == nullptr || !heap->IsShaderVisible()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,7 +581,7 @@ void D3D12CommandList::SetComputeDescriptorSets(
|
|||||||
}
|
}
|
||||||
|
|
||||||
D3D12DescriptorHeap* heap = d3d12Set->GetHeap();
|
D3D12DescriptorHeap* heap = d3d12Set->GetHeap();
|
||||||
if (heap == nullptr) {
|
if (heap == nullptr || !heap->IsShaderVisible()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,9 +77,6 @@ D3D12_CPU_DESCRIPTOR_HANDLE D3D12DescriptorHeap::GetCPUDescriptorHandleForHeapSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
D3D12_GPU_DESCRIPTOR_HANDLE D3D12DescriptorHeap::GetGPUDescriptorHandleForHeapStart() const {
|
D3D12_GPU_DESCRIPTOR_HANDLE D3D12DescriptorHeap::GetGPUDescriptorHandleForHeapStart() const {
|
||||||
if (!m_shaderVisible || m_descriptorHeap == nullptr) {
|
|
||||||
return D3D12_GPU_DESCRIPTOR_HANDLE{0};
|
|
||||||
}
|
|
||||||
return m_descriptorHeap->GetGPUDescriptorHandleForHeapStart();
|
return m_descriptorHeap->GetGPUDescriptorHandleForHeapStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,10 +89,6 @@ CPUDescriptorHandle D3D12DescriptorHeap::GetCPUDescriptorHandle(uint32_t index)
|
|||||||
}
|
}
|
||||||
|
|
||||||
GPUDescriptorHandle D3D12DescriptorHeap::GetGPUDescriptorHandle(uint32_t index) {
|
GPUDescriptorHandle D3D12DescriptorHeap::GetGPUDescriptorHandle(uint32_t index) {
|
||||||
if (!m_shaderVisible || m_descriptorHeap == nullptr) {
|
|
||||||
return GPUDescriptorHandle{0};
|
|
||||||
}
|
|
||||||
|
|
||||||
D3D12_GPU_DESCRIPTOR_HANDLE handle = m_descriptorHeap->GetGPUDescriptorHandleForHeapStart();
|
D3D12_GPU_DESCRIPTOR_HANDLE handle = m_descriptorHeap->GetGPUDescriptorHandleForHeapStart();
|
||||||
handle.ptr += index * m_descriptorSize;
|
handle.ptr += index * m_descriptorSize;
|
||||||
GPUDescriptorHandle result;
|
GPUDescriptorHandle result;
|
||||||
|
|||||||
@@ -51,10 +51,6 @@ bool HasShaderPayload(const ShaderCompileDesc& desc) {
|
|||||||
return !desc.source.empty() || !desc.fileName.empty() || !desc.compiledBinary.empty();
|
return !desc.source.empty() || !desc.fileName.empty() || !desc.compiledBinary.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UsesTransientShaderVisibleDescriptorHeap(DescriptorHeapType type) {
|
|
||||||
return type == DescriptorHeapType::CBV_SRV_UAV || type == DescriptorHeapType::Sampler;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShouldTraceVolumetricShaderCompile(const ShaderCompileDesc& desc) {
|
bool ShouldTraceVolumetricShaderCompile(const ShaderCompileDesc& desc) {
|
||||||
const std::string fileName = NarrowAscii(desc.fileName);
|
const std::string fileName = NarrowAscii(desc.fileName);
|
||||||
if (fileName.find("volumetric") != std::string::npos) {
|
if (fileName.find("volumetric") != std::string::npos) {
|
||||||
@@ -1283,9 +1279,6 @@ RHIDescriptorPool* D3D12Device::CreateDescriptorPool(const DescriptorPoolDesc& d
|
|||||||
auto* pool = new D3D12DescriptorHeap();
|
auto* pool = new D3D12DescriptorHeap();
|
||||||
DescriptorPoolDesc poolDesc = desc;
|
DescriptorPoolDesc poolDesc = desc;
|
||||||
poolDesc.device = m_device.Get();
|
poolDesc.device = m_device.Get();
|
||||||
if (UsesTransientShaderVisibleDescriptorHeap(poolDesc.type)) {
|
|
||||||
poolDesc.shaderVisible = false;
|
|
||||||
}
|
|
||||||
if (pool->Initialize(poolDesc)) {
|
if (pool->Initialize(poolDesc)) {
|
||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,22 +65,6 @@ const Resources::ShaderPass* FindCompatibleComputePass(
|
|||||||
: nullptr;
|
: nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Resources::ShaderPass* FindCompatibleGraphicsPass(
|
|
||||||
const Resources::Shader& shader,
|
|
||||||
const Containers::String& passName,
|
|
||||||
const Resources::ShaderKeywordSet& keywordSet,
|
|
||||||
Resources::ShaderBackend backend) {
|
|
||||||
const Resources::ShaderPass* shaderPass = shader.FindPass(passName);
|
|
||||||
if (shaderPass == nullptr) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader.FindVariant(passName, Resources::ShaderType::Vertex, backend, keywordSet) != nullptr &&
|
|
||||||
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend, keywordSet) != nullptr
|
|
||||||
? shaderPass
|
|
||||||
: nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
||||||
RHI::RHIType backendType,
|
RHI::RHIType backendType,
|
||||||
RHI::RHIPipelineLayout* pipelineLayout,
|
RHI::RHIPipelineLayout* pipelineLayout,
|
||||||
@@ -121,44 +105,6 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc(
|
|||||||
return pipelineDesc;
|
return pipelineDesc;
|
||||||
}
|
}
|
||||||
|
|
||||||
RHI::GraphicsPipelineDesc CreateCompositePipelineDesc(
|
|
||||||
RHI::RHIType backendType,
|
|
||||||
RHI::RHIPipelineLayout* pipelineLayout,
|
|
||||||
const Resources::Shader& shader,
|
|
||||||
const Resources::ShaderPass& shaderPass,
|
|
||||||
const Containers::String& passName,
|
|
||||||
const RenderSurface& surface) {
|
|
||||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
|
||||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
|
||||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
|
||||||
::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
|
||||||
pipelineDesc.depthStencilFormat =
|
|
||||||
static_cast<uint32_t>(::XCEngine::Rendering::Internal::ResolveSurfaceDepthFormat(surface));
|
|
||||||
ApplyResolvedRenderState(&shaderPass, nullptr, pipelineDesc);
|
|
||||||
|
|
||||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(backendType);
|
|
||||||
if (const Resources::ShaderStageVariant* vertexVariant =
|
|
||||||
shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) {
|
|
||||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
||||||
shader.GetPath(),
|
|
||||||
shaderPass,
|
|
||||||
backend,
|
|
||||||
*vertexVariant,
|
|
||||||
pipelineDesc.vertexShader);
|
|
||||||
}
|
|
||||||
if (const Resources::ShaderStageVariant* fragmentVariant =
|
|
||||||
shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) {
|
|
||||||
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
||||||
shader.GetPath(),
|
|
||||||
shaderPass,
|
|
||||||
backend,
|
|
||||||
*fragmentVariant,
|
|
||||||
pipelineDesc.fragmentShader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pipelineDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
RHI::ComputePipelineDesc CreateComputePipelineDesc(
|
RHI::ComputePipelineDesc CreateComputePipelineDesc(
|
||||||
RHI::RHIType backendType,
|
RHI::RHIType backendType,
|
||||||
RHI::RHIPipelineLayout* pipelineLayout,
|
RHI::RHIPipelineLayout* pipelineLayout,
|
||||||
@@ -262,10 +208,6 @@ void BindDescriptorSetRanges(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RHI::Format ResolveGaussianAccumulationFormat() {
|
|
||||||
return RHI::Format::R16G16B16A16_Float;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
BuiltinGaussianSplatPass::~BuiltinGaussianSplatPass() {
|
BuiltinGaussianSplatPass::~BuiltinGaussianSplatPass() {
|
||||||
@@ -362,31 +304,10 @@ bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EnsureCompositeResources(context.renderContext, context.surface)) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass failed to initialize composite resources");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Internal::BuiltinGaussianSplatPassResources::AccumulationSurface* accumulationSurface = nullptr;
|
|
||||||
if (m_passResources == nullptr ||
|
|
||||||
!m_passResources->EnsureAccumulationSurface(
|
|
||||||
m_device,
|
|
||||||
context.surface.GetWidth(),
|
|
||||||
context.surface.GetHeight(),
|
|
||||||
ResolveGaussianAccumulationFormat(),
|
|
||||||
accumulationSurface) ||
|
|
||||||
accumulationSurface == nullptr ||
|
|
||||||
accumulationSurface->renderTargetView == nullptr ||
|
|
||||||
accumulationSurface->shaderResourceView == nullptr) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass failed to allocate gaussian accumulation surface");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
||||||
|
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
||||||
|
commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment());
|
||||||
|
|
||||||
const RHI::Viewport viewport = {
|
const RHI::Viewport viewport = {
|
||||||
static_cast<float>(renderArea.x),
|
static_cast<float>(renderArea.x),
|
||||||
static_cast<float>(renderArea.y),
|
static_cast<float>(renderArea.y),
|
||||||
@@ -401,31 +322,9 @@ bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) {
|
|||||||
renderArea.x + renderArea.width,
|
renderArea.x + renderArea.width,
|
||||||
renderArea.y + renderArea.height
|
renderArea.y + renderArea.height
|
||||||
};
|
};
|
||||||
const RHI::Rect clearRects[] = { scissorRect };
|
|
||||||
const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
||||||
|
|
||||||
RenderSurface accumulationRenderSurface(context.surface.GetWidth(), context.surface.GetHeight());
|
|
||||||
accumulationRenderSurface.SetColorAttachment(accumulationSurface->renderTargetView);
|
|
||||||
accumulationRenderSurface.SetDepthAttachment(context.surface.GetDepthAttachment());
|
|
||||||
accumulationRenderSurface.SetRenderArea(renderArea);
|
|
||||||
accumulationRenderSurface.SetAutoTransitionEnabled(false);
|
|
||||||
accumulationRenderSurface.SetSampleDesc(1u, 0u);
|
|
||||||
|
|
||||||
commandList->EndRenderPass();
|
|
||||||
if (accumulationSurface->currentColorState != RHI::ResourceStates::RenderTarget) {
|
|
||||||
commandList->TransitionBarrier(
|
|
||||||
accumulationSurface->renderTargetView,
|
|
||||||
accumulationSurface->currentColorState,
|
|
||||||
RHI::ResourceStates::RenderTarget);
|
|
||||||
accumulationSurface->currentColorState = RHI::ResourceStates::RenderTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
RHI::RHIResourceView* accumulationRenderTarget = accumulationSurface->renderTargetView;
|
|
||||||
commandList->SetRenderTargets(1, &accumulationRenderTarget, context.surface.GetDepthAttachment());
|
|
||||||
commandList->SetViewport(viewport);
|
commandList->SetViewport(viewport);
|
||||||
commandList->SetScissorRect(scissorRect);
|
commandList->SetScissorRect(scissorRect);
|
||||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||||
commandList->ClearRenderTarget(accumulationRenderTarget, clearColor, 1u, clearRects);
|
|
||||||
|
|
||||||
for (const VisibleGaussianSplatItem& visibleGaussianSplat : context.sceneData.visibleGaussianSplats) {
|
for (const VisibleGaussianSplatItem& visibleGaussianSplat : context.sceneData.visibleGaussianSplats) {
|
||||||
if (!MarkVisibleGaussianSplatChunks(
|
if (!MarkVisibleGaussianSplatChunks(
|
||||||
@@ -451,23 +350,14 @@ bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) {
|
|||||||
|
|
||||||
if (!DrawVisibleGaussianSplat(
|
if (!DrawVisibleGaussianSplat(
|
||||||
context.renderContext,
|
context.renderContext,
|
||||||
accumulationRenderSurface,
|
context.surface,
|
||||||
context.sceneData,
|
context.sceneData,
|
||||||
visibleGaussianSplat)) {
|
visibleGaussianSplat)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commandList->EndRenderPass();
|
return true;
|
||||||
if (accumulationSurface->currentColorState != RHI::ResourceStates::PixelShaderResource) {
|
|
||||||
commandList->TransitionBarrier(
|
|
||||||
accumulationSurface->shaderResourceView,
|
|
||||||
accumulationSurface->currentColorState,
|
|
||||||
RHI::ResourceStates::PixelShaderResource);
|
|
||||||
accumulationSurface->currentColorState = RHI::ResourceStates::PixelShaderResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CompositeAccumulationSurface(context, accumulationSurface->shaderResourceView);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuiltinGaussianSplatPass::Shutdown() {
|
void BuiltinGaussianSplatPass::Shutdown() {
|
||||||
@@ -482,8 +372,6 @@ bool BuiltinGaussianSplatPass::EnsureInitialized(const RenderContext& context) {
|
|||||||
if (m_device == context.device &&
|
if (m_device == context.device &&
|
||||||
m_backendType == context.backendType &&
|
m_backendType == context.backendType &&
|
||||||
m_builtinGaussianSplatShader.IsValid() &&
|
m_builtinGaussianSplatShader.IsValid() &&
|
||||||
m_builtinGaussianSplatCompositeShader.IsValid() &&
|
|
||||||
m_builtinGaussianSplatUtilitiesShader.IsValid() &&
|
|
||||||
m_builtinGaussianSplatMaterial != nullptr) {
|
m_builtinGaussianSplatMaterial != nullptr) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -505,16 +393,6 @@ bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_builtinGaussianSplatCompositeShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
|
||||||
Resources::GetBuiltinGaussianSplatCompositeShaderPath());
|
|
||||||
if (!m_builtinGaussianSplatCompositeShader.IsValid()) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass failed to load builtin gaussian composite shader resource");
|
|
||||||
DestroyResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_builtinGaussianSplatUtilitiesShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
m_builtinGaussianSplatUtilitiesShader = Resources::ResourceManager::Get().Load<Resources::Shader>(
|
||||||
Resources::GetBuiltinGaussianSplatUtilitiesShaderPath());
|
Resources::GetBuiltinGaussianSplatUtilitiesShaderPath());
|
||||||
if (!m_builtinGaussianSplatUtilitiesShader.IsValid()) {
|
if (!m_builtinGaussianSplatUtilitiesShader.IsValid()) {
|
||||||
@@ -537,207 +415,6 @@ bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BuiltinGaussianSplatPass::EnsureCompositeResources(
|
|
||||||
const RenderContext& context,
|
|
||||||
const RenderSurface& surface) {
|
|
||||||
const RHI::Format renderTargetFormat =
|
|
||||||
::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat(surface, 0u);
|
|
||||||
const Core::uint32 sampleCount =
|
|
||||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
|
||||||
const Core::uint32 sampleQuality =
|
|
||||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
|
||||||
|
|
||||||
if (m_compositeResources.pipelineLayout != nullptr &&
|
|
||||||
m_compositeResources.pipelineState != nullptr &&
|
|
||||||
m_compositeResources.textureSet.set != nullptr &&
|
|
||||||
m_compositeResources.renderTargetFormat == renderTargetFormat &&
|
|
||||||
m_compositeResources.sampleCount == sampleCount &&
|
|
||||||
m_compositeResources.sampleQuality == sampleQuality) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
DestroyCompositeResources();
|
|
||||||
return CreateCompositeResources(context, surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BuiltinGaussianSplatPass::CreateCompositeResources(
|
|
||||||
const RenderContext& context,
|
|
||||||
const RenderSurface& surface) {
|
|
||||||
if (!context.IsValid() || !m_builtinGaussianSplatCompositeShader.IsValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
RHI::Format renderTargetFormat = RHI::Format::Unknown;
|
|
||||||
if (!::XCEngine::Rendering::Internal::TryResolveSingleColorAttachmentFormat(surface, renderTargetFormat)) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass composite requires a valid single-color destination surface");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Containers::String compositePassName("GaussianComposite");
|
|
||||||
const Resources::Shader& shader = *m_builtinGaussianSplatCompositeShader.Get();
|
|
||||||
const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend(context.backendType);
|
|
||||||
const Resources::ShaderPass* compositePass = FindCompatibleGraphicsPass(
|
|
||||||
shader,
|
|
||||||
compositePassName,
|
|
||||||
Resources::ShaderKeywordSet(),
|
|
||||||
backend);
|
|
||||||
if (compositePass == nullptr) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass failed to resolve gaussian composite shader pass");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
RHI::DescriptorSetLayoutBinding textureBinding = {};
|
|
||||||
textureBinding.binding = 0u;
|
|
||||||
textureBinding.type = static_cast<uint32_t>(RHI::DescriptorType::SRV);
|
|
||||||
textureBinding.count = 1u;
|
|
||||||
textureBinding.visibility = static_cast<uint32_t>(RHI::ShaderVisibility::All);
|
|
||||||
|
|
||||||
RHI::DescriptorSetLayoutDesc textureLayout = {};
|
|
||||||
textureLayout.bindings = &textureBinding;
|
|
||||||
textureLayout.bindingCount = 1u;
|
|
||||||
|
|
||||||
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
|
||||||
pipelineLayoutDesc.setLayouts = &textureLayout;
|
|
||||||
pipelineLayoutDesc.setLayoutCount = 1u;
|
|
||||||
m_compositeResources.pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
|
||||||
if (m_compositeResources.pipelineLayout == nullptr) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass failed to create gaussian composite pipeline layout");
|
|
||||||
DestroyCompositeResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
RHI::DescriptorPoolDesc poolDesc = {};
|
|
||||||
poolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
|
||||||
poolDesc.descriptorCount = 1u;
|
|
||||||
poolDesc.shaderVisible = true;
|
|
||||||
m_compositeResources.textureSet.pool = m_device->CreateDescriptorPool(poolDesc);
|
|
||||||
if (m_compositeResources.textureSet.pool == nullptr) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass failed to create gaussian composite descriptor pool");
|
|
||||||
DestroyCompositeResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_compositeResources.textureSet.set =
|
|
||||||
m_compositeResources.textureSet.pool->AllocateSet(textureLayout);
|
|
||||||
if (m_compositeResources.textureSet.set == nullptr) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass failed to allocate gaussian composite descriptor set");
|
|
||||||
DestroyCompositeResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_compositeResources.pipelineState = m_device->CreatePipelineState(
|
|
||||||
CreateCompositePipelineDesc(
|
|
||||||
m_backendType,
|
|
||||||
m_compositeResources.pipelineLayout,
|
|
||||||
shader,
|
|
||||||
*compositePass,
|
|
||||||
compositePassName,
|
|
||||||
surface));
|
|
||||||
if (m_compositeResources.pipelineState == nullptr || !m_compositeResources.pipelineState->IsValid()) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass failed to create gaussian composite pipeline state");
|
|
||||||
DestroyCompositeResources();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_compositeResources.renderTargetFormat = renderTargetFormat;
|
|
||||||
m_compositeResources.sampleCount =
|
|
||||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface);
|
|
||||||
m_compositeResources.sampleQuality =
|
|
||||||
::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality(surface);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BuiltinGaussianSplatPass::DestroyCompositeResources() {
|
|
||||||
m_compositeResources.boundAccumulationView = nullptr;
|
|
||||||
|
|
||||||
if (m_compositeResources.pipelineState != nullptr) {
|
|
||||||
m_compositeResources.pipelineState->Shutdown();
|
|
||||||
delete m_compositeResources.pipelineState;
|
|
||||||
m_compositeResources.pipelineState = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
DestroyOwnedDescriptorSet(m_compositeResources.textureSet);
|
|
||||||
|
|
||||||
if (m_compositeResources.pipelineLayout != nullptr) {
|
|
||||||
m_compositeResources.pipelineLayout->Shutdown();
|
|
||||||
delete m_compositeResources.pipelineLayout;
|
|
||||||
m_compositeResources.pipelineLayout = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_compositeResources.renderTargetFormat = RHI::Format::Unknown;
|
|
||||||
m_compositeResources.sampleCount = 1u;
|
|
||||||
m_compositeResources.sampleQuality = 0u;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BuiltinGaussianSplatPass::CompositeAccumulationSurface(
|
|
||||||
const RenderPassContext& context,
|
|
||||||
RHI::RHIResourceView* accumulationTextureView) {
|
|
||||||
if (m_compositeResources.pipelineLayout == nullptr ||
|
|
||||||
m_compositeResources.pipelineState == nullptr ||
|
|
||||||
m_compositeResources.textureSet.set == nullptr ||
|
|
||||||
accumulationTextureView == nullptr) {
|
|
||||||
Debug::Logger::Get().Error(
|
|
||||||
Debug::LogCategory::Rendering,
|
|
||||||
"BuiltinGaussianSplatPass composite failed: composite resources are incomplete");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<RHI::RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
|
||||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_compositeResources.boundAccumulationView != accumulationTextureView) {
|
|
||||||
m_compositeResources.textureSet.set->Update(0u, accumulationTextureView);
|
|
||||||
m_compositeResources.boundAccumulationView = accumulationTextureView;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Math::RectInt renderArea = context.surface.GetRenderArea();
|
|
||||||
const RHI::Viewport viewport = {
|
|
||||||
static_cast<float>(renderArea.x),
|
|
||||||
static_cast<float>(renderArea.y),
|
|
||||||
static_cast<float>(renderArea.width),
|
|
||||||
static_cast<float>(renderArea.height),
|
|
||||||
0.0f,
|
|
||||||
1.0f
|
|
||||||
};
|
|
||||||
const RHI::Rect scissorRect = {
|
|
||||||
renderArea.x,
|
|
||||||
renderArea.y,
|
|
||||||
renderArea.x + renderArea.width,
|
|
||||||
renderArea.y + renderArea.height
|
|
||||||
};
|
|
||||||
|
|
||||||
RHI::RHICommandList* commandList = context.renderContext.commandList;
|
|
||||||
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
|
||||||
commandList->SetRenderTargets(1u, &renderTarget, context.surface.GetDepthAttachment());
|
|
||||||
commandList->SetViewport(viewport);
|
|
||||||
commandList->SetScissorRect(scissorRect);
|
|
||||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
|
||||||
commandList->SetPipelineState(m_compositeResources.pipelineState);
|
|
||||||
|
|
||||||
RHI::RHIDescriptorSet* descriptorSet = m_compositeResources.textureSet.set;
|
|
||||||
commandList->SetGraphicsDescriptorSets(
|
|
||||||
0u,
|
|
||||||
1u,
|
|
||||||
&descriptorSet,
|
|
||||||
m_compositeResources.pipelineLayout);
|
|
||||||
commandList->Draw(3u, 1u, 0u, 0u);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BuiltinGaussianSplatPass::DestroyResources() {
|
void BuiltinGaussianSplatPass::DestroyResources() {
|
||||||
if (m_passResources != nullptr) {
|
if (m_passResources != nullptr) {
|
||||||
m_passResources->Shutdown();
|
m_passResources->Shutdown();
|
||||||
@@ -773,11 +450,8 @@ void BuiltinGaussianSplatPass::DestroyResources() {
|
|||||||
}
|
}
|
||||||
m_passResourceLayouts.clear();
|
m_passResourceLayouts.clear();
|
||||||
|
|
||||||
DestroyCompositeResources();
|
|
||||||
|
|
||||||
m_builtinGaussianSplatMaterial.reset();
|
m_builtinGaussianSplatMaterial.reset();
|
||||||
m_builtinGaussianSplatUtilitiesShader.Reset();
|
m_builtinGaussianSplatUtilitiesShader.Reset();
|
||||||
m_builtinGaussianSplatCompositeShader.Reset();
|
|
||||||
m_builtinGaussianSplatShader.Reset();
|
m_builtinGaussianSplatShader.Reset();
|
||||||
m_device = nullptr;
|
m_device = nullptr;
|
||||||
m_backendType = RHI::RHIType::D3D12;
|
m_backendType = RHI::RHIType::D3D12;
|
||||||
@@ -1474,10 +1148,10 @@ bool BuiltinGaussianSplatPass::MarkVisibleGaussianSplatChunks(
|
|||||||
sceneData.cameraData.projection,
|
sceneData.cameraData.projection,
|
||||||
sceneData.cameraData.view,
|
sceneData.cameraData.view,
|
||||||
visibleGaussianSplat.localToWorld.Transpose(),
|
visibleGaussianSplat.localToWorld.Transpose(),
|
||||||
visibleGaussianSplat.localToWorld.Inverse().Transpose(),
|
visibleGaussianSplat.localToWorld.Inverse(),
|
||||||
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
||||||
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
||||||
Math::Vector4(sceneData.cameraData.worldPosition, sceneData.cameraData.nearClipPlane),
|
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
|
||||||
Math::Vector4(
|
Math::Vector4(
|
||||||
static_cast<float>(sceneData.cameraData.viewportWidth),
|
static_cast<float>(sceneData.cameraData.viewportWidth),
|
||||||
static_cast<float>(sceneData.cameraData.viewportHeight),
|
static_cast<float>(sceneData.cameraData.viewportHeight),
|
||||||
@@ -1651,10 +1325,10 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat(
|
|||||||
sceneData.cameraData.projection,
|
sceneData.cameraData.projection,
|
||||||
sceneData.cameraData.view,
|
sceneData.cameraData.view,
|
||||||
visibleGaussianSplat.localToWorld.Transpose(),
|
visibleGaussianSplat.localToWorld.Transpose(),
|
||||||
visibleGaussianSplat.localToWorld.Inverse().Transpose(),
|
visibleGaussianSplat.localToWorld.Inverse(),
|
||||||
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
||||||
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
||||||
Math::Vector4(sceneData.cameraData.worldPosition, sceneData.cameraData.nearClipPlane),
|
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
|
||||||
Math::Vector4(
|
Math::Vector4(
|
||||||
static_cast<float>(sceneData.cameraData.viewportWidth),
|
static_cast<float>(sceneData.cameraData.viewportWidth),
|
||||||
static_cast<float>(sceneData.cameraData.viewportHeight),
|
static_cast<float>(sceneData.cameraData.viewportHeight),
|
||||||
@@ -1835,10 +1509,10 @@ bool BuiltinGaussianSplatPass::SortVisibleGaussianSplat(
|
|||||||
sceneData.cameraData.projection,
|
sceneData.cameraData.projection,
|
||||||
sceneData.cameraData.view,
|
sceneData.cameraData.view,
|
||||||
visibleGaussianSplat.localToWorld.Transpose(),
|
visibleGaussianSplat.localToWorld.Transpose(),
|
||||||
visibleGaussianSplat.localToWorld.Inverse().Transpose(),
|
visibleGaussianSplat.localToWorld.Inverse(),
|
||||||
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
||||||
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
||||||
Math::Vector4(sceneData.cameraData.worldPosition, sceneData.cameraData.nearClipPlane),
|
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
|
||||||
Math::Vector4(
|
Math::Vector4(
|
||||||
static_cast<float>(sceneData.cameraData.viewportWidth),
|
static_cast<float>(sceneData.cameraData.viewportWidth),
|
||||||
static_cast<float>(sceneData.cameraData.viewportHeight),
|
static_cast<float>(sceneData.cameraData.viewportHeight),
|
||||||
@@ -2008,10 +1682,10 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat(
|
|||||||
sceneData.cameraData.projection,
|
sceneData.cameraData.projection,
|
||||||
sceneData.cameraData.view,
|
sceneData.cameraData.view,
|
||||||
visibleGaussianSplat.localToWorld.Transpose(),
|
visibleGaussianSplat.localToWorld.Transpose(),
|
||||||
visibleGaussianSplat.localToWorld.Inverse().Transpose(),
|
visibleGaussianSplat.localToWorld.Inverse(),
|
||||||
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
Math::Vector4(sceneData.cameraData.worldRight, 0.0f),
|
||||||
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
Math::Vector4(sceneData.cameraData.worldUp, 0.0f),
|
||||||
Math::Vector4(sceneData.cameraData.worldPosition, sceneData.cameraData.nearClipPlane),
|
Math::Vector4(sceneData.cameraData.worldPosition, 0.0f),
|
||||||
Math::Vector4(
|
Math::Vector4(
|
||||||
static_cast<float>(sceneData.cameraData.viewportWidth),
|
static_cast<float>(sceneData.cameraData.viewportWidth),
|
||||||
static_cast<float>(sceneData.cameraData.viewportHeight),
|
static_cast<float>(sceneData.cameraData.viewportHeight),
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ constexpr const char* kBuiltinSelectionMaskShaderPath = "builtin://shaders/selec
|
|||||||
constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline";
|
constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline";
|
||||||
constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox";
|
constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox";
|
||||||
constexpr const char* kBuiltinGaussianSplatShaderPath = "builtin://shaders/gaussian-splat";
|
constexpr const char* kBuiltinGaussianSplatShaderPath = "builtin://shaders/gaussian-splat";
|
||||||
constexpr const char* kBuiltinGaussianSplatCompositeShaderPath =
|
|
||||||
"builtin://shaders/gaussian-splat-composite";
|
|
||||||
constexpr const char* kBuiltinGaussianSplatUtilitiesShaderPath =
|
constexpr const char* kBuiltinGaussianSplatUtilitiesShaderPath =
|
||||||
"builtin://shaders/gaussian-splat-utilities";
|
"builtin://shaders/gaussian-splat-utilities";
|
||||||
constexpr const char* kBuiltinVolumetricShaderPath = "builtin://shaders/volumetric";
|
constexpr const char* kBuiltinVolumetricShaderPath = "builtin://shaders/volumetric";
|
||||||
@@ -75,8 +73,6 @@ constexpr const char* kBuiltinSkyboxShaderAssetRelativePath =
|
|||||||
"engine/assets/builtin/shaders/skybox.shader";
|
"engine/assets/builtin/shaders/skybox.shader";
|
||||||
constexpr const char* kBuiltinGaussianSplatShaderAssetRelativePath =
|
constexpr const char* kBuiltinGaussianSplatShaderAssetRelativePath =
|
||||||
"engine/assets/builtin/shaders/gaussian-splat.shader";
|
"engine/assets/builtin/shaders/gaussian-splat.shader";
|
||||||
constexpr const char* kBuiltinGaussianSplatCompositeShaderAssetRelativePath =
|
|
||||||
"engine/assets/builtin/shaders/gaussian-splat-composite.shader";
|
|
||||||
constexpr const char* kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath =
|
constexpr const char* kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath =
|
||||||
"engine/assets/builtin/shaders/gaussian-splat-utilities.shader";
|
"engine/assets/builtin/shaders/gaussian-splat-utilities.shader";
|
||||||
constexpr const char* kBuiltinVolumetricShaderAssetRelativePath =
|
constexpr const char* kBuiltinVolumetricShaderAssetRelativePath =
|
||||||
@@ -178,9 +174,6 @@ const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinS
|
|||||||
if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatShaderPath)) {
|
if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatShaderPath)) {
|
||||||
return kBuiltinGaussianSplatShaderAssetRelativePath;
|
return kBuiltinGaussianSplatShaderAssetRelativePath;
|
||||||
}
|
}
|
||||||
if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatCompositeShaderPath)) {
|
|
||||||
return kBuiltinGaussianSplatCompositeShaderAssetRelativePath;
|
|
||||||
}
|
|
||||||
if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath)) {
|
if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath)) {
|
||||||
return kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath;
|
return kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath;
|
||||||
}
|
}
|
||||||
@@ -761,10 +754,6 @@ Shader* BuildBuiltinGaussianSplatShader(const Containers::String& path) {
|
|||||||
return TryLoadBuiltinShaderFromAsset(path);
|
return TryLoadBuiltinShaderFromAsset(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader* BuildBuiltinGaussianSplatCompositeShader(const Containers::String& path) {
|
|
||||||
return TryLoadBuiltinShaderFromAsset(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Shader* BuildBuiltinVolumetricShader(const Containers::String& path) {
|
Shader* BuildBuiltinVolumetricShader(const Containers::String& path) {
|
||||||
return TryLoadBuiltinShaderFromAsset(path);
|
return TryLoadBuiltinShaderFromAsset(path);
|
||||||
}
|
}
|
||||||
@@ -887,10 +876,6 @@ bool TryGetBuiltinShaderPathByShaderName(
|
|||||||
outPath = GetBuiltinGaussianSplatShaderPath();
|
outPath = GetBuiltinGaussianSplatShaderPath();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (shaderName == "Builtin Gaussian Splat Composite") {
|
|
||||||
outPath = GetBuiltinGaussianSplatCompositeShaderPath();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (shaderName == "Builtin Gaussian Splat Utilities") {
|
if (shaderName == "Builtin Gaussian Splat Utilities") {
|
||||||
outPath = GetBuiltinGaussianSplatUtilitiesShaderPath();
|
outPath = GetBuiltinGaussianSplatUtilitiesShaderPath();
|
||||||
return true;
|
return true;
|
||||||
@@ -986,10 +971,6 @@ Containers::String GetBuiltinGaussianSplatShaderPath() {
|
|||||||
return Containers::String(kBuiltinGaussianSplatShaderPath);
|
return Containers::String(kBuiltinGaussianSplatShaderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
Containers::String GetBuiltinGaussianSplatCompositeShaderPath() {
|
|
||||||
return Containers::String(kBuiltinGaussianSplatCompositeShaderPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath() {
|
Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath() {
|
||||||
return Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath);
|
return Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath);
|
||||||
}
|
}
|
||||||
@@ -1114,8 +1095,6 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) {
|
|||||||
shader = BuildBuiltinSkyboxShader(path);
|
shader = BuildBuiltinSkyboxShader(path);
|
||||||
} else if (path == GetBuiltinGaussianSplatShaderPath()) {
|
} else if (path == GetBuiltinGaussianSplatShaderPath()) {
|
||||||
shader = BuildBuiltinGaussianSplatShader(path);
|
shader = BuildBuiltinGaussianSplatShader(path);
|
||||||
} else if (path == GetBuiltinGaussianSplatCompositeShaderPath()) {
|
|
||||||
shader = BuildBuiltinGaussianSplatCompositeShader(path);
|
|
||||||
} else if (path == GetBuiltinGaussianSplatUtilitiesShaderPath()) {
|
} else if (path == GetBuiltinGaussianSplatUtilitiesShaderPath()) {
|
||||||
shader = TryLoadBuiltinShaderFromAsset(path);
|
shader = TryLoadBuiltinShaderFromAsset(path);
|
||||||
} else if (path == GetBuiltinVolumetricShaderPath()) {
|
} else if (path == GetBuiltinVolumetricShaderPath()) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ active=1
|
|||||||
layer=0
|
layer=0
|
||||||
parent=0
|
parent=0
|
||||||
transform=position=5.87107,3.04462,0.305848;rotation=0.0591537,-0.523754,0.799249,0.288762;scale=1,1,1;
|
transform=position=5.87107,3.04462,0.305848;rotation=0.0591537,-0.523754,0.799249,0.288762;scale=1,1,1;
|
||||||
component=Light;type=0;color=1,1,1,1;intensity=0.5;range=10;spotAngle=30;shadows=0;
|
component=Light;type=0;color=1,1,1,1;intensity=0.5;range=10;spotAngle=30;shadows=1;
|
||||||
gameobject_end
|
gameobject_end
|
||||||
|
|
||||||
gameobject_begin
|
gameobject_begin
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ tag=Untagged
|
|||||||
active=1
|
active=1
|
||||||
layer=0
|
layer=0
|
||||||
parent=0
|
parent=0
|
||||||
transform=position=0,1.3,-2.75;rotation=0.104528,0,0,0.994522;scale=1,1,1;
|
transform=position=0,1.2,-4.25;rotation=0.104528,0,0,0.994522;scale=1,1,1;
|
||||||
component=Camera;projection=0;fov=35;orthoSize=5;near=0.01;far=100;depth=0;primary=1;clearMode=0;stackType=0;cullingMask=4294967295;viewportRect=0,0,1,1;clearColor=0.04,0.05,0.07,1;skyboxEnabled=0;skyboxMaterialPath=;skyboxMaterialRef=;skyboxTopColor=0.18,0.36,0.74,1;skyboxHorizonColor=0.78,0.84,0.92,1;skyboxBottomColor=0.92,0.93,0.95,1;finalColorOverrideOutputTransferEnabled=0;finalColorOverrideOutputTransferMode=0;finalColorOverrideExposureModeEnabled=0;finalColorOverrideExposureMode=0;finalColorOverrideExposureValueEnabled=0;finalColorOverrideExposureValue=1;finalColorOverrideToneMappingModeEnabled=0;finalColorOverrideToneMappingMode=0;finalColorOverrideScaleEnabled=0;finalColorOverrideScale=1,1,1,1;postProcessPassCount=0;
|
component=Camera;projection=0;fov=35;orthoSize=5;near=0.01;far=100;depth=0;primary=1;clearMode=0;stackType=0;cullingMask=4294967295;viewportRect=0,0,1,1;clearColor=0.04,0.05,0.07,1;skyboxEnabled=0;skyboxMaterialPath=;skyboxMaterialRef=;skyboxTopColor=0.18,0.36,0.74,1;skyboxHorizonColor=0.78,0.84,0.92,1;skyboxBottomColor=0.92,0.93,0.95,1;finalColorOverrideOutputTransferEnabled=0;finalColorOverrideOutputTransferMode=0;finalColorOverrideExposureModeEnabled=0;finalColorOverrideExposureMode=0;finalColorOverrideExposureValueEnabled=0;finalColorOverrideExposureValue=1;finalColorOverrideToneMappingModeEnabled=0;finalColorOverrideToneMappingMode=0;finalColorOverrideScaleEnabled=0;finalColorOverrideScale=1,1,1,1;postProcessPassCount=0;
|
||||||
gameobject_end
|
gameobject_end
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "fixtures/D3D12TestFixture.h"
|
#include "fixtures/D3D12TestFixture.h"
|
||||||
|
|
||||||
#include "XCEngine/RHI/D3D12/D3D12DescriptorHeap.h"
|
|
||||||
#include "XCEngine/RHI/D3D12/D3D12DescriptorSet.h"
|
#include "XCEngine/RHI/D3D12/D3D12DescriptorSet.h"
|
||||||
#include "XCEngine/RHI/D3D12/D3D12PipelineLayout.h"
|
#include "XCEngine/RHI/D3D12/D3D12PipelineLayout.h"
|
||||||
#include "XCEngine/RHI/RHIDescriptorPool.h"
|
#include "XCEngine/RHI/RHIDescriptorPool.h"
|
||||||
@@ -55,64 +54,6 @@ TEST_F(D3D12TestFixture, DescriptorSet_MixedBindings_AssignDescriptorIndicesByTy
|
|||||||
delete pool;
|
delete pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(D3D12TestFixture, DescriptorSet_TableBindingsUseCpuVisibleStagingHeaps) {
|
|
||||||
DescriptorPoolDesc texturePoolDesc = {};
|
|
||||||
texturePoolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
|
||||||
texturePoolDesc.descriptorCount = 2;
|
|
||||||
texturePoolDesc.shaderVisible = true;
|
|
||||||
|
|
||||||
RHIDescriptorPool* texturePool = GetDevice()->CreateDescriptorPool(texturePoolDesc);
|
|
||||||
ASSERT_NE(texturePool, nullptr);
|
|
||||||
|
|
||||||
DescriptorSetLayoutBinding textureBinding = {};
|
|
||||||
textureBinding.binding = 0;
|
|
||||||
textureBinding.type = static_cast<uint32_t>(DescriptorType::SRV);
|
|
||||||
textureBinding.count = 1;
|
|
||||||
|
|
||||||
DescriptorSetLayoutDesc textureLayoutDesc = {};
|
|
||||||
textureLayoutDesc.bindings = &textureBinding;
|
|
||||||
textureLayoutDesc.bindingCount = 1;
|
|
||||||
|
|
||||||
RHIDescriptorSet* textureSet = texturePool->AllocateSet(textureLayoutDesc);
|
|
||||||
ASSERT_NE(textureSet, nullptr);
|
|
||||||
auto* textureD3D12Set = static_cast<D3D12DescriptorSet*>(textureSet);
|
|
||||||
ASSERT_NE(textureD3D12Set->GetHeap(), nullptr);
|
|
||||||
EXPECT_FALSE(textureD3D12Set->GetHeap()->IsShaderVisible());
|
|
||||||
|
|
||||||
DescriptorPoolDesc samplerPoolDesc = {};
|
|
||||||
samplerPoolDesc.type = DescriptorHeapType::Sampler;
|
|
||||||
samplerPoolDesc.descriptorCount = 1;
|
|
||||||
samplerPoolDesc.shaderVisible = true;
|
|
||||||
|
|
||||||
RHIDescriptorPool* samplerPool = GetDevice()->CreateDescriptorPool(samplerPoolDesc);
|
|
||||||
ASSERT_NE(samplerPool, nullptr);
|
|
||||||
|
|
||||||
DescriptorSetLayoutBinding samplerBinding = {};
|
|
||||||
samplerBinding.binding = 0;
|
|
||||||
samplerBinding.type = static_cast<uint32_t>(DescriptorType::Sampler);
|
|
||||||
samplerBinding.count = 1;
|
|
||||||
|
|
||||||
DescriptorSetLayoutDesc samplerLayoutDesc = {};
|
|
||||||
samplerLayoutDesc.bindings = &samplerBinding;
|
|
||||||
samplerLayoutDesc.bindingCount = 1;
|
|
||||||
|
|
||||||
RHIDescriptorSet* samplerSet = samplerPool->AllocateSet(samplerLayoutDesc);
|
|
||||||
ASSERT_NE(samplerSet, nullptr);
|
|
||||||
auto* samplerD3D12Set = static_cast<D3D12DescriptorSet*>(samplerSet);
|
|
||||||
ASSERT_NE(samplerD3D12Set->GetHeap(), nullptr);
|
|
||||||
EXPECT_FALSE(samplerD3D12Set->GetHeap()->IsShaderVisible());
|
|
||||||
|
|
||||||
textureSet->Shutdown();
|
|
||||||
delete textureSet;
|
|
||||||
texturePool->Shutdown();
|
|
||||||
delete texturePool;
|
|
||||||
|
|
||||||
samplerSet->Shutdown();
|
|
||||||
delete samplerSet;
|
|
||||||
samplerPool->Shutdown();
|
|
||||||
delete samplerPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(D3D12TestFixture, DescriptorSet_MultipleConstantBuffersUploadIndependently) {
|
TEST_F(D3D12TestFixture, DescriptorSet_MultipleConstantBuffersUploadIndependently) {
|
||||||
DescriptorPoolDesc poolDesc = {};
|
DescriptorPoolDesc poolDesc = {};
|
||||||
poolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
poolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
||||||
|
|||||||
@@ -28,13 +28,10 @@
|
|||||||
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
|
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -51,23 +48,12 @@ namespace {
|
|||||||
constexpr const char* kD3D12Screenshot = "gaussian_splat_scene_d3d12.ppm";
|
constexpr const char* kD3D12Screenshot = "gaussian_splat_scene_d3d12.ppm";
|
||||||
constexpr const char* kOpenGLScreenshot = "gaussian_splat_scene_opengl.ppm";
|
constexpr const char* kOpenGLScreenshot = "gaussian_splat_scene_opengl.ppm";
|
||||||
constexpr const char* kVulkanScreenshot = "gaussian_splat_scene_vulkan.ppm";
|
constexpr const char* kVulkanScreenshot = "gaussian_splat_scene_vulkan.ppm";
|
||||||
constexpr const char* kD3D12AlphaDebugScreenshot = "gaussian_splat_scene_d3d12_alpha.ppm";
|
|
||||||
constexpr const char* kOpenGLAlphaDebugScreenshot = "gaussian_splat_scene_opengl_alpha.ppm";
|
|
||||||
constexpr const char* kVulkanAlphaDebugScreenshot = "gaussian_splat_scene_vulkan_alpha.ppm";
|
|
||||||
constexpr uint32_t kFrameWidth = 1280;
|
constexpr uint32_t kFrameWidth = 1280;
|
||||||
constexpr uint32_t kFrameHeight = 720;
|
constexpr uint32_t kFrameHeight = 720;
|
||||||
constexpr uint32_t kBaselineSubsetSplatCount = 262144u;
|
constexpr uint32_t kBaselineSubsetSplatCount = 65536u;
|
||||||
constexpr const char* kSubsetGaussianSplatAssetPath = "Assets/room_subset.xcgsplat";
|
constexpr const char* kSubsetGaussianSplatAssetPath = "Assets/room_subset.xcgsplat";
|
||||||
constexpr float kTargetSceneExtent = 4.0f;
|
constexpr float kTargetSceneExtent = 4.0f;
|
||||||
constexpr float kGaussianPointScale = 1.00f;
|
constexpr float kGaussianPointScale = 3.00f;
|
||||||
const Vector3 kDefaultCameraPosition(0.0f, 1.0f, 1.0f);
|
|
||||||
const Vector3 kDefaultCameraLookAt(0.0f, 1.0f, 0.0f);
|
|
||||||
const Vector3 kDefaultRootPosition = Vector3::Zero();
|
|
||||||
|
|
||||||
enum class GaussianSplatDebugView : uint8_t {
|
|
||||||
Scene = 0,
|
|
||||||
Alpha = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
XCEngine::Core::uint16 FloatToHalfBits(float value) {
|
XCEngine::Core::uint16 FloatToHalfBits(float value) {
|
||||||
uint32_t bits = 0u;
|
uint32_t bits = 0u;
|
||||||
@@ -142,178 +128,15 @@ void LinkOrCopyFixture(const std::filesystem::path& sourcePath, const std::files
|
|||||||
ASSERT_FALSE(ec) << ec.message();
|
ASSERT_FALSE(ec) << ec.message();
|
||||||
}
|
}
|
||||||
|
|
||||||
GaussianSplatDebugView GetDebugViewFromEnvironment() {
|
const char* GetScreenshotFilename(RHIType backendType) {
|
||||||
const char* debugViewValue = std::getenv("XCENGINE_GAUSSIAN_SPLAT_DEBUG_VIEW");
|
|
||||||
if (debugViewValue == nullptr) {
|
|
||||||
return GaussianSplatDebugView::Scene;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_stricmp(debugViewValue, "alpha") == 0 ||
|
|
||||||
std::strcmp(debugViewValue, "1") == 0) {
|
|
||||||
return GaussianSplatDebugView::Alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GaussianSplatDebugView::Scene;
|
|
||||||
}
|
|
||||||
|
|
||||||
float GetFloatFromEnvironment(const char* name, float defaultValue) {
|
|
||||||
const char* value = std::getenv(name);
|
|
||||||
if (value == nullptr || value[0] == '\0') {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* end = nullptr;
|
|
||||||
const float parsedValue = std::strtof(value, &end);
|
|
||||||
return end != value ? parsedValue : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t GetUIntFromEnvironment(const char* name, uint32_t defaultValue) {
|
|
||||||
const char* value = std::getenv(name);
|
|
||||||
if (value == nullptr || value[0] == '\0') {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* end = nullptr;
|
|
||||||
const unsigned long parsedValue = std::strtoul(value, &end, 10);
|
|
||||||
return end != value ? static_cast<uint32_t>(parsedValue) : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 GetVector3FromEnvironment(const char* name, const Vector3& defaultValue) {
|
|
||||||
const char* value = std::getenv(name);
|
|
||||||
if (value == nullptr || value[0] == '\0') {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float x = 0.0f;
|
|
||||||
float y = 0.0f;
|
|
||||||
float z = 0.0f;
|
|
||||||
if (std::sscanf(value, "%f,%f,%f", &x, &y, &z) == 3) {
|
|
||||||
return Vector3(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExpectVector3Near(const Vector3& actual, const Vector3& expected, float epsilon = 1.0e-6f) {
|
|
||||||
EXPECT_NEAR(actual.x, expected.x, epsilon);
|
|
||||||
EXPECT_NEAR(actual.y, expected.y, epsilon);
|
|
||||||
EXPECT_NEAR(actual.z, expected.z, epsilon);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExpectVector4Near(const Vector4& actual, const Vector4& expected, float epsilon = 1.0e-6f) {
|
|
||||||
EXPECT_NEAR(actual.x, expected.x, epsilon);
|
|
||||||
EXPECT_NEAR(actual.y, expected.y, epsilon);
|
|
||||||
EXPECT_NEAR(actual.z, expected.z, epsilon);
|
|
||||||
EXPECT_NEAR(actual.w, expected.w, epsilon);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExpectQuaternionNear(const Quaternion& actual, const Quaternion& expected, float epsilon = 1.0e-6f) {
|
|
||||||
EXPECT_NEAR(actual.x, expected.x, epsilon);
|
|
||||||
EXPECT_NEAR(actual.y, expected.y, epsilon);
|
|
||||||
EXPECT_NEAR(actual.z, expected.z, epsilon);
|
|
||||||
EXPECT_NEAR(actual.w, expected.w, epsilon);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExpectGaussianSplatRoundTripMatches(
|
|
||||||
const GaussianSplat& source,
|
|
||||||
const GaussianSplat& loaded) {
|
|
||||||
ASSERT_EQ(source.GetSplatCount(), loaded.GetSplatCount());
|
|
||||||
ASSERT_EQ(source.GetChunkCount(), loaded.GetChunkCount());
|
|
||||||
ASSERT_EQ(source.GetSHOrder(), loaded.GetSHOrder());
|
|
||||||
ExpectVector3Near(loaded.GetBounds().GetMin(), source.GetBounds().GetMin());
|
|
||||||
ExpectVector3Near(loaded.GetBounds().GetMax(), source.GetBounds().GetMax());
|
|
||||||
ASSERT_NE(source.GetPositionRecords(), nullptr);
|
|
||||||
ASSERT_NE(loaded.GetPositionRecords(), nullptr);
|
|
||||||
ASSERT_NE(source.GetOtherRecords(), nullptr);
|
|
||||||
ASSERT_NE(loaded.GetOtherRecords(), nullptr);
|
|
||||||
ASSERT_NE(source.GetColorRecords(), nullptr);
|
|
||||||
ASSERT_NE(loaded.GetColorRecords(), nullptr);
|
|
||||||
ASSERT_NE(source.GetSHRecords(), nullptr);
|
|
||||||
ASSERT_NE(loaded.GetSHRecords(), nullptr);
|
|
||||||
|
|
||||||
const uint32_t sampleIndices[] = {
|
|
||||||
0u,
|
|
||||||
source.GetSplatCount() / 2u,
|
|
||||||
source.GetSplatCount() - 1u
|
|
||||||
};
|
|
||||||
for (uint32_t sampleIndex : sampleIndices) {
|
|
||||||
ExpectVector3Near(
|
|
||||||
loaded.GetPositionRecords()[sampleIndex].position,
|
|
||||||
source.GetPositionRecords()[sampleIndex].position);
|
|
||||||
ExpectQuaternionNear(
|
|
||||||
loaded.GetOtherRecords()[sampleIndex].rotation,
|
|
||||||
source.GetOtherRecords()[sampleIndex].rotation);
|
|
||||||
ExpectVector3Near(
|
|
||||||
loaded.GetOtherRecords()[sampleIndex].scale,
|
|
||||||
source.GetOtherRecords()[sampleIndex].scale);
|
|
||||||
ExpectVector4Near(
|
|
||||||
loaded.GetColorRecords()[sampleIndex].colorOpacity,
|
|
||||||
source.GetColorRecords()[sampleIndex].colorOpacity);
|
|
||||||
|
|
||||||
for (uint32_t coefficientIndex = 0u; coefficientIndex < kGaussianSplatSHCoefficientCount; ++coefficientIndex) {
|
|
||||||
EXPECT_NEAR(
|
|
||||||
loaded.GetSHRecords()[sampleIndex].coefficients[coefficientIndex],
|
|
||||||
source.GetSHRecords()[sampleIndex].coefficients[coefficientIndex],
|
|
||||||
1.0e-6f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto* sourceChunkSection = static_cast<const GaussianSplatChunkRecord*>(
|
|
||||||
source.GetSectionData(GaussianSplatSectionType::Chunks));
|
|
||||||
const auto* loadedChunkSection = static_cast<const GaussianSplatChunkRecord*>(
|
|
||||||
loaded.GetSectionData(GaussianSplatSectionType::Chunks));
|
|
||||||
ASSERT_NE(sourceChunkSection, nullptr);
|
|
||||||
ASSERT_NE(loadedChunkSection, nullptr);
|
|
||||||
if (source.GetChunkCount() > 0u) {
|
|
||||||
const uint32_t chunkIndices[] = { 0u, source.GetChunkCount() - 1u };
|
|
||||||
for (uint32_t chunkIndex : chunkIndices) {
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].colR, sourceChunkSection[chunkIndex].colR);
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].colG, sourceChunkSection[chunkIndex].colG);
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].colB, sourceChunkSection[chunkIndex].colB);
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].colA, sourceChunkSection[chunkIndex].colA);
|
|
||||||
ExpectVector3Near(
|
|
||||||
Vector3(
|
|
||||||
loadedChunkSection[chunkIndex].posX.x,
|
|
||||||
loadedChunkSection[chunkIndex].posY.x,
|
|
||||||
loadedChunkSection[chunkIndex].posZ.x),
|
|
||||||
Vector3(
|
|
||||||
sourceChunkSection[chunkIndex].posX.x,
|
|
||||||
sourceChunkSection[chunkIndex].posY.x,
|
|
||||||
sourceChunkSection[chunkIndex].posZ.x));
|
|
||||||
ExpectVector3Near(
|
|
||||||
Vector3(
|
|
||||||
loadedChunkSection[chunkIndex].posX.y,
|
|
||||||
loadedChunkSection[chunkIndex].posY.y,
|
|
||||||
loadedChunkSection[chunkIndex].posZ.y),
|
|
||||||
Vector3(
|
|
||||||
sourceChunkSection[chunkIndex].posX.y,
|
|
||||||
sourceChunkSection[chunkIndex].posY.y,
|
|
||||||
sourceChunkSection[chunkIndex].posZ.y));
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].sclX, sourceChunkSection[chunkIndex].sclX);
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].sclY, sourceChunkSection[chunkIndex].sclY);
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].sclZ, sourceChunkSection[chunkIndex].sclZ);
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].shR, sourceChunkSection[chunkIndex].shR);
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].shG, sourceChunkSection[chunkIndex].shG);
|
|
||||||
EXPECT_EQ(loadedChunkSection[chunkIndex].shB, sourceChunkSection[chunkIndex].shB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetScreenshotFilename(RHIType backendType, GaussianSplatDebugView debugView) {
|
|
||||||
switch (backendType) {
|
switch (backendType) {
|
||||||
case RHIType::D3D12:
|
case RHIType::D3D12:
|
||||||
return debugView == GaussianSplatDebugView::Alpha
|
return kD3D12Screenshot;
|
||||||
? kD3D12AlphaDebugScreenshot
|
|
||||||
: kD3D12Screenshot;
|
|
||||||
case RHIType::Vulkan:
|
case RHIType::Vulkan:
|
||||||
return debugView == GaussianSplatDebugView::Alpha
|
return kVulkanScreenshot;
|
||||||
? kVulkanAlphaDebugScreenshot
|
|
||||||
: kVulkanScreenshot;
|
|
||||||
case RHIType::OpenGL:
|
case RHIType::OpenGL:
|
||||||
default:
|
default:
|
||||||
return debugView == GaussianSplatDebugView::Alpha
|
return kOpenGLScreenshot;
|
||||||
? kOpenGLAlphaDebugScreenshot
|
|
||||||
: kOpenGLScreenshot;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,8 +180,6 @@ GaussianSplat* CreateGaussianSplatSubset(
|
|||||||
std::vector<GaussianSplatOtherRecord> subsetOther(subsetSplatCount);
|
std::vector<GaussianSplatOtherRecord> subsetOther(subsetSplatCount);
|
||||||
std::vector<GaussianSplatColorRecord> subsetColors(subsetSplatCount);
|
std::vector<GaussianSplatColorRecord> subsetColors(subsetSplatCount);
|
||||||
std::vector<GaussianSplatSHRecord> subsetSh(shSection != nullptr && sh != nullptr ? subsetSplatCount : 0u);
|
std::vector<GaussianSplatSHRecord> subsetSh(shSection != nullptr && sh != nullptr ? subsetSplatCount : 0u);
|
||||||
Bounds subsetBounds;
|
|
||||||
bool hasSubsetBounds = false;
|
|
||||||
for (uint32_t subsetIndex = 0u; subsetIndex < subsetSplatCount; ++subsetIndex) {
|
for (uint32_t subsetIndex = 0u; subsetIndex < subsetSplatCount; ++subsetIndex) {
|
||||||
const uint32_t sourceIndex = selectedIndices[subsetIndex];
|
const uint32_t sourceIndex = selectedIndices[subsetIndex];
|
||||||
subsetPositions[subsetIndex] = positions[sourceIndex];
|
subsetPositions[subsetIndex] = positions[sourceIndex];
|
||||||
@@ -367,14 +188,6 @@ GaussianSplat* CreateGaussianSplatSubset(
|
|||||||
if (!subsetSh.empty()) {
|
if (!subsetSh.empty()) {
|
||||||
subsetSh[subsetIndex] = sh[sourceIndex];
|
subsetSh[subsetIndex] = sh[sourceIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Vector3& position = subsetPositions[subsetIndex].position;
|
|
||||||
if (!hasSubsetBounds) {
|
|
||||||
subsetBounds.SetMinMax(position, position);
|
|
||||||
hasSubsetBounds = true;
|
|
||||||
} else {
|
|
||||||
subsetBounds.Encapsulate(position);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t subsetChunkCount =
|
const uint32_t subsetChunkCount =
|
||||||
@@ -483,7 +296,7 @@ GaussianSplat* CreateGaussianSplatSubset(
|
|||||||
|
|
||||||
GaussianSplatMetadata metadata = source.GetMetadata();
|
GaussianSplatMetadata metadata = source.GetMetadata();
|
||||||
metadata.splatCount = subsetSplatCount;
|
metadata.splatCount = subsetSplatCount;
|
||||||
metadata.bounds = subsetBounds;
|
metadata.bounds = source.GetBounds();
|
||||||
metadata.chunkCount = subsetChunkCount;
|
metadata.chunkCount = subsetChunkCount;
|
||||||
metadata.cameraCount = 0u;
|
metadata.cameraCount = 0u;
|
||||||
metadata.chunkFormat = GaussianSplatSectionFormat::ChunkFloat32;
|
metadata.chunkFormat = GaussianSplatSectionFormat::ChunkFloat32;
|
||||||
@@ -523,12 +336,10 @@ private:
|
|||||||
ResourceHandle<GaussianSplat> m_gaussianSplat;
|
ResourceHandle<GaussianSplat> m_gaussianSplat;
|
||||||
ResourceHandle<GaussianSplat> m_subsetGaussianSplat;
|
ResourceHandle<GaussianSplat> m_subsetGaussianSplat;
|
||||||
Material* m_material = nullptr;
|
Material* m_material = nullptr;
|
||||||
GaussianSplatDebugView m_debugView = GaussianSplatDebugView::Scene;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void GaussianSplatSceneTest::SetUp() {
|
void GaussianSplatSceneTest::SetUp() {
|
||||||
RHIIntegrationFixture::SetUp();
|
RHIIntegrationFixture::SetUp();
|
||||||
m_debugView = GetDebugViewFromEnvironment();
|
|
||||||
PrepareRuntimeProject();
|
PrepareRuntimeProject();
|
||||||
|
|
||||||
m_sceneRenderer = std::make_unique<SceneRenderer>();
|
m_sceneRenderer = std::make_unique<SceneRenderer>();
|
||||||
@@ -641,7 +452,7 @@ void GaussianSplatSceneTest::PrepareRuntimeProject() {
|
|||||||
std::unique_ptr<GaussianSplat> subsetGaussianSplat(
|
std::unique_ptr<GaussianSplat> subsetGaussianSplat(
|
||||||
CreateGaussianSplatSubset(
|
CreateGaussianSplatSubset(
|
||||||
*m_gaussianSplat.Get(),
|
*m_gaussianSplat.Get(),
|
||||||
GetUIntFromEnvironment("XCENGINE_GAUSSIAN_SPLAT_SUBSET_COUNT", kBaselineSubsetSplatCount),
|
kBaselineSubsetSplatCount,
|
||||||
kSubsetGaussianSplatAssetPath));
|
kSubsetGaussianSplatAssetPath));
|
||||||
ASSERT_NE(subsetGaussianSplat, nullptr);
|
ASSERT_NE(subsetGaussianSplat, nullptr);
|
||||||
ASSERT_TRUE(subsetGaussianSplat->IsValid());
|
ASSERT_TRUE(subsetGaussianSplat->IsValid());
|
||||||
@@ -663,7 +474,6 @@ void GaussianSplatSceneTest::PrepareRuntimeProject() {
|
|||||||
ASSERT_GT(m_subsetGaussianSplat->GetSplatCount(), 0u);
|
ASSERT_GT(m_subsetGaussianSplat->GetSplatCount(), 0u);
|
||||||
ASSERT_EQ(m_subsetGaussianSplat->GetSHOrder(), 3u);
|
ASSERT_EQ(m_subsetGaussianSplat->GetSHOrder(), 3u);
|
||||||
ASSERT_NE(m_subsetGaussianSplat->FindSection(GaussianSplatSectionType::Chunks), nullptr);
|
ASSERT_NE(m_subsetGaussianSplat->FindSection(GaussianSplatSectionType::Chunks), nullptr);
|
||||||
ExpectGaussianSplatRoundTripMatches(*subsetGaussianSplat, *m_subsetGaussianSplat.Get());
|
|
||||||
|
|
||||||
database.Shutdown();
|
database.Shutdown();
|
||||||
}
|
}
|
||||||
@@ -680,11 +490,8 @@ void GaussianSplatSceneTest::BuildScene() {
|
|||||||
m_material->Initialize(params);
|
m_material->Initialize(params);
|
||||||
m_material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinGaussianSplatShaderPath()));
|
m_material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinGaussianSplatShaderPath()));
|
||||||
m_material->SetRenderQueue(MaterialRenderQueue::Transparent);
|
m_material->SetRenderQueue(MaterialRenderQueue::Transparent);
|
||||||
m_material->SetFloat(
|
m_material->SetFloat("_PointScale", kGaussianPointScale);
|
||||||
"_PointScale",
|
|
||||||
GetFloatFromEnvironment("XCENGINE_GAUSSIAN_SPLAT_POINT_SCALE", kGaussianPointScale));
|
|
||||||
m_material->SetFloat("_OpacityScale", 1.0f);
|
m_material->SetFloat("_OpacityScale", 1.0f);
|
||||||
m_material->SetFloat("_DebugViewMode", m_debugView == GaussianSplatDebugView::Alpha ? 1.0f : 0.0f);
|
|
||||||
|
|
||||||
GameObject* cameraObject = m_scene->CreateGameObject("MainCamera");
|
GameObject* cameraObject = m_scene->CreateGameObject("MainCamera");
|
||||||
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
||||||
@@ -705,12 +512,10 @@ void GaussianSplatSceneTest::BuildScene() {
|
|||||||
const float sizeY = std::max(boundsMax.y - boundsMin.y, 0.001f);
|
const float sizeY = std::max(boundsMax.y - boundsMin.y, 0.001f);
|
||||||
const float sizeZ = std::max(boundsMax.z - boundsMin.z, 0.001f);
|
const float sizeZ = std::max(boundsMax.z - boundsMin.z, 0.001f);
|
||||||
const float maxExtent = std::max(sizeX, std::max(sizeY, sizeZ));
|
const float maxExtent = std::max(sizeX, std::max(sizeY, sizeZ));
|
||||||
const float uniformScale =
|
const float uniformScale = kTargetSceneExtent / maxExtent;
|
||||||
GetFloatFromEnvironment("XCENGINE_GAUSSIAN_SPLAT_TARGET_EXTENT", kTargetSceneExtent) / maxExtent;
|
|
||||||
|
|
||||||
GameObject* root = m_scene->CreateGameObject("GaussianSplatRoot");
|
GameObject* root = m_scene->CreateGameObject("GaussianSplatRoot");
|
||||||
root->GetTransform()->SetLocalPosition(
|
root->GetTransform()->SetLocalPosition(Vector3::Zero());
|
||||||
GetVector3FromEnvironment("XCENGINE_GAUSSIAN_SPLAT_ROOT_POS", kDefaultRootPosition));
|
|
||||||
root->GetTransform()->SetLocalScale(Vector3(uniformScale, uniformScale, uniformScale));
|
root->GetTransform()->SetLocalScale(Vector3(uniformScale, uniformScale, uniformScale));
|
||||||
|
|
||||||
GameObject* splatObject = m_scene->CreateGameObject("RoomGaussianSplat");
|
GameObject* splatObject = m_scene->CreateGameObject("RoomGaussianSplat");
|
||||||
@@ -722,11 +527,7 @@ void GaussianSplatSceneTest::BuildScene() {
|
|||||||
splatRenderer->SetMaterial(m_material);
|
splatRenderer->SetMaterial(m_material);
|
||||||
splatRenderer->SetCastShadows(false);
|
splatRenderer->SetCastShadows(false);
|
||||||
splatRenderer->SetReceiveShadows(false);
|
splatRenderer->SetReceiveShadows(false);
|
||||||
|
cameraObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 1.0f, 1.0f));
|
||||||
cameraObject->GetTransform()->SetLocalPosition(
|
|
||||||
GetVector3FromEnvironment("XCENGINE_GAUSSIAN_SPLAT_CAMERA_POS", kDefaultCameraPosition));
|
|
||||||
cameraObject->GetTransform()->LookAt(
|
|
||||||
GetVector3FromEnvironment("XCENGINE_GAUSSIAN_SPLAT_CAMERA_LOOK_AT", kDefaultCameraLookAt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RHIResourceView* GaussianSplatSceneTest::GetCurrentBackBufferView() {
|
RHIResourceView* GaussianSplatSceneTest::GetCurrentBackBufferView() {
|
||||||
@@ -784,8 +585,7 @@ TEST_P(GaussianSplatSceneTest, RenderRoomGaussianSplatScene) {
|
|||||||
ASSERT_NE(swapChain, nullptr);
|
ASSERT_NE(swapChain, nullptr);
|
||||||
|
|
||||||
constexpr int kTargetFrameCount = 2;
|
constexpr int kTargetFrameCount = 2;
|
||||||
const GaussianSplatDebugView debugView = GetDebugViewFromEnvironment();
|
const char* screenshotFilename = GetScreenshotFilename(GetBackendType());
|
||||||
const char* screenshotFilename = GetScreenshotFilename(GetBackendType(), debugView);
|
|
||||||
|
|
||||||
for (int frameCount = 0; frameCount <= kTargetFrameCount; ++frameCount) {
|
for (int frameCount = 0; frameCount <= kTargetFrameCount; ++frameCount) {
|
||||||
if (frameCount > 0) {
|
if (frameCount > 0) {
|
||||||
@@ -799,11 +599,6 @@ TEST_P(GaussianSplatSceneTest, RenderRoomGaussianSplatScene) {
|
|||||||
commandQueue->WaitForIdle();
|
commandQueue->WaitForIdle();
|
||||||
ASSERT_TRUE(TakeScreenshot(screenshotFilename));
|
ASSERT_TRUE(TakeScreenshot(screenshotFilename));
|
||||||
|
|
||||||
if (debugView != GaussianSplatDebugView::Scene) {
|
|
||||||
SUCCEED() << "Debug view screenshot captured for inspection: " << screenshotFilename;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::filesystem::path gtPath = ResolveRuntimePath("GT.ppm");
|
const std::filesystem::path gtPath = ResolveRuntimePath("GT.ppm");
|
||||||
if (!std::filesystem::exists(gtPath)) {
|
if (!std::filesystem::exists(gtPath)) {
|
||||||
GTEST_SKIP() << "GT.ppm missing, screenshot captured for baseline generation: " << screenshotFilename;
|
GTEST_SKIP() << "GT.ppm missing, screenshot captured for baseline generation: " << screenshotFilename;
|
||||||
|
|||||||
@@ -100,18 +100,6 @@ std::unordered_set<std::string> GetIsolationObjectNames() {
|
|||||||
std::unordered_set<std::string> result;
|
std::unordered_set<std::string> result;
|
||||||
const char* value = std::getenv("XC_NAHIDA_DIAG_ONLY");
|
const char* value = std::getenv("XC_NAHIDA_DIAG_ONLY");
|
||||||
if (value == nullptr) {
|
if (value == nullptr) {
|
||||||
if (GetDiagnosticMode() != DiagnosticMode::Unlit) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.emplace("Body_Mesh0");
|
|
||||||
result.emplace("EyeStar");
|
|
||||||
result.emplace("Body_Mesh1");
|
|
||||||
result.emplace("Body_Mesh2");
|
|
||||||
result.emplace("Face");
|
|
||||||
result.emplace("Body_Mesh3");
|
|
||||||
result.emplace("Brow");
|
|
||||||
result.emplace("Face_Eye");
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <sstream>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -110,19 +109,6 @@ bool PumpAsyncLoadsUntil(ResourceManager& manager,
|
|||||||
return condition();
|
return condition();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path GetRepositoryRoot() {
|
|
||||||
std::filesystem::path current = std::filesystem::path(__FILE__).parent_path();
|
|
||||||
while (!current.empty()) {
|
|
||||||
if (std::filesystem::exists(current / "project") &&
|
|
||||||
std::filesystem::exists(current / "engine")) {
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
current = current.parent_path();
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::filesystem::path(__FILE__).parent_path();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DirectoryHasEntries(const std::filesystem::path& directoryPath) {
|
bool DirectoryHasEntries(const std::filesystem::path& directoryPath) {
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
if (!std::filesystem::exists(directoryPath, ec) || !std::filesystem::is_directory(directoryPath, ec)) {
|
if (!std::filesystem::exists(directoryPath, ec) || !std::filesystem::is_directory(directoryPath, ec)) {
|
||||||
@@ -132,38 +118,6 @@ bool DirectoryHasEntries(const std::filesystem::path& directoryPath) {
|
|||||||
return std::filesystem::directory_iterator(directoryPath) != std::filesystem::directory_iterator();
|
return std::filesystem::directory_iterator(directoryPath) != std::filesystem::directory_iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> SplitTabSeparatedLine(const std::string& line) {
|
|
||||||
std::vector<std::string> fields;
|
|
||||||
std::stringstream stream(line);
|
|
||||||
std::string field;
|
|
||||||
while (std::getline(stream, field, '\t')) {
|
|
||||||
fields.push_back(field);
|
|
||||||
}
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ReadSourceHashFromAssetsDb(const std::filesystem::path& assetsDbPath,
|
|
||||||
const std::string& relativePath) {
|
|
||||||
std::ifstream input(assetsDbPath, std::ios::binary);
|
|
||||||
if (!input.is_open()) {
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(input, line)) {
|
|
||||||
if (line.empty() || line[0] == '#') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<std::string> fields = SplitTabSeparatedLine(line);
|
|
||||||
if (fields.size() > 7 && fields[1] == relativePath) {
|
|
||||||
return fields[7];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::filesystem::path> ListArtifactEntries(const std::filesystem::path& artifactsRoot) {
|
std::vector<std::filesystem::path> ListArtifactEntries(const std::filesystem::path& artifactsRoot) {
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
@@ -470,47 +424,6 @@ TEST(AssetImportService_Test, EnsureArtifactExposesContainerEntryRuntimeLoadPath
|
|||||||
fs::remove_all(projectRoot);
|
fs::remove_all(projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AssetImportService_Test, BootstrapProjectDefersSourceHashUntilArtifactIsNeeded) {
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
AssetImportService importService;
|
|
||||||
importService.Initialize();
|
|
||||||
|
|
||||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_asset_import_service_deferred_source_hash_test";
|
|
||||||
const fs::path assetsDir = projectRoot / "Assets";
|
|
||||||
const fs::path materialPath = assetsDir / "runtime.material";
|
|
||||||
|
|
||||||
fs::remove_all(projectRoot);
|
|
||||||
fs::create_directories(assetsDir);
|
|
||||||
{
|
|
||||||
std::ofstream materialFile(materialPath);
|
|
||||||
ASSERT_TRUE(materialFile.is_open());
|
|
||||||
materialFile << "{\n";
|
|
||||||
materialFile << " \"renderQueue\": \"geometry\"\n";
|
|
||||||
materialFile << "}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
importService.SetProjectRoot(projectRoot.string().c_str());
|
|
||||||
ASSERT_TRUE(importService.BootstrapProject());
|
|
||||||
|
|
||||||
const fs::path libraryRoot(importService.GetLibraryRoot().CStr());
|
|
||||||
const fs::path assetsDbPath = libraryRoot / "assets.db";
|
|
||||||
ASSERT_TRUE(fs::exists(assetsDbPath));
|
|
||||||
EXPECT_FALSE(DirectoryHasEntries(libraryRoot / "Artifacts"));
|
|
||||||
EXPECT_TRUE(ReadSourceHashFromAssetsDb(assetsDbPath, "Assets/runtime.material").empty());
|
|
||||||
|
|
||||||
AssetImportService::ImportedAsset importedAsset;
|
|
||||||
ASSERT_TRUE(importService.EnsureArtifact("Assets/runtime.material", ResourceType::Material, importedAsset));
|
|
||||||
EXPECT_TRUE(importedAsset.exists);
|
|
||||||
EXPECT_TRUE(importedAsset.artifactReady);
|
|
||||||
EXPECT_TRUE(importedAsset.imported);
|
|
||||||
EXPECT_FALSE(ReadSourceHashFromAssetsDb(assetsDbPath, "Assets/runtime.material").empty());
|
|
||||||
EXPECT_TRUE(DirectoryHasEntries(libraryRoot / "Artifacts"));
|
|
||||||
|
|
||||||
importService.Shutdown();
|
|
||||||
fs::remove_all(projectRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ResourceManager_Test, RebuildProjectAssetCacheRefreshesLookupState) {
|
TEST(ResourceManager_Test, RebuildProjectAssetCacheRefreshesLookupState) {
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
@@ -632,58 +545,6 @@ TEST(ResourceManager_Test, SetResourceRootBootstrapsProjectAssetCache) {
|
|||||||
fs::remove_all(projectRoot);
|
fs::remove_all(projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ResourceManager_ProjectSample, BootstrapProjectKeepsCloudSourceHashDeferred) {
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
const fs::path repositoryRoot = GetRepositoryRoot();
|
|
||||||
const fs::path projectRoot = repositoryRoot / "project";
|
|
||||||
const fs::path volumePath = projectRoot / "Assets" / "cloud.nvdb";
|
|
||||||
|
|
||||||
if (!fs::exists(volumePath)) {
|
|
||||||
GTEST_SKIP() << "Project cloud volume fixture is not available.";
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceManager& manager = ResourceManager::Get();
|
|
||||||
manager.Initialize();
|
|
||||||
|
|
||||||
struct ResourceManagerGuard {
|
|
||||||
ResourceManager* manager = nullptr;
|
|
||||||
~ResourceManagerGuard() {
|
|
||||||
if (manager != nullptr) {
|
|
||||||
manager->Shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} resourceManagerGuard{ &manager };
|
|
||||||
|
|
||||||
struct CurrentPathGuard {
|
|
||||||
fs::path previousPath;
|
|
||||||
~CurrentPathGuard() {
|
|
||||||
if (!previousPath.empty()) {
|
|
||||||
fs::current_path(previousPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} currentPathGuard{ fs::current_path() };
|
|
||||||
|
|
||||||
fs::current_path(projectRoot);
|
|
||||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
|
||||||
|
|
||||||
AssetRef volumeRef;
|
|
||||||
EXPECT_TRUE(manager.TryGetAssetRef("Assets/cloud.nvdb", ResourceType::VolumeField, volumeRef));
|
|
||||||
EXPECT_TRUE(volumeRef.IsValid());
|
|
||||||
|
|
||||||
const AssetImportService::ImportStatusSnapshot status = manager.GetProjectAssetImportStatus();
|
|
||||||
EXPECT_TRUE(status.HasValue());
|
|
||||||
EXPECT_FALSE(status.inProgress);
|
|
||||||
EXPECT_TRUE(status.success);
|
|
||||||
EXPECT_EQ(std::string(status.operation.CStr()), "Bootstrap Project");
|
|
||||||
|
|
||||||
const fs::path assetsDbPath = projectRoot / "Library" / "assets.db";
|
|
||||||
ASSERT_TRUE(fs::exists(assetsDbPath));
|
|
||||||
EXPECT_TRUE(ReadSourceHashFromAssetsDb(assetsDbPath, "Assets/cloud.nvdb").empty());
|
|
||||||
|
|
||||||
manager.SetResourceRoot("");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(AssetImportService_Test, ClearLibraryAndReimportAllAssetsManageArtifactsExplicitly) {
|
TEST(AssetImportService_Test, ClearLibraryAndReimportAllAssetsManageArtifactsExplicitly) {
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ std::shared_ptr<XCEngine::Editor::AssetItem> MakeModelAssetItem(
|
|||||||
void ConfigurePreviewCamera(XCEngine::Components::GameObject& gameObject) {
|
void ConfigurePreviewCamera(XCEngine::Components::GameObject& gameObject) {
|
||||||
using namespace XCEngine;
|
using namespace XCEngine;
|
||||||
|
|
||||||
gameObject.GetTransform()->SetLocalPosition(Math::Vector3(0.0f, 1.3f, -2.75f));
|
gameObject.GetTransform()->SetLocalPosition(Math::Vector3(0.0f, 1.2f, -4.25f));
|
||||||
gameObject.GetTransform()->SetLocalRotation(Math::Quaternion(0.104528f, 0.0f, 0.0f, 0.994522f));
|
gameObject.GetTransform()->SetLocalRotation(Math::Quaternion(0.104528f, 0.0f, 0.0f, 0.994522f));
|
||||||
|
|
||||||
auto* camera = gameObject.AddComponent<Components::CameraComponent>();
|
auto* camera = gameObject.AddComponent<Components::CameraComponent>();
|
||||||
|
|||||||
Reference in New Issue
Block a user