转载
Android 之理解 VSYNC 信号
Android应用性能优化系列视频双语字幕讲解 By Google
Android Project Butter分析
Android VSYNC与图形系统中的撕裂、双缓冲、三缓冲浅析
在阅读Matrix源码时, 发现Matrix会hook Choreographer这个类, 然后跟进这个Choreographer类, 又涉及到VSYNC这个概念, 先对这个VSYNC概念进行整理.
以下所有内容全部为从各种文章东拼西凑而来.
包含以下这些概念:
1. 刷新率
2. 帧速率
3. 屏幕撕裂
4. 双缓冲
5. Triple Buffering
1. 刷新率
屏幕在一秒内刷新画面的次数, 刷新率取决于硬件的固定参数, 单位Hz. 例如常见的60Hz, 即每秒钟刷新60次.
逐行扫描:
显示器并不是一次性将画面显示到屏幕上, 而是从左到右, 从上到下逐行扫描显示, 不过这一过程快到人眼无法察觉到变化, 以60Hz刷新率的屏幕为例, 即1000 / 60 = 16ms.
2. 帧率
表示GPU显卡在一秒内绘制操作的帧数. 例如电影采用24fps、Android系统采用60fps, 即一秒钟绘制30 / 60帧的画面
3. 屏幕撕裂
刷新频率和帧速率需要一起合作, 才能使图形内容呈现在屏幕上, GPU会获取图形数据进行绘制, 然后硬件负责把图像内容呈现到屏幕上, 这一过程在应用程序的生命周期内一遍又一遍的发生.
流程简述为:
CPU准备图形数据 -> GPU获取图形数据并绘制 -> 显示器显示数据
如果刷新频率和帧率不能保持同步, 例如帧速率实际比刷新率快, 例如帧速率是120fps, 显示器的刷新频率是60Hz. 此时将会发生一些视觉上的问题.
由于显示器提取画面是从左到右, 从上到下逐行扫描提取图像显示, 这一过程需要16ms(1000 / 60), 当GPU利用一块内存区域写入帧数据时, 从顶部开始新一帧覆盖前一帧, 并立刻输出一行内容. 当屏幕刷新时, 它并不知道图像缓冲区的状态, 因此它从GPU抓取的帧并不是完整的数据. 也就是它有一半的前一帧和一半的当前帧, 这种情况称之为屏幕撕裂
理想情况下, 希望显示器在完成一帧绘制之后再从GPU获取到下一帧图像数据, 但是由于帧率和刷新频率不一致导致显示器在绘图过程中, GPU已经开始加载下一帧图像数据. 屏幕撕裂是由于帧率和刷新频率不一致到情况导致.
4. 双缓冲
解决屏幕撕裂的问题是双缓冲, 即GPU和显示器都有各自的工作缓冲区. GPU始终将完成的一帧绘制数据写入到Back Buffer
, 而显示器使用Frame Buffer
. 当屏幕刷新时, Frame Buffer并不会发生变化. Back Buffer根据屏幕的刷新将数据拷贝到Frame Buffer中, 这便是VSYNC
在Android4.1之前, Android使用双缓冲机制. 同一个View Hierarchy内的View都会共用一个Window, 也就是共用一个Surface.
而每个Surface都会有一个BufferQueue缓存队列, 但是这个队列会由SurfaceFlinger管理, 通过匿名共享内存与App应用层交互.
流程如下:
- 每个Surface对应的BufferQueue内部都有两个Graphic Buffer, 一个用于绘制, 一个用于显示. 系统会把内容放到离屏缓冲区(OffScreen Buffer), 在需要显示时, 才把离屏缓冲区的内容通过Swap Buffer复制到Front Graphic Buffer中.
- 这样SurfaceFlinger就拿到了某个Surface最终要显示的内容, 但是同一时间我们可能会有多个Surface. 这里面可能是不同应用的Surface, 也可能是同一个应用里面类似SurfaceView和TextureView, 它们都会有自己单独的Surface.
- 这个时候SurfaceFlinger把所有Surface要显示的内容统一交给Hardware Composer, 它会根据位置、Z-Order顺序等信息合成为最终屏幕需要显示的内容, 而这个内容会交给系统的帧缓冲区Frame Buffer来显示.
4.1 Android4.1之前未使用VSYNC的双缓冲机制
流程如下:
- 1.时间从0开始, 进入第一个16ms, Display显示第0帧, CPU处理完第一帧后, GPU紧接其后继续处理第一帧, 三者互不干扰
- 2.进入到第二个16ms, 因为早在上一个16ms时间内, 第一帧已经由CPU、GPU处理完毕, 故Display可以直接显示第一帧. 显示没有问题, 但在本16ms期间, CPU和GPU却并未及时去绘制第二帧数据, 而是在本周期快结束时, CPU/GPU才去处理第二帧的数据.
- 3.时间进入第三个16ms, 此时Display应该显示第二帧的数据, 但由于CPU和GPU还没有处理完第二帧数据, 故Display只能继续显示第一帧数据, 结果使得第一帧多画了一次(对应时间段标注了一个Jank)
- 4.通过上述分析可知, 此处发生的Jank的关键问题在于, 为何第1个16ms段内, CPU/GPU没有及时处理第二帧数据? 原因是CPU可能在忙别的事情(比如某个应用通过sleep固定时间来实现动画的逐帧显示), 不知道到该处理UI绘制的时间了, 可CPU一旦想起来要处理第二帧数据时, 时间又错过了.
Android使用VSYNC来阻止屏幕撕裂, 对于Android4.0来说, CPU可能会因为在忙其他的事情, 导致没来得及处理UI绘制. 所以从4.1开始VSYNC则更进一步, VSYNC脉冲现在用于开始下一帧的所有处理.
4.2 Android4.1之后引入VSYNC机制
- 1.这样应用总是在VSYNC边界上开始绘制, 而SurfaceFlinger总是在VSYNC边界上进行合成. 这样便可以消除卡顿, 并提升图形的视觉表现.
- 2.结合图中的内容, 也就是说在显示当前数据的同时, CPU/GPU开始准备下一帧数据的绘制, 如果在16ms内完成, 也就可以保证屏幕显示下一帧图形时能够有数据可取.
- 3.从图中可以看出CPU和GPU基本上都能在16ms内处理完数据, 也就是说CPU和GPU可以的fps(frames per second)要高于Display的fps. 由于CPU和GPU只有在接收到VSYNC信号时, 才会开始处理数据, 因此CPU和GPU的fps被拉低到和Display的fps相同的水平.
4.3 CPU和GPU的fps低于Display的fps
- 1.在第二个16ms内, Display本来应该展示帧, 但是因为GPU还在处理B帧, 导致Display只能重复展示A帧.
- 2.在第四个16ms时, Display本来需要展示A帧, 但是又因为GPU此时还没有处理完A帧数据, 导致Display继续展示B帧
- 3.一旦过了VSYNC的时间点, CPU就不能被触发以处理绘制工作
- 4.因为只有两个Buffer, 例如在第二个16ms内, Display和GPU分别占据着一个Buffer, 导致CPU只能处于空闲状态.
4.4 Triple Buffer(三缓冲区)
- 1.在第二个16ms内, 虽然Display还是会重复展示A帧数据, 但是后续展示还是流畅的.
- 2.但是结合图中可以看出CPU准备的C帧数据在第4个16ms内才会被Display展示. 因此也并不是Buffer越多越好.
至此, VCYNS概念算是东拼西凑勉勉强强copy完成, 接下来继续东拼西凑Choreographer
, 待这些基本概念弄清楚之后再继续Matrix—FrameTracer的分析.