使用imageAtomicCompSwap的GLSL每像素自旋锁

Zhi*_*hen 4 opengl mutex glsl spinlock

OpenGL红皮书版本9(OpenGL 4.5)示例11.13是Simple Per-Pixel Mutex。它imageAtomicCompSwapdo {} while()循环中使用每个像素的锁定,以防止同时访问对应于同一像素坐标的像素着色器调用之间的共享资源。

layout (binding = 0, r32ui) uniform volatile coherent uimage2D lock_image;

void main(void)
{
    ivec2 pos = ivec2(gl_FragCoord.xy);

    // spinlock - acquire
    uint lock_available;
    do {
        lock_available = imageAtomicCompSwap(lock_image, pos, 0, 1);
    } while (lock_available != 0);

    // do some operations protected by the lock
    do_something();

    // spinlock - release
    imageStore(lock_image, pos, uvec4(0));
}
Run Code Online (Sandbox Code Playgroud)

此示例在Nvidia和AMD GPU上均产生APPCRASH。我知道在这两个平台上PS的​​职业无法彼此独立地进行-线程的子组以锁步的方式执行,共享控制流(Nvidia术语中的32个线程的“扭曲”)。因此可能导致死锁。

但是,OpenGL规范在任何地方都没有提到“以锁步执行的线程”。它仅提及“未定义相同着色器类型的调用的相对顺序”。。如本例所示,为什么我们不能使用原子操作imageAtomicCompSwap来确保不同PS调用之间的互斥访问?这是否意味着Nvidia和AMD GPU不符合OpenGL规范?

Nic*_*las 5

如本例所示,为什么我们不能使用原子操作imageAtomicCompSwap来确保不同PS调用之间的互斥访问?

如果您使用原子操作来锁定对像素的访问,那么您将依赖于相对顺序的一个方面:所有线程最终都会前进。也就是说,您假设在锁上旋转的任何线程都不会使拥有其执行资源锁的线程饿死。持有锁的线程最终将前进并释放它。

但是,由于执行的相对顺序是不确定的,因此不能保证其中的任何一个。因此,您的代码无法正常工作。任何依赖于单个着色器阶段调用之间的顺序的任何方面的代码都无法工作(除非有特定的保证)。

这就是为什么存在ARB_fragment_shader_interlock的原因。


话虽这么说,即使有前进的保证,您的代码仍然会被破坏。

您使用非原子操作来释放锁定。您应该使用原子设置操作。

另外,正如其他人指出的那样,如果原子比较/交换的返回值为零,则需要继续旋转。请记住:所有原子函数从图像返回原始值。因此,如果原子读取的原始值不为0,则它​​将比较为false,并且您没有锁。

现在,按照规范,您的代码仍将是UB。但这更有可能起作用。


BDL*_*BDL 0

如果执行顺序有问题,稍微重新排序代码可能会解决问题:

layout (binding = 0, r32ui) uniform volatile coherent uimage2D lock_image;

void main(void)
{
    ivec2 pos = ivec2(gl_FragCoord.xy);

    // spinlock - acquire
    uint lock_available;
    do {
        lock_available = imageAtomicCompSwap(lock_image, pos, 0, 1);

        if (lock_available == 0)
        {
            // do some operations protected by the lock
            do_something();

            // spinlock - release
            imageAtomicExchange(lock_image, pos, 0);
        }

    } while (lock_available != 0);
}
Run Code Online (Sandbox Code Playgroud)