我正在尝试在GLSL中实现一个自旋锁。它将用于体素圆锥体跟踪的上下文中。我尝试将存储锁定状态的信息移动到允许原子操作的单独3D纹理中。为了不浪费内存,我没有使用一个完整的整数来存储锁状态,而只使用了一位。问题是在不限制最大迭代次数的情况下,循环永远不会终止。我在C#中实现了完全相同的机制,创建了许多在共享资源上工作的任务,在那里它工作得很好。Euro Par 2017: Parallel Processing Page 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章基于八叉树的稀疏Voxelization使用GPU硬件光栅化中描述的机制完全相同。他们只是使用整数纹理来存储每个体素的颜色,这是我想要避免的,因为这会使Mip映射和其他事情变得复杂。我希望这篇文章是可以理解的,我感觉它已经变得太长了…
为什么GLSL实现不终止?
发布于 2017-10-09 01:48:46
如果我很好地理解您,您使用lockImage作为线程锁:在确定的坐标处确定的值意味着“只有这个着色器实例可以执行下一步操作”(在该坐标处更改其他图像中的数据)。正确的。
关键是imageAtomicCompSwap。我们知道它完成了这项工作,因为它能够存储确定的值(假设0表示“自由”,1表示“锁定”)。我们知道它是因为返回值(原始值)是"free“(即发生了交换操作):
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;
}
}我认为您的代码会永远循环,因为您使用bitvar使您的生活复杂化,并且错误地使用了oldVale和newValue
编辑:
如果lockImage的'z‘是32的倍数(只是为了便于理解,不需要精确的倍数),您可以尝试将32个体素锁打包到一个整数中。让我们将这个整数称为32C。
着色器实例("SI")可能希望在32C、lock或unlock中更改其位。所以你必须(A)得到当前的值并且(B)只改变你的位。
其他SI正在尝试改变他们的比特。一些具有相同的位,其他具有不同的位。
在两次调用一个SI中的imageAtomicCompSwap之间,另一个SI可能已更改,而不是您的位(它已锁定,不是吗?)而是相同32C值中的其他位。你不知道哪个是当前值,你只知道你自己的一部分。因此,您在imageAtomicCompSwap调用中没有什么可比较的(或一个旧的错误值)。它可能无法设置新值。几个SIs失败会导致“死锁”,而while循环永远不会结束。
您可以尝试避免在oldValue = result中使用旧的错误值并在imageAtomicCompSwap中再次尝试。这是我之前写的(A)-(B)。但在(A)和(B)之间,还有其他SI可能更改了result= 32C值,从而毁掉了您的想法。
的想法:你可以使用我的简单方法(只是0或1值在lockImage中),没有bits的东西。结果是lockImage变小了。但是,尝试更新与lockImage中的32C值相关的32个image坐标中的任何的所有着色器实例都将等待,直到锁定该值的人释放该值。
使用另一个lockImage2来锁定-解锁32C值以进行一点更新,似乎有点太麻烦了。
发布于 2017-10-09 22:05:24
我写了一篇关于如何在片段着色器中实现每像素互斥锁的文章以及代码。我想你可以参考一下。您正在做的事情与我在那里解释的非常相似。我们开始吧:
获取绘制计数和每像素互斥锁
什么是透支计数?
主要是在嵌入式硬件上,性能下降的主要问题可能是透支。基本上,由于我们正在绘制的几何或场景的性质,屏幕上的一个像素会被GPU多次着色,这称为透绘。有许多工具可以可视化透支计数。
透支的详细信息?
当我们绘制一些顶点时,这些顶点将被转换到裁剪空间,然后转换到窗口坐标。然后,光栅化器将此坐标映射到像素/碎片。然后,对于像素/碎片,GPU调用像素着色器。当我们绘制几何体的多个实例并混合它们时,可能会出现这种情况。因此,这将在同一个像素上绘制多个times.This将导致透支,并可能会降低性能。
避免透支的策略?
如果你观察第二点,我们是以完全相反的顺序渲染的,因为blending.We是从后到前渲染的。我们需要这样做,因为混合发生在z测试之后。如果任何片段没有通过z测试,那么尽管它在后面,我们仍然应该认为它是混合的,但是,该片段将被完全忽略,因为我们需要从后到前保持顺序的artifacts.Hence。由于这一点,当混合被启用时,我们得到更多的透支计数。
为什么我们需要Per Pixel Mutex?
这种情况可能会对相同的像素进行着色,因此访问相同的pixels.This可能会导致一些同步issues.This可能会产生一些不想要的效果。在此应用程序中,我将图像缓冲区中的透支计数初始化为0。我所做的操作顺序如下。
正如我告诉你的,像素着色器的多个实例可以在同一像素上工作,这可能会导致计数器variable.As的损坏,这些步骤的算法不是原子的。我本可以使用内置函数imageAtomicAdd().我想展示我们如何实现每像素互斥锁,所以,我没有使用内置函数imageAtomicAdd()。
#version 430
layout(binding = 0,r32ui) uniform uimage2D overdraw_count;
layout(binding = 1,r32ui) uniform uimage2D image_lock;
void mutex_lock(ivec2 pos) {
uint lock_available;
do {
lock_available = imageAtomicCompSwap(image_lock, pos, 0, 1);
} while (lock_available == 0);
}
void mutex_unlock(ivec2 pos) {
imageStore(image_lock, pos, uvec4(0));
}
out vec4 color;
void main() {
mutex_lock(ivec2(gl_FragCoord.xy));
uint count = imageLoad(overdraw_count, ivec2(gl_FragCoord.xy)).x + 1;
imageStore(overdraw_count, ivec2(gl_FragCoord.xy), uvec4(count));
mutex_unlock(ivec2(gl_FragCoord.xy));
}Fragment_Shader.fs
关于Demo。
在演示视频中,你可以看到我们正在渲染许多茶壶,混合是on.So像素,强度更高,显示透支数量很高。
注意:在android上,你可以在调试GPU选项中看到这个透支计数。
https://stackoverflow.com/questions/46632261
复制相似问题