我在计算着色器中实现了一个算法
我的工作组尺寸设置为layout (local_size_x = 256) in;
和glDispatchCompute(1, 256, 1);
在步骤2中读取临时图像之前,每个像素都要求其所有8个邻居都已完成步骤1.所以我在步骤1和步骤2之间放置了一个memoryBarrier(),因为OpenGL Programming Guide, 8th Edition
说memory barrier functions apply globally
,而不仅仅是同一个地方工作组
但这并不像预期的那样有效。
为了证明结果,请考虑一个简化但类似的问题,
这会导致黑色矩形变得越来越大。 但结果是,矩形在变大时会变形。
那么,memoryBarrier()真的要等到同一个glDispatchCompute调用触发的所有调用完成内存访问吗?
在第2步和第3步之间实施锁 后,结果按预期工作。(但后来我发现有时会因为超出Windows时间而导致程序崩溃 - 限制!http://nvidia.custhelp.com/app/answers/detail/a_id/3007)
(p是当前位置,p + e [i]是它附近的8个像素&#39;位置。我使用着色器存储缓冲区对象代替图像变量,所以我添加了一个函数posi()来将ivec2转换为数组索引)< / p>
bool finished;
do
{
finished = true;
for(int i = 1; i < 9; i++)
{
if(!outOfBound(p+e[i]) && lock[posi(p+e[i])] != 1)
{
finished = false;
}
}
}while(!finished);
如果我错误地使用了memoryBarrier()并且它无法执行我想要的操作,是否有更好的方法来同步计算着色器的调用?
这是我上面描述的黑色矩形示例的计算着色器代码:
实际上标记是用于判断像素的颜色是黑色还是白色的图像,它在白色背景上初始化为一个小的黑色矩形。
在运行此计算着色器之前, temp 设置为零。
评论的代码是关于上面描述的锁。使用此锁定,着色器将提供所需的输出。
#version 430 core
layout (local_size_x = 256) in;
const ivec2 e[9] = {
ivec2(0,0),
ivec2(1,0), ivec2(0,1), ivec2(-1,0), ivec2(0,-1),
ivec2(1,1), ivec2(-1,1), ivec2(-1,-1), ivec2(1,-1)
};
layout(std430, binding = 14) coherent buffer tag_buff
{
int tag[];
};
layout(std430, binding = 15) coherent buffer temp_buff
{
int temp[];
};
layout(std430, binding = 16) coherent buffer lock_buff
{
int lock[];
};
int posi(ivec2 point)
{
return point.y * 256 + point.x;
}
bool outOfBound(ivec2 p)
{
return p.x < 0 || p.x >= 256
|| p.y < 0 || p.y >= 256;
}
void main()
{
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
int x = tag[posi(p)];
temp[posi(p)] = x;
//lock[posi(p)] = 1;
memoryBarrier();
//bool finished;
//do
//{
// finished = true;
// for(int i = 1; i < 9; i++)
// {
// if(!outOfBound(p+e[i]) && lock[posi(p+e[i])] != 1)
// {
// finished = false;
// }
// }
//}while(!finished);
// if it's black or at least one of its 8 nearby pixel is black
// set itself to black
for(int i = 0; i < 9; i++)
{
if(!outOfBound(p+e[i]) && temp[posi(p+e[i])] == 1)
{
tag[posi(p)] = 1;
}
}
}
后来我尝试将lock
设置为另一个ssbo,然后将其元素设置为1并调用memoryBarrier(),然后在片段着色器中加载新的ssbo并将其打印到屏幕上,我从中发现了一些lock
的元素尚未设置为1。
我还在片段着色器或计算着色器中使用图像变量而不是ssbo,只是为了找到memoryBarrier而且相干无法改变任何东西。看起来memoryBarrier或连贯似乎不起作用。
memoryBarrier
无法通过同步内存访问来同步调用。更具体地说,memoryBarrier
究竟在做什么只是等待已经在调用中发生的所有内存访问 。即使它在源代码中的memoryBarrier
之前,它也不会等待内存访问代码完成但尚未执行。 Opengl编程指南说When memoryBarrier() is called, it ensures that any writes to memory that have been performed by the shader invocation have been committed to memory rather than lingering in caches or being scheduled after the call to memoryBarrier()
。这意味着,例如,假设有三个调用,如果调用A和B都为coherent
图像变量运行了imageStore(),那么A或B的后续memoryBarrier
将保证此imageStore()已更改主内存中的数据,而不仅仅是缓存。但是如果调用C在A或B调用memoryBarrier
时没有运行imageStore(),则此memoryBarrier
调用将不会等待C运行其imageStore()。因此memoryBarrier
无法帮助我实施该算法。
答案 0 :(得分:3)
我遇到了类似的问题。我不是专家,但我相信我找到了一个很好的解决方案。
您已正确识别memoryBarrier
以确保先前写入的可见性。
但是,它自己的memoryBarrier
几乎没用,因为它不能确保执行顺序。因此,尽管您有一个memoryBarrier
,但在其他人开始运行之前,可能会有完全完成的调用。
memoryBarrier
无法显示尚未发生的写入。
我们有barrier
来解决这个问题:
对于计算着色器中任何给定的静态屏障实例,所有在单个工作组中的调用必须先输入它,然后才允许任何调用继续超出它。
请注意重点:barrier
不帮助您在一个glDispatchCompute
调用中同步工作组,它只在工作组内同步。
显然,barrier
对您的问题没有帮助,
所以你介绍了自己的障碍,它有缺点:
如果驾驶员知道障碍物,它可以安排那些尚未到达障碍的调用。在您的解决方案中,驱动程序盲目地调度所有调用,在已经等待的调用上浪费资源,而不是运行那些尚未到达障碍的调用。
该怎么做?
要在所有调用中实现屏障,只需将多个glDispatchCompute
与适当的glMemoryBarrier
电话交错。
分成多个glDispatchCompute
电话会在它们之间形成障碍。
glMemoryBarrier
使以前调用的写入对后来的可见。