GLSL 编译器优化导致浮点运算行为不正确
GLSL compiler optimizations lead to incorrect behavior with floating point operations
我是使用 OpenGL 编写 Android 应用程序的团队的一员。我们有大量使用浮点数模拟双精度数学的着色器代码。 (具体来说,我们在 Andrew Thall 的 Extended-Precision Floating-Point Numbers for GPU Computation 中实现了算法。)它在应用程序的 DirectX 版本中运行良好,但我发现在 Android 上,GLSL 编译器正在优化中的一些代码这样一种方式,在代数上,行为应该被保留,但实际上它改变了行为,因为优化正在丢弃浮点错误。例如,在下面:
vec2 add(float a, float b) {
float sum = a + b;
float err = b - (sum - a);
return vec2(sum, err);
}
错误值 e 被编译器简化为 0,因为这在代数上是正确的,但当然,当考虑到浮点错误时,情况并非总是如此。
我试过“#pragma optimize (off)”,但它不标准而且没有效果。我发现唯一可行的技巧是创建一个“零”统一浮点数,该浮点数保持设置为 0,并将其添加到战略位置的违规值中,因此上述函数的工作版本为:
vec2 add(float a, float b) {
float sum = a + b;
sum += zero;
float err = b - (sum - a);
return vec2(sum, err);
}
这显然不理想。 1)这是一个 PITA 来追踪必要的地方,并且 2)它依赖于编译器。另一个编译器可能不需要它,另一个编译器可以将 e 值优化为 zero。是否有解决此问题并确保 GLSL 编译器不会优化实际行为的“正确”方法?
编辑:
虽然技术答案看起来仍然是“否”,但我找到了更好的解决方法并想在此处记录下来。 “零”统一方法确实开始因更复杂的 expressions/chained 操作而失败。我找到的解决方法是创建两个用于加法和减法的函数:
float plus_frc(float a, float b) {
return mix(a, a + b, b != 0);
}
float minus_frc(float a, float b) {
return mix(0, a - b, a != b);
}
(“frc”代表“force”和“farce”,因为你是在强制操作,但必要性是愚蠢的。)这些复制了 (a + b) 和 (a - b),但以编译器不应该能够优化的方式,不使用分支并使用 fast builtin 来完成工作。所以上面的防错“add”函数就变成了:
vec2 add(float a, float b) {
float sum = plus_frc(a, b);
float err = b - (sum - a);
return vec2(sum, err);
}
请注意,我们总是需要使用我们的“frc”函数(例如,查找错误的方程式),但仅在编译器可以完成中断优化的地方使用.
没有。在 GLSL 中没有控制优化的绑定方法。如果编译器认为假设您的错误项为零是合理的,那么它将为零。
我是使用 OpenGL 编写 Android 应用程序的团队的一员。我们有大量使用浮点数模拟双精度数学的着色器代码。 (具体来说,我们在 Andrew Thall 的 Extended-Precision Floating-Point Numbers for GPU Computation 中实现了算法。)它在应用程序的 DirectX 版本中运行良好,但我发现在 Android 上,GLSL 编译器正在优化中的一些代码这样一种方式,在代数上,行为应该被保留,但实际上它改变了行为,因为优化正在丢弃浮点错误。例如,在下面:
vec2 add(float a, float b) {
float sum = a + b;
float err = b - (sum - a);
return vec2(sum, err);
}
错误值 e 被编译器简化为 0,因为这在代数上是正确的,但当然,当考虑到浮点错误时,情况并非总是如此。
我试过“#pragma optimize (off)”,但它不标准而且没有效果。我发现唯一可行的技巧是创建一个“零”统一浮点数,该浮点数保持设置为 0,并将其添加到战略位置的违规值中,因此上述函数的工作版本为:
vec2 add(float a, float b) {
float sum = a + b;
sum += zero;
float err = b - (sum - a);
return vec2(sum, err);
}
这显然不理想。 1)这是一个 PITA 来追踪必要的地方,并且 2)它依赖于编译器。另一个编译器可能不需要它,另一个编译器可以将 e 值优化为 zero。是否有解决此问题并确保 GLSL 编译器不会优化实际行为的“正确”方法?
编辑:
虽然技术答案看起来仍然是“否”,但我找到了更好的解决方法并想在此处记录下来。 “零”统一方法确实开始因更复杂的 expressions/chained 操作而失败。我找到的解决方法是创建两个用于加法和减法的函数:
float plus_frc(float a, float b) {
return mix(a, a + b, b != 0);
}
float minus_frc(float a, float b) {
return mix(0, a - b, a != b);
}
(“frc”代表“force”和“farce”,因为你是在强制操作,但必要性是愚蠢的。)这些复制了 (a + b) 和 (a - b),但以编译器不应该能够优化的方式,不使用分支并使用 fast builtin 来完成工作。所以上面的防错“add”函数就变成了:
vec2 add(float a, float b) {
float sum = plus_frc(a, b);
float err = b - (sum - a);
return vec2(sum, err);
}
请注意,我们总是需要使用我们的“frc”函数(例如,查找错误的方程式),但仅在编译器可以完成中断优化的地方使用.
没有。在 GLSL 中没有控制优化的绑定方法。如果编译器认为假设您的错误项为零是合理的,那么它将为零。