每日一问02——渲染流程

我本来是想知道“一张 png/jpg 格式的图片渲染到页面上显示有哪些流程”这个问题。万万没想到我撞到了一个庞然大物上面,今天就只记录一下渲染过程中发生了些什么事儿。

一,屏幕显示图像原理

我们看到的图像其实就是不同颜色像素的集合,它们密集的排列在屏幕上,形成了我们看到的图形。

计算机显示的流程大致可以描述为将图像转化为一系列像素点的排列然后打印在屏幕上,由图像转化为像素点的过程又可以称之为光栅化,就是从矢量的点线面的描述,变成像素的描述。(光栅化先记一下,深究又要扯到着色器等等,先不提)

每日一问02——渲染流程_第1张图片
ios_screen_scan.png

回溯历史,可以从过去的 CRT 显示器原理说起。CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称 VSync。显示器通常以固定频率进行刷新,这个刷新率就是 VSync 信号产生的频率。尽管现在的设备大都是液晶显示屏了,但原理仍然没有变。(这段完全是转载,目测是某本书上的原话反正我没读过)

从上面我们了解到几个词:水平同步信号,垂直同步信号,硬件时钟定时。
结合图片,一个图像的形成就是一行一行刷出来的。

iOS的显示架构
每日一问02——渲染流程_第2张图片
arch01.png

从图片上可以知道,对于APP而言,我们只能直接使用Core Graphics,Core Animation,Core Image三个。而它们3个都是依赖于OpenGL(ES)来与底层沟通,从而完成界面显示。

GPU是一个专门为图形高并发计算而量身定做的处理单元。这也是为什么它能同时更新所有的像素,并呈现到显示器上。它并发的本性让它能高效的将不同纹理合成起来。因为涉及到各种图形矩阵的计算,它跟CPU最直观的区别在于浮点计算能力要超出CPU很多。简单来讲,CPU就是多面手,它需要做各种各样不同逻辑的事儿,而GPU只专心做图像方面的事情

GPU Driver 是直接和 GPU 交流的代码块。

硬件之间的关系
每日一问02——渲染流程_第3张图片
arch03.png

CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。

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

引入一个新的问题。当视频控制器还未读取完成时,即屏幕内容刚显示一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象。

每日一问02——渲染流程_第4张图片
ios_vsync_off.jpg

为了解决这个问题,GPU 通常有一个机制叫做垂直同步(简写也是 V-Sync),当开启垂直同步后,GPU 会等待显示器的 VSync 信号发出后,才进行新的一帧渲染和缓冲区更新。这样能解决画面撕裂现象,也增加了画面流畅度,但需要消费更多的计算资源,也会带来部分延迟。

图形显示流程与卡顿现象的产生原因
每日一问02——渲染流程_第5张图片
ios_frame_drop.png

在 VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。随后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
从上面的图中可以看到,CPU 和 GPU 不论哪个阻碍了显示流程,都会造成掉帧现象。

上次与底层的关系

每日一问02——渲染流程_第6张图片
offscreen06.png

可以看出渲染的核心是Core Animation,并且CPU和GPU都可以进行渲染,但一个是通过OpenGL ES 另一个是通过Core Graphics。就是这里的不同导致了渲染时的性能差异,我们写代码时才有了那么多优化讲究。
关键在于应该尽量让CPU负责主线程的UI调动,把图形显示相关的工作交给GPU来处理

扯了这么多,感觉还是差一点点。CPU/GPU处理后的结果送到Core Animation后是怎么显示到UI的呢?
原来Core Animation 在 RunLoop 中注册了一个 Observer,监听了 BeforeWaiting 和 Exit 事件。当一个触摸事件到来时,RunLoop 被唤醒,App 中的代码会执行一些操作,比如创建和调整视图层级、设置 UIView 的 frame、修改 CALayer 的透明度、为视图添加一个动画;这些操作最终都会被 CALayer 标记,并通过 CATransaction 提交到一个中间状态去。当上面所有操作结束后,RunLoop 即将进入休眠(或者退出)时,关注该事件的 Observer 都会得到通知。这时 Core Animation 注册的那个 Observer 就会在回调中,把所有的中间状态合并提交到 GPU 去显示;如果此处有动画,通过 DisplayLink 稳定的刷新机制会不断的唤醒runloop,使得不断的有机会触发observer回调,从而根据时间来不断更新这个动画的属性值并绘制出来。

总结:上面那一大堆东西80%都是从其他地方转载过来的,自己看了好久大概理清楚基本流程和原理了。
当我们创建或执行某些操作UI的代码时,Core Animation在runloop里监听到这些行为,告诉CPU/GPU去渲染最终显示在界面上

参考文章:
iOS 保持界面流畅的技巧
iOS图形原理与离屏渲染
iOS 事件处理机制与图像渲染过程

你可能感兴趣的:(每日一问02——渲染流程)