iOS渲染流程解析

1,111 阅读9分钟

1.计算机渲染原理

CPU与GPU的芯片架构

对于现代计算机系统,简单来说可以大概视作三层架构:硬件、操作系统与进程。对于移动端来说,进程就是 app,而 CPU 与 GPU 是硬件层面的重要组成部分。CPU 与 GPU 提供了计算能力,通过操作系统被 App 调用。

  • CPU:主要用来进行运算、控制核心。

  • GPU:主要是进行绘制图形运算的专用微处理器,是连接计算机和显示终端的纽带。

CPU和GPU他们一开始设计的目的就不同,他们针对不同的应用场景。CPU 是运算核心与控制核心,需要有很强的运算通用性,兼容各种数据类型,同时也需要能处理大量不同的跳转、中断等指令,因此 CPU 的内部结构更为复杂。而 GPU 则面对的是类型统一、更加单纯的运算,也不需要处理复杂的指令,但也肩负着更大的运算任务。

从上图可以看出,CPU 拥有更多的缓存空间 Cache 以及复杂的控制单元,计算能力并不是 CPU 的主要诉求。CPU 是设计目标是低时延,更多的高速缓存也意味着可以更快地访问数据;同时复杂的控制单元也能更快速地处理逻辑分支,更适合串行计算。

而 GPU 拥有更多的计算单元 Arithmetic Logic Unit,具有更强的计算能力,同时也具有更多的控制单元。GPU 基于大吞吐量而设计,每一部分缓存都连接着一个流处理器(stream processor),更加适合大规模的并行计算。

2.图像渲染流程

图像渲染流程粗粒度地大概分为下面这些步骤

上述图像渲染流水线中,除了第一部分 Application 阶段,后续主要都由 GPU 负责,GPU 的渲染流程图如下:

上图就是一个三角形被渲染的过程中,GPU 所负责的渲染流水线。可以看到简单的三角形绘制就需要大量的计算,如果再有更多更复杂的顶点、颜色、纹理信息(包括 3D 纹理),那么计算量是难以想象的。这也是为什么 GPU 更适合于渲染流程。

下面是各个阶段具体的任务:

  • Application应用处理阶段:得到图元(可以理解为数组中的一堆,x、y、z坐标数据)。这个阶段具体指的就是图像在应用中被处理的阶段,此时还处于 CPU 负责的时期。在这个阶段应用可能会对图像进行一系列的操作或者改变,最终将新的图像信息传给下一阶段。这部分信息被叫做图元(primitives),通常是三角形、线段、顶点等。

  • Geometry 几何处理阶段:处理图元。进入这个阶段之后,以及之后的阶段,就都主要由 GPU 负责了。此时 GPU 可以拿到上一个阶段传递下来的图元信息,GPU 会对这部分图元进行处理,之后输出新的图元。这一系列阶段包括:

    1.顶点着色器(Vertex Shader):这个阶段中会将图元中的顶点信息进行视角转换、添加光照信息、增加纹理等操作。

    2.形状装配(Shape Assembly):图元中的三角形、线段、点分别对应三个 Vertex、两个 Vertex、一个 Vertex。这个阶段会将 Vertex 连接成相对应的形状。

    3.几何着色器(Geometry Shader):额外添加额外的Vertex,将原始图元转换成新图元,以构建一个不一样的模型。简单来说就是基于通过三角形、线段和点构建更复杂的几何图形。

  • 光栅化阶段:图元转换为像素。光栅化的主要目的是将几何渲染之后的图元信息,转换为一系列的像素,以便后续显示在屏幕上。这个阶段中会根据图元信息,计算出每个图元所覆盖的像素信息等,从而将像素划分成不同的部分。

一种简单的划分就是根据中心点,如果像素的中心点在图元内部,那么这个像素就属于这个图元。如上图所示,深蓝色的线就是图元信息所构建出的三角形;而通过是否覆盖中心点,可以遍历出所有属于该图元的所有像素,即浅蓝色部分。

  • Pixel 像素处理阶段:处理像素,得到位图。经过上述光栅化阶段,我们得到了图元所对应的像素,此时,我们需要给这些像素填充颜色和效果。所以最后这个阶段就是给像素填充正确的内容,最终显示在屏幕上。这些经过处理、蕴含大量信息的像素点集合,被称作位图(bitmap)。也就是说,Pixel阶段最终输出的结果就是位图,过程具体包含:这些点可以进行不同的排列和染色以构成图样。当放大位图时,可以看见赖以构成整个图像的无数单个方块。只要有足够多的不同色彩的像素,就可以制作出色彩丰富的图象,逼真地表现自然界的景象。缩放和旋转容易失真,同时文件容量较大。
  • 片段着色器(Fragment Shader):也叫做 Pixel Shader,这个阶段的目的是给每一个像素 Pixel 赋予正确的颜色。颜色的来源就是之前得到的顶点、纹理、光照等信息。由于需要处理纹理、光照等复杂信息,所以这通常是整个系统的性能瓶颈。
  • 测试与混合(Tests and Blending):也叫做 Merging 阶段,这个阶段主要处理片段的前后位置以及透明度。这个阶段会检测各个着色片段的深度值 z 坐标,从而判断片段的前后位置,以及是否应该被舍弃。同时也会计算相应的透明度 alpha 值,从而进行片段的混合,得到最终的颜色。

3.屏幕成像与卡顿

图像显示流程

首先从过去的 CRT 显示器原理说起。CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称 VSync。显示器通常以固定频率进行刷新,这个刷新率就是 VSync 信号产生的频率。

通常来说,计算机系统中 CPU、GPU、显示器是以上面这种方式协同工作的。CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。

在最简单的情况下,帧缓冲区只有一个,这时帧缓冲区的读取和刷新都都会有比较大的效率问题。为了解决效率问题,显示系统通常会引入两个缓冲区,即双缓冲机制。在这种情况下,GPU 会预先渲染好一帧放入一个缓冲区内,让视频控制器读取,当下一帧渲染好后,GPU 会直接把视频控制器的指针指向第二个缓冲器。如此一来效率会有很大的提升。

双缓冲虽然能解决效率问题,但会引入一个新的问题。造成画面撕裂现象,如下图:

撕裂的原因是因为当视频控制器还未读取完成时,即屏幕内容刚显示一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,视频控制器就会把新的一帧数据的下半段显示到屏幕上,如下图:
为了解决这个问题,GPU 通常有一个机制叫做垂直同步(简写也是 V-Sync),当开启垂直同步后,GPU 会等待显示器的 VSync 信号发出后,才进行新的一帧渲染和缓冲区更新。这样能解决画面撕裂现象,也增加了画面流畅度,但需要消费更多的计算资源,也会带来部分延迟。

目前主流的移动设备iOS 设备会始终使用双缓存,并开启垂直同步。而安卓设备直到 4.1 版本,Google 才开始引入这种机制,目前安卓系统是三缓存+垂直同步。

卡顿

如上图所示,由于增加了新的帧缓冲器,可以一定程度上地利用掉帧的空档期,合理利用 CPU 和 GPU 性能,从而减少掉帧的次数。但不能完全解决掉帧的问题。手机卡顿的直接原因就是掉帧。所以屏幕刷新频率必须要足够高才能流畅。对于 iPhone 手机来说,屏幕最大的刷新频率是 60 FPS,一般只要保证 50 FPS 就已经是较好的体验了。但是如果掉帧过多,导致刷新频率过低,就会造成不流畅的使用体验。所以造成卡顿主要原因就是 CPU 和 GPU 渲染流水线耗时过长,导致掉帧。解决CPU和GPU资源消耗过长的原因可以参考iOS 保持界面流畅的技巧这篇文章

7.参考资料

  1. iOS 保持界面流畅的技巧
  2. iOS Rendering 渲染全解析(长文干货)