Shader 指令成本回归:离线编译、设备权重与误报控制
Shader 源码只改了几行,为什么某类 GPU 上突然变慢?单看源码行数、SPIR-V 指令数或桌面 GPU 截帧都不够。本文建立离线指标、设备族权重和实机验证的三级回归体系。
一、指令数不是执行时间
不同指令吞吐、延迟和占用不同,编译器还会做常量折叠、展开、合并与寄存器分配。同一中间表示在不同 GPU 架构上可能产生不同机器代码。纹理访问、分支发散、寄存器压力和带宽瓶颈也不会被一个总指令数完整表达。
因此离线数据适合做风险筛选,不适合作为最终性能结论。
二、建立三层指标
第一层是源码与变体:宏、材质特性、Pass、入口函数和目标平台。第二层是编译产物:二进制大小、指令类别、纹理采样、分支、寄存器或编译器可提供的资源统计。第三层是实机:GPU 时间、占用、带宽和场景帧耗时。
每条记录必须绑定 Shader 内容哈希、编译器版本、编译参数、目标架构和变体键。否则所谓"版本差异"可能只是工具链漂移。
三、按设备族建权重,而不是一个全球分数
假设离线工具提供算术、纹理、控制流和寄存器压力等指标,可为每个设备族建立经验风险分数:
text
risk_family =
w1 × arithmetic_delta +
w2 × texture_delta +
w3 × branch_delta +
w4 × register_pressure_delta
权重来自历史实机相关性,不应凭感觉永久固定。这个分数只用于安排验证优先级;超过阈值意味着"必须实测",不是"确认性能回退"。
四、增量分析要找真正变化的变体
Shader 变体数量巨大,不能每次全部高成本分析。先比较源码依赖图、宏配置和内容哈希,筛出受影响变体;再对编译产物做结构化差异;最后挑选高风险变体进入代表场景实测。
公共 include 改动可能影响大面积变体,CI 应显示扇出数量和受影响 Pass。若变体数突然增加,还要先排查变体爆炸,而不只是逐个看指令变化。
五、如何减少误报
- 编译器或驱动版本变化时重建基线,不与旧基线硬比;
- 对极小 Shader 使用绝对变化门槛,避免百分比夸张;
- 对本来就大的 Shader 同时看相对变化和预算上限;
- 忽略经验证不影响目标设备的调试或元数据差异;
- 将离线告警与实际可见性、屏幕覆盖率和调用频次结合;
- 重复实机采样,排除温控和场景随机性。
六、从 Shader 风险映射回场景
离线告警只有映射到材质、Pass、资源和测试场景才可行动。建议维护:
text
shader_variant -> material -> asset -> render_pass -> scenario
当某变体恶化时,系统能自动找出覆盖率最高的回归路线,并在 RenderDoc 截帧中定位对应 DrawCall。这样实测不需要盲目跑全量地图。
七、CI 的分级策略
- 低风险:记录差异,不阻断;
- 中风险:要求目标设备族自动实测;
- 高风险:变体数、寄存器压力或关键 Pass 超预算,阻止晋级;
- 工具链变化:冻结自动判定,先完成基线迁移。
门禁结果要保留原始统计、权重版本和场景验证链接,避免分数成为无法解释的黑盒。
结语
Shader 回归治理的正确路径是"离线筛选---设备族建模---场景实测---截帧归因"。任何单一指标都不足以替代 GPU 时间,但合理的分层体系能把有限设备时间集中到最值得验证的变化上。