我使用计算着色器将单像素点渲染为uint32纹理。纹理是3d纹理,x和y是视口坐标,z有关于坐标0的深度信息和1上的附加属性。所以两个手动构建的rendertargets,如果你愿意的话。代码如下所示:
layout (r32ui, binding = 0) coherent volatile uniform uimage3D renderBuffer;
layout (rgba32f, binding = 1) restrict readonly uniform imageBuffer pointBuffer;
for(int j = 0; j < numPoints / gl_WorkGroupSize.x + 1; j++)
{
vec4 point = imageLoad(pointBuffer, ...)
// ... transform point ...
uint originalDepth = imageAtomicMin(renderBuffer, ivec3(imageCoords, 0), point.depth);
if (originalDepth >= point.depth)
{
// write happened, store the attributes
imageStore(renderBuffer, ivec3(imageCoords, 1), point.attributes);
}
}
当深度值正确时,我有一些像素,其中属性在两个值之间闪烁。
pointBuffer中的点的顺序是随机的(但我已经验证了所有点的集合始终是相同的),所以我首先想到的是两个相等的深度值可能会改变输出,具体取决于哪一个首先出现。所以我明白了,if originalDepth == point.depth
它使用imageAtomicMax
总是使用相同的两个替代属性来写,但是没有改变。
我把barrier()
和memoryBarrier()
分散到了整个地方,但没有改变。为此我也删除了所有不同的控制流程,没有任何改变。
将局部工作大小减少到32可以消除90%的闪烁,但仍有一些仍然存在。
任何想法都将不胜感激。
编辑:在你问为什么我手动操作这个东西而不是使用普通的光栅化和片段着色器之前,原因就是性能。光栅化器没有帮助,因为我渲染单像素点,共享内存大大加快了速度,并且我多次渲染每个点,这要求我使用缓慢的几何着色器。
答案 0 :(得分:1)
问题在于:您在写入renderBuffer
时遇到了竞争条件。如果两个不同的CS调用映射到同一个像素,并且它们都决定写入该值,那么您的imageStore
调用就会出现竞争。一个可能会覆盖另一个,它可能是部分覆盖,或者完全不同。但无论如何,它并不能保证有效。
通过执行光栅化器可以最好地解决这个问题:将过程分解为两个单独的阶段。第一阶段执行... transform point ...
部分,将数据写入缓冲区。第二阶段然后通过点并将它们写入最终图像。
在阶段2中,每个CS调用对特定输出像素执行所有处理。这样,没有竞争条件。当然,这要求阶段1以可以按像素排序的方式生成数据。
有几种方法可以解决后者问题。您可以使用链表,每个像素列表。或者您可以使用每个工作组列表,其中工作组表示像素空间的某些X / Y区域。在这种情况下,您将使用本地共享内存作为本地深度缓冲区,所有CS调用都从该区域读取/写入。完成所有处理像素后,将其写入实际内存。基本上,您将手动实现基于图块的渲染。
实际上,如果您有批次这些点,基于图块的解决方案将允许您合并流水线,这样您就不必等到第1阶段的所有阶段完成之前从阶段2的某些阶段开始。您可以将阶段1分解为块。你启动了几个第1阶段的块,然后是第2阶段的块,它从第一阶段1读取,然后是另一阶段1,依此类推。
Vulkan及其事件系统,有更好的工具来构建比OpenGL更高效的依赖链。