示例代码
这是一个简单的计算着色器来说明我的问题
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[]
),其中 acount
为当前元素数。当满足某个条件时,计算着色器会自动增加计数。此操作返回用于索引data[]
存储新元素的先前索引。这保证了没有两个着色器调用会覆盖彼此的元素。
另一个计算着色器最终会从该堆栈中弹出值并使用它们。glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)
在两个计算着色器调度之间当然需要。
问题
所有这一切都很好,但我想知道我是否只是对时间感到幸运,我想验证我对 API 的使用。
那么,是否需要其他任何东西来确保存储在 SSBO 中的计数器正常工作(参见1)?我期待atomicAdd()
处理内存同步,否则它没有什么意义。其效果仅在单个线程中可见的原子操作有什么意义?
关于内存屏障,OpenGL wiki 指出:
请注意,原子计数器在功能上与原子图像/缓冲区变量操作不同。后者仍然需要连贯的限定词、障碍等。
这让我想知道是否有一些我没有正确理解的东西并且 amemoryBarrierBuffer()
实际上是必需的。atomicAdd()
但是,如果是这样的话,在其中一个线程进入下一个线程之前,如何阻止 2 个线程执行memoryBarrierBuffer()
?
glDispatchCompute()
此外,无论派遣一个工作组还是更多,答案是否会改变?