Metal API简介

Metal是一种低开销,高性能的API,可以在GPU上执行图形和计算工作。 GPU的常见工作是绘制几何图形,而Metal的基本设计原理旨在帮助应用程序极其快速地绘制几何图形。

在GPU上执行绘制调用即可完成绘制几何图形。 绘图调用是图形命令和状态的集合,这些命令和状态在屏幕上产生视觉效果; 每个绘制调用都需要有自己的图形状态向量,这意味着它需要明确指定用于执行绘制的着色器,图形状态,数据缓冲区,纹理和渲染目标。 在所有上一代的硬件图形API(如OpenGL ES)中,更改状态向量都是非常昂贵的操作,因为所有API命令都必须在相应的硬件命令中转换。 这样做的成本通常全部在CPU上,负责执行这种转换的函数和所有API命令必须在GPU开始执行任何工作之前进行转换。 下图显示了典型的绘制调用序列以及从应用程序(CPU端)到GPU的执行流程。

设计原理与架构

金属围绕6个关键设计原则构建:

  • 最薄的API,意味着减少在应用程序和GPU之间执行的代码量。
  • 旨在为所有现代GPU硬件功能提供全面支持
  • 减少昂贵的操作频率。
  • 提供可预测的性能。
  • 提供对命令提交的明确控制。
  • 针对CPU行为进行了优化。

几乎所有现代手机游戏都倾向于以特定帧速率为目标来管理CPU和GPU工作负载,大多数情况下,此目标为60帧每秒(fps),而其他时间为30 fps。 下图显示了一个游戏的常见情况,该游戏试图优化CPU和GPU的工作量以保持稳定的30 fps:CPU为某一帧准备渲染命令,而GPU在下一帧消耗这些命令。

当一切都按预期工作时,此设置可以提供完美且平衡的并行性,但这是一种理想的情况,因为在现实生活中,大多数情况下,CPU生成渲染命令的时间要比GPU消耗它们的时间长得多,因此GPU空闲了一部分帧。 再详细一点看CPU必须执行的工作,我们可以将其分为两部分:CPU执行应用程序逻辑所花费的时间和CPU准备呈现API命令所花费的时间; 通常,后者是占用大部分可用帧时间的时间。 如下图所示,CPU无法在目标帧时间内转换所有API命令,这可能会导致GPU跳过帧。

Metal尝试着重于准备渲染API命令的工作,并提供了支持以最小程度地减少它。 这实际上释放了可用于其他活动的CPU时间,并且大部分时间都将这些额外时间用于生成更多绘图调用。

为了更好地了解Metal API如何达到这种效果,重要的是要了解GPU编程为什么在CPU上如此昂贵。

主要有以下三个原因:

  • 状态验证:应用程序每次调用呈现API时,呈现API实现都必须验证调用是否以正确的方式执行:应用程序使用正确数量和类型的参数,并且硬件上下文将在进入状态后变为有效状态。通话完成。 但是还有更多! 在调用API时,实现还必须将API状态编码为相应的硬件状态,并再次检查其他硬件状态以弄清楚如何将它们组合在一起以将全局上下文移动到新的状态。
  • 着色器编译必须编译所有着色器的源代码才能生成GPU机器代码,这通常在运行时发生。 通常,状态和着色器代码的描述方式与硬件真正期望的不完全相同,因此,当应用程序更改某些状态时,可能会发生,必须重新编译生成的机器代码。
  • GPU工作提交状态和着色器代码可以请求不在GPU端驻留的资源,因此必须将它们在内存中移动到GPU可以访问它们的位置。

因为所有这些,所有游戏所做的就是将共享相似状态和资源的操作组合在一起,目的是减少工作量并提高效率,因此我们通常将此过程称为批处理命令……但批处理命令需要在服务器上运行更多逻辑CPU创建这些批次。 因此,最终结果是,在为CPU安排适当的工作量以产生可让GPU在整个帧中保持繁忙并在目标帧时间内完成所有这些工作的工作量之间,始终存在一个平衡的工作。

Metal与众不同的原因在于设计原则,因此不经常进行昂贵的操作。

在Metal之前的所有渲染API中,特别是OpenGL ES,状态验证,着色器编译和GPU工作提交,都是在绘制帧期间发生的,这使得帧时间的管理不受事物的直接控制,而不受应用程序的直接控制。 Metal支持在渲染对象创建时进行脱机着色器编译和状态验证,这使应用程序不必担心将工作提交给GPU,仅此而已。

为了更好地理解所有这些,让我们详细研究Metal API的所有对象部分。 让我们来看看所有这些:

  • 设备MTLDevice ):这是物理GPU的抽象,将消耗渲染和计算命令; 这也是在Metal中执行任何操作的首选对象,因为应用程序与之交互的所有对象都来自该对象。
  • 命令队列MTLCommandQueue ):此对象存储所有命令,并允许应用程序控制所有命令的执行顺序。
  • 命令缓冲区MTLCommandBuffer ):此对象存储已翻译的硬件命令,可供GPU使用。
  • 命令编码器MTLCommandEncoder ):该对象负责将渲染和计算命令转换为硬件命令。
  • 状态:GPU的状态由一系列状态对象描述:帧缓冲区的配置,混合类型,深度函数,处理纹理时要使用的不同样本都存储在对象中。
  • Code :这表示应用程序声明和使用的所有顶点和片段着色器的源代码。
  • 资源 :这些对象存储在内存中,这些数据代表诸如顶点缓冲区或纹理或着色器常量集之类的资源。

如上图所示,应用程序从MTLDevice实例创建MTLCommandQueue对象。 通常,应用程序会在初始化时创建一个或多个命令队列,然后在整个生命周期中始终保留这些队列。 应用程序使用MTLCommandQueue对象的实例,创建一个或多个MTLCommandBuffer对象,以存储将提交给MTLCommandEncoder对象的硬件命令。 为了生成命令,有必要向MTLCommandEncoder对象指定一些信息,这是通过在可以使用它之前附加各种对象来完成的。 为了创建资源对象,Metal提供了一种围绕称为描述符的数据结构构建的机制。 描述符允许应用程序指定创建特定资源对象所需的所有必要状态。 状态对象使用相同的概念:API提供了应用程序创建它们必须使用的描述符。 在上图中,可以看到两个最常用的状态对象:分别使用MTLRenderPipelineDescriptorMTLDepthStencilDescriptor创建的Render Pipeline State对象和Depth Stencil State对象; 这两个对象允许应用程序设置GPU的各种渲染状态。 最后一个基本状态对象是可以通过MTLRenderPassDescriptor创建的Render Pass对象,该对象描述应用程序将如何输出几何形状。

Metal之所以在描述符和状态/资源对象之间拆分状态是因为一旦应用程序创建了所有内容并声明了所有不同的状态组合,Metal将所有这些都烘烤成少量的状态对象,这些对象已经使用着色器转换为它们的硬件格式编译并通过状态验证; 这样,剩下要做的唯一工作就是提交和执行命令,这意味着绘制调用的速度非常快!

命令提交模型

如前所述,命令编码器将API命令作为硬件命令存储在命令缓冲区对象中。 命令缓冲区对象是非常轻量级的对象,通常应用程序在执行帧时会创建大量的对象。 命令缓冲区对象是线程安全的,因此使用多个线程并行准备它们是很常见的,当它们准备就绪时,一次提交所有内容以控制它们将由GPU执行的顺序。 Command Encoder之所以如此高效,是因为它们不仅存储了以后必须在执行之前必须由CPU占用的某些工作,而且它们无需延迟状态验证即可立即生成命令……这就像直接调用GPU驱动程序一样。

资源更新模型

Metal中的资源模型旨在支持统一的内存系统,这意味着CPU和GPU共享相同的存储空间来交换数据,这消除了执行隐式副本以使GPU能够查看由CPU处理的数据的需求,反之,反之亦然。 Metal还提供了对自动缓存一致性模型的支持,并确保CPU和GPU在触摸内存时将遵守命令缓冲区的执行边界; 应用程序唯一需要做的就是确保按计划进行渲染工作,这样CPU和GPU不会在同一时间写在同一块内存上。 将渲染工作的调度和同步委托给应用程序,它将使API免于执行任何内部和/或隐式额外的同步块,从而显着提高性能。

谈到资源,要强调的关键概念是资源的结构是不可变的,一旦创建就不能改变。 这样可以避免在使用资源时进行昂贵的资源验证。 当然,资源的内容可以随时更改,并且由于该模型是围绕统一系统的概念构建的,并且读/写同步是在应用程序的控制之下,因此不需要锁定API即可访问资源。资源数据; 因此在Metal上,资源更新需要获取内存中的指针并读取/写入数据。