Functions and Libraries

  • MTLFunction Represents a Shader or Compute Function
  • A Library Is a Repository of Functions
  • Determining Function Details at Runtime

  • 本章介绍如何创建一个 MTLFunction object对应一个metal shader或者compute function,以及如何通过 MTLLibrary object 来组织和访问function

    MTLFunction Represents a Shader or Compute Function

    MTLFunction object代表着一个用metal shading language编写的函数,作为graphics or compute pipeline的一部分执行在 GPU 上。

    为了在metal runtime和graphic/compute function中传输数据,需要给texture、buffer、samplers assign一个argument index。argument index表明metal runtime和metal shading code用到的texture、buffer、sampler

    在渲染pass中,MTLRenderPipelineDescriptor 使用 MTLFunction 来指明 vertex、fragment shader。在compute pass中,MTLComputePipelineState 需要指定一个 MTLFunction。

    A Library Is a Repository of Functions

    一个 MTLLibrary object 对应一个或者多个 MTLFunction。一个 MTLFunction对应一个用shading language编写的metal function。使用Metal function qualifier(vertex、fragment、kernel)的函数,都可以对应library中的一个MTLFunction。不含这些qualifier的函数不能直接对应 MTLFunction,但可以被shader中的其它函数调用

    library 中的MTLFunction可以通过以下source创建:

    • 在app编译阶段,把metal shading language code编译成binary library
    • 在app运行阶段,编译的包含metal shading language source code的text string。

    Creating a Library from Compiled Code

    为了更好的性能,建议app在xcode build阶段,将metal shading language编译成library,这样就避免了运行时编译function source。调用MTLDevice的以下方法,通过library binary创建一个MTLLibrary。

    • newDefaultLibrary 创建一个library 对应包含了app xcode project中所有shader和compute function的main bundle。(xcode project中的所有.metal文件都被编译和build进一个默认的library)
    • newLibraryWithFile:error: 根据patch指定一个library file,用于创建一个MTLLibrary包含该library file中所有的function
    • newLibraryWithData:error: 传入包含一个library中的functions对应的binary blob,用于创建以恶搞MTLLibrary

    Creating a Library from Source Code

    通过 MTLDevice 的下列函数,根据一个可能包含多个function的metal shading language source code string创建一个 MTLLibrary。在创建library的时候会编译source code。设置 MTLCompileOptions的属性来指定编译选项。

    • newLibraryWithSource:options:error: 同步编译 source code来创建 MTLFunction,然后返回一个MTLLibrary
    • newLibraryWithSource:options:completionHandler: 异步编译 source code来创建NTLFunction,然后返回一个MTLLibrary。completionHandler是一个block of code,当创建结束的时候调用

    Getting a Function from a Library

    MTLLibrary 的 newFunctionWithName: 方法会根据name返回一个 MTLFunction。如果在library中没有找到使用 metal shading language function qualifier的 function name,则返回nil

    下面代码将通过 MTLDevice的 newLibraryWithFile:error: 方法根据路径指定一个library,并根据该library的内容创建一个包含一饿或者多个 MTLFunction 的 MTLLibrary。在loading过程中的任何error都将在error参数中返回。然后通过 MTLLibrary的 newFunctionWithName: 方法创建一个 MTLFunction 来表示 source code中 function name为my_func的funtion。然后返回的function my_func 将可以在app中使用

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

    Determining Function Details at Runtime

    由于MTLFunction的实际内容(graphic shader or compute function)可能在MTLFunction被创建之前都已经编译过了,所以app中可能没有其source code。在runtime可以查询 MTLFunction 的如下属性

    • name:一个包含function name的string
    • functionType:表示function 是vertex、fragment、compute function
    • vertexAttributes:一个MTLVertexAttribute数组描述vertex attribute data在内存中的组织方式,以及如何映射到vertex function argument。

    MTLFunction 不提供接口获取function argument。在创建pipeline state的时候可以获取到一个reflection object(MTLComputePipelineReflection/MTLRenderPipelineReflection)表示shader/compute function的function arguments。如果不使用的话,避免获取reflection data。

    一个reflection object包含一个用于command ecoder支持的function type 的 MTLArgument 数组。比如 MTLComputeCommandEncoder 中 MTLComputePipelineReflection 通过 arguments 属性包含一个 MTLArgument数组,对应compute function的参数。MTLRenderCommandEncoder中,MTLRenderPipelineReflection包含2个属性, vertexArguments 和 fragmentArguments ,是两个数组,分别对应 vertex function argument和fragment function argument

    并非所有的function argument都会反映在 reflection object中。reflection object只包含有关联resource的argument,而不包含带有 stage_in 修饰符(VertexDescriptor 以及 vs->ps的varying)或者build-in vertex_id\attribute_id 修饰符的argument

    下面代码描述了如何获取一个relfection object(MTLComputePipelineReflection),以及根据arguments属性获取 MTLArgument

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

    MTLArgument 的属性描述了shading language function argument的detail

    • name 属性为 argument的name
    • active 属性为一个bool值,表示该argument是否能被ignore
    • index 属性对应argument table中0开始的index,比如buffer(2),index为2
    • access 表示access限制,比如read、write access qualifier
    • type 用shading language qualifier表示,比如buffer(n)、texture(n)、sampler(n)、threadgroup(n),type属性决定了 MTLArgument 的其它属性
    • 如果 type 为 MTLArgumentTypeTexture,则 textureType 属性表示 texture type(比如 shading language中的texture1d_array、texture2d_ms、texturecube)。 textureDataType 表示 commponent data type(比如half、float、int)
    • 如果 type 为 MTLArgumentTypeThreadgroupMemory,则关联 threadgroupMemoryAlignment 和 threadgroupMemoryDataSize 属性
    • 如果 type 为 MTLArgumentTypeBuffer,则关联 bufferAlignment、bufferDataSize、bufferDataType 和 bufferStructType 属性

    如果buffer argument是一个结构体(也就是说 bufferDataType 是 MTLDataTypeStruct),则 bufferStructType 属性包含一个 MTLStructType 来表示该struct, bufferDataSize 包含struct的尺寸,单位为bytes。如果buffer argument是一个数组(或者一个指向数组的指针),则 bufferDataType 表示一个元素的数据类型,bufferDataSize 为一个数组元素的尺寸,单位为bytes

    下面例子将深入讲述一个 MTLStructType object 以及其 struct member的detail,每个member 都是一个 MTLStructMember。一个struct member可能是一个simple type、一个数组或者一个嵌套的struct。如果member是一个嵌套的结构体,则通过 MTLStructMember 的 structType 方法来获取一个 MTLStructType object来表示 struct,然后递归分析下去。如果member是一个数组,则使用 MTLStructMember 的 arrayType 方法来获取一个 MTLArrayType 来表示它,然后分析它的 elementType 属性。如果 elementType 是一个 MTLDataTypeStruct,则调用 elementStructType 方法来表示这个结构体,然后继续研究其 member。如果 elementType是一个 MTLDataTypeArray,则调用 elementArrayType 来表示该 subarray,并继续分析它

        	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) {
    		       MTLArrayType *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
    		    }
    		}
        

    本节教程就到此结束,希望大家继续阅读我之后的教程。

    谢谢大家,再见!


    原创技术文章,撰写不易,转载请注明出处:电子设备中的画家|王烁 于 2022 年 4 月 24 日发表,原文链接(http://geekfaner.com/shineengine/blog49_MetalProgrammingGuide_4.html)