Как использовать атомарные операции над SSBO в вычислительном шейдере

Пример кода

Вот шейдерный компьютер для иллюстрации моего вопроса

layout(local_size_x = 64) in;

// Persistent LIFO structure with a count of elements
layout(std430, binding = 0) restrict buffer SMyBuffer
{
    int count;
    float data[];
} MyBuffer;

bool AddDataElement(uint i);
float ComputeDataElement(uint i);

void main()
{
    for (uint i = gl_GlobalInvocationID.x; i < some_end_condition; i += gl_WorkGroupSize.x)
    {
        if (AddDataElement(i))
        {
            // We want to store this data piece in the next available free space
            uint dataIndex = atomicAdd(MyBuffer.count, 1);
            // [1] memoryBarrierBuffer() ?
            MyBuffer.data[dataIndex] = ComputeDataElement(i);
        }
    }
}

объяснение

SMyBuffer это стек элементов (data[]) с count текущего количества элементов. Когда определенное условие выполнено, вычислительный шейдер увеличивает счетчик атомарно. Эта операция возвращает предыдущий индекс, который используется для индексации data[] сохранить новый элемент. Это гарантирует, что никакие два вызова шейдеров не перезаписывают элементы друг друга.

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

Вопрос

Все это прекрасно работает, но мне интересно, если мне просто повезло с таймингами, и я хочу проверить свое использование API.

Итак, требуется ли что-то еще, чтобы убедиться, что счетчик, хранящийся в SSBO, работает (см. 1)? Я ожидаю atomicAdd() заботится о синхронизации памяти, потому что в противном случае это имеет мало смысла. Какой смысл в атомарной операции, эффект которой виден только в одном потоке?

Относительно барьеров памяти, вики OpenGL заявляет:

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

что заставляет меня задуматься, есть ли что-то, что я не поняла должным образом, и memoryBarrierBuffer() на самом деле требуется. Но тогда, если это так, что мешает 2 потокам выполнять atomicAdd() прежде чем один из них доберется до следующего memoryBarrierBuffer()?

Кроме того, меняется ли ответ glDispatchCompute() рассылает одну рабочую группу или больше?

1 ответ

Вам не нуженmemoryBarrierBuffer()позвони какglMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)остановит любое чтение, которое выдает ваш второй шейдер (потребитель), пока все записи из вашего первого шейдера не будут завершены.
Количество отправленных рабочих групп не меняет ответ, поскольку все записи изglDispatchCompute()нужно будет закончить.

Другие вопросы по тегам