『iOS 渲染原理解析』的阅读总结,写一下学习心得与收获
带着几个问题去阅读这篇优秀的文章,可以收获很多
- CPU 和 GPU 的设计目的分别是什么?
- CPU 和 GPU 哪个的 Cache\ALU\Control unit 的比例更高?
- 计算机图像渲染流水线的大致流程是什么?
- Framebuffer 帧缓冲器的作用是什么?
- Screen Tearing 屏幕撕裂是怎么造成的?
- 如何解决屏幕撕裂的问题?
- 掉帧是怎么产生的?
CPU 与 GPU 的架构

CPU 中拥有更多的缓存空间 Cache 以及复杂的控制单元,计算能力并不是 CPU 的主要诉求。
GPU 中则拥有更多的计算单元,具有更强的计算能力,同时也具有更多的控制单元
CPU 和 GPU 其设计目标是不同的,它们分别针对了两种不同的应用场景。
CPU 是运算核心与控制核心,需要有很强的运算通用型,兼容各种数据类型,同时也需要能处理大量不同的跳转、中断等指令,因此 CPU 的内部结构更为复杂。
GPU 则面对的是类型统一、更加单纯的运算,也不需要处理复杂的指令,但也肩负着更大的运算任务。
计算机图像渲染流水线的大致流程
CPU 会将图像进行一系列的操作或改变,将最终的图像信息传给 GPU
GPU 的渲染流程图:

屏幕成像与卡顿
通过 GPU 渲染结束之后的像素信息,会被存在帧缓冲器(Framebuffer)中,之后视频控制器会读取帧缓冲器中的信息,经过数模转换传递给显示器,进行显示,整个流程如下图所示:

显示器的电子束会从屏幕的左上角开始逐行扫描,屏幕上的每个点的图像信息都从帧缓冲器中的位图进行读取,在屏幕上对应地显示。
每次整个屏幕被扫描完一次,就相当于呈现一帧完整的图像。屏幕不断地刷新,不停呈现新的帧,就能呈现出连续的影像。
这个屏幕刷新的频率,就是帧率(Frame per Second,FPS)。
由于人眼的视觉暂留效应,当屏幕刷新频率足够高时,就能让画面看起来是连续而流畅的。
对于 iOS 而言,app 应尽量保证 60FPS 才是最好的体验
屏幕撕裂
在理想的情况下,一个流畅的图像显示流水线:显示器的电子束对新的一帧进行扫描时,CPU + GPU 对于该帧的渲染应该已经结束,渲染结束的位图已经存在帧缓冲器中。但这种情况是非常脆弱的,很容易产生屏幕撕裂:

CPU+GPU 的渲染过程是一个非常耗时的过程。
如果电子束开始扫描新的一帧时,位图还没有渲染好,而是在扫描到屏幕中间才渲染完成,被放入帧缓冲器中,就会出现上图这样的现象:已扫描的部分是上一帧的画面,未扫描的部分是新的一帧图像,从而导致屏幕撕裂

垂直同步 Vsync + 双缓冲机制 Double Buffering
解决屏幕撕裂的一个策略是使用垂直同步信号 Vsync 与双缓冲机制 Double Buffering。
根据苹果的官方文档,iOS 设备会始终使用 Vsync + Double Buffering 的策略。
垂直同步信号(vertical synchronisation,Vsync)相当于给帧缓冲器加锁。
当电子束完成一帧的扫描后,将要从头开始扫描时,就会发出一个垂直同步的信号。只有当视频控制器接收到 Vsync 之后,才会将帧缓冲器中的位图更新为下一帧,这样就能保证每次显示的都是同一帧的画面,因而避免了撕裂。
但是在这种情况下,需要整个 CPU+GPU 的渲染流程都要在一瞬间完成,这明显是不现实的。

双缓冲机制会增加一个新的备用缓冲器(back buffer)。
渲染结果会预先保存在 back buffer 中,在接收到 Vsync 信号时,视频控制器会将 back buffer 中的内容置换到 frame buffer 中,此时就能保证置换操作几乎在一瞬间完成(实际上是内存地址的交换)
掉帧 Jank
启用 Vsync 信号以及双缓冲机制置换,能够解决屏幕撕裂的问题,但是会引入新的问题:掉帧。
如果在接收到 Vsync 时,CPU+GPU 还没有渲染好新的位图,视频控制器就不会去替换 frame buffer 中的位图。这时屏幕就会重新扫描呈现上一帧一模一样的画面。相当于两个周期显示了同样的画面,这就是所谓的掉帧情况。

如图所示,A、B 代表两帧缓冲器,当 B 没有渲染完毕时就接收到了 Vsync 信号,所以屏幕只能再显示相同帧 A
屏幕卡顿的本质
手机使用卡顿的直接原因,就是掉帧。当掉帧过多,导致刷新频率(FPS)过低,就会造成不流畅的使用体验
总结一下:
- 屏幕卡顿的根本原因:CPU 和 GPU 渲染图像过程耗时过长,导致掉帧
- 垂直同步信号+双缓冲机制的意义:强制同步屏幕刷新,会以掉帧为代价解决屏幕撕裂问题