下面的GLSL计算着色器只是将inImage复制到outImage。它是从更复杂的后处理过程派生而来的。
在main()的前几行中,单个线程将64像素的数据加载到共享数组中。然后,在同步之后,64个线程中的每个线程向输出图像写入一个像素。
根据我同步的方式,我会得到不同的结果。我最初认为memoryBarrierShared()是正确的调用,但它产生了以下结果:

这与没有同步或使用memoryBarrier()是相同的结果。
如果我使用barrier(),我会得到以下(期望的)结果:

条带的宽度为32像素,如果我将工作组大小更改为小于或等于32的任何值,则会得到正确的结果。
这里发生了什么事?我是不是误解了memoryBarrierShared()的用途?为什么barrier()应该起作用?
#version 430
#define SIZE 64
layout (local_size_x = SIZE, local_size_y = 1, local_size_z = 1) in;
layout(rgba32f) uniform readonly image2D inImage;
uniform writeonly image2D outImage;
shared vec4 shared_data[SIZE];
void main() {
ivec2 base = ivec2(gl_WorkGroupID.xy * gl_WorkGroupSize.xy);
ivec2 my_index = base + ivec2(gl_LocalInvocationID.x,0);
if (gl_LocalInvocationID.x == 0) {
for (int i = 0; i < SIZE; i++) {
shared_data[i] = imageLoad(inImage, base + ivec2(i,0));
}
}
// with no synchronization: stripes
// memoryBarrier(); // stripes
// memoryBarrierShared(); // stripes
// barrier(); // works
imageStore(outImage, my_index, shared_data[gl_LocalInvocationID.x]);
}发布于 2013-07-03 01:14:00
图像加载存储和好友的问题是,实现不能再确保着色器只更改其专用输出值的数据(例如,片段着色器之后的帧缓冲区)。这更适用于计算着色器,这些着色器没有专用输出,而只是通过将数据写入可写存储来输出内容,如图像、存储缓冲区或原子计数器。这可能需要在各个过程之间进行手动同步,否则尝试访问纹理的片段着色器可能不会通过之前的过程(如计算着色器)通过图像存储操作将最新数据写入该纹理。
因此,您的计算着色器可能工作得很好,但与以下显示(或其他任何)过程(需要以某种方式读取此图像数据)的同步失败。为此,存在glMemoryBarrier函数。根据读取显示过程中图像数据的方式(或者更准确地说,是在计算机着色器过程之后读取图像的过程),需要为该函数指定不同的标志。如果使用纹理读取,请使用GL_TEXTURE_FETCH_BARRIER_BIT;如果再次使用图像加载,请使用GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;如果使用glBlitFramebuffer进行显示,请使用GL_FRAMEBUFFER_BARRIER_BIT...
虽然我在图像加载/存储和手动内存同步方面没有太多经验,但这只是我在理论上提出的想法。因此,如果有人知道得更好,或者您已经使用了合适的glMemoryBarrier,那么请随时纠正我。同样,这也不一定是您唯一的错误(如果有)。但链接的维基文章中的最后两点实际上解决了您的用例,我明确表示您需要某种类型的glMemoryBarrier
在一个渲染过程中写入图像变量并在以后的过程中由着色器读取的
coherent变量或memoryBarrier()。在一个渲染过程中由着色器necessary.SHADER_IMAGE_ACCESS_BARRIER_BIT调用glMemoryBarrier不需要使用coherent变量或memoryBarrier()。调用glMemoryBarrier时,需要在两次传递之间的障碍中设置适当的位。编辑:实际上是Wiki article on compute shaders说的
共享变量访问使用非一致内存访问的规则。这意味着用户必须执行某些同步,以确保共享变量可见。
共享变量都是隐式声明的coherent,因此您不需要(也不能使用)该限定符。但是,您仍然需要提供适当的内存屏障。
通常的内存屏障集可用于计算着色器,但它们也可以访问memoryBarrierShared();此屏障专门用于共享变量排序。groupMemoryBarrier()的行为类似于memoryBarrier(),它为所有类型的变量排序内存写操作,但它只对当前工作组的读/写操作排序。
虽然说一个工作组中的所有调用都是“并行”执行的,但这并不意味着您可以假设所有这些调用都是以锁步方式执行的。如果需要确保调用已写入某个变量,以便可以读取它,则需要将执行与调用同步,而不仅仅是发出内存屏障(尽管仍然需要内存屏障)。
要在工作组内的调用之间同步读取和写入,必须使用 barrier()**函数。这会强制工作组中的所有调用之间进行显式同步。在所有其他调用达到此障碍之前,工作组内的执行将不会继续进行。一旦通过** barrier()**,,之前在组中所有调用中编写的所有共享变量都将可见。**
因此,这听起来像是您需要barrier,而memoryBarrierShared是不够的(尽管正如最后一句话所说的,您不需要两者都需要)。内存屏障只会同步内存,但它不会停止线程的执行以跨越它。因此,如果第一个线程已经写入了某些内容,则这些线程不会从共享内存中读取任何旧的缓存数据,但它们可以在第一个线程尝试写入任何内容之前很好地达到读取点。
这实际上完全符合这样的事实,即对于32个或更小的块大小,它可以工作,并且前32个像素可以工作。至少在NVIDIA硬件32上是翘曲大小,并且因此是以完美锁步运行的线程的数量。因此,前32个线程(好吧,32个线程的每个块)总是完全并行地工作(好吧,从概念上讲就是这样),因此它们不会引入任何竞争条件。这也是为什么你实际上不需要任何同步的原因,如果你知道你在一个单一的warp中工作,这是一个常见的优化。
https://stackoverflow.com/questions/17430443
复制相似问题