约翰已经写了一个很好的答案,所以考虑这个答案是他的扩展。
我目前正在为各种算法使用计算着色器。总的来说,我发现计算着色器比其等效的像素着色器或基于变换反馈的替代方法要快得多。
一旦您掌握了计算着色器的工作原理,在许多情况下它们也变得更加有意义。使用像素着色器过滤图像需要设置帧缓冲区,发送顶点,使用多个着色器阶段等。为什么要过滤图像呢?我认为习惯于渲染全屏四边形进行图像处理无疑是继续使用它们的唯一“有效”理由。我坚信,计算图形领域的新手会发现计算着色器比渲染到纹理更自然地适合图像处理。
您的问题特别涉及图像过滤,因此在其他主题上我不会赘述。在我们的某些测试中,仅设置转换反馈或切换帧缓冲区对象以渲染为纹理可能会导致约0.2ms的性能损失。请记住,这不包括任何渲染!在一种情况下,我们将完全相同的算法移植到计算着色器中,并看到了明显的性能提升。
使用计算着色器时,GPU上的更多芯片可用于完成实际工作。使用像素着色器路线时,所有这些附加步骤都是必需的:
- 顶点组合(读取顶点属性,顶点除数,类型转换,将它们扩展到vec4等)
- 无论顶点着色器有多小,都需要对其进行调度
- 光栅化器必须计算一个像素列表以对顶点输出进行着色和内插(可能仅用于图像处理的纹理坐标)
- 必须设置和管理所有不同的状态(深度测试,alpha测试,剪刀,混合)
您可能会争辩说,前面提到的所有性能优势都可以由智能驱动程序否定。你说的没错。这样的驱动程序可以识别出您在不进行深度测试等情况下渲染全屏四边形,并配置了“快速路径”,该路径跳过了为支持像素着色器所做的所有无用工作。如果某些驱动程序为特定的GPU加速某些AAA游戏中的后处理过程,我不会感到惊讶。如果您不是在玩AAA游戏,您当然可以忘记任何此类处理。
但是,驱动程序无法做的是找到计算着色器管道提供的更好的并行性机会。以高斯滤波器的经典示例为例。使用计算着色器,您可以执行以下操作(是否分离过滤器):
- 对于每个工作组,将源图像的采样划分为工作组大小,并将结果存储到组共享内存中。
- 使用存储在共享内存中的样本结果计算滤波器输出。
- 写入输出纹理
步骤1是这里的关键。在像素着色器版本中,每个像素对源图像进行多次采样。在计算着色器版本中,每个源纹理像素在一个工作组中只能读取一次。纹理读取通常使用基于图块的缓存,但是此缓存仍然比共享内存慢得多。
高斯滤波器是较简单的示例之一。其他过滤算法为使用共享内存在工作组内部共享中间结果提供了其他机会。
但是有一个陷阱。计算着色器需要显式的内存屏障来同步其输出。也很少有保护措施来防止错误的内存访问。对于具有良好并行编程知识的程序员,计算着色器提供了更大的灵活性。但是,这种灵活性意味着将计算着色器像普通的C ++代码一样对待并编写慢速或不正确的代码也更加容易。
参考文献