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

met*_*eap 1 opengl parallel-processing gpgpu glsl 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 uniform)uimage1D的(uniform)uint.因此,一个操作数肯定是一个统一的标量,但另一个是一个imageLoad().x虽然图像是统一的 - 这种分支仍然是"完全并行化"的吗?你可以看到两个分支都是两个,几乎相同的指令.假设一个"完美优化"的GLSL编译器,这种分支是否会引入停顿?

Nic*_*las 5

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

因此,如果要确保每个着色器从计数器获取唯一值,则每个着色器必须使用atomicCounterIncrement(或者Decrement,但它们必须全部使用相同的一个)来访问该计数器.

做你正在建议的正确方法是:

  1. 检查一些价值:
  2. 如果x:
    1. atomicCounterIncrement(AC1),存储返回的值.
    2. 使用存储的值作为将内容设置为uimage1D_A的texel.
  3. 其他:
    1. atomicCounterIncrement(AC2),存储返回的值.
    2. 使用存储的值来计算要在uimage1D_B中设置内容的texel(imgwidth - val - 1).

你的"获取和后来的增量"策略是等待发生的竞争条件.它是否"完全并行化"并不重要,因为它已被破坏.在想知道它是否会很快之前,你需要它才能工作.

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