Jimmy Chen

A Programmer

(转载)SurfaceFlinger之VSync工作原理

这篇文章写得挺不错的,就是篇幅长,内容多,而且平时没有解过这方面的bug,感觉理解的并不是特别透彻

  VSync信号的科普我们上一篇已经介绍过了,这篇我们要分析在SurfaceFlinger中的作用。(愈发觉得做笔记对自己记忆模块巩固有很多帮助,整理文章不一定是用来给别人看的,但一定是为加强自己记忆的~)

流程基础

  从上一篇得知,Android 4.1一个很大的更新是Project Butter,黄油计划,为了解决用户交互体验差的问题(Jelly Bean is crazy fast)。Project Butter对Android Display系统进行了重构,引入了三个核心元素,即VSYNC、Triple Buffer和Choreographer。

硬件加载

  上上一节我们初略分析了SurfaceFlinger的启动流程,从SurfaceFlinger类的初始化流程得知,其init方法初始化了很多参数。我们分析Vsync流程需要这些信息,位于frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp中。

显示设备

  SurfaceFlinger中需要显示的图层(layer)将通过DisplayDevice对象传递到OpenGLES中进行合成,合成之后的图像再通过HWComposer对象传递到Framebuffer中显示。DisplayDevice对象中的成员变量mVisibleLayersSortedByZ保存了所有需要显示在本显示设备中显示的Layer对象,同时DisplayDevice对象也保存了和显示设备相关的显示方向、显示区域坐标等信息。

  DisplayDevice是显示设备的抽象,定义了3中类型的显示设备。引用枚举类位于frameworks/native/services/surfaceflinger/DisplayDevice.h中,定义枚举位于hardware/libhardware/include/hardware/Hwcomposer_defs.h中:

  • DISPLAY_PRIMARY:主显示设备,通常是LCD屏
  • DISPLAY_EXTERNAL:扩展显示设备。通过HDMI输出的显示信号
  • DISPLAY_VIRTUAL:虚拟显示设备,通过WIFI输出信号

  这三种设备,第一种就是我们手机、电视的主显示屏,另外两种需要硬件扩展。

  初始化显示设备模块位于SurfaceFlinger中的init函数:

  上述过程就是初始化显示设备,我们分部查看。

  1)检查设备连接:

  所有显示设备的输出都要通过HWComposer对象完成,因此上面这段代码先调用了HWComposer的isConnected来检查显示设备是否已连接,只有和显示设备连接的DisplayDevice对象才会被创建出来。即使没有任何物理显示设备被检测到,SurfaceFlinger都需要一个DisplayDevice对象才能正常工作,因此,DISPLAY_PRIMARY类型的DisplayDevice对象总是会被创建出来。

  createBuiltinDisplayLocked函数就是为显示设备对象创建一个BBinder类型的Token:

  一般这种token之类的东西,都是一个binder,就像我们创建window需要一个token,也是new一个binder然后在WindowManagerService里注册。

  2)初始化GraphicBuffer相关内容:(这一部分我们后续分析GraphicBuffer管理相关会仔细分析它,这里只是粗略介绍)

  然后会调用createBufferQueue函数创建一个producer和consumer。然后又创建了一个FramebufferSurface对象。这里我们看到在新建FramebufferSurface对象时把consumer参数传入了代表是一个消费者。

  而在DisplayDevice的构造函数中(下面会讲到),会创建一个Surface对象传递给底层的OpenGL ES使用,而这个Surface是一个生产者。在OpenGl ES中合成好了图像之后会将图像数据写到Surface对象中,这将触发consumer对象的onFrameAvailable函数被调用。

  这就是Surface数据好了就通知消费者来拿数据做显示用,在onFrameAvailable函数汇总,通过nextBuffer获得图像数据,然后调用HWComposer对象mHwc的fbPost函数输出。

  FramebufferSurface类位于frameworks/native/services/surfaceflinger/displayhardware/FramebufferSurface.cpp中,我们查看它的的onFrameAvailable函数:

  这个流程我们下几节会仔细分析,这里我们粗略过个流程:

  通过nextBuffer获得图像数据;

  然后调用HWComposer对象mHwc的fbPost函数输出,fbPost函数最后通过调用Gralloc模块的post函数来输出图像。这一部分我们在Android SurfaceFlinger 学习之路(一)—-Android图形显示之HAL层Gralloc模块实现分析过了,可以查看这个链接。

  3)创建DisplayDevice对象,用来描述显示设备:

  我们再来看看DisplayDevice的构造函数,位于frameworks/native/services/surfaceflinger/DisplayDevice.cpp中:

  DisplayDevice的构造函数初始化了一些参数,但是最重要的是:创建了一个Surface对象mNativeWindow,同时用它作为参数创建EGLSurface对象,这个EGLSurface对象是OpenGL ES中绘图需要的。

  这样,在DisplayDevice中就建立了一个通向Framebuffer的通道,只要向DisplayDevice的mSurface写入数据。就会到消费者FrameBufferSurface的onFrameAvailable函数,然后到HWComposer在到Gralloc模块,最后输出到显示设备。

  附(以后会讲到):DisplayDevice有个函数swapBuffers。swapBuffers函数将内部缓冲区的图像数据刷新到显示设备的Framebuffer中,它通过调用eglSwapBuffers函数来完成缓冲区刷新工作:

  但是注意调用swapBuffers输出图像是在显示设备不支持硬件composer的情况下。

HWComposer设备

  通过上一篇文章得知,Android通过VSync机制来提高显示效果,那么VSync是如何产生的?通常这个信号是由显示驱动产生,这样才能达到最佳效果。但是Android为了能运行在不支持VSync机制的设备上,也提供了软件模拟产生VSync信号的手段。

   在SurfaceFlinger中,是通过HWComposer类来表示硬件设备。我们继续产看init函数:

  我们继续查看HWComposer的构造函数,位于frameworks/native/services/surfaceflinger/displayhardware/HWComposer.cpp中:

  构造函数主要做了以下几件事情:

  1)加载FrameBuffer硬件驱动:

  调用loadFbHalModule函数实现,我们继续查看:

   hw_get_module函数是HAL层框架中封装的加载gralloc的函数,这个我们之前讲过,可以查看Android SurfaceFlinger 学习之路(一)—-Android图形显示之HAL层Gralloc模块实现中的Gralloc模块的加载过程部分.

  framebuffer_open函数同上,是用来打开fb设备,同样查看fb设备的打开过程模块。

  2)装载HWComposer的硬件模块:

  调用loadHwcModule函数来加载HWC模块,我们继续查看:

  同样是借助hw_get_module这个HAL函数,不过参数是HWC_HARDWARE_MODULE_ID,加载HWC模块;然后hwc_open_1函数打开hwc设备。

  3)FB和HWC相关逻辑判断(1):

  如果有FB和HWC,并且也有pre-1.1 HWC,就要先关闭FB设备。这个是暂时的,直到HWC准备好;

  如果我们没有HWC硬件支持,或者之前的pre-1.1 HWC,,如HWC 1.0,那么FrameBuffer就是必须的。如果脸FB都没有,那就GG。

  4)硬件设备打开成功:

  如果硬件设备打开成功,则将钩子函数hook_invalidate、hook_vsync和hook_hotplug注册进硬件设备,作为回调函数。这三个都是硬件产生事件信号,通知上层SurfaceFlinger的回调函数,用于处理这个信号。

  因为我们本节是Vsync信号相关,所以我们只看看hook_vsync钩子函数。这里指定了vsync的回调函数是hook_vsync,如果硬件中产生了VSync信号,将通过这个函数来通知上层,看看它的代码:

  hook_vsync钩子函数会调用vsync函数,我们继续看:

  mEventHandler对象类型为EventHandler,我们在SurfaceFlinger的init函数创建HWComposer类实例时候讲SurfaceFlinger强转为EventHandler作为构造函数的参数传入其中。再者SurfaceFlinger继承HWComposer::EventHandler,所以这里没毛病,老铁~所以最终会调用SurfaceFlinger的onVSyncReceived函数。这就是硬件vsync信号的产生,关于vsync信号流程工作我们下面马上会讲到。

  5)FB和HWC相关逻辑判断(2):

  如果mFbDev为不为null,情况为:没有hwc或者hwc是1.0老的版本。此时就要设置一些FB的属性了,从主屏幕获取;

  如果mFbDev为null,情况为:fb打开失败,或者暂时关闭(上面逻辑分析了)。仅仅打开了hwc,这里我们至少有HWC 1.1,上面分析过了。此时我们要获取显示设备相关属性,进入else if逻辑判断,里面的NUM_BUILTIN_DISPLAYS 为2,则只有主屏幕和扩展屏幕需要查询显示设备属性,调用queryDisplayProperties函数,我们可以产看这个函数:

  queryDisplayProperties查询每个显示设备的属性,主要有以下几步:

  1. hwc设备获取显屏配置信息,都是HAL层封装的函数,并保存在这些指针指向内容中;
  2. 获取显示先关属性,并保存起来。

  这一部分注释写的比较清晰,可以查看注释。

  6)软件VSync信号产生:

  如果硬件设备打开失败,则需要软件vsync信号,因此needVSyncThread为true,会进入最后一个if条件判断逻辑,则会创建一个VSyncThread类实例,用来模拟软件vsync信号。

  所以我们看看VSyncThread这个类,它集成与Thread,又是一个线程,定义位于HWComposer.h中:

  构造函数位于HWComposer.cpp中:

  构造函数中初始化了mEnable变量为false,mNextFakeVSync为0。还有获取了主屏幕的刷新率,并保存在mRefreshPeriod。屏幕刷新率我们在上一篇VSync信号已经科普过了。

  我们看看屏幕刷新率的获取,hwc.getRefreshPeriod(HWC_DISPLAY_PRIMARY)函数:

  这里获取的屏幕刷新率refresh就是我们之前queryDisplayProperties函数里获取的。其实queryDisplayProperties不止在HWComposer构造函数里调用,还会在钩子函数hook_hotplug里调用,我们查看hotplug函数:

  如果是主屏幕或者虚拟设备则不会查询设备属性。

  我猜测是插入扩展屏幕后,会重新再查询一下屏幕信息,从硬件发出的信号。

  好了我们继续回到上面,因为VSyncThread对象付给了一个强指针,所以会调用onFirstRef函数:

   这样这个软件vsync产生线程就运行起来了,因此我们需要查看threadLoop函数:

  上面的代码,我们需要注意两个地方:

  1. mEnabled默认为false,mCondition在这里阻塞,直到有人调用了signal(),同时mEnabled为true;
  2. 如果阻塞解开,则会定期休眠,然后去驱动SF,这就相当于产生了持续的Vsync信号。

  这个函数会间隔模拟产生VSync的信号的原理是在固定时间发送消息给HWCompoer的消息对象mEventHandler,这个其实就到SurfaceFlinger的onVSyncReceived函数了。用软件模拟VSync信号在系统比较忙的时候可能会丢失一些信号,所以才有中间的sleep小于0的情况。

  到目前为止,HWC中新建了一个线程VSyncThread,阻塞中,下面我们看下打开Vsync开关的闸刀是如何建立的,以及何处去开闸刀。

VSync 信号闸刀控制

  上面我分析了Vsync软件和硬件信号的产生过程,但是HWC中新建的线程VSyncThread是处于阻塞当中,因此我们需要一个闸刀去控制这个开关。

Vsync信号开关

  这个闸刀还是在SurfaceFlinger的init函数中建立的,我们查看这个位置:

   这是一个Thread线程类,因此都会实现threadLoop函数。不过我们先看看它的构造函数:

  比较简单,将SurfaceFlinger对象传了进来,并且将mVsyncEnabled标志初始为false。

  然后我们看看他的threadLoop线程函数:

  threadLoop函数主要逻辑分两步:

1.进来时候在mCond处阻塞,可以看到外面是个死循环,所以如果有其他地方对这个mCond调用了signal,执行完下面的代码又会阻塞
2.解除阻塞后,如果vsyncEnabled != mVsyncEnabled,也就是开关状态不同,由开到关和由关到开,都回去调用mFlinger->eventControl,调用完成后把这次的开关状态保存,vsyncEnabled = mVsyncEnabled;,与下次做比较。

  从上面我们能明显认识到EventControlThread线程,其实就起到了个闸刀的作用,等待着别人去开、关。

  所以这个开关操作就在SurfaceFlinger的eventControl函数,我们看看它怎么实现:

  它又会调用HWComposer的eventControl函数,我们继续查看:

  这里可以看到还是分软件闸刀和硬件闸刀:

  1. 硬件闸刀控制就到了HAL层函数了,调用了mHwc->eventControl(mHwc, disp, event, enabled)这个逻辑;
  2. 如果没有硬件Vsync支持,则会调用软件闸刀控制手段,VsyncThread的setEnable函数。

  硬件就不用了看,反正也看不到代码。。。我们看看软件Vsync闸刀控制,即VsyncThread的setEnable函数:

  将软件模拟线程VSyncThread的mCondition释放,这时候VSyncThread就会定期产生“信号”,去驱动SF了。

EventControlThread开关

  但是我们上边漏了东西,就是EventControlThread本身的闸刀,它自己还在mCond.wait(mMutex)这里阻塞呢。那么EventControlThread这个闸刀到底在哪儿打开呢?

  所以我们还得的查看SurfaceFlinger的init函数,再这里它有一处逻辑:

  我们查看initializeDisplays函数:

  这里放了一个消息给SF的MessageQueue,然后消息回调onInitializeDisplays函数,我们继续查看:

   其它都不重要,重要在setPowerModeInternal函数,我们继续查看:

  上面逻辑也不难,只要我们记住以前赋值的变量时多少就行了。

  我们获取主显屏的信息,因为是灭的HWC_POWER_MODE_OFF模式,所以进入这个if判断里面,然后调用resyncToHardwareVsync函数。我们继续查看它:

  先将mHWVsyncAvailable 标志位置为true,然后设置硬件刷新周期,最后打开mEventControlThread闸刀,我们可以查看它的setVsyncEnabled函数:

  可以看到,在这里释放了前面的mCond,让EventControlThread去调用mFlinger->eventControl()函数,从而去HWC中打开Vsync开关。

VSync信号构成

  本来我们应该继续顺着代码去跟踪vsync信号的分发过程,但是在这之前我们有必要了解一下vsync信号的构成,以便我们后续分析没有盲点。

  我们知道Android是用Vsync来驱动系统的画面更新包括APPview draw ,surfaceflinger 画面的合成,display把surfaceflinger合成的画面呈现在LCD上。要分析Vsync信号的组成,可以了解一下systrace工具。

Systrace简介

  Systrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统(如surfaceflinger、WindowManagerService等Framework部分关键模块、服务,View系统等)的运行信息,从而帮助开发者更直观的分析系统瓶颈,改进性能。

  systrace的使用可以查看官网,或者问娘问哥。
  官网使用教程如下:
  https://developer.android.com/studio/profile/systrace-walkthru.html

  命令行使用方式:
  https://developer.android.com/studio/profile/systrace-commandline.html

  生成html分析:
  https://developer.android.com/studio/profile/systrace.html

  分析systrace生成文件,要在eng版本rom,其他的要么无法生成,要么没有权限。

  下面我抄一个例子分析。

VSync的构成

  就挑一个相册这个应用,是系统自带的system app,包名是com.android.gallery3d。

《(转载)SurfaceFlinger之VSync工作原理》

  在systrace中,我们经常可以看到如上图的信息。

  • 红色框1是与Vsync相关event信息.这些Vsync event构成了android系统画面更新基础;
  • 红色框2和红色框3是Vsync-app的信息.我们可以看到红色框2比红色框3稀疏.我们将会在本文说明其原因;
  • 红色框4和红色框5是Vsync-sf的信息.我们可以看到红色框4比红色框5稀疏.我们将会在本文说明其原因。

  从上图可以看出来,VSync信号由HW_VSYNC_ON_0,HW_VSYNC_0, Vsync-app和Vsync-sf四部分构成:

  • HW_VSYNC_ON_0:代表PrimaryDisplay的VSync被enable或disable。0这个数字代表的是display的编号, 0是PrimaryDisplay,如果是Externalmonitor,就会是HW_VSYNC_ON_1。当SF要求HWComposer将Display的VSync打开或关掉时,这个event就会记录下来;
  • HW_VSYNC_0:代表PrimaryDisplay的VSync发生时间点, 0同样代表display编号。其用来调节Vsync-app和Vsync-sfevent的输出;
  • Vsync-app:App,SystemUI和systemserver 等viewdraw的触发器;
  • Vsync-sf:Surfaceflinger合成画面的触发器。

  通常为了避免Tearing的现象,画面更新(Flip)的动作通常会在VSync开始的时候才做,因为在VSync开始到它结束前,Display不会把framebuffer资料显示在display上,所以在这段时间做Flip可以避免使用者同时看到前后两个部份画面的现象。目前user看到画面呈现的过程是这样的,app更新它的画面后,它需要透过BufferQueue通知SF,SF再将更新过的app画面与其它的App或SystemUI组合后,再显示在User面前。在这个过程里,有3个component牵涉进来,分别是App、SF、与Display。以目前Android的设计,这三个Component都是在VSync发生的时候才开始做事。我们将它们想成一个有3个stage的pipeline,这个pipeline的clock就LCD的TE信号(60HZ)也即HW_VSYNC_0。

  我们来看看android draw的pipeline,如下:

《(转载)SurfaceFlinger之VSync工作原理》

  1. T = 0时, App正在画N, SF与Display都没内容可用
  2. T = 1时, App正在画N+1, SF组合N, Display没Buffer可显示
  3. T = 2时, App正在画N+2, SF组合N+1, Display显示N
  4. T = 3时, App正在画N, SF组合N+2, Display显示N+1
  5. ……

  如果按照这个步骤,当user改变一个画面时,要等到2个VSync后,画面才会显示在user面前,latency大概是33ms (2个frame的时间)。但是对大部份的操作来讲,可能app加SF画的时间一个frame(16.6ms)就可以完成。因此,Android就从HW_VSYNC_0中产生出两个VSync信号,VSYNC-app是给App用的,VSYNC-sf是给SF用的, Display则是使用HW_VSYNC_0。VSYNC-app与VSYNC-sf可以分别给定一个phase,简单的说:

VSYNC-app = HW_VSYNC_0 + phase_app

VSYNC-sf =HW_VSYNC_0 + phase_sf

  从而使App draw和surfaceflinger的合成,错开时间运行。这样就有可能整个系统draw的pipeline更加有效率,从而提高用户体验。

《(转载)SurfaceFlinger之VSync工作原理》

  也就是说,如果phase_app与phase_sf设定的好的话,可能大部份user使用的状况, App+SF可以在一个frame里完成,然后在下一个HW_VSYNC_0来的时候,显示在display上。

  理论上透过VSYNC-sf与VSYNC-app的确是可以有机会在一个frame里把App+SF做完,但是实际上不是一定可以在一个frame里完成。因为有可能因为CPU调度,GPUperformance不够,以致App或SF没办法及时做完。但是即便如此,把app,surfaceflinger和displayVsync分开也比用一个Vsync来trigger appdraw,surfaceflinger合成,display在LCD上draw的性能要好很多(FPS更高)。因为如果放在同一时间,CPU调度压力更大,可能会卡顿、延迟。

  那么是否我们收到LCD的Vsync event就会触发Vsync-app和Vsync-SF呢?如果是这样我们就不会看到本文开头的Vsync-app和Vsync-SF的节奏不一致的情况(红色框2比红色框3稀疏)。事实上,在大多数情况下,APP的画面并不需要一直更新。比如我们看一篇文章,大部份时间,画面是维持一样的,如果我们在每个LCD的VSYNC来的时候都触发SF或APP,就会浪费时间和Power。可是在画面持续更新的情况下,我们又需要触发SF和App的Vsync event。例如玩game或播影片时。就是说我们需要根据是否有内容的更新来选择Vsync event出现的机制。

  Vsync event 的触发需要按需出现。所以在Android里,HW_VSYNC_0,Vsync-sf和Vsync-app都会按需出现。HW_VSYNC_0是根据是否需要调节sf和app的Vsync event而出现,而SF和App则会call requestNextVsync()来告诉系统我要在下一个VSYNC需要被trigger。也是虽然系统每秒有60个HW VSYNC,但不代表APP和SF在每个VSYNC都要更新画面。因此,在Android里,是根据SoftwareVSYNC(Vsync-sf,Vsync-app)来更新画面。Software VSYNC是根据HWVSYNC过去发生的时间,推测未来会发生的时间。因此,当APP或SF利用requestNextVsync时,Software VSYNC才会触发VSYNC-sf或VSYNC-app。

  下面我们就继续跟着代码,分析VSync信号工作流程。

VSync工作流程

整体概述

  我们知道Vsync是android display系统的重要基石,其驱动android display系统不断的更新App侧的绘画,并把相关内容及时的更新到LCD上.其包含的主要代码如下:

  • DispSync.cpp:这个class包含了DispSyncThread,是SW-SYNC的心脏,所有的SW-SYNCevent均由其产生。在Android系统中,只有一个DispSync;
  • SurfaceFlinger.cpp:这个类主要处理layer的合成,它合成好相关的layer后发送command给HW display进行进行显示;
  • DispSyncSource:Vsync source 在Android系统中有两个instance,一是Vsync-app,另一个Vsync-sf。当SW-SYNC发生时,Vsyncsource会callback到其相应的EventThread,并且会在Systrace上显示出Vsync-sf和Vsync-app的跳变;
  • EventThread.cpp:Vsync event处理线程,在系统中有两个EventThread,一个用于Vsync-app,另一个用于Vsync-sf。其记录App和SurfaceFlinger的requestnextVsync()请求,当SW-VSYNCevent触发时,EventThread线程通过BitTube通知App或SurfaceFlinger,App开始drawview,SurfaceFlinger 开始合成 dirty layer;
  • MessageQueue.cpp:MessageQueue 主要处理surfaceflinger的Vsync请求和发生Vsync事件给surfaceFlinger;
  • BitTube.cpp:Vsync 事件的传输通道。App或Surfaceflinger首先与EventThread建立DisplayEventConnection(EventThread::Connection::Connection,Connection 是BnDisplayEventConnection子类,BitTube是其dataChannel)。App或surfaceFlinger通过call DisplayEventConnection::requestNextVsync()(binder 通信)向EventThread请求Vsync event,当EventThread收到SW-VSYNCevent时,其通过BitTube把Vsync evnet发送给App或SufaceFlinger;
  • BufferQueueProducer.cpp:在Vsync架构中,其主要作用是向EventThread请求Vsync-sfevent。当App画完一个frame后,其会把画完的buffer放到bufferqueue中通过call BufferQueueProducer::queueBuffer(),进而surfaceflinger进程会通过call DisplayEventConnection:: requestNextVsync()向EventThread请求Vsync event。

  Vsyncevent产生的示意图如下:

《(转载)SurfaceFlinger之VSync工作原理》

  App需要draw一个frame时,其会向EventThread(appEventThread)请求Vysncevent,当EventThread收到Vsync event时,EventThread通过BitTueb把Vsync event通知到App,同时跳变systrace中的Vsync-app。App收到Vsync-app,开始draw frame。当App画完一个frame后,把画好的内容放到bufferqueue中,就会要求一个Vsync-sf event,以便surfaceflinger在收到Vsync-sf event时合成相关的dirty的内容并通知DisplayHW。Display HW 会在下一个HWvsync时间点,把相关的内容更新到LCD上完成内容的更新。

  下面是其大概的流程图:

《(转载)SurfaceFlinger之VSync工作原理》

  1. (SF-1)APP 画完一个frame以后,就会把其绘画的buffer放到buffer queue中.从生产者和消费者的关系来看,App是生产者,surfaceflinger进程是消费者.
  2. (SF-2)在SurfaceFlinger 进程内,当收到有更新的可用的frame时(需要render到LCD ),就会向EventThread请求Vsync event(requestNextVsync()).EventThread会记录下此请求,当下次Vsync event到来时,便会triggerVsync-sf event.
  3. DispSync 不断产生SW-VSYNCEVENT.当SW-VSYNCEVENT产生时,会检查是否有EventListener关心该event(SF和APP请求Vsync 时会在DispSync中创建EventListener并把其相应的DispSyncSource作为EventListener的callback),如有则调用EventListenercallback(DispSyncSource)的onDispSyncEvent.(callbacks[i].mCallback->onDispSyncEvent(callbacks[i].mEventTime)).
  4. DispSyncSource会引起Vsync-sf或Vsync-app跳变,当onDispSyncEvent()被调用时,并且会直接调用EventThread的onVSyncEvent().
  5. EventThread会把Vsync-sf或Vsync-app event通知到App或surfaceFlinger当Vsync-sfevent产生时(callonDispSyncEvent),surfaceflinger进程合成dirty layer的内容(SF-5)并通知Display HW把相关的更新到LCD上.App则在Vsync-ap时开始drawview.
  6. Display HW 便会在HW 的vsync 到来时,更新LCD 的内容(SF-6).
  7. 如果有Layer 的内容需要更新,surfaceflinger 便会把相关内容合成在一起,并且通知DisplayHW ,有相关的更新内容.

程序实现

  我们来看看其代码的实现,继续回到SurfaceFlinger.cpp中的init函数:

  当surfaceFlinger初始化时,其创建了两个DispSyncSource和两个EventThread,一个用于APP,而另一个用于SurfaceFlinger本身。我们知道DispSyncSource和DispSync协同工作,共同完成Vsyncevent的fire。而EventThread会trigger App draw frame和surfaceFlinger合成”dirty” layer在SW-VSYNCevent产生时。

《(转载)SurfaceFlinger之VSync工作原理》

  上面图形是Vsync信号从产生到驱动SF去工作的一个过程,其中绿色部分是HWC中的Vsync信号软件模拟进程,其他都是SF中的:

  其中包含4个线程,EventControlThread,就像是Vsync信号产生的闸刀,当然闸刀肯定需要人去打开和关闭,这个人就是SF;

  VsyncThread,下面的代码只介绍软件模拟的Vsync信号,这个线程主要工作就是循环定期产生信号,然后调用SF中的函数,这就相当于触发了;

  DispSyncThread,是Vsync信号的模型,VsyncThread首先触发DispSyncThread,然后DispSyncThread再去驱动其他事件,它就是Vsync在SF中的代表;

  EventThread,具体的事件线程,由DispSyncThread去驱动。

  我们接下来根据Vsync-sf先关分析一下流程

DispSync和DispSyncThread

  从上面SurfaceFlinger的init函数中,在构造DispSyncSource会传入一个mPrimaryDispSync的引用,它是SF类中有一个field为DispSync mPrimaryDispSync,因此在SF类创建时会在栈上生成这个对象。我们首先看看这个类的构造函数:

  在DispSync 构造函数中,创建了DispSyncThread线程,并运行。DispSyncThread实现也位于DispSync.cpp中:

  这里面将mStop设置为false,mPeriod设置为0,还有其他的也置为0。因为DispSyncThread是Thread派生的,所以我们需要看看它的线程函数:

  DispSyncThread的threadLoop函数,主要这个函数比较计算时间来决定是否要发送信号。主要工作如下:

  1. 模型线程DispSyncThread阻塞在mCond,等待别人给mPeriod 赋值和signal;
  2. gatherCallbackInvocationsLocked函数获取本次VSync信号的回调函数列表,这些回调函数是通过DispSync类的addEventListener函数加入的;
  3. 接着就调用fireCallbackInvocations来依次调用列表中所有对象的onDispSyncEvent函数。

  新建的DispSyncThread线程,目前被阻塞,先不管,我们先看模型DispSync和要驱动的事件(DispSyncSource,EventThread等)是如何联系起来的。

DispSyncSource和EventThread

  在SF的init函数中,有如下代码,涉及到了DispSync,DispSyncSource,EventThread和mEventQueue的纠缠关系。我们可以看到在驱动事件DispSyncSource的构造中,我们输入了模型DispSync,这样就为回调创造了机会,下面看具体如何实现的。

  这个我们分布分析:
  1)先看下DispSyncSource对象的建立,其实这个对象从名字上看就是模型所驱动的事件。我们看看它的构造函数,也位于SurfaceFlinger.cpp中:

  构造函数中主要设置了模型mDispSync(dispSync),以及触发时间的偏移量mPhaseOffset(phaseOffset)。这个实参vsyncPhaseOffsetNs和sfVsyncPhaseOffsetNs定义如下,也位于SurfaceFlinger.cpp中:

  这个phase就是用来调节vsync信号latency 时间,其定义我也是搜了整个工程才找到如下定义:

《(转载)SurfaceFlinger之VSync工作原理》

  generic版本中phase-app为0,phase-sf为5000000。(不知道对不对=。=)

  2)我们继续往下看,mSFEventThread = new EventThread(sfVsyncSrc);先看看EventThread的构造函数:

  EventThread是Thread的派生类,还是RefBase的间接派生类,所以我们需要产看它的onFirstRef函数:

  将这个线程启动了起来,那么我们需要看下线程循环函数:

  这个线程函数里面主要做了三件事:

  1. 调用waitForEvent等待事件到来;
  2. 把事件分发给listener;
  3. 然后调用每个连接的postEvent来发送Event。

  我们分部查看,先看看waitForEvent实现:

  waitForEvent函数中下面代码段,timestamp为0表示没有时间,waitForSync为true表示至少有一个客户和EventThread建立了连接。这段代码一旦有客户连接,就调用enableVSyncLocked接收DispSyncSource的VSync信号。如果在接受信号中,所有客户都断开了连接,则调用disableVSyncLocked函数停止接受DispSyncSource对象的信号。

  虽然代码量有些多,但是我们是初始化时候,所以省去了很多判断逻辑。因此主要工作就不多了,主要如下:
1. 初始的时候,mCondition会阻塞;
2. 因为mCondition有可能异常返回,所以要看下外围的while循环,就知道何时mCondition会被signal。即使mCondition异常返回,也会再去判断signalConnections是否为空。空的话继续阻塞,如果signalConnections不为空了,这时候就会从while中退出来,也就是上面的mDisplayEventConnections有东西了。所以mDisplayEventConnections需要留意何时赋值啦。

  至此,创建了一个mSFEventThread = new EventThread(sfVsyncSrc);,也阻塞着了。。

  至于下面的 2. 把事件分发给listener;3. 然后调用每个连接的postEvent来发送Event。这两个步骤我们下面会讲到,这里先不看它。

  3)从上面得知在DsipSync里面创建的DispSyncThread线程阻塞等待着;还有上面的EventThread线程,也阻塞等待了。

  下面我们继续看mEventQueue.setEventThread(mSFEventThread);会不会解除阻塞。这里EventThread和SF主线程的MessageQueue又纠缠到了一起:

  我们依然分部查看:

  1. 首先, eventThread->createEventConnection(),新建了一个Connection对象:

  Connection对象如下图所示:

《(转载)SurfaceFlinger之VSync工作原理》

  Connection中保存了了一个EventThread对象,和一个生成的BitTube对象mChannel,下面看下Connection的构造函数:

  调用了BitTube的无参构造函数:

  构造函数里调用了init函数:

  建立一个域套接字,用fcntl设置成非阻塞模式,然后两个fd,一个读一个写,mSendFd和mReceiveFd。

  我们上面创建一个Connection,但是又是一个sp指针,所以就得看看它的onFirstRef函数:

  这里又调用EventThread的registerDisplayEventConnection函数,我们继续查看:

  前面提到,EventThread一直阻塞在waitForEvent中,正是这个mCondition,这里也对mDisplayEventConnections添加了东西,不为空了。

  为了方便分析,我们再把前面的waitForEvent函数列出来,这次就是另一套逻辑了:

  所以在waitEvent里,这次的逻辑和上次不同了,主要是一下几点:

  1. 建立Connection后,mCondition返回了;
  2. 这里timestamp 还是0 ;
  3. mDisplayEventConnections非空了,将waitForVSync = true;
  4. 所以会执行到enableVSyncLocked();。

  那么我们看看enableVSyncLocked函数:

  这个也分两步:

  ①将mVsyncEnabled 设为true,调用mVSyncSource->setCallback(static_cast(this));,this为EventThread,既是调用DispSyncSource的setCallback,把mCallback设置为EventThread:

  ②调用mVSyncSource->setVSyncEnabled(true);,既是调用DispSyncSource的setVSyncEnabled:

  从上面的代码可以看出,这里是将驱动事件DispSyncSource和硬件模型mDispSync建立起关系。既然调用了DispSync的addEventListener,那么我们就继续查看:

  这里可以看出,驱动事件DispSyncSource是硬件模型DispSync的“listener”,监听者,把两者联系了起来。并把DispSyncThread线程中的阻塞mCond解除,但是,前面我们分析过,还要mPeriod 非0。我们可以回顾一下上面的代码片段,DispSyncThread的threadLoop函数:

  那么哪里给mPeriod 赋值呢?我们上面讲EventControlThread闸刀控制线程的时候,initializeDisplays()函数会被调用,最终会调用resyncToHardwareVsync函数,这个里面会获取mPeriod屏幕刷新率,然后给mPeriod 赋值:

  然后调用DispSync的setPeriod函数:

  mPeriod 赋值后,已经不为0;然后调用线程的更新模型函数updateModel:

  至此,DispSync中设置了监听者DispSyncSource,mPeriod 也不为0,硬件模型线程不再阻塞,不阻塞就会去处理vsync事件,这个我们后面会讲。

  经过调用mEvents = eventThread->createEventConnection();完成了一下几个功能:

  1. MessageQueue中保存了一个Connection,mEvents ;
  2. EventThread中保存了这个Connection,mDisplayEventConnections.add(connection);
  3. mVSyncSource->setCallback 把mCallback = callback 设置为EventThread;
  4. 在mDispSync中注册listener, 放到DispSyncthread的mEventListeners中,这个listener的callback就是mVSyncSource。

  2. 然后就是创建通信通道。接着继续MessageQueue::setEventThread()函数,调用mEventTube = mEvents->getDataChannel()。

  mEvents类型为sp< IDisplayEventConnection > ;在上一步mEvents = eventThread->createEventConnection();返回的直接是Connection对象。Connection继承于BnDisplayEventConnection,BnDisplayEventConnection继承于BnInterface< IDisplayEventConnection >。因此我们找到bp端的getDataChannel函数,位于BpDisplayEventConnection类当中,继承于BpInterface< IDisplayEventConnection >位于frameworks/native/libs/gui/IDisplayEventConnection.cpp:

  相应的bn端位onTransact方法于也位于IDisplayEventConnection.cpp:

  上面那个getDataChannel是Connection类的函数,我们看看Connection的getDataChannel函数:

  mChannel就是我们在构造函数里面的new BitTube()。

  这样mEventTube 中只包含了读fd,而mEvents这个connection中的mChannel只剩下写fd,两个依然是一对读写,但是分开了,如下图所示:

《(转载)SurfaceFlinger之VSync工作原理》

  3. 继续调用,这里就是把mEventTube这个读tube注册到SF主线程的Looper中去,回调函数为MessageQueue::cb_eventReceiver:

  这里用到了Android消息处理零散分析的内容,如果忘记了,可以翻一翻。

  因为注册了回调事件,因此我们需要查看MessageQueue::cb_eventReceiver函数:

  然后是eventReceiver函数:

  这里会从MessageQueue的读BitTube中读出event,然后调用mHandler->dispatchInvalidate():

  接收消息就是handleMessage函数:

  进而去调用SF的onMessageReceived函数,最终每次Vsync信号来了,SF都会去执行handleMessageTransaction()等函数。

  至此我们对于vsync消息的准备逻辑工作都做完了。但是细心的读者应该发现了,我们还没有按正常流程处理一遍vsync信号,就是当vsync产生后,SurfaceFlinger的回调函数onVSyncReceived处理过程。接下来我们就分析一下这个过程。

VSync信号处理

矫正VSync时间

  我们再次借助上面那幅Vsync event产生的示意图:

《(转载)SurfaceFlinger之VSync工作原理》

  这里HW_VSYNC就是HW_VSYNC_0。

  当SF从HWComposer收到VSYNC(HW_VSYNC_0)时,会调用onVSyncReceived函数处理:

  它会利用DispSync::addResyncSample将新的VSYNC时间交给DispSync。addResyncSample决定是否还需要HW_VSYNC的输入,如果不需要,就会将HW_VSYNC关掉。

  然后我们继续查看DispSync::addResyncSample:

  DispSync是利用HW_VSYNC和PresentFence来判断是否需要开启HW_VSYNC。HW_VSYNC最少要3个,最多是32个,实际上要用几个则不一定,DispSync拿到3个HW_VSYNC后就会计算出SW_VSYNC,只要收到的PresentFence没有超过误差,则HW_VSYNC就会关掉,以便节省功耗.不然会继续开启HW_VSYNC计算SW_VSYNC的值,直到误差小于threshold。其计算的方法是DispSync::updateModelLocked():

  基本思想如下:

  1. 计算目前收到HW_VSYNC间隔,取平均值(AvgPeriod) HW_VSYNC;
  2. 将每个收到的VSYNC时间与AvgPeriod算出误差. (Delta = Time %AvgPeriod);
  3. 将Delta转换成角度(DeltaPhase),如果AvgPeriod是360度,DeltaPhase = 2PIDelta/AvgPeriod;
  4. 从DeltaPhase可以得到DeltaX与DeltaY (DeltaX =cos(DeltaPhase), DeltaY = sin(DeltaPhase));
  5. 将每个收到的VSYNC的DeltaX与DeltaY取平均,可以得到AvgX与AvgY;
  6. 利用atan与AvgX, AvgY可以得到平圴的phase (AvgPhase);
  7. AvgPeriod + AvgPhase就是SW_VSYNC。

《(转载)SurfaceFlinger之VSync工作原理》

  当DispSync收到addPresentFence时(最多记录8个sample),每一个fence的时间算出(Time% AvgPeriod)的平方当作误差,将所有的Fence误差加总起来如果大于某个Threshold,就表示需要校正(DispSync::updateErrorLocked)。校正的方法是呼叫DispSync::beginResync()将所有的HW_VSYNC清掉,开启HW_VSYNC。等至少3个HW_VSYNC再重新计算。

处理VSync消息

  消息处理流程依然需要分步骤:

  1)我们继续回到DispSync的threadLoop函数中,回顾上面的内容,因为在这里我们需要搜集vsync触发事件:

  mPeriod 不为0,也有signal,DispSyncThread线程不阻塞了,执行gatherCallbackInvocationsLocked(now)和fireCallbackInvocations(callbackInvocations)。我们先查看gatherCallbackInvocationsLocked函数:

  然后发送事件,fireCallbackInvocations函数:

  这里会回调DispSyncSource的onDispSyncEvent函数:

  继续调用,这里已经从驱动事件,转化到驱动事件的线程EventThread中,填充EventThread的mVSyncEvent,因此我们查看onVSyncEvent函数:

  这里将mVSyncEvent事件相关属性复制,且threadLoop等待阻塞唤醒,以便往下继续执行。

  2)EventThread的waitForEvent(),返回signalConnections,就是开始建立的Connection,这个Connection里面有个BitTube的写fd,另外的读fd在MessageQueue中。

  此时我们回到上面没有讲的,当waitForEvent返回后,2. 把事件分发给listener;3. 然后调用每个连接的postEvent来发送Event。这两个步骤。

  waitForEvent返回,调用conn->postEvent(event),我们查看Connection的postEvent函数:

  也就是通过Connection的写fd将event发送给MessageQueue。

  这时候MessageQueue的looper epoll返回,最终会去调用response.request.callback->handleEvent,最终调用的是mCallback(fd, events, data);而这个mCallback,既是MessageQueue::cb_eventReceiver。这一步可以查看Android消息处理零散分析的内容,如果忘记了,可以翻一翻。

  所以最终还是回到了MessageQueue::cb_eventReceiver:

  这个步骤上面分析过了。最终去调用SF的onMessageReceived函数,最终每次Vsync信号来了,SF都会去执行handleMessageTransaction()等函数。

  这就是vsync信号的处理过程,和上面章节的结合在了一起。

VSync实例

重要点小结

  我们列出了EventThread的重要function.下面一一说明其功能:

  • EventThread::Connection::Connection():Connection的构造函数.用于进程间的通信by BitTube..在此处主要是搭建一个通路(BitTube)来完成client(App或SurfaceFlinger)对Vsync event事件的请求(通过requestNextVsync())和EventThread把SW-Vsync event callback到其感兴趣的client。需要注意的是App是通过SurfaceFlinger::createDisplayEventConnection()创建此连接的,而sufaceflinge是在其初始化时call EventQueue.setEventThread(mSFEventThread)创建的。所以对App 的EventThread 来说可能有多个connection ,也有可能没有,而对sufaceflinger目前来说有且只有一个;
  • spEventThread::createEventConnection():创建 Connection连接;
  • status_tEventThread::registerDisplayEventConnection():如其名所描述.其功能是把创建的Connection注册到一个容器中。当SW-VSYNCevent发生时,EventThread会从Connection注册的容器中,找到那些对SW-VSYNC event感兴趣的connection并把vsyncevent通过BitTube传到client;
  • void EventThread::requestNextVsync():Clinet 端通过Connection call 这函数通知EventThread,其对SW-SYNCevent的请求;
  • voidEventThread::onVSyncEvent(nsecs_t timestamp):SW-VSYNCEVENT 发生时,DispSyncSource 会call此函数,告知EventThread,Vsync event已经发生,如果此时有connect对Vsync感兴趣,EventThread便会通过connect->postEvent(event)把Vsync事件发送到client端(App或surfaceflinger);
  • bool EventThread::threadLoop():线程的主体函数.其完成两件事,一是把对SW-VSYNC event有请求并且还没有处理的connect找出来,二是把Vsyncevent通过connect通知到client;
  • Vector<sp> EventThread::waitForEvent():EventThread 的主要功能都在此函数里,此函数由threadLoop()调用。EventThread在大部分时间里是sleep的,如果系统的性能比较好,那么其sleep的节奏是和SW-VSYNC event的节奏一致,即16.6mssleep一次。然而由于其App或surfaceflinger没有Vsync的请求,其sleep的时间为更长。此函数的名为waitForEvent,其到底在等什么event?原来此函数在等待的event就是Dispsync产生的SW-SYNC event,其功能check所有的connect是否有Vsync事件请求根据不同的情况做如下处理。
  1. 所有的connect都没有Vsync请求,则其通过disableVSyncLocked(),disableVsync event,那么此EventThread将不会收到SW-SYNCevent,一直sleep直到有connect有Vsync请求为止。
  2. 在所有的connect中,有SW-SYNC event请求,但是当其请求SW-SYNCevent时,SW-SYNCevent还没有fire,则其通过enableVSyncLocked() enable Vsync并进入sleep。当下一个SW-SYNCevent来到时,便把所有有SW-SYNCevent请求的connection返回给threadLoop。

  下图是整个流程的大图:

《(转载)SurfaceFlinger之VSync工作原理》

   我们接下来依然以gallery3d(相册)为例子,分析Vsync-app和Vsync-sf的工作流程。

《(转载)SurfaceFlinger之VSync工作原理》

  1. App 通过Connection向EventThread请求SW-VSYNCevent(最终并且通过callEventThread::requestNextVsync()).一般来说App是通过Choreographer.doScheduleVsync()来请求Vsyncevent.
  2. DispSync 产生SW-VSYNC event并call DispSyncSource的onDispSyncEvent().
  3. DispSyncSource产生VSYNC-app信号跳变.
  4. 同时DispSyncSource使EventThread(app)的waitForEvent()结束sleep,并把VSYNC-app通知到App进程(gallery3d).
  5. Gallery3d 进程收到VSYNC-app信号,在其main线程(UI Thread)中开始draw一个frame(发送draw command给GL thread).
  6. GL Thread 开始draw view.
  7. 当GL thread 线程Draw 完一frame后,把draw buffer放到buffer queue中.
  8. SufaceView中画好的buffer 数据增加为1.这个buffer 将被surfaceflinger 在适当的时候合成.

Vsync-sf实例

《(转载)SurfaceFlinger之VSync工作原理》

  1. App draw 完一个frame (SurfaceView) 并且把此frame通过调用BufferQueueProducer::queueBuffer()放到 bufferqueue中,并且通过call EventThread::requestNextVsync(),请求SW-VSYNCevent.此时由于DispSync没有fireSW-Sync event所以EventThread在EventThread::waitForEvent()中sleep.
  2. DispSync触发SW-SYNC event信号并且call DispSyncSource::onDispSyncEvent().
  3. 在DispSyncSource::onDispSyncEvent()中首先引起Vsync-sf跳变,然后在call EventThread::onVSyncEvent(nsecs_ttimestamp).
  4. 在EventThread::onVSyncEvent(nsecs_ttimestamp)中,会唤醒EventThread::waitForEvent()中的sleep,从而发送消息到Surfaceflinger.
  5. SurfaceFlinger收到EventThread发过来的Vsync-sf跳变信息,开始合成dirty layer, 本例是SurfaceView.合成完以后发送更新的消息到DisplayHW.
  6. Display HW 收到更新的消息时,HW vsync event还没有来,因此DisplayHW sleep了一段事件.当HWvsync event到来时,DisplayHW 便更新其LCD的内容.把最新的内容显示在LCD上.

  自此我们完成了Vsync子系统的分析。

性能影响

  回到系统性能上,我们可以看出系统的性能(FPS)可能于如下因素有关:

  1. DispSync的调度. DispSync是Vsync系统的心脏,如果DispSync来不及调度,则有可能由于去SW-SYNC event的产生不及时而影响系统的性能.
  2. View Draw 花费了过多的时间也会引性能问题, 如果其不能在一个SW-SYNC时间内完成, 那么此应用就会有一个Janks.由于现在的Androd系统都采用了HWUI,viewdraw往往是GPU直接draw,所以很多时候是GPUperformance问题.
  3. HW display 的性能问题.LCD 上所绘画的内容, 最终需要HW display在HW vsync触发是把其内容显示在LCD上.
  4. Binder 的调度问题, 我们可以看到无论是requestNextVsync () 还是queueBuffer()都是App通过binder与surfaceflinger进程通信的.而这些binder在大多数时间是sleep的.如果binder由于CPU调度而错过了一个Vsync跳变点,那么就有一个frame发生Janks.
    结语

  对于VSync信号的学习就到这里,我们可以看到还是十分复杂的。

转载自:http://windrunnerlihuan.com/2017/05/25/Android-SurfaceFlinger-%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF-%E4%BA%94-VSync-%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注