为了方便理解核心原理,以下流程均已精简。
一. 完整流程
流程图:
基础流程
上图是 Android 将一个布局通过硬件渲染的方式显示到屏幕,这个过程可简化为俩步:应用侧绘制和系统侧绘制。
- 应用侧
通过树形结构组织 UI 元素,通过 measure、layout、draw 三个阶段,在 canvas 上绘制出应用程序窗口的所有视图,最终生成一个缓存绘制命令的 Buffer -- DisplayList。
这一过程是在 CPU 里计算完成的。
- 系统侧
为了提高渲染性能,Android 3.0 起提供了硬件加速,将部分绘制工作交给 GPU 完成。 GPU 主要负责栅格化,可以简单理解为将向量图形格式表示的图像转换成位图(像素)以用于显示设备输出的过程。
因此在系统侧,首先将 DisplayList 转为 GPU 可以识别的 OpenGL 指令,然后由 GPU 使用 OpenGL ES API 将图像渲染到 Surface 上。接下来,可以(简单的)理解为一个图形数据缓冲交换的生产消费模型:
BufferQueue 是供生产消费者使用的图形数据缓冲区队列;Surface 作为生产者,提供了应用渲染上屏图像的能力,通过 GPU 渲染生成的图形缓冲区,最终通过 Surface 传递给 BufferQueue;SurfaceFlinger 作为消费者,接收来自多个源的数据缓冲区,它在接收到 BufferQueues 的通知后,取出可用的图形缓冲区,发送(或合成后发送,取决于 HWC)给 HWC。
HWC 是硬件混合渲染器,它负责合成所有图层并直接写入输出到帧缓冲,帧缓冲的数据最终会转为适当的信号发送给显示器完成最后的上屏。
上图中的 vsync 指同步,并非下文中的 Vsync 信号。
二. 显示器的图形显示
以下内容涉及显示器显示图像的基础硬件知识。
显示器必须通过图形显示子系统中的显示控制部件才能完成显示,常见的图形显示子系统有光栅扫描子系统,这里将介绍如何通过光栅扫描子系统在显示器上显示图像。
光栅扫描子系统有俩个重要的部件:帧缓冲存储器(即显存)和显示控制器。
帧缓冲存储器是用来存储像素颜色(灰度)的存储器,可由显示控制器直接访问,以便随时刷新屏幕。前文中的帧缓冲即存储在这里。
显示控制器可看作是一个独立于 CPU 的本地处理器(实际仍然由 CPU 控制),它的主要功能是依据设定的显示方式,自主地、反复不断的读取显存中的图像点阵数据,将它们转为 R、G、B 三色信号并配以同步信号送至显示器,即可刷新屏幕。
由于计算像素数据并写入相应的帧缓冲单元计算量相当大,为了减轻 CPU 的负担,诞生了独立的显示模块:显卡,它除了包含显示控制器、帧缓冲存储器(显存)外,还增加了独立的显示处理器(GPU)和显示处理器存储区域(主要用于临时存放显示处理时的程序和数据)。
三*. OpenGL 渲染管线
通过 OpenGL 渲染管线,可以将一张特定尺寸的 Bitmap,最终采样并显示在不同尺寸分辨率的显示器上。
OpenGL 渲染管线的基本流程如下,其中,光栅化会把图元映射为最终屏幕上相应的像素,片段着色器即对要渲染的每个像素单元着色。
抛开 Android,如果有兴趣了解如何使用 OpenGL 去加载图片、并对图片进行处理(给图片增加各种效果)和渲染,可见此代码演示 -- Github 链接。
另外对于 Bitmap 的绘制,Bitmap 持有像素数据,而像素数据可以转为 OpenGL 需要的 Texture,进而采样完成渲染到 Surface。
四. 黄油计划
为了提升 Android 屏幕渲染的流畅度,从 Android 4.1 开始对显示系统进行了重构,称之为黄油计划。
黄油计划引入了三个核心元素:VSYNC、Tripple Buffer 和 Choreographer。
- VSYNC
一种定时中断机制,它的作用是使 CPU 在收到 VSYNC 信号后,马上开始处理下一帧的数据(结合 Handler 的同步屏障机制),并保证刷新率和帧率的同步。
它的好处有俩个:在帧率 > 刷新率的情况下,GPU 所产生的帧数据会因为等待 VSYNC 的刷新信息而被 Hold 住,这样能够保持每次刷新都有实际的新的数据可以显示,以防止画面撕裂;在帧率 < 刷新率的情况下,它能提升渲染任务的优先级来减少丢帧。
- Tripple Buffer
三重缓冲机制,当使用双缓存机制、且 GPU 或 CPU 计算超时时,由于屏幕显示占用一个缓存、计算中的 GPU 或 CPU 占用另一个缓存,因此在单帧时间内,必定有 CPU 或 GPU 处于空闲状态,三缓冲机制可以弥补该空缺,进一步提升流畅度。
- Choreographer
用于从显示子系统接收定时脉冲(例如 VSYNC 信号),然后安排工作以渲染下一个显示帧。它可以协调动画(animations)、输入(input)、绘制(drawing) 三个 UI 相关的操作。
五. 绘制的触发
第一节简述了绘制开始后,单帧绘制数据从应用侧、系统侧直到上屏的过程。
由于绘制是在收到 Vsync 信号才开始的,而请求绘制则可以在任意时间点(如手动调用 invalidate),所以本节将补充从请求绘制到绘制开始的这段时间节点里发生了什么。
绘制触发.png要点:
- 界面不变时,底层也会以固定的屏幕刷新率(如 16.6 ms)来切换每一帧的画面。但对 App 而言,只有注册了下一个 Vsync 信号才能接收到回调、重新绘制。如果界面不变,App 不会接收 Vsync 事件,CPU/GPU 也就不会走绘制流程。
- 当请求绘制时,系统会添加同步屏障,阻止 MessageQueue 中同步消息的执行。此后无论是 Choreographer 注册下一个 Vsync 信号的过程、还是 Choreographer 接收 Vsync 信号执行 Callback 的过程中,都会发送异步消息优先执行。
- 由于直到 Vsync 信号到来并绘制后同步屏障才会移除,所以同步消息最多被延迟一帧执行。有时为了让我们的任务尽快执行,可以发送异步消息,这样便可以在等待 Vsync 信号期间执行任务,但需要注意异步任务无法保证与绘制任务的顺序关系。
- 当请求绘制时,绘制任务不会立即开始,而是等到下一个 Vsync 信号到来时才开始;当 CPU&GPU 绘制流程执行完毕,界面也不会立即刷新,而是等到再下一个 Vsync 信号到来才进行缓存交换并显示。
- 同步屏障 + Vsync 并不能保证绘制一定在固定的 Vsync 信号点执行,原因是添加同步屏障的时间点之前可能在执行一个耗时的同步任务。所以造成丢帧的原因有俩个:1. 主线程在执行耗时任务导致绘制任务不能开始;2. CPU&GPU 绘制 View 超过了单帧的时间。
六. 参考链接
Android 显示刷新机制、VSYNC和三重缓存机制
Android 黄油计划和显示刷新机制学习笔记
Android 渲染机制——原理篇(显示原理全过程解析)
掌握Android图像显示原理上
计算机图形学基础 -- 第三版
Android-Choreographer原理
网友评论