1

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

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

  • 在一些小的不可滚动循环中,比如 FOR 0-20 (compile-time resolvable const) ...
  • 获取 AC1 和 AC2 的计数器值
  • 检查一些值:
  • 如果 x:在索引 AC1 处设置 uimage1D_A 中的纹素,增加 AC1
  • else: 设置 uimage1D_B 中的纹理像素 (imgwidth-AC2-1),增加 AC2

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

至于分支(如果 x)——我将另一个 ( readonly restrict uniform)uimage1D中的纹素与 ( uniform)进行比较uint。所以一个操作数肯定是一个统一的标量,但另一个是一个imageLoad().x虽然图像是统一的——这种分支是否仍然“完全并行化”?您可以看到两个分支都恰好是两个几乎相同的指令。假设一个“完美优化”的 GLSL 编译器,这种分支是否可能会导致停顿?

4

2 回答 2

6

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

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

执行您建议的正确方法是:

  1. 检查一些值:
  2. 如果 x:
    1. atomicCounterIncrement(AC1),存储返回的值。
    2. 使用存储的值作为在 uimage1D_A 中设置某些东西的纹素。
  3. 别的:
    1. atomicCounterIncrement(AC2),存储返回的值。
    2. 使用存储的值来计算 texel (imgwidth - val - 1) 将某些东西设置到 uimage1D_B 中。

您的“获取并稍后增加”策略是等待发生的竞争条件。它是否“完全并行化”并不重要,因为它已损坏。在想知道它是否会很快之前,您需要它来工作。

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

于 2012-03-17T08:44:29.607 回答
2

正如 Nicol Bolas 建议的那样,如果您想确保从原子计数器读取的值永远不会被另一个内核读取,您需要执行原子增量并使用返回的值,除非它们执行,否则其他内核将不会拥有该值atomicCounter(AC1)它检查值而不增加。当您以原子方式递增值并取回旧值时,您要确保执行相同操作的其他所有人只会获得递增的值。

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

于 2014-07-28T23:20:42.057 回答