Что происходит, когда вы делите на 0 в шейдере?

#iphone #opengl-es #glsl #shader

#iPhone #opengl-es #glsl #шейдер

Вопрос:

Известно, что ветвление в шейдере OpenGL ES требует особенно больших вычислительных затрат. В таком шейдере я проверяю, является ли значение null, прежде чем делить на него, например:

 if(value == 0.0)
    other_value = 0.0;
else
    other_value = 1.0 / value;
  

Чтобы ускорить процесс, я хотел бы избежать этого if , выполнив напрямую:

 other_value = 1.0 / value;
  

Интересно, что произойдет, если value значение окажется равным 0, что немного редко в моей обработке, вот почему это нетривиально протестировать. Происходит ли сбой шейдера? Происходит ли сбой приложения?

Комментарии:

1. Для вас большие накладные расходы на увеличение значения на крошечное плавающее значение, такое как 0.000001? таким образом, значение всегда будет > 0.

2. komplexify.com/images/2010/Divide-by-zero-2.jpg

3. Не ответ, но согласно this thread похоже, что это неопределенное поведение, но ваша программа продолжает выполняться.

4. @Benj: хорошая идея, я, вероятно, воспользуюсь ею @DarkDust: вот и все! Я нашел это в спецификации OpenGL ES: Treatment of conditions such as divide by 0 may lead to an unspecified result, but must not lead to the interruption or termination of processing.

5. @Benj ваше предложение не имеет смысла, поскольку в этом случае увеличенному ‘other_value’ было бы присвоено огромное значение 1.0 / 0.000001 в случае, когда other_value начинается с 0.0!

Ответ №1:

Действительно, не определено.

В симуляторе iPad деление красного компонента на 0.0 дает 0 (весь красный цвет исчезает с этой картинки).

введите описание изображения здесь

На аппаратном обеспечении iPad при делении компонента r на 0.0 он насыщается полностью красным цветом (т. е. ограниченная бесконечность).

введите описание изображения здесь

Итак, вы вообще не можете полагаться на неопределенное поведение для получения согласованного результата, но само по себе деление на 0 не приводит к чему-то плохому.

В моем шейдере я делю значения rgb на альфа. Просто так получилось, что если альфа-значение равно 0, пиксель все равно не отображается, поэтому у меня нет проблем с делением в любом случае (давая 0 или inf).

Комментарии:

1. Этот ответ заслуживает уважения, поскольку он дает вам эмпирический пример того, как непредсказуемое поведение может привести к нежелательным, а иногда и разрушающим игру / приложение результатам. Программисты шейдеров, рассматривающие возможность того, чтобы процессор обрабатывал деление на ноль любым старым способом, который он считает нужным, должны принять к сведению эту потенциальную катастрофу. Другими словами, это может не привести к сбою вашего приложения, но хуже: это может проявиться только позже, когда оно уже будет в руках тысяч людей, пытающихся прочитать искаженную графику.

Ответ №2:

Подобные ветви обычно не дороги. Обычно они реализуются с использованием предикации. То есть графический процессор вычисляет обе ветви, но фактически сохраняет результаты инструкций только там, где условие предикации истинно. Следовательно, инструкция перехода не используется. Вот как мог бы выглядеть ассемблерный код:

 cmp_eq p0, r0, 0.0   // predicate = (value == 0.0)
(p0) mov r1, 0.0     // other_value = 0.0, if predicate true
(!p0) rcp r1, r0     // other_value = 1.0 / value, if predicate false
  

Обратите внимание, что в этом случае вторая инструкция на самом деле не обязательно должна быть предопределена. В любом случае, как указано другими, результат деления (обратный) не определен, когда знаменатель равен нулю. Но, как вы можете видеть, вы должны быть в состоянии получить четко определенное поведение всего за пару дешевых инструкций (деление обычно происходит медленно). Насколько мне известно, все графические процессоры, которые поддерживают реальные ветви (инструкции перехода), также поддерживают предикацию. Компилятор шейдера оценит, использовать ли предикацию или переход, и обычно поступит правильно.

Конечно, если вас действительно не волнует результат деления на ноль, вы можете сэкономить на любых затратах на предикацию.

Ответ №3:

Вместо

 float invert_value(in float value)
{
if(value == 0.0)
    return 0.0;
else
    return 1.0 / value;
}
  

вы можете написать

 float invert_value_ifless(in float value)
{
float sign_value = sign(value);
float sign_value_squared = sign_value*sign_value;
return sign_value_squared / ( value   sign_value_squared - 1.0);
}
  

Это возвращает именно то, что вы хотите, и имеет

  1. никаких «если»
  2. никаких делений на 0

Я не уверен, что это на самом деле быстрее на новейшем оборудовании с предикацией.

Комментарии:

1. Действительно умный трюк! Перенесемся в 2022 год. Вы когда-нибудь тестировали свой код, сравнивая его с предикацией?

Ответ №4:

Я никогда не видел, чтобы шейдер вылетал из-за чего-то подобного. Скорее всего, это приведет к мусорному значению (например, nan) и испортит любые другие вычисления, которые вы выполняете с результатом. Помимо этого, я бы не стал беспокоиться об этом (и определенно не добавлял код ветвления, чтобы предотвратить это).

Комментарии:

1. В спецификации OpenGL ES 2.0 сказано, что шейдер должен не завершаться сбоем, но поведение не определено.

2. Это плохой ответ, его не следует помечать как правильный. Как показано в ответе @bobobobo, вы действительно должны «беспокоиться об этом»: это ] портит шейдер и все последующее за ним выполнение, использующее значения (я пришел сюда после того, как увидел проблему с живым кодом, когда разное оборудование выдает совершенно разные конечные результаты).

Ответ №5:

Если вы хотите ограничиться iPhone, почему бы вам не попробовать и не посмотреть, что получится? Однако нет никаких гарантий относительно того, что произойдет на будущем оборудовании, на котором будут запущены приложения iPhone. Это может привести к сбою. Это ничего не могло сделать. Это может отображать странные пиксели. Это может вызвать вашу тещу. (Все, поскольку это неопределенное поведение.)

Комментарии:

1. Согласно спецификации, это не приведет к сбою … что я предпочитаю, чем звонить моей теще 🙂

Ответ №6:

на старом графическом оборудовании деление на ноль привело бы к значению бесконечности с плавающей запятой, что на самом деле является правильным ответом. однако это вряд ли то, чего вы хотите, потому что вы, вероятно, собираетесь просто сохранить бесконечность (или что-то производное от нее) в вашем буфере кадров, который, вероятно, имеет формат, подобный RGBA8, который не способен отличить бесконечность от 0 или 1.

на более новом оборудовании для настольных компьютеров возможно иметь буфер кадров с плавающей запятой, и в этом случае infinity — это допустимая вещь для хранения там, но все равно вряд ли это то, что вам нужно, если только ваше кунг-фу с плавающей запятой не является достаточно продвинутым.