使用Metal中的function_constants创建UberShader的正确方法是什么?

5

我刚看了一段 WWDC2016 中的 "Metal 新特性" 视频,学到了关于 function_constants 的知识,并且其中多次提到了 UberShaders。我想创建一个能用于各种通道(如 simplePassThrough、defferred 等)的片元 UberShader。以下是我想要使用它的方式。

constant int passType [[function_constant(0)]];
constant bool simplePassThrough = (passType == 0);
constant bool forwardShading = (passType == 1);
constant bool deferredShading = (passType == 2);

fragment FragmentOutStruct UberFragmentShader()
{
FragmentOutputStruct frgOut;
if (simplePassThrough) {
    // Update frgOut
} else if (forwardShading) {
    // Update frgOut
} else if (deferredShading) {
    // Update frgOut
}
return frgOut;
}

这是正确的方法吗?如果我使用这种方法,我的最终编译MTLFunction是否会看到太多的分支?

2个回答

7

这是使用函数常量的正当用例,并且在运行时不会有分支成本。这是因为编译器将消除它确定永远不会被执行的代码(例如,因为它等同于 if(false) { ... })。


7

是的,你走在正确的路上。(正如@warrenm已经指出的那样。 但是为了更详细地解释一下他的答案...)

你的示例本质上与苹果在WWDC16会议中介绍函数常量时展示的相同:你的“分支”都直接源自函数常量值,这意味着着色器编译器可以(在构建应用程序时)针对取决于函数常量值的代码路径生成IR变体。

在这里,你将一个int传递给着色器,但这并不意味着它必须编译232个着色器变体——编译器可以进行一些静态分析,并看到基于该值有四个可能的代码路径(0、1、2和任何其他值,最后一个只是省略了if语句并返回frgOut)。

运行时,Metal框架根据您为常量传递的值来确定要发送到GPU的四个着色器中的哪一个,因此在着色器/ GPU上没有分支。例如,如果您传递一个值1,则运行的着色器基本上看起来像这样:

fragment FragmentOutStruct UberFragmentShader() {
    FragmentOutputStruct frgOut;
    // Update frgOut per `if (forwardShading)` chunk of original shader source
    return frgOut;
}

正如您所看到的,该着色器中没有分支。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接