# Shader 统一预编译缓存并入 Library 正式方案 文档日期:2026-04-11 ## 1. 目标 把 shader 的预编译结果正式并入现有 `Library/Artifacts` 体系,不再允许出现: 1. project shader 走 `Library`,builtin shader 走裸加载。 2. 运行时每次开编辑器都重新现场编译重 shader。 3. 只修 D3D12,一到 Vulkan / OpenGL 又退回运行时临时编译。 本轮正式目标: 1. builtin shader 与 project shader 统一走同一个 artifact 产物格式。 2. `ShaderStageVariant` 保持现有 authoring / runtime 变体模型不变,不扩成“每后端一个 variant”。 3. 每个 variant 可以携带“按后端区分的已编译 payload”。 4. D3D12 / Vulkan / OpenGL 运行时优先消费 artifact 中的 payload,只有 miss 时才 fallback 到现场编译。 ## 2. 架构原则 ### 2.1 唯一正式缓存位置 唯一正式 shader 预编译缓存位置是: `project/Library/Artifacts/.../main.xcshader` 不新增独立 runtime cache 目录,不做旁路缓存。 ### 2.2 builtin shader 也必须纳入 Library 外部标识仍然保持: `builtin://shaders/...` 但在有 project root 时,真实加载路径应优先变成: `Library/Artifacts/.../main.xcshader` 这样 builtin shader 与普通 `Assets/*.shader` 在缓存语义上完全一致。 ### 2.3 不改变现有 variant 选择逻辑 现有很多代码和测试都依赖: 1. authoring variant 可以是 `Generic` 2. 运行时按 backend 查找时先找精确 backend,再回退 `Generic` 因此不把一个 `Generic` variant 展开成三份后端 variant,而是让一个 variant 内部携带: 1. 旧字段 `compiledBinary` 2. 新字段 `backendCompiledBinaries` ## 3. 数据模型 ### 3.1 运行时 compile desc `RHI::ShaderCompileDesc` 新增: 1. `compiledBinaryBackend` 2. `compiledBinary` 含义: 1. source / fileName 仍然描述“可回退到现场编译的输入” 2. compiledBinary 描述“当前后端可直接消费的已编译 payload” ### 3.2 shader variant `Resources::ShaderStageVariant` 支持: 1. `GetCompiledBinaryForBackend(...)` 2. `SetCompiledBinaryForBackend(...)` 语义: 1. backend-specific variant 可以继续把本后端 payload 放在旧 `compiledBinary` 2. `Generic` variant 的 D3D12 / Vulkan / OpenGL payload 放在 `backendCompiledBinaries` ### 3.3 artifact schema shader artifact schema 升级为 `6`,`ShaderVariantArtifactHeader` 新增: 1. `backendCompiledBinaryCount` 并追加 `ShaderBackendCompiledBinaryArtifactHeader + payload` 序列。 ## 4. 导入阶段 导入 `.shader` 时: 1. 先用 `ShaderLoader` 生成现有 authoring/runtime variant。 2. 对每个 pass / variant 生成运行时 compile desc。 3. 按目标后端尝试预编译: - D3D12:生成 DXBC - Vulkan:生成 Vulkan SPIR-V - OpenGL:生成 OpenGL-target SPIR-V 4. 把结果写回 variant。 5. 最终统一写入 `main.xcshader` artifact。 后端目标规则: 1. `Generic` variant 预编译到 D3D12 / Vulkan / OpenGL 2. backend-specific variant 只预编译自己的 backend 失败策略: 1. 预编译失败不阻断 import 2. artifact 仍然照常生成 3. 运行时保持 fallback 能力 4. 日志明确记录 pass / stage / backend / reason ## 5. builtin shader 接入方式 ### 5.1 ResourceManager `ResourceManager::LoadResource(...)` 在 shader + builtin path 情况下: 1. 解析到真实 builtin shader 资产路径 2. 在当前 project 的 `Library` 中确保对应 artifact 3. 真正加载 artifact 4. 资源对外 path 仍然恢复成 `builtin://...` ### 5.2 AssetDatabase 由于 builtin shader 资产位于 project root 外部: 1. 允许绝对路径解析成相对 project root 的 `../engine/...` 2. 这些外部 source record 不写 `.meta` 3. 使用 synthetic GUID / synthetic meta hash 4. `Refresh()` 时显式扫描 builtin shader 资产,避免下次启动把 builtin artifact 当 orphan 清掉 ## 6. 运行时消费 ### 6.1 D3D12 1. `D3D12Shader` 增加 `InitializeFromBytecode(...)` 2. `CompileD3D12Shader(...)` 优先命中 `compiledBinaryBackend == D3D12` 3. miss 时才走 `D3DCompile(...)` 4. `CreateShader(...)` 统一复用这条逻辑 ### 6.2 Vulkan 1. `CompileSpirvShader(...)` 支持直接消费 `compiledBinaryBackend == Vulkan` 2. `VulkanDevice / VulkanPipelineState` 通过既有 `CompileVulkanShader(...)` 自动命中缓存 payload ### 6.3 OpenGL 1. 若命中 `compiledBinaryBackend == OpenGL` 2. 则把该 payload 作为 SPIR-V 输入 3. 继续走现有 `SPIR-V -> GLSL` 转译路径 4. 原始 HLSL source 仍保留给 sampler/texture 绑定推导逻辑使用 ## 7. 验收标准 ### 7.1 工程标准 1. `shader_tests` 全绿 2. `XCEditor` 能完整编译通过 ### 7.2 行为标准 1. shader artifact roundtrip 后不丢失 backend payload 2. builtin shader 与 project shader 都能命中 `Library/Artifacts` 3. D3D12 / Vulkan / OpenGL 运行时在 payload 存在时不再重复现场编译 ### 7.3 性能标准 1. 影响 NanoVDB 首帧的 volumetric shader 不再在每次启动时重编 2. editor 打开主场景时,shader stall 不再成为 `SceneReady` 的主瓶颈 ## 8. 后续扩展 后续若继续做: 1. import-time completeness 标记 2. 运行时回写 artifact 3. PSO cache blob 4. 更完整的 shader compile telemetry 都必须建立在本方案之上,不允许再引入平行缓存体系。