20 KiB
Library 统一 Artifact 容器与多后端 Shader 缓存重构计划
日期:2026-04-11
0. 当前执行状态(2026-04-11 更新)
当前整体进度:约 88%
已完成:
ArtifactContainer读写、校验、虚拟 entry path 已落地,并有核心单测覆盖。ShaderCompilationCache已正式进入Library体系,D3D12已有稳定命中路径与单测覆盖。Texture / Material / Shader / Model / Volume / GaussianSplat已切到单文件 artifact container 写入与读取语义。Model子资源已从真实 sibling 文件切到 container entry,AssetDatabase子资源解析优先走 entry 解析。XCUI文档 artifact 也已补齐为单文件 container,避免Library内继续残留旧式 artifact directory 生产路径。- 新格式写入路径已经统一到
Library/Artifacts/<shard>/<artifactKey>.<ext>,旧格式保持读取兼容。 artifact_inspect已落地,可直接查看 container entry 列表并导出指定 entry payload。
已验证:
ArtifactContainer.*ShaderCompilationCache.*TextureLoader关键 artifact 回归MaterialLoader关键 artifact / reimport 回归ShaderLoader关键 artifact / dependency 回归ModelLoader / ModelImportPipeline / MeshLoader关键 container-entry 回归VolumeFieldLoader关键 artifact 回归GaussianSplatLoader.*UIDocumentLoader.*UISchemaDocument.*
尚未收口:
ArtifactDB还没有彻底升级到“显式 entry 元数据” schema,当前仍保留一部分旧字段语义用于兼容迁移。Library清理工具、最终性能报告、旧格式彻底收口文档还没完成。Vulkan / OpenGL目前已经接入统一 shader cache 框架边界,但还没有做到和D3D12同等级的完整产物复用能力。
1. 文档定位
这份计划不是在讨论“把 Library/Artifacts 的目录名字改得更像 Unity”。
这份计划要解决的是更底层的问题:
- 现在的 artifact 基本存储单位是“一个目录 + 多个真实文件”,不是“一个统一工件”。
- 复杂资源类型一增加,导入器、加载器、子资源解析、测试都会继续把这种多文件假设扩散到更多模块。
- shader 运行时编译缓存虽然已经开始建立,但还没有上升为整个
Library体系中的正式基础设施。
因此,本计划的目标是正式把 Library 重构为一套统一的缓存架构:
- 导入产物统一落成单文件 artifact container。
- 子资源从“磁盘上的 sibling 文件”变成“artifact 内部 entry”。
- shader 编译产物纳入
Library正式缓存体系,并支持多后端。 - 整个系统继续保留现有
AssetDatabase / ResourceManager / ArtifactDB的架构思路,不搞一套平行新系统。
2. 当前状态与根本问题
2.1 当前已经成立的基础
目前 Library 体系已经具备以下基础能力:
assets.db与artifacts.db已经位于Library根目录。AssetDatabase已经负责 source asset 扫描、依赖签名、artifact 复用和重导。- 各资源导入器已经能够把 source asset 写入 artifact。
- shader 编译缓存已经有初步命中机制,且收益已经被实际验证过。
2.2 当前真正的问题
当前问题不在于“有没有缓存”,而在于“缓存的基本抽象还不够统一”。
以模型为例,当前一个导入结果并不是一个真正的工件,而是一整个目录:
main.xcmodelmesh_0.xcmeshmaterial_0.xcmattexture_0.xctexsubassets.tsv
这意味着:
- artifact 的语义是“目录”,不是“文件”。
- loader 和测试可以直接依赖某个 sibling 文件的真实路径。
- 子资源关系是通过外部文件名和外部 manifest 维持的。
- 新增资源类型时,导入器很容易继续沿用“往 artifact 目录里再塞几个文件”的模式。
2.3 为什么这个问题越晚改越难
随着资源类型增加,当前模式会继续扩散到:
ModelShaderVolumeGaussianSplat- 未来的
AnimationClip / Skeleton / Audio / VFX / Prefab-like Asset
越往后,耦合会越来越深:
- 更多 importer 会直接生成多个磁盘文件。
- 更多 loader 会默认自己拿到的就是磁盘路径。
- 更多测试会把 artifact 文件名结构视为系统契约。
- 后面再做统一 container 时,每种资源都要单独回迁一次。
所以这次重构如果要做,越早越值。
3. 重构目标
本轮重构的最终目标如下:
Library中“一个 artifactKey 对应一个单文件 container”。- artifact container 内部可以保存主资源和多个子资源 entry。
ArtifactDB记录的是“工件文件 + 条目索引信息”,不再把目录结构当成核心语义。- loader 不再依赖 sibling 文件真实存在,而是通过 container entry 加载。
- shader 编译缓存成为正式的
Library子系统,支持D3D12 / Vulkan / OpenGL多后端扩展。 - 老项目和旧
Library尽量兼容迁移,避免一次性炸库。
4. 核心架构决策
4.1 一个 artifact 必须变成一个单文件 container
正式改为:
- 一个
artifactKey只对应一个 artifact 文件。 - artifact 文件内部包含 header、目录表、entry 表、payload 区。
- entry 可以表达:
- main asset
- subasset
- import metadata
- dependency snapshot
- debug manifest
建议目标形态:
Library/Artifacts/07/071822a133c727103210833b93d2b846.xca- 或保留无扩展 hash 文件形式,但内部仍然是统一 container
这里更重要的是“单文件容器语义”,不是扩展名本身。
4.2 子资源必须从真实文件变成逻辑 entry
当前的:
mesh_0.xcmeshmaterial_0.xcmattexture_0.xctexsubassets.tsv
后续都不再作为 artifact 目录中的真实文件暴露。
改为:
- container entry
main - container entry
mesh/0 - container entry
material/0 - container entry
texture/0 - container entry
manifest/subassets
这样做的目的不是“隐藏文件”,而是统一系统抽象:
- importer 只负责产出 entry。
- loader 只负责按 entry 解析。
- 外部磁盘布局不再影响运行时语义。
4.3 ArtifactDB 只维护工件语义,不维护目录语义
ArtifactDB 需要从当前的字段语义升级为:
artifactKeyartifactPathartifactFormatVersionmainEntryIdresourceTypedependencySignaturesourceHash / metaHash / importerVersion- 可选的
subasset index snapshot
不再把下列内容作为核心前提:
artifactDirectorymainArtifactPath必须是磁盘上的某个独立 sibling 文件
4.4 Shader 编译缓存必须纳入 Library 正式体系
shader 编译缓存不能继续以“某个功能修复补丁”的方式存在,必须被定义为 Library 正式缓存层的一部分。
正式目标:
- 以编译 key 唯一标识一份 shader bytecode / program binary。
- 编译 key 必须包含:
- shader 源文件签名
- include 依赖签名
- keyword / variant 宏
- backend 类型
- compiler 类型与版本
- profile / entry point
- 相关编译选项
- 多后端共用同一套 key 生成规则和缓存查询流程。
- 后端各自写入自己的 payload,不共享错误的二进制格式。
4.5 必须坚持“统一接口,多后端实现”
这次重构不能只为 D3D12 做一套临时特化路径。
必须采用:
- 上层统一的
ShaderCompilationCache接口 - 后端相关的 key 参与规则
- 后端相关的 payload 类型
- 后端相关的反序列化逻辑
这样后面:
D3D12存 DXIL / DXBCVulkan存 SPIR-VOpenGL可以先存 source variant 或 program binary capability 结果
都能挂到同一架构下,而不是分裂成三套缓存系统。
5. 目标目录与数据布局
5.1 Library 根目录
建议最终保留如下根层文件:
Library/assets.dbLibrary/artifacts.dbLibrary/shadercache.dbLibrary/assets.db.tmp/.bak/.lockLibrary/artifacts.db.tmp/.bak/.lockLibrary/shadercache.db.tmp/.bak/.lock
说明:
- 单文件数据库应放在
Library根目录,不再额外套目录。 .tmp/.bak/.lock是数据库运行期一致性与恢复所需的辅助文件,属于正常成本,应保留。
5.2 Artifact 内容目录
建议统一为:
Library/Artifacts/<2-hex-shard>/<artifactKey>.xca
说明:
- 仍然保留分片目录,避免单目录堆积过多文件。
- 从“目录工件”改为“单文件工件”。
- 这里已经足够接近 Unity 的外观,但关键收益来自统一 container,不来自“像 Unity”本身。
5.3 Shader 编译缓存目录
建议使用:
Library/ShaderCache/<backend>/<2-hex-shard>/<compileKey>.xcbc
说明:
- 这是运行时编译产物,不建议和 source artifact 混在同一目录。
- 它本质上也是缓存工件,但生命周期和失效条件不同。
- 数据库仍然在根目录,二进制 payload 用分片目录管理。
6. Artifact Container 格式设计
6.1 容器最小结构
每个 .xca 至少包含:
- 文件头
- 格式版本
- 工件类型
- entry 数量
- entry 目录表
- payload 区
- 校验信息
6.2 Entry 元信息
每个 entry 需要至少记录:
entryIdentryNameresourceTypelocalIDoffsetsizecompressionflags
6.3 Manifest 内置化
原本外置的:
subassets.tsv
后续变为:
- container 内置 manifest entry
- 或者直接由 entry table 本身承担 manifest 作用
原则是:
- 子资源映射不能再依赖工件目录里的额外文本文件。
6.4 调试能力不能丢
虽然外部从多文件变成单文件,但不能把可调试性彻底丢掉。
因此建议提供:
- debug dump 工具
- artifact inspect 命令
- container 列表查看能力
- 按需导出某个 entry 的开发期工具
这样能避免后续排障时变成纯黑盒。
7. 对导入链路的改造要求
7.1 Importer 层统一改为“写 entry”
各 importer 不再自己决定在 artifact 目录里创建几个文件。
统一改为:
- importer 构建内存中的 artifact package
- 向 package 写入 main entry
- 按需写入 subasset entry
- 由统一
ArtifactWriter一次性落成单文件
7.2 各类资源的落地规则
建议采用如下规则:
Texture- 通常一个 main entry 即可
Material- 通常一个 main entry 即可
Shader- 主 entry + 变体元数据 entry + 预编译记录 entry
Model- 主 model entry + mesh/material/texture subasset entries
Volume- 主 volume entry + 可选页表/brick 元数据 entry
GaussianSplat- 主 splat entry + 索引/排序辅助元数据 entry
7.3 导入器必须停止暴露真实 sibling 文件路径
后续 importer 不应再让外部系统依赖:
mesh_0.xcmeshmaterial_3.xcmattexture_1.xctex
如果某处仍然需要这种路径式访问,只能通过兼容层临时桥接,不能作为新架构继续扩散。
8. 对加载链路的改造要求
8.1 Loader 必须从“读文件路径”改成“读 artifact entry”
未来 loader 的输入应逐步变为:
- artifact 文件路径
- entry id / local id / entry name
而不是:
- 一个已经存在于磁盘上的子文件路径
8.2 ResourceManager / AssetDatabase 需要新增解析能力
需要正式提供:
ResolveMainArtifactEntry(...)ResolveSubAssetEntry(...)OpenArtifactContainer(...)LoadResourceFromArtifactEntry(...)
这样 AssetDatabase 和 ResourceManager 就能以“工件条目”作为统一边界。
8.3 允许阶段性兼容旧式 Loader
为了降低一次性改造风险,允许在迁移阶段存在:
- 新容器 loader
- 旧文件路径 loader
但要求:
- 新导入默认只产出新容器
- 旧 loader 仅承担读旧缓存或读测试基线的过渡职责
- 最终必须收口到容器 entry 模型
9. Shader 编译缓存设计
9.1 目标
shader 缓存系统要解决三件事:
- 避免同一 shader variant 在同一 backend 上反复现场编译
- 避免 editor 启动时因大批 shader 同步编译造成长时间卡顿
- 把编译结果纳入
Library生命周期管理,而不是散落在临时模块里
9.2 Shader 缓存分层
建议分成两层:
Shader Artifact- 记录 shader 源资源、变体定义、依赖签名、反射元数据
Shader Bytecode Cache- 记录某 backend、某 profile、某 variant 的实际编译产物
这样做的原因是:
- shader 资源本身是 source artifact 的一部分
- 字节码则是“shader artifact 在某后端上的派生产物”
9.3 多后端适配策略
必须从一开始就定义好多后端字段:
backend = D3D12 / Vulkan / OpenGLcompiler = DXC / glslang / driver / otherprofile = ps_6_0 / vs_6_0 / spirv-vulkan1.2 / glsl-450bytecodeFormat = DXIL / SPIRV / GL_PROGRAM_BINARY / SOURCE_FALLBACK
9.4 OpenGL 的现实处理
OpenGL 不一定能像 D3D12 一样稳定落真正可移植字节码,因此计划要区分:
- 理想路径:驱动支持 program binary,可缓存 program binary
- 保守路径:缓存预处理后的 shader source / variant expansion 结果
- 最低保证:即使不能完全复用最终 program,也要复用前置预处理和 variant 展开结果
也就是说,多后端适配不等于三端完全同构,而是统一框架下允许 payload 能力不同。
10. 迁移策略
10.1 不能一次性硬切
因为当前项目已经存在:
- 老
Library - 老 artifact 目录结构
- 依赖老结构的 loader
- 依赖老结构的测试
所以必须采用兼容迁移。
10.2 建议迁移阶段
阶段 A:底层能力先落地
- 新增
ArtifactContainerReader/Writer - 新增 container format version
- 新增
ShaderCompilationCache正式接口 - 新增调试工具和 dump 工具
阶段 B:数据库与解析接口升级
ArtifactDB加入新字段AssetDatabase提供 entry 级解析ResourceManager能按 entry 加载
阶段 C:先迁移低复杂度资源
TextureMaterialShader
原因:
- 这三类资源 entry 结构简单
- 容器化收益直接
- 风险比
Model低
阶段 D:迁移复杂资源
ModelVolumeGaussianSplat
这里才是这次重构的真正硬骨头。
阶段 E:兼容收口
- 新导入全面只写新 container
- 老 artifact 仅保留读取兼容
- 清理临时桥接路径
- 测试与工具全部对齐新语义
10.3 旧缓存处理策略
建议采用:
- 读旧、写新
- 命中新格式则直接用
- 命中旧格式则读取并标记可重导
- 必要时提供“一键清空旧缓存并全量重导”入口
11. 分阶段执行计划
Phase 1:方案冻结与底层抽象
目标:
- 冻结 artifact container 格式
- 冻结 shader compile key 规则
- 冻结
ArtifactDB新字段语义
交付物:
ArtifactContainer设计文档ShaderCompilationCache设计文档Library目录与文件命名规范
验收:
- 所有核心模块对新名词和新边界达成一致
- 不再新增基于目录 sibling 文件的新逻辑
Phase 2:容器读写基础设施
目标:
- 写出可工作的
.xca读写器 - 写出 entry table、payload、校验、版本管理
交付物:
ArtifactContainerWriterArtifactContainerReader- artifact inspect / dump 工具
- 单元测试
验收:
- 可写入多个 entry
- 可随机读取指定 entry
- 可检测格式版本与损坏文件
Phase 3:数据库与资源解析升级
目标:
- 让
AssetDatabase和ResourceManager理解 entry 语义
交付物:
ArtifactDBschema 升级- entry 级 resolve API
- 新旧格式兼容查询路径
验收:
- 不依赖真实 sibling 文件也能定位主资源和子资源
- 旧缓存仍可读
Phase 4:Shader 缓存正式化
目标:
- 把 shader 编译缓存正式接入
Library - 打通多后端 key 体系
交付物:
shadercache.dbLibrary/ShaderCache/...- D3D12 正式字节码缓存
- Vulkan/OpenGL 占位与接口接入
- 计时日志、命中日志、失效日志
验收:
- D3D12 重启 editor 后能稳定命中缓存
- shader compile log 能明确区分 cache hit / miss
- Vulkan/OpenGL 不破坏接口一致性
Phase 5:低复杂度资源迁移
目标:
- 先迁移
Texture / Material / Shader
交付物:
- 新 importer 写 container
- 新 loader 读 container
- 旧逻辑兼容桥接
验收:
- 三类资源脱离目录式 artifact
- 现有测试通过
Phase 6:复杂资源迁移
目标:
- 迁移
Model / Volume / GaussianSplat
交付物:
Model子资源 entry 化Volume复杂数据 entry 化GaussianSplat复杂数据 entry 化- 新的 subasset resolve 机制
验收:
- 不再需要
subassets.tsv - 不再依赖
mesh_0.xcmesh/material_0.xcmat/...真实文件 - 场景加载、运行时 streaming、编辑器选择与重载行为正常
Phase 7:兼容收口与库清理
目标:
- 清理临时桥接逻辑
- 完成旧格式收口
交付物:
- 旧 artifact 兼容策略文档
Library清理工具- 最终回归测试与性能报告
验收:
- 新项目默认只产生新格式
- 旧项目迁移路径明确
- 文档、测试、工具全部收口
12. 风险评估
12.1 高风险点
Model子资源引用路径改造- 旧 loader 和新 loader 并存期间的行为分叉
ArtifactDBschema 迁移- shader 缓存 key 设计不完整导致误命中或漏命中
- OpenGL 后端缓存能力不如 D3D12/Vulkan 稳定
12.2 主要回归风险
- 资源重复导入
- 子资源引用失效
- 重启后缓存不命中
- editor 启动时再次出现同步长阻塞
- 清理
Library后行为和旧项目不一致
12.3 风险控制原则
- 每种资源类型单独迁移、单独验收
- 每阶段都保留明确日志
- 优先做“读旧写新”,不要先做“只认新格式”
- 性能优化必须建立在日志可证伪的基础上,不能靠感觉判断
13. 验收标准
重构完成后,应满足以下标准:
Library/Artifacts中一个 artifactKey 对应一个单文件 container。assets.db / artifacts.db / shadercache.db位于Library根目录。Texture / Material / Shader / Model / Volume / GaussianSplat都能通过统一 container 读写。- 新增资源类型时,不需要再发明“这个类型自己往 artifact 目录塞几个文件”的模式。
- shader 缓存在
D3D12上稳定命中,Vulkan / OpenGL具备同框架下的扩展位。 - editor 启动和场景 ready 时间相较当前基线有可测收益。
- 调试工具可以查看 container 内部 entry,不会因为单文件化而失去排障能力。
14. 本计划明确不做的事情
本计划不做以下偏题内容:
- 为了“长得像 Unity”而做无意义目录重命名
- 把所有缓存强行塞进一个数据库 blob 字段里
- 为了单文件化而牺牲调试能力
- 只做 D3D12 特化、把其他后端晾着不管
- 一次性删掉旧
Library兼容路径
15. 推荐执行顺序
建议严格按下面顺序推进:
- 先冻结 container 格式和 shader key 规则
- 再做底层 reader/writer 与数据库升级
- 先迁移
Texture / Material / Shader - 再迁移
Model / Volume / GaussianSplat - 最后做兼容收口、清理和性能复盘
这条顺序的原因很简单:
- 先做底层,后续每种资源都能复用
- 先啃简单资源,能尽快验证架构不是错的
- 把最难的复杂资源放在基础设施稳定之后处理,整体风险最低
16. 最终判断
这个重构的代价不小,但如果项目接下来还会持续扩展资源类型,那么它属于“越早做越划算”的长期基础设施工程。
真正值得做的不是把目录外观改得像 Unity,而是:
- 统一 artifact 的存储模型
- 统一子资源的表达方式
- 统一 shader 编译缓存的 Library 化管理
- 统一多后端的缓存接口
只要这四件事做对了,后面资源类型再增加,系统复杂度才不会继续失控。