着色程序(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
对象。
在 MTLLibrary
中 MTLFunction
对象的创建方法有如下两种:
- 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 使用 MTLDevice
的 newLibraryWithFile:error:
方法,通过一个库文件的全路径载入该库文件,然后使用库文件的内容创建一个包含一个或者多个 MTLFunction
对象的 MTLLibrary
对象。载入过程中如果有任何错误,将被记录在 error
中。然后 MTLLibrary
的 newFunctionWithName:
方法创建一个 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 State 和 Creating 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
属性是相关的。
- 如果
type
是MTLArgumentTypeTexture
,那么textureType
属性指示纹理类型 (比如着色语言中的texture1d_array
,texture2d_ms
,和texturecube
类型),textureDataType
属性指示其分量数据类型 (比如half
,float
,int
,uint
). - 如果
type
是MTLArgumentTypeThreadgroupMemory
,那么threadgroupMemoryAlignment
属性和threadgroupMemoryDataSize
属性相关。 - 如果
type
是MTLArgumentTypeBuffer
,那么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
}
}