# 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 entry,`AssetDatabase` 子资源解析优先走 entry 解析。 5. `XCUI` 文档 artifact 也已补齐为单文件 container,避免 `Library` 内继续残留旧式 artifact directory 生产路径。 6. 新格式写入路径已经统一到 `Library/Artifacts//.`,旧格式保持读取兼容。 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.db` 与 `artifacts.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>/.xca` 说明: 1. 仍然保留分片目录,避免单目录堆积过多文件。 2. 从“目录工件”改为“单文件工件”。 3. 这里已经足够接近 Unity 的外观,但关键收益来自统一 container,不来自“像 Unity”本身。 ### 5.3 Shader 编译缓存目录 建议使用: 1. `Library/ShaderCache//<2-hex-shard>/.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(...)` 这样 `AssetDatabase` 和 `ResourceManager` 就能以“工件条目”作为统一边界。 ### 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. 让 `AssetDatabase` 和 `ResourceManager` 理解 entry 语义 交付物: 1. `ArtifactDB` schema 升级 2. entry 级 resolve API 3. 新旧格式兼容查询路径 验收: 1. 不依赖真实 sibling 文件也能定位主资源和子资源 2. 旧缓存仍可读 ## Phase 4:Shader 缓存正式化 目标: 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. 统一多后端的缓存接口 只要这四件事做对了,后面资源类型再增加,系统复杂度才不会继续失控。