Files
XCEngine/docs/used/Shader统一预编译缓存并入Library正式方案_完成归档_2026-04-11.md

182 lines
5.3 KiB
Markdown
Raw Normal View History

# 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
都必须建立在本方案之上,不允许再引入平行缓存体系。