计算着色器 - 如何全局同步线程?

3

编辑:我重新措辞了问题,使其更加通用,并简化了代码。

我可能在计算着色器的线程同步方面遗漏了一些东西。我有一个简单的计算着色器,可以对一些数字进行并行归约,然后我需要修改最终的总和:

#version 430 core
#define SIZE 256
#define CLUSTERS 5

layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;

struct Cluster {
    vec3 cntr;
    uint size;
};
coherent restrict layout(std430, binding = 0) buffer destBuffer {
    Cluster clusters[CLUSTERS];
};
shared uint sizeCache[SIZE];

void main() {
    const ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    const uint id = pos.y * (gl_WorkGroupSize.x + gl_NumWorkGroups.x) + pos.x;

    if(id < CLUSTERS) {
        clusters[id].size = 0;
    }

    memoryBarrierShared();
    barrier();
    sizeCache[gl_LocalInvocationIndex] = 1;
    int stepv = (SIZE >> 1); 
    while(stepv > 0) { //reduction over data in each working group
        if (gl_LocalInvocationIndex < stepv) {
            sizeCache[gl_LocalInvocationIndex] += sizeCache[gl_LocalInvocationIndex + stepv];
        }
        memoryBarrierShared();
        barrier();
        stepv = (stepv >> 1);
    }
    if (gl_LocalInvocationIndex == 0) {
        atomicAdd(clusters[0].size, sizeCache[0]);
    }

    memoryBarrier();
    barrier();

    if(id == 0) {
        clusters[0].size = 23; //this doesn't do what I would expect
        clusters[1].size = 13; //this works
    }
}

减少操作是有效的并且产生了正确的结果。如果我注释掉最后一个条件,那么在clusters[0].size中的值是262144,这是正确的(它是线程的数量)。如果我取消注释,我期望得到23的值,因为据我所知,在barrier()之后的线程应该是同步的,并且在memoryBarrier()之后,所有先前的内存更改都应该是可见的。然而,它不起作用,它产生的结果像259095一样。我猜想值23被另一个线程的先前的atomicAdd重新写入了,但我不明白为什么。
这是我在CPU上读取结果的方式:
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, resultBuffer);

//currently it dispatches 262144 threads
glDispatchCompute(32, 32, 1);
glCheckError();

glMemoryBarrier(GL_ALL_BARRIER_BITS); //for debug

struct Cl {
    glm::vec3 cntr;
    uint size;
};

glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, resultBuffer);

std::vector<Cl> data(5);
glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeOfresult, &data[0]);

我有一张NVIDIA GT630M显卡和安装了nvidia专有驱动程序(331.49版本)的Linux系统。


一般来说,你需要声明一个变量coherent,才能让内存屏障对更新的可见性产生任何影响。我会考虑将整个destBuffer的定义声明为coherent。这将确保对ob.clusters []的写入被屏障所遵循。否则,此计算着色器的另一个调用可能会轻松地破坏你在if(id == 0)分支末尾写入的值。 - Andon M. Coleman
谢谢,我错过了那个。然而,即使我声明缓冲区是“coherent”,它仍然产生与之前相同的结果。 - Jaa-c
如果在GL代码中的内存屏障中添加 | GL_BUFFER_UPDATE_BARRIER_BIT,是否会有任何变化?我认为你现在拥有的位更多是用于调度访问SSB的绘制调用,而不是确保着色器在使用 glGetBufferSubData(...) 读取缓冲区之前完成。 - Andon M. Coleman
不,我甚至尝试了GL_ALL_BARRIER_BITS进行调试,但结果仍然相同。如果我使用glGetBufferSubData从缩减中读取原始数据,我总是能得到正确的结果。 - Jaa-c
barrier的描述仅声称同步单个工作组内的执行。我猜id不等于0的组的线程可以覆盖clusters [0] - GuyRT
1
正如已经提到的几次,问题在于你有一个 if(id == 0),因为任何非0 id组都可以在 clusters[0和1] 中写入任何它想要的内容,只有id为0的组才会触发你想要写入那里的值。如果你希望23和13在所有情况下都存在,无论哪个组在写入,请尝试将 if(id == 0) 更改为 if(id == id)(仅用于代码保留测试),或使 if 子句内的2行代码无条件执行。 - GMasucci
1个回答

2
您无法全局同步线程,即跨工作组进行同步。GuyRT在评论中指出了这一点。在您的代码中,一个工作组可能会触发。
clusters[0].size = 23;

与此同时,另一个工作组正在愉快地执行原子递增操作。由于只有第一个工作组的第一个线程进入了if(id==0)块,并且大多数GPU按顺序分派工作组,因此该值将被写入一次,然后被其他(大多数)工作组递增多次。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接