docs(asset): sync model and volume api docs

This commit is contained in:
2026-04-10 18:17:19 +08:00
parent 0602b34652
commit effca78771
7 changed files with 419 additions and 480 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,51 @@
# VolumeField::Create
**命名空间**: `XCEngine::Resources`
**类型**: `method`
**头文件**: `XCEngine/Resources/Volume/VolumeField.h`
**源文件**: `engine/src/Resources/Volume/VolumeField.cpp`
## 签名
```cpp
bool Create(
VolumeStorageKind storageKind,
const void* payload,
size_t payloadSize,
const Math::Bounds& bounds = Math::Bounds(),
const Math::Vector3& voxelSize = Math::Vector3::Zero(),
const VolumeIndexBounds& indexBounds = VolumeIndexBounds(),
Core::uint32 gridType = 0u,
Core::uint32 gridClass = 0u);
```
## 作用
用“拷贝输入内存”的方式初始化 `VolumeField`。调用完成后,资源对象会持有一份独立的 payload 副本。
## 当前实现
当前实现会:
1. 拒绝 `payload == nullptr``payloadSize == 0`
2. 写入存储类型、bounds、voxel size、index bounds 与 grid 元数据
3. 为内部 `m_payload` 分配空间
4. 通过 `std::memcpy` 拷贝整份 payload
5. 标记资源为 valid
6. 更新 `memorySize`
## 与 CreateOwned 的区别
- `Create(...)` 适合输入数据生命周期不由调用方转移,或者数据来源只是只读外部缓冲
- [CreateOwned](CreateOwned.md) 适合调用方已经拥有 `Containers::Array<Core::uint8>`,希望直接把所有权移动给资源对象
如果 payload 很大,且调用方本来就拥有可移动数组,优先使用 `CreateOwned(...)`,可以避免一次完整拷贝。
## 相关文档
- [VolumeField](VolumeField.md)
- [CreateOwned](CreateOwned.md)
- [VolumeFieldLoader](../VolumeFieldLoader/VolumeFieldLoader.md)

View File

@@ -0,0 +1,56 @@
# VolumeField::CreateOwned
**命名空间**: `XCEngine::Resources`
**类型**: `method`
**头文件**: `XCEngine/Resources/Volume/VolumeField.h`
**源文件**: `engine/src/Resources/Volume/VolumeField.cpp`
## 签名
```cpp
bool CreateOwned(
VolumeStorageKind storageKind,
Containers::Array<Core::uint8>&& payload,
const Math::Bounds& bounds = Math::Bounds(),
const Math::Vector3& voxelSize = Math::Vector3::Zero(),
const VolumeIndexBounds& indexBounds = VolumeIndexBounds(),
Core::uint32 gridType = 0u,
Core::uint32 gridClass = 0u);
```
## 作用
用“转移所有权”的方式初始化 `VolumeField`。与 [Create](Create.md) 不同,这个方法不会再复制 payload而是直接接管传入的字节数组。
## 当前实现
当前实现会:
1. 拒绝空 payload
2. 通过内部 `ApplyMetadata(...)` 写入存储类型、bounds、voxel size、index bounds 与 grid 元数据
3.`payload` 移动到 `m_payload`
4. 标记资源为 valid
5. 更新 `memorySize`
## 设计意义
这条路径主要服务于 loader
- 读取 `.xcvol` artifact 时payload 已经以字节数组形式存在
- 读取 `.nvdb` 源文件时,也会先把 handle 数据复制进 `Containers::Array<Core::uint8>`
此时如果再走 `Create(...)`,就会额外产生一次完整拷贝。`CreateOwned(...)` 避免了这一步,对大体积资源尤其重要。
## 使用建议
- 当调用方已经拥有可转移的 `Containers::Array<Core::uint8>` 时,优先使用本方法
- 当数据来源是只读外部缓冲或生命周期不受调用方控制时,再使用 [Create](Create.md)
## 相关文档
- [VolumeField](VolumeField.md)
- [Create](Create.md)
- [VolumeFieldLoader](../VolumeFieldLoader/VolumeFieldLoader.md)

View File

@@ -8,57 +8,91 @@
**源文件**: `engine/src/Resources/Volume/VolumeField.cpp` **源文件**: `engine/src/Resources/Volume/VolumeField.cpp`
**描述**: 体积 payload 的运行时资源对象,保存存储类型、包围盒、体素尺寸、索引边界和原始字节载 **描述**: 体积 payload 的运行时资源对象,保存存储类型、包围盒、体素尺寸、索引边界和原始字节载。
## 概述 ## 角色概述
`VolumeField` 是当前体积资源在运行时的最小封装。它并不解析体积体素内容,而是把 loader 导入的 payload 元数据原样保存下来,供后续: `VolumeField` 是当前体积资源在运行时的最小封装。它不直接解析体素内容,也不暴露采样级 API它的职责是把 loader 或 artifact 读到的 payload 元数据安全地装进统一资源对象,供后续缓存、组件和渲染系统消费。
- 场景组件绑定
- artifact 重建
- `RenderResourceCache` 上传 structured-buffer
使用。
## 当前状态模型 ## 当前状态模型
| 字段 | 说明 | | 字段 | 说明 |
|------|------| |------|------|
| `m_storageKind` | 当前体积存储类型,现阶段主要是 `NanoVDB` | | `m_storageKind` | 当前体积存储类型,现阶段主要是 `NanoVDB` |
| `m_bounds` | 体积世界/局部包围盒 | | `m_bounds` | 体积包围盒 |
| `m_voxelSize` | 体素尺寸 | | `m_voxelSize` | 体素尺寸 |
| `m_indexBounds` | 体素索引空间边界 | | `m_indexBounds` | 体素索引边界 |
| `m_gridType` / `m_gridClass` | NanoVDB metadata 的轻量转存 | | `m_gridType` / `m_gridClass` | 体积格式元数据 |
| `m_payload` | 原始 payload 字节数组 | | `m_payload` | 原始 payload 字节数组 |
## 两条创建路径
### [Create](Create.md)
`Create(...)` 接收外部只读内存并执行一次拷贝。这条路径适合调用方只临时持有输入数据,或者数据源本身不可转移所有权的场景。
### [CreateOwned](CreateOwned.md)
这是本轮新增的重要接口:
```cpp
bool CreateOwned(
VolumeStorageKind storageKind,
Containers::Array<Core::uint8>&& payload,
const Math::Bounds& bounds = Math::Bounds(),
const Math::Vector3& voxelSize = Math::Vector3::Zero(),
const VolumeIndexBounds& indexBounds = VolumeIndexBounds(),
Core::uint32 gridType = 0u,
Core::uint32 gridClass = 0u);
```
它允许调用方把已经拥有的 payload 直接移动进 `VolumeField`,避免再做一份内存复制。
## 为什么 CreateOwned 很重要
体积资源通常 payload 很大。对 `.xcvol` artifact 或 `.nvdb` 源文件来说loader 往往已经先把整块数据读进了 `Containers::Array<Core::uint8>`。如果再走一次 `Create(...)`,就会产生第二次大块拷贝。
`CreateOwned(...)` 的设计价值正是:
- loader 读入 payload 后可以直接转移所有权
- `VolumeField` 仍然保持统一的内部数据结构
- 避免无意义的二次复制和额外峰值内存
这类“copy path + move path”双入口在商业引擎资源系统里非常常见尤其适合大块二进制资源。
## 当前实现行为 ## 当前实现行为
- `Create(...)` - `Create(...)`
- `payload == nullptr``payloadSize == 0`直接失败 - `payload == nullptr``payloadSize == 0` 时失败
- 成功时会复制整份 payload,而不是借用外部内存 - 会复制整份 payload
- 会写入边界、体素尺寸、索引边界与 grid metadata - 成功后标记资源为 valid 并更新 `memorySize`
- 成功后把资源标记为 valid并更新 `memorySize` - `CreateOwned(...)`
- `GetWorldBounds()` 当前只是 `GetBounds()` 的别名 - `payload.Empty()` 时失败
- `Release()` 当前直接执行 `delete this` - 会移动 `payload`,不再复制
- 同样会写入元数据、标记 valid 并更新 `memorySize`
- 两条路径现在共享内部元数据填充逻辑 `ApplyMetadata(...)`
- `GetWorldBounds()` 当前仍然只是 `GetBounds()` 的别名
- `Release()` 依旧执行 `delete this`
## 测试与调用链 ## 当前调用链
- `tests/Resources/Volume/test_volume_field.cpp` - `LoadVolumeFieldArtifact()` 现在会把 artifact 中读出的 payload 通过 `CreateOwned(...)` 直接转移给资源
- 覆盖 payload 拷贝、边界元数据与 `GetMemorySize()` - `LoadNanoVDBSourceFile()` 也会先组装 `Containers::Array<Core::uint8>`,再通过 `CreateOwned(...)` 交给资源
- `engine/src/Core/Asset/AssetDatabase.cpp` - `RenderResourceCache` 会继续把 payload 上传成体积相关 GPU 资源
- 当前会把本资源写回 `.xcvol` artifact
- `engine/src/Rendering/Caches/RenderResourceCache.cpp`
- 当前把 payload 上传成体积 structured-buffer
## 当前实现边界 这说明 `CreateOwned(...)` 已经不是预留接口,而是 loader 的真实生产路径。
- 当前只保存 payload不提供体素级查询 API。 ## 当前边界
- `Create(...)` 是一次性初始化入口,没有增量修改协议。
- `GetWorldBounds()` 尚未引入独立于 `GetBounds()` 的额外变换语义。 - `VolumeField` 仍然只保存 payload不负责体素级查询
- 它仍然是一次性初始化对象,没有增量修改协议
- 运行时世界空间语义目前没有超出 `bounds` 本身
## 相关文档 ## 相关文档
- [Volume](../Volume.md) - [Volume](../Volume.md)
- [CreateOwned](CreateOwned.md)
- [VolumeFieldLoader](../VolumeFieldLoader/VolumeFieldLoader.md) - [VolumeFieldLoader](../VolumeFieldLoader/VolumeFieldLoader.md)
- [VolumeRendererComponent](../../../Components/VolumeRendererComponent/VolumeRendererComponent.md) - [VolumeRendererComponent](../../../Components/VolumeRendererComponent/VolumeRendererComponent.md)
- [BuiltinVolumetricPass](../../../Rendering/Passes/BuiltinVolumetricPass/BuiltinVolumetricPass.md) - [BuiltinVolumetricPass](../../../Rendering/Passes/BuiltinVolumetricPass/BuiltinVolumetricPass.md)
- [API 总索引](../../../../main.md)