Vsync垂直同步信号分发和SurfaceFlinger响应执行渲染流程分析(一)

来源:互联网 发布:python windows 路径 编辑:程序博客网 时间:2024/05/15 05:55

     之前有一篇博客已经讲过一点Android应用程序和SurfaceFlinger建立连接,获取一个binder本地代理对象的过程,有了这个本地代理对象,应用程序就可以使用它调用SurfaceFlinger的UI合成、渲染功能了,可参考:Android Application与SurfaceFlinger连接过程分析,讲的很粗糙,但是我们可以看到一个大致过程,就是在WindowManagerService为每个Activity执行添加窗口addView方法时,创建好了WindowState对象,然后会调用win.attch()方法,和SurfaceFlinger取得连接也就是这个时候建立的。如果大家还想了解更细的话,可以参考老罗的博客:Android应用程序与SurfaceFlinger服务的连接过程分析,这里讲的非常详细。

     关于UI合成渲染这块的知识,用到的东西太多了,我也是平时看看书,查查百度,想尽量搞清楚一些,但是目前为止,还是只能了解的部分,无法从全面掌握,也是为了怕自己忘记,所以将自己学习到的总结出来,方便随时查阅,如果能给大家带来帮助,那就更好了。

     好了,我们进入正题,话说Android在4.1版本之前,UI界面方面存在很大的一个问题,就是经常会出现界面错乱,Google官方叫Jank,我们来看看下面这幅图:

绘制过程没有采用Vsync同步的情况

     从图上我们就很明显可以看出来问题产生的原因了,就是我们的CPU、GPU、Display三个器件需要进行配合对所有的UI进行合成、渲染、显示,但是之前的版本当中,没有一个统一的命令,导致CPU不知道何时开始执行合成,于是推迟了时间,后边的渲染和显示自然也就耽误了,那么就会出现数字1的画面显示了两帧,还有可能出现,数字1的一部分和数字2的一部分显示在同一界面上了,Google为了解决这个问题,所以加入了Vsync垂直同步信息,我们来看看加入了Vsync垂直同步信息后的情况:

加入了Vsync之后的情况

     从加入了垂直同步信号后的情况明显可以看出来,情况好了很多,由信号产生模块发出信号,只要信号发出,CPU马上进行合成,完成后交给GPU渲染,最后由Display进行显示,这样的话,就能很好的保证我们的界面显示问题了,当然,任何软件措施都无法保证100%的解决问题,但是从现在实际场景来看效果,确实还是非常满意的。

     现在呢,我们就来了解一下Vsync垂直同步信号。从上面的逻辑图当中,应该也很容易看出来,我们需要的是什么呢?就是一个信号产生器就OK了,由它产生信号,指挥CPU、GPU、Display三者对UI进行同步的合成,这样就够了,那么Google在提供这个功能的时候,提供了两种方式:一种是由硬件产生,一种是由软件产生。接下来我们就来看一下Vsync的生产过程,产生的代码在HWComposer.cpp当中,composer的英文意思就是设计者,看着这个名字都让我们非常清晰它的作用,这也是细节之处,高手写出来的代码,处处都是亮点,小到一个变量、一个方法的修饰词,大到一个类、项目框架,里边有很多值得我们学习的地方。下面我们来看一下这块的代码:     

     首先调用property_get获取系统属性,然后加载硬件模块loadHwcModule,mHwc变量的意思就很明显了,就是当前是否有硬件模块支持,如果有,那么needVSyncThread就赋值为false,表示Vsync信号由硬件产生,不需要软件模拟,否则的话,就需要由软件来产生了。那么如果需要由软件产生的话,就启动一个VSyncThread来执行这个功能。这个方法的意思也非常清晰,就是要确定好Vsync信号的产生源,到底这个事情由硬件来作,还是由软件来作。当然中间还包括一些初始化、设备加载等的逻辑。

     下面我们就分硬件产生和软件产生两个方面来分析一下后续流程:

     一、Vsync由硬件产生

     如果Vsync信号由硬件产生,那么我们在执行初始化的时候,需要注册硬件回调,也就是我们上面代码当中mHwc->registerProcs(mHwc, &mCBContext->procs)这句的作用,mHwc为true,mCBContext是HWComposer的私有类变量,是一个cb_context*指针,它的定义也在HWComposer.cpp当中,代码如下:

     可以看到mCBContext->procs是一个callbacks的回调接口,后期当有事件产生时,如vsync或者invalidate,硬件模块将分别通过procs.vsync或者procs.invalidate来通知HWComposer。这也就是HWComposer.h头文件中定义的hook_invalidate、hook_vsync两个方法的作用,hook就是勾子的意思,在OpenGL的本地化窗口实现SurfaceFlinger、Surface的定义当中,大家也可以看到这样的方法命名,非常形象,就是和硬件勾在一处的。那么如果有Vsync事件到来,就会执行ctx->hwc->vsync(disp, timestamp),我们来看一下它的调用,代码如下:

     这里就是将产生的事件直接传递给mEventHandler,传递什么呢?其实就是一个时间点,我们回头看一下,解决Jank的方案就是要确定好Vsync信号产生的时间点,所以呢,这里由硬件确定好时间点,然后通知各个模块,当然,大家肯定都以系统时间为基点了,这样才能保证统一。我们来看一下mEventHandler是谁创建的。这就联系到SurfaceFlinger的初始化过程了,HWComposer就是在那里创建的,代码如下:

 
     可以看到,在这里创建HWComposer的时候,第二个参数就是要赋值给HWComposer的构造函数中的handler,而且传入的是一个this指针,那就是说当前的SurfaceFlinger就是那个回调的处理类,我们找一下SurfaceFlinger的定义,在SurfaceFlinger.h中,因为定义太长,这里就不引用代码了,可以明显看到SurfaceFlinger就是继承HWComposer::EventHandler,所以它可以在接收到HWComposer传过来的事件,并进行处理。好了,到这里呢,硬件的产生流程我们就清楚了,至于SurfaceFlinger的具体处理,我们放在下一篇博客中进行讲解。
     二、Vsync由软件产生
     软件源和硬件源相比最大的一个区别就是它需要启动一个新的线程VSyncThread,它的运行优先级和SurfaceFlinger是一样的,都是-9,我们先来看一下VSyncThread的定义,代码在HWComposer.h当中:
     可以看到,它是继承Thread的,那么当它启动起来之后,就会执行threadLoop方法去不断的根据时间点产生Vsync信号。在源码当中,可以看到以loop命名的方法是非常多的,就像一个循环一样,不断在执行事件,比如我们应用程序当的主线程,也是一个Looper.loop(),这样才能保证我们的应用程序能响应到所有的事件:包括用户触发的、系统分发的、UI显示的等等。来看一下threadLoop方法是如何通过软件源产生Vsync信号的:
     mEnabled是一个Vsync的信号开关,可以通过它判断系统是否使能了Vsync的软件触发机制,接下来就需要计算Vsync的信号时间点了,mRefreshPeriod是指两个信号之间的时间间隔,它是在VSyncThread启动时,由hwc.getRefreshPeriod(HWC_DISPLAY_PRIMARY)方法进行赋值的,getRefreshPeriod方法实现很简单,就是获取当前mDisplayData[disp].refresh,也就是当前显示器的刷新间隔时间,disp是指第几个显示器,因为Android系统的定义是支持多个显示器的,不过当前的实际使用当中只用到了一个,所以disp的值一般都是0,这也是和Linux当中的显示驱动相对应的,在设备文件系统中对应是就是fb0的设备节点。接下来获取当前时间点,然后是产生信号的时间,得到了产生信号的时间和当前时间,紧接着就可以计算出中间要休眠多久了,如果休眠时间小于0,说明上一个Vsync信号已经被错过了,那么就要重新计算下一次的Vsync的产生时间,那么如何在指定的时间点产生信号呢?有两种方法,第一种方法是采用定时回调,第二种是采用休眠的形式主动等待,从代码中可以看到,HWComposer使用的是后者。一般的显示器的刷新频率是60Hz,也就是说每秒会刷新60次,那么每次所需要的时间大概就是16ms,可以想像,这里对时间的要求是非常高的,所以系统采用更高级别的nanosecond,也就是纳秒,clock_nanosleep传入的第一个参数是CLOCK_MONOTONIC,这种类型的时钟更加稳定,且不受系统时间的影响。最后呢,调用clock_nanosleep进入休眠,当休眠时间到了,就通过mHwc.mEventHandler.onVSyncReceived(0, next_vsync)通知事件的关心者,信号到了,你们需要起床干活了。
     这里有些奇怪了,应该是一个循环才对啊,比如我们在应用主线程中也是通过一个for(::)无限循环来不断的处理事件的,这里为什么只有一个do/while,那么执行一次之后,while循环跳出了,分发一次,后面就不再分发了?这显然是不可能的,在这里的无限循环是通过返回值true来控制的,当threadLoop返回true时,它将会被系统调次调用,也就形成循环了。
     好了,这篇博客,我们就讲到这里了,至于课题当中的SurfaceFlinger对Vsync的事件响应及处理,我们在下一篇当中进行讨论!
     同学们,下课!!

1 0