片段着色器使用两个原子计数器。它可能会或可能不会增加第一个,可能会或可能不会增加第二个(但从不两者)。但是,在修改计数器之前,总会读取它们的当前值,如果计数器稍后被修改,那些先前读取的值将用于某些自定义逻辑。所有这些都发生在(很可能是不可滚动的)循环中。
设想一个大致如下的流程:
问题:着色器查询当前计数器值 - 是否始终获得“最新”值?我是否在这里失去了片段着色器的大规模并行性(仅就当代和未来的GPU和驱动程序而言)?
关于分支(如果是x) - 我将另一个(readonly restrict uniform
)uimage1D
中的纹素与(uniform
)uint
进行比较。因此,一个操作数绝对是一个统一的标量,但另一个是imageLoad().x
,尽管图像是统一的 - 这种分支仍然是“完全并行化”的吗?你可以看到两个分支都是两个,几乎相同的指令。假设一个“完美优化”的GLSL编译器,这种分支是否可能引入一个失速?
答案 0 :(得分:5)
原子计数器是原子的。但是每个原子操作仅对该操作是原子操作。
因此,如果您想确保每个着色器从计数器获取唯一值,则每个着色器必须使用atomicCounterIncrement
(或Decrement
访问该计数器 ,但他们都必须使用同一个。)
正确的做法是:
atomicCounterIncrement(AC1)
,存储返回的值。atomicCounterIncrement(AC2)
,存储返回的值。你的“获取和后来的增量”策略是一种等待发生的竞争条件。它是否“完全并行化”并不重要,因为它是已破坏。在想知道它是否会很快之前,你需要它才能工作。
我强烈建议在尝试解决GPU问题之前熟悉 CPU 上的原子和线程。这是初学者在处理atomics时常犯的错误。如果你想成功使用GLSL原子和图像加载/存储,你需要成为一个线程专家(或至少是中级)。
答案 1 :(得分:2)
正如Nicol Bolas建议的那样,如果你想确保从原子计数器读取的值不会被另一个内核读取,你需要执行原子增量并使用返回的值,而不是其他内核除非他们执行atomicCounter(AC1)
检查值而不增加。在您原子地递增值并返回旧值的那一刻,您确保执行相同操作的所有其他人只会获得递增的值。
你好像在做一个A-Buffer,我很好奇为什么你需要第二个计数器。我假设uimage1D_A是你的屏幕大小的指向片段列表的指针映射,它存储在uimage1D_B中,对吗?您使用AC2生成指向uimage1D_B的新未使用内存部分的指针,但您的AC1建议您逐渐访问uimage1D_A,因此我可能完全错误:)