From effca7877104df00fad604248e1beaf107bdd5fc Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 10 Apr 2026 18:17:19 +0800 Subject: [PATCH] docs(asset): sync model and volume api docs --- .../Asset/ArtifactFormats/ArtifactFormats.md | 336 +++++------------- docs/api/XCEngine/Core/Asset/Asset.md | 78 ++-- .../Core/Asset/AssetDatabase/AssetDatabase.md | 186 ++++------ .../Core/Asset/ResourceTypes/ResourceTypes.md | 100 +++--- .../Resources/Volume/VolumeField/Create.md | 51 +++ .../Volume/VolumeField/CreateOwned.md | 56 +++ .../Volume/VolumeField/VolumeField.md | 92 +++-- 7 files changed, 419 insertions(+), 480 deletions(-) create mode 100644 docs/api/XCEngine/Resources/Volume/VolumeField/Create.md create mode 100644 docs/api/XCEngine/Resources/Volume/VolumeField/CreateOwned.md diff --git a/docs/api/XCEngine/Core/Asset/ArtifactFormats/ArtifactFormats.md b/docs/api/XCEngine/Core/Asset/ArtifactFormats/ArtifactFormats.md index b7f475f3..aeeefa28 100644 --- a/docs/api/XCEngine/Core/Asset/ArtifactFormats/ArtifactFormats.md +++ b/docs/api/XCEngine/Core/Asset/ArtifactFormats/ArtifactFormats.md @@ -6,301 +6,147 @@ **头文件**: `XCEngine/Core/Asset/ArtifactFormats.h` -**描述**: 定义 `Texture`、`Material`、`Mesh` 与 `Shader` artifact 的二进制头结构和 schema 常量,供 `AssetDatabase` 写出、各 loader 回读共享。 +**描述**: 定义项目 artifact 的二进制头结构、schema 常量与记录布局,供 `AssetDatabase` 写出、各类 loader 回读以及资源系统共享使用。 ## 角色概述 -`ArtifactFormats.h` 是项目资产导入链路的磁盘格式契约层。 +`ArtifactFormats.h` 是资产导入链路里的磁盘格式契约层。它回答的是: -它回答的是: +- 某类 source asset 导入后会写出什么主 artifact +- 二进制文件头里有哪些 schema 常量和 magic +- loader 回读时应如何解释结构化头信息 -- `AssetDatabase` 把 source asset 导入到 `Library/Artifacts/...` 时,头结构和 magic 是什么 -- `TextureLoader`、`MaterialLoader`、`MeshLoader`、`ShaderLoader` 回读时,二进制布局该怎样解释 - -它不负责: - -- 扫描 `Assets` -- 生成 GUID -- 维护 source / artifact 数据库 -- 直接触发运行时异步加载 +它本身不负责扫描项目、生成 GUID 或执行导入;它只定义“写什么”和“读什么”。 ## 当前 schema 常量 - `kTextureArtifactSchemaVersion = 1` - `kMaterialArtifactSchemaVersion = 4` - `kMeshArtifactSchemaVersion = 2` -- `kShaderArtifactSchemaVersion = 4` +- `kShaderArtifactSchemaVersion = 5` - `kUIDocumentArtifactSchemaVersion = 2` +- `kVolumeFieldArtifactSchemaVersion = 2` +- `kModelArtifactSchemaVersion = 1` +- `kGaussianSplatArtifactSchemaVersion = 1` -## Texture artifact: `.xctex` +这些常量说明 artifact 契约已经不再只覆盖传统的贴图、材质、网格和 shader,模型层级资源与 Gaussian Splat 资源也已经进入了格式层。 -- 头结构: `TextureArtifactHeader` +## 当前格式覆盖 + +### Texture artifact: `.xctex` + +- 文件头: `TextureArtifactHeader` - magic: `XCTEX01` +- 记录纹理维度、格式、mip 层数、数组大小与像素 payload 大小 -`TextureArtifactHeader` 当前记录: - -- `textureType` -- `textureFormat` -- `width` -- `height` -- `depth` -- `mipLevels` -- `arraySize` -- `pixelDataSize` - -写入顺序当前是: - -1. `TextureArtifactHeader` -2. 原始像素 payload - -## Material artifact: `.xcmat` +### Material artifact: `.xcmat` - 文件头: `MaterialArtifactFileHeader` -- 主头结构: `MaterialArtifactHeader` -- 属性结构: `MaterialPropertyArtifact` +- 主头: `MaterialArtifactHeader` +- 属性记录: `MaterialPropertyArtifact` - schema: `4` -- magic: `XCMAT04` +- 支持 render-state override、keyword、property 与 texture binding -### 当前正文布局 +### Mesh artifact: `.xcmesh` -`.xcmat` 当前不是“一个大 struct 一次性写完”,而是“文件头 + 变长字符串段 + 固定头 + 变长数组”的布局: - -1. `MaterialArtifactFileHeader` -2. 材质名字符串 -3. source 材质路径字符串 -4. shader 路径字符串 -5. shader pass 字符串 -6. `MaterialArtifactHeader` -7. `tagCount` 对 tag 名/值字符串 -8. `keywordCount` 个 keyword 字符串 -9. `propertyCount` 个属性名字符串 + `MaterialPropertyArtifact` -10. `textureBindingCount` 组 texture binding 三元组: - - binding name - - 编码后的 `AssetRef` 字符串 - - 可选 texture path 字符串 - -### `MaterialArtifactHeader` - -当前记录: - -- `renderQueue` -- `renderState` -- `tagCount` -- `hasRenderStateOverride` -- `keywordCount` -- `propertyCount` -- `textureBindingCount` - -### `MaterialPropertyArtifact` - -只记录: - -- `propertyType` -- `MaterialProperty::Value` - -当前 writer 只把非 texture 属性写进 property 段;texture / cubemap 绑定统一走 texture binding 段。 - -### render-state override 语义 - -`MaterialArtifactHeader::hasRenderStateOverride` 当前显式区分两种情况: - -- 材质真的声明了 `renderState` -- 材质只是保留了一份默认 `MaterialRenderState` 缓存 - -回读侧 `MaterialLoader` 当前会据此恢复 `HasRenderStateOverride()`。 - -### texture binding 语义 - -texture binding 当前不再只保存 path。 - -写入侧 `AssetDatabase::WriteMaterialArtifactFile(...)` 会尽量为每个 binding 同时保存: - -- 编码 `AssetRef` - 格式是 `guid,localID,resourceTypeInt` -- 可选 path - -`AssetRef` 的求值优先级当前是: - -1. binding 自己显式持有的 `AssetRef` -2. 已加载纹理句柄映射出的 `AssetRef` -3. 纹理 path 反查得到的 `AssetRef` - -path 的求值优先级当前是: - -1. 已加载纹理映射出的 artifact / source path -2. binding 自己保存的 `texturePath` - -回读侧 `MaterialLoader` 当前会: - -- 先解码 `AssetRef` -- 再解析 path -- 有效 `AssetRef` 优先走 `Material::SetTextureAssetRef(...)` -- 否则退到 `Material::SetTexturePath(...)` - -这意味着当前 `.xcmat` 版本可以只靠 `AssetRef` 恢复稳定引用,path 只作为可选辅助信息存在。 - -## Mesh artifact: `.xcmesh` - -- 头结构: `MeshArtifactHeader` +- 文件头: `MeshArtifactHeader` - schema: `2` -- magic: `XCMESH2` +- 记录顶点流、索引流、section、bounds 与材质引用路径 -`MeshArtifactHeader` 当前记录: +### Model artifact: `.xcmodel` -- `vertexCount` -- `vertexStride` -- `vertexAttributes` -- `indexCount` -- `use32BitIndex` -- `sectionCount` -- `materialCount` -- `boundsMin` -- `boundsMax` -- `vertexDataSize` -- `indexDataSize` +这是本轮最关键的新内容之一。当前模型 artifact 由以下结构组成: -`.xcmesh` 当前写入顺序是: +- `ModelArtifactFileHeader` +- `ModelArtifactHeader` +- `ModelNodeArtifactHeader` +- `ModelMeshBindingArtifact` +- `ModelMaterialBindingArtifact` -1. `MeshArtifactHeader` -2. `sectionCount` 个 `MeshSection` -3. 顶点数据 blob -4. 索引数据 blob -5. `materialCount` 个材质 artifact 路径字符串 +它表达的不是单个 mesh payload,而是一个结构化模型资源: -## Shader artifact +- 节点层级 +- 根节点索引 +- 每个节点的局部位移、旋转与缩放 +- mesh 绑定范围 +- 材质槽位绑定范围 + +真实的 mesh 数据仍然写在同目录的 `mesh_.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_.xctex` +## 当前导入链路中的真实产物 + +按本轮审查到的 `AssetDatabase` 实现,当前明确写出的主 artifact 包括: + +- 纹理: `main.xctex` +- 材质: `main.xcmat` +- 模型: `main.xcmodel` +- Shader: `main.xcshader` +- 体积: `main.xcvol` + +其中模型导入还会在同一 artifact 目录下生成: + +- `mesh_.xcmesh` - `material_.xcmat` +- `texture_.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) diff --git a/docs/api/XCEngine/Core/Asset/Asset.md b/docs/api/XCEngine/Core/Asset/Asset.md index b99f01f1..89ce9eda 100644 --- a/docs/api/XCEngine/Core/Asset/Asset.md +++ b/docs/api/XCEngine/Core/Asset/Asset.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_.xcmesh` +- 额外生成 `material_.xcmat` +- 必要时生成 `texture_.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 ## 相关指南 diff --git a/docs/api/XCEngine/Core/Asset/AssetDatabase/AssetDatabase.md b/docs/api/XCEngine/Core/Asset/AssetDatabase/AssetDatabase.md index 905199b3..3de06b95 100644 --- a/docs/api/XCEngine/Core/Asset/AssetDatabase/AssetDatabase.md +++ b/docs/api/XCEngine/Core/Asset/AssetDatabase/AssetDatabase.md @@ -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///...` -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>//...` -- 主 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_.xcmesh` +- 为每个材质生成 `material_.xcmat` +- 必要时生成 `texture_.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) diff --git a/docs/api/XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md b/docs/api/XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md index 083c5566..0f245631 100644 --- a/docs/api/XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md +++ b/docs/api/XCEngine/Core/Asset/ResourceTypes/ResourceTypes.md @@ -6,23 +6,21 @@ **头文件**: `XCEngine/Core/Asset/ResourceTypes.h` -**描述**: 定义资源系统的基础类型,包括资源类别枚举、GUID 规则和类型映射模板。 +**描述**: 定义资源系统的基础类型,包括 `ResourceType` 枚举、`ResourceGUID` 以及 `GetResourceType()` 类型映射模板。 ## 角色概述 -`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()` 当前只有少数显式特化: +`GetResourceType()` 现在已经有这些显式特化: - `Texture` - `Mesh` @@ -62,51 +70,45 @@ - `Shader` - `AudioClip` - `BinaryResource` +- `UIView` +- `UITheme` +- `UISchema` +- `VolumeField` +- `Model` +- `GaussianSplat` -这正是 `ResourceManager::Load()` 能正常分派到 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()` 没有通用默认实现,只能用于已显式特化的资源类型。 -- `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) diff --git a/docs/api/XCEngine/Resources/Volume/VolumeField/Create.md b/docs/api/XCEngine/Resources/Volume/VolumeField/Create.md new file mode 100644 index 00000000..e08cfc08 --- /dev/null +++ b/docs/api/XCEngine/Resources/Volume/VolumeField/Create.md @@ -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`,希望直接把所有权移动给资源对象 + +如果 payload 很大,且调用方本来就拥有可移动数组,优先使用 `CreateOwned(...)`,可以避免一次完整拷贝。 + +## 相关文档 + +- [VolumeField](VolumeField.md) +- [CreateOwned](CreateOwned.md) +- [VolumeFieldLoader](../VolumeFieldLoader/VolumeFieldLoader.md) diff --git a/docs/api/XCEngine/Resources/Volume/VolumeField/CreateOwned.md b/docs/api/XCEngine/Resources/Volume/VolumeField/CreateOwned.md new file mode 100644 index 00000000..941d9b93 --- /dev/null +++ b/docs/api/XCEngine/Resources/Volume/VolumeField/CreateOwned.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&& 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` + +此时如果再走 `Create(...)`,就会额外产生一次完整拷贝。`CreateOwned(...)` 避免了这一步,对大体积资源尤其重要。 + +## 使用建议 + +- 当调用方已经拥有可转移的 `Containers::Array` 时,优先使用本方法 +- 当数据来源是只读外部缓冲或生命周期不受调用方控制时,再使用 [Create](Create.md) + +## 相关文档 + +- [VolumeField](VolumeField.md) +- [Create](Create.md) +- [VolumeFieldLoader](../VolumeFieldLoader/VolumeFieldLoader.md) diff --git a/docs/api/XCEngine/Resources/Volume/VolumeField/VolumeField.md b/docs/api/XCEngine/Resources/Volume/VolumeField/VolumeField.md index 5044ca5f..27c34c42 100644 --- a/docs/api/XCEngine/Resources/Volume/VolumeField/VolumeField.md +++ b/docs/api/XCEngine/Resources/Volume/VolumeField/VolumeField.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&& 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`。如果再走一次 `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`,再通过 `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)