【Graphic】Android5.0 Graphic(1) VSync与Project Butter
来源:互联网 发布:java返回string 编辑:程序博客网 时间:2024/06/06 06:50
VSync的概念
典型的现实系统中,Frame Buffer代表了显示一帧画面的缓冲数据。GPU/CPU绘图过程与屏幕刷新使用的是同一块缓冲,它们的速度不同的时候,就会出现画面“割裂”的显示。假如屏幕的刷新率为66HZ,而CPU/GPU的绘图能力有100HZ,它们分别处理完成一帧数据需要0.015秒和0.01秒。
From:https://en.wikipedia.org/wiki/File:Tearing_%28simulated%29.jpg
以时间为横坐标来描述接下来会发生的事情,如下图:
图 Screen Tearing产生过程分析
上半部分的方框表示在不同的时间点时显示屏的内容(加深的部分),下半部分则是同一时间点时frame buffer中的数据状态,编号表示第几个frame,不考虑清屏。
·0.01秒
由于两者速率相差不小,此时buffer中已经准备好了第1帧数据,显示器只显示了第1帧画面的2/3。
·0.015秒
第1帧画面完整地显示出来了,此时buffer中有1/3的部分已经被填充上第2帧数据了。
·0.02秒
Buffer中已经准备好第2帧数据,而显示屏出现了screentearing,有三分之一是第2帧内容,其余的则属于第1帧画面。
在单缓冲区的情况下,这个问题很难规避。采用双缓冲技术,基本原理就是采用两块buffer。一块back buffer用于CPU/GPU后台绘制,另一块framebuffer则用于显示,当back buffer准备就绪后,它们才进行交换。不可否认,doublebuffering可以在很大程度上降低screen tearing错误,但也不是万能的。这就是后来Google对UI这块进行了优化,引入了VSync和triple buffering的实现。
一个需要考虑的问题是我们什么时候进行两个缓冲区的交换呢?假如是back buffer准备完成一帧数据以后就进行,那么如果此时屏幕还没有完整显示上一帧内容的话,肯定是会出问题的。看来只能是等到屏幕处理完一帧数据后,才可以执行这一操作了。
典型的显示器有两个重要特性,即“行频和场频”。行频(Horizontal Scanning Frequency)又称为“水平扫描频率”,是屏幕每秒钟从左至右扫描的次数;场频(Vertical Scanning Frequency)又称为“垂直扫描频率”,是每秒钟整个屏幕刷新的次数。所以有如下的关系,行频=场频×纵坐标分辨率。
当扫描完一个屏幕之后,设备需要回到第一行以进入下一轮的循环,此时有一段时间的空隙,称为Vertical Blanking Interval(VBI)。对于使用双缓冲区的情况下,这个时间点就是需要缓冲区交换的最佳时间。此时屏幕没有在刷新,避免了交换过程中出现Screen Tearing的现象。VSync(垂直同步)是Vertical Synchronization,利用VBI时期出现的VerticalSync Pluse来保证双缓冲能在最佳时间点进行交换。举例来说,如果屏幕的刷新率为60HZ,那么生成帧的速度就应该被固定在1/60秒。
Project Butter
直译过来,就是“黄油计划”,为什么叫这个名字呢?这个Project的目的是为了改善用户抱怨最多的Android几大缺陷之一,即UI响应速度——Google希望这一新计划可以让Android系统摆脱UI交互上给人带来的“滞后”感,而能像黄油一般“顺滑”。Google在2012年的I/O大会上宣布了这一计划,并在Android 4.1中正式搭载了实现机制。
Project Butter对AndroidDisplay系统进行了重构,引入了三个核心元素,即VSYNC、Triple Buffer和Choreographer。
前面讨论的情况基于的假设是绘图速度大于显示速度,那么如果反过来呢?
图绘图过程没有采用VSync同步的情况
(引用自Google2012 I/O,作者Chet Haase和Romain Guy,AndroidUI Toolkit Engineers)
这个图中有三个元素,Display是显示屏幕,GPU和CPU负责渲染帧数据,每个帧以方框表示,并以数字进行编号,如0、1、2等等。VSync用于指导双缓冲区的交换。
以时间的顺序来看下将会发生的异常:
Step1. Display显示第0帧数据,此时CPU和GPU渲染第1帧画面,而且赶在Display显示下一帧前完成。
Step2.因为渲染及时,Display在第0帧显示完成后,也就是第1个VSync后,正常显示第1帧。
Step3.由于某些原因,比如CPU资源被占用,系统没有及时地开始处理第2帧,直到第2个VSync快来前才开始处理。
Step4.第2个VSync来时,由于第2帧数据还没有准备就绪,显示的还是第1帧。这种情况被Android开发组命名为“Jank”。
Step5.当第2帧数据准备完成后,它并不会马上被显示,而是要等待下一个VSync。
所以总的来说,就是屏幕平白无故地多显示了一次第1帧。原因大家应该都看到了,就是CPU没有及时地开始着手处理第2帧的渲染工作,以致“延误军机”。 Android系统中一直存在着这个问题,即便是上一版本的Ice Cream Sandwich。
从Android 4.1Jelly Bean开始,VSync得到了进一步的应用。系统在收到VSync pulse后,将马上开始下一帧的渲染。
图整个显示系统都以VSync进行同步
如上图所示,一旦VSync出现后,CPU不再犹豫,紧接着就开始执行buffer的准备工作。大部分的Android显示设备刷新率是60Hz,这也就意味着每一帧最多只能有1/60=16ms左右的准备时间。假如CPU/GPU的FPS(FramesPer Second)高于这个值,那么这个方案是完美的,显示效果将很好。
可是我们没有办法保证所有设备的硬件配置都能达到要求。假如CPU/GPU的性能无法满足上图的条件,又是什么情况呢?
在分析这一问题之前,我们先来看下正常情况下,采用双缓冲区的系统的运行情况。
图双缓冲展示
这个图采用了双缓冲,以及前面介绍的VSync,可以看到整个过程还是相当不错的,虽然CPU/GPU处理所用的时间时短时长,但总的来说都在16ms以内,因而不影响显示效果。A和B分别代表两个缓冲区,它们不断地交换来正确显示画面。
现在我们可以继续分析FPS低于屏幕刷新率的情况。
如下图所示:
图FPS低于屏幕刷新率的情况
当CPU/GPU的处理时间超过16ms时,第一个VSync到来时,缓冲区B中的数据还没有准备好,于是只能继续显示之前A缓冲区中的内容。而B完成后,又因为缺乏VSync pulse信号,它只能等待下一个signal的来临。于是在这一过程中,有一大段时间是被浪费的。当下一个VSync出现时,CPU/GPU马上执行操作,此时它可操作的buffer是A,相应的显示屏对应的就是B。这时看起来就是正常的。只不过由于执行时间仍然超过16ms,导致下一次应该执行的缓冲区交换又被推迟了——如此循环反复,便出现了越来越多的“Jank”。
那么有没有规避的办法呢?
很显然,第一次的Jank看起来是没有办法的,除非升级硬件配置来加快FPS。我们关注的重点是被CPU/GPU浪费的时间段,怎么才能充分利用起来呢?分析上述的过程,造成CPU/GPU无事可做的假象是因为当前已经没有可用的buffer了。换句话说,如果增加一个buffer,情况会不会好转呢?
图 Triple Buffering
Triple Buffering是MultipleBuffering的一种,指的是系统使用3个缓冲区用于显示工作。我们来逐步分析下这个新机制是否有效。首先和预料中的一致,第一次“Jank”无可厚非。不过让人欣慰的是,当第一次VSync发生后,CPU不用再等待了,它会使用第三个buffer C来进行下一帧数据的准备工作。虽然对缓冲区C的处理所需时间同样超过了16ms,但这并不影响显示屏——第2次VSync到来后,它选择buffer B进行显示;而第3次VSync时,它会接着采用C,而不是像double buffering中所看到的情况一样只能再显示一遍B了。这样子就有效地降低了系统显示错误的机率。
底层的BufferQueue中最多有32个BufferSlot,不过在实际使用时具体值是可以设置的。
·TARGET_DISABLE_TRIPLE_BUFFERING
这个宏用于disable triple buffering。如果宏打开的话,Layer.cpp在onFirstRef有如下操作:
#ifdefTARGET_DISABLE_TRIPLE_BUFFERING
#warning"disabling triplebuffering"
mSurfaceTexture->setBufferCountServer(2);
#else
mSurfaceTexture->setBufferCountServer(3);
#endif
也就是将mSurfaceTexture(即BufferQueue)中的mServerBufferCount设为2,否则就是3
·对于应用程序来说,它也可以通过ISurfaceTexture::setBufferCount来告诉BufferQueue它希望的Slot值,对应的则是mClientBufferCount。默认情况下这个变量是0,表示应用端不关心到底有多少buffer可用。
·BufferQueue中还有另一个变量mBufferCount,默认值是MIN_ASYNC_BUFFER_SLOTS。在具体的实现中,以上这三个变量都是要考虑到的,BufferQueue会通过权衡各个值来选择最佳的解决方式。
参考文档:《深入理解Android内核设计思想》
- 【Graphic】Android5.0 Graphic(1) VSync与Project Butter
- android graphic(4)—surfaceflinger和Vsync
- android graphic(4)—surfaceflinger和Vsync
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- Android 4.4 Graphic系统详解(3) VSYNC的处理
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- android graphic(5)—surfaceflinger和Vsync (简化)
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- android graphic(5)—surfaceflinger和Vsync (简化)
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- Android 4.4 Graphic系统详解(2) VSYNC的生成
- android graphic(1)—轮廓
- android graphic(1)—轮廓
- Spring Security hello world example
- webFontIcon三种字体图标详解
- jQuery选择器总结
- 编译.java后出现:Note:checkUser.java uses unchecked or unsafe operations.Note:Recompile with -Xlint :unche
- DQL、DML、DDL、DCL的概念与区别
- 【Graphic】Android5.0 Graphic(1) VSync与Project Butter
- 四个好看的CSS样式表格
- H-Index
- GO 获取时间的年份、月份以及日期
- RHCS概念详解
- DH Diffie-Hellman算法(D-H算法),密钥一致协议。
- MD5算法的C++实现
- 广州市的街道信息
- eclipse调试java程序的九个技巧