【问题标题】:Differing floating point behaviour between uniform and constants in GLSLGLSL中统一和常量之间的不同浮点行为
【发布时间】:2019-01-06 04:22:26
【问题描述】:

我正在尝试在 GLSL 中实现模拟双精度,但我观察到一种奇怪的行为差异,导致 GLSL 中出现细微的浮点错误。

考虑以下片段着色器,写入 4 浮点纹理以打印输出。

layout (location = 0) out vec4 Output
uniform float s;
void main()
{
  float a = 0.1f;
  float b = s;

  const float split = 8193.0; // = 2^13 + 1

  float ca = split * a;
  float cb = split * b;

  float v1a = ca - (ca - a);
  float v1b = cb - (cb - b);

  Output = vec4(a,b,v1a,v1b);
}

这是我观察到的输出

GLSL 输出统一:

a = 0.1            0x3dcccccd
b = 2.86129e-06    0x36400497
v1a = 0.0999756    0x3dccc000
v1b = 2.86129e-06  0x36400497

现在,将b1b2 的给定值作为输入,v2b 的值不具有预期结果。或者至少它没有与 CPU 相同的结果(如 here 所示):

C++ 输出:

a = 0.100000     0x3dcccccd
b = 0.000003     0x36400497
v1a = 0.099976   0x3dccc000
v1b = 0.000003   0x36400000

请注意 v1b 的值的差异(0x364004970x36400000)。

因此,为了弄清楚发生了什么(以及谁是对的),我尝试在 GLSL 中重做计算,将统一值替换为常数,使用稍微修改的着色器,将统一值替换为它的值.

layout (location = 0) out vec4 Output
void main()
{
  float a = 0.1f;
  float b = uintBitsToFloat(0x36400497u);

  const float split = 8193.0; // = 2^13 + 1

  float ca = split * a;
  float cb = split * b;

  float v1a = ca - (ca - a);
  float v1b = cb - (cb - b);

  Output = vec4(a,b,v1a,v1b);
}

这一次,我得到了与相同计算的 C++ 版本相同的输出。

带有常量的 GLSL 输出:

a = 0.1            0x3dcccccd
b = 2.86129e-06    0x36400497
v1a = 0.0999756    0x3dccc000
v1b = 2.86102e-06  0x36400000

我的问题是,是什么让浮点计算在统一变量和常量之间表现不同?这是某种幕后编译器优化吗?

这是我笔记本电脑的英特尔 GPU 中的 OpenGL 供应商字符串,但我也在 nVidia 卡上观察到了相同的行为。

Renderer : Intel(R) HD Graphics 520
Vendor   : Intel
OpenGL   : 4.5.0 - Build 23.20.16.4973
GLSL     : 4.50 - Build 23.20.16.4973

【问题讨论】:

  • 你在比较不同的东西。如果我做对了,差异在于 CPU 而不是 GLSL ...const float 可能会被编译器优化并在编译时计算为double,因此变量和常量之间的差异。 CPU 和 GPU 结果之间通常也存在差异(但不是您的情况),因为 GPU 浮点数并不总是标准浮点数,它们有时具有不同的位数...
  • 如果将float v1b = cb - (cb - b); 更改为float tempb = cb - b; float v1b = cb - tempb;v1a 也是如此),行为是否会改变?
  • 尝试将precise 修饰符添加到v1av1b,因为这看起来像是浮点重新关联或FMA 收缩被用作性能优化的情况(默认情况下允许,我认为),改变结果。
  • re: @Spektre : 问题是第一个版本的结果错误(因为它不符合 IEEE 754 标准,其他两个遵循)。

标签: opengl floating-point glsl shader precision


【解决方案1】:

因此,正如 @njuffa 所提到的,在 cmets 中,通过对依赖于严格 IEEE754 操作的值使用 precise 修饰符来解决问题:

layout (location = 0) out vec4 Output
uniform float s;
void main()
{
  float a = 0.1f;
  float b = s;

  const float split = 8193.0; // = 2^13 + 1

  precise float ca = split * a;
  precise float cb = split * b;

  precise float v1a = ca - (ca - a);
  precise float v1b = cb - (cb - b);

  Output = vec4(a,b,v1a,v1b);
}

输出:

a = 0.1            0x3dcccccd
b = 2.86129e-06    0x36400497
v1a = 0.0999756    0x3dccc000
v1b = 2.86102e-06  0x36400000

编辑:很可能只需要最后一个 precise 来约束导致其计算的操作以避免不必要的优化。

【讨论】:

  • 我也有同样的问题。但我正在研究 WebGL 1.0,而不是 2.0,它不支持“精确”限定符。你知道那里可以做些什么吗?
  • precision highp 怎么样?
  • 我不想迁移到 WebGL 2.0 的原因是某些移动设备不支持它:例如在 iPhone 上
  • 这就是我要尝试的方法(参考我原来问题的一些 cmets 答案): - 尝试添加中间变量来存储 ca-aet al 的结果。 - 尝试使这些中间变量volatile(如果此关键字适用于 OpenGL ES...)
  • 不幸的是,“volatile”只是一个保留字。 WebGL2 中没有对它们的任何支持。中间变量也没有帮助
【解决方案2】:

GPU 不一定具有/使用 IEEE 754 一些实现的位数较少,因此毫无疑问,结果会有所不同。它与您在 FPU 上比较 floatdouble 的结果相同。但是,如果您的 GLSL 实现允许它看到,您可以尝试强制执行精度:

如果您的 GPU 允许,请使用 doubledvec,但要注意目前还没有 64 位插值器(至少据我所知)。

要排除由于通过纹理传递结果而导致的舍入,请参阅:

您还可以通过打印来检查 GPU 上的尾数位数

1.0+1.0/2.0
1.0+1.0/4.0
1.0+1.0/8.0
1.0+1.0/16.0
...
1.0+1.0/2.0^i

未打印为1.0 的最后一个数字的i 是尾数位数。所以你可以检查它是否是 23 ......

【讨论】:

    猜你喜欢
    • 2018-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多