显示绘制--垂直同步、双缓冲、三缓冲
网上这类的文章挺多,我看的时候也晕乎,有点是爬虫趴下来的格式图片都挂了,有的参入和很多代码方面的讲解,一些概念性的平台无关的机制如果能不涉及代码细节,可能会更好。
我尝试用这篇文章,把尝试把这三个东西讲清楚。(前置知识:需要先了解什么是掉帧,16ms这个数字怎么来的)
屏幕显示图像的原理
拿过去的CRT显示器原理来说,CRT的电子枪按照上面的方式,从上到下逐行扫描,扫描完成以后显示器就呈现一帧的画面,然后电子枪就回到初始位置继续下一次扫描。
为了把显示器的显示和系统视频控制器同步,显示器会用硬件时钟产生一系列定时信号。
当电子枪换下一行准备扫描的时候,显示器会发出一个水平同步信号HSync。
当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号VSync。
显示器通常以VSync信号的频率来刷新。
计算机系统中的CPU、GPU、显示器的大致协同工作如下:
image.jpeg
CPU计算好现实内容提交到GPU,GPU渲染完成后将渲染结果放入帧缓冲区,随后视频控制器就会按照VSync信号逐行读取帧缓冲区的数据,然后在显示器上显示。
即,电脑显示一张画面是分成两个步骤完成的。
- 第一步是CPU和显卡把所要显示的画面数据计算出来。
- 第二步是显示器把这些数据写到屏幕上。
这两步工作都需要时间,并且可以并行执行,因为具体执行这两个过程的硬件是相互独立的(是cpu/显卡 和 视频控制器)。但是呢,这两个工作的耗时是不同的。
cpu以及显卡每秒能计算出的画面数量是根据硬件性能决定的。 但是显示器每秒刷新频率是固定的(一般是60hz,所以每隔16.667ms就会刷新一次)。
这种两边速率不统一的问题(先不说谁快谁慢),引入了帧缓冲(FrameBuffer)的概念。
帧缓冲能在一定程度上提升效率,但还是有几个问题:
- 画面闪烁
- 画面撕裂
- 跳帧
- 卡顿
接下来聊聊显示上会遇到的几个问题以及方案的演进:
画面撕裂、跳帧、闪烁
image.jpeg如上面说过的,显示器刷新的时候是从最上面的一行像素开始逐行向下刷新,所以从顶端到底部的刷新是有时间差的。如果显卡的性能很强,也就是显卡帧率大于屏幕刷新率的时候,就会出现屏幕上半部分还停留在上一帧的画面,新的一帧的数据已经拷贝上来了,那么屏幕的下半部分渲染出来的就是下一帧的画面-----这种情况被称为画面撕裂(问题-1)。
如果显卡再快一点,那么下一帧的图像还没来得及显示,下下一帧的数据就覆盖上来了,中间这帧就跳过了-----这种情况被称为跳帧(问题-2)。
反过来,如果显卡帧率小于显示器刷新率,那每次在屏幕上看到的可能不是完整的图形,每次看到的图形比上次更完整一些。于是在用户看起来,画面是卡顿掉帧不顺滑(问题-3)。
在单缓冲的场景下,渲染下一帧的时候先清除画布的当前视图,这样就会导致画面看起来闪烁,比如大学时候在win32的GDI+写过小游戏的朋友一定有印象,不使用额外手段的情况下,画面动起来的时候是会一闪一闪的。(问题-4)。
方案:针对这问题-1和-2,引入了垂直同步的技术。
垂直同步(V-Sync),开启后GPU会等待显示器的VSync信号发出后再进行新的一帧渲染和缓冲区更新。即,把显卡帧率锁定为显示器的刷新率,
由上述结论我们只能得到,垂直同步可以在显卡帧率比显示器刷新率高的时候解决撕裂和跳帧的问题。但是,显卡帧率小于显示器刷新率的时候,也就是问题-3和问题-4,引入了双缓冲技术。
双缓冲技术,GPU会预先渲染好一帧放入一个缓冲区内,让视频控制器读取,当下一帧渲染好后,GPU会直接把视频控制器的指针指向第二个缓冲区。也就是说,在一帧被渲染完以后才会交给屏幕显示,不会看到“半成品画面”。并且有两个缓冲区互换,不需要在显示前台清理画布,所以不会闪烁。
安卓在4.1引入了是三缓存+垂直同步的机制。
下面再来说一下Android的三重缓冲:先对比总结一下上面说的几种情况
image.jpeg image.jpegGPU帧率小于显示器刷新率的时候还是会出现下面的情况(掉帧):
image.jpeg
这样当掉帧的时候,第二个16ms时间段内,显示控制器占用一个Buffer,GPU暂用一个Buffer。两个Buffer都被占用,导致CPU空闲下来浪费了资源,因为垂直同步的原因只有V-SYNC时间点CPU才能触发绘制工作。
这时候引入第三个Buffer。这个Tripple Buffer机制利用CPU/GPU的空闲等待时间提前准备好数据,但是不一定会使用。
image.jpeg
如上图所示,一开始会掉帧一次后面就不会掉帧了。这个所谓的引入Buffer的机制,就和App和SurfaceFlinger通信的时使用的匿名共享内存Ashmem里的数据结构SharedClient里的SSHaredBufferStack关联上了,这个stack有16个位置,在4.1以后这个启用了3个缓冲位。这块SurfaceFlinger的文章我会进行专门的整理。
参考文献:
https://source.android.com/devices/graphics/index.html
https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
https://developer.android.google.cn/topic/performance/vitals/render#java
网友评论