Android 图形系统(2)---- Android vsyn
显示系统基础知识
一个典型的图形显示系统包含 CPU,GPU,Display 三个部分,CPU负责计算需要渲染的数据,把计算好的数据交给GPU,GPU会对图形数据进行渲染,渲染好后放到buffer(图像缓冲区)里存起来,然后Display(屏幕或显示器)负责把buffer里的数据呈现到屏幕上。
CPU 和 GPU 的计算是可以并行执行的。
draw display flow.png屏幕刷新频率 一秒内屏幕刷新的次数(一秒内显示了多少帧的图像),单位 Hz(赫兹),如常见的 60 Hz。刷新频率取决于硬件的固定参数(不会变的)。
帧率 (Frame Rate) 表示 GPU 在一秒内绘制操作的帧数,单位 fps。例如在电影界采用 24 帧的速度足够使画面运行的非常流畅。而 Android 系统则采用更加流程的 60 fps,即每秒钟GPU最多绘制 60 帧画面。帧率是动态变化的,例如当画面静止时,GPU 是没有绘制操作的,屏幕刷新的还是buffer中的数据,即GPU最后操作的帧数据。
Tearing 与double buffer
屏幕刷新频是固定的,比如每16.6ms从buffer取数据显示完一帧,理想情况下帧率和刷新频率保持一致,即每绘制完成一帧,显示器显示一帧。但是CPU/GPU写数据是不可控的,所以会出现buffer里有些数据根本没显示出来就被重写了,即buffer里的数据可能是来自不同的帧的, 当屏幕刷新时,此时它并不知道buffer的状态,因此从buffer抓取的帧并不是完整的一帧画面,即出现画面撕裂(tearing)。
简单来说就是边画边显示造成的,正在显示的buffer 里的内容被篡改,导致显示画面里的内容不是来自同一帧数据。
Tearing.pngdouble buffe双缓存,让绘制和显示器拥有各自的buffer:GPU 始终将完成的一帧图像数据写入到 Back Buffer,而显示器使用 Front Buffer,当屏幕刷新时,Frame Buffer 并不会发生变化,当Back buffer准备就绪后,它们才进行交换。如下图:
double buffer.pngVsync 机制
Android4.1之前,屏幕刷新也遵循 上面介绍的双缓存+VSync 机制;但是会存在下面的缺陷。
上层的有更新画面的需求时,才会去重新绘制和显示画面;
上层更新画面的时机是不确定的。
draw without vsync.png问题:
第2个Diplay VSync来时,由于第2帧数据还没有准备就绪,缓存没有交换,显示的还是第1帧。这种情况被Android开发组命名为“Jank”,即发生了丢帧。
为了优化显示性能,Google在Android 4.1系统中对Android Display系统进行了重构,实现了Project Butter(黄油工程):只有系统在收到Display VSync后,才会马上开始下一帧的渲染。即一旦收到VSync通知(16ms触发一次),CPU和GPU 才立刻开始计算然后把数据写入buffer
vsync flow.png总结起来就是只有在vsync 到来之后,才会开始进行帧的渲染和绘制。
draw with vsync.pngVSync同步使得CPU/GPU充分利用了16.6ms时间,减少jank。
Triple buffer
这里再考虑CPU和 GPU处理时间超出一个Vsync 的情形
draw beyond 1 vsync.png-
在第二个时间段内,但却因 GPU 还在处理 B 帧,缓存没能交换,导致 A 帧被重复显示
-
而B完成后,只有等到下一个vsync 来临才可以显示。
-
CPU 和 GPU 都是可以多线程执行的,第一个vsync 到来之后,由于没有可以用的buffer,所以CPU和GPU都无法继续绘制下一帧。
如果在double buffer 基础上增加一个,使用triple buffer:
triple buffer.png第一个Jank,是不可避免的。但是在第二个 16ms 时间段,CPU/GPU 使用 第三个 Buffer 完成C帧的计算,虽然还是会多显示一次 A 帧,但后续显示就比较顺畅了,有效避免 Jank 的进一步加剧。
总结
-
为了保证画面不出现tearing 的问题,我们需要采用doublebuffer,做到显示front buffer的同时在back buffer 上进行绘制。
-
在vsync 到来之后进行绘制可以优化UI 系统的显示性能,减少Jank
-
使用Triple buffer 可以让CPU/GPU draw 的时间在1~2Vsync 之间的情形同样流畅的以60Fps 显示。