我编写了一个延迟渲染器,可以使用片段着色器或计算着色器执行着色过程。不幸的是,计算着色器实现的运行速度较慢。我正试图了解原因。我相信最直接的原因:访问纹理时出现内存局部性。片段着色器的访问在某种程度上比计算着色器的访问更加一致。

为了演示这一点,我删除了除阴影映射代码以外的所有内容,然后将其更改为随机采样。类似(GLSL伪代码):
 uniform sampler2D tex_shadowmap;

uniform float param;

#ifdef COMPUTE_SHADER
layout(local_size_x=8, local_size_y=4, local_size_z=1) in;
#endif

struct RNG { uint64_t state; uint64_t inc; } _rng;
void rand_seed(ivec2 coord) { /*seed `_rng` with hash of `coord`*/ }
float rand_float() { /*return random float in [0,1]*/ }

void main() {
    rand_seed(/*pixel coordinate*/);

    vec4 light_coord = /*vertex in scaled/biased light's NDC*/;
    vec3 shadowmap_test_pos = light_coord.xyz / light_coord.w;

    float rand_shadow = 0.0;
    for (int i=0;i<200;++i) {
        vec2 coord = fract(mix( shadowmap_test_pos.xy, vec2(rand_float(),rand_float()), param ));
        float tap = textureLod(tex_shadowmap,coord,0.0).r;
        rand_shadow += clamp(shadowmap_test_pos.z,0.0,1.0)<=tap+0.00001 ? 1.0 : 0.0;
    }
    vec4 color = vec4(vec3(rand_shadow)/200.0,1.0);

    /*[set `color` into output]*/
}
 

param设置为0时,阴影图在shadowmap_test_pos采样,我们得到校正场景的硬阴影。在这种情况下,阴影贴图纹理查找位置与像素坐标有些相关,因此我们希望获得良好的性能。当param设置为1时,我们得到一个完全随机的纹理坐标vec2(rand_float(),rand_float()),因此纹理查找与像素坐标完全不相关,并且我们预期性能会很差。可以看到param的值并使用计时器查询来测量阴影传递的延迟:

可以看到,当使用完全随机坐标(param = 1,右侧)时,片段着色器和计算着色器具有相同的性能。但是,随着坐标变得越来越不随机,无论片段着色器在做什么,都使它变得更加连贯。当坐标是确定性的并且与屏幕位置相关时(param≈0,左侧),片段着色器获胜2倍(注意:由于GLSL编译器优化了循环,省略了param = 0的情况)。 />尤其奇怪的是,片段着色器更快似乎取决于纹理样本坐标与像素坐标的关联。例如,如果我使用shadowmap_test_pos.xy代替vec2(0.5)作为确定性坐标,则效果消失,并且两个着色器对于任何param都具有相同的性能。除了进行一些设置和写出数据(可能会有一点变化)外,着色器是相同的。您可以在这里看到我对PTX拆卸件所做的比较。
注意:测试的硬件是具有当前(446.14)驱动程序的NVIDIA GTX1080。

我的问题基本上是:我该怎么办?我正在计算着色器中的8×4切片中工作,但是谁知道片段着色器在做什么。但是,我不希望片段着色器执行任何神奇的秘密着色顺序会更好,以至于当您运行相同的实际代码时性能会有> 2%的差异。 (FWIW,我尝试过使用不同的组大小,以使上述行为没有真正的改变。)
有一些关于不同着色器如何工作的常规讨论,但是我没有找到任何可以解释这一点的东西。而且,尽管过去驱动程序问题引起了怪异的行为,但计算着色器已经在核心GL中使用了将近8年了,将它们用于延迟着色是一个显而易见的,可以说是常见的用例,我希望它能很好地工作。
我在这里想念什么?

评论

“将它们用于延迟着色是一个显而易见的,可以争论的通用用例,我希望它能很好地发挥作用”是吗?我从来没听说过。当CS和FS都具有相同的表达能力并且FS可以使用混合操作,深度测试和其他可用来改善整体渲染性能的有用功能时,就没有理由为此使用CS。基本上,CS永远不会比FS快,并且FS可以完成CS可以做的所有事情,那为什么要麻烦呢?

@NicolBolas与链接参考中一样,CS比FS具有更强大的表达能力,并且由于它绕过了上面的整个图形管道和下面的ROP,因此从理论上讲它可以更快。确实,在此着色器中,如果我用昂贵的不接触纹理的BRDF替换这些纹理访问,则CS的速度将提高约10%。关于您为什么更喜欢CS的原因:如果这还不够,那么设置起来也更加简洁,并且CS的附加灵活性允许您在复制FS之后扩展其功能。
“由于它绕过了上面的整个图形流水线和下面的ROP,因此从理论上讲它可以更快。”图形操作的成本将是该操作最慢阶段的成本。顶点处理将涉及很少的顶点,并且ROP操作与所有FS处理无关紧要。因此,整体性能将由最慢的步骤控制:片段着色器。也许如果您的FS很小,您可能会从CS中获得一些性能优势,但总的来说,没有。不用于延迟渲染。

“ CS比FS具有更多的表现力”表现力对于延迟渲染器的特定情况而言并不重要。共享的数据存储和调用相互通信是无关紧要的,因为延迟的灯光不需要与其他调用相互通信(SSAO,模糊效果之类的后效应并不是特定于延迟渲染的,因此不在此处计算)。同样,对工作组规模的控制也不重要。因此,无论表现力如何,都是无关紧要的。

@NicolBolas您正在转移球门柱并且不屑一顾。而且,尽管我确实同意您所说的许多话,但前提是对它们的措辞更谨慎,并作为一般启发法,但问题不在于此。我不在乎它,这也不是评论部分所需要的。 。 。

#1 楼

经过更多分析,这里的TL; DR是,是的,速度下降是由于内存局部性造成的,是的,像素顺序是问题。更有趣的是,通过以不同的方式编写着色器,我们可以大大超过片段着色器的性能-尽管显然我们不应该依赖能够定期执行此操作。弄清楚GPU中发生了什么的最好方法是询问它。在这种情况下,相关工具是NVIDIA NSight。经过一番摆弄之后,我得到了直接可比的结果,表明在这两种情况下,内存都是瓶颈,而在计算着色器的情况下,情况更糟。参见上文),并且可以通过更改阴影代码从等式中删除内存来实现(略好于)的性能,我们可以确信像素的阴影顺序应该受到指责。 >也许我们可以找到更好的阴影顺序?
扰流板警报:我们可以。经过一些实验后,考虑使用一个新的着色器,其中有一个全局的图块队列,每个扭曲都将抓取一个图块并按扫描线顺序对其中的像素进行着色。事实证明,这比片段着色器快50%!被嵌入,如果您在阅读文本时遇到困难,可以这样做。)
总结了这些实验的结果,以及每个实验的性能数字以及我所猜想的可视化内容场景(简化:仅显示一个扭曲,其宽度为8,并且隐藏的延迟未显示)。
在左侧,有片段着色器,标记为“ Vendor Magic Goes Here”。我们不知道供应商为他们的片段着色器像素遍历顺序做什么(尽管我们可以通过写出原子变量等来获得提示),但是总体上它确实很好。在中间,我们有我描述的原始计算着色器(param = 0),它将帧缓冲区分为矩形工作组。请注意,工作组可能以最合理的顺序执行,以完全减轻这些缓存的影响,但并不能保证完全按照任何顺序执行-的确不会由于延迟隐藏而造成的:这解释了工作组为什么要走过去帧缓冲区以一种基本连贯的方式,但是仍然略有跳跃。这是片段着色器速度的一半,我相信可能的跳过是对配置文件中显示的其他内存不一致性的合理的初步猜测。
最后,我们有了tile版本。由于切片是在切片队列(由全局计数器定义,在切片上方可视化)中进行处理的,因此对切片和像素的处理更加有序(忽略了延迟隐藏和其他线程组)。我相信这是一个合理的开始猜测,为什么这个结果比片段着色器快50%。
要强调的是,尽管这些结果对于这个特定的实验是正确的,但是使用这些特定的驱动,这些结果并不一定能一概而论。这可能特定于此特定的场景,视图和平台配置,并且此行为实际上甚至可能是错误。这绝对是一件有趣的事情,但不要因为一个狭窄定义的实验中的一个数据点而撕裂渲染器(仅)。确实,整个调查的开始是自上一次在2018年进行分析以来,(更复杂的)计算着色器的性能在相对性能方面有所下降,并且在同一硬件上使用相同的代码。唯一的区别是更新了驱动程序。
该课程很简单:像素着色顺序很困难,并且最好由GPU供应商确定。计算着色器使我们可以选择执行类似着色的操作,但是我们不应期望能够可靠地超过片段着色器的性能(即使偶尔,我们可以做到),因为我们的实现不是基于内部人员如何可以针对特定的GPU进行优化-即使在只有一个特定的GPU的情况下。 。使用计算着色器的主要原因是您是否想要方便或灵活。当然,如果您全面剖析并看到性能提升,并且有理由期望您在其之上构建的GPU基础架构不会落在脚下(例如,您正在编写控制台),那么也许可以使用计算着色器是正确的选择。

评论


$ \ begingroup $
非常好的实验!您如何将CS更改为图块/组渲染?这到底是什么意思呢?只是团体人数吗?
$ \ endgroup $
–托马斯
20年7月17日在12:32

$ \ begingroup $
@Thomas有关可视化的信息,请参见右侧的图。红色的方框代表各个线程组。
$ \ endgroup $
–imallett
20 Jul 17 '19:52