文章都是通过阅读源码分析出来的,还在不断完善与改进中,其中难免有些地方理解得不对,欢迎大家批评指正。
转载请注明:From LXS. http://blog.csdn.net/uiop78uiop78/
GUI系统之SurfaceFlinger章节目录:
blog.csdn.net/uiop78uiop78/article/details/8954508
1.1 SurfaceFlinger
从这一小节开始,我们正式切入SurfaceFlinger的分析。为了保持讲解的连贯性,部分内容可能在前面的章节中已经有所涉及了,接下来将会对其中的细节做更多的扩展讲解。
内容组织如下:
l 首先介绍Android 4.1引入的新特性(Project Butter),理解这个项目是必要的,可以说SurfaceFlinger有很大一部分的内容就是围绕它来的
l SurfaceFlinger的启动过程及工作方式
l SurfaceFlinger与BufferQueue及应用程序间的关系
l SurfaceFlinger对VSYNC信号的处理过程(重点)
1.1.1 ProjectButter
直译过来,就是“黄油计划”,为什么叫这个名字呢?这个Project的目的是为了改善用户抱怨最多的Android几大缺陷之一,即UI响应速度——Google希望这一新计划可以让Android系统摆脱UI交互上给人带来的“滞后”感,而能像黄油一般“顺滑”。Google在2012年的I/O大会上宣布了这一计划,并在Android 4.1中正式搭载了实现机制。
Butter中有两个重要的组成部分,即VSync和Triple Buffering。下面先分别介绍引入它们的原因。
喜欢玩游戏或者看电影的读者可能遇到过这样的情形:
Ø 某些游戏场面好像是几个场景“拼凑”而成的
Ø 电影画面不连贯,好像被“割裂”了
这样子描述有点抽象,我们引用widipedia上的一张图来看下实际的效果:
图 11‑20 Screen Tearing实例
引自http://en.wikipedia.org/wiki/File:Tearing_%28simulated%29.jpg
我们把这种显示错误称为“screentearing”,那么为什么会出现这样的情况呢?
相信大家都能得出结论,那就是屏幕上显示的画面实际上来源于多个“帧”。
在一个典型的显示系统中,frame buffer代表了屏幕即将要显示的一帧画面。假如CPU/GPU绘图过程与屏幕刷新所使用的buffer是同一块,那么当它们的速度不同步的时候,是很可能出现类似的画面“割裂”的。举个具体的例子来说,假设显示器的刷新率为66Hz,而CPU/GPU绘图能力则达到100Hz,也就是它们处理完成一帧数据分别需要0.015秒和0.01秒。
以时间为横坐标来描述接下来会发生的事情,如下图:
图 11‑21 Screen Tearin*生过程分析
上半部分的方框表示在不同的时间点时显示屏的内容(加深的部分),下半部分则是同一时间点时frame buffer中的数据状态,编号表示第几个frame,不考虑清屏。
· 0.01秒
由于两者速率相差不小,此时buffer中已经准备好了第1帧数据,显示器只显示了第1帧画面的2/3
· 0.015秒
第1帧画面完整地显示出来了,此时buffer中有1/3的部分已经被填充上第2帧数据了
· 0.02秒
Buffer中已经准备好第2帧数据,而显示屏出现了screen tearing,有三分之一是第2帧内容,其余的则属于第1帧画面
在单缓冲区的情况下,这个问题很难规避。所以之前我们介绍了双缓冲技术,基本原理就是采用两块buffer。一块back buffer用于CPU/GPU后台绘制,另一块framebuffer则用于显示,当back buffer准备就绪后,它们才进行交换。不可否认,doublebuffering可以在很大程度上降低screen tearing错误,但是它是万能的吗?
一个需要考虑的问题是我们什么时候进行两个缓冲区的交换呢?假如是back buffer准备完成一帧数据以后就进行,那么如果此时屏幕还没有完整显示上一帧内容的话,肯定是会出问题的。看来只能是等到屏幕处理完一帧数据后,才可以执行这一操作了。
我们知道,一个典型的显示器有两个重要特性,行频和场频。行频(Horizontal ScanningFrequency)又称为“水平扫描频率”,是屏幕每秒钟从左至右扫描的次数; 场频(Vertical Scanning Frequency)也称为“垂直扫描频率”,是每秒钟整个屏幕刷新的次数。由此也可以得出它们的关系:行频=场频*纵坐标分辨率。
当扫描完一个屏幕后,设备需要重新回到第一行以进入下一次的循环,此时有一段时间空隙,称为VerticalBlanking Interval(VBI)。大家应该能想到了,这个时间点就是我们进行缓冲区交换的最佳时间。因为此时屏幕没有在刷新,也就避免了交换过程中出现screentearing的状况。VSync(垂直同步)是VerticalSynchronization的简写,它利用VBI时期出现的vertical sync pulse来保证双缓冲在最佳时间点才进行交换。
所以说V-sync这个概念并不是Google首创的,它在早些年前的PC机领域就已经出现了。不过Android 4.1给它赋予了新的功用,稍后就可以看到。
上面我们讨论的情况基于的假设是绘图速度大于显示速度,那么如果反过来呢?
图 11‑22绘图过程没有采用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后,将马上开始下一帧的渲染。
图 11‑23 整个显示系统都以VSync进行同步
如上图所示,一旦VSync出现后,CPU不再犹豫,紧接着就开始执行buffer的准备工作。大部分的Android显示设备刷新率是60Hz,这也就意味着每一帧最多只能有1/60=16ms左右的准备时间。假如CPU/GPU的FPS(FramesPer Second)高于这个值,那么这个方案是完美的,显示效果将很好。
可是我们没有办法保证所有设备的硬件配置都能达到要求。假如CPU/GPU的性能无法满足上图的条件,又是什么情况呢?
在分析这一问题之前,我们先来看下正常情况下,采用双缓冲区的系统的运行情况。
图 11‑24 双缓冲展示
这个图采用了双缓冲,以及前面介绍的VSync,可以看到整个过程还是相当不错的,虽然CPU/GPU处理所用的时间时短时长,但总的来说都在16ms以内,因而不影响显示效果。A和B分别代表两个缓冲区,它们不断地交换来正确显示画面。
现在我们可以继续分析FPS低于屏幕刷新率的情况。
如下图所示:
图 11‑25 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,情况会不会好转呢?
图 11‑26 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 triple buffering"
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会通过权衡各个值来选择最佳的解决方式
请大家务必理解本小节的几个场景分析,明白采用TripleBuffering、VSync机制的原因。带着这些理解进入SurfaceFlinger的学习,可以帮助我们“有的放矢”,对源码的分析也能事半功倍。