奇妙的Shader精度(续)

Author Avatar
Kanglai Qian 1月 15, 2022

这个话题其实已经连着好几次了orz(之前博客分享过Metal 2.1中的新关键字invariant奇妙的Shader精度),但昨天有朋友又遇到PreZ挂了的情况: D3D11下如果关掉Shader Optimize后PreZ和Shading Pass的深度又对不上,但打开Shader Optimize就显示正常。

用老朋友RenderDoc抓帧确实能稳定复现,看下具体VS的输入和输出之后发现:输入的顶点属性是精确一致的,输出的数据有细微区别——具体来说变换后坐标的y是1600.097781600.09802。Shader的话两个Pass略有不同,PreZ用的是简化版的逻辑。直观上觉得这个应该是精度问题,而且是可以稳定复现的那种(朋友电脑上和我本地均可replay)。

继续用Debug Shader功能分别调试了下两个Pass里的,发现最后的结果始终是1600.09778,这个就有点尴尬了…陷入僵局后了,索性尝试了下Edit Shader功能——如果把Shading Pass里的VS简化到类似PreZ的版本,发现确实能得到得到正确的深度。

后来Google下找到了Discrepancy between Mesh Viewer VS Output and corresponding Vertex Shader debug output 感觉和现在这个情况很像:

If the vertex shaders aren’t identical then the calculation for TEXCOORD1 might be slightly different between the two. This would not necessarily reproduce in RenderDoc’s shader debugger since only texture sampling and transcendental functions are run on the GPU, all other operations are run on the CPU at full precision. I’m not clear on whether or not the intermittent appearance was for a precisely identical scene or if you meant it varied while things were moving/animating. If they were, and you’re only out by 1 ULP, it could be the rounding was going in and out of sync between the two shaders.

对比了下我们遇到的情况,确实蛮像的

>>> binascii.hexlify(struct.pack('>f', 1600.09802))
'44c80323'
>>> binascii.hexlify(struct.pack('>f', 1600.09778))
'44c80321'

然后突然被all other operations are run on the CPU at full precision这句话惊醒——RenderDoc的调试功能真的”可靠”么? 往常用的时候从没质疑过这个事情,但是目前确实出现了调试结果和VS Output不一致的情况… 稍微翻了下这部分的实现driver/shaders/dxbc/dxbc_debug.cpp,正如issue里作者所说:只有采样及超越函数是运行在GPU上,其它的本质是跑的CPU版本实现。

进一步翻阅了微软的Floating-point rules (Direct3D 11)

32-bit floating-point operations produce a result that is within 0.5 unit-last-place (ULP) of the infinitely-precise result.

也就是说GPU上跑的结果本来就无法保证和CPU模拟版本的保持一致

自此感觉所有拼图已经准备完成orz

  • DX11上没有invariant类似关键词,所以建议PreZ和Shading Pass的VS保持一模一样(实际上对Metal我们也有类似的Dont know why but it works的玄学经验);
  • 当打开Shader优化时,Shading Pass里的VS可能被简化到比较接近PreZ Pass的版本,所以精度上没出问题;
  • 当关闭Shader优化时,俩Pass的VS计算恰好撞到了精度差异;
    • 关闭Shader优化时、人肉删掉一些Shading Pass里VS的废逻辑,又确实能得到一致的深度结果

没什么用的姿势又增加了又是学到新知识的一天呢