Files
XCEngine/docs/used/Library统一Artifact容器与多后端Shader缓存重构计划_阶段归档_2026-04-11.md

20 KiB
Raw Blame History

Library 统一 Artifact 容器与多后端 Shader 缓存重构计划

日期2026-04-11

0. 当前执行状态2026-04-11 更新)

当前整体进度:约 88%

已完成:

  1. ArtifactContainer 读写、校验、虚拟 entry path 已落地,并有核心单测覆盖。
  2. ShaderCompilationCache 已正式进入 Library 体系,D3D12 已有稳定命中路径与单测覆盖。
  3. Texture / Material / Shader / Model / Volume / GaussianSplat 已切到单文件 artifact container 写入与读取语义。
  4. Model 子资源已从真实 sibling 文件切到 container entryAssetDatabase 子资源解析优先走 entry 解析。
  5. XCUI 文档 artifact 也已补齐为单文件 container避免 Library 内继续残留旧式 artifact directory 生产路径。
  6. 新格式写入路径已经统一到 Library/Artifacts/<shard>/<artifactKey>.<ext>,旧格式保持读取兼容。
  7. artifact_inspect 已落地,可直接查看 container entry 列表并导出指定 entry payload。

已验证:

  1. ArtifactContainer.*
  2. ShaderCompilationCache.*
  3. TextureLoader 关键 artifact 回归
  4. MaterialLoader 关键 artifact / reimport 回归
  5. ShaderLoader 关键 artifact / dependency 回归
  6. ModelLoader / ModelImportPipeline / MeshLoader 关键 container-entry 回归
  7. VolumeFieldLoader 关键 artifact 回归
  8. GaussianSplatLoader.*
  9. UIDocumentLoader.*
  10. UISchemaDocument.*

尚未收口:

  1. ArtifactDB 还没有彻底升级到“显式 entry 元数据” schema当前仍保留一部分旧字段语义用于兼容迁移。
  2. Library 清理工具、最终性能报告、旧格式彻底收口文档还没完成。
  3. Vulkan / OpenGL 目前已经接入统一 shader cache 框架边界,但还没有做到和 D3D12 同等级的完整产物复用能力。

1. 文档定位

这份计划不是在讨论“把 Library/Artifacts 的目录名字改得更像 Unity”。

这份计划要解决的是更底层的问题:

  1. 现在的 artifact 基本存储单位是“一个目录 + 多个真实文件”,不是“一个统一工件”。
  2. 复杂资源类型一增加,导入器、加载器、子资源解析、测试都会继续把这种多文件假设扩散到更多模块。
  3. shader 运行时编译缓存虽然已经开始建立,但还没有上升为整个 Library 体系中的正式基础设施。

因此,本计划的目标是正式把 Library 重构为一套统一的缓存架构:

  1. 导入产物统一落成单文件 artifact container。
  2. 子资源从“磁盘上的 sibling 文件”变成“artifact 内部 entry”。
  3. shader 编译产物纳入 Library 正式缓存体系,并支持多后端。
  4. 整个系统继续保留现有 AssetDatabase / ResourceManager / ArtifactDB 的架构思路,不搞一套平行新系统。

2. 当前状态与根本问题

2.1 当前已经成立的基础

目前 Library 体系已经具备以下基础能力:

  1. assets.dbartifacts.db 已经位于 Library 根目录。
  2. AssetDatabase 已经负责 source asset 扫描、依赖签名、artifact 复用和重导。
  3. 各资源导入器已经能够把 source asset 写入 artifact。
  4. shader 编译缓存已经有初步命中机制,且收益已经被实际验证过。

2.2 当前真正的问题

当前问题不在于“有没有缓存”,而在于“缓存的基本抽象还不够统一”。

以模型为例,当前一个导入结果并不是一个真正的工件,而是一整个目录:

  1. main.xcmodel
  2. mesh_0.xcmesh
  3. material_0.xcmat
  4. texture_0.xctex
  5. subassets.tsv

这意味着:

  1. artifact 的语义是“目录”,不是“文件”。
  2. loader 和测试可以直接依赖某个 sibling 文件的真实路径。
  3. 子资源关系是通过外部文件名和外部 manifest 维持的。
  4. 新增资源类型时,导入器很容易继续沿用“往 artifact 目录里再塞几个文件”的模式。

2.3 为什么这个问题越晚改越难

随着资源类型增加,当前模式会继续扩散到:

  1. Model
  2. Shader
  3. Volume
  4. GaussianSplat
  5. 未来的 AnimationClip / Skeleton / Audio / VFX / Prefab-like Asset

越往后,耦合会越来越深:

  1. 更多 importer 会直接生成多个磁盘文件。
  2. 更多 loader 会默认自己拿到的就是磁盘路径。
  3. 更多测试会把 artifact 文件名结构视为系统契约。
  4. 后面再做统一 container 时,每种资源都要单独回迁一次。

所以这次重构如果要做,越早越值。


3. 重构目标

本轮重构的最终目标如下:

  1. Library 中“一个 artifactKey 对应一个单文件 container”。
  2. artifact container 内部可以保存主资源和多个子资源 entry。
  3. ArtifactDB 记录的是“工件文件 + 条目索引信息”,不再把目录结构当成核心语义。
  4. loader 不再依赖 sibling 文件真实存在,而是通过 container entry 加载。
  5. shader 编译缓存成为正式的 Library 子系统,支持 D3D12 / Vulkan / OpenGL 多后端扩展。
  6. 老项目和旧 Library 尽量兼容迁移,避免一次性炸库。

4. 核心架构决策

4.1 一个 artifact 必须变成一个单文件 container

正式改为:

  1. 一个 artifactKey 只对应一个 artifact 文件。
  2. artifact 文件内部包含 header、目录表、entry 表、payload 区。
  3. entry 可以表达:
    • main asset
    • subasset
    • import metadata
    • dependency snapshot
    • debug manifest

建议目标形态:

  1. Library/Artifacts/07/071822a133c727103210833b93d2b846.xca
  2. 或保留无扩展 hash 文件形式,但内部仍然是统一 container

这里更重要的是“单文件容器语义”,不是扩展名本身。

4.2 子资源必须从真实文件变成逻辑 entry

当前的:

  1. mesh_0.xcmesh
  2. material_0.xcmat
  3. texture_0.xctex
  4. subassets.tsv

后续都不再作为 artifact 目录中的真实文件暴露。

改为:

  1. container entry main
  2. container entry mesh/0
  3. container entry material/0
  4. container entry texture/0
  5. container entry manifest/subassets

这样做的目的不是“隐藏文件”,而是统一系统抽象:

  1. importer 只负责产出 entry。
  2. loader 只负责按 entry 解析。
  3. 外部磁盘布局不再影响运行时语义。

4.3 ArtifactDB 只维护工件语义,不维护目录语义

ArtifactDB 需要从当前的字段语义升级为:

  1. artifactKey
  2. artifactPath
  3. artifactFormatVersion
  4. mainEntryId
  5. resourceType
  6. dependencySignature
  7. sourceHash / metaHash / importerVersion
  8. 可选的 subasset index snapshot

不再把下列内容作为核心前提:

  1. artifactDirectory
  2. mainArtifactPath 必须是磁盘上的某个独立 sibling 文件

4.4 Shader 编译缓存必须纳入 Library 正式体系

shader 编译缓存不能继续以“某个功能修复补丁”的方式存在,必须被定义为 Library 正式缓存层的一部分。

正式目标:

  1. 以编译 key 唯一标识一份 shader bytecode / program binary。
  2. 编译 key 必须包含:
    • shader 源文件签名
    • include 依赖签名
    • keyword / variant 宏
    • backend 类型
    • compiler 类型与版本
    • profile / entry point
    • 相关编译选项
  3. 多后端共用同一套 key 生成规则和缓存查询流程。
  4. 后端各自写入自己的 payload不共享错误的二进制格式。

4.5 必须坚持“统一接口,多后端实现”

这次重构不能只为 D3D12 做一套临时特化路径。

必须采用:

  1. 上层统一的 ShaderCompilationCache 接口
  2. 后端相关的 key 参与规则
  3. 后端相关的 payload 类型
  4. 后端相关的反序列化逻辑

这样后面:

  1. D3D12 存 DXIL / DXBC
  2. Vulkan 存 SPIR-V
  3. OpenGL 可以先存 source variant 或 program binary capability 结果

都能挂到同一架构下,而不是分裂成三套缓存系统。


5. 目标目录与数据布局

5.1 Library 根目录

建议最终保留如下根层文件:

  1. Library/assets.db
  2. Library/artifacts.db
  3. Library/shadercache.db
  4. Library/assets.db.tmp/.bak/.lock
  5. Library/artifacts.db.tmp/.bak/.lock
  6. Library/shadercache.db.tmp/.bak/.lock

说明:

  1. 单文件数据库应放在 Library 根目录,不再额外套目录。
  2. .tmp/.bak/.lock 是数据库运行期一致性与恢复所需的辅助文件,属于正常成本,应保留。

5.2 Artifact 内容目录

建议统一为:

  1. Library/Artifacts/<2-hex-shard>/<artifactKey>.xca

说明:

  1. 仍然保留分片目录,避免单目录堆积过多文件。
  2. 从“目录工件”改为“单文件工件”。
  3. 这里已经足够接近 Unity 的外观,但关键收益来自统一 container不来自“像 Unity”本身。

5.3 Shader 编译缓存目录

建议使用:

  1. Library/ShaderCache/<backend>/<2-hex-shard>/<compileKey>.xcbc

说明:

  1. 这是运行时编译产物,不建议和 source artifact 混在同一目录。
  2. 它本质上也是缓存工件,但生命周期和失效条件不同。
  3. 数据库仍然在根目录,二进制 payload 用分片目录管理。

6. Artifact Container 格式设计

6.1 容器最小结构

每个 .xca 至少包含:

  1. 文件头
  2. 格式版本
  3. 工件类型
  4. entry 数量
  5. entry 目录表
  6. payload 区
  7. 校验信息

6.2 Entry 元信息

每个 entry 需要至少记录:

  1. entryId
  2. entryName
  3. resourceType
  4. localID
  5. offset
  6. size
  7. compression
  8. flags

6.3 Manifest 内置化

原本外置的:

  1. subassets.tsv

后续变为:

  1. container 内置 manifest entry
  2. 或者直接由 entry table 本身承担 manifest 作用

原则是:

  1. 子资源映射不能再依赖工件目录里的额外文本文件。

6.4 调试能力不能丢

虽然外部从多文件变成单文件,但不能把可调试性彻底丢掉。

因此建议提供:

  1. debug dump 工具
  2. artifact inspect 命令
  3. container 列表查看能力
  4. 按需导出某个 entry 的开发期工具

这样能避免后续排障时变成纯黑盒。


7. 对导入链路的改造要求

7.1 Importer 层统一改为“写 entry”

各 importer 不再自己决定在 artifact 目录里创建几个文件。

统一改为:

  1. importer 构建内存中的 artifact package
  2. 向 package 写入 main entry
  3. 按需写入 subasset entry
  4. 由统一 ArtifactWriter 一次性落成单文件

7.2 各类资源的落地规则

建议采用如下规则:

  1. Texture
    • 通常一个 main entry 即可
  2. Material
    • 通常一个 main entry 即可
  3. Shader
    • 主 entry + 变体元数据 entry + 预编译记录 entry
  4. Model
    • 主 model entry + mesh/material/texture subasset entries
  5. Volume
    • 主 volume entry + 可选页表/brick 元数据 entry
  6. GaussianSplat
    • 主 splat entry + 索引/排序辅助元数据 entry

7.3 导入器必须停止暴露真实 sibling 文件路径

后续 importer 不应再让外部系统依赖:

  1. mesh_0.xcmesh
  2. material_3.xcmat
  3. texture_1.xctex

如果某处仍然需要这种路径式访问,只能通过兼容层临时桥接,不能作为新架构继续扩散。


8. 对加载链路的改造要求

8.1 Loader 必须从“读文件路径”改成“读 artifact entry”

未来 loader 的输入应逐步变为:

  1. artifact 文件路径
  2. entry id / local id / entry name

而不是:

  1. 一个已经存在于磁盘上的子文件路径

8.2 ResourceManager / AssetDatabase 需要新增解析能力

需要正式提供:

  1. ResolveMainArtifactEntry(...)
  2. ResolveSubAssetEntry(...)
  3. OpenArtifactContainer(...)
  4. LoadResourceFromArtifactEntry(...)

这样 AssetDatabaseResourceManager 就能以“工件条目”作为统一边界。

8.3 允许阶段性兼容旧式 Loader

为了降低一次性改造风险,允许在迁移阶段存在:

  1. 新容器 loader
  2. 旧文件路径 loader

但要求:

  1. 新导入默认只产出新容器
  2. 旧 loader 仅承担读旧缓存或读测试基线的过渡职责
  3. 最终必须收口到容器 entry 模型

9. Shader 编译缓存设计

9.1 目标

shader 缓存系统要解决三件事:

  1. 避免同一 shader variant 在同一 backend 上反复现场编译
  2. 避免 editor 启动时因大批 shader 同步编译造成长时间卡顿
  3. 把编译结果纳入 Library 生命周期管理,而不是散落在临时模块里

9.2 Shader 缓存分层

建议分成两层:

  1. Shader Artifact
    • 记录 shader 源资源、变体定义、依赖签名、反射元数据
  2. Shader Bytecode Cache
    • 记录某 backend、某 profile、某 variant 的实际编译产物

这样做的原因是:

  1. shader 资源本身是 source artifact 的一部分
  2. 字节码则是“shader artifact 在某后端上的派生产物”

9.3 多后端适配策略

必须从一开始就定义好多后端字段:

  1. backend = D3D12 / Vulkan / OpenGL
  2. compiler = DXC / glslang / driver / other
  3. profile = ps_6_0 / vs_6_0 / spirv-vulkan1.2 / glsl-450
  4. bytecodeFormat = DXIL / SPIRV / GL_PROGRAM_BINARY / SOURCE_FALLBACK

9.4 OpenGL 的现实处理

OpenGL 不一定能像 D3D12 一样稳定落真正可移植字节码,因此计划要区分:

  1. 理想路径:驱动支持 program binary可缓存 program binary
  2. 保守路径:缓存预处理后的 shader source / variant expansion 结果
  3. 最低保证:即使不能完全复用最终 program也要复用前置预处理和 variant 展开结果

也就是说,多后端适配不等于三端完全同构,而是统一框架下允许 payload 能力不同。


10. 迁移策略

10.1 不能一次性硬切

因为当前项目已经存在:

  1. Library
  2. 老 artifact 目录结构
  3. 依赖老结构的 loader
  4. 依赖老结构的测试

所以必须采用兼容迁移。

10.2 建议迁移阶段

阶段 A底层能力先落地

  1. 新增 ArtifactContainerReader/Writer
  2. 新增 container format version
  3. 新增 ShaderCompilationCache 正式接口
  4. 新增调试工具和 dump 工具

阶段 B数据库与解析接口升级

  1. ArtifactDB 加入新字段
  2. AssetDatabase 提供 entry 级解析
  3. ResourceManager 能按 entry 加载

阶段 C先迁移低复杂度资源

  1. Texture
  2. Material
  3. Shader

原因:

  1. 这三类资源 entry 结构简单
  2. 容器化收益直接
  3. 风险比 Model

阶段 D迁移复杂资源

  1. Model
  2. Volume
  3. GaussianSplat

这里才是这次重构的真正硬骨头。

阶段 E兼容收口

  1. 新导入全面只写新 container
  2. 老 artifact 仅保留读取兼容
  3. 清理临时桥接路径
  4. 测试与工具全部对齐新语义

10.3 旧缓存处理策略

建议采用:

  1. 读旧、写新
  2. 命中新格式则直接用
  3. 命中旧格式则读取并标记可重导
  4. 必要时提供“一键清空旧缓存并全量重导”入口

11. 分阶段执行计划

Phase 1方案冻结与底层抽象

目标:

  1. 冻结 artifact container 格式
  2. 冻结 shader compile key 规则
  3. 冻结 ArtifactDB 新字段语义

交付物:

  1. ArtifactContainer 设计文档
  2. ShaderCompilationCache 设计文档
  3. Library 目录与文件命名规范

验收:

  1. 所有核心模块对新名词和新边界达成一致
  2. 不再新增基于目录 sibling 文件的新逻辑

Phase 2容器读写基础设施

目标:

  1. 写出可工作的 .xca 读写器
  2. 写出 entry table、payload、校验、版本管理

交付物:

  1. ArtifactContainerWriter
  2. ArtifactContainerReader
  3. artifact inspect / dump 工具
  4. 单元测试

验收:

  1. 可写入多个 entry
  2. 可随机读取指定 entry
  3. 可检测格式版本与损坏文件

Phase 3数据库与资源解析升级

目标:

  1. AssetDatabaseResourceManager 理解 entry 语义

交付物:

  1. ArtifactDB schema 升级
  2. entry 级 resolve API
  3. 新旧格式兼容查询路径

验收:

  1. 不依赖真实 sibling 文件也能定位主资源和子资源
  2. 旧缓存仍可读

Phase 4Shader 缓存正式化

目标:

  1. 把 shader 编译缓存正式接入 Library
  2. 打通多后端 key 体系

交付物:

  1. shadercache.db
  2. Library/ShaderCache/...
  3. D3D12 正式字节码缓存
  4. Vulkan/OpenGL 占位与接口接入
  5. 计时日志、命中日志、失效日志

验收:

  1. D3D12 重启 editor 后能稳定命中缓存
  2. shader compile log 能明确区分 cache hit / miss
  3. Vulkan/OpenGL 不破坏接口一致性

Phase 5低复杂度资源迁移

目标:

  1. 先迁移 Texture / Material / Shader

交付物:

  1. 新 importer 写 container
  2. 新 loader 读 container
  3. 旧逻辑兼容桥接

验收:

  1. 三类资源脱离目录式 artifact
  2. 现有测试通过

Phase 6复杂资源迁移

目标:

  1. 迁移 Model / Volume / GaussianSplat

交付物:

  1. Model 子资源 entry 化
  2. Volume 复杂数据 entry 化
  3. GaussianSplat 复杂数据 entry 化
  4. 新的 subasset resolve 机制

验收:

  1. 不再需要 subassets.tsv
  2. 不再依赖 mesh_0.xcmesh/material_0.xcmat/... 真实文件
  3. 场景加载、运行时 streaming、编辑器选择与重载行为正常

Phase 7兼容收口与库清理

目标:

  1. 清理临时桥接逻辑
  2. 完成旧格式收口

交付物:

  1. 旧 artifact 兼容策略文档
  2. Library 清理工具
  3. 最终回归测试与性能报告

验收:

  1. 新项目默认只产生新格式
  2. 旧项目迁移路径明确
  3. 文档、测试、工具全部收口

12. 风险评估

12.1 高风险点

  1. Model 子资源引用路径改造
  2. 旧 loader 和新 loader 并存期间的行为分叉
  3. ArtifactDB schema 迁移
  4. shader 缓存 key 设计不完整导致误命中或漏命中
  5. OpenGL 后端缓存能力不如 D3D12/Vulkan 稳定

12.2 主要回归风险

  1. 资源重复导入
  2. 子资源引用失效
  3. 重启后缓存不命中
  4. editor 启动时再次出现同步长阻塞
  5. 清理 Library 后行为和旧项目不一致

12.3 风险控制原则

  1. 每种资源类型单独迁移、单独验收
  2. 每阶段都保留明确日志
  3. 优先做“读旧写新”,不要先做“只认新格式”
  4. 性能优化必须建立在日志可证伪的基础上,不能靠感觉判断

13. 验收标准

重构完成后,应满足以下标准:

  1. Library/Artifacts 中一个 artifactKey 对应一个单文件 container。
  2. assets.db / artifacts.db / shadercache.db 位于 Library 根目录。
  3. Texture / Material / Shader / Model / Volume / GaussianSplat 都能通过统一 container 读写。
  4. 新增资源类型时,不需要再发明“这个类型自己往 artifact 目录塞几个文件”的模式。
  5. shader 缓存在 D3D12 上稳定命中,Vulkan / OpenGL 具备同框架下的扩展位。
  6. editor 启动和场景 ready 时间相较当前基线有可测收益。
  7. 调试工具可以查看 container 内部 entry不会因为单文件化而失去排障能力。

14. 本计划明确不做的事情

本计划不做以下偏题内容:

  1. 为了“长得像 Unity”而做无意义目录重命名
  2. 把所有缓存强行塞进一个数据库 blob 字段里
  3. 为了单文件化而牺牲调试能力
  4. 只做 D3D12 特化、把其他后端晾着不管
  5. 一次性删掉旧 Library 兼容路径

15. 推荐执行顺序

建议严格按下面顺序推进:

  1. 先冻结 container 格式和 shader key 规则
  2. 再做底层 reader/writer 与数据库升级
  3. 先迁移 Texture / Material / Shader
  4. 再迁移 Model / Volume / GaussianSplat
  5. 最后做兼容收口、清理和性能复盘

这条顺序的原因很简单:

  1. 先做底层,后续每种资源都能复用
  2. 先啃简单资源,能尽快验证架构不是错的
  3. 把最难的复杂资源放在基础设施稳定之后处理,整体风险最低

16. 最终判断

这个重构的代价不小,但如果项目接下来还会持续扩展资源类型,那么它属于“越早做越划算”的长期基础设施工程。

真正值得做的不是把目录外观改得像 Unity而是

  1. 统一 artifact 的存储模型
  2. 统一子资源的表达方式
  3. 统一 shader 编译缓存的 Library 化管理
  4. 统一多后端的缓存接口

只要这四件事做对了,后面资源类型再增加,系统复杂度才不会继续失控。