If语句会减慢我的着色器吗?

Tho*_*mas 53 opengl shader direct3d glsl hlsl

我想知道着色器内部的"If-statements"(顶点/片段/像素......)是否真的会降低着色器性能.例如:

使用它更好吗:

vec3 output;
output = input*enable + input2*(1-enable);
Run Code Online (Sandbox Code Playgroud)

而不是使用这个:

vec3 output;
if(enable == 1)
{
    output = input;
}
else
{
    output = input2;
}
Run Code Online (Sandbox Code Playgroud)

在另一个论坛上有一个关于那个的讨论(2013):http://answers.unity3d.com/questions/442688/shader-if-else-performance.html 这里有人说,If语句真的很糟糕用于着色器的性能.

此外,他们还在讨论if/else语句(2012)中的内容:https: //www.opengl.org/discussion_boards/showthread.php/177762-Performance-alternative-for-if-(-)

也许硬件或着色器编译器现在更好,他们修复了这个(可能不存在)性能问题.

编辑:

在这种情况下,这里假设enable是一个统一变量,它总是设置为0:

if(enable == 1) //never happens
{
    output = vec4(0,0,0,0);
}
else  //always happens
{
    output = calcPhong(normal, lightDir);
}
Run Code Online (Sandbox Code Playgroud)

我想在这里我们在着色器中有一个分支,它会降低着色器的速度.那是对的吗?

制作2个不同的阴影是否更有意义,比如一个用于其他阴影,另一个用于if部分?

Nic*_*las 111

什么是着色器甚至可能导致if语句性能问题?它与着色器的执行方式以及GPU从中获得巨大的计算性能有关.

单独的着色器调用通常按顺序执行,同时执行相同的指令.他们只是在不同的输入值集上执行它们; 他们分享制服,但他们有不同的内部变量.所有执行相同操作序列的着色器组的一个术语是"波前".

任何形式的条件分支的潜在问题是它可以搞砸所有这些.它导致波前的不同调用必须执行不同的代码序列.这是一个非常昂贵的过程,必须创建一个新的wavefront,将数据复制到它上面等等.

除非......它没有.

例如,如果条件是wavefront中每次调用所采用的条件,则不需要运行时分歧.因此,成本if仅仅是检查条件的成本.

所以,假设你有一个条件分支,让我们假设wavefront中的所有调用都将采用相同的分支.在这种情况下,表达式的性质有三种可能性:

  • 编译时静态.条件表达式完全基于编译时常量.这样,你知道从查看代码并知道将采取哪些分支.几乎所有编译器都将此作为基本优化的一部分来处理.
  • 静态均匀分支.该条件基于涉及在编译时已知的事物的表达式(具体地,常量和uniform值).但是在编译时不会知道表达式的.因此,编译器可以静态地确定波前永远不会被此打破if,但编译器无法知道将采用哪个分支.
  • 动态分支.条件表达式包含常量和制服以外的术语.在这里,编译器无法判断波前是否会被分解.是否需要发生取决于条件表达式的运行时评估.

不同的硬件可以处理不同的分支类型而不会发散.

此外,即使不同波前采用条件,编译器也可以重新构造代码以不需要实际的分支.你给出了一个很好的例子:output = input*enable + input2*(1-enable);在功能上等同于if语句.编译器可以检测到if正在使用a来设置变量,从而执行双方.这通常是针对树枝体很小的动态条件的情况.

几乎所有硬件都可以处理var = bool ? val1 : val2而不必分叉.这可能是在2002年.

由于这非常依赖于硬件,因此......取决于硬件.然而,可以看到某些硬件时代:

桌面,预先D3D10

在那里,它有点狂野的西部.NVIDIA针对此类硬件的编译器因检测此类情况而臭名昭着,并且每当您更改影响此类情况的制服时,实际上都会重新编译着色器.

一般来说,这个时代约有80%的"从不使用if陈述"来自.但即使在这里,也不一定如此.

您可以期待静态分支的优化.您可以希望静态统一分支不会导致任何额外的减速(尽管NVIDIA认为重新编译比执行它更快的事实使得它至少对于他们的硬件来说不太可能).但是动态分支会花费你一些东西,即使所有的调用都采用相同的分支.

这个时代的编译器尽力优化着色器,以便简单地执行简单的条件.例如,你output = input*enable + input2*(1-enable);的东西是一个体面的编译器可以从你的等效if语句中生成的东西.

桌面,后D3D10

这个时代的硬件通常能够处理静态统一的分支语句而几乎没有减速.对于动态分支,您可能会或可能不会遇到减速.

桌面,D3D11 +

这个时代的硬件几乎可以保证能够处理动态均匀的条件而几乎没有性能问题.实际上,它甚至不必是动态统一的; 只要同一波前的所有调用都采用相同的路径,您就不会看到任何重大的性能损失.

请注意,上一个时代的某些硬件也可能会这样做.但这是几乎肯定是真实的.

移动版,ES 2.0

欢迎回到狂野的西部.虽然不像Pre-D3D10桌面,但这主要是由于ES 2.0口径硬件的巨大差异.有如此多的东西可以处理ES 2.0,它们的工作方式各不相同.

静态分支可能会得到优化.但是,从静态统一分支中获得良好性能是否与硬件有关.

移动,ES 3.0+

这里的硬件比ES 2.0更加成熟和强大.因此,您可以期望静态统一分支执行得相当好.一些硬件可能像现代桌面硬件那样处理动态分支.

  • @ Tenfour04是的,它们使用专用指令或条件赋值指令,如上所述,它们不会引起差异。作为附带说明,我认为没有充分的理由使用步进功能。您总是可以用更直观的条件赋值代替它:`step(a,b)* c->(a> = b)?c:0` (2认同)
  • @Andrew:“*WebGL 怎么样?*”怎么样?我们谈论的是*硬件*的行为,而不是软件。界面没那么重要;重要的是底层硬件如何反应。我只提到 API 版本,因为某些硬件支持的最高 API 版本是对其功能的粗略估计。 (2认同)

246*_*tNt 7

它高度依赖于硬件和条件.

如果你的条件是统一的:不要打扰,让编译器处理它.如果你的条件是动态的(比如从属性计算的值或从纹理或某物中获取的值),那么它就更复杂了.

对于后一种情况,您几乎必须进行测试和基准测试,因为它取决于每个分支中代码的复杂性以及分支决策的"一致性".

例如,如果其中一个分支占据了99%的情况并丢弃了该片段,那么很可能您希望保留条件.但是在上面的简单例子中OTOH如果enable是一些动态条件,算术选择可能会更好.

除非你有一个像上面这样的明确案例,或者除非你针对一个固定的已知架构进行优化,否则你可能会更好地完成编译器的数据.