GLSL атомные счетчики (и ветвления) в фрагментных шейдерах - PullRequest
1 голос
/ 17 марта 2012

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

Представьте поток, примерно такой:

  • в каком-то небольшом неуправляемом цикле, скажем FOR 0-20 (разрешаемое во время компиляцииconst) ...
  • получить значения счетчиков для AC1 и AC2
  • проверить некоторое значение:
  • если x: установить тексель в uimage1D_A для индекса AC1, приращение AC1
  • else: установить texel в uimage1D_B по индексу (imgwidth-AC2-1), с приращением AC2

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

Что касается ветвления (если x) - я сравниваю тексель в другом (readonly restrict uniform) uimage1D до (uniform) uint.Таким образом, один операнд определенно является равномерным скаляром, а другой - imageLoad().x, хотя изображение является однородным - этот тип ветвления все еще «полностью распараллелен»?Вы можете видеть, что обе ветви - это точно две, почти идентичные инструкции.Предполагая, что GLSL-компилятор «идеально оптимизирует», может ли такой тип ветвления привести к остановке?

Ответы [ 2 ]

5 голосов
/ 17 марта 2012

Атомные счетчики атомные. Но каждая атомарная операция является атомарной только для этой операции.

Итак, если вы хотите убедиться, что каждый шейдер получает уникальное значение из счетчика, то каждый шейдер должен получить доступ к этому счетчику только с помощью atomicCounterIncrement (или Decrement, но все они должны использовать то же самое).

Правильный способ сделать то, что вы предлагаете:

  1. проверить значение:
  2. если х:
    1. atomicCounterIncrement(AC1), сохраняя возвращенное значение.
    2. Используйте сохраненное значение в качестве текселя для установки чего-либо в uimage1D_A.
  3. еще:
    1. atomicCounterIncrement(AC2), сохраняя возвращенное значение.
    2. Используйте сохраненное значение для вычисления texel (imgwidth - val - 1), для которого нужно установить что-то в uimage1D_B.

Ваша стратегия "извлечения и последующего приращения" - это состояние гонки, ожидающее наступления. Неважно, если он «полностью распараллелен», потому что он сломан . Вам нужно, чтобы он работал, прежде чем задуматься, будет ли он быстрым.

Я бы настоятельно рекомендовал ознакомиться с атомарностью и многопоточностью на CPU , прежде чем пытаться заняться GPU. Это распространенная ошибка новичков при работе с атомами. Вы должны быть экспертом по многопоточности (или, по крайней мере, промежуточного уровня), если хотите успешно использовать атомарность GLSL и загрузку / хранение изображений.

2 голосов
/ 29 июля 2014

Как предложил Николь Болас, если вы хотите, чтобы значение, которое вы читаете из атомного счетчика, никогда не читалось другим ядром, вам нужно выполнить атомарный инкремент и использовать возвращаемое значение, которое не будет иметь другое ядро. если они не выполнили atomicCounter(AC1), который проверяет значение без приращения. В тот момент, когда вы атомарно увеличиваете значение и возвращаете старое значение, вы гарантируете, что все остальные, кто делает то же самое, получат только увеличенное значение.

Вы, похоже, делаете A-Buffer, мне любопытно, зачем вам второй счетчик. Я полагаю, что uimage1D_A - это карта указателей на экран размером с экран, которая хранится в uimage1D_B, я прав? Вы используете AC2 для генерации указателя на новую неиспользуемую часть памяти uimage1D_B, но ваш AC1 предполагает, что вы постепенно переходите к uimage1D_A, поэтому я могу быть совершенно неправ:)

...