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

m3v*_*rik 4 shader ios metal

我刚从WWDC 2016的"金属中有什么新东西"视频中了解到function_constants,并且很多次提到UberShaders.我想创建一个片段超级着色器,可以用于不同类型的传递,如simplePassThrough,defferred等.以下是我想要使用它的方法.

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;
}
Run Code Online (Sandbox Code Playgroud)

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

war*_*enm 6

这是函数常量的合法用例,在运行时没有分支成本.这是因为编译器将消除它确定永远不能执行的代码(例如,因为它等同于if(false) { ... }).


ric*_*ter 6

是的,你走在正确的轨道上.(正如@warrenm已经注意到的那样.但是要扩大他的答案......)

您的示例与介绍函数常量的WWDC16会话中的Apple显示基本相同:您的"分支"都直接从函数常量值派生,这意味着着色器编译器可以(当您构建应用程序时)生成IR变体代码中依赖于函数常量值的每条可能路径.

在这里,你要传递int到着色器,但这并不意味着它必须编译2 32个着色器变体 - 编译器可以进行一些静态分析,并看到有四个可能的代码路径基于该值(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;
}
Run Code Online (Sandbox Code Playgroud)

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