Различное поведение с плавающей точкой между униформой и константами в GLSL - PullRequest
0 голосов
/ 06 января 2019

Я пытаюсь реализовать эмулированную двойную точность в 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

Теперь, с заданными значениями b1 и b2 в качестве входных данных, значение v2b не имеет ожидаемого результата. Или, по крайней мере, он не имеет такой же результат, как на процессоре (как видно здесь ):

C ++ вывод:

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

Обратите внимание на расхождение для значения v1b (0x36400497 против 0x36400000).

Поэтому, чтобы выяснить, что происходит (и кто был прав), я попытался повторить вычисления в 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

Мой вопрос: что заставляет вычисления с плавающей запятой вести себя по-разному между однородной переменной и константой? Это какая-то закулисная оптимизация компилятора?

Вот строки моего поставщика OpenGL от Intel GPU моего ноутбука, но я также наблюдал такое же поведение на карте 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

Ответы [ 2 ]

0 голосов
/ 13 января 2019

Итак, как отмечалось в комментариях @ njuffa , проблема была решена с помощью модификатора precise для значений, которые зависели от строгих операций IEEE754:

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 необходим для ограничения операций, ведущих к его вычислениям, чтобы избежать нежелательных оптимизаций.

0 голосов
/ 07 января 2019

Графический процессор не обязательно имеет / использует IEEE 754 В некоторых реализациях количество битов меньше, поэтому ежу понятно, что результат будет другим. Это так же, как вы сравниваете float с double результатами на FPU . Однако вы можете попытаться обеспечить точность, если ваша реализация GLSL позволяет увидеть:

В худшем случае используйте double и dvec, если ваш GPU позволяет это, но будьте осторожны, пока нет 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

i последнего номера, не напечатанного как 1.0, является числом битов мантиссы. Таким образом, вы можете проверить, 23 это или нет ...

...