76523c54ce17674121a2af32a678b726
Metal【2】—— 基础框架

前言

好了,终于又到了 Metal 系列,接下去很长一段时间,应该都是完成 Metal 相关文章。

这篇是 Metal 的基础框架介绍,会从宏观角度,分析怎么利用 Metal 和 GPU 交互,以及 Graphics Rendering Pipeline 的工作流程。

会介绍一些基础的概念,以及一些之后会频繁打交道的对象。但它们具体是什么,怎么使用,还是等到真正实战用到的时候,再进一步说明。

PS:

我们一直说到,相对于 OpenGL 杂乱无章的句柄,Metal 的面向对象设计是十分科学,简洁的。

但这里要提一点,Metal API 提供的大多是 protocols,而不是具体的 classes。

即大多数接口返回的,是实现了特定协议的对象,具体的类是次要的。

@protocol MTLDevice <NSObject>
 ///////
MTL_EXTERN id <MTLDevice> __nullable MTLCreateSystemDefaultDevice(void) NS_AVAILABLE(10_11, 8_0) NS_RETURNS_RETAINED;

这样的好处就是不需要关心具体类的实现过程,而只要知道这个对象具备这样的能力就好了。

这里为了方便,之后的描述,会直接说成 MTLDevice 对象。


Metal 框架介绍

Metal provides low-level and low-overhead access to the graphics processing unit (GPU).

我们一直说到,Metal 提供给开发者与 GPU 交互的能力。那么,这里有两个问题:

  • 为什么是 GPU?
  • 怎么和 GPU 交互?

理解了这两个问题,我们就会对 Metal 的框架设计有个宏观的认识。

为什么是 GPU

上一篇文章里面,我们提到过,CPU 擅长逻辑控制,串行的运算。GPU 擅长大规模并发计算,浮点计算优化。但是并没有具体展开细讲。

这里,我觉得有必要结合 Metal,再具体看下。

先看下 CPU,GPU 的架构图:

其中,

  • ALU(Arithmetic and Logic Unit),即计算单元
  • Cache,进行高速数据交换的存储器
  • DRAM(Dynamic Random Access Memory),即动态随机存取存储器,最为常见的系统内存

硬件设计的差异,导致了每个处理器有各自适合的任务。

从架构图上,我们可以很直观看到,GPU 有很多的 ALU,GPU 可以通过在各个计算单元之间分配工作负载,共同处理大量数据,天生就是为计算而设计的。

而图形处理的时候,图像上的每一个像素点都有被处理的需要,而且每个像素点处理的过程和方式都十分相似,也就成了 GPU 的天然温床。

PS:

GPU 中有很多的 ALU 和很少的 Cache,Cache 的目的不是保存后面需要访问的数据的,这点和 CPU 不同,而是为多线程提高效率服务的。如果有很多线程需要访问同一个相同的数据,缓存会合并这些访问,然后再去访问 DRAM。

下面举一个 Metal 在计算方面的优势,一个简单的相加操作。inputA 和 inputB 中各个元素相加,输出到 outputC 中。通过 compute function 实现,具体代码如下:

using namespace metal;
kernel void
compute_function(constant float *inputA  [[buffer(0)]],
                 constant float *inputB  [[buffer(1)]],
                 device   float *outputC [[buffer(2)]],
                 uint           index    [[thread_position_in_grid]])
{
    outputC[index] = inputA[index] + inputB[index];
}

在 GPU 上执行的时候,会有很多 ALU 同时参与,并行处理。而 CPU 要执行上述操作,则需要放在一个循环里,按序执行。

这,就是为什么选择 GPU 的原因。

PS:

这里不用深究 compute function 是什么,上述的代码又是什么,等用到的时候,我们自然就知道了。

现在只要了解,Metal 能做这些事就好了。

怎么和 GPU 交互

试想一下,我们用 Metal 是要做什么?

笼统来讲,都可以抽象成 input resource —> GPU —> output resources 这样的一个过程。

我们先以一个实际例子,【眼睛放大】效果思考下。

做这个功能,我们大致会拆分成如下几步:

  • 创建一个视图,用来展示结果图
  • 提供原图,以供进行效果处理
  • 提供原图中,眼睛区域,以便指定区域进行处理
  • 提供眼睛放大的处理算法
  • 提交给 GPU 处理
  • 视图显示处理完的结果图

而这几步,正是我们和 GPU 交互的具体描述。用 Metal 描述如下:

如上图所示,我们使用 Metal 过程中,关心的无非是这三类对象:

  • Resource:Store and manage your app's data in GPU-accessible containers。 使用 GPU 可访问的容器,来存储和管理相关数据。包括 MTLBuffer,MTLTexture 等。
  • Function:Write custom GPU functions to process your app's data。指编写自定义的函数,让 GPU 处理数据。包括 MTLFunction,MTLLibrary 等。
  • Commands:Organize, commit, and execute your app's GPU commands。 组织、提交和执行应用程序的GPU命令。 包括 MTLCommandQueue ,MTLCommandBuffer, MTLCommandEncoder 等。

结合刚才提到的眼睛放大实例,Resourcs 对应原图,眼睛区域这类的数据。 Function 对应眼睛放大算法。Commands 则体现在提交给 GPU 处理。

PS:

上述提到的 MTLCommandQueue,MTLCommandBuffer,MTLTexture 等,你可以会很陌生,不过这不重要。

这节不会具体介绍,只要记得,他们各自对应的功能模块即可。或者记不住也没关系,只要知道有这个东西就好。:)

这里顺便提一下 Metal 对象的关系图,至于它们之间的具体关系,之后用到的时候,会一一详细说明。

top Created with Sketch.