alsa的动态库安装在哪里_Metal 动态库 MTLDynamicLibrary 使用指南

本文介绍了iOS 14和macOS 11中Metal动态库的使用,包括MTLLibrary和MTLDynamicLibrary的区别,动态库的创建、加载、运行时链接和查找机制。动态库主要用于Compute Pipeline,不直接提供MTLFunction,而MTLFunction可以通过运行时编译或链接到动态库中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2122718efaf2fce6b8453cc87acc7d86.png

Warning:本文写于 2020 年 11 月 18 日,当您读到时,文中所述情况和 APIs 可能已经发生变动。

Apple 在 WWDC 2020 Build GPU binaries with Metal 中介绍了 iOS 14、macOS 11 新引入的 Metal 动态库特性。此时官方的手册还很不完善,本文的目的是为了帮助读者理清思路,建立概念模型。请按顺序阅读。

适用范围

动态库特性目前是只提供给 Compute Pipeline 使用的,动态库中 包含可用于链接的导出函数,但无法直接通过 API 获取其中的函数对象 (MTLFunction)。Metal 的动态库类似于常说的"动态链接库"。

查询兼容性

此功能是否在设备上支持,需要查询 MTLDevice 上的 supportsDynamicLibraries 属性。官方目前还没有给出这个 feature 的设备支持列表,已知 A13 和其后的芯片能支持。

MTLLibrary

MTLLibrary 不是动态库,但要弄明白动态库,必须先理解 MTLLibrary 。那 MTLLibrary 是什么呢?

在 Xcode 工程中, .metal 后缀的文件被识别为 Metal shader 的源码文件。工程构建时这些文件会被编译成 Apple IR (Intermediate Representation),简称 AIR。抛开 Xcode,假设手工调用 metal 编译器,编译名为 kernel1.metal 的源文件 ,编译目标 iOS 14:

metal -c -target air64-apple-ios14.0 kernel1.metal

(文默认对应SDK的工具链已经在PATH中,如果不在PATH中可以如下调用 )

xcrun -sdk iphoneos metal -c -target air64-apple-ios14.0 kernel1.metal

源文件经过编译,转成了硬件无关的中间语言 AIR (有别于执行在特定硬件上的机器码),然后保存到磁盘上,这里会在当前目录下生成 kernel1.air 。

将多个这样包含了 kernel 的 .air 文件可以打包到一起作为一个库使用:

metal kernel1.air kernel2.air kernel3.air -o MyKernels.metallib

此处我们生成了 MyKernels.metallib。

在 Metal 的编程接口中 MTLLibrary 便对应了此类包含编译成中间语言的可执行函数库。函数库生成到磁盘后,可以调用 newLibraryWithFile:error: 或newLibraryWithURL:error: 将 .metallib 文件加载到内存中。通过 functionNames 查询库中的 public 函数。用名为 newFunctionWith* 的一系列方法,加载 MTLLibrary 中的 MTLFunction。

创建 Compute Pipeline State 所要填写的 MTLComputePipelineDescriptor 中最重要的 computeFunction 就可以通过这样的方式获得。

除了 newLibraryWithFile:error: 和 newLibraryWithURL:error: ,还可以用 newLibraryWithData:error: 直接从二进制数据加载 MTLLibrary 。

Xcode 工程在构建时,会将工程中的 .metal 文件编译放入一个默认库(default library),程序可以调用 newDefaultLibrary 加载当前 App 的默认库。另外可以用 newDefaultLibraryWithBundle:error: 加载某个 bundle 的默认库。

以上提到的 MTLLibrary 创建方式都是加载已经编译好的函数库,但也可以在运行时编译 Metal 的 shader 源码。接口提供了同步和异步两种方式,分别为 newLibraryWithSource:options:error: 以及 newLibraryWithSource:options:completionHandler: 。

总结一下,MTLLibrary 中包含可被管线直接使用的 MTLFunction 以及它们的依赖,并且这些函数以中间语言(Apple IR)的形式存在。

MTLDynamicLibrary

MTLDynamicLibrary 便是 Metal 中的动态库。文章开头有提到,动态库中存放可用于链接的导出函数。换句话说,动态库中的函数都不应是 kernel 函数,因为无法获取 MTLFunction 不能被管线直接使用。但我们的 kernel 函数可以去调用动态库里函数,动态库中的函数也调用其它动态库中的函数。

假设我们现在有两个源文件 utility1.metalutility2.metal 。其中包含了一些可被 kernel 重用的辅助函数。通过命令行:

metal -c -target air64-apple-ios14.0 utility1.metal utility2.metal

可以生成 utility1.air 以及 utility2.air 。随后,

metal -dynamiclib utility1.air utility2.air 
 -o libUtility.metallib 
 -install_name @loader_path/libUtility.metallilb

生成动态库 libUtility.metallib ,命令行中通过 -dynamiclib 告诉编译器,我们要生成的是动态库,而不是一般可执行函数库。 另外还有 -install_name ,稍后再解释。

有动态库后,就要考虑如何使用了。 我们可以调用 newDynamicLibraryWithURL:error: 将动态库加载,加载出来的对象即 MTLDynamicLibrary 。我们加载 MTLDynamicLibrary 有何用呢? 当然是为了链接其中的函数,这就涉及到运行时链接了。

运行时链接

假设 utility1.metal中有一个函数实现 void foo(),此时我们的动态库 libUtility.metallib中就有 foo()函数了 ,有一个 kernel 名叫 k(),想要调用 foo()

因为是运行时编译k(),在调用 newLibraryWithSource:options:error: 或 newLibraryWithSource:options:completionHandler: 时 k() 的源码是以 NSString * 的形式提供的。为了告诉编译器 foo() 实现在别的地方,要在源码定义 k()的之前通过 extern 加函数签名声明一下 foo(), 即

extern void foo();

此外, newLibraryWithSource* 函数还需要一个参数 MTLCompileOptions* ,MTLCompileOption 上新增了一个属性 libraries,

26147dbd7a2640f32e069d74e641ec7a.png

苹果的开发团队惜字如金,连 overview 都懒得写。 我们需要将包含了 foo() 实现的动态库加到这个数组中。 如果 newLibraryWithSource* 成功,我们便可以得到最终管线可以使用的 k()k() 会调用动态库中实现的 foo()

离线链接

本节讲下离线链接,假设你已经生成好了一个动态库 libUtility.metallib ,现在要生成一个包含 kernel 函数 k() 的普通库,k() 会调用到我们动态库中的 foo(),且 k() 的字节码已经保存在 kernel1.air 中。 我们可以:

metal kernel1.air -o MyKernels.metallib -lUtility -L <libUtility.metallib所在目录>

,如同 gcc,此处 -l 指定了要链接的库名(lib省略),-L 指定库的搜索目录。如此一来我们可以得到名为 MyKernels.metallib 的函数库,当我们的 MyKernels.metallib 运行时被加载, Metal 同时也会加载 libUtility.metallib,当 k() 函数被执行时, 就可以调用到动态库中的 foo() 函数。

动态库是如何被找到的

上节讲到,当一个函数调用了动态库中的函数时,Metal 会加载对应的动态库,Metal 如何知道动态库在哪的呢?

这里就需要解释前面看到还没解释的 -install_name 参数,这个参数相当于指定了生成的动态库将来会被放到哪里使用(安装到哪里),动态库可以随便生成在哪,但安装路径需要在生成动态库时指定

以上节的情况为例, MyKernels.metallib 使用了动态库 libUtility.metallibMyKernels.metallib 会在生成阶段,把记录在动态库中的 install_name 保存到自己身上。

熟悉 Mach-O 的同学就会知道,它会被记录在 Load Command 中。不知道也没关系,这并不重要。

MyKernels.metallib加载时,会使用记录下来的依赖库的 install_name,找到依赖库的所在路径。

install_name 可以使用绝对路径,但是我们软件的安装路径可能是不固定的,使用绝对路径非常不灵活。我们可以用特殊名称 @executable_path 和 @loader_path,分别指代前执行程序所在目录和当前 loader 所在目录(在 Metal 的语境下 loader 就是当前正在加载的 metallib,可以是普通库,也可以是动态库,因为动态库也可以调用动态库)。

举例来讲,MyKernels.metallib 放在 /tmp/ 下,如果 libUtility.metallib 的 install_name 是 @loader_path/libUtility.metallib ,那 MyKernels.metallib加载时会在 /tmp/ 目录下找 libUtility.metallib,即 /tmp/libUtility.metallib

运行时生成动态库

前面讲到了通过命令行生成动态库,即增加 -dynamiclib参数。Metal 还支持运行时生成动态库。

生成动态库的步骤,从命令行可以看出,首先要将 .metal 文件编译成 .air, 然后将 .air 文件链接到一起生成 .metallib。

通过代码生成的步骤也类似。

第一步,利用 newLibraryWithSource* 函数生成 MTLLibrary,但参数中的 MTLCompileOptions 需要改两个参数,libraryType 变量要指定 MTLLibraryTypeDynamic 。此外还要指定 installName 。

第二步,将包含 AIR 代码 MTLLibrary 转变成 MTLDynamicLibrary 。这一步通过调用 newDynamicLibrary:error: 实现。在此过程中,会将 MTLLibrary 中的 AIR 编译成当前运行架构的机器码 (Machine Code)。

MTLDynamicLibrary 的可用看作包含 AIR 的 MTLLibrary 加当前架构的 Machine Code。

MTLDynamicLibrary 可以保存起来以便之后使用,调用 serializeToURL:error: 即可。通过 serializeToURL:error: 保存下来的动态库文件中,除了 MTLLibrary 的那部分字节码,还含有当前架构的机器码,如果要想生成包含多个架构的机器码的 .metallib 动态库,则需要利用 metal-lipo 工具

值得注意的是,假设你已经有一个包含多架构机器码的动态库,使用时 Metal 只会加载一套当前架构的机器码,如果没有合适的机器码,则会找到 AIR 并编译成当前架构机器码。由于只加载一份机器码,即使原文件是多架构的,serialize 下来的文件并不是多架构的。

通过命令行工具生成的动态库只包含 AIR 代码,希望未来能开放机器码交叉编译,这样能省去从不同结构设备上拉取 metallib 的步骤 。

metal-lipo

类似 Xcode 提供的 lipo,metal-lipo 可以用来生成 fat 文件,即合并多架构,生成 universal binaries。

metal-lipo -create libUtility-A13.metallib libUtility-A14.metal -o libUtility.metallib

这样我们就得到了同时包含适用成A13、A14芯片机器码的 libUtility.metallib

insertLibraries

MTLComputePipelineState 创建时,需要生成 computeFunction 的机器码,在没有 shader cache 和 MTLBinaryArchive 的情况下,需要完成 AIR 到机器码的转换过程,这个过程就涉及了外部符号(动态库函数)查找、链接,通过设置 MTLComputePipelineDescriptor 的 insertLibraries 属性,我们可以让这个数组中的动态库具有更高查找优先级。

ec704cfac22d29859a17f240e475418f.png

ce21f05a4f0a0992f346974f8663ee38.png

最后

时间仓促,文中疏漏之处请留言指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值