片段着色器中的GLSL原子计数器(和分支)

时间:2012-03-17 08:13:32

标签: opengl parallel-processing glsl gpgpu fragment-shader

片段着色器使用两个原子计数器。它可能会或可能不会增加第一个,可能会或可能不会增加第二个(但从不两者)。但是,在修改计数器之前,总会读取它们的当前值,如果计数器稍后被修改,那些先前读取的值将用于某些自定义逻辑。所有这些都发生在(很可能是不可滚动的)循环中。

设想一个大致如下的流程:

  • 在一些小的不可滚动的循环中,比如FOR 0-20(编译时可解析的const)......
  • 获取AC1和AC2的计数器值
  • 检查一些值:
  • 如果x:在索引AC1的uimage1D_A中设置texel,则递增AC1
  • else:在索引(imgwidth-AC2-1)的uimage1D_B中设置texel,增加AC2

问题:着色器查询当前计数器值 - 是否始终获得“最新”值?我是否在这里失去了片段着色器的大规模并行性(仅就当代和未来的GPU和驱动程序而言)?

关于分支(如果是x) - 我将另一个(readonly restrict uniformuimage1D中的纹素与(uniformuint进行比较。因此,一个操作数绝对是一个统一的标量,但另一个是imageLoad().x,尽管图像是统一的 - 这种分支仍然是“完全并行化”的吗?你可以看到两个分支都是两个,几乎相同的指令。假设一个“完美优化”的GLSL编译器,这种分支是否可能引入一个失速?

2 个答案:

答案 0 :(得分:5)

原子计数器是原子的。但是每个原子操作仅对该操作是原子操作。

因此,如果您想确保每个着色器从计数器获取唯一值,则每个着色器必须使用atomicCounterIncrement(或Decrement访问该计数器 ,但他们都必须使用同一个。)

正确的做法是:

  1. 检查一些值:
  2. 如果x:
    1. atomicCounterIncrement(AC1),存储返回的值。
    2. 使用存储的值作为将内容设置为uimage1D_A的纹素。
  3. 否则:
    1. atomicCounterIncrement(AC2),存储返回的值。
    2. 使用存储的值计算要在uimage1D_B中设置内容的texel(imgwidth - val - 1)。
  4. 你的“获取和后来的增量”策略是一种等待发生的竞争条件。它是否“完全并行化”并不重要,因为它是已破坏。在想知道它是否会很快之前,你需要它才能工作。

    我强烈建议在尝试解决GPU问题之前熟悉 CPU 上的原子和线程。这是初学者在处理atomics时常犯的错误。如果你想成功使用GLSL原子和图像加载/存储,你需要成为一个线程专家(或至少是中级)。

答案 1 :(得分:2)

正如Nicol Bolas建议的那样,如果你想确保从原子计数器读取的值不会被另一个内核读取,你需要执行原子增量并使用返回的值,而不是其他内核除非他们执行atomicCounter(AC1)检查值而不增加。在您原子地递增值并返回旧值的那一刻,您确保执行相同操作的所有其他人只会获得递增的值。

你好像在做一个A-Buffer,我很好奇为什么你需要第二个计数器。我假设uimage1D_A是你的屏幕大小的指向片段列表的指针映射,它存储在uimage1D_B中,对吗?您使用AC2生成指向uimage1D_B的新未使用内存部分的指针,但您的AC1建议您逐渐访问uimage1D_A,因此我可能完全错误:)