Вызывает ли == ветвление в GLSL?

27

Попытка выяснить, что именно вызывает ветвление, а что нет в GLSL.

Я делаю это много в моем шейдере:

float(a==b)

Я использую его для симуляции операторов if, без условного ветвления ... но эффективно ли это? У меня нет операторов if где-либо в моей программе, и у меня нет никаких циклов.

РЕДАКТИРОВАТЬ: Чтобы уточнить, я делаю такие вещи в моем коде:

float isTint = float((renderflags & GK_TINT) > uint(0)); // 1 if true, 0 if false
    float isNotTint = 1-isTint;//swaps with the other value
    float isDarken = float((renderflags & GK_DARKEN) > uint(0));
    float isNotDarken = 1-isDarken;
    float isAverage = float((renderflags & GK_AVERAGE) > uint(0));
    float isNotAverage = 1-isAverage;
    //it is none of those if:
    //* More than one of them is true
    //* All of them are false
    float isNoneofThose = isTint * isDarken * isAverage + isNotTint * isAverage * isDarken + isTint * isNotAverage * isDarken + isTint * isAverage * isNotDarken + isNotTint * isNotAverage * isNotDarken;
    float isNotNoneofThose = 1-isNoneofThose;

    //Calc finalcolor;
    finalcolor = (primary_color + secondary_color) * isTint * isNotNoneofThose + (primary_color - secondary_color) * isDarken * isNotNoneofThose + vec3((primary_color.x + secondary_color.x)/2.0,(primary_color.y + secondary_color.y)/2.0,(primary_color.z + secondary_color.z)/2.0) * isAverage * isNotNoneofThose + primary_color * isNoneofThose;

РЕДАКТИРОВАТЬ: я знаю, почему я не хочу ветвления. Я знаю, что такое ветвление. Я рад, что вы учите детей ветвлению, но я хотел бы узнать себя о булевых операторах (и побитовых операциях, но я уверен, что все в порядке)

Геклминтенд не офигенный
источник

Ответы:

42

Причины ветвления в GLSL зависят от модели графического процессора и версии драйвера OpenGL.

Большинство графических процессоров, по-видимому, имеют форму операции «выбрать одно из двух значений», которая не требует затрат на переход:

n = (a==b) ? x : y;

а иногда даже такие вещи, как:

if(a==b) { 
   n = x;
   m = y;
} else {
   n = y;
   m = x;
}

будет уменьшено до нескольких операций выбора значения без штрафа за переход.

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

Где это может быть быстрее сделать:

gl_FragColor.xyz = ((tmp1 - tmp2) != vec3(0.0)) ? E : tmp1;

а не сравнивать (tmp1 != tmp2)напрямую, но это сильно зависит от графического процессора и драйвера, поэтому, если вы не нацеливаетесь на очень конкретный графический процессор и никакие другие, я рекомендую использовать операцию сравнения и оставить эту задачу оптимизации драйверу OpenGL, поскольку другой драйвер может иметь проблему с более длинной формой и будьте быстрее с более простым, более читаемым способом.

«Ветви» тоже не всегда плохи. Например, для графического процессора SGX530, используемого в OpenPandora, этот шейдер scale2x (30 мс):

    lowp vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;
    lowp vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;
    lowp vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;
    lowp vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;
    lowp vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;
    if ((D - F) * (H - B) == vec3(0.0)) {
            gl_FragColor.xyz = E;
    } else {
            lowp vec2 p = fract(pos);
            lowp vec3 tmp1 = p.x < 0.5 ? D : F;
            lowp vec3 tmp2 = p.y < 0.5 ? H : B;
            gl_FragColor.xyz = ((tmp1 - tmp2) != vec3(0.0)) ? E : tmp1;
    }

Завершается значительно быстрее, чем этот эквивалентный шейдер (80 мс):

    lowp vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;
    lowp vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;
    lowp vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;
    lowp vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;
    lowp vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;
    lowp vec2 p = fract(pos);

    lowp vec3 tmp1 = p.x < 0.5 ? D : F;
    lowp vec3 tmp2 = p.y < 0.5 ? H : B;
    lowp vec3 tmp3 = D == F || H == B ? E : tmp1;
    gl_FragColor.xyz = tmp1 == tmp2 ? tmp3 : E;

Вы никогда не знаете заранее, как будет работать конкретный компилятор GLSL или конкретный графический процессор, пока вы не сравните его.


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

  • Intel HD Graphics 3000
  • Intel HD 405 Graphics
  • nVidia GTX 560M
  • nVidia GTX 960
  • AMD Radeon R7 260X
  • nVidia GTX 1050

Широкий выбор различных распространенных моделей графических процессоров для тестирования.

Тестирование каждого с использованием Windows, проприетарных Linux и Linux OpenGL и OpenCL драйверов с открытым исходным кодом.

И каждый раз, когда я пытаюсь микрооптимизировать шейдер GLSL (как в примере SGX530 выше) или операции OpenCL для одного конкретного комбо GPU / Driver, я в конечном итоге одинаково снижаю производительность более чем на одном из других GPU / драйверов.

Таким образом, кроме явного снижения математической сложности высокого уровня (например, вместо преобразования 5 идентичных делений в одно обратное и 5 умножений) и сокращения поиска текстур / пропускной способности, это, скорее всего, будет пустой тратой вашего времени.

Каждый графический процессор слишком отличается от других.

Если бы вы работали специально над (а) игровыми приставками с определенной графической картой, это была бы другая история.

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

Они сделают это для популярных игр, которые часто используются в качестве тестов.

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

Например, для Oblivion есть фанатские шейдерные и текстурные пакеты, которые значительно увеличивают частоту кадров на плохо воспроизводимом оборудовании.

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

Стефан Хоккенхалл
источник
«Или если вы дадите своим игрокам доступ к шейдерам, чтобы они могли легко их редактировать ...» Поскольку вы упомянули об этом, каков ваш подход к шейдерам Wallhack и т. П.? Система чести, проверено, отчеты ...? Мне нравится идея, чтобы лобби ограничивалось одними и теми же шейдерами / активами, какими бы они ни были, поскольку позиции в отношении максимального / минимального / масштабируемого реализма, эксплойтов и т. Д. Должны объединять игроков и моддеров для поощрения обзора, совместной работы и т. Д. помнить, что так работал мод Гэри, но я не в курсе.
Джон П,
1
@JohnP Безопасность, если предположить, что клиент не скомпрометирован, все равно не работает. Конечно, если вы не хотите, чтобы люди редактировали свои шейдеры, нет смысла их выставлять, но это не очень помогает с безопасностью. Ваша стратегия обнаружения таких вещей, как WallHacks, должна рассматривать беспорядок на стороне клиента как низкий первый барьер, и, возможно, может быть большее преимущество в том, чтобы позволить легкое моддинг, как в этом ответе, если это не приводит к обнаруживаемым несправедливым преимуществам для игрока. ,
Куб
8
@JohnP Если вы не хотите, чтобы игроки слишком хорошо видели сквозь стены, не позволяйте серверу отправлять им информацию о том, что находится за стеной.
Полигном
1
Вот и все - я не против взлома стен между игроками, которым это нравится по любой причине. Тем не менее, как игрок, я отказался от нескольких игр AAA, потому что - среди прочих причин - они делали примеры эстетических моддеров, в то время как деньги / XP / и т. хакеры остались невредимыми (которые зарабатывали реальные деньги на тех, кто был достаточно разочарован, чтобы платить), недоукомплектовали персоналом и автоматизировали свою систему отчетов и апелляций, и позаботились о том, чтобы игры жили и умирали по количеству серверов, которые они заботились, чтобы поддерживать жизнь. Я надеялся, что может быть более децентрализованный подход как для разработчика, так и для игрока.
Джон П,
Нет, я не делаю inline, если где-нибудь. Я просто
плаваю
7

Ответ @Stephane Hockenhull в значительной степени дает вам то, что вам нужно знать, он будет полностью зависеть от аппаратного обеспечения.

Но позвольте мне привести несколько примеров того, как это может быть зависят от аппаратного обеспечения, и поэтому ветвление даже проблема вообще, то , что делает GPU делает за кулисами , когда ветвление делает место.

Я сфокусирован в основном на Nvidia, у меня есть некоторый опыт программирования на низком уровне CUDA, и я вижу, что такое PTX ( IR для ядер CUDA) генерируется , например, SPIR-V, но только для Nvidia), и вижу критерии для внесения определенных изменений.

Почему ветвление в архитектуре GPU так важно?

Почему плохо ветвиться в первую очередь? Почему графические процессоры пытаются избежать ветвления в первую очередь? Поскольку графические процессоры обычно используют схему, в которой потоки используют один и тот же указатель команд . Графические процессоры следуют архитектуре SIMDкак правило, и хотя степень детализации этого может измениться (то есть 32 потока для Nvidia, 64 для AMD и других), на некотором уровне группа потоков совместно использует один и тот же указатель команд. Это означает, что эти потоки должны смотреть на одну и ту же строку кода, чтобы работать вместе над одной и той же проблемой. Вы можете спросить, как они могут использовать одни и те же строки кода и делать разные вещи? Они используют разные значения в регистрах, но эти регистры все еще используются в одних и тех же строках кода во всей группе. Что происходит, когда это перестает быть так? (IE ветвь?) Если программа действительно не может обойти ее, она разделяет группу (Nvidia, такие связки из 32 потоков называются деформацией , для AMD и академий параллельных вычислений она называется волновым фронтом ) на две или более разных групп.

Если есть только две разные строки кода, на которых вы в конечном итоге окажетесь, тогда рабочие потоки будут разделены на две группы (отсюда одну я буду называть их перекосами). Предположим, архитектура Nvidia, где размер деформации составляет 32, если половина этих потоков расходится, то у вас будет 2 деформации, занятые 32 активными потоками, что делает вдвое менее эффективными вычисления от конца до конца. На многих архитектурах GPU будет пытаться исправить это, объединяя потоки обратно в одну деформацию, как только они достигнут одной и той же ветки постов инструкций, или компилятор явно установит точку синхронизации, которая говорит GPU сходить потоки назад или попытаться это сделать.

например:

if(a)
    x += z * w;
    q >>= p;
else if(c)
    y -= 3;
r += t;

Поток может сильно расходиться (разные пути команд), поэтому в таком случае может произойти конвергенция, r += t;когда указатели команд снова будут одинаковыми. Расхождение также может происходить с более чем двумя ветвями, что приводит к еще более низкому использованию деформации, четыре ветви означают, что 32 потока разделены на 4 деформации, 25% -ная пропускная способность. Однако конвергенция может скрыть некоторые из этих проблем, поскольку 25% не сохраняют пропускную способность всей программы.

На менее сложных графических процессорах могут возникнуть другие проблемы. Вместо того, чтобы расходиться, они просто вычисляют все ветви, а затем выбирают результат в конце. Это может выглядеть так же, как расхождение (оба имеют пропускную способность 1 / n), но есть несколько основных проблем с подходом дублирования.

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

Еще одна меньшая проблема - когда вы сравниваете GPU с CPU, у них часто нет механизмов прогнозирования и других надежных механизмов ветвления, поскольку из-за того, сколько аппаратного обеспечения занимает этот механизм, из-за этого часто можно увидеть заполнение без операций на современных GPU.

Практический пример архитектурной разницы графического процессора

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

n = (a==b) ? x : y;

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

cmpeq rega, regb
// implicit setting of comparison bit used in next part
choose regn, regx, regy

на других без инструкции выбора, она может быть скомпилирована в

n = ((a==b))* x + (!(a==b))* y

который может выглядеть так:

cmpeq rega regb
// implicit setting of comparison bit used in next part
mul regn regcmp regx
xor regcmp regcmp 1
mul regresult regcmp regy
mul regn regn regresult

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

WHN
источник
5

Я согласен со всем, что сказано в ответе @Stephane Hockenhull. Чтобы расширить на последний пункт:

Вы никогда не знаете заранее, как будет работать конкретный компилятор GLSL или конкретный графический процессор, пока вы не сравните его.

Абсолютная правда. Более того, я вижу, что подобные вопросы возникают довольно часто. Но на практике я редко видел фрагментный шейдер, являющийся источником проблем с производительностью. Гораздо чаще встречаются другие факторы, такие как слишком много чтений состояния из графического процессора, перестановка слишком большого количества буферов, слишком много работы за один вызов отрисовки и т. Д.

Другими словами, прежде чем беспокоиться о микрооптимизации шейдера, профилируйте все приложение и убедитесь, что именно шейдеры вызывают замедление.

user1118321
источник