在前文中,我们分析了SurfaceFlinger服务的启动过程。SurfaceFlinger服务在启动的过程中,会对系统的硬件帧缓冲区进行初始化。由于系统的硬件帧缓冲区一般只有一个,并且不是谁都可以随便访问的,因此,它就需要由一个服务来统一管理。在Android系统中,这个服务便是SurfaceFlinger。在本文中,我们就详细分析SurfaceFlinger服务是如何管理系统的硬件帧缓冲区的。
从前面Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划一文可以知道,SurfaceFlinger服务通过一个GraphicPlane对象来描述系统的显示屏,即系统的硬件帧缓冲区。GraphicPlane类内部聚合了一个DisplayHardware对象,通过这个DisplayHardware对象就可以访问系统的硬件帧缓冲区。DisplayHardware类内部又包含了一个FramebufferNativeWindow对象,这个FramebufferNativeWindow对象才是真正用来描述系统的硬件帧缓冲区的。FramebufferNativeWindow类的作用类似于在前面Android应用程序请求SurfaceFlinger服务创建Surface的过程分析一文中所介绍的Surface类,它是连接OpenGL库和Android的UI系统的一个桥梁,OpenGL库就是通过这个桥梁来将Android系统的UI渲染到硬件帧缓冲区中去的。GraphicPlane、DisplayHardware和FramebufferNativeWindow这三个类的关系如图1所示。
图1 GraphicPlane、DisplayHardware和FramebufferNativeWindow的类关系图
接下来,我们就分别介绍GraphicPlane、DisplayHardware和FramebufferNativeWindow这三个类的实现,以便可以理解SurfaceFlinger服务是如何通过它们来管理系统的硬件帧缓冲区的。
从前面Android系统Surface制的SurfaceFlinger服务的启动过程分析一文可以知道,SurfaceFlinger服务在启动的过程中,会对系统的硬件帧缓冲区进行初始化,如下所示:
这个代码段首先创建了一个DisplayHardware对象,用来初始化编号为0的GraphicPlane对象,接着再将这个DisplayHardware对象设置为系统当前活动的DisplayHardware对象,这就相当于是将编号为0的GraphicPlane对象所描述的显示屏设置为系统当前活动的显示屏。
接下来,我们就首先分析编号为0的GraphicPlane对象的初始化过程,接着再分析DisplayHardware对象的创建过程。
编号为0的GraphicPlane对象的初始化过程主要是调用GraphicPlane类的成员函数setDisplayHardware来实现的,如下所示:
函数首先设置显示屏的初始大小和旋转方向。GraphicPlane类有三个成员变量mDisplayWidth、mDisplayHeight和mDisplayTransform,前两者的类型为float,分别用描述显示屏的初始宽度和高度,而后者的类型为Transform,用来描述显示屏的初始旋转矩阵。Transform类的作用是来描述变换矩阵,以便后面在渲染UI时可以用来动态地计算显示屏的大小和旋转方向等。
显示屏的初始化宽度和高度是由参数hw所描述的一个DisplayHardware对象来描述的,而显示屏的初始旋转方向则是由名称为“ro.sf.hwrotation”的系统属性来决定的。如果没有设置名称为“ro.sf.hwrotation”的系统属性,那么显示屏的旋转方向就为默认方向,即ISurfaceComposer::eOrientationDefault。
获得了显示屏的初始化宽度w、高度h和旋转方向displayOrientation之后,函数接着就调用GraphicPlane类的静态成员函数orientationToTransfrom来将它们构造成一个变换矩阵,并且保存在GraphicPlane类的成员变量mDisplayTransform中。
函数接下来继续判断显示屏的初始化旋转方向是否将初始化宽度和高度值翻转了。如果翻转了,那么就需要相互调换GraphicPlane类的成员变量mDisplayWidth和mDisplayHeight的值,以便可以正确地反映显示屏的初始化宽度和高度。
注意,显示屏的初始宽度、高度和旋转方向一经初始化之后,就会保持不变,以后显示屏的实际旋转方向计算都是要在此基础上进行计算的,即要在变换矩阵mDisplayTransform的基础上进行计算。从这里还可以看出,通过设置名称为“ro.sf.hwrotation”的系统属性的值,就可以设置系统显示屏的初始化旋转方向,以便匹配实际的硬件帧缓冲区的旋转方向。
函数最后调用GraphicPlane类的成员函数setOrientation来设备显示屏的实际度度、高度以及旋转方向,如下所示:
参数orientation的值等于ISurfaceComposer::eOrientationDefault,即SurfaceFlinger服务在初始化系统显示屏时,会将它的旋转方向设置为默认值,以后再根据实际情况来做调整。
GraphicPlane类有三个成员变量mWidth、mHeight和mOrientation,它们的类型均为int,分别用来描述显示屏的实际宽度、高度和旋转方向。与成员变量mDisplayWidth、mDisplayHeight和mDisplayTransform所描述的显示屏初始化宽度、高度和旋转矩阵一经初始化后就保持不变不同,mWidth、mHeight和mOrientation这三个成员变量是会动态变化的。例如,当显示屏由LANDSCAPE变为PORTRAIT模式时,mWidth、mHeight和mOrientation这三个成员变量就会相应地发生改变。
函数首先将显示屏的实际宽度mWidth、高度mHeight和旋转方向mOrientation设置为显示屏的初始宽度mDisplayWidth、高度mDisplayHeight以及参数orientation所描述的旋转方向,接着再调用GraphicPlane类的静态成员函数orientationToTransfrom来将它们构造成一个变换矩阵orientationTransform。
函数接着判断显示屏的实际旋转方向orientation是否将原来的实际宽度和高度值翻转了。如果翻转了,那么就需要相互调换GraphicPlane类的成员变量mWidth和mHeight的值,以便可以正确地反映显示屏的实际宽度和高度。
函数最后将用来描述显示屏的初始化旋转方向的变换矩阵mDisplayTransform和用来描述显示屏的实际旋转方向的变换矩阵orientationTransform相乘,就可以得到一个全局变换矩阵,并且保存在GraphicPlane类的成员变量mGlobalTransform中。这样以后渲染UI时,对于一个任意的点向量,只要将它乘以全局变换矩阵mGlobalTransform,那么就可以得到它所描述的实际位置。
至此,编号为0的GraphicPlane对象的初始化过程就完成了,以后SurfaceFlinger服务就可以调用它的成员函数displayHardware来获得它内部的一个DisplayHardware对象,如下所示:
接下来,回到前面SurfaceFlinger类的成员函数readyToRun中,我们通过DisplayHardware对象的创建过程来分析DisplayHardware类的实现。
在创建DisplayHardware对象的过程中,会调用DisplayHardware类的构造函数,如下所示:
从这里可以看出,DisplayHardware类是从DisplayHardwareBase类继承下来的。接下来,我们首先继续分析一个DisplayHardware对象的初始化过程,接着再分析这个DisplayHardware对象的父对象DisplayHardwareBase的初始化过程,以便可以了解DisplayHardwareBase类的实现。
一个DisplayHardware对象的初始化过程是通过调用DisplayHardware类的成员函数init来实现的。DisplayHardware类的成员函数init的实现比较长,我们分段来阅读:
这段代码接着再加载HAL层中的overlay模块,目的是要打开系统的overlay设备。在Android系统中,我们可以将overlay看作是一种特殊的Surface,一般用来显示视频。在这一系列文章中,我们暂时不关心overlay设备的实现。
我们接着往下阅读代码:
我们接着往下阅读代码:
这段代码首先调用eglGetDisplay和eglInitialize函数来获得和初始化OpengGL库的默认显示屏,接着再调用EGLUtils::selectConfigForNativeWindow函数来获得前面所创建的一个FramebufferNativeWindow对象所描述的系统主绘图表面的配置信息,并且保存在EGLConfig对象config。有了这些配置信息之后,接下来就可以在硬件帧缓冲区上面创建系统的主绘图表面。
我们接着往下阅读代码:
我们接着往下阅读代码:
这段代码接着还获得系统的主绘图表面的宽度和高度,分别保存在并且保存在DisplayHardware类的成员变量mWidth和mHeight中。
这段代码最后还判断硬件帧缓冲区是否支持部分更新。如果支持的话,就会在调用函数eglSwapBuffers来渲染系统的UI时,不保留后端图形缓冲区的内容,因为保留是有代价的。如果不支持的话,那么就会就会调用函数eglQuerySurface来检查在调用函数eglSwapBuffers来渲染系统的UI时是否需要保留后端图形缓冲区的内容。如果需要的话,那么就会将DisplayHardware类的成员变量mFlags的BUFFER_PRESERVED位设置为1。在保留后端图形缓冲区的内容的情况下,系统就可以支持仅仅渲染那些需要更新的脏区域,这些区域可以是不规则的。然而,实现不规则区域部分更新功能是有代价的,因为每次在渲染UI时,都要将后端图形缓冲区的内容拷贝回那些不在那些需要更新的区域中去,这会导致性能低下。因此,系统一般都不支持不规则区域部分更新功能。
我们接着往下阅读代码:
这段代码用来设备系统的主绘图表面的点密度信息。系统的主绘图表面的点密度信息可以通过名称为“qemu.sf.lcd_density”或者“ro.sf.lcd_density”的系统属性来配置。如果没有配置,那么默认值就为160dpi。
我们接着往下阅读代码:
我们接着往下阅读代码:
这段代码主要用来检查系统的主绘图表面是否支持EGL_ANDROID_swap_rectangle扩展属性。如果支持的话,那么每次在调用函数eglSwapBuffers来渲染UI时,都会使用软件的方式来支持部分更新区域功能,即:先得到不在新脏区域里面的那部分旧脏区域的内容,然后再将得到的这部分旧脏区域的内容拷贝回到要渲染的新图形缓冲区中去,这要求每次在渲染UI时,都要将被渲染的图形缓冲区以及对应的脏区域保存下来。注意,如果系统的主绘图表面同时支持EGL_ANDROID_swap_rectangle扩展属性以及部分更新属性,那么将会优先使用部分更新属性,因为后者是直接在硬件上支持部分更新,因而性能会更好。
我们接着往下阅读最后一段代码:
从这里就可以看出,一个DisplayHardware对象在初始化完成之后,它还不能直接用来渲染系统的UI,因为它所初始化的的绘图表面以及绘图上下文并没有作为当前线程的绘图表面以及绘图上下文。这是由于SurfaceFlinger服务可以同时支持多个DisplayHardware对象,即同时支持多个显示屏造成的。
从前面SurfaceFlinger类的成员函数readyToRun可以知道,当前正在初始化的DisplayHardware对象的编号为0,并且它是在SurfaceFlinger服务的UI渲染线程中创建的,为了可以将它设置系统的主显示屏,即主绘图表面,SurfaceFlinger类的成员函数readyToRun接下来还会调用它的成员函数makeCurrent来将它所里面的绘图表面以及绘图上下文设置为SurfaceFlinger服务的UI渲染线程的绘图表面以及绘图上下文。
DisplayHardware类的成员函数makeCurrent的实现如下所示:
DisplayHardware类的成员函数makeCurrent的实现很简单,它只是通过调用函数eglMakeCurrent来将前面已经创建好的绘图表面以及绘图上下文设置为当前线程的绘图表面以及绘图上下文,即设置为SurfaceFlinger服务的UI渲染线程的绘图表面以及绘图上下文。
系统的硬件帧缓冲区在初始化完成之后,SurfaceFlinger服务以后就可以调用用来描述它的一个DisplayHardware对象的成员函数flip来在它上面渲染系统的UI了,这个成员函数的实现如下所示:
这个函数主要就是调用OpenGL库中的函数eglSwapBuffers来将系统的UI渲染到系统的主绘图表面上去的,即渲染到系统的硬件帧缓冲区上去的。在渲染之前,函数会首先判断系统的主绘图表面是否支持EGL_ANDROID_swap_rectangle扩展属性和部分更新属性。如果支持EGL_ANDROID_swap_rectangle扩展属性,即DisplayHardware类的成员变量mFlags的SWAP_RECTANGLE位等于1,那么就需要调用函数eglSetSwapRectangleANDROID来设置要渲染的区域,以便在渲染UI时,可以通过软件的方式来支持部分更新。如果硬件帧缓冲区直接支持部分更新属性,即DisplayHardware类的成员变量mFlags的PARTIAL_UPDATES位等于1,那么就需要调用DisplayHardware类的成员变量mNativeWindow所描述的一个本地窗口的成员函数setUpdateRectangle来设置要更新的那一部分区域。
DisplayHardware类的成员函数flip在调用函数eglSwapBuffers来渲染UI之前,实际上需要通过其成员变量mNativeWindow所描述的一个本地窗口(FramebufferNativeWindow)来获得一个空闲的图形缓冲区,然后才可以将UI数据写入到这个空闲的图形缓冲区中去,最后再渲染到硬件帧缓冲区中去。前面提到,FramebufferNativeWindow类的作用类似于在前面Android应用程序请求SurfaceFlinger服务创建Surface的过程分析一文中所介绍的Surface类,不过它里面所维护的图形缓冲区是直接在硬件帧缓冲区上创建的,后面我们在分析FramebufferNativeWindow类的实现时,再详细分析。
至此,我们就分析完成DisplayHardware类的实现了,接下来我们还需要继续介绍它的父类DisplayHardwareBase的实现,以便可以了解DisplayHardware类的另外一个作用,即它还会创建一个线程来监控硬件帧缓冲区的睡眠和唤醒事件。分析完成DisplayHardwareBase类的实现之后,我们最后再分析FramebufferNativeWindow类的实现。
前面在分析DisplayHardware类的实现时提到,DisplayHardware对象在创建的过程中,会对其父类对象DisplayHardwareBase进行初始化,因此,接下来我们就从DisplayHardwareBase对象的初始化过程入手,来分析DisplayHardwareBase类的实现。不过,在分析DisplayHardwareBase类的实现之前,我们首先看看它的类关系图,如图2所示。
图2 DisplayHardwareBase类关系图
DisplayHardwareBase类一方面用来控制SurfaceFlinger服务当前是否能够访问显示屏。当显示屏处于唤醒状态时,DisplayHardwareBase类的成员变量mScreenAcquired的值就会等于1,表示SurfaceFlinger服务就可以访问显示屏;而当显示屏处于睡眠状态时,DisplayHardwareBase类的成员变量mScreenAcquired的值就会等于0,表示SurfaceFlinger服务不可以访问显示屏。
显示屏的唤醒/睡眠状态切换是由内核来通知DisplayHardwareBase类的,因此,DisplayHardwareBase类会通过一个线程来监控显示屏的唤醒/睡眠状态切换。这个线程是通过DisplayHardwareBase类的成员变量mDisplayEventThread来描述的。DisplayHardwareBase类的成员变量mDisplayEventThread所描述的线程的类型要么是DisplayEventThread,要么是ConsoleManagerThread,这两者均是从DisplayEventThreadBase类继续下来的,而后者又是从Thread类继承下来的。当硬件帧缓冲区的控制台被打开时,DisplayHardwareBase类的成员变量mDisplayEventThread所描述的线程的类型就是DisplayEventThread;当硬件帧缓冲区的控制台没有被打开时,DisplayHardwareBase类的成员变量mDisplayEventThread所描述的线程的类型就是ConsoleManagerThread。这里我们只考虑硬件帧缓冲区的控制台被打开的情况。
用来监控显示屏唤醒/睡眠状态切换的线程是在DisplayHardwareBase对象的初始化过程中创建的,它运行起来之后,就会在一个无限循环中不断地监控显示屏唤醒/睡眠状态切换事件。为了方便描述,我们将这个线程称为控制台事件监控线程。DisplayEventThreadBase类的成员变量mFlinger指向了SurfaceFlinger服务,一旦控制台事件监控线程监控到显示屏发生唤醒/睡眠状态切换,那么就会通过它来通知SurfaceFlinger服务。
控制台事件监控线程的运行过程大概上这样的。在每一次循环中,控制台事件监控线程首先监控显示屏是否要进入睡眠状态了。如果是的话,那么该线程就会通过DisplayEventThreadBase类的成员变量mFlinger来通知SurfaceFlinger服务,并且等待SurfaceFlinger服务处理完成这个通知。SurfaceFlinger服务一旦处理完成显示屏进入睡眠状态的事件,它就会调用DisplayHardwareBase类的成员函数releaseScreen来将其成员变量mScreenAcquired的值设置为0,表示它目前不可以访问显示屏。控制台事件监控线程接下来就会等待显示屏被唤醒过来。一旦显示屏被唤醒过来,那么该线程就会通过DisplayEventThreadBase类的成员变量mFlinger来通知SurfaceFlinger服务。SurfaceFlinger服务得到这个通知之后,就会调用DisplayHardwareBase类的成员函数acquireScreen来将其成员变量mScreenAcquired的值设置为1,表示它目前可以访问显示屏。在下一篇文章分析SurfaceFlinger服务的线程模型时,我们再详细分析这个过程。
DisplayHardwareBase类另一方面用来控制SurfaceFlinger服务当前是否能够在显示屏上渲染UI。当系统的其它组件请求SurfaceFlinger服务关闭显示屏时,SurfaceFlinger服务就会调用DisplayHardwareBase类的成员函数setCanDraw来将其成员变量mCanDraw的值设置为0;而当系统的其它组件请求SurfaceFlinger服务打开显示屏时,SurfaceFlinger服务就会调用DisplayHardwareBase类的成员函数setCanDraw来将其成员变量mCanDraw的值设置为1。只有当DisplayHardwareBase类的成员变量mScreenAcquired和mCanDraw的值均等于1时,SurfaceFlinger服务才可以在显示屏上渲染系统的UI。为了方便SurfaceFlinger服务判断它当前是否可以在显示屏上渲染系统的UI,DisplayHardwareBase类提供了另外一个成员函数canDraw。当DisplayHardwareBase类的成员函数canDraw的返回值等于true时,就表示SurfaceFlinger服务可以在显示屏上渲染系统的UI,否则就不可以。
了解了DisplayHardwareBase类的作用之后,接下来我们就从它的构造函数开始分析它的初始化过程。
DisplayHardwareBase类的构造函数的实现如下所示:
函数首先创建一个类型为DisplayEventThread的线程。如果这个线程能够通过初始化检查,即DisplayEventThread类的成员函数initCheck的返回值等于NO_ERROR,那么SurfaceFlinger服务就会使用这个类型为DisplayEventThread的线程来监控显示屏的睡眠/唤醒状态切换事件,否则的话,函数接下来就会创建另外一个类型为ConsoleManagerThread的线程来监控显示屏的睡眠/唤醒状态切换事件。
DisplayEventThread类的成员函数initCheck的实现如下所示:
kSleepFileName、kWakeFileName、kOldSleepFileName、kOldWakeFileName和kFbconSysDir是五个字符串常量,它们的定义如下所示:
回到DisplayHardwareBase类的构造函数中,最终创建出来的线程对象保存在其成员变量mDisplayEventThread中。DisplayHardwareBase类的mDisplayEventThread是一个类型为DisplayEventThreadBase的强指针,从前面Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析一文可以知道,当一个强指针第一次引用一个对象的时候,这个对象的成员函数onFirstRef就会被调用,因此,接下来我们就继续分析DisplayEventThreadBase类的成员函数onFirstRef的实现,看看它在里面做了一件什么事情。
DisplayEventThreadBase类的成员函数onFirstRef的实现如下所示:
DisplayEventThreadBase类的成员函数onFirstRef主要就调用父类Thread的成员函数run来创建一个名称为“DisplayEventThread”的线程,用来监控显示屏的睡眠/唤醒状态切换事件。这个线程在创建完成之后,首先会调用DisplayEventThread类的成员函数readyToRun来执行一些初始化操作,接下来不断地循环调用DisplayEventThread类的成员函数threadLoop来监控显示屏的睡眠/唤醒状态切换事件。接下来,我们就主要分析这个线程的初始化操作,即DisplayEventThread类的成员函数readyToRun的实现,在接下来的一篇文章中分析SurfaceFlinger服务的线程模型时,再详细分析这个线程监控显示屏的睡眠/唤醒状态切换事件的过程,即DisplayEventThread类的成员函数threadLoop的实现。
DisplayEventThread类的成员函数readyToRun的实现如下所示:
DisplayEventThread类的成员函数readyToRun的实现很简单,它首先判断/sys/power/wait_for_fb_sleep和/sys/power/wait_for_fb_wake文件是否存在。如果存在的话,那么就通过它们来监控显示屏的睡眠/唤醒状态切换事件,否则的话,就通过/sys/android_power/wait_for_fb_sleep和/sys/android_power/wait_for_fb_wake文件来监控显示屏的睡眠/唤醒状态切换事件。如果这四个文件都不存在,那么就说明硬件帧缓冲区的控制台没有被打开了,这时候就不能使用类型为DisplayEventThread的线程来监控显示屏的睡眠/唤醒状态切换事件。
至此,我们就分析完成DisplayHardwareBase类的实现了,接下来我们继续分析FramebufferNativeWindow类的实现,以便可以了解它是如何管理硬件帧缓冲区的。
在分析FramebufferNativeWindow类的实现之前,我们首先看看它的类关系图,如图3所示。
图3 FramebufferNativeWindow类关系
前面提到,FramebufferNativeWindow类与在前面Android应用程序请求SurfaceFlinger服务创建Surface的过程分析一文中提到的Surface类的作用是类似的。FramebufferNativeWindow类一方面用来在OpenGL库和Android本地窗口系统之间建立连接,这样,我们就可以使用它的成员函数dequeueBuffer来为OpenGL库分配空闲图形缓冲区,以及使用它的成员函数queueBuffer来将OpenGL已经填充好UI数据的图形缓冲区渲染到硬件帧缓冲区中去。FramebufferNativeWindow类另一方面还继承了LightRefBase类,因此,从前面Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析一文可以知道,FramebufferNativeWindow类对象可以结合Android系统的轻量级指针sp来使用,以便可以自动维护生命周期。
FramebufferNativeWindow类与Surface类又有不同的地方。从前面Android应用程序请求SurfaceFlinger服务创建Surface的过程分析一文可以知道,Surface类使用的图形缓冲区一般是在匿名共享内存中分配的,并且是由SurfaceFlinger服务来负责分配,然后再传递给应用程序进程使用的,而FramebufferNativeWindow类使用的图形缓冲区是直接在硬件帧缓冲区分配的,并且它可以直接将这些图形缓冲区渲染到硬件帧缓冲区中去。从前面Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析一文可以知道,要从硬件帧缓冲区中分配和渲染图形缓冲区,就必须要将HAL层中的Gralloc模块加载到当前的进程空间来,并且打开里面的gralloc设备和fb设备,其中,gralloc设备用来分配图形缓冲区,而fb设备用来渲染图形缓冲区。因此,FramebufferNativeWindow类包含了一个类型的alloc_device_t*的成员变量grDev和一个类型为framebuffer_device_t*的成员变量fbDev,它们分别指向HAL层中的Gralloc模块的gralloc设备和fb设备。
FramebufferNativeWindow类在内部还包含了一个类型为sp<NativeBuffer>的数组buffers,用来描述OpenGL库可以使用的图形缓冲区,数组的大小等于NUM_FRAME_BUFFERS,即等于硬件帧缓冲区能够提供的图形缓冲区的个数。例如,在Android 2.3系统中,硬件帧缓冲区能够提供的图形缓冲区的个数等于2,这意味着Android系统可以使用双缓冲区技术来渲染系统的UI。由于OpenGL库所使用的图形缓冲区必须要实现android_native_buffer_t接口,因此,NativeBuffer类继承了android_native_buffer_t类。此外,NativeBuffer类还继承了LightRefBase类,因此,它的对象就和FramebufferNativeWindow类对象一样,可以结合Android系统的轻量级指针sp来使用,以便可以自动维护生命周期。
了解了FramebufferNativeWindow类的作用之后,接下来我们就从它的构造函数开始分析它的实现,即分析它的类对象的创建过程。从前面DisplayHardware类的成员函数init的实现可以知道,FramebufferNativeWindow对象是在DisplayHardware对象初始化的过程中创建的,并且包含在DisplayHardware对象内部中,用来管理硬件帧缓冲区。
FramebufferNativeWindow类的构造函数定义在文件frameworks/base/libs/ui/FramebufferNativeWindow.cpp中,它的实现比较长,我们分段来阅读:
这段代码接着判断硬件帧缓冲区是否支持部区更新UI。如果支持的话,那么从Gralloc模块中打开的fb设备的成员函数setUpdateRect就不等于0。这时候这段代码就会FramebufferNativeWindow类的成员变量mUpdateOnDemand的值设置为true。
这段代码最后还会将FramebufferNativeWindow类的成员变量mNumBuffers和mNumFreeBuffers的值均设置为NUM_FRAME_BUFFERS,它们分别用来描述硬件帧缓冲区可以提供的图形缓冲区的个数,以及当前可用的空闲图形缓冲区的个数。此外,FramebufferNativeWindow类的成员变量mBufferHead的值还会被设置为(NUM_FRAME_BUFFERS - 1),表示下一个可用的空闲图形缓冲区在FramebufferNativeWindow类的成员变量buffers所描述的一个图形缓冲区数组的位置。
我们接着往下阅读代码:
我们接着往下阅读代码:
我们接着往下阅读代码:
FramebufferNativeWindow类的成员函数dequeueBuffer的实现如下所示:
参数window虽然是一个类型为ANativeWindow的指针,但是它指向的实际上是一个FramebufferNativeWindow对象,这个FramebufferNativeWindow对象是在DisplayHardware类的成员函数init中创建的,因此,函数在开始的地方就可以将它转换一个FramebufferNativeWindow对象self。
有了FramebufferNativeWindow对象self之后,我们就可以在它内部的图形缓冲区数组buffers中获取下一个空闲图形缓冲区。前面提到,下一个空闲图形缓冲区的在数组buffer中的位置就保存在FramebufferNativeWindow对象self的成员变量mBufferHead中。因此,函数就可以将FramebufferNativeWindow对象self的成员变量mBufferHead的值取出来保存在变量index中,以便接下来可以从FramebufferNativeWindow对象self内部的图形缓冲区数组buffers中取出一个图形缓冲区。此外,函数还需要将FramebufferNativeWindow对象self的成员变量mBufferHead增加1,以便它可以指向下一个空闲的图形缓冲区。注意,FramebufferNativeWindow对象self内部的图形缓冲区数组buffers是循环使用的,因此,在将它的成员变量mBufferHead增加1之后,要判断它的值是否已经大于等于数组的大小,如果大于等于的话,就需要将它的值设置为0,即绕回到前面去。
从前面的分析可以知道,FramebufferNativeWindow对象self内部可用的空闲图形缓冲区的个数保存在其成员变量mNumFreeBuffers中,因此,当这个成员变量的值等于0的时候,就表示FramebufferNativeWindow对象self没有空闲的空闲图形缓冲区可用,这时候当前线程就会通过FramebufferNativeWindow对象self的成员变量mCondition所描述的一个条件变量进入到睡眠等待状态,直到有可用的空闲图形缓冲区为止。什么时候FramebufferNativeWindow对象self内部才会有可用的空闲图形缓冲区呢?当OpenGL库请求FramebufferNativeWindow对象self将一个图形缓冲区的内容渲染到硬件帧缓冲区之后,FramebufferNativeWindow对象self就会获得一个可用的空闲图形缓冲区了,后面我们分析FramebufferNativeWindow类的成员函数queueBuffer的实现时就会看到这个逻辑。
一旦FramebufferNativeWindow对象self内部有可用的空闲图形缓冲区,那么函数就会将这个空闲图形缓冲区就会返回给OpenGL库,即保存在输出参数buffer,并且将FramebufferNativeWindow对象self内部可用的空闲图形缓冲区的个数减1,以及将OpenGL库当前正前正在使用的图形缓冲区在数组buffers中的位置保存在FramebufferNativeWindow对象self的成员变量mCurrentBufferIndex中。
至此,我们就分析完成FramebufferNativeWindow类的成员函数dequeueBuffer的实现了,接下来我们继续分析FramebufferNativeWindow类的成员函数queueBuffer的实现,如下所示:
参数window指向的实际上也是一个FramebufferNativeWindow对象,这个FramebufferNativeWindow对象是在DisplayHardware类的成员函数init中创建的,因此,函数在开始的地方同样是先将它转换一个FramebufferNativeWindow对象self。
参数buffer指向的是一个实际类型为NativeBuffer的图形缓冲区,这个图形缓冲区是在FramebufferNativeWindow类的成员函数dequeueBuffer中分配的,如前所述。
FramebufferNativeWindow类的成员函数queueBuffer目标就是要将参数buffer所描述的图形缓冲区渲染到硬件帧缓冲区中去,因此,我们就需要获得FramebufferNativeWindow对象self的成员变量fbDev所描述的一个fb设备。有了这个fb设备之后, 我们就可以调用它的成员函数post来将参数buffer所描述的图形缓冲区渲染到硬件帧缓冲区中去,这个过程可以参考Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析一文。
参数buffer所描述的图形缓冲区被渲染到硬件帧缓冲区中去之后,它就变成一个空闲的图形缓冲区了,因此,我们就需要将它返回给FramebufferNativeWindow对象self内部的图形缓冲区数组buffers中去,并且将可用的空闲图形缓冲区的个数增加1,最后通过FramebufferNativeWindow对象self的成员变量mCondition所描述的一个条件变量将前面正在等待从FramebufferNativeWindow对象self内部分配空闲图形缓的线程唤醒。
至此,FramebufferNativeWindow类的成员函数queueBuffer的实现就分析完成了,FramebufferNativeWindow类的实现也分析完成了。通过GraphicPlane、DisplayHardware和FramebufferNativeWindow这三个类的实现,我们就可以知道SurfaceFlinger服务是如何管理系统的显示屏,即系统的硬件帧缓冲区的了。
从SurfaceFlinger服务创建一个DisplayHardwareBase对象来管理系统的显示屏的过程可以知道,这个DisplayHardwareBase对象会创建一个控制台事件监控线程来监控硬件帧缓冲区的睡眠/唤醒状态切换事件,而从前面Android系统Surface制的SurfaceFlinger服务的启动过程分析一文又可以知道,System进程在启动SurfaceFlinger服务过程中,又会创建一个Binder线程池,以及为SurfaceFlinger服务创建一个UI渲染线程,这样在SurfaceFlinger服务中,就存在三种不同类型的线程,在接下来的一篇文章中,我们就将分析详细SurfaceFlinger服务的线程模型,以便最后我们就可以更好地分析SurfaceFlinger服务的实现,敬请关注!