如何在计算着色器中对 SSBO 使用原子操作

ber*_*nie 6 opengl glsl gpu-atomics

示例代码

这是一个简单的计算着色器来说明我的问题

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

解释

SMyBuffer是具有当前元素数量的元素( data[])堆栈count。当满足某个条件时,计算着色器会自动增加计数。此操作返回用于索引data[]以存储新元素的前一个索引。这保证了没有两个着色器调用覆盖彼此的元素。

另一个计算着色器最终从这个堆栈中弹出值并使用它们。glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)两个计算着色器调度之间当然需要。

所有这些工作正常,但我想知道我是否只是在时间上很幸运,我想验证我对 API 的使用。

那么,是否还需要其他任何东西来确保 SSBO 中存储的计数器正常工作(参见1)?我期待atomicAdd()处理内存同步,否则它毫无意义。其效果仅在单个线程中可见的原子操作有什么意义?

关于内存障碍,OpenGL wiki 指出

请注意,原子计数器在功能上与原子图像/缓冲区变量操作不同。后者仍然需要连贯的限定词、障碍等。

这让我想知道是否有一些我没有正确理解并且memoryBarrierBuffer()实际上需要a 的东西。但是,如果是这样的话,atomicAdd()在其中一个线程进入下一个线程之前,如何阻止 2 个线程执行memoryBarrierBuffer()

此外,答案是否会改变是glDispatchCompute()派遣单个工作组还是更多?

Kev*_*tti 1

您不需要该memoryBarrierBuffer()调用,因为glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)它将停止第二个着色器(使用者)发出的任何读取,直到第一个着色器的所有写入完成为止。
分派的工作组数量不会改变答案,因为所有写入都glDispatchCompute()需要完成。