MENU

Metal 简介

November 22, 2020 • iOS,Metal阅读设置

2014年,Apple为iOS引入了新的底层GPU编程框架:Metal。一年后,Metal进入了macOS,随后是watchOS和tvOS。 Apple设备有两个可以编程以创建应用程序的“大脑”:中央处理器(CPU)和图形处理器(GPU)。 GPU是专用处理器,可以非常快速,高效地并行执行浮点数学运算。这些任务在CPU上非常昂贵,因为它们无法并行完成,因此已经创建了各种框架和API,以将这些昂贵的任务卸载到最能胜任这些任务的处理器上。

浮点数学是图形编程不可或缺的,但这并不是唯一的应用。非图形GPU编程称为通用GPU(GPGPU)编程。在GPGPU编程可行之前,已开发了较旧的图形范例,例如OpenGL。 OpenGL在iPhone的前5年就足够了,但是随着功能的增强和更多任务的完成,它得到了发展。

图形编程 API 的进化史

在20世纪80年代,编写跨平台软件非常困难,尤其是对于游戏而言。每台计算机和视频游戏系统都有自己专有的硬件和软件驱动程序。每次发生硬件设备更新时,很多软件代码都要重新对设备进行适配。这种重复性的工作对于软件开发来说是一个沉重的负担。

1981年,硅谷图形公司(SGI)成立,它制造了高性能的3D工作站。与其他设备制造商一样,它给自己的硬件平台设计了一套名为 IRIS GL 专有的 3D 图形 API。该公司在 80 年代的大部分时间里占据了市场的主导地位。但是该公司在 90 年代遭到了对手 IBM 、惠普的强力竞争,市场份额开始下滑。为了防止市场份额被对手进一步基站,1992 年它开放了IRIS GL API并将其重命名为OpenGL(详解 OpenGL 历史)。但是 OpenGL 一开始并为成为 3D 图形编程的业界标准 API ,直到一款游戏的流行。

1997 年,idSoftware 公司开发了畅销游戏 Doom 的续集 Quake。 idSoftware 公司的首席程序员 John Carmack 大佬不想给每个平台重写一套 3D 图形适配代码,所以选择 OpenGL 作为游戏底层图像 API 。并且他告诉各个游戏平台和制造商,如果他们设备不支持 OpenGL 的话,Quake 游戏就不会被移植到该平台。厂商们当然不可能放任对手通过游戏吸引走了潜在用户,所有纷纷加入了 OpenGL 支持大军。这样一来,其他的游戏开发商看到大部分设备厂商都支持了 OpenGL ,也纷纷选用 OpenGL 进行开发。一个游戏催生了一个行业标准的诞生,世事就是如此奇妙。

Figure1.1.png

左图是 OpenGL 2.0 版本前渲染的分子图,右图是进行使用着色器增强的版本

2004 年,OpenGL 发布了重大版本更新 2.0。在这次更新中,OpenGL 引入了类 C 的着色语言,该语言运行程序员伟 GPU 编写程序,并且在编程管线中进行图像转换和着色。当着色器添加到OpenGL后,

另外为了应对手机、掌机(PSV)这类嵌入式设备的发展,2003年 OpenGL发布了一个精简的子集版本:OpenGL ES。OpenGL ES 主要工作就是删除那些低效的旧有实现,精简体积。2007年,又升级了着色器和可编程管道发布了 OpenGL ES 2.0。

当第一部 iPhone 在2007年发布后,世界迎来了移动互联网时代。此时 OpenGL ES 理所当然也成为了当时 iOS 平台图像编程的标准 SDK 。当时 iOS 平台还远没有现在成熟,但是开发者已经可以使用 OpenGL ES 进行游戏开发了。虽然后续几年苹果为 iOS 平台上的图形开发了更高级别的抽象:Sprite Kit、Scene Kit。但是如果你想直接对 GPU 编程,你还是必须使用 OpenGL ES。这个时候问题又出现了: OpenGL ES 毕竟是一个跨平台框架,它不能利用 Apple 自身紧密硬件/软件集成带来的优势。所以苹果不得不自研一套自有平台专用的更高效图形编程框架 Metal 。

Metal: 图形编程的新时代

移动互联网发展到现在,我们手上的移动设备的计算能力已经接近甚至超过了许多笔记本电脑的计算能力。但是作为一个跨平台的图形框架 OpenGL 要适应GPU和CPU的所有组合和变化,所以它无法充分利用苹果对其产品的深度集成优势。

除了无用利用深度集成的优势之外,OpenGL还存在一些结构性问题,使其无法发挥最大的效率。OpenGL 中需用费时操作都被安排在每次图像绘制调用时进行,其中一些完全可以避免。Metal 就通过更改操作顺序,将这部分耗时操作提前到绘制发生前进行,这样就释放了更多的处理器带宽。另外,程序员还可以自行控制 GPU 的工程流程,进一步压榨设备的计算潜力。

平衡 CPU 和 GPU 之间的工作

CPU 和 GPU 是相互交替进行工作的,CPU 为 GPU 准备任务指令,当 GPU 处理它接收到命令时,CPU 则继续准备下一批命令。将工作从 CPU 批处理和发送到 GPU 在时间上非常昂贵。发送给 GPU 的工作越多,花费的时间就越长。通常 CPU 无法向 GPU 发送足够的工作以使其在整个帧中保持忙碌状态,因此 GPU 处于空闲状态,如图1.2所示。所以我们需要平衡好 CPU 和 GPU 之间的工作负载,避免 GPU 出现空等的浪费。Metal 提高了这种任务交互的效率,因此 GPU 可以发挥其最大容量。 这种提高效率的一部分是通过消除在 CPU 和 GPU 之间复制内存的需求来实现的。

Figure1.2.png

内存拷贝

OpenGL 只所以需要在 CPU 和 GPU 之间进行内存复制,是因为大部分情况下 CPU 和 GPU 是两个独立的芯片。但是对于现在的移动计算设备来说芯片都是 SoC ,CPU 和 GPU 封装在一起。所以完全可以避免来回复制数据,消除了从以前的计算机体系结构遗留下来的昂贵操作(参见图1.3)。CPU 只需要简单地授予GPU 数据的访问权即可。从 A7 芯片开始, Metal 就充分利用了这一设计。

Figure1.3.png

直接控制命令缓冲区

命令缓冲区是 Metal 应用程序的中央直接控制对象。Metal 与 OpenGL 的不同之处在于,Metal 给程序员提供了命令缓冲区控制权限,而不是以简化的名义提供一个抽象。 命令缓冲区是 Metal 应用程序的中央控制对象。这种设计给程序员在何时以及如何执行渲染缓冲区方面提供了更大的灵活性。在 OpenGL 中,决定工作顺序的是 OpenGL 驱动程序,而不是开发人员。通过向开发人员公开缓冲区的复杂性,并允许他们按首选顺序对工作进行排队,Metal 可以更好地控制应用程序的性能。可以创建多个线程,并且可以延迟缓冲区执行。

预编译的着色器

在 OpenGL 中,着色器在运行时编译。编译着色器的成本很高,这个任务在 Metal 之前,必须在每次调用时完成。在加载时编译这些着色器并没有多大意义,完全可以将此工作移到加载时间之外的应用程序构建时间。预编译的着色器可以存储在库中,可以在运行时引用它们,这会将耗时且昂贵的工作移动到不会影响用户体验的位置。

状态预验证

在其他图形API中,在每次绘制调用时,应用程序都必须检查状态,以避免向 GPU发送错误命令。应用程序中的状态更改并不会在每次绘制调用时发生。作为开发人员,您需要通过编码才能对命令编码器中对这些状态进行更改。因此我们可以在命令编码器被提交到命令缓冲区,并且发生在状态实际更改时会进行代价高昂的状态验证。

苹果的工程师把 Metal 的 API 做的尽可能的简洁轻量,这种精简的设计意味着将任务指令从 CPU 发送到 GPU 时所需的时间也更少。程序员完全掌控 GPU 后,我们就可以移除之前那些 GPU 静默执行的非必要任务了,例如状态检测。任务指令发送时间的压缩意味着我们可以充分利用 GPU 的潜力,不再需要太过顾虑 CPU 的处理能力限制。

Metal 与其他框架的关系

Metal 是一个非常优秀的框架,但是它无法脱离整个苹果生态而独立存在。它只是属于 Cocoa 生态系统的一员,它支持了其他框架的运行同时也依赖于生态中的其他框架。例如,SceneKit 和 SpriteKit 现在内部都是通过 Metal 进行图像编程的支持。

Metal 还遵循数据守恒定律,它不会创建或销毁数据;它只是修改数据。也因此 Cocoa 专门提供了一个 Model I/O框架充当 Metal 数据源。Model I/O提供了一种从三维建模程序引入顶点数据的简单方法。您可以让 Model I/O 完成解析数据文件和加载顶点缓冲区的所有繁重工作,这样您就可以专注于想要对数据做什么,而不是如何将其放入应用程序。

总结

苹果革命性的新 GPU 编程框架解决了传统图形编程 API 的许多长期问题。通过利用紧密的硬件和软件集成优势,苹果挤占了 GPU 的每一项功能。通过将昂贵的任务转移到绘图调用之外,苹果可以降低向 GPU 发送命令的成本,从而腾出更多的 CPU 时间来改进应用程序的其他方面。Metal 还被设计成与其他 Apple 框架很好地集成在一起,使您比以往任何时候都更容易利用这些框架来完成的常见任务。

Last Modified: November 29, 2020