Полезность GLSL memoryBarrierShared()?

Меня интересует полезность memoryBarrierShared.

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

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

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

memoryBarrierShared();
barrier();

или что-то не так, как

barrier();
memoryBarrierShared();

Итак, мой вопрос: какова цель memoryBarrier{Shared,...}, если достаточно использовать барьер?

Для memoryBarrierBuffer/Image я могу понять, используем ли мы несколько этапов, но для общего доступа я понятия не имею...


person Antoine Morrier    schedule 08.09.2016    source источник


Ответы (1)


Обновление (07.12.2019):

Приведенное ниже пояснение к GLSL 4.60 теперь неверно. После редакции 5 спецификация GLSL 4.60 теперь читается как :

Частная проблема GLSL № 24: Уточнить, что barrier() достаточно для синхронизации как потока управления, так и доступа к памяти к общим переменным и выходным переменным управления тесселяцией. Для других обращений к памяти по-прежнему требуется дополнительный барьер памяти.

Это также отражено в документации по GLSL ES 3.20:

Чтобы добиться упорядочения операций чтения и записи в общие переменные, необходимо использовать барьеры потока управления с помощью функции barrier() (см. «Функции управления вызовом шейдера»).

Они также идут немного дальше и объясняют

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

TL;DR: если вы используете барьеры только для общих переменных, barrier() будет достаточно. Если вы используете их для «другого доступа к памяти», то barrier() недостаточно.


GLSL 4.60 поясняет это:

Чтобы добиться упорядочения операций чтения и записи в общие переменные, необходимо использовать комбинацию потока управления и барьеров памяти с помощью функций barrier() и memoryBarrier() (см. «Функции управления вызовом шейдера»).

Вероятно, лучше относиться к GLSL для настольных ПК так, как если бы он всегда говорил это. Несмотря на то, что в GLSL 4.50 было указано следующее.


GLSL 4.50 совершенно ясно дает понять, что явные барьеры памяти не нужны. Этот barrier в вычислительном шейдере включает все барьеры памяти.

Однако GLSL ES 3.20 столь же совершенно ясно дает понять, что barrier не включает никаких барьеров памяти:

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

Примечательно, что автономный компилятор glslang всегда использует формулировку GLSL ES. Поэтому, если вы создаете SPIR-V для передачи в Vulkan, вы должны следовать правилам ES. Что ж, пока они так или иначе не исправят это.

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

Я бы предложил использовать барьер памяти вместе с вызовом barrier. Таким образом, ваш шейдер будет правильным, даже если он может быть немного медленнее в некоторых реализациях. Однако если вы собираетесь использовать барьеры памяти вместе с вызовами barrier, то барьер памяти должен стоять первым. Выполнение барьера памяти после синхронизации выполнения является неправильным.

person Nicol Bolas    schedule 08.09.2016
comment
В случае, если люди сочтут вышеизложенное запутанным, вот мое резюме. В текущей версии спецификаций, которые теперь согласуются между OpenGL 4.6 и GL ES 3.2, вызов barrier() синхронизирует как поток управления (вызовы внутри рабочей группы), так и общую память. Явный барьер памяти нужен только для других обращений к памяти, например буферов. - person Raph Levien; 10.05.2021