diff --git a/docs/api/XCEngine/Editor/Scripting/EditorScriptAssemblyBuilder/EditorScriptAssemblyBuilder.md b/docs/api/XCEngine/Editor/Scripting/EditorScriptAssemblyBuilder/EditorScriptAssemblyBuilder.md index fc4e410f..a81f092d 100644 --- a/docs/api/XCEngine/Editor/Scripting/EditorScriptAssemblyBuilder/EditorScriptAssemblyBuilder.md +++ b/docs/api/XCEngine/Editor/Scripting/EditorScriptAssemblyBuilder/EditorScriptAssemblyBuilder.md @@ -8,65 +8,100 @@ **描述**: 负责把 Editor 项目里的 C# 源码编译成 `Library/ScriptAssemblies` 下的脚本程序集。 -## 概述 +## 角色概述 -`EditorScriptAssemblyBuilder` 是当前 Editor 脚本工作流里真正执行“编译”的那一层。 - -它会把两类源码分别编成: +`EditorScriptAssemblyBuilder` 是当前 Editor 托管脚本工作流里真正执行“编译”的那一层。它会把两组 C# 源码分别编成: - `XCEngine.ScriptCore.dll` - `GameScripts.dll` -并把结果统一落到: +并把输出统一落到: - `/Library/ScriptAssemblies/` +## 为什么这层设计重要 + +编辑器脚本系统要稳定工作,关键不只是“能调用 Roslyn”,而是要把几件事情一起稳定下来: + +- 仓库根目录如何解析 +- Mono 运行时依赖从哪里找 +- `dotnet` / Roslyn / `.NET Framework 4.7.2` 引用程序集如何就位 +- 输出目录里的 `mscorlib.dll` 如何避免被活动 Mono runtime 锁住后反复覆盖 + +`EditorScriptAssemblyBuilder` 正是在做这层工程收口。 + ## 公开方法 | 方法 | 说明 | |------|------| | [RebuildProjectAssemblies](RebuildProjectAssemblies.md) | 全量重建项目脚本程序集并返回结果消息。 | -这是当前唯一入口。返回 `EditorScriptAssemblyBuildResult`: +当前这就是它唯一的公开入口。返回的 `EditorScriptAssemblyBuildResult` 包含: - `succeeded` - `message` ## 当前重建流程 -按 `EditorScriptAssemblyBuilder.cpp` 当前实现,主流程大致是: +按 `EditorScriptAssemblyBuilder.cpp` 当前实现,主流程大致如下: 1. 校验项目路径非空。 -2. 解析仓库根与 Mono 根目录。 -3. 创建 `/Library/ScriptAssemblies`。 -4. 在 `PATH` 上查找 `dotnet.exe`。 -5. 运行 `dotnet --list-sdks`,取最后一条 SDK 版本。 -6. 定位该 SDK 下的 `Roslyn/bincore/csc.dll`。 -7. 校验 `.NET Framework 4.7.2` 参考程序集存在。 -8. 收集 `managed/XCEngine.ScriptCore/**/*.cs`。 -9. 收集 `/Assets/**/*.cs`。 -10. 若项目脚本为空,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件。 -11. 先编译 `XCEngine.ScriptCore.dll`。 -12. 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用旧文件,避免在增量重建时覆盖仍可能被 Mono 映射的 corlib。 -13. 再引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`。 +2. 解析仓库根目录。 +3. 解析 Mono 根目录。 +4. 创建 `/Library/ScriptAssemblies`。 +5. 在 `PATH` 上查找 `dotnet.exe`。 +6. 运行 `dotnet --list-sdks`,取最后一条 SDK 版本。 +7. 定位该 SDK 下的 `Roslyn/bincore/csc.dll`。 +8. 校验 `.NET Framework 4.7.2` 引用程序集存在。 +9. 收集 `managed/XCEngine.ScriptCore/**/*.cs`。 +10. 收集 `/Assets/**/*.cs`。 +11. 若项目脚本为空,则生成 `Generated/EmptyProjectGameScripts.cs` 占位文件。 +12. 先编译 `XCEngine.ScriptCore.dll`。 +13. 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用。 +14. 再引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`。 + +## Mono 根目录解析顺序 + +这是本轮必须同步进文档的重点变化。 + +当前 `GetMonoRootDirectory()` 的真实优先级是: + +1. 先从仓库根目录下找 bundled Mono: + - 优先检查 `engine/third_party/mono/binary/mscorlib.dll` + - 若不存在,再扫描仓库一级子目录里的 `Fermion/Fermion/external/mono/binary/mscorlib.dll` +2. 如果没找到 bundled Mono,再检查编译期配置的 `XCENGINE_EDITOR_MONO_ROOT_DIR` +3. 如果配置路径也无效,最后回退到 `/managed/mono` + +这意味着当前编辑器会优先使用项目内随仓库提供的 Mono,而不是先依赖外部配置路径。这是一个非常实际的工程改进: + +- 减少对开发机环境的隐式依赖 +- 让构建行为更可复现 +- 更适合团队协作和 CI + +## 仓库根目录解析 + +仓库根目录也不是写死的。当前顺序是: + +1. 若定义了 `XCENGINE_EDITOR_REPO_ROOT`,直接使用它 +2. 否则以可执行文件目录为起点,向上回退三级,得到 fallback repository root + +这让同一套构建器既能支持配置化构建,也能支持开发环境里的相对部署。 ## 真实使用位置 -- `Application.cpp` 会在重建脚本程序集时调用它。 -- `tests/Editor/test_editor_script_assembly_builder.cpp` 当前覆盖了两类关键语义: - - 成功路径:`XCEngine.ScriptCore.dll`、`GameScripts.dll` 与项目本地 `mscorlib.dll` 都会落到 `Library/ScriptAssemblies` - - 锁语义:如果活动 Mono runtime 仍持有已加载的 `GameScripts.dll`,直接重建会失败;释放 runtime 后再重建则可以成功并看到新增脚本类型 +- `Application.cpp` 会在编辑器触发脚本重建时调用它。 +- `tests/Editor/test_editor_script_assembly_builder.cpp` 当前覆盖了成功路径与“活动 Mono runtime 持有程序集导致重建失败”的锁语义。 ## 当前实现边界 - 当前是全量重建,不是增量编译。 -- 编译链明显依赖 Windows 路径和本机安装的 `dotnet` SDK。 -- 参考程序集路径当前写死在 `.NET Framework 4.7.2` 目录。 -- 构建器本身不会主动卸载当前脚本运行时;如果调用方没有先释放 Mono app domain,`GameScripts.dll` 仍可能因为文件锁而构建失败。 -- 若外部环境缺少 `dotnet.exe`、Roslyn 或参考程序集,返回的只是一条失败消息,而不是更复杂的恢复策略。 +- 编译链明显依赖 Windows、本机安装的 `dotnet` SDK 与 `.NET Framework 4.7.2` 引用程序集。 +- 它会优先消费仓库内 bundled Mono,但不会主动管理 Mono runtime 生命周期。 +- 如果调用方没有先释放活动脚本运行时,`GameScripts.dll` 仍可能因文件锁而重建失败。 ## 相关文档 - [Scripting](../Scripting.md) +- [RebuildProjectAssemblies](RebuildProjectAssemblies.md) - [EditorScriptAssemblyBuilderUtils](../EditorScriptAssemblyBuilderUtils/EditorScriptAssemblyBuilderUtils.md) - [EditorScriptRuntimeStatus](../EditorScriptRuntimeStatus/EditorScriptRuntimeStatus.md) diff --git a/docs/api/XCEngine/Editor/Scripting/EditorScriptAssemblyBuilder/RebuildProjectAssemblies.md b/docs/api/XCEngine/Editor/Scripting/EditorScriptAssemblyBuilder/RebuildProjectAssemblies.md index e64a1ecd..64250a0d 100644 --- a/docs/api/XCEngine/Editor/Scripting/EditorScriptAssemblyBuilder/RebuildProjectAssemblies.md +++ b/docs/api/XCEngine/Editor/Scripting/EditorScriptAssemblyBuilder/RebuildProjectAssemblies.md @@ -14,53 +14,123 @@ static EditorScriptAssemblyBuildResult RebuildProjectAssemblies(const std::strin ## 作用 -把指定项目下的托管脚本源码全量编译到 `Library/ScriptAssemblies` 目录,并返回成功/失败消息。 +把指定项目下的托管脚本源码全量编译到 `Library/ScriptAssemblies` 目录,并返回成功/失败结果。 -## 当前实现行为 +## 当前实现流程 -### 1. 解析路径与外部工具 +### 1. 解析路径与基础环境 -- 要求 `projectPath` 非空,否则直接返回失败结果。 -- 会解析: - - 仓库根目录 - - Mono 根目录 - - `managed/XCEngine.ScriptCore` - - `/Library/ScriptAssemblies` -- 当前依赖系统 `PATH` 上可找到 `dotnet.exe`,并通过 `dotnet --list-sdks` 解析最新 SDK 版本,再定位对应 `Roslyn/bincore/csc.dll`。 +- 要求 `projectPath` 非空,否则直接返回失败。 +- 解析 `projectRoot`、`repositoryRoot`、`monoRoot`、`managedRoot` 与输出目录。 +- 创建 `/Library/ScriptAssemblies`。 -### 2. 校验引用与源文件 +### 2. 解析仓库根目录 -- 校验 `.NET Framework 4.7.2` 参考程序集存在: - - `mscorlib.dll` - - `System.dll` - - `System.Core.dll` -- 校验 Mono 自带 `binary/mscorlib.dll` 存在。 -- 收集两组 C# 源文件: - - `managed/XCEngine.ScriptCore/**/*.cs` - - `/Assets/**/*.cs` -- 若项目脚本为空,会生成 `Generated/EmptyProjectGameScripts.cs` 占位源码。 +仓库根目录顺序如下: -### 3. 编译输出 +1. 若 `XCENGINE_EDITOR_REPO_ROOT` 非空,优先使用该配置。 +2. 否则以可执行文件目录为起点向上回退三级,得到 fallback repository root。 -- 先编译 `XCEngine.ScriptCore.dll`。 -- 仅当输出目录里还没有项目本地 `mscorlib.dll` 时,才从 Mono 目录复制一份;如果已经存在,则直接复用旧文件。 -- 然后引用 `XCEngine.ScriptCore.dll` 编译 `GameScripts.dll`。 -- 成功时返回: - - `succeeded = true` - - `message = "Rebuilt script assemblies in ..."` +### 3. 解析 Mono 根目录 -### 4. 失败语义 +当前 Mono 根目录的真实查找顺序是: -- 任意路径校验、进程启动、编译失败、首次 `mscorlib.dll` 复制失败,或目标程序集仍被活动 Mono runtime 持有时,都会返回 `succeeded = false`。 -- 这个函数本身不负责关闭现有脚本运行时;如果调用方在同一进程里仍持有已加载的 `GameScripts.dll`,重建可能因为文件锁失败。 -- 函数内部还包了一层 `try/catch`,用于把标准异常和未知异常转成失败消息。 +1. 优先查找仓库内 bundled Mono: + - `/engine/third_party/mono` + - 若未命中,再扫描仓库一级子目录下的 `Fermion/Fermion/external/mono` +2. 若未找到 bundled Mono,则尝试 `XCENGINE_EDITOR_MONO_ROOT_DIR` +3. 若配置路径也无效,则回退到 `/managed/mono` -## 当前实现边界 +判定依据都是目标目录下是否存在 `binary/mscorlib.dll`。 -- 当前是全量重建,不做增量分析。 -- 平台和工具链路径明显偏向 Windows + 本机安装 `.NET SDK`。 -- 参考程序集版本当前固定为 `.NET Framework 4.7.2`。 -- 输出目录里的 `mscorlib.dll` 当前采用“首次复制、后续复用”的策略,不会在每次重建时强制覆盖。 +这条顺序很关键,因为它决定了编辑器现在优先消费项目内置的 Mono 依赖,而不是先依赖开发机外部配置。 + +### 4. 解析外部编译工具 + +- 在 `PATH` 上查找 `dotnet.exe` +- 运行 `dotnet --list-sdks` +- 取输出中的最后一条 SDK 版本 +- 组装 `C:\Program Files\dotnet\sdk\\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` +- 收集 `/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` 因重复覆盖而触发额外锁冲突 + +从商业级工具链视角看,这属于“先把本地开发和编辑器热重建跑稳,再逐步演进成更完整脚本构建系统”的典型路线。 ## 相关文档 diff --git a/docs/api/_meta/rebuild-status.md b/docs/api/_meta/rebuild-status.md index aaeb5e7e..7185f2c4 100644 --- a/docs/api/_meta/rebuild-status.md +++ b/docs/api/_meta/rebuild-status.md @@ -1,6 +1,6 @@ # API 文档重构状态 -**生成时间**: `2026-04-10 18:41:38` +**生成时间**: `2026-04-12 22:45:25` **来源**: `docs/api/_tools/audit_api_docs.py` @@ -8,10 +8,10 @@ - Markdown 页面数(全部): `4006` - Markdown 页面数(canonical): `3978` -- Public headers 数: `384` -- `XCEditor` public headers 数: `64`(canonical 已覆盖 `64`) -- `XCEngine` public headers 数: `320`(canonical 已覆盖 `320`) -- Editor source headers 数: `144` +- Public headers 数: `393` +- `XCEditor` public headers 数: `66`(canonical 已覆盖 `64`) +- `XCEngine` public headers 数: `327`(canonical 已覆盖 `320`) +- Editor source headers 数: `145` - 有效头文件引用数(全部): `384` - 有效头文件引用数(canonical): `384` - 无效头文件引用数: `0` @@ -41,20 +41,20 @@ |------|----------------|--------|--------| | `XCEditor/Collections` | `9` | `9` | `0` | | `XCEditor/Fields` | `23` | `23` | `0` | -| `XCEditor/Foundation` | `4` | `4` | `0` | +| `XCEditor/Foundation` | `6` | `4` | `2` | | `XCEditor/Shell` | `24` | `24` | `0` | | `XCEditor/Widgets` | `4` | `4` | `0` | | `XCEngine/Audio` | `11` | `11` | `0` | -| `XCEngine/Components` | `11` | `11` | `0` | -| `XCEngine/Core` | `48` | `48` | `0` | +| `XCEngine/Components` | `12` | `11` | `1` | +| `XCEngine/Core` | `49` | `48` | `1` | | `XCEngine/Debug` | `10` | `10` | `0` | | `XCEngine/Input` | `5` | `5` | `0` | | `XCEngine/Memory` | `5` | `5` | `0` | | `XCEngine/Platform` | `11` | `11` | `0` | | `XCEngine/RHI` | `88` | `88` | `0` | -| `XCEngine/Rendering` | `42` | `42` | `0` | -| `XCEngine/Resources` | `29` | `29` | `0` | -| `XCEngine/Scene` | `4` | `4` | `0` | +| `XCEngine/Rendering` | `44` | `42` | `2` | +| `XCEngine/Resources` | `31` | `29` | `2` | +| `XCEngine/Scene` | `5` | `4` | `1` | | `XCEngine/Scripting` | `7` | `7` | `0` | | `XCEngine/Threading` | `10` | `10` | `0` | | `XCEngine/UI` | `39` | `39` | `0` | @@ -67,7 +67,7 @@ | `Actions` | `9` | `9` | `0` | | `Commands` | `4` | `4` | `0` | | `ComponentEditors` | `11` | `11` | `0` | -| `Core` | `20` | `20` | `0` | +| `Core` | `21` | `20` | `1` | | `Layers` | `1` | `1` | `0` | | `Layout` | `1` | `1` | `0` | | `Managers` | `3` | `3` | `0` | @@ -88,3 +88,19 @@ | `描述` | `618` | | `头文件` | `1975` | | `源文件` | `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` diff --git a/docs/plan/Library启动Bootstrap与SourceHash校验解耦修复计划_2026-04-11.md b/docs/plan/Library启动Bootstrap与SourceHash校验解耦修复计划_2026-04-11.md deleted file mode 100644 index a96fc96e..00000000 --- a/docs/plan/Library启动Bootstrap与SourceHash校验解耦修复计划_2026-04-11.md +++ /dev/null @@ -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. 启动检查、按需导入、显式全量重建三种语义边界清晰稳定。 diff --git a/docs/plan/Rendering通用Shader多Pass执行重构计划_2026-04-12.md b/docs/plan/Rendering通用Shader多Pass执行重构计划_2026-04-12.md deleted file mode 100644 index 257199a5..00000000 --- a/docs/plan/Rendering通用Shader多Pass执行重构计划_2026-04-12.md +++ /dev/null @@ -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 细节问题”混在一起,最后继续变成补丁式推进。 diff --git a/engine/assets/builtin/shaders/gaussian-splat-composite.shader b/engine/assets/builtin/shaders/gaussian-splat-composite.shader deleted file mode 100644 index e5f2faf0..00000000 --- a/engine/assets/builtin/shaders/gaussian-splat-composite.shader +++ /dev/null @@ -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 - } - } -} diff --git a/engine/assets/builtin/shaders/gaussian-splat-utilities.shader b/engine/assets/builtin/shaders/gaussian-splat-utilities.shader index 656481bf..708f1f4b 100644 --- a/engine/assets/builtin/shaders/gaussian-splat-utilities.shader +++ b/engine/assets/builtin/shaders/gaussian-splat-utilities.shader @@ -229,21 +229,21 @@ Shader "Builtin Gaussian Splat Utilities" } float3 CalcCovariance2D( - float3 localPosition, + float3 viewPosition, float3 covariance3D0, float3 covariance3D1, - float4x4 modelViewMatrix, + float4x4 viewMatrix, float4x4 projectionMatrix, float2 screenSize) { - const float3 viewPosition = mul(modelViewMatrix, float4(localPosition, 1.0)).xyz; if (abs(viewPosition.z) <= 1.0e-5) { 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 tanFovY = rcp(projectionMatrix[1][1]); + const float tanFovY = rcp(projectionMatrix[1][1] * aspect); const float limitX = 1.3 * tanFovX; const float limitY = 1.3 * tanFovY; @@ -264,7 +264,7 @@ Shader "Builtin Gaussian Splat Utilities" 0.0, 0.0, 0.0); - const float3x3 worldToView = (float3x3)modelViewMatrix; + const float3x3 worldToView = (float3x3)viewMatrix; const float3x3 transform = mul(jacobian, worldToView); const float3x3 covariance3D = float3x3( covariance3D0.x, covariance3D0.y, covariance3D0.z, @@ -284,7 +284,7 @@ Shader "Builtin Gaussian Splat Utilities" const float offDiagonal = covariance2D.y; const float mid = 0.5 * (diagonal0 + diagonal1); 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); float2 basis = normalize(float2(offDiagonal, lambda0 - diagonal0)); @@ -425,22 +425,22 @@ Shader "Builtin Gaussian Splat Utilities" GaussianSplatSortDistances[index] = FloatToSortableUint(viewCenter.z); const float4 clipCenter = mul(gProjectionMatrix, float4(viewCenter, 1.0)); - const float nearClip = max(gCameraWorldPos.w, 1.0e-4); - if (clipCenter.w > 0.0 && viewCenter.z > nearClip) + if (clipCenter.w > 0.0) { - const float4x4 modelViewMatrix = mul(gViewMatrix, gModelMatrix); + const float3x3 modelLinear = (float3x3)gModelMatrix; const float3x3 rotationScaleMatrix = CalcMatrixFromRotationScale(otherData.rotation, otherData.scaleReserved.xyz); + const float3x3 worldRotationScale = mul(modelLinear, rotationScaleMatrix); float3 covariance3D0 = 0.0; float3 covariance3D1 = 0.0; - CalcCovariance3D(rotationScaleMatrix, covariance3D0, covariance3D1); + CalcCovariance3D(worldRotationScale, covariance3D0, covariance3D1); const float3 covariance2D = CalcCovariance2D( - localCenter, + viewCenter, covariance3D0, covariance3D1, - modelViewMatrix, + gViewMatrix, gProjectionMatrix, gScreenParams.xy); diff --git a/engine/assets/builtin/shaders/gaussian-splat.shader b/engine/assets/builtin/shaders/gaussian-splat.shader index 5ce5ac3a..93a86ab1 100644 --- a/engine/assets/builtin/shaders/gaussian-splat.shader +++ b/engine/assets/builtin/shaders/gaussian-splat.shader @@ -4,7 +4,6 @@ Shader "Builtin Gaussian Splat" { _PointScale ("Point Scale", Float) = 1.0 _OpacityScale ("Opacity Scale", Float) = 1.0 - _DebugViewMode ("Debug View Mode", Float) = 0.0 } HLSLINCLUDE cbuffer PerObjectConstants @@ -24,7 +23,6 @@ Shader "Builtin Gaussian Splat" { float4 gPointScaleParams; float4 gOpacityScaleParams; - float4 gDebugViewModeParams; }; struct GaussianSplatViewData @@ -93,14 +91,8 @@ Shader "Builtin Gaussian Splat" discard; } - if (gDebugViewModeParams.x >= 0.5) - { - return float4(alpha, alpha, alpha, alpha); - } - - return float4(input.colorOpacity.rgb * alpha, alpha); + return float4(input.colorOpacity.rgb, alpha); } - ENDHLSL SubShader @@ -113,13 +105,12 @@ Shader "Builtin Gaussian Splat" Cull Off ZWrite Off ZTest LEqual - Blend OneMinusDstAlpha One + Blend SrcAlpha OneMinusSrcAlpha HLSLPROGRAM #pragma target 4.5 #pragma vertex MainVS #pragma fragment MainPS ENDHLSL } - } } diff --git a/engine/include/XCEngine/Core/Asset/AssetDatabase.h b/engine/include/XCEngine/Core/Asset/AssetDatabase.h index a102e9ba..12f277d8 100644 --- a/engine/include/XCEngine/Core/Asset/AssetDatabase.h +++ b/engine/include/XCEngine/Core/Asset/AssetDatabase.h @@ -112,10 +112,6 @@ public: private: static constexpr Core::uint32 kBaseImporterVersion = 7; - enum class SourceHashPolicy : Core::uint8 { - PreserveOrClear = 0, - EnsureCurrent = 1 - }; void EnsureProjectLayout(); void LoadSourceAssetDB(); @@ -130,7 +126,6 @@ private: bool EnsureMetaForPath(const std::filesystem::path& sourcePath, bool isFolder, - SourceHashPolicy sourceHashPolicy, SourceAssetRecord& outRecord); bool ReadMetaFile(const std::filesystem::path& metaPath, SourceAssetRecord& inOutRecord) const; diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h b/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h index fdcd4d5e..22decc77 100644 --- a/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h @@ -70,16 +70,6 @@ private: 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 { const Resources::Shader* shader = nullptr; Containers::String passName; @@ -295,21 +285,10 @@ private: const RenderSurface& surface, const RenderSceneData& sceneData, 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::RHIType m_backendType = RHI::RHIType::D3D12; Resources::ResourceHandle m_builtinGaussianSplatShader; - Resources::ResourceHandle m_builtinGaussianSplatCompositeShader; Resources::ResourceHandle m_builtinGaussianSplatUtilitiesShader; std::unique_ptr m_builtinGaussianSplatMaterial; RenderResourceCache m_resourceCache; @@ -319,7 +298,6 @@ private: std::unordered_map m_pipelineStates; std::unordered_map m_computePipelineStates; std::unordered_map m_dynamicDescriptorSets; - CompositePipelineResources m_compositeResources = {}; }; } // namespace Passes diff --git a/engine/include/XCEngine/Resources/BuiltinResources.h b/engine/include/XCEngine/Resources/BuiltinResources.h index 92d75fce..72514396 100644 --- a/engine/include/XCEngine/Resources/BuiltinResources.h +++ b/engine/include/XCEngine/Resources/BuiltinResources.h @@ -39,7 +39,6 @@ Containers::String GetBuiltinSelectionMaskShaderPath(); Containers::String GetBuiltinSelectionOutlineShaderPath(); Containers::String GetBuiltinSkyboxShaderPath(); Containers::String GetBuiltinGaussianSplatShaderPath(); -Containers::String GetBuiltinGaussianSplatCompositeShaderPath(); Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath(); Containers::String GetBuiltinVolumetricShaderPath(); Containers::String GetBuiltinColorScalePostProcessShaderPath(); diff --git a/engine/src/Core/Asset/AssetDatabase.cpp b/engine/src/Core/Asset/AssetDatabase.cpp index 522df6db..a77a3613 100644 --- a/engine/src/Core/Asset/AssetDatabase.cpp +++ b/engine/src/Core/Asset/AssetDatabase.cpp @@ -689,7 +689,6 @@ std::vector CollectBuiltinShaderAssetPaths() { GetBuiltinSelectionOutlineShaderPath(), GetBuiltinSkyboxShaderPath(), GetBuiltinGaussianSplatShaderPath(), - GetBuiltinGaussianSplatCompositeShaderPath(), GetBuiltinGaussianSplatUtilitiesShaderPath(), GetBuiltinVolumetricShaderPath(), GetBuiltinColorScalePostProcessShaderPath(), @@ -1735,11 +1734,7 @@ bool AssetDatabase::ReimportAsset(const Containers::String& requestPath, } SourceAssetRecord sourceRecord; - if (!EnsureMetaForPath( - absoluteFsPath, - false, - SourceHashPolicy::EnsureCurrent, - sourceRecord)) { + if (!EnsureMetaForPath(absoluteFsPath, false, sourceRecord)) { SetLastErrorMessage(Containers::String("Failed to prepare asset metadata: ") + absolutePath); return false; } @@ -1811,35 +1806,18 @@ bool AssetDatabase::ReimportAllAssets(MaintenanceStats* outStats) { bool allSucceeded = true; MaintenanceStats localStats; 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; - if (!ImportAsset(currentRecord, rebuiltRecord)) { + if (!ImportAsset(record, rebuiltRecord)) { Debug::Logger::Get().Error( Debug::LogCategory::FileSystem, - Containers::String("[AssetDatabase] ReimportAllAssets failed path=") + currentRecord.relativePath); + Containers::String("[AssetDatabase] ReimportAllAssets failed path=") + record.relativePath); allSucceeded = false; continue; } - m_artifactsByGuid[currentRecord.guid] = rebuiltRecord; - m_sourcesByGuid[currentRecord.guid].lastKnownArtifactKey = rebuiltRecord.artifactKey; - m_sourcesByPathKey[ToStdString(MakeKey(currentRecord.relativePath))].lastKnownArtifactKey = - rebuiltRecord.artifactKey; + m_artifactsByGuid[record.guid] = rebuiltRecord; + m_sourcesByGuid[record.guid].lastKnownArtifactKey = rebuiltRecord.artifactKey; + m_sourcesByPathKey[ToStdString(MakeKey(record.relativePath))].lastKnownArtifactKey = rebuiltRecord.artifactKey; ++localStats.importedAssetCount; } @@ -2108,11 +2086,7 @@ void AssetDatabase::ScanAssetPath(const fs::path& path, const bool isFolder = fs::is_directory(path); SourceAssetRecord record; - if (EnsureMetaForPath( - path, - isFolder, - SourceHashPolicy::PreserveOrClear, - record)) { + if (EnsureMetaForPath(path, isFolder, record)) { seenPaths[ToStdString(MakeKey(record.relativePath))] = true; } @@ -2220,7 +2194,6 @@ Core::uint32 AssetDatabase::CleanupOrphanedArtifacts() const { bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath, bool isFolder, - SourceHashPolicy sourceHashPolicy, SourceAssetRecord& outRecord) { const Containers::String relativePath = NormalizeRelativePath(sourcePath); if (relativePath.Empty()) { @@ -2243,35 +2216,6 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath, outRecord.importerName = GetImporterNameForPath(relativePath, isFolder); 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 (!outRecord.guid.IsValid()) { outRecord.guid = HashStringToAssetGUID(NormalizePathString(sourcePath)); @@ -2289,7 +2233,24 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath, 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_sourcesByGuid[outRecord.guid] = outRecord; @@ -2329,7 +2290,24 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath, } 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_sourcesByGuid[outRecord.guid] = outRecord; @@ -2534,11 +2512,6 @@ bool AssetDatabase::ShouldReimport(const SourceAssetRecord& sourceRecord, return true; } - if (!sourceRecord.isFolder && - (sourceRecord.sourceHash.Empty() || artifactRecord->sourceHash.Empty())) { - return true; - } - return artifactRecord->importerVersion != sourceRecord.importerVersion || artifactRecord->sourceHash != sourceRecord.sourceHash || artifactRecord->metaHash != sourceRecord.metaHash || @@ -2607,14 +2580,8 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath, return false; } - const bool isFolder = fs::is_directory(absoluteFsPath); - SourceAssetRecord sourceRecord; - if (!EnsureMetaForPath( - absoluteFsPath, - isFolder, - SourceHashPolicy::PreserveOrClear, - sourceRecord)) { + if (!EnsureMetaForPath(absoluteFsPath, fs::is_directory(absoluteFsPath), sourceRecord)) { SetLastErrorMessage(Containers::String("Failed to prepare asset metadata: ") + absolutePath); return false; } @@ -2661,25 +2628,6 @@ bool AssetDatabase::EnsureArtifact(const Containers::String& requestPath, } 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)) { Debug::Logger::Get().Info( Debug::LogCategory::FileSystem, diff --git a/engine/src/RHI/D3D12/D3D12CommandList.cpp b/engine/src/RHI/D3D12/D3D12CommandList.cpp index 913b89b2..b5aab708 100644 --- a/engine/src/RHI/D3D12/D3D12CommandList.cpp +++ b/engine/src/RHI/D3D12/D3D12CommandList.cpp @@ -348,7 +348,7 @@ void D3D12CommandList::SetGraphicsDescriptorSets( } D3D12DescriptorHeap* heap = d3d12Set->GetHeap(); - if (heap == nullptr) { + if (heap == nullptr || !heap->IsShaderVisible()) { continue; } @@ -581,7 +581,7 @@ void D3D12CommandList::SetComputeDescriptorSets( } D3D12DescriptorHeap* heap = d3d12Set->GetHeap(); - if (heap == nullptr) { + if (heap == nullptr || !heap->IsShaderVisible()) { continue; } diff --git a/engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp b/engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp index 408ce0b0..a44156e5 100644 --- a/engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp +++ b/engine/src/RHI/D3D12/D3D12DescriptorHeap.cpp @@ -77,9 +77,6 @@ D3D12_CPU_DESCRIPTOR_HANDLE D3D12DescriptorHeap::GetCPUDescriptorHandleForHeapSt } D3D12_GPU_DESCRIPTOR_HANDLE D3D12DescriptorHeap::GetGPUDescriptorHandleForHeapStart() const { - if (!m_shaderVisible || m_descriptorHeap == nullptr) { - return D3D12_GPU_DESCRIPTOR_HANDLE{0}; - } return m_descriptorHeap->GetGPUDescriptorHandleForHeapStart(); } @@ -92,10 +89,6 @@ CPUDescriptorHandle D3D12DescriptorHeap::GetCPUDescriptorHandle(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(); handle.ptr += index * m_descriptorSize; GPUDescriptorHandle result; diff --git a/engine/src/RHI/D3D12/D3D12Device.cpp b/engine/src/RHI/D3D12/D3D12Device.cpp index 1a175ce4..760845d8 100644 --- a/engine/src/RHI/D3D12/D3D12Device.cpp +++ b/engine/src/RHI/D3D12/D3D12Device.cpp @@ -51,10 +51,6 @@ bool HasShaderPayload(const ShaderCompileDesc& desc) { 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) { const std::string fileName = NarrowAscii(desc.fileName); if (fileName.find("volumetric") != std::string::npos) { @@ -1283,9 +1279,6 @@ RHIDescriptorPool* D3D12Device::CreateDescriptorPool(const DescriptorPoolDesc& d auto* pool = new D3D12DescriptorHeap(); DescriptorPoolDesc poolDesc = desc; poolDesc.device = m_device.Get(); - if (UsesTransientShaderVisibleDescriptorHeap(poolDesc.type)) { - poolDesc.shaderVisible = false; - } if (pool->Initialize(poolDesc)) { return pool; } diff --git a/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp b/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp index 621fb059..5eb4cdda 100644 --- a/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp +++ b/engine/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp @@ -65,22 +65,6 @@ const Resources::ShaderPass* FindCompatibleComputePass( : 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::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, @@ -121,44 +105,6 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc( 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(RHI::PrimitiveTopologyType::Triangle); - ::XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(surface, pipelineDesc); - pipelineDesc.depthStencilFormat = - static_cast(::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::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, @@ -262,10 +208,6 @@ void BindDescriptorSetRanges( } } -RHI::Format ResolveGaussianAccumulationFormat() { - return RHI::Format::R16G16B16A16_Float; -} - } // namespace BuiltinGaussianSplatPass::~BuiltinGaussianSplatPass() { @@ -362,31 +304,10 @@ bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) { 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::RHIResourceView* renderTarget = colorAttachments[0]; + commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment()); + const RHI::Viewport viewport = { static_cast(renderArea.x), static_cast(renderArea.y), @@ -401,31 +322,9 @@ bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) { renderArea.x + renderArea.width, 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->SetScissorRect(scissorRect); commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); - commandList->ClearRenderTarget(accumulationRenderTarget, clearColor, 1u, clearRects); for (const VisibleGaussianSplatItem& visibleGaussianSplat : context.sceneData.visibleGaussianSplats) { if (!MarkVisibleGaussianSplatChunks( @@ -451,23 +350,14 @@ bool BuiltinGaussianSplatPass::Execute(const RenderPassContext& context) { if (!DrawVisibleGaussianSplat( context.renderContext, - accumulationRenderSurface, + context.surface, context.sceneData, visibleGaussianSplat)) { return false; } } - commandList->EndRenderPass(); - 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); + return true; } void BuiltinGaussianSplatPass::Shutdown() { @@ -482,8 +372,6 @@ bool BuiltinGaussianSplatPass::EnsureInitialized(const RenderContext& context) { if (m_device == context.device && m_backendType == context.backendType && m_builtinGaussianSplatShader.IsValid() && - m_builtinGaussianSplatCompositeShader.IsValid() && - m_builtinGaussianSplatUtilitiesShader.IsValid() && m_builtinGaussianSplatMaterial != nullptr) { return true; } @@ -505,16 +393,6 @@ bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) { return false; } - m_builtinGaussianSplatCompositeShader = Resources::ResourceManager::Get().Load( - 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::GetBuiltinGaussianSplatUtilitiesShaderPath()); if (!m_builtinGaussianSplatUtilitiesShader.IsValid()) { @@ -537,207 +415,6 @@ bool BuiltinGaussianSplatPass::CreateResources(const RenderContext& context) { 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(RHI::DescriptorType::SRV); - textureBinding.count = 1u; - textureBinding.visibility = static_cast(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& 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(renderArea.x), - static_cast(renderArea.y), - static_cast(renderArea.width), - static_cast(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() { if (m_passResources != nullptr) { m_passResources->Shutdown(); @@ -773,11 +450,8 @@ void BuiltinGaussianSplatPass::DestroyResources() { } m_passResourceLayouts.clear(); - DestroyCompositeResources(); - m_builtinGaussianSplatMaterial.reset(); m_builtinGaussianSplatUtilitiesShader.Reset(); - m_builtinGaussianSplatCompositeShader.Reset(); m_builtinGaussianSplatShader.Reset(); m_device = nullptr; m_backendType = RHI::RHIType::D3D12; @@ -1474,10 +1148,10 @@ bool BuiltinGaussianSplatPass::MarkVisibleGaussianSplatChunks( sceneData.cameraData.projection, sceneData.cameraData.view, visibleGaussianSplat.localToWorld.Transpose(), - visibleGaussianSplat.localToWorld.Inverse().Transpose(), + visibleGaussianSplat.localToWorld.Inverse(), Math::Vector4(sceneData.cameraData.worldRight, 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( static_cast(sceneData.cameraData.viewportWidth), static_cast(sceneData.cameraData.viewportHeight), @@ -1651,10 +1325,10 @@ bool BuiltinGaussianSplatPass::PrepareVisibleGaussianSplat( sceneData.cameraData.projection, sceneData.cameraData.view, visibleGaussianSplat.localToWorld.Transpose(), - visibleGaussianSplat.localToWorld.Inverse().Transpose(), + visibleGaussianSplat.localToWorld.Inverse(), Math::Vector4(sceneData.cameraData.worldRight, 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( static_cast(sceneData.cameraData.viewportWidth), static_cast(sceneData.cameraData.viewportHeight), @@ -1835,10 +1509,10 @@ bool BuiltinGaussianSplatPass::SortVisibleGaussianSplat( sceneData.cameraData.projection, sceneData.cameraData.view, visibleGaussianSplat.localToWorld.Transpose(), - visibleGaussianSplat.localToWorld.Inverse().Transpose(), + visibleGaussianSplat.localToWorld.Inverse(), Math::Vector4(sceneData.cameraData.worldRight, 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( static_cast(sceneData.cameraData.viewportWidth), static_cast(sceneData.cameraData.viewportHeight), @@ -2008,10 +1682,10 @@ bool BuiltinGaussianSplatPass::DrawVisibleGaussianSplat( sceneData.cameraData.projection, sceneData.cameraData.view, visibleGaussianSplat.localToWorld.Transpose(), - visibleGaussianSplat.localToWorld.Inverse().Transpose(), + visibleGaussianSplat.localToWorld.Inverse(), Math::Vector4(sceneData.cameraData.worldRight, 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( static_cast(sceneData.cameraData.viewportWidth), static_cast(sceneData.cameraData.viewportHeight), diff --git a/engine/src/Resources/BuiltinResources.cpp b/engine/src/Resources/BuiltinResources.cpp index cd4dd57f..19a2e60b 100644 --- a/engine/src/Resources/BuiltinResources.cpp +++ b/engine/src/Resources/BuiltinResources.cpp @@ -37,8 +37,6 @@ constexpr const char* kBuiltinSelectionMaskShaderPath = "builtin://shaders/selec constexpr const char* kBuiltinSelectionOutlineShaderPath = "builtin://shaders/selection-outline"; constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox"; constexpr const char* kBuiltinGaussianSplatShaderPath = "builtin://shaders/gaussian-splat"; -constexpr const char* kBuiltinGaussianSplatCompositeShaderPath = - "builtin://shaders/gaussian-splat-composite"; constexpr const char* kBuiltinGaussianSplatUtilitiesShaderPath = "builtin://shaders/gaussian-splat-utilities"; constexpr const char* kBuiltinVolumetricShaderPath = "builtin://shaders/volumetric"; @@ -75,8 +73,6 @@ constexpr const char* kBuiltinSkyboxShaderAssetRelativePath = "engine/assets/builtin/shaders/skybox.shader"; constexpr const char* kBuiltinGaussianSplatShaderAssetRelativePath = "engine/assets/builtin/shaders/gaussian-splat.shader"; -constexpr const char* kBuiltinGaussianSplatCompositeShaderAssetRelativePath = - "engine/assets/builtin/shaders/gaussian-splat-composite.shader"; constexpr const char* kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath = "engine/assets/builtin/shaders/gaussian-splat-utilities.shader"; constexpr const char* kBuiltinVolumetricShaderAssetRelativePath = @@ -178,9 +174,6 @@ const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinS if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatShaderPath)) { return kBuiltinGaussianSplatShaderAssetRelativePath; } - if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatCompositeShaderPath)) { - return kBuiltinGaussianSplatCompositeShaderAssetRelativePath; - } if (builtinShaderPath == Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath)) { return kBuiltinGaussianSplatUtilitiesShaderAssetRelativePath; } @@ -761,10 +754,6 @@ Shader* BuildBuiltinGaussianSplatShader(const Containers::String& path) { return TryLoadBuiltinShaderFromAsset(path); } -Shader* BuildBuiltinGaussianSplatCompositeShader(const Containers::String& path) { - return TryLoadBuiltinShaderFromAsset(path); -} - Shader* BuildBuiltinVolumetricShader(const Containers::String& path) { return TryLoadBuiltinShaderFromAsset(path); } @@ -887,10 +876,6 @@ bool TryGetBuiltinShaderPathByShaderName( outPath = GetBuiltinGaussianSplatShaderPath(); return true; } - if (shaderName == "Builtin Gaussian Splat Composite") { - outPath = GetBuiltinGaussianSplatCompositeShaderPath(); - return true; - } if (shaderName == "Builtin Gaussian Splat Utilities") { outPath = GetBuiltinGaussianSplatUtilitiesShaderPath(); return true; @@ -986,10 +971,6 @@ Containers::String GetBuiltinGaussianSplatShaderPath() { return Containers::String(kBuiltinGaussianSplatShaderPath); } -Containers::String GetBuiltinGaussianSplatCompositeShaderPath() { - return Containers::String(kBuiltinGaussianSplatCompositeShaderPath); -} - Containers::String GetBuiltinGaussianSplatUtilitiesShaderPath() { return Containers::String(kBuiltinGaussianSplatUtilitiesShaderPath); } @@ -1114,8 +1095,6 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) { shader = BuildBuiltinSkyboxShader(path); } else if (path == GetBuiltinGaussianSplatShaderPath()) { shader = BuildBuiltinGaussianSplatShader(path); - } else if (path == GetBuiltinGaussianSplatCompositeShaderPath()) { - shader = BuildBuiltinGaussianSplatCompositeShader(path); } else if (path == GetBuiltinGaussianSplatUtilitiesShaderPath()) { shader = TryLoadBuiltinShaderFromAsset(path); } else if (path == GetBuiltinVolumetricShaderPath()) { diff --git a/project/Assets/Scenes/Main.xc b/project/Assets/Scenes/Main.xc index c9c09f38..69771a30 100644 --- a/project/Assets/Scenes/Main.xc +++ b/project/Assets/Scenes/Main.xc @@ -23,7 +23,7 @@ active=1 layer=0 parent=0 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_begin diff --git a/project/Assets/Scenes/NahidaPreview.xc b/project/Assets/Scenes/NahidaPreview.xc index 4821365c..fdd4b160 100644 --- a/project/Assets/Scenes/NahidaPreview.xc +++ b/project/Assets/Scenes/NahidaPreview.xc @@ -10,7 +10,7 @@ tag=Untagged active=1 layer=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; gameobject_end diff --git a/tests/RHI/D3D12/unit/test_backend_specific.cpp b/tests/RHI/D3D12/unit/test_backend_specific.cpp index bb2347c3..e500dddb 100644 --- a/tests/RHI/D3D12/unit/test_backend_specific.cpp +++ b/tests/RHI/D3D12/unit/test_backend_specific.cpp @@ -1,6 +1,5 @@ #include "fixtures/D3D12TestFixture.h" -#include "XCEngine/RHI/D3D12/D3D12DescriptorHeap.h" #include "XCEngine/RHI/D3D12/D3D12DescriptorSet.h" #include "XCEngine/RHI/D3D12/D3D12PipelineLayout.h" #include "XCEngine/RHI/RHIDescriptorPool.h" @@ -55,64 +54,6 @@ TEST_F(D3D12TestFixture, DescriptorSet_MixedBindings_AssignDescriptorIndicesByTy 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(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(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(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(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) { DescriptorPoolDesc poolDesc = {}; poolDesc.type = DescriptorHeapType::CBV_SRV_UAV; diff --git a/tests/Rendering/integration/gaussian_splat_scene/main.cpp b/tests/Rendering/integration/gaussian_splat_scene/main.cpp index fbd996f7..7ddac325 100644 --- a/tests/Rendering/integration/gaussian_splat_scene/main.cpp +++ b/tests/Rendering/integration/gaussian_splat_scene/main.cpp @@ -28,13 +28,10 @@ #include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h" #include -#include -#include #include #include #include #include -#include #include #include @@ -51,23 +48,12 @@ namespace { constexpr const char* kD3D12Screenshot = "gaussian_splat_scene_d3d12.ppm"; constexpr const char* kOpenGLScreenshot = "gaussian_splat_scene_opengl.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 kFrameHeight = 720; -constexpr uint32_t kBaselineSubsetSplatCount = 262144u; +constexpr uint32_t kBaselineSubsetSplatCount = 65536u; constexpr const char* kSubsetGaussianSplatAssetPath = "Assets/room_subset.xcgsplat"; constexpr float kTargetSceneExtent = 4.0f; -constexpr float kGaussianPointScale = 1.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 -}; +constexpr float kGaussianPointScale = 3.00f; XCEngine::Core::uint16 FloatToHalfBits(float value) { uint32_t bits = 0u; @@ -142,178 +128,15 @@ void LinkOrCopyFixture(const std::filesystem::path& sourcePath, const std::files ASSERT_FALSE(ec) << ec.message(); } -GaussianSplatDebugView GetDebugViewFromEnvironment() { - 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(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( - source.GetSectionData(GaussianSplatSectionType::Chunks)); - const auto* loadedChunkSection = static_cast( - 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) { +const char* GetScreenshotFilename(RHIType backendType) { switch (backendType) { case RHIType::D3D12: - return debugView == GaussianSplatDebugView::Alpha - ? kD3D12AlphaDebugScreenshot - : kD3D12Screenshot; + return kD3D12Screenshot; case RHIType::Vulkan: - return debugView == GaussianSplatDebugView::Alpha - ? kVulkanAlphaDebugScreenshot - : kVulkanScreenshot; + return kVulkanScreenshot; case RHIType::OpenGL: default: - return debugView == GaussianSplatDebugView::Alpha - ? kOpenGLAlphaDebugScreenshot - : kOpenGLScreenshot; + return kOpenGLScreenshot; } } @@ -357,8 +180,6 @@ GaussianSplat* CreateGaussianSplatSubset( std::vector subsetOther(subsetSplatCount); std::vector subsetColors(subsetSplatCount); std::vector subsetSh(shSection != nullptr && sh != nullptr ? subsetSplatCount : 0u); - Bounds subsetBounds; - bool hasSubsetBounds = false; for (uint32_t subsetIndex = 0u; subsetIndex < subsetSplatCount; ++subsetIndex) { const uint32_t sourceIndex = selectedIndices[subsetIndex]; subsetPositions[subsetIndex] = positions[sourceIndex]; @@ -367,14 +188,6 @@ GaussianSplat* CreateGaussianSplatSubset( if (!subsetSh.empty()) { 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 = @@ -483,7 +296,7 @@ GaussianSplat* CreateGaussianSplatSubset( GaussianSplatMetadata metadata = source.GetMetadata(); metadata.splatCount = subsetSplatCount; - metadata.bounds = subsetBounds; + metadata.bounds = source.GetBounds(); metadata.chunkCount = subsetChunkCount; metadata.cameraCount = 0u; metadata.chunkFormat = GaussianSplatSectionFormat::ChunkFloat32; @@ -523,12 +336,10 @@ private: ResourceHandle m_gaussianSplat; ResourceHandle m_subsetGaussianSplat; Material* m_material = nullptr; - GaussianSplatDebugView m_debugView = GaussianSplatDebugView::Scene; }; void GaussianSplatSceneTest::SetUp() { RHIIntegrationFixture::SetUp(); - m_debugView = GetDebugViewFromEnvironment(); PrepareRuntimeProject(); m_sceneRenderer = std::make_unique(); @@ -641,7 +452,7 @@ void GaussianSplatSceneTest::PrepareRuntimeProject() { std::unique_ptr subsetGaussianSplat( CreateGaussianSplatSubset( *m_gaussianSplat.Get(), - GetUIntFromEnvironment("XCENGINE_GAUSSIAN_SPLAT_SUBSET_COUNT", kBaselineSubsetSplatCount), + kBaselineSubsetSplatCount, kSubsetGaussianSplatAssetPath)); ASSERT_NE(subsetGaussianSplat, nullptr); ASSERT_TRUE(subsetGaussianSplat->IsValid()); @@ -663,7 +474,6 @@ void GaussianSplatSceneTest::PrepareRuntimeProject() { ASSERT_GT(m_subsetGaussianSplat->GetSplatCount(), 0u); ASSERT_EQ(m_subsetGaussianSplat->GetSHOrder(), 3u); ASSERT_NE(m_subsetGaussianSplat->FindSection(GaussianSplatSectionType::Chunks), nullptr); - ExpectGaussianSplatRoundTripMatches(*subsetGaussianSplat, *m_subsetGaussianSplat.Get()); database.Shutdown(); } @@ -680,11 +490,8 @@ void GaussianSplatSceneTest::BuildScene() { m_material->Initialize(params); m_material->SetShader(ResourceManager::Get().Load(GetBuiltinGaussianSplatShaderPath())); m_material->SetRenderQueue(MaterialRenderQueue::Transparent); - m_material->SetFloat( - "_PointScale", - GetFloatFromEnvironment("XCENGINE_GAUSSIAN_SPLAT_POINT_SCALE", kGaussianPointScale)); + m_material->SetFloat("_PointScale", kGaussianPointScale); m_material->SetFloat("_OpacityScale", 1.0f); - m_material->SetFloat("_DebugViewMode", m_debugView == GaussianSplatDebugView::Alpha ? 1.0f : 0.0f); GameObject* cameraObject = m_scene->CreateGameObject("MainCamera"); auto* camera = cameraObject->AddComponent(); @@ -705,12 +512,10 @@ void GaussianSplatSceneTest::BuildScene() { const float sizeY = std::max(boundsMax.y - boundsMin.y, 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 uniformScale = - GetFloatFromEnvironment("XCENGINE_GAUSSIAN_SPLAT_TARGET_EXTENT", kTargetSceneExtent) / maxExtent; + const float uniformScale = kTargetSceneExtent / maxExtent; GameObject* root = m_scene->CreateGameObject("GaussianSplatRoot"); - root->GetTransform()->SetLocalPosition( - GetVector3FromEnvironment("XCENGINE_GAUSSIAN_SPLAT_ROOT_POS", kDefaultRootPosition)); + root->GetTransform()->SetLocalPosition(Vector3::Zero()); root->GetTransform()->SetLocalScale(Vector3(uniformScale, uniformScale, uniformScale)); GameObject* splatObject = m_scene->CreateGameObject("RoomGaussianSplat"); @@ -722,11 +527,7 @@ void GaussianSplatSceneTest::BuildScene() { splatRenderer->SetMaterial(m_material); splatRenderer->SetCastShadows(false); splatRenderer->SetReceiveShadows(false); - - cameraObject->GetTransform()->SetLocalPosition( - GetVector3FromEnvironment("XCENGINE_GAUSSIAN_SPLAT_CAMERA_POS", kDefaultCameraPosition)); - cameraObject->GetTransform()->LookAt( - GetVector3FromEnvironment("XCENGINE_GAUSSIAN_SPLAT_CAMERA_LOOK_AT", kDefaultCameraLookAt)); + cameraObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 1.0f, 1.0f)); } RHIResourceView* GaussianSplatSceneTest::GetCurrentBackBufferView() { @@ -784,8 +585,7 @@ TEST_P(GaussianSplatSceneTest, RenderRoomGaussianSplatScene) { ASSERT_NE(swapChain, nullptr); constexpr int kTargetFrameCount = 2; - const GaussianSplatDebugView debugView = GetDebugViewFromEnvironment(); - const char* screenshotFilename = GetScreenshotFilename(GetBackendType(), debugView); + const char* screenshotFilename = GetScreenshotFilename(GetBackendType()); for (int frameCount = 0; frameCount <= kTargetFrameCount; ++frameCount) { if (frameCount > 0) { @@ -799,11 +599,6 @@ TEST_P(GaussianSplatSceneTest, RenderRoomGaussianSplatScene) { commandQueue->WaitForIdle(); 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"); if (!std::filesystem::exists(gtPath)) { GTEST_SKIP() << "GT.ppm missing, screenshot captured for baseline generation: " << screenshotFilename; diff --git a/tests/Rendering/integration/nahida_preview_scene/main.cpp b/tests/Rendering/integration/nahida_preview_scene/main.cpp index d0a664c7..7f1e8881 100644 --- a/tests/Rendering/integration/nahida_preview_scene/main.cpp +++ b/tests/Rendering/integration/nahida_preview_scene/main.cpp @@ -100,18 +100,6 @@ std::unordered_set GetIsolationObjectNames() { std::unordered_set result; const char* value = std::getenv("XC_NAHIDA_DIAG_ONLY"); 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; } diff --git a/tests/core/Asset/test_resource_manager.cpp b/tests/core/Asset/test_resource_manager.cpp index c8348a0f..8e5b7287 100644 --- a/tests/core/Asset/test_resource_manager.cpp +++ b/tests/core/Asset/test_resource_manager.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -110,19 +109,6 @@ bool PumpAsyncLoadsUntil(ResourceManager& manager, 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) { std::error_code 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(); } -std::vector SplitTabSeparatedLine(const std::string& line) { - std::vector 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 fields = SplitTabSeparatedLine(line); - if (fields.size() > 7 && fields[1] == relativePath) { - return fields[7]; - } - } - - return std::string(); -} - std::vector ListArtifactEntries(const std::filesystem::path& artifactsRoot) { namespace fs = std::filesystem; @@ -470,47 +424,6 @@ TEST(AssetImportService_Test, EnsureArtifactExposesContainerEntryRuntimeLoadPath 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) { namespace fs = std::filesystem; @@ -632,58 +545,6 @@ TEST(ResourceManager_Test, SetResourceRootBootstrapsProjectAssetCache) { 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) { namespace fs = std::filesystem; diff --git a/tests/editor/nahida_preview_regenerator.cpp b/tests/editor/nahida_preview_regenerator.cpp index 562343d6..05c8a80d 100644 --- a/tests/editor/nahida_preview_regenerator.cpp +++ b/tests/editor/nahida_preview_regenerator.cpp @@ -39,7 +39,7 @@ std::shared_ptr MakeModelAssetItem( void ConfigurePreviewCamera(XCEngine::Components::GameObject& gameObject) { 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)); auto* camera = gameObject.AddComponent();