c# - GLSL 自旋锁永远阻塞

标签 c# opengl glsl

我正在尝试在 GLSL 中实现自旋锁。它将在 Voxel Cone Tracing 的上下文中使用。我尝试将存储锁定状态的信息移动到允许原子操作的单独 3D 纹理。为了不浪费内存,我不使用完整的整数来存储锁定状态,而是只使用一个位。问题是在不限制最大迭代次数的情况下,循环永远不会终止。我在 C# 中实现了完全相同的机制,创建了很多在共享资源上工作的任务,并且在那里工作得很好。 Euro Par 2017:并行处理第 274 页(可在 Google 上找到)一书提到了在 SIMT 设备上使用锁时可能需要注意的事项。我认为代码应该绕过这些警告。

有问题的 GLSL 代码:

void imageAtomicRGBA8Avg(layout(RGBA8) volatile image3D image, layout(r32ui) volatile uimage3D lockImage,
    ivec3 coords, vec4 value)
{
    ivec3 lockCoords = coords;

    uint bit = 1<<(lockCoords.z & (4)); //1<<(coord.z % 32)  
    lockCoords.z = lockCoords.z >> 5;  //Division by 32    

    uint oldValue = 0;
    //int counter=0;
    bool goOn = true;
    while (goOn /*&& counter < 10000*/)
    //while(true)
    {
        uint newValue = oldValue | bit;
        uint result = imageAtomicCompSwap(lockImage, lockCoords, oldValue, newValue);

        //Writing is allowed if could write our value and if the bit indicating the lock is not already set
        if (result == oldValue && (result & bit) == 0) 
        {
            vec4 rval = imageLoad(image, coords);
            rval.rgb = (rval.rgb * rval.a); // Denormalize
            vec4 curValF = rval + value;    // Add
            curValF.rgb /= curValF.a;       // Renormalize   
            imageStore(image, coords, curValF);

            //Release the lock and set the flag such that the loops terminate
            bit = ~bit;
            oldValue = 0;
            while (goOn)
            {
                newValue = oldValue & bit;
                result = imageAtomicCompSwap(lockImage, lockCoords, oldValue, newValue);
                if (result == oldValue) 
                    goOn = false; //break;
                oldValue = result;
            }
            //break;
        }
        oldValue = result;
        //++counter;
    }
}

具有相同功能的工作 C# 代码

public static void Test()
{
    int buffer = 0;
    int[] resource = new int[2];
    Action testA = delegate ()
    {
        for (int i = 0; i < 100000; ++i)
            imageAtomicRGBA8Avg(ref buffer, 1, resource);
    };
    Action testB = delegate ()
    {
        for (int i = 0; i < 100000; ++i)
            imageAtomicRGBA8Avg(ref buffer, 2, resource);
    };

    Task[] tA = new Task[100];
    Task[] tB = new Task[100];
    for (int i = 0; i < tA.Length; ++i)
    {
        tA[i] = new Task(testA);
        tA[i].Start();
        tB[i] = new Task(testB);
        tB[i].Start();
    }

    for (int i = 0; i < tA.Length; ++i)
        tA[i].Wait();
    for (int i = 0; i < tB.Length; ++i)
        tB[i].Wait();
}

public static void imageAtomicRGBA8Avg(ref int lockImage, int bit, int[] resource)
{
    int oldValue = 0;
    int counter = 0;
    bool goOn = true;
    while (goOn /*&& counter < 10000*/)
    {
        int newValue = oldValue | bit;
        int result = Interlocked.CompareExchange(ref lockImage, newValue, oldValue); //imageAtomicCompSwap(lockImage, lockCoords, oldValue, newValue);
        if (result == oldValue && (result & bit) == 0)
        {
            //Now we hold the lock and can write safely
            resource[bit - 1]++;

            bit = ~bit;
            oldValue = 0;
            while (goOn)
            {
                newValue = oldValue & bit;
                result = Interlocked.CompareExchange(ref lockImage, newValue, oldValue); //imageAtomicCompSwap(lockImage, lockCoords, oldValue, newValue);
                if (result == oldValue)
                    goOn = false; //break;
                oldValue = result;
            }
            //break;
        }
        oldValue = result;
        ++counter;
    }
}

锁定机制的工作方式应该与 Cyril Crassin 和 Simon Green 在 OpenGL Insigts 第 22 章使用 GPU 硬件光栅器进行基于八叉树的稀疏体素化中描述的机制完全相同。他们只使用整数纹理来存储我想避免的每个体素的颜色,因为这会使 Mip 映射和其他事情复杂化。 我希望帖子是可以理解的,我觉得它已经变得太长了......

为什么 GLSL 实现没有终止?

最佳答案

如果我理解得很好,你可以使用 lockImage作为线程锁:确定坐标处的确定值意味着“只有此着色器实例可以执行下一步操作”(在该坐标处更改其他图像中的数据)。正确的。

关键是imageAtomicCompSwap .我们知道它完成了这项工作,因为它能够存储确定的值(假设 0 表示“免费”,1 表示“锁定”)。我们知道它是因为返回值(原始值)是“免费的”(即交换操作发生了):

bool goOn = true;
unit oldValue = 0; //free
uint newValue = 1; //locked
//Wait for other shader instance to free the simulated lock
while ( goON )
{
    uint result = imageAtomicCompSwap(lockImage, lockCoords, oldValue, newValue);
    if ( result == oldValue ) //it was free, now it's locked
    {
        //Just this shader instance executes next lines now.
        //Other instances will find a "locked" value in 'lockImage' and will wait
        ...
        //release our simulated lock
        imageAtomicCompSwap(lockImage, lockCoords, newValue, oldValue);
        goOn = false;
    }
}

我认为你的代码永远循环,因为你用 bit 复杂化了你的生活var 并错误地使用了 oldValenewValue

编辑:

如果 lockImage 的 'z'是 32 的倍数(只是一个理解的提示,不需要精确的倍数),您尝试将 32 个体素锁打包成一个整数。我们称这个整数为32C .

着色器实例(“SI”)可能想要更改其在 32C 中的位,锁定或解锁。因此,您必须 (A) 获取当前值并且 (B) 仅更改您的位。

其他 SI 正在尝试更改其位。有些位相同,有些位不同。

两次调用 imageAtomicCompSwap 之间在一个 SI 中,其他 SI 可能改变的不是你的位(它被锁定了,不是吗?),而是同一 32C 中的其他位。值(value)。你不知道哪个是当前值,你只知道你的位。因此,您在 imageAtomicCompSwap 中没有任何东西(或旧的错误值)可以比较称呼。它可能无法设置新值。几个 SI 失败会导致“死锁”并且 while 循环永远不会结束。

您尝试通过 oldValue = result 避免使用旧的错误值并再次尝试 imageAtomicCompSwap .这是我之前写的(A)-(B)。但是在 (A) 和 (B) 之间还有其他 SI 可能已经改变了 result= 32C值(value),毁了你的想法。

想法: 您可以使用我的简单方法(仅 01 中的值 lockImage ),无需 bits事物。结果是 lockImage更小。但是所有着色器实例都试图更新 32 个 image 中的任何32C 相关的坐标lockImage 中的值将等到锁定该值的人释放它。

使用另一个lockImage2只是为了锁定-解锁 32C有点更新的值(value),似乎旋转太多。

关于c# - GLSL 自旋锁永远阻塞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46632261/

相关文章:

c# - 在 GridView 中获取选中行的文本框值

c# - 当 DatabaseGenerated.Identity 已经与 Entity Framework 一起使用时自动增加一个数字

c++ - (C++ 和 OpenGL)我试图在批处理渲染器中旋转一组顶点(它将模拟一个正方形),但它不是 100% 工作 :(

c++ - 解析 GLSL 着色器字符串以在 Android NDK 中查找变量名称

c++ - 纹理渲染和 VBO 的 [OpenGL/SDL/C++]

opengl - GLSL 几何着色器和投影矩阵

c# - IDispatchMessageInspector : Improving BeforeSendReply functionality

c# - 我可以使用 null 条件运算符而不是经典的事件引发模式吗?

opengl - "missing glut32.dll"在 VS2010 下使用 OpenGL

OpenGL 规范规定 `GLint` 必须是 32 位宽,但 gl.xml 天真地将其定义为 `int` 而不是 `int32_t` 。为什么?