HLSL分支避免

Dar*_*man 7 optimization shader branch hlsl

我有一个着色器,我想在顶点着色器中移动一半顶点.我试图从性能的角度来决定最好的方法,因为我们处理的是超过100,000个顶点,因此速度至关重要.我看了3种不同的方法:(伪代码,但足以给你这个想法.<complex formula>我不能给出,但我可以说它涉及一个sin()函数,以及一个函数调用(只是返回一个)数字,但仍然是函数调用),以及浮点数的一堆基本算术).

if (y < 0.5)
{
    x += <complex formula>;
}
Run Code Online (Sandbox Code Playgroud)

这样做的优点<complex formula>是只执行了一半的时间,但缺点是肯定会导致分支,实际上可能比公式慢.它是最具可读性的,但在这种情况下我们更关心速度而不是可读性.

x += step(y, 0.5) * <complex formula>;
Run Code Online (Sandbox Code Playgroud)

使用HLSL的step()函数(如果第一个参数更大则返回0,如果更少则返回1),可以消除分支,但现在<complex formula>每次都调用它,并且其结果乘以0(因此浪费了精力)一半的时间.

x += (y < 0.5) ? <complex formula> : 0;
Run Code Online (Sandbox Code Playgroud)

这个我不知道.是否?:会导致一个分支?如果不是,那么等式的两边都要进行评估,还是只考虑相关的一方?

最后的可能性是<complex formula>可以将其卸载回CPU而不是GPU,但我担心在计算sin()和其他操作时会更慢,这可能会导致净损失.此外,这意味着必须将另外一个数字传递给着色器,这也可能导致开销.任何人都有任何见解,哪个是最好的行动方案?


附录:

根据http://msdn.microsoft.com/en-us/library/windows/desktop/bb509665%28v=vs.85%29.aspx

step()函数在?:内部使用,所以它可能并不比我的第三个解决方案更好,并且可能更糟,因为<complex formula>每次都会被调用,而它可能只用一半时间调用?:.(目前还没有人回答这部分问题.)虽然避免使用和使用:

x += (1.0 - y) * <complex formula>;
Run Code Online (Sandbox Code Playgroud)

可能比其中任何一个都好,因为在任何地方都没有比较.(并且y始终为0或1.)仍然执行<complex formula>不必要的一半时间,但可能值得完全避免分支.

And*_*ell 7

或许看看这个答案.

我的猜测(这是一个性能问题:测量它!)是你最好保留if声明.

原因一:理论上(如果正确调用),着色器编译器应该足够聪明,以便step在编译if语句时在分支指令和类似函数之间做出最佳选择.改进它的唯一方法是剖析[1].请注意,它可能在此粒度级别依赖于硬件.

[1]或者如果您对如何布置数据有具体的了解,请继续阅读......

第二个原因是着色器单元的工作方式:如果单元中的一个片段或顶点对其他分支或顶点采用不同的分支,则着色器单元必须采用两个分支.但如果他们都采用相同的分支 - 另一个分支被忽略.因此,虽然它是单位的,而不是每个顶点 - 但仍然可以跳过昂贵的分支.

对于片段,着色器单元具有屏幕上的位置 - 这意味着您可以获得最佳性能,附近像素组都采用相同的分支(请参阅我的链接答案中的插图).说实话,我不知道如何将顶点分组为单位 - 但如果您的数据被适当地分组 - 您应该获得所需的性能优势.

最后:值得指出的是<complex formula>- 如果你说你可以手动将它从你的HLSL中提升出来 - 它可能会被提升到基于CPU的预着色器中(至少在PC上,从内存Xbox 360开始)不支持这个,不知道PS3).您可以通过反编译着色器来检查这一点.如果它是你需要每次绘制一次计算的东西(而不是每顶点/片段),那么在CPU上执行它可能最好的.