docs(asset): sync model and volume api docs
This commit is contained in:
@@ -6,301 +6,147 @@
|
||||
|
||||
**头文件**: `XCEngine/Core/Asset/ArtifactFormats.h`
|
||||
|
||||
**描述**: 定义 `Texture`、`Material`、`Mesh` 与 `Shader` artifact 的二进制头结构和 schema 常量,供 `AssetDatabase` 写出、各 loader 回读共享。
|
||||
**描述**: 定义项目 artifact 的二进制头结构、schema 常量与记录布局,供 `AssetDatabase` 写出、各类 loader 回读以及资源系统共享使用。
|
||||
|
||||
## 角色概述
|
||||
|
||||
`ArtifactFormats.h` 是项目资产导入链路的磁盘格式契约层。
|
||||
`ArtifactFormats.h` 是资产导入链路里的磁盘格式契约层。它回答的是:
|
||||
|
||||
它回答的是:
|
||||
- 某类 source asset 导入后会写出什么主 artifact
|
||||
- 二进制文件头里有哪些 schema 常量和 magic
|
||||
- loader 回读时应如何解释结构化头信息
|
||||
|
||||
- `AssetDatabase` 把 source asset 导入到 `Library/Artifacts/...` 时,头结构和 magic 是什么
|
||||
- `TextureLoader`、`MaterialLoader`、`MeshLoader`、`ShaderLoader` 回读时,二进制布局该怎样解释
|
||||
|
||||
它不负责:
|
||||
|
||||
- 扫描 `Assets`
|
||||
- 生成 GUID
|
||||
- 维护 source / artifact 数据库
|
||||
- 直接触发运行时异步加载
|
||||
它本身不负责扫描项目、生成 GUID 或执行导入;它只定义“写什么”和“读什么”。
|
||||
|
||||
## 当前 schema 常量
|
||||
|
||||
- `kTextureArtifactSchemaVersion = 1`
|
||||
- `kMaterialArtifactSchemaVersion = 4`
|
||||
- `kMeshArtifactSchemaVersion = 2`
|
||||
- `kShaderArtifactSchemaVersion = 4`
|
||||
- `kShaderArtifactSchemaVersion = 5`
|
||||
- `kUIDocumentArtifactSchemaVersion = 2`
|
||||
- `kVolumeFieldArtifactSchemaVersion = 2`
|
||||
- `kModelArtifactSchemaVersion = 1`
|
||||
- `kGaussianSplatArtifactSchemaVersion = 1`
|
||||
|
||||
## Texture artifact: `.xctex`
|
||||
这些常量说明 artifact 契约已经不再只覆盖传统的贴图、材质、网格和 shader,模型层级资源与 Gaussian Splat 资源也已经进入了格式层。
|
||||
|
||||
- 头结构: `TextureArtifactHeader`
|
||||
## 当前格式覆盖
|
||||
|
||||
### Texture artifact: `.xctex`
|
||||
|
||||
- 文件头: `TextureArtifactHeader`
|
||||
- magic: `XCTEX01`
|
||||
- 记录纹理维度、格式、mip 层数、数组大小与像素 payload 大小
|
||||
|
||||
`TextureArtifactHeader` 当前记录:
|
||||
|
||||
- `textureType`
|
||||
- `textureFormat`
|
||||
- `width`
|
||||
- `height`
|
||||
- `depth`
|
||||
- `mipLevels`
|
||||
- `arraySize`
|
||||
- `pixelDataSize`
|
||||
|
||||
写入顺序当前是:
|
||||
|
||||
1. `TextureArtifactHeader`
|
||||
2. 原始像素 payload
|
||||
|
||||
## Material artifact: `.xcmat`
|
||||
### Material artifact: `.xcmat`
|
||||
|
||||
- 文件头: `MaterialArtifactFileHeader`
|
||||
- 主头结构: `MaterialArtifactHeader`
|
||||
- 属性结构: `MaterialPropertyArtifact`
|
||||
- 主头: `MaterialArtifactHeader`
|
||||
- 属性记录: `MaterialPropertyArtifact`
|
||||
- schema: `4`
|
||||
- magic: `XCMAT04`
|
||||
- 支持 render-state override、keyword、property 与 texture binding
|
||||
|
||||
### 当前正文布局
|
||||
### Mesh artifact: `.xcmesh`
|
||||
|
||||
`.xcmat` 当前不是“一个大 struct 一次性写完”,而是“文件头 + 变长字符串段 + 固定头 + 变长数组”的布局:
|
||||
|
||||
1. `MaterialArtifactFileHeader`
|
||||
2. 材质名字符串
|
||||
3. source 材质路径字符串
|
||||
4. shader 路径字符串
|
||||
5. shader pass 字符串
|
||||
6. `MaterialArtifactHeader`
|
||||
7. `tagCount` 对 tag 名/值字符串
|
||||
8. `keywordCount` 个 keyword 字符串
|
||||
9. `propertyCount` 个属性名字符串 + `MaterialPropertyArtifact`
|
||||
10. `textureBindingCount` 组 texture binding 三元组:
|
||||
- binding name
|
||||
- 编码后的 `AssetRef` 字符串
|
||||
- 可选 texture path 字符串
|
||||
|
||||
### `MaterialArtifactHeader`
|
||||
|
||||
当前记录:
|
||||
|
||||
- `renderQueue`
|
||||
- `renderState`
|
||||
- `tagCount`
|
||||
- `hasRenderStateOverride`
|
||||
- `keywordCount`
|
||||
- `propertyCount`
|
||||
- `textureBindingCount`
|
||||
|
||||
### `MaterialPropertyArtifact`
|
||||
|
||||
只记录:
|
||||
|
||||
- `propertyType`
|
||||
- `MaterialProperty::Value`
|
||||
|
||||
当前 writer 只把非 texture 属性写进 property 段;texture / cubemap 绑定统一走 texture binding 段。
|
||||
|
||||
### render-state override 语义
|
||||
|
||||
`MaterialArtifactHeader::hasRenderStateOverride` 当前显式区分两种情况:
|
||||
|
||||
- 材质真的声明了 `renderState`
|
||||
- 材质只是保留了一份默认 `MaterialRenderState` 缓存
|
||||
|
||||
回读侧 `MaterialLoader` 当前会据此恢复 `HasRenderStateOverride()`。
|
||||
|
||||
### texture binding 语义
|
||||
|
||||
texture binding 当前不再只保存 path。
|
||||
|
||||
写入侧 `AssetDatabase::WriteMaterialArtifactFile(...)` 会尽量为每个 binding 同时保存:
|
||||
|
||||
- 编码 `AssetRef`
|
||||
格式是 `guid,localID,resourceTypeInt`
|
||||
- 可选 path
|
||||
|
||||
`AssetRef` 的求值优先级当前是:
|
||||
|
||||
1. binding 自己显式持有的 `AssetRef`
|
||||
2. 已加载纹理句柄映射出的 `AssetRef`
|
||||
3. 纹理 path 反查得到的 `AssetRef`
|
||||
|
||||
path 的求值优先级当前是:
|
||||
|
||||
1. 已加载纹理映射出的 artifact / source path
|
||||
2. binding 自己保存的 `texturePath`
|
||||
|
||||
回读侧 `MaterialLoader` 当前会:
|
||||
|
||||
- 先解码 `AssetRef`
|
||||
- 再解析 path
|
||||
- 有效 `AssetRef` 优先走 `Material::SetTextureAssetRef(...)`
|
||||
- 否则退到 `Material::SetTexturePath(...)`
|
||||
|
||||
这意味着当前 `.xcmat` 版本可以只靠 `AssetRef` 恢复稳定引用,path 只作为可选辅助信息存在。
|
||||
|
||||
## Mesh artifact: `.xcmesh`
|
||||
|
||||
- 头结构: `MeshArtifactHeader`
|
||||
- 文件头: `MeshArtifactHeader`
|
||||
- schema: `2`
|
||||
- magic: `XCMESH2`
|
||||
- 记录顶点流、索引流、section、bounds 与材质引用路径
|
||||
|
||||
`MeshArtifactHeader` 当前记录:
|
||||
### Model artifact: `.xcmodel`
|
||||
|
||||
- `vertexCount`
|
||||
- `vertexStride`
|
||||
- `vertexAttributes`
|
||||
- `indexCount`
|
||||
- `use32BitIndex`
|
||||
- `sectionCount`
|
||||
- `materialCount`
|
||||
- `boundsMin`
|
||||
- `boundsMax`
|
||||
- `vertexDataSize`
|
||||
- `indexDataSize`
|
||||
这是本轮最关键的新内容之一。当前模型 artifact 由以下结构组成:
|
||||
|
||||
`.xcmesh` 当前写入顺序是:
|
||||
- `ModelArtifactFileHeader`
|
||||
- `ModelArtifactHeader`
|
||||
- `ModelNodeArtifactHeader`
|
||||
- `ModelMeshBindingArtifact`
|
||||
- `ModelMaterialBindingArtifact`
|
||||
|
||||
1. `MeshArtifactHeader`
|
||||
2. `sectionCount` 个 `MeshSection`
|
||||
3. 顶点数据 blob
|
||||
4. 索引数据 blob
|
||||
5. `materialCount` 个材质 artifact 路径字符串
|
||||
它表达的不是单个 mesh payload,而是一个结构化模型资源:
|
||||
|
||||
## Shader artifact
|
||||
- 节点层级
|
||||
- 根节点索引
|
||||
- 每个节点的局部位移、旋转与缩放
|
||||
- mesh 绑定范围
|
||||
- 材质槽位绑定范围
|
||||
|
||||
真实的 mesh 数据仍然写在同目录的 `mesh_<n>.xcmesh` 中;`main.xcmodel` 更像“模型装配说明书”,负责把层级、mesh 和材质绑定组织起来。
|
||||
|
||||
### Shader artifact: `.xcshader`
|
||||
|
||||
- 文件头: `ShaderArtifactFileHeader`
|
||||
- 主头结构: `ShaderArtifactHeader`
|
||||
- pass 头结构: `ShaderPassArtifactHeader`
|
||||
- 属性结构: `ShaderPropertyArtifact`
|
||||
- 资源结构: `ShaderResourceArtifact`
|
||||
- variant 头结构: `ShaderVariantArtifactHeader`
|
||||
- schema: `4`
|
||||
- magic: `XCSHD04`
|
||||
- 主头: `ShaderArtifactHeader`
|
||||
- pass 头: `ShaderPassArtifactHeaderV4`
|
||||
- 变体头: `ShaderVariantArtifactHeader`
|
||||
- schema: `5`
|
||||
|
||||
### 当前正文布局
|
||||
它已经不是简单的 shader 源码缓存,而是包含 property、resource、keyword declaration、variant 与可选编译产物的完整 artifact 格式。
|
||||
|
||||
`.xcshader` 当前采用“文件头 + 名称/路径字符串 + 逻辑 shader 头 + property 段 + pass 段”的顺序写出:
|
||||
### VolumeField artifact: `.xcvol`
|
||||
|
||||
1. `ShaderArtifactFileHeader`
|
||||
2. shader 名称字符串
|
||||
3. source shader 路径字符串
|
||||
4. fallback 字符串
|
||||
5. `ShaderArtifactHeader`
|
||||
6. `propertyCount` 组 property 数据:
|
||||
- property 名称字符串
|
||||
- display name 字符串
|
||||
- default value 字符串
|
||||
- semantic 字符串
|
||||
- `ShaderPropertyArtifact`
|
||||
7. `passCount` 组 pass 数据:
|
||||
- pass 名称字符串
|
||||
- `ShaderPassArtifactHeaderV4`
|
||||
- `tagCount` 对 tag 名/值字符串
|
||||
- `resourceCount` 组资源绑定:
|
||||
- resource 名称字符串
|
||||
- semantic 字符串
|
||||
- `ShaderResourceArtifact`
|
||||
- `keywordDeclarationCount` 组 keyword 声明:
|
||||
- `ShaderKeywordDeclarationArtifactHeader`
|
||||
- `optionCount` 个 option 字符串
|
||||
- `variantCount` 组 stage variant:
|
||||
- `ShaderVariantArtifactHeader`
|
||||
- entry point 字符串
|
||||
- profile 字符串
|
||||
- source code 字符串
|
||||
- `keywordCount` 个 required keyword 字符串
|
||||
- 可选 `compiledBinary` payload(长度由 `compiledBinarySize` 指定)
|
||||
- 文件头: `VolumeFieldArtifactHeader`
|
||||
- schema: `2`
|
||||
- 记录存储类型、bounds、voxel size、index bounds、grid metadata 与 payload 大小
|
||||
|
||||
### 各头结构当前记录
|
||||
### GaussianSplat artifact
|
||||
|
||||
- `ShaderArtifactHeader`
|
||||
- `propertyCount`
|
||||
- `passCount`
|
||||
- `ShaderPassArtifactHeader`
|
||||
- `tagCount`
|
||||
- `resourceCount`
|
||||
- `keywordDeclarationCount`
|
||||
- `variantCount`
|
||||
- `ShaderPassArtifactHeaderV4`
|
||||
- `tagCount`
|
||||
- `resourceCount`
|
||||
- `keywordDeclarationCount`
|
||||
- `variantCount`
|
||||
- `hasFixedFunctionState`
|
||||
- `fixedFunctionState`
|
||||
- `ShaderPropertyArtifact`
|
||||
- `propertyType`
|
||||
- `ShaderResourceArtifact`
|
||||
- `resourceType`
|
||||
- `set`
|
||||
- `binding`
|
||||
- `ShaderVariantArtifactHeader`
|
||||
- `stage`
|
||||
- `language`
|
||||
- `backend`
|
||||
- `keywordCount`
|
||||
- `compiledBinarySize`
|
||||
- `ShaderKeywordDeclarationArtifactHeader`
|
||||
- `declarationType`
|
||||
- `optionCount`
|
||||
当前头文件已经定义了完整的 Gaussian Splat artifact 契约:
|
||||
|
||||
## 导入与消费链路
|
||||
- `GaussianSplatArtifactFileHeader`
|
||||
- `GaussianSplatArtifactHeader`
|
||||
- `GaussianSplatArtifactSectionRecord`
|
||||
|
||||
### 写入侧
|
||||
头信息覆盖了:
|
||||
|
||||
- `AssetDatabase::ImportTextureAsset()` 产出 `main.xctex`
|
||||
- `AssetDatabase::ImportMaterialAsset()` 产出 `main.xcmat`
|
||||
- `AssetDatabase::ImportModelAsset()` 产出 `main.xcmesh`
|
||||
- `AssetDatabase::ImportShaderAsset()` 产出 `main.xcshader`
|
||||
- splat / chunk / camera 数量
|
||||
- 包围盒
|
||||
- 各 section 的格式声明
|
||||
- section 数量与总 payload 大小
|
||||
- 每个 section 的类型、格式、偏移、字节大小、元素个数与步长
|
||||
|
||||
模型导入还会在同一个 artifact 目录里附带生成:
|
||||
这说明 Gaussian Splat 至少在磁盘格式层已经被认真建模,而不是临时塞进某个通用二进制资源。
|
||||
|
||||
- `texture_<n>.xctex`
|
||||
## 当前导入链路中的真实产物
|
||||
|
||||
按本轮审查到的 `AssetDatabase` 实现,当前明确写出的主 artifact 包括:
|
||||
|
||||
- 纹理: `main.xctex`
|
||||
- 材质: `main.xcmat`
|
||||
- 模型: `main.xcmodel`
|
||||
- Shader: `main.xcshader`
|
||||
- 体积: `main.xcvol`
|
||||
|
||||
其中模型导入还会在同一 artifact 目录下生成:
|
||||
|
||||
- `mesh_<n>.xcmesh`
|
||||
- `material_<n>.xcmat`
|
||||
- `texture_<n>.xctex`
|
||||
|
||||
### 读取侧
|
||||
这证明 `ArtifactFormats.h` 现在服务的是“模型主 artifact + 子资源 artifact”这种组合式导入结果,而不是只有单文件资产。
|
||||
|
||||
- `TextureLoader` 回读 `.xctex`
|
||||
- `MaterialLoader` 回读 `.xcmat`
|
||||
- `MeshLoader` 回读 `.xcmesh`
|
||||
- `ShaderLoader` 回读 `.xcshader`
|
||||
## 设计理解
|
||||
|
||||
`ResourceManager::LoadResource()` 在项目模式下会先经 `AssetImportService::EnsureArtifact()` 准备 `ImportedAsset`,再把 `runtimeLoadPath` 交给具体 loader。
|
||||
把 artifact 契约集中定义在一个头文件里,有几个非常现实的好处:
|
||||
|
||||
## 兼容性与边界
|
||||
- `AssetDatabase` 与 loader 可以共享完全一致的格式定义
|
||||
- 新资源类型可以先把磁盘契约建起来,再逐步补 importer、loader、编辑器与运行时渲染链路
|
||||
- schema 版本升级点集中,后续做兼容迁移时不必在多个模块里分散维护
|
||||
|
||||
### 当前版本语义
|
||||
这类集中式格式契约,在商业引擎的资产导入层非常常见。
|
||||
|
||||
- loader 目前显式检查的仍主要是 magic
|
||||
- `schemaVersion` 已经写进文件头,但当前代码没有基于它做多版本分支读取
|
||||
## 边界与注意事项
|
||||
|
||||
因此当前兼容策略更接近“magic + 整套代码同步升级”,而不是长期维护多代 reader。
|
||||
|
||||
### 二进制稳定性边界
|
||||
|
||||
当前 artifact 文件仍直接写入/读回这些 C++ 原生布局:
|
||||
|
||||
- `TextureArtifactHeader`
|
||||
- `MaterialArtifactFileHeader`
|
||||
- `MaterialArtifactHeader`
|
||||
- `MaterialPropertyArtifact`
|
||||
- `MeshArtifactHeader`
|
||||
- `ShaderArtifactFileHeader`
|
||||
- `ShaderArtifactHeader`
|
||||
- `ShaderPassArtifactHeader`
|
||||
- `ShaderPropertyArtifact`
|
||||
- `ShaderResourceArtifact`
|
||||
- `ShaderVariantArtifactHeader`
|
||||
|
||||
这意味着它本质上仍是引擎内部缓存格式,不是长期稳定的外部交换格式。
|
||||
- 这些结构本质上仍然是引擎内部 artifact 契约,不是长期稳定的外部交换格式
|
||||
- `GaussianSplat` 虽然已经有格式契约,但在本轮代码里还没有看到和 `AssetDatabase` 同等级的公开 importer 入口
|
||||
- schema 常量已就位,但多版本 reader/upgrade 路径是否完整,仍要结合具体 loader 再看
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [当前模块](../Asset.md)
|
||||
- [AssetDatabase](../AssetDatabase/AssetDatabase.md)
|
||||
- [AssetRef](../AssetRef/AssetRef.md)
|
||||
- [ResourceManager](../ResourceManager/ResourceManager.md)
|
||||
- [Material](../../../Resources/Material/Material/Material.md)
|
||||
- [MaterialLoader](../../../Resources/Material/MaterialLoader/MaterialLoader.md)
|
||||
- [ResourceTypes](../ResourceTypes/ResourceTypes.md)
|
||||
- [Model](../../../Resources/Model/Model.md)
|
||||
- [GaussianSplat](../../../Resources/GaussianSplat/GaussianSplat.md)
|
||||
- [VolumeField](../../../Resources/Volume/VolumeField/VolumeField.md)
|
||||
- [API 总索引](../../../../main.md)
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
|
||||
**类型**: `submodule`
|
||||
|
||||
**描述**: 定义项目资产数据库、artifact 缓存、运行时资源加载、句柄、缓存与项目资产查询快照这一整套资源基础设施。
|
||||
**描述**: 项目资产身份、artifact 缓存、导入服务、热路径索引与运行时资源加载共同组成的资源基础设施模块。
|
||||
|
||||
## 概述
|
||||
## 模块概述
|
||||
|
||||
当前 `Core/Asset` 已经不只是“运行时按路径加载资源”的薄层接口,而是可以分成三层来看:
|
||||
当前 `Core/Asset` 已经不是单纯的“按路径加载资源”接口,而是可以分成三层来看:
|
||||
|
||||
1. [AssetDatabase](AssetDatabase/AssetDatabase.md)
|
||||
负责扫描项目 `Assets`、维护 `.meta`、保存 source/artifact 索引,并在需要时把源资产导入成 `Library/Artifacts/...` 下的 artifact。
|
||||
负责扫描项目 `Assets`、维护 `.meta` 与 source/artifact 快照,并在需要时把 source asset 导入到 `Library/Artifacts/...`
|
||||
2. [AssetImportService](AssetImportService/AssetImportService.md) + [ProjectAssetIndex](ProjectAssetIndex/ProjectAssetIndex.md)
|
||||
负责把 `AssetDatabase` 包装成线程安全服务,并导出供热路径查询使用的 path/GUID snapshot。
|
||||
负责把 `AssetDatabase` 包装成线程安全服务,并导出供热路径使用的 path/GUID 查询快照
|
||||
3. [ResourceManager](ResourceManager/ResourceManager.md)
|
||||
负责运行时加载、缓存、句柄与 loader 分发;当设置了项目根目录后,它会先经 `AssetImportService` 准备 artifact,再通过 `ProjectAssetIndex` 做 `AssetRef` / 路径查询。
|
||||
负责运行时加载、缓存、句柄与 loader 分发;当设置了项目根目录后,它会先经 `AssetImportService` 准备 artifact,再把可直接加载的路径交给具体 loader
|
||||
|
||||
这意味着当前资源系统的真实链路是:
|
||||
真实链路大致如下:
|
||||
|
||||
```text
|
||||
Assets/... source files
|
||||
@@ -28,32 +28,48 @@ Assets/... source files
|
||||
-> concrete loader / runtime resource
|
||||
```
|
||||
|
||||
其中,[AssetGUID](AssetGUID/AssetGUID.md)、[AssetRef](AssetRef/AssetRef.md) 和 [ArtifactFormats](ArtifactFormats/ArtifactFormats.md) 分别补上了“资产身份”“资产引用”和“artifact 磁盘格式”这三块基础协议。
|
||||
## 当前导入与 artifact 覆盖
|
||||
|
||||
## 当前实现关系
|
||||
按当前 `AssetDatabase::GetImporterNameForPath()` 与导入实现,已经明确落地的项目资产类型包括:
|
||||
|
||||
- `editor/src/Application.cpp` 初始化编辑器时会调用 `ResourceManager::SetResourceRoot(projectRoot)`,从而绑定项目根、初始化 `ResourceFileSystem`,并刷新 `ProjectAssetIndex` 快照。
|
||||
- `ResourceManager::LoadResource()` 在加载项目资产时,会先调用 `AssetImportService::EnsureArtifact()`;只有 artifact 就绪后,才把 `ImportedAsset::runtimeLoadPath` 交给具体 loader。
|
||||
- `ProjectAssetIndex::TryGetAssetRef()` 当前会优先查本地 snapshot,cache miss 时再回退到 `AssetImportService`;必要时会先 `Refresh()` 数据库再整体重建 snapshot。
|
||||
- `ProjectAssetIndex::TryResolveAssetPath()` 当前同样优先查 snapshot,但 miss 时只回退到 `AssetImportService::TryGetPrimaryAssetPath()`,不会主动刷新整份 snapshot。
|
||||
- `engine/src/Resources/Material/MaterialLoader.cpp` 现在已经把材质纹理路径解析、`.xcmat` 中的 texture `AssetRef` 回读,以及首次访问时的懒加载纳入真实行为范围,因此 `AssetDatabase` 的材质依赖快照不再只是预留设计。
|
||||
- `.xcmat` 当前已经是 material artifact v4:texture binding 会同时写出编码后的 `AssetRef` 和可选路径字符串,并额外保存 `hasRenderStateOverride` 与 keywords,后续由 `MaterialLoader` 与 `Material` 协作懒解析成真正贴图句柄。
|
||||
- `TextureImporter` -> `Texture` -> `main.xctex`
|
||||
- `MaterialImporter` -> `Material` -> `main.xcmat`
|
||||
- `ModelImporter` -> `Model` -> `main.xcmodel`
|
||||
- `ShaderImporter` -> `Shader` -> `main.xcshader`
|
||||
- `VolumeFieldImporter` -> `VolumeField` -> `main.xcvol`
|
||||
|
||||
其中模型导入已经不再把“模型文件 = 单个 mesh artifact”简单等同处理。当前实现会:
|
||||
|
||||
- 产出一个主模型 artifact `main.xcmodel`
|
||||
- 额外生成 `mesh_<n>.xcmesh`
|
||||
- 额外生成 `material_<n>.xcmat`
|
||||
- 必要时生成 `texture_<n>.xctex`
|
||||
|
||||
这说明模型系统已经从“把导入模型直接压扁成一个 Mesh”演进到“保留层级、mesh 绑定和材质绑定”的结构化资源。
|
||||
|
||||
## 资源类型与磁盘契约的新变化
|
||||
|
||||
最近这轮源码改动里,资产基础层又向前走了一步:
|
||||
|
||||
- [ResourceTypes](ResourceTypes/ResourceTypes.md) 新增了 `Model` 与 `GaussianSplat`
|
||||
- [ArtifactFormats](ArtifactFormats/ArtifactFormats.md) 新增了 `Model` 与 `GaussianSplat` 的 artifact 结构定义
|
||||
- [VolumeField](../../Resources/Volume/VolumeField/VolumeField.md) 新增了 [CreateOwned](../../Resources/Volume/VolumeField/CreateOwned.md),让 loader 可以把已读入的 payload 直接转移给运行时资源
|
||||
|
||||
需要注意的是,`GaussianSplat` 目前已经有资源类型和 artifact 结构契约,但我在本轮审查到的 `AssetDatabase` 路径里还没有看到公开的 GaussianSplat importer 入口。也就是说,契约层已经先建立,导入链路仍在继续建设。
|
||||
|
||||
## 设计要点
|
||||
|
||||
- 把“项目资产身份与导入缓存”和“运行时资源实例加载”拆成两层,避免 `ResourceManager` 直接承载 GUID、`.meta` 和 artifact 细节。
|
||||
- 再引入 `AssetImportService + ProjectAssetIndex` 作为服务层和只读快照层,把线程同步、项目根切换和高频查询从 `AssetDatabase` 与 `ResourceManager` 本体里拆出来。
|
||||
- `AssetDatabase` 通过 `guid + importer + source/meta/dependency snapshot` 生成 `artifactKey`,让重导入条件可复现。
|
||||
- `ResourceManager` 仍保留 `builtin://` 这类虚拟路径直载能力;只有能解析到项目 `Assets` 的路径才会进入项目资产链路。
|
||||
- `ResourceHandle`、`ResourceCache` 和 `AsyncLoader` 继续围绕运行时 `IResource` 实例运转,而不是直接把 source asset 当作运行时对象。
|
||||
- 把“项目资产身份与导入缓存”和“运行时资源实例”拆层,避免 `ResourceManager` 直接承担 `.meta`、GUID 和磁盘 artifact 细节
|
||||
- 用 `AssetImportService + ProjectAssetIndex` 分离线程同步和热路径查询
|
||||
- 让 `ArtifactFormats` 与 `ResourceTypes` 作为稳定契约层,支持上层先扩展资源类型,再逐步补齐 importer、loader 与编辑器流程
|
||||
|
||||
## 当前实现边界
|
||||
这种先稳定契约、再逐步铺满各条生产链路的做法,是商业级引擎重构里很典型的推进方式。
|
||||
|
||||
- `AssetDatabase` 当前只有 `TextureImporter`、`MaterialImporter` 和 `ModelImporter` 会产出 artifact;`FolderImporter` 和 `DefaultImporter` 只保留 source 记录。
|
||||
- `.meta` 文件当前只保存 `guid`、`folderAsset`、`importer` 和 `importerVersion`,还没有持久化 importer 专属设置。
|
||||
- `ProjectAssetIndex` 当前只缓存主资产路径,生成的 `AssetRef` 仍默认使用 `kMainAssetLocalID`。
|
||||
- `ResourceManager` 会在同步加载路径里触发 artifact 生成,但当前没有后台导入队列或文件监听闭环。
|
||||
- `LoadAsync()` / `AsyncLoader` 当前已经具备真实的工作线程与完成队列闭环,但回调仍依赖 `Update()` 手动轮询分发,取消与配置所有权语义也还比较原始。
|
||||
## 当前边界
|
||||
|
||||
- 并不是所有 `ResourceType` 都已经有完整的项目导入器
|
||||
- `GaussianSplat` 已进入类型与 artifact 契约层,但公开导入路径仍未在这轮代码里完全接起
|
||||
- `ResourceManager` 仍会在同步加载路径里触发按需导入,后台导入队列与文件监听闭环还不是这一层的重点
|
||||
|
||||
## 头文件
|
||||
|
||||
@@ -74,11 +90,11 @@ Assets/... source files
|
||||
|
||||
## 推荐阅读顺序
|
||||
|
||||
1. 先读 [AssetGUID](AssetGUID/AssetGUID.md) 与 [AssetRef](AssetRef/AssetRef.md),理解项目资产的身份与引用契约。
|
||||
2. 再读 [AssetDatabase](AssetDatabase/AssetDatabase.md) 与 [ArtifactFormats](ArtifactFormats/ArtifactFormats.md),理解 source asset 如何导入为 `Library/Artifacts` 下的 artifact。
|
||||
3. 然后读 [AssetImportService](AssetImportService/AssetImportService.md) 与 [ProjectAssetIndex](ProjectAssetIndex/ProjectAssetIndex.md),理解数据库如何被包装成线程安全服务和热路径查询缓存。
|
||||
4. 接着读 [ResourceManager](ResourceManager/ResourceManager.md) 与 [ResourceHandle](ResourceHandle/ResourceHandle.md),理解运行时如何消费这些 artifact 和 snapshot。
|
||||
5. 最后再看 [ResourceCache](ResourceCache/ResourceCache.md)、[AsyncLoader](AsyncLoader/AsyncLoader.md) 和 [ResourceDependencyGraph](ResourceDependencyGraph/ResourceDependencyGraph.md),理解缓存、异步与依赖管理的现状。
|
||||
1. 先读 [AssetGUID](AssetGUID/AssetGUID.md) 和 [AssetRef](AssetRef/AssetRef.md),理解项目资产的身份与引用契约
|
||||
2. 再读 [ResourceTypes](ResourceTypes/ResourceTypes.md) 和 [ArtifactFormats](ArtifactFormats/ArtifactFormats.md),理解资源类型与磁盘格式
|
||||
3. 接着读 [AssetDatabase](AssetDatabase/AssetDatabase.md),理解 source asset 如何变成 artifact
|
||||
4. 然后读 [AssetImportService](AssetImportService/AssetImportService.md) 和 [ProjectAssetIndex](ProjectAssetIndex/ProjectAssetIndex.md),理解服务层和热路径索引
|
||||
5. 最后读 [ResourceManager](ResourceManager/ResourceManager.md) 与相关 loader,理解运行时如何消费这些 artifact
|
||||
|
||||
## 相关指南
|
||||
|
||||
|
||||
@@ -6,190 +6,124 @@
|
||||
|
||||
**头文件**: `XCEngine/Core/Asset/AssetDatabase.h`
|
||||
|
||||
**描述**: 管理项目 `Assets` 目录里的 source asset、`.meta` sidecar、source/artifact 快照,以及 artifact 导入结果的项目资产数据库。
|
||||
**描述**: 管理项目 `Assets` 目录下的 source asset、`.meta` sidecar、source/artifact 快照以及导入结果的项目资产数据库。
|
||||
|
||||
## 概览
|
||||
## 角色概述
|
||||
|
||||
`AssetDatabase` 处理的是“项目资产”和“artifact 索引”,而不是已经加载进内存的运行时资源实例。
|
||||
|
||||
它当前的真实职责是:
|
||||
`AssetDatabase` 处理的是“项目资产及其导入缓存”,而不是已经加载进内存的运行时资源实例。它的核心职责是:
|
||||
|
||||
1. 扫描项目 `Assets`
|
||||
2. 为每个 source asset 或文件夹维护稳定 `.meta` 与 GUID
|
||||
3. 维护 source 快照和 artifact 快照
|
||||
4. 在按需导入或显式重导时,把 source asset 导入到 `Library/Artifacts/<shard>/<artifactKey>/...`
|
||||
5. 向上游提供路径解析、GUID 查询、可导入类型探测、显式重导与 lookup snapshot 导出能力
|
||||
|
||||
当前它并不直接暴露给 `ResourceManager`;运行时与编辑器通常都通过 [AssetImportService](../AssetImportService/AssetImportService.md) 间接访问。
|
||||
2. 为每个 source asset 维护稳定 `.meta` 与 `GUID`
|
||||
3. 维护 source 快照与 artifact 快照
|
||||
4. 在按需导入或显式重导时,把 source asset 写成 `Library/Artifacts/...` 下的 artifact
|
||||
5. 向上层提供路径解析、GUID 查询、类型探测与 artifact 准备能力
|
||||
|
||||
## 生命周期
|
||||
|
||||
- [Initialize](Initialize.md)
|
||||
设置项目根、`Assets` 根和 `Library` 根,确保目录结构存在,然后加载 source/artifact 数据库并扫描当前资产树。
|
||||
设置项目根、`Assets` 根与 `Library` 根,确保目录结构存在,然后加载快照并扫描资产树
|
||||
- [Refresh](Refresh.md)
|
||||
重新扫描 `Assets`、维护 source 快照并清理孤儿 artifact;当前返回 [MaintenanceStats](MaintenanceStats.md)。
|
||||
重新扫描 `Assets`、维护 source 快照并清理孤儿 artifact
|
||||
- [Shutdown](Shutdown.md)
|
||||
写回 source/artifact 数据库,然后清空内存状态。
|
||||
写回 source/artifact 数据库并清理内存状态
|
||||
|
||||
`AssetDatabase` 自身没有内部锁。线程同步当前由外层 [AssetImportService](../AssetImportService/AssetImportService.md) 的 `recursive_mutex` 提供。
|
||||
|
||||
## 持久化布局
|
||||
## 持久化与目录布局
|
||||
|
||||
### Source 侧
|
||||
|
||||
- 每个 source asset 或文件夹都对应一个 `.meta` sidecar
|
||||
- `.meta` 当前记录:
|
||||
- `guid`
|
||||
- `folderAsset`
|
||||
- `importer`
|
||||
- `importerVersion`
|
||||
- 每个 source asset 或目录都有对应 `.meta`
|
||||
- `Library/SourceAssetDB/assets.db` 保存 [SourceAssetRecord](SourceAssetRecord.md) 快照
|
||||
|
||||
`assets.db` 当前是制表符分隔的文本快照,而不是二进制数据库。
|
||||
|
||||
### Artifact 侧
|
||||
|
||||
- `Library/ArtifactDB/artifacts.db` 保存 [ArtifactRecord](ArtifactRecord.md) 快照
|
||||
- `Library/ArtifactDB/artifacts.db` 保存 [ArtifactRecord](ArtifactRecord.md)
|
||||
- artifact 目录按 `artifactKey` 前两位分片:
|
||||
- `Library/Artifacts/<2-char-shard>/<artifactKey>/...`
|
||||
- 主 artifact 文件当前约定:
|
||||
- 纹理: `main.xctex`
|
||||
- 材质: `main.xcmat`
|
||||
- 模型: `main.xcmesh`
|
||||
- Shader: `main.xcshader`
|
||||
|
||||
模型 artifact 还会在同一目录下写出内嵌材质和贴图 artifact,例如 `material_0.xcmat`、`texture_0.xctex`。
|
||||
当前明确的主 artifact 命名为:
|
||||
|
||||
## 查询与快照链路
|
||||
- 纹理: `main.xctex`
|
||||
- 材质: `main.xcmat`
|
||||
- 模型: `main.xcmodel`
|
||||
- Shader: `main.xcshader`
|
||||
- 体积: `main.xcvol`
|
||||
|
||||
### 路径解析
|
||||
## 当前 importer 映射
|
||||
|
||||
- [ResolvePath](ResolvePath.md) 只把项目内 `Assets/...`,或能还原成 `Assets/...` 的绝对路径视为正式项目资产路径。
|
||||
- 带 `://` 的虚拟路径会直接失败,不进入项目资产数据库。
|
||||
- 普通相对路径虽然可以被展开成绝对路径,但如果无法得到 `Assets/...` 相对路径,就不会被当作正式项目资产。
|
||||
按当前 `GetImporterNameForPath()` 与 `GetPrimaryResourceTypeForImporter()`,公开可见的路径映射如下:
|
||||
|
||||
### GUID、类型探测与 `AssetRef`
|
||||
|
||||
- [TryGetAssetGuid](TryGetAssetGuid.md)
|
||||
通过规范化、大小写无关的 `Assets/...` path key 查询 GUID。
|
||||
- [TryGetImportableResourceType](TryGetImportableResourceType.md)
|
||||
返回当前 importer 的 primary `ResourceType`;目录、项目外路径和 `Unknown` importer 都直接失败。
|
||||
- [TryGetAssetRef](TryGetAssetRef.md)
|
||||
只是把 GUID 包装成主资产 `AssetRef`,并原样写入调用方指定的 `ResourceType`。
|
||||
- [TryGetPrimaryAssetPath](TryGetPrimaryAssetPath.md)
|
||||
做 GUID 到主 source 路径的反查。
|
||||
|
||||
### Lookup snapshot
|
||||
|
||||
[BuildLookupSnapshot](BuildLookupSnapshot.md) 会从当前 source 快照导出两张表:
|
||||
|
||||
- `pathKey -> AssetGUID`
|
||||
- `AssetGUID -> relativePath`
|
||||
|
||||
它不扫描磁盘,也不触发导入。当前真实链路是:
|
||||
|
||||
```text
|
||||
AssetDatabase::BuildLookupSnapshot
|
||||
-> AssetImportService::BuildLookupSnapshot
|
||||
-> ProjectAssetIndex::RefreshFrom
|
||||
-> ResourceManager hot-path queries
|
||||
```
|
||||
|
||||
## 导入与重导链路
|
||||
|
||||
### 当前 importer 映射
|
||||
|
||||
| importer | 扩展名/来源 | 主资源类型 | 当前主 artifact |
|
||||
| importer | 典型扩展名 | primary resource type | 主 artifact |
|
||||
|------|------|------|------|
|
||||
| `TextureImporter` | `.png` `.jpg` `.jpeg` `.bmp` `.tga` `.gif` `.hdr` | `Texture` | `main.xctex` |
|
||||
| `MaterialImporter` | `.mat` `.material` `.json` | `Material` | `main.xcmat` |
|
||||
| `ModelImporter` | `.obj` `.fbx` `.gltf` `.glb` `.dae` `.stl` | `Mesh` | `main.xcmesh` + 内嵌材质/贴图 artifact |
|
||||
| `ShaderImporter` | `.shader` `.hlsl` `.glsl` `.vert` `.frag` `.geom` `.comp` | `Shader` | `main.xcshader` |
|
||||
| `ModelImporter` | `.obj` `.fbx` `.gltf` `.glb` `.dae` `.stl` | `Model` | `main.xcmodel` |
|
||||
| `ShaderImporter` | `.shader` | `Shader` | `main.xcshader` |
|
||||
| `VolumeFieldImporter` | `.nvdb` | `VolumeField` | `main.xcvol` |
|
||||
| `FolderImporter` | 文件夹 | `Unknown` | 无 |
|
||||
| `DefaultImporter` | 其他扩展名 | `Unknown` | 无 |
|
||||
|
||||
[SourceAssetRecord](SourceAssetRecord.md) 的 `importerVersion` 当前统一取头文件里的 `kCurrentImporterVersion`,现在是 `5`。
|
||||
这张表里最重要的新变化是:`ModelImporter` 的 primary type 已经从 `Mesh` 升级为 `Model`。
|
||||
|
||||
### 重导判定
|
||||
## Model 导入链路的新语义
|
||||
|
||||
`ShouldReimport()` 当前会在以下任一条件满足时要求重导:
|
||||
`ImportModelAsset()` 当前不再通过 `MeshLoader` 直接把文件导成一个单体 mesh artifact,而是走更完整的模型导入链:
|
||||
|
||||
- 没有现存 `ArtifactRecord`
|
||||
- `artifactKey` 或 `mainArtifactPath` 为空
|
||||
- 主 artifact 文件已经不存在
|
||||
- importer 版本变化
|
||||
- source 内容哈希变化
|
||||
- `.meta` 哈希变化
|
||||
- source 文件大小或写时间变化
|
||||
- 任一依赖文件快照不再匹配
|
||||
- 使用 Assimp 模型导入数据
|
||||
- 收集导入出的贴图依赖,而不是从单个 mesh 对象反推
|
||||
- 生成 `main.xcmodel`
|
||||
- 为每个子 mesh 生成 `mesh_<n>.xcmesh`
|
||||
- 为每个材质生成 `material_<n>.xcmat`
|
||||
- 必要时生成 `texture_<n>.xctex`
|
||||
|
||||
因此当前 artifact key 不是唯一 gate,真正的重导判定是一组 source / meta / dependency snapshot 联合判断。
|
||||
这说明 `AssetDatabase` 现在已经把“模型”视为有层级、有绑定关系的主资源,而不是把导入结果压扁为单个 `Mesh`。
|
||||
|
||||
### 公开导入入口
|
||||
## EnsureArtifact 的真实职责
|
||||
|
||||
- [EnsureArtifact](EnsureArtifact.md)
|
||||
按需保证单个 source asset 的主 artifact 可用;只有命中 `ShouldReimport()` 时才真正重建。
|
||||
- [ReimportAsset](ReimportAsset.md)
|
||||
强制重导单个 source asset,并返回最新主 artifact 的 [ResolvedAsset](ResolvedAsset.md)。
|
||||
- [ReimportAllAssets](ReimportAllAssets.md)
|
||||
强制重导当前 source 快照里的全部可导入记录;单个条目失败不会中断整轮循环。
|
||||
[EnsureArtifact](EnsureArtifact.md) 仍然是最关键的公开入口之一。它会:
|
||||
|
||||
### `Refresh()` 与显式重导的区别
|
||||
1. 解析请求路径
|
||||
2. 确保 source 记录与 `.meta` 有效
|
||||
3. 根据 importer 推导 primary `ResourceType`
|
||||
4. 校验请求类型与 primary type 一致
|
||||
5. 查询或重建 artifact
|
||||
6. 返回可直接交给 loader 的 [ResolvedAsset](ResolvedAsset.md)
|
||||
|
||||
- [Refresh](Refresh.md) 是“扫描 + 清理”入口,返回 [MaintenanceStats](MaintenanceStats.md),但当前不主动重建 artifact。
|
||||
- [ReimportAsset](ReimportAsset.md) 和 [ReimportAllAssets](ReimportAllAssets.md) 才是显式重导入口,它们会真正调用 importer 重写 artifact。
|
||||
所以对运行时系统来说,`AssetDatabase` 的价值不是“告诉你有没有这个文件”,而是“把 source asset 准备成当前可用、类型正确的 artifact”。
|
||||
|
||||
## 公开数据结构
|
||||
## 设计理解
|
||||
|
||||
- [MaintenanceStats](MaintenanceStats.md) - 一次维护操作的汇总统计
|
||||
- [ArtifactDependencyRecord](ArtifactDependencyRecord.md) - 单个依赖文件的快照记录
|
||||
- [SourceAssetRecord](SourceAssetRecord.md) - source 资产记录
|
||||
- [ArtifactRecord](ArtifactRecord.md) - artifact 数据库记录
|
||||
- [ResolvedAsset](ResolvedAsset.md) - `EnsureArtifact()` 与 `ReimportAsset()` 返回给调用方的解析结果
|
||||
这套设计体现的是典型的商业引擎资产流水线思路:
|
||||
|
||||
## 公开方法
|
||||
- source asset 和 runtime resource 严格分层
|
||||
- artifact 作为可缓存、可重建的中间产物
|
||||
- importer 的 primary type 决定外部看到的主资源身份
|
||||
- 子资源 artifact 可以作为主资源导入链的一部分自动生成
|
||||
|
||||
`Model -> main.xcmodel + mesh/material/texture artifacts` 就是这种设计成熟度提升的直接信号。
|
||||
|
||||
## 重点公共方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| [Initialize](Initialize.md) | 初始化项目根与数据库状态。 |
|
||||
| [Shutdown](Shutdown.md) | 写回数据库并清理内存状态。 |
|
||||
| [Refresh](Refresh.md) | 重新扫描 `Assets` 并返回维护统计。 |
|
||||
| [ResolvePath](ResolvePath.md) | 解析请求路径到绝对/相对项目路径。 |
|
||||
| [ResolvePath](ResolvePath.md) | 把请求路径解析为项目资产路径。 |
|
||||
| [TryGetAssetGuid](TryGetAssetGuid.md) | 通过路径查询 GUID。 |
|
||||
| [TryGetImportableResourceType](TryGetImportableResourceType.md) | 探测当前 importer 的主资源类型。 |
|
||||
| [TryGetAssetRef](TryGetAssetRef.md) | 通过路径组装主资产 `AssetRef`。 |
|
||||
| [TryGetImportableResourceType](TryGetImportableResourceType.md) | 探测 importer 对应的 primary `ResourceType`。 |
|
||||
| [TryGetAssetRef](TryGetAssetRef.md) | 通过路径组装主资源 `AssetRef`。 |
|
||||
| [ReimportAsset](ReimportAsset.md) | 强制重导单个 project asset。 |
|
||||
| [ReimportAllAssets](ReimportAllAssets.md) | 强制重导当前快照里的全部可导入资产。 |
|
||||
| [ReimportAllAssets](ReimportAllAssets.md) | 强制重导当前快照中的全部可导入资产。 |
|
||||
| [EnsureArtifact](EnsureArtifact.md) | 确保 source asset 对应的主 artifact 可用。 |
|
||||
| [TryGetPrimaryAssetPath](TryGetPrimaryAssetPath.md) | 通过 GUID 反查主 source 路径。 |
|
||||
| [BuildLookupSnapshot](BuildLookupSnapshot.md) | 导出 path/GUID lookup snapshot。 |
|
||||
| [GetProjectRoot](GetProjectRoot.md) | 获取项目根目录。 |
|
||||
| [GetAssetsRoot](GetAssetsRoot.md) | 获取 `Assets` 根目录。 |
|
||||
| [GetLibraryRoot](GetLibraryRoot.md) | 获取 `Library` 根目录。 |
|
||||
|
||||
## 真实使用位置
|
||||
|
||||
- [AssetImportService](../AssetImportService/AssetImportService.md) 当前持有 `AssetDatabase`,并负责所有外部访问的加锁与项目根切换。
|
||||
- `AssetImportService` 的显式工具入口当前会:
|
||||
- 先 `Refresh()`,再调用 [ReimportAsset](ReimportAsset.md)
|
||||
- 先 `Refresh()`,再调用 [ReimportAllAssets](ReimportAllAssets.md)
|
||||
- 在持锁且项目根有效时转发 [TryGetImportableResourceType](TryGetImportableResourceType.md)
|
||||
- [ProjectAssetIndex](../ProjectAssetIndex/ProjectAssetIndex.md) 通过 [BuildLookupSnapshot](BuildLookupSnapshot.md) 构建热路径 snapshot。
|
||||
- `ResourceManager::LoadResource()` 通过 `AssetImportService::EnsureArtifact()` 准备 artifact,再把结果交给具体 loader。
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
- 当前只处理项目 `Assets` 下的资产,`builtin://` 等虚拟路径不会进入这里。
|
||||
- `.meta` 只保存 importer 名称和版本,不保存更细的 importer 设置。
|
||||
- `FolderImporter` / `DefaultImporter` 不会产出 artifact。
|
||||
- 当前没有文件监听器,也没有后台导入队列;artifact 通常在首次加载、显式重导或按需导入时同步生成。
|
||||
- [TryGetAssetRef](TryGetAssetRef.md) 只保证“路径有 GUID”,不保证请求的 `ResourceType` 与真实 importer 一致;真正的类型匹配要到 [EnsureArtifact](EnsureArtifact.md) 阶段才检查。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [当前模块](../Asset.md)
|
||||
- [ArtifactFormats](../ArtifactFormats/ArtifactFormats.md)
|
||||
- [ResourceTypes](../ResourceTypes/ResourceTypes.md)
|
||||
- [AssetImportService](../AssetImportService/AssetImportService.md)
|
||||
- [ProjectAssetIndex](../ProjectAssetIndex/ProjectAssetIndex.md)
|
||||
- [ResourceManager](../ResourceManager/ResourceManager.md)
|
||||
- [ImportSettings](../ImportSettings/ImportSettings.md)
|
||||
- [API 总索引](../../../../main.md)
|
||||
|
||||
@@ -6,23 +6,21 @@
|
||||
|
||||
**头文件**: `XCEngine/Core/Asset/ResourceTypes.h`
|
||||
|
||||
**描述**: 定义资源系统的基础类型,包括资源类别枚举、GUID 规则和类型映射模板。
|
||||
**描述**: 定义资源系统的基础类型,包括 `ResourceType` 枚举、`ResourceGUID` 以及 `GetResourceType<T>()` 类型映射模板。
|
||||
|
||||
## 角色概述
|
||||
|
||||
`ResourceTypes.h` 是整个资源模块最底层的身份定义头文件。它回答的是三个基础问题:
|
||||
`ResourceTypes.h` 是整个资源系统最底层的身份定义文件。它决定了三件事:
|
||||
|
||||
- 这是什么类别的资源
|
||||
- 这个资源的稳定标识是什么
|
||||
- 给定一个 C++ 资源类型,应该映射到哪个 `ResourceType`
|
||||
1. 一个运行时资源属于哪种 `ResourceType`
|
||||
2. 一个资源路径如何生成稳定的 `ResourceGUID`
|
||||
3. 给定 C++ 资源类型时,模板系统如何映射回对应的 `ResourceType`
|
||||
|
||||
这类头文件在商业引擎里通常非常核心,因为 loader、缓存、依赖图、序列化和调试输出都会依赖同一套身份系统。
|
||||
loader、缓存、artifact 数据库、热路径索引和调试输出都依赖这套定义。
|
||||
|
||||
## 当前定义
|
||||
## 当前 ResourceType 枚举
|
||||
|
||||
### ResourceType
|
||||
|
||||
`ResourceType` 当前包含:
|
||||
当前枚举值包括:
|
||||
|
||||
- `Unknown`
|
||||
- `Texture`
|
||||
@@ -37,12 +35,22 @@
|
||||
- `ParticleSystem`
|
||||
- `Scene`
|
||||
- `Prefab`
|
||||
- `UIView`
|
||||
- `UITheme`
|
||||
- `UISchema`
|
||||
- `VolumeField`
|
||||
- `Model`
|
||||
- `GaussianSplat`
|
||||
|
||||
`GetResourceTypeName()` 是对应的 constexpr 名称映射,用于日志和调试输出。
|
||||
最近新增的 `Model` 与 `GaussianSplat` 很关键,因为它们意味着引擎不再只把“模型”理解成 `Mesh`,也已经为 3D Gaussian Splat 这类新型资源准备好了正式身份。
|
||||
|
||||
### ResourceGUID
|
||||
## GetResourceTypeName()
|
||||
|
||||
`ResourceGUID` 本质上是一个 `uint64` 包装类型,提供:
|
||||
`GetResourceTypeName(ResourceType)` 提供了与枚举保持同步的字符串映射。文档、日志、调试面板和 artifact 元数据输出通常都会依赖这个函数保持名字一致。
|
||||
|
||||
## ResourceGUID
|
||||
|
||||
`ResourceGUID` 本质上是 `uint64` 包装类型,提供:
|
||||
|
||||
- `IsValid()`
|
||||
- 相等 / 不等比较
|
||||
@@ -50,11 +58,11 @@
|
||||
- `Generate(const Containers::String&)`
|
||||
- `ToString()`
|
||||
|
||||
当前 `Generate()` 的实现使用 64 位 FNV-1a 风格字符串哈希,对输入路径逐字节哈希。
|
||||
它的设计目标是轻量、可复制、易于做哈希键,而不是引入额外的注册中心。
|
||||
|
||||
### 类型映射模板
|
||||
## 类型映射模板
|
||||
|
||||
`GetResourceType<T>()` 当前只有少数显式特化:
|
||||
`GetResourceType<T>()` 现在已经有这些显式特化:
|
||||
|
||||
- `Texture`
|
||||
- `Mesh`
|
||||
@@ -62,51 +70,45 @@
|
||||
- `Shader`
|
||||
- `AudioClip`
|
||||
- `BinaryResource`
|
||||
- `UIView`
|
||||
- `UITheme`
|
||||
- `UISchema`
|
||||
- `VolumeField`
|
||||
- `Model`
|
||||
- `GaussianSplat`
|
||||
|
||||
这正是 `ResourceManager::Load<T>()` 能正常分派到 loader 的基础。
|
||||
其中 `Model` 和 `GaussianSplat` 的加入很重要,因为这代表模板层已经认可它们是正式的一等资源类型,而不是临时附属数据。
|
||||
|
||||
## GUID 语义
|
||||
## 为什么 Model 不是 Mesh
|
||||
|
||||
当前 `ResourceGUID::Generate(path)` 的行为非常直接:
|
||||
这是本轮改动里最值得说清楚的设计点之一。
|
||||
|
||||
- 只对传入字符串本身做哈希
|
||||
- 不做路径规范化
|
||||
- 不统一大小写
|
||||
- 不消除相对路径和绝对路径差异
|
||||
`Mesh` 只表达一段可绘制的几何数据;`Model` 则表达:
|
||||
|
||||
因此:
|
||||
- 节点层级
|
||||
- 局部变换
|
||||
- mesh 绑定
|
||||
- 材质绑定
|
||||
|
||||
- `"textures/a.png"` 和 `"./textures/a.png"` 会生成不同 GUID
|
||||
- 路径大小写不同,也会生成不同 GUID
|
||||
- 调用方必须自己保证路径规范一致,否则缓存命中和资源去重都会受影响
|
||||
把二者拆开,才能让资源系统正确表达一个真正的导入模型,而不是把层级资产粗暴压平为单网格资源。这也是商业引擎里非常标准的做法。
|
||||
|
||||
`ToString()` 当前输出 16 位十六进制字符串,便于日志和调试面板展示。
|
||||
## 为什么要把 GaussianSplat 提前放进 ResourceType
|
||||
|
||||
## 设计取向
|
||||
即使某些导入与运行时链路还在建设中,先把 `GaussianSplat` 放进正式 `ResourceType` 仍然有明确价值:
|
||||
|
||||
用路径派生 GUID 而不是维护中心注册表,是一种很轻量的资源身份策略。它的优点是:
|
||||
- artifact、资源缓存和调试输出可以先统一识别这种资源
|
||||
- 上层系统不必把它伪装成 `Binary` 或其他临时类型
|
||||
- 后续补 importer、loader 和渲染器时,不需要再重做身份契约
|
||||
|
||||
- 不需要额外数据库
|
||||
- 同一路径天然得到稳定结果
|
||||
- 非常适合当前这种运行时轻量资源系统
|
||||
|
||||
代价也很明确:
|
||||
|
||||
- 身份稳定性完全依赖路径字符串规范
|
||||
- 理论上存在哈希碰撞可能
|
||||
- 没有单独的 GUID 到路径反查系统保证
|
||||
|
||||
## 当前实现限制
|
||||
|
||||
- `GetResourceType<T>()` 没有通用默认实现,只能用于已显式特化的资源类型。
|
||||
- `ResourceGUID` 不做路径规范化,调用方必须自己统一路径形式。
|
||||
- 当前没有更强的 GUID 持久化或冲突检测机制。
|
||||
- `ResourceType` 已经枚举了不少类别,但并不是每一类当前都有成熟 loader 或完整资源实现。
|
||||
这是一种典型的“先确立系统边界,再逐步补实现”的工程推进方式。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [当前模块](../Asset.md)
|
||||
- [ResourceManager](../ResourceManager/ResourceManager.md)
|
||||
- [ResourceHandle](../ResourceHandle/ResourceHandle.md)
|
||||
- [IResource](../IResource/IResource.md)
|
||||
- [AssetGUID](../AssetGUID/AssetGUID.md)
|
||||
- [AssetRef](../AssetRef/AssetRef.md)
|
||||
- [ArtifactFormats](../ArtifactFormats/ArtifactFormats.md)
|
||||
- [Model](../../../Resources/Model/Model.md)
|
||||
- [GaussianSplat](../../../Resources/GaussianSplat/GaussianSplat.md)
|
||||
- [VolumeField](../../../Resources/Volume/VolumeField/VolumeField.md)
|
||||
- [API 总索引](../../../../main.md)
|
||||
|
||||
51
docs/api/XCEngine/Resources/Volume/VolumeField/Create.md
Normal file
51
docs/api/XCEngine/Resources/Volume/VolumeField/Create.md
Normal 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)
|
||||
@@ -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)
|
||||
@@ -8,57 +8,91 @@
|
||||
|
||||
**源文件**: `engine/src/Resources/Volume/VolumeField.cpp`
|
||||
|
||||
**描述**: 体积 payload 的运行时资源对象,保存存储类型、包围盒、体素尺寸、索引边界和原始字节载荷。
|
||||
**描述**: 体积 payload 的运行时资源对象,保存存储类型、包围盒、体素尺寸、索引边界和原始字节负载。
|
||||
|
||||
## 概述
|
||||
## 角色概述
|
||||
|
||||
`VolumeField` 是当前体积资源在运行时的最小封装。它并不解析体积体素内容,而是把 loader 导入的 payload 和元数据原样保存下来,供后续:
|
||||
|
||||
- 场景组件绑定
|
||||
- artifact 重建
|
||||
- `RenderResourceCache` 上传 structured-buffer
|
||||
|
||||
使用。
|
||||
`VolumeField` 是当前体积资源在运行时的最小封装。它不直接解析体素内容,也不暴露采样级 API;它的职责是把 loader 或 artifact 读到的 payload 与元数据安全地装进统一资源对象,供后续缓存、组件和渲染系统消费。
|
||||
|
||||
## 当前状态模型
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `m_storageKind` | 当前体积存储类型,现阶段主要是 `NanoVDB` |
|
||||
| `m_bounds` | 体积世界/局部包围盒 |
|
||||
| `m_bounds` | 体积包围盒 |
|
||||
| `m_voxelSize` | 体素尺寸 |
|
||||
| `m_indexBounds` | 体素索引空间边界 |
|
||||
| `m_gridType` / `m_gridClass` | NanoVDB metadata 的轻量转存 |
|
||||
| `m_indexBounds` | 体素索引边界 |
|
||||
| `m_gridType` / `m_gridClass` | 体积格式元数据 |
|
||||
| `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(...)`
|
||||
- `payload == nullptr` 或 `payloadSize == 0` 时直接失败
|
||||
- 成功时会复制整份 payload,而不是借用外部内存
|
||||
- 会写入边界、体素尺寸、索引边界与 grid metadata
|
||||
- 成功后把资源标记为 valid,并更新 `memorySize`
|
||||
- `GetWorldBounds()` 当前只是 `GetBounds()` 的别名
|
||||
- `Release()` 当前直接执行 `delete this`
|
||||
- `payload == nullptr` 或 `payloadSize == 0` 时失败
|
||||
- 会复制整份 payload
|
||||
- 成功后标记资源为 valid 并更新 `memorySize`
|
||||
- `CreateOwned(...)`
|
||||
- `payload.Empty()` 时失败
|
||||
- 会移动 `payload`,不再复制
|
||||
- 同样会写入元数据、标记 valid 并更新 `memorySize`
|
||||
- 两条路径现在共享内部元数据填充逻辑 `ApplyMetadata(...)`
|
||||
- `GetWorldBounds()` 当前仍然只是 `GetBounds()` 的别名
|
||||
- `Release()` 依旧执行 `delete this`
|
||||
|
||||
## 测试与调用链
|
||||
## 当前调用链
|
||||
|
||||
- `tests/Resources/Volume/test_volume_field.cpp`
|
||||
- 覆盖 payload 拷贝、边界元数据与 `GetMemorySize()`
|
||||
- `engine/src/Core/Asset/AssetDatabase.cpp`
|
||||
- 当前会把本资源写回 `.xcvol` artifact
|
||||
- `engine/src/Rendering/Caches/RenderResourceCache.cpp`
|
||||
- 当前把 payload 上传成体积 structured-buffer
|
||||
- `LoadVolumeFieldArtifact()` 现在会把 artifact 中读出的 payload 通过 `CreateOwned(...)` 直接转移给资源
|
||||
- `LoadNanoVDBSourceFile()` 也会先组装 `Containers::Array<Core::uint8>`,再通过 `CreateOwned(...)` 交给资源
|
||||
- `RenderResourceCache` 会继续把 payload 上传成体积相关 GPU 资源
|
||||
|
||||
## 当前实现边界
|
||||
这说明 `CreateOwned(...)` 已经不是预留接口,而是 loader 的真实生产路径。
|
||||
|
||||
- 当前只保存 payload,不提供体素级查询 API。
|
||||
- `Create(...)` 是一次性初始化入口,没有增量修改协议。
|
||||
- `GetWorldBounds()` 尚未引入独立于 `GetBounds()` 的额外变换语义。
|
||||
## 当前边界
|
||||
|
||||
- `VolumeField` 仍然只保存 payload,不负责体素级查询
|
||||
- 它仍然是一次性初始化对象,没有增量修改协议
|
||||
- 运行时世界空间语义目前没有超出 `bounds` 本身
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Volume](../Volume.md)
|
||||
- [CreateOwned](CreateOwned.md)
|
||||
- [VolumeFieldLoader](../VolumeFieldLoader/VolumeFieldLoader.md)
|
||||
- [VolumeRendererComponent](../../../Components/VolumeRendererComponent/VolumeRendererComponent.md)
|
||||
- [BuiltinVolumetricPass](../../../Rendering/Passes/BuiltinVolumetricPass/BuiltinVolumetricPass.md)
|
||||
- [API 总索引](../../../../main.md)
|
||||
|
||||
Reference in New Issue
Block a user