概述
阅读完本文,你将了解 Metal 是如何在 GPU 上执行命令的。
让 GPU 来执行任务是通过发送命令来实现的。 该命令可以执行绘图、并行计算或资源管理相关的操作工作。
Metal 应用程序和 GPU 之间的关系是客户端-服务器模式:
- Metal 应用程序是客户端
- GPU 是服务器
- 可以通过向 GPU 发送命令来发出请求
- 处理完命令后,GPU 通知应用空闲状态
下图为 Metal 客户端-服务器模式
要将命令发送到 GPU,可以使用命令编码器对象将它们添加到命令缓冲区。
将命令缓冲区添加到命令队列,然后提交命令缓冲区来让 Metal 执行命令缓冲区的命令。
入队和提交命令缓冲区中放置命令的顺序很重要,因为它会影响 Metal 执行命令的顺序。
以下部分涵盖了设置工作命令结构的步骤,这些步骤按照创建与 Metal 交互的对象的方式进来组织。
生成初始化时的对象
在初始化时创建的一些 Metal 对象,通常会无限期地保留它们。创建命令队列和管道对象的成本很高,只需要初始化一次,后续就可以复用它们。
创建命令队列
通过调用 makeCommandQueue() 函数来创建命令队列:
swift commandQueue = device.makeCommandQueue()
需要创建一个强引用对象来指向命令队列,命令队列是用来保存命令缓冲区的,如下所示:
创建一个或多个管道对象
管道对象封装了 Metal 着色语言编写的函数,告诉 Metal 如何处理命令。
以下是管道处理 Metal 工作流程:
- 编写处理数据的 Metal 着色器函数
- 创建一个包含着色器的管道对象
- 启用管道
- 绘制、计算或 blit 调用
Metal 不会立即执行绘制、计算或 blit 调用;相反,可以使用编码器对象将这些调用的命令封装起来,然后插入到命令缓冲区中。提交命令缓冲区后,Metal 将其发送到 GPU 并使用激活的管道对象来处理命令。
向 GPU 发出命令
准备好命令队列和管道后,就可以向 GPU 发出命令了。具体流程如下:
- 创建命令缓冲区
- 用命令填充缓冲区
- 将命令缓冲区提交给 GPU
如果将动画作为渲染循环的一部分执行,则对动画的每一帧都执行此操作。还可以按照此过程执行一次性图像处理或机器学习任务。
创建命令缓冲区
在命令队列上调用 makeCommandBuffer() 来创建命令缓冲区。
swift guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }
对于单线程应用程序,可以创建一个命令缓冲区。如下显示了命令与其命令缓冲区之间的关系:
将命令添加到命令缓冲区
当在编码器对象上调用特定于任务的函数时(例如绘制、计算或 blit 操作),编码器会将这些调用的命令放入命令缓冲区。编码器对命令进行编码以包含 GPU 在运行时处理任务所需的一切。如下显示了命令编码器将命令插入命令缓冲区作为渲染结果工作流程:
具体取决于不同的任务 可以使用 MTLCommandEncoder 的具体子类对实际命令进行编码: - 使用 MTLRenderCommandEncoder 发出渲染命令 - 使用 MTLComputeCommandEncoder 发出并行计算命令 - 使用 MTLBlitCommandEncoder 发出资源管理命令
提交渲染缓冲区
为了运行命令,需要将命令缓冲区提交给 GPU:
swift commandBuffer.commit()
提交命令缓冲区不会立即运行其命令。相反,Metal 按照队列中优先级来处理缓冲区中的命令。如果没有显示地将命令缓冲区入队,一旦提交到缓冲区,Metal 就会执行此操作。
Metal 所遵循的规则是命令执行的顺序与添加它们的顺序相同。虽然 Metal 可能会在处理某些命令之前对它们进行重新排序,但这通常只发生在性能提升且没有其他可影响的情况下。
总结
本文介绍了 Metal 在 GPU 上执行命令的流程。Metal 执行任务是通过发送 GPU 命令来完成的,它们之间的关系是客户端-服务器模式。执行的命令需要添加到命令缓冲区中,命令缓冲区再加入到命令队列中,最后按照顺序依次提交到 GPU 来处理。