着色程序(Functions)和库(Libraries)

本章描述如何创建一个表示 Metal 渲染着色程序或是并行计算着色程序的 MTLFunction 对象,以及如何在 MTLLibrary 中组织和访问 MTLFunction 对象。

MTLFunction 表示一个渲染着色程序或是并行计算着色程序

MTLFunction 对象表示一个着色程序,一个着色程序是用 Metal 着色语言编写的,它作为图形渲染或者数据并行计算全程的一部分,在 GPU 中执行。 更多关于 Metal 着色语言,详见 Metal Shading Language Guide.

为了在 Metal 运行时和用 Metal 着色语言编写的图形着色程序、并行数据计算着色程序直接传递数据和状态,纹理、缓存和采样器被赋予参数索引。这个索引指定了哪个纹理、缓存或是采样器被 Metal 运行时和 Metal 着色代码引用。

对于一个渲染 pass,可以在 MTLRenderPipelineDescriptor 对象中用 MTLFunction 类型的对象设定一个顶点或是片段着色器。详见 Creating a Render Pipeline State. 对于一个数据计算 pass,可以在目标设备创建 MTLComputePipelineState 对象时,设定 MTLFunction 对象。详见 Specify a Compute State and Resources for a Compute Command Encoder.

Library是着色程序资源库

一个 MTLLibrary 对象代表了一个或是多个 MTLFunction 对象的资源库。一个单独的 MTLFunction 对象代表了一个使用 Metal 着色语言编写的 Metal 着色程序。在着色程序源代码中,所有使用了 Metal 函数修饰符的函数 (vertex, fragment,或 kernel) 可以在 MTLLibrary 中被表示为一个 MTLFunction 对象。而一个没有使用 Metal 函数修饰符的函数,尽管在着色程序中可以被其他函数调用,但是不能被直接表示为一个 MTLFunction 对象。

MTLLibraryMTLFunction 对象的创建方法有如下两种:

  • Metal 着色语言源代码在 App 的编译阶段就被编译到二进制库中。
  • 一个包含了 Metal 着色语言源代码的字符串在 App 运行时被编译。

从已编译的代码中创建 Library

为了提高性能,可以利用 Xcode 在 App 创建过程中就把 Metal 着色语言源代码编译打包到一个库文件中,如此避免了在 App 运行时编译着色语言代码带来的开销。从已编译的二进制库中创建一个 MTLLibrary 对象可以使用如下的 MTLDevice 方法:

  • newDefaultLibrary ,该方法返回一个 library 对象,它包含一个 App 工程中所有的着色程序。
  • newLibraryWithFile:error: ,该方法指定了一个库文件路径,它将返回一个 MTLLibrary 对象,包含这个库文件中所有的着色程序。
  • newLibraryWithData:error: ,该方法指定了一个二进制数据块对象,它将返回一个 MTLLibrary 对象,包含这个数据块中所有的着色程序。

更多关于创建过程编译 Metal 着色语言源代码,详见 Creating Libraries During the App Build Process.

从源代码中创建 Library

从包含有 Metal 着色语言源代码编写的着色程序的字符串中创建 MTLLibrary 对象,可以通过调用如下的 MTLDevice 方法。这些方法将在 MTLLibrary 对象创建时编译源代码。可以设置 MTLCompileOptions 对象的属性值来指定编译选项:

  • newLibraryWithSource:options:error: ,该方法是同步调用的,它编译输入的字符串里的源代码来创建 MTLFunction 对象,最后返回一个包含这些 MTLFunction 对象的 MTLLibrary 对象。
  • newLibraryWithSource:options:completionHandler: ,该方法是异步调用的,它也是编译输入的字符串里的源代码来创建 MTLFunction 对象,最后返回一个包含这些 MTLFunction 对象的 MTLLibrary 对象。参数 completionHandler 是一个 block,它将在 MTLLibrary 对象创建完成后被调用。

从 Library 中获取着色程序

MTLLibrary 的 newFunctionWithName: 方法返回一个名字为输入参数的 MTLFunction 对象。如果在library 中没有找到一个名字与输入参数匹配的方法,那么该函数返回 nil

列表 4-1 使用 MTLDevicenewLibraryWithFile:error: 方法,通过一个库文件的全路径载入该库文件,然后使用库文件的内容创建一个包含一个或者多个 MTLFunction 对象的 MTLLibrary 对象。载入过程中如果有任何错误,将被记录在 error 中。然后 MTLLibrarynewFunctionWithName: 方法创建一个 MTLFunction 对象,它代表了源代码中被命名为 my_func 的着色程序。最后 myFunc 对象将可以被应用在 App 中。

列表 4-1 从 Library 中获取着色程序

NSError *errors;
id <MTLLibrary> library = [device newLibraryWithFile:@"myarchive.metallib"
                          error:&errors];
id <MTLFunction> myFunc = [library newFunctionWithName:@"my_func"];

在运行时决定着色程序的细节

一个 MTLFunction 的实质内容被定义为图形渲染着色程序或是并行计算着色程序,(着色程序)在这个对象创建前就被编译好了,着色程序的源代码不能直接被 App 直接使用。你可以在运行时查询下面的 MTLFunction 属性:

  • name, 是一个表示着色程序名字的字符串。
  • functionType, 这个属性说明这个着色程序的类型是顶点着色器、片段着色器,还是并行计算着色器。
  • vertexAttributes, 一个由 MTLVertexAttribute 对象组成的数组,它描述顶点属性数据在内存中如何组织以及如何映射到顶点着色程序参数中。详见 Vertex Descriptor for Data Organization.

MTLFunction 不提供访问着色程序参数的方法。在管道(pipeline) state 对象创建过程中,可以获得一个 reflection 对象(根据 command encoder 的类型不同,reflection 对象可以是MTLRenderPipelineReflection 类型或是MTLComputePipelineReflection 类型),它用于展示着色程序的参数细节。更多关于创建管道 state 和 reflection 对象,详见 Creating a Render Pipeline StateCreating a Compute Pipeline State. 如果不使用 reflection 数据,请不要创建这种对象。

一个 reflection 对象包含一个 MTLArgument 对象的数组,视其关联的 Encoder 不同而不同。对于 MTLComputeCommandEncoder, MTLComputePipelineReflection 类型的 reflection 对象的 arguments 属性还有一个 MTLArgument 数组,该数组和并行计算着色程序的参数相关联。对于 MTLRenderCommandEncoder, MTLRenderPipelineReflection 类的的 reflection 对象有两个属性, vertexArguments and fragmentArguments,分别对应顶点着色程序的输入参数和片段着色程序的输入参数(数组类型)。

不是所有的着色程序的参数都在 reflection 对象中表示,一个 reflection 对象只包含那些引用了相应资源的参数,通过修饰符 [[ stage_in ]][[ vertex_id ]][[ attribute_id ]] 修饰的参数不会被包含。

列表 4-2 展示了如何获取 reflection 对象 (在这个例子中,它是 MTLComputePipelineReflection 类型的) ,然后遍历它的 arguments 属性中的 MTLArgument 对象。

Listing 4-2 遍历着色程序参数

MTLComputePipelineReflection* reflection;
id <MTLComputePipelineState> computePS = [device
              newComputePipelineStateWithFunction:func
              options:MTLPipelineOptionArgumentInfo
              reflection:&reflection error:&error];
for (MTLArgument *arg in reflection.arguments) {
    //  process each MTLArgument
}

MTLArgument 类型对象表示一个着色语言方法入参的细节:

  • name 参数的名字。
  • active 一个 Boolean,指示这个参数是否可以被忽略。
  • index 表示该参数在其对应的参数索引表中的下标位置(0-based)。比如,着色程序中的 [[ buffer(2) ]] ,对应的 index 是 2。
  • access 描述了访问限制,比如读写访问权限。
  • type 表示着色语言的修饰符。比如 [[ buffer(n) ]], [[ texture(n) ]], [[ sampler(n) ]], 或者[[ threadgroup(n) ]].

type 还决定了哪些 MTLArgument 属性是相关的。

  • 如果 typeMTLArgumentTypeTexture,那么 textureType 属性指示纹理类型 (比如着色语言中的 texture1d_array,texture2d_ms,和 texturecube 类型), textureDataType 属性指示其分量数据类型 (比如 half, float, int, uint).
  • 如果 typeMTLArgumentTypeThreadgroupMemory,那么 threadgroupMemoryAlignment 属性和 threadgroupMemoryDataSize 属性相关。
  • 如果 typeMTLArgumentTypeBuffer,那么 bufferAlignment, bufferDataSize, bufferDataType,和 bufferStructType 这几个属性相关。

如果缓存参数是一个结构体, (比如, bufferDataType 属性的值是 MTLDataTypeStruct),那么 bufferStructType 属性包含一个 MTLStructType 类型的值,它表示这个结构体,同时 bufferDataSize 属性表示这个结构体的长度(单位是 byte)。如果缓存参数是一个数组(或者一个指向数组的指针),那么 bufferDataType 属性表示数组中元素的数据类型,bufferDataSize 属性表示数组中一个元素的长度(单位是 byte)。

列表 4-3 的代码深入探讨了如何检查一个 MTLStructType 对象中 members 属性的细节(members 属性是一个 MTLStructMember 类型的数组)。members 数组中的元素,可能是一个简单类型值,也可能是一个数组,还可能是一个嵌套结构体。如果元素是一个嵌套结构体,调用它的 structType 方法,来获取一个 MTLStructType 对象,该对象表示一个子结构体,接下去可以递归展开分析。如果元素是一个数组,调用它的 arrayType 方法,获取一个 MTLArrayType 对象,然后检查这个对象的 elementType 属性。如果 elementType 属性是一个 MTLDataTypeStruct 类型值,调用它的 elementStructType 方法,可以获取到结构体,然后继续往下分析。如果 elementType 属性是一个 MTLDataTypeArray 类型值,调用它的 elementArrayType 方法,可以获取到一个子数组,然后继续往下分析。

列表 4-3 处理结构体参数

MTLStructType *structObj = [arg.bufferStructType];
for (MTLStructMember *member in structObj.members) {
    //  process each MTLStructMember
    if (member.dataType == MTLDataTypeStruct) {
       MTLStructType *nestedStruct = member.structType;
       // recursively drill down into the nested struct
    }
    else if (member.dataType == MTLDataTypeArray) {
       MTLStructType *memberArray = member.arrayType;
       // examine the elementType and drill down, if necessary
    }
    else {
       // member is neither struct nor array
       // analyze it; no need to drill down further
    }
}

results matching ""

    No results matching ""