着色器优化:三元运算符是否等同于分支?

sha*_*roz 27 graphics optimization shader

我正在研究一个顶点着色器,我想要有条件地删除一些顶点:

float visible = texture(VisibleTexture, index).x;
if (visible > threshold)
    gl_Vertex.z = 9999; // send out of frustum
Run Code Online (Sandbox Code Playgroud)

我知道当相邻数据之间几乎没有共性时,分支会破坏性能.在这种情况下,每个其他顶点可能会得到一个不同的"可见"值,这对于本地着色器核心集群的性能(从我的理解)来说是不利的.

我的问题:三元运算符是否更好(无论可读性问题)?

float visible = texture(VisibleTexture, index).x;
gl_Vertex.z = (visible > threshold) ? 9999 : gl_Vertex.z;
Run Code Online (Sandbox Code Playgroud)

如果没有,是否将其转换为有价值的计算?

float visible = texture(VisibleTexture, index).x;
visible = sign(visible - threshold) * .5 + .5; // 1=visible, 0=invisible
gl_Vertex.z += 9999 * visible; // original value only for visible
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法来放弃顶点而不依赖于几何着色器?

在此先感谢您的帮助!

Mne*_*cat 10

此数学解法可用于替换条件语句.这也在OpenCL中实现bitselect(condition, falsereturnvalue, truereturnvalue);

int a = in0[i], b = in1[i];
int cmp = a < b; //if TRUE, cmp has all bits 1, if FALSE all bits 0
// & bitwise AND
// | bitwise OR
// ~ flips all bits
out[i] = (a&cmp) | (b&~cmp); //a when TRUE and b when FALSE
Run Code Online (Sandbox Code Playgroud)

但是我不确定在你的情况下实现这个,我不确定我是否完全理解你的代码,但我希望为你提供这个答案会有所帮助,或者其他人.


Olh*_*sky 8

三元运算符只是if语句的语法糖.他们是一样的.

如果您在if语句中有更多内容,可能会有一些优化可以在这里完成,但是如果在任一分支内部都很少,那么就没有什么可以优化的.

通常默认情况下不使用分支.

在您的情况下,三元运算符(或if语句)可能首先评估条件的两侧,然后丢弃条件不满足的分支.

为了使用分支,您需要在着色器代码中设置分支编译器标志,以生成指示GPU实际尝试分支的程序集(如果GPU支持分支).在这种情况下,只有当分支预测器表示某些预定义数量的核心将采用其中一个分支时,GPU才会尝试分支.

您的里程可能因编译器和GPU而异.

  • 三元运算符不仅仅是语法糖.至少在x86上,它是一个流水线优化,有助于分支预测(这将有助于OP).我知道这是GPU而不是CPU,但我觉得值得一提. (8认同)
  • 我不确定 OP 的代码是代表 GLSL 还是 HLSL,但如果所有用户定义的函数都存在,我假设 GLSL(所以为了清楚起见,我添加了 GLSL 标签)。然而,在 HLSL 的情况下,这个答案是错误的。if 语句的条件需要计算为标量,而 ? : 构造可用于标量 ** 和 ** 向量。 (2认同)

sou*_*rce 8

实际上这取决于使用的着色器语言.

  • 在HLSL和Cg中,三元运算符永远不会导致分支.相反,总是评估两种可能的结果,并且丢弃未使用的结果.引用HLSL文档:

    与C中的&&,||和?:的短路评估不同,HLSL表达式永远不会使评估短路,因为它们是向量运算.始终评估表达式的所有方面.

    对于Cg,情况类似,此处三元条件运算符也是向量运算符.(文件):

    与C不同,无论条件如何,都始终执行第二个和第三个操作数中表达式的副作用.

  • 在ESSL和GLSL中,三元运算符总是会导致分支.它不是向量运算符,因此条件必须求值为布尔值.参见GLSL规范:

    它运行在三个表达式(exp1?exp2:exp3)上.此运算符计算第一个表达式,该表达式必须生成标量布尔值.如果结果为true,则选择计算第二个表达式,否则选择计算第三个表达式.仅评估第二和第三表达式中的一个.

    (ESSL的来源)

例如,可以在Khronos WebGL测试站点上获得三元运算符的差异.