什么时候计算着色器比像素着色器更有效地进行图像过滤?


37

图像过滤操作(例如模糊,SSAO,Bloom等)通常使用像素着色器和“聚集”操作完成,其中每个像素着色器调用都会发出许多纹理提取操作以访问相邻像素值,并计算单个像素的值结果。这种方法在理论上效率低下,因为完成了许多冗余提取:附近的着色器调用将重新提取许多相同的纹理像素。

另一种方法是使用计算着色器。这些具有的潜在优势是能够在一组着色器调用之间共享少量内存。例如,您可以让每个调用获取一个纹理元素并将其存储在共享内存中,然后从那里计算结果。这可能会或可能不会更快。

问题是在什么情况下(如果有的话)compute-shader方法实际上比pixel-shader方法更快?它是否取决于内核的大小,它是什么类型的过滤操作等?显然,答案从一个GPU型号到另一个GPU都不同,但是我很想听听是否存在任何普遍趋势。


如果计算着色器正确完成,我认为答案是“总是” 。这并非易事。从概念上讲,对于图像处理算法而言,计算着色器比像素着色器更好地匹配。但是,像素着色器提供的余地较小,可以用来编写性能不佳的滤镜。
伯尼

@bernie您能否阐明“正确完成”计算着色器需要什么?也许写一个答案?总是很高兴获得关于该主题的更多观点。:)
内森·里德

2
现在看看你让我做什么!:)
伯尼

除了跨线程共享工作之外,使用异步计算的能力也是使用计算着色器的一大原因。
JarkkoL

Answers:


23

用于图像处理的计算着色器的体系结构优势是它们可以跳过ROP步骤。即使不使用像素着色器,写入很有可能会通过所有常规混合硬件。一般来说,计算着色器通过不同的(通常是更直接的)内存路径,因此可以避免原本会遇到的瓶颈。我听说这归功于相当大的性能优势。

计算着色器的体系结构缺点是GPU不再知道哪些工作项退回到哪些像素。如果您正在使用像素着色管线,则GPU可以将工作打包到扭曲/波前中,并写入内存中连续的渲染目标区域(可能是Z阶平铺或类似的表现)原因)。如果您使用的是计算管道,GPU可能无法再以最佳批次启动工作,从而导致更多带宽使用。

但是,如果您知道特定的操作具有一个子结构,可以通过将相关工作打包到同一线程组中加以利用,则可以再次将这种变化的翘曲/波前压缩变成优势。就像您说的那样,理论上您可以通过以下方式来中断采样硬件:对每个通道采样一个值,然后将结果放入成组共享的内存中,以供其他通道访问而无需采样。这是否要取胜取决于组共享内存的昂贵程度:如果它比最低级别的纹理缓存便宜,那么这也许是一个取胜,但不能保证。GPU已经可以很好地处理高度局部的纹理提取(必要时)。

如果您在操作中有一个想要共享结果的中间阶段,那么使用组共享内存可能更有意义(因为您必须先将中间结果实际写到内存中才能使用纹理采样硬件)。不幸的是,您也不能不依赖于任何其他线程组的结果,因此第二阶段必须将自身限制为仅在同一图块中可用。我认为这里的典型示例是计算屏幕自动曝光的平均亮度。我也可以想象将纹理上采样与其他一些操作结合在一起(因为上采样与下采样和模糊不同,不依赖于给定图块之外的任何值)。


我严重怀疑如果禁用混合,ROP会增加性能开销。
GroverManheim '16

@GroverManheim取决于体系结构!即使禁用混合,输出合并/ ROP步骤也必须处理排序保证。对于全屏三角形,没有任何实际订购危险,但硬件可能不知道。硬件中可能有一些特殊的快速路径,但是要确定您有资格获得这些资格……
John Calsbeek,2016年

10

约翰已经写了一个很好的答案,所以考虑这个答案是他的扩展。

我目前正在为各种算法使用计算着色器。总的来说,我发现计算着色器比其等效的像素着色器或基于变换反馈的替代方法要快得多。

一旦您掌握了计算着色器的工作原理,在许多情况下它们也变得更加有意义。使用像素着色器过滤图像需要设置帧缓冲区,发送顶点,使用多个着色器阶段等。为什么要过滤图像呢?我认为习惯于渲染全屏四边形进行图像处理无疑是继续使用它们的唯一“有效”理由。我坚信,计算图形领域的新手会发现计算着色器比渲染到纹理更自然地适合图像处理。

您的问题特别涉及图像过滤,因此在其他主题上我不会赘述。在我们的某些测试中,仅设置转换反馈或切换帧缓冲区对象以渲染为纹理可能会导致约0.2ms的性能损失。请记住,这不包括任何渲染!在一种情况下,我们将完全相同的算法移植到计算着色器中,并看到了明显的性能提升。

使用计算着色器时,GPU上的更多芯片可用于完成实际工作。使用像素着色器路线时,所有这些附加步骤都是必需的:

  • 顶点组合(读取顶点属性,顶点除数,类型转换,将它们扩展到vec4等)
  • 无论顶点着色器有多小,都需要对其进行调度
  • 光栅化器必须计算一个像素列表以对顶点输出进行着色和内插(可能仅用于图像处理的纹理坐标)
  • 必须设置和管理所有不同的状态(深度测试,alpha测试,剪刀,混合)

您可能会争辩说,前面提到的所有性能优势都可以由智能驱动程序否定。你说的没错。这样的驱动程序可以识别出您在不进行深度测试等情况下渲染全屏四边形,并配置了“快速路径”,该路径跳过了为支持像素着色器所做的所有无用工作。如果某些驱动程序为特定的GPU加速某些AAA游戏中的后处理过程,我不会感到惊讶。如果您不是在玩AAA游戏,您当然可以忘记任何此类处理。

但是,驱动程序无法做的是找到计算着色器管道提供的更好的并行性机会。以高斯滤波器的经典示例为例。使用计算着色器,您可以执行以下操作(是否分离过滤器):

  1. 对于每个工作组,将源图像的采样划分为工作组大小,并将结果存储到组共享内存中。
  2. 使用存储在共享内存中的样本结果计算滤波器输出。
  3. 写入输出纹理

步骤1是这里的关键。在像素着色器版本中,每个像素对源图像进行多次采样。在计算着色器版本中,每个源纹理像素在一个工作组中只能读取一次。纹理读取通常使用基于图块的缓存,但是此缓存仍然比共享内存慢得多。

高斯滤波器是较简单的示例之一。其他过滤算法为使用共享内存在工作组内部共享中间结果提供了其他机会。

但是有一个陷阱。计算着色器需要显式的内存屏障来同步其输出。也很少有保护措施来防止错误的内存访问。对于具有良好并行编程知识的程序员,计算着色器提供了更大的灵活性。但是,这种灵活性意味着将计算着色器像普通的C ++代码一样对待并编写慢速或不正确的代码也更加容易。

参考文献


3

我偶然发现了这个博客: AMD的计算着色器优化

考虑到可以在计算着色器中完成哪些技巧(仅适用于计算着色器),我很好奇计算着色器上的并行缩减是否比像素着色器上的并行缩减更快。我给作者沃尔夫·恩格尔发了电子邮件,询问他是否尝试过像素着色器。他回答是,然后再写博客文章时,计算着色器版本明显快于像素着色器版本。他还补充说,今天的差异更大。因此,显然在某些情况下使用计算着色器可能会具有很大的优势。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.