OpenGL计算着色器中的简单原子计数器测试问题

OpenGL计算着色器中的简单原子计数器测试问题,opengl,atomic,compute-shader,Opengl,Atomic,Compute Shader,我一直试图通过尝试一些琐碎的例子来了解内存同步和一致性 在本文中,我将调度一个具有8x1大小工作组的计算着色器。工作组的数量足以覆盖屏幕,即720x480 计算着色器代码: #version 450 core layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in; layout (binding = 0, rgba8) uniform image2D u_fboImg; layout (binding = 0, o

我一直试图通过尝试一些琐碎的例子来了解内存同步和一致性

在本文中,我将调度一个具有8x1大小工作组的计算着色器。工作组的数量足以覆盖屏幕,即720x480

计算着色器代码:

#version 450 core

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

layout (binding = 0, rgba8) uniform image2D u_fboImg;

layout (binding = 0, offset = 0) uniform atomic_uint u_counters[100];

void main() {
    ivec2 texCoord = ivec2(gl_GlobalInvocationID.xy);

    // Only use shader invocations within first 100x500 pixels
    if (texCoord.x >= 100 || texCoord.y >= 500) {
        return;
    }

    // Each counter should be incremented 400 times
    atomicCounterIncrement(u_counters[texCoord.x]);

    memoryBarrier();

    // Use only "bottom row" of invocations to draw results
    // Draw a white column as high as the counter at given x
    if (texCoord.y == 0) {
        int c = int(atomicCounter(u_counters[texCoord.x]));
        for (int y = 0; y < c; ++y) {
            imageStore(u_fboImg, ivec2(texCoord.x, y), vec4(1.0f));
        }
    }
}
#版本450核心
布局(本地大小x=8,本地大小y=8,本地大小z=1);
布局(绑定=0,rgba8)均匀图像2D u_fboImg;
布局(绑定=0,偏移=0)统一原子单元计数器[100];
void main(){
ivec2 texCoord=ivec2(gl_globalinOccationId.xy);
//仅在前100x500像素内使用着色器调用
如果(texCoord.x>=100 | | texCoord.y>=500){
返回;
}
//每个计数器应递增400倍
原子计数器增量(u_计数器[texCoord.x]);
记忆载体();
//仅使用调用的“底行”来绘制结果
//在给定的x处绘制一个与计数器一样高的白色列
如果(texCoord.y==0){
int c=int(原子计数器(u_计数器[texCoord.x]);
对于(int y=0;y
这就是我所得到的:(锯齿状的酒吧的高度每次都不一样,但平均大约是这个高度)

这是我所期望的,也是硬编码for循环到400的结果

奇怪的是,如果我减少分派中的工作组数量,比如将x值减半(现在只覆盖屏幕的一半),那么条会变大:

最后,为了证明没有其他的胡说八道,这里我只是基于本地调用id进行着色:


*编辑:忘记提及分派后紧接着是
glMemoryBarrier(GL\u ALL\u BARRIER\u位)

除非另有说明,否则特定着色器阶段(包括计算着色器阶段)的所有着色器调用将以未定义的顺序彼此独立执行。调用
memoryBarrier
不会改变这一事实。这意味着,当调用
memoryBarrier
之后的内容时,无法保证来自原子计数器的值已通过最终将执行的所有着色器调用递增

因此,您所看到的正是我们期望看到的:调用写入了一些随机值,这取决于调用执行的依赖于实现的顺序

您要做的是为所有调用执行所有原子增量,然后读取这些值并根据您读取的内容绘制内容。您编写的代码无法做到这一点

虽然计算着色器确实有,但这仅适用于同一工作组内的调用(这实际上就是工作组存在的原因)。也就是说,可以在工作组中对调用进行一定程度的排序,但不能在工作组之间进行排序

简单的修复方法是将其转换为2个计算着色器分派操作。第一个执行所有递增操作。第二个将读取值并将结果写入图像


更聪明的解决方案是雇佣工作组。也就是说,对您的工作进行分组,以便在相同的工作组中执行增加相同原子计数器的内容。这样,你甚至不需要原子计数器;您只需使用共享变量(可以)。完成共享变量的所有递增操作后,调用
barrier()
;这确保了在任何调用继续超过该点之前,所有调用都至少执行了那么远。所以所有的递增都完成了。

您的实现实际上允许100个原子计数器吗?当然不允许。8是我为GL\u MAX\u COMPUTE\u原子计数器\u缓冲区得到的值。我不知道为什么我不想检查它。这是原子计数器缓冲区的数量,而不是计数器。我问的是原子计数器缓冲区大小的问题。例如,大多数NVIDIA实现允许64KB的原子计数器缓冲区大小,因此可以使用16K计数器。相比之下,很多AMD硬件只有32字节的缓冲区大小,所以你只能写8个计数器。我不会从glGetIntegerv中得到GLU原子计数器缓冲区大小的值,尽管它是定义的。此外,我在文档中没有看到它作为选项列出:原子计数器是OpenGL4.x的一个特性;在OpenGL 2.1的页面上看不到它们。