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

What is the right approach to creating an UberShader using function_constants in Metal?

我刚刚在 WWDC 2016 的 "What's new in Metal" 视频中了解到 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;
}

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

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

是的,您走对了。 ( 但要稍微扩展一下他的回答...)

您的示例与 Apple 在 WWDC16 session 介绍函数常量中展示的示例基本相同:您的 "branches" 都是直接从函数常量值派生的,这意味着着色器编译器可以(当你构建你的应用程序时)为通过你的代码的每个可能路径生成 IR 变体,这些路径取决于函数常量值。

在这里,您将 int 传递给着色器,但这并不意味着它必须编译 232 个着色器变体 — 编译器可以做到一些静态分析,发现有四种可能的代码路径基于该值(0、1、2 和其他任何东西,其中最后一个只是完全省略了 if 语句和 returns frgOut).

在 运行 时,Metal 框架根据您为常量传递的值确定将四个着色器中的哪一个发送到 GPU,因此在着色器/GPU 上没有分支。例如,如果您传递 1 的值,则您 运行 正在使用一个基本上如下所示的着色器:

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

如您所见,该着色器中没有分支。