Graphics 契约:NVN 到 Metal/Vulkan
图形移植不只是 API 逐条翻译,而是把 CPU 命令流、shader stage IO、资源元数据、管线状态、同步和宿主能力共同 组成的隐含契约显式化。完整的契约重建研究和未来工具方向见 21-contract-reconstruction-research.md。
图形链路分层
任何一层的“dummy”“空 handle”“旧 snapshot”都可能静默地产生黑、白或透明画面。
先确认 draw 是否存在
按以下顺序查:
- guest 是否发出 draw/dispatch;
- program 是否为非零、VS/FS 是否都存在;
- vertex/index/UBO 是否有效;
- target 是否有 host backing;
- descriptor 是否绑定真实 image/view/sampler;
- shader 是否产生片元;
- target 是否被后续 pass 合成到 present source。
“no program”不能自动解释黑屏:可能是合法的空绑定,也可能是宿主丢失了 guest state。
Shader 迁移
离线翻译要记录:原始 shader hash、stage、control/code、翻译器版本、binding reservation、SPIR-V/Metal 输出 hash。 必须验证 VS outputs 与 FS inputs 的 location、component、类型和插值语义;不能只验证 shader 编译成功。
纹理与通道
逐个资源记录:NVN format、target、levels/layers、stride/layout、pool offset、view swizzle、host format、 alpha 语义、是否 CPU 写入、是否 render target、是否可复用。R8、BC、capture texture 和 render target view 不能共享同一套全局“聪明规则”。
对象生命周期
对象 key 至少要覆盖 storage identity、描述、format、view/swizzle、代际和 dirty/upload 状态。对象被销毁、pool 重注册或 guest 地址复用时,旧 image 不能以“看起来相同”的 key 复活。异步 replay 下必须让 GPU 完成后再退休资源。
Present 和同步
每个 swapchain image 需要独立的 acquire/submit/present 生命周期;不能只用一个全局 semaphore 或假定窗口 image
始终对应 guest 当前 render target。动态渲染、传统 render pass、sync2、timeline semaphore 等能力要根据 API 版本
和扩展查询,不根据 vkGetDeviceProcAddr 返回非空就判定支持。
诊断的正确用法
- 固定颜色:验证 shader/目标是否真的产生片元;不是修复。
- magenta dummy:验证是否走 fallback;不是证明真实资源错。
- snapshot/readback:判断写入发生在哪个 target;注意 snapshot 时机。
- per-draw fingerprint:比较 Mac/Android 有效输入;优先 hash/摘要,避免大量日志改变时序。