GUI显示系统之SurfaceFlinger(1)~(4)

GUI系统之SurfaceFlinger(1)OpenGLES与EGL


原文链接:  http://blog.csdn.net/xuesen_lin/article/details/8954508


文章都是通过阅读源码分析出来的,还在不断完善与改进中,其中难免有些地方理解得不对,欢迎大家批评指正
转载请注明:From LXS. http://blog.csdn.net/uiop78uiop78/




第一章: GUI系统之SurfaceFlinger


在进入GUI系统的学习前,建议大家可以先阅读本书应用篇中的“OpenGLES”章节,并参阅OpenGL ES官方指南。因为Android的GUI系统是基于OpenGL/EGL来实现的,如果没有一定基础的话,分析源码时有可能会“事倍功半”


1.1 OpenGL ES与EGL


SurfaceFlinger虽然是GUI的核心,但相对于OpenGL ES来讲,它其实只是一个“应用”。对于没有做过OpenGLES开发的人来讲,理解这部分的内容还是有一定难度的,特别是容易对系统中既有EGL/OpenGLES,又有SurfaceFlinger、GraphicPlane、DisplayHardware、GrallocFramebufferNativeWindow等一系列陌生的模块感到混乱而无序。的确如此,假如不先理清这些模块的相互关系,对于我们深入研究整个Android显示系统就是一个很大的障碍。有鉴于此,我们先来从框架的高度审视一下它们之间看似错综复杂、剪不断理还乱的依赖。

         

                                             图 11‑1 SurfaceFlinger与OpenGLES等模块关系


我们根据上面这个图,由底层往上层来逐步分析整个架构:

1. Linux内核提供了统一的framebuffer显示驱动,设备节点/dev/graphics/fb*或者/dev/fb*,以fb0表示第一个Monitor,当前实现中只用到了一个显示屏


2. Android的HAL层提供了Gralloc,分为fb和gralloc两个设备。前者负责打开内核中的framebuffer、初始化配置,以及提供post、setSwapInterval等操作,后者则管理帧缓冲区的分配和释放。上层只能通过Gralloc访问帧缓冲区,这样一来就实现了有序的封装保护


3. 由于OpenGL ES是一个通用的函数库,在不同的平台系统上需要被“本地化”——即把它与具体平台上的窗口系统建立起关联,这样才能保证它正常工作。从FramebufferNativeWindow这个名称就能判断出来,它就是将OpenGL ES在Android平台上本地化的中介之一。后面我们还会看到应用程序端所使用的另一个“本地窗口”。为OpengGL ES配置本地窗口的是EGL


4. OpenGL或者OpenGL ES 更多的只是一个接口协议,实现上既可以采用软件,也能依托于硬件。这一方面给产品开发带来了灵活性,我们可以根据成本与市场定位来决定具体的硬件设计,从而达到很好的定制需求;另一方面,既然有多种实现的可能,那么OpenGL ES在运行时是如何取舍的呢?这也是EGL的作用之一。它会去读取egl.cfg这个配置文件,然后根据用户的设定来动态加载libagl(软件实现)或者libhgl(硬件实现)。然后上层才可以正常使用各种glXXX接口


5. SurfaceFlinger中持有一个GraphicPlane成员变量mGraphicPlanes来描述“显示屏”;GraphicPlane类中又包含了一个DisplayHardware对象实例(mHw)。具体是在SurfaceFlinger::readyToRun中,完成对它们的创建与初始化。并且DisplayHardware在初始化时还将调用eglInitialize、eglCreateWindowSurface等接口,利用EGL来完成对OpenGLES环境的搭建。其中:

    surface =eglCreateWindowSurface(display, config, mNativeWindow.get(), NULL);

mNativeWindow 就是一个FramebufferNativeWindow对象。DisplayHardware为OpenGL ES设置了“本地化”所需的窗口


6. 很多模块都可以调用OpenGLES提供的API(这些接口以“gl”为前缀,比如glViewport、glClear、glMatrixMode、glLoadIdentity等等),包括SurfaceFlinger、DisplayHardware等


7. 与OpenGL ES相关的模块,可以分为如下几类:


Ø  配置类

即帮助OpenGL ES完成配置的,包括EGL、DisplayHardware都可以认为是这一类

Ø  依赖类

也就是OpenGL ES要正常运行起来所依赖的“本地化”的东西,上图中是指FramebufferNativeWindow

Ø  使用类

使用者也可能是配置者,比如DisplayHardware既扮演了“帮助”OpenGL的角色,同时它也是其使用方。另外只要处在与OpenGL ES同一个环境(Context)中的模块,都可以使用它来完成操作,比如SurfaceFlinger

 

如果是对EGL的作用、工作方式以及它所提供的重要接口等有不明白的,强烈建议大家先阅读官方文档以及本书应用篇中的章节,否则会大大影响后面的学习和理解。


GUI系统之SurfaceFlinger(2)Gralloc与Framebuffer


1.1 Gralloc与framefbuffer

相信做过Linux开发的人对framebuffer不会太陌生,它是内核系统提供的一个与硬件无关的显示抽象层。之所以称之为buffer,是由于它也是系统存储空间的一部分,是一块包含屏幕显示信息的缓冲区。由此可见,在“一切都是文件”的Linux系统中,Framebuffer被看成了终端monitor的“化身”。它借助于文件系统向上层提供统一而方便的操作接口,从而让用户空间程序可以不用修改就能适应多种屏幕——无论这些屏幕是哪家厂商、什么型号,都由framebuffer内部来兼容。


在Android系统中,framebuffer提供的设备文件节点是/dev/graphics/fb*。因为理论上支持多个屏幕显示,所以fb按数字序号进行排列,即fb0、fb1等等。其中第一个fb0是主显示屏幕,必须存在。如下是某设备的fb设备截图:

                                    

                                                                         图 11‑2 fb节点

根据前面章节学习过的知识,Android中各子系统通常不会直接基于Linux驱动来实现,而是由HAL层间接引用底层架构,在显示系统中也同样如此——它借助于HAL层来操作帧缓冲区,而完成这一中介任务的就是Gralloc,下面我们分几个方面来介绍。

<1>  Gralloc的加载

Gralloc对应的模块是由FramebufferNativeWindow(OpenGLES的本地窗口之一,后面小节有详细介绍)在构造时加载的,即:

hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule);

这个hw_get_module函数我们在前面已经见过很多次了,它是上层加载HAL库的入口,这里传入的模块ID名为:

#define GRALLOC_HARDWARE_MODULE_ID  "gralloc"

按照hw_get_module的作法,它会在如下路径中查找与ID值匹配的库:

#define HAL_LIBRARY_PATH1 "/system/lib/hw"

#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"

lib库名有如下几种形式:

    gralloc.[ro.hardware].so

    gralloc.[ro.product.board].so

    gralloc.[ro.board.platform].so

    gralloc.[ro.arch].so

或者当上述的系统属性组成的文件名都不存在时,就使用默认的:

    gralloc.default.so

最后这个库是Android原生态的实现,位置在hardware/libhardware/modules/gralloc/中,它由gralloc.cpp、framebuffer.cpp和mapper.cpp三个主要源文件编译生成。


<2>  Gralloc提供的接口

Gralloc对应的库被加载后,我们来看下它都提供了哪些接口方法。

由于Gralloc代表的是一个hw_module_t,这是HAL中统一定义的硬件模块描述体,所以和其它module所能提供的接口是完全一致的:

/*hardware/libhardware/include/hardware/Hardware.h*/
typedef struct hw_module_t {…
    struct hw_module_methods_t* methods;
                …
} hw_module_t;
 
typedef struct hw_module_methods_t {
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);
} hw_module_methods_t;

这个open接口可以帮助上层打开两个设备,分别是:

       #defineGRALLOC_HARDWARE_FB0   "fb0"

以及   #define GRALLOC_HARDWARE_GPU0  "gpu0"


“fb0”就是我们前面说的主屏幕,gpu0负责图形缓冲区的分配和释放。这两个设备将由FramebufferNativeWindow中的fbDev和grDev成员变量来管理。

/*frameworks/native/libs/ui/FramebufferNativeWindow.cpp*/      
FramebufferNativeWindow::FramebufferNativeWindow()
            : BASE(),fbDev(0), grDev(0), mUpdateOnDemand(false)
{…
    err = framebuffer_open(module, &fbDev);
    err =gralloc_open(module, &grDev);

这两个open函数分别是由hardware/libhardware/include/hardware目录下的Fb.h和Gralloc.h头文件提供的打开fb及gralloc设备的便捷实现。其中fb对应的设备名为GRALLOC_HARDWARE_FB0,gralloc则是GRALLOC_HARDWARE_GPU0。各硬件生产商可以根据自己的平台配置来实现fb和gralloc的打开、关闭以及管理,比如hardware/msm7k/libgralloc就是一个很好的参考例子。

原生态的实现在hardware/libhardware/modules/gralloc中,对应的是gralloc_device_open@Gralloc.cpp。在这个函数中,根据设备名来判断是打开fb或者gralloc。

/*hardware/libhardware/modules/gralloc/Gralloc.cpp*/
int gralloc_device_open(const hw_module_t* module, const char* name,hw_device_t** device)
{
    int status = -EINVAL;
    if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {//打开gralloc设备
        …
    } else {
        status = fb_device_open(module, name, device);//否则就是fb设备
    }
    return status;
}

先来大概看下framebuffer设备的打开过程:

/*hardware/libhardware/modules/gralloc/Framebuffer.cpp*/
int fb_device_open(hw_module_t const* module, const char* name,hw_device_t** device)
{
    int status = -EINVAL;
    if (!strcmp(name,GRALLOC_HARDWARE_FB0)) {//设备名是否正确
        fb_context_t *dev =(fb_context_t*)malloc(sizeof(*dev));//分配hw_device_t空间,这是一个“壳”
        memset(dev, 0,sizeof(*dev));//初始化,良好的编程习惯
                   …
       dev->device.common.close = fb_close;//这几个接口是fb设备的核心
       dev->device.setSwapInterval = fb_setSwapInterval;
        dev->device.post          = fb_post;
                   …
        private_module_t* m =(private_module_t*)module;
        status = mapFrameBuffer(m);//内存映射,以及参数配置
        if (status >= 0) {
            …
            *device =&dev->device.common;//“壳”和“核心”的关系
        }
    }
    return status;
}

其中fb_context_t是framebuffer内部使用的一个类,它包含了众多信息,而最终返回的device只是其内部的device.common。这种“通用和差异”并存的编码风格在HAL层非常常见,大家要做到习以为常。

Struct类型fb_context_t里的唯一成员就是framebuffer_device_t,这是对frambuffer设备的统一描述。一个标准的fb设备通常要提供如下的函数实现:


Ø  int(*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer);

将buffer数据post到显示屏上。要求buffer必须与屏幕尺寸一致,并且没有被locked。这样的话buffer内容将在下一次VSYNC中被显示出来

Ø  int(*setSwapInterval)(struct framebuffer_device_t* window, int interval);

设置两个缓冲区交换的时间间隔

Ø  int(*setUpdateRect)(struct framebuffer_device_t* window, int left, int top,

                 int width, int height);

设置刷新区域,需要framebuffer驱动支持“update-on-demand”。也就是说在这个区域外的数据很可能被认为无效

 

我们再来解释下framebuffer_device_t中一些重要的成员变量,如下表:


表格 11‑1 framebuffer_device_t中的重要成员变量

变量

描述

uint32_t  flags

标志位,指示framebuffer的属性配置

uint32_t  width;

uint32_t  height;

framebuffer的宽和高,以像素为单位

int       format

framebuffer的像素格式,比如:HAL_PIXEL_FORMAT_RGBA_8888 HAL_PIXEL_FORMAT_RGBX_8888

HAL_PIXEL_FORMAT_RGB_888

HAL_PIXEL_FORMAT_RGB_565等等

float     xdpi;

float     ydpi;

x和y轴的密度(pixel per inch)

float     fps

屏幕的每秒刷新频率,假如无法正常从设备获取的话,默认设置为60Hz

int       minSwapInterval;

int       maxSwapInterval;

该framebuffer支持的最小和最大缓冲交换时间



到目前为止,我们还没看到系统是如何打开具体的fb设备、以及如何对fb进行配置,这些工作都是在mapFrameBuffer()完成的。这个函数首先尝试打开(调用open,权限为O_RDWR)如下路径中的fb设备:

"/dev/graphics/fb%u"或者 "/dev/fb%u",其中%u当前的实现中只用了“0”,也就是只会打开一个fb,虽然Android从趋势上看是要支持多屏幕的。成功打开fb后,我们通过:

ioctl(fd, FBIOGET_FSCREENINFO, &finfo);

ioctl(fd, FBIOGET_VSCREENINFO, &info)

来得到显示屏的一系列参数,同时通过

ioctl(fd, FBIOPUT_VSCREENINFO, &info)来对底层fb进行配置。

这个函数的另一重要任务,就是对fb做内存映射,主要语句如下:

   void* vaddr = mmap(0,fbSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);   
   module->framebuffer->base = intptr_t(vaddr);
   memset(vaddr, 0, fbSize);
所以映射地址是module->framebuffer->base,这个module对应的是前面hw_get_module(GRALLOC_HARDWARE_MODULE_ID,&module)得到的hw_module_t(被强制类型转化为private_module_t,大家可以自己看下这个struct)。
接下来再看下对gralloc设备的打开操作,它相对fb简单些,如下所示:

/*hardware/libhardware/modules/gralloc/Gralloc.cpp*/
int gralloc_device_open(const hw_module_t* module, const char* name,hw_device_t** device)
{
    int status = -EINVAL;
    if (!strcmp(name,GRALLOC_HARDWARE_GPU0)) {
        gralloc_context_t*dev;//做法和fb类似
        dev =(gralloc_context_t*)malloc(sizeof(*dev));//分配空间
        /* initialize ourstate here */
        memset(dev, 0,sizeof(*dev));
                   …
       dev->device.alloc   =gralloc_alloc; //从提供的接口来看,gralloc和分配/释放有关系
       dev->device.free    =gralloc_free;
…
}
与fb相似的部分我们就不多做介绍了。因为gralloc担负着图形缓冲区的分配与释放,所以它提供了两个最重要的实现即alloc和free。这里我们先不深入分析了,只要知道gralloc所提供的功能就可以了。

我们以下面简图来小结对Gralloc的分析。

                                                       图 11‑3 Gralloc简图



GUI系统之SurfaceFlinger(3)Android中的本地窗口FramebufferNativewindow

1.1 Android中的本地窗口

在OpenGL的学习过程中,我们不断提及“本地窗口”(NativeWindow)这一概念。那么对于Android系统来说,它是如何将OpenGL ES本地化的呢,或者说,它提供了什么样的本地窗口?

根据整个Android系统的GUI设计理念,我们不难猜想到至少需要两种本地窗口:

Ø  面向管理者(SurfaceFlinger)

既然SurfaceFlinger扮演了系统中所有UI界面的管理者,那么它无可厚非地需要直接或间接地持有“本地窗口”。从前一小节我们已经知道,这个窗口就是FramebufferNativeWindow

Ø  面向应用程序

我们先给出答案,这类窗口是SurfaceTextureClient

 

有不少读者可能会觉得困惑,为什么需要两种窗口,同一个系统不是应该只有一种窗口吗?比如这样子:

                                                     

                                                     图 11‑4理想的窗口系统

这个图中,由Window来管理Framebuffer。我们打个比方来说,OpenGL就像是一台通用的打印机一样,只要输入正确的指令,它就能按照要求输出结果;而Window则是“纸”,它是用来承载OpenGL的输出结果的。OpenGL并不介意Window是A4纸或者是A6,甚至是塑料纸也没有关系,对它来说都只是“本地窗口”。

理解了这个图后,我们再来思考下,这样的模型是否能符合Android的要求?假如整个系统仅有一个需要显示UI的程序,我们有理由相信它是可以胜任的。但是如果有N个UI程序的情况呢?Framebuffer显然只有一个,不可能让各个应用程序自己单独管理。

这样子问题就来了,该如何改进呢?下面这个方法如何?

       

                                                                                        图 11‑5 改进的窗口系统

在这个改进的窗口系统中,我们有了两类本地窗口,即Window-1和Window-2。第一种窗口是能直接显示在终端屏幕上的——它使用了帧缓冲区,而后一种Window实际上是从内存缓冲区分配的空间。当系统中存在多个应用程序时,这能保证它们都可以获得一个“本地窗口”,并且这些窗口最终也能显示到屏幕上——SurfaceFlinger会收集所有程序的显示需求,对它们做统一的图像混合操作(有点类似于AudioFlinger),然后输出到自己的Window-1上。

当然,这个改进的窗口系统有一个前提,即应用程序与SurfaceFlinger都是基于OpenGL ES来实现的。有没有其它选择呢?答案是肯定的,比如应用程序端完全可以采用Skia等第三方的图形库,只要保持它们与SurfaceFlinger间的“协议”不变就可以了,如下所示:

         

                                                                                   图 11‑6 另一种改进的窗口系统

理论上来说,采用哪一种方式都是可行的。不过对于开发人员,特别是没有OpenGLES项目经验的人而言,前一种系统的门槛相对较高。事实上,Android系统同时提供了这两种实现来供上层选择。正常情况下我们按照SDK向导生成的apk应用,就属于后面的情况;而对于希望使用OpenGLES来完成复杂的界面渲染的应用开发者,也可以使用GLSurfaceView来达到目标。

在接下来的源码分析中,我们将对上面所提出的假设做进一步验证。


1.1.1  FramebufferNativeWindow

先把EGL创建一个Window Surface的函数原型列出如下:

EGLSurface eglCreateWindowSurface(  EGLDisplay dpy, EGLConfig config,

                          NativeWindowType  window, const EGLint *attrib_list);

显然不论是哪一种本地窗口,它都必须要与NativeWindowType保持一致,否则就无法正常使用EGL了。先从数据类型的定义来看下这个window参数有什么特别之处:

/*frameworks/native/opengl/include/egl/Eglplatform.h*/
typedef  EGLNativeWindowType  NativeWindowType;//注意这两种类型其实是一样的
…
#if defined(_WIN32) || defined(__VC32__) &&!defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
                /* Win32 和WinCE系统下的定义 */
    …
    typedef  HWND    EGLNativeWindowType;
#elif defined(__WINSCW__) || defined(__SYMBIAN32__)  /* Symbian系统*/
    …
     typedef  void *  EGLNativeWindowType;
#elif defined(__ANDROID__) || defined(ANDROID) /* Android系统 */
    struct ANativeWindow;
    …
    typedef struct ANativeWindow*          EGLNativeWindowType;
    …
#elif defined(__unix__) /* Unix系统*/
    …
    typedef  Window   EGLNativeWindowType;
#else
    #error "Platform notrecognized"
#endif


我们以下表来概括在不同的操作系统平台下EGLNativeWindowType所对应的具体数据类型:

表格 11‑2 不同平台下的EGLNativeWindowType

操作系统

数据类型

Win32, WinCE

HWND,即句柄

Symbian

Void*

Android

ANativeWindow*          

Unix

Window

其它

暂时不支持



由于OpenGL ES并不是只针对某一个操作系统平台设计的,它在很多地方都要考虑兼容性和可移植性,这个EGLNativeWindowType就是其中一个例子。它在不同的系统中对应的是不一样的数据类型,比如Android中就指的是ANativeWindow指针。

ANativeWindow的定义在Window.h中:

/*system/core/include/system/Window.h*/
struct ANativeWindow
{…
    const uint32_t flags; //与Surface或updater有关的属性
    const int   minSwapInterval;//所支持的最小交换间隔时间
    const int   maxSwapInterval;//所支持的最大交换间隔时间
    const float xdpi; //水平方向的密度,以dpi为单位
    const float ydpi;//垂直方向的密度,以dpi为单位
    intptr_t    oem[4];//为OEM定制驱动所保留的空间
    int     (*setSwapInterval)(struct ANativeWindow*window, int interval);
    int     (*dequeueBuffer)(struct ANativeWindow*window, struct ANativeWindowBuffer** buffer);
    int     (*lockBuffer)(struct ANativeWindow*window, struct ANativeWindowBuffer* buffer);   
    int    (*queueBuffer)(struct ANativeWindow* window, struct ANativeWindowBuffer*buffer);
    int     (*query)(const struct ANativeWindow*window, int what, int* value);
    int     (*perform)(struct ANativeWindow* window,int operation, ... );
    int     (*cancelBuffer)(struct ANativeWindow*window, struct ANativeWindowBuffer* buffer);
    void* reserved_proc[2];
};

我们在下表中详细解释这个类的成员函数。

表格 11‑3 ANativeWindow类成员函数解析

Member Function

Description

setSwapInterval

设置交换间隔时间,后面我们会讲解swap的作用

dequeueBuffer

EGL通过这个接口来申请一个buffer。以前面我们所举的例子来说,两个本地窗口所提供的buffer分别来自于帧缓冲区和内存空间。单词“dequeue”的字面意思是“出队列”,这从侧面告诉我们,一个Window所包含的buffer很可能不只一份

lockBuffer

申请到的buffer并没有被锁定,这种情况下是不允许我们去修改其中的内容的。所以我们必须要先调用lockBuffer来获得一个锁

queueBuffer

当EGL对一块buffer渲染完成后,它调用这个接口来unlock和post buffer

query

用于向本地窗口咨询相关信息

perform

用于执行本地窗口支持的各种操作,比如:

NATIVE_WINDOW_SET_USAGE

NATIVE_WINDOW_SET_CROP

NATIVE_WINDOW_SET_BUFFER_COUNT

NATIVE_WINDOW_SET_BUFFERS_TRANSFORM

NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP

等等

cancelBuffer

这个接口可以用来取消一个已经dequeued的buffer,要特别注意同步的问题



从上面对ANativeWindow的描述可以看出,它更像是一份“协议”,规定了一个本地窗口的形态和功能。这对于支持多种本地窗口的系统是必须的,因为只有这样子我们才能针对某种特定的平台窗口,来填充具体的实现。

这个小节中我们先来看下FramebufferNativeWindow是如何履行这份“协议”的。

FramebufferNativeWindow本身代码并不多,下面分别选取其构造函数及dequeue()两个函数来分析,其它部分的实现都是类似的,大家可以自行阅读。

(1) FramebufferNativeWindow构造函数

基于FramebufferNativeWindow的功能,可以大概推测出它的构造函数里应该至少完成如下的初始化操作:

Ø  加载GRALLOC_HARDWARE_MODULE_ID模块,详细流程我们在Gralloc小节已经解释过了

Ø  分别打开fb和gralloc设备。我们在Gralloc小节也已经分析过了,打开后的设备由全局变量fbDev和grDev管理

Ø  根据设备的属性来给FramebufferNativeWindow赋初值

Ø  根据FramebufferNativeWindow的实现来填充ANativeWindow中的“协议”

Ø  其它一些必要的初始化

 

下面从源码入手看下每个步骤具体是怎样实现的。

/*frameworks/native/libs/ui/FramebufferNativeWindow.cpp*/
FramebufferNativeWindow::FramebufferNativeWindow()
    : BASE(), fbDev(0),grDev(0), mUpdateOnDemand(false)
{
    hw_module_t const* module;
if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) == 0) {…
   int err;
        int i;
        err = framebuffer_open(module, &fbDev);
        err = gralloc_open(module, &grDev);
        /*上面这部分我们在前几个小节已经分析过了,不清楚的可以回头看下*/
        …
        mNumBuffers = NUM_FRAME_BUFFERS; //buffer个数,目前为2
        mNumFreeBuffers =NUM_FRAME_BUFFERS; //可用的buffer个数,初始时所有buffer可用
        mBufferHead =mNumBuffers-1;
        …
        for (i = 0; i <mNumBuffers; i++) //给每个buffer初始化
        {
             buffers[i] = new NativeBuffer(fbDev->width,fbDev->height, fbDev->format, GRALLOC_USAGE_HW_FB);
        }// NativeBuffer是什么?
 
        for (i = 0; i <mNumBuffers; i++) //给每个buffer分配空间
        {
              err =grDev->alloc(grDev, fbDev->width, fbDev->height, fbDev->format,
                        GRALLOC_USAGE_HW_FB, &buffers[i]->handle,&buffers[i]->stride);
                                                …
        }
             /*为本地窗口赋属性值*/
       const_cast<uint32_t&>(ANativeWindow::flags) = fbDev->flags;
        const_cast<float&>(ANativeWindow::xdpi)= fbDev->xdpi;
       const_cast<float&>(ANativeWindow::ydpi) = fbDev->ydpi;
       const_cast<int&>(ANativeWindow::minSwapInterval) =fbDev->minSwapInterval;
       const_cast<int&>(ANativeWindow::maxSwapInterval) =fbDev->maxSwapInterval;
    } else {
        ALOGE("Couldn'tget gralloc module");
    }
    /*以下履行窗口“协议”*/
   ANativeWindow::setSwapInterval = setSwapInterval;
   ANativeWindow::dequeueBuffer = dequeueBuffer;
    ANativeWindow::lockBuffer= lockBuffer;
    ANativeWindow::queueBuffer= queueBuffer;
    ANativeWindow::query =query;
    ANativeWindow::perform =perform;
}

这个函数逻辑上很简单,开头一部分我们已经分析过了,就不再赘述。需要注意的是FramebufferNativeWindow是如何分配buffer的,换句话说,后面的dequeue所获得的缓冲区是从何而来。

成员变量mNumBuffers代表了FramebufferNativeWindow所管理的buffer总数,NUM_FRAME_BUFFERS当前定义为2。有人可能会觉得奇怪,既然FramebufferNativeWindow对应的是真实的物理屏幕,那么为什么需要两个buffer呢?

假设我们需要绘制这样一个画面,包括两个三角形和三个圆形,最终结果如下图所示:

                                                  
                                                                     图 11‑7 希望在屏幕上显示的完整结果
先来看只有一个buffer的情况,这意味着我们是直接以屏幕为画板来实时做画的——我们画什么,屏幕上就显示什么。以绘制上图中的每一个三角形或圆形都需要0.5秒为例,那么总计耗时应该是0.5*5=2.5秒。换句话说,用户在不同时间点所看到的屏幕是这样子的:

                      

                                                                      图 11‑8 只有一个buffer的情况

对于用户来说,他将看到一个不断刷新的画面。通俗来讲,就是画面很“卡”。对于图像刷新很频繁的情况,比如游戏场景,用户的体验就会更差。那么有什么解决的办法呢?我们知道,出现这种现象的原因就是程序直接以屏幕为绘图板,把还没有准备就绪的图像直接呈现给了用户。换句话说,如果可以等待整幅图绘制完成以后再刷新到屏幕上,那么对于用户来说,他在任何时候看到的都是正确而完整的图像,问题也就解决了。下图解释了当采用两个缓冲区时的情况:

                           

                                                                    图 11‑9 采用两个缓冲区的情况

上图中所述的就是通常所称的“双缓冲”(Double-Buffering)技术。除此以外,其实还有三缓冲(TripleBuffering)、四缓冲(Quad Buffering)等等,我们将它们统称为“多缓冲”(MultipleBuffering)机制。

理解了为什么需要双缓冲以后,我们再回过头来看FramebufferNativeWindow的构造函数。接下来就要解决另一个问题,即两个缓冲区空间是从哪里分配的?根据前几个小节的知识,应该是要向HAL层的Gralloc申请。两个缓冲区以全局变量buffers[NUM_FRAME_BUFFERS]来记录,每个数据元素是一个NativeBuffer,这个类定义如下:

class NativeBuffer : public ANativeObjectBase< ANativeWindowBuffer,

       NativeBuffer,LightRefBase<NativeBuffer> >

{…

所以这个“本地缓冲区”继承了ANativeWindowBuffer的特性,后者的定义在/system/core/include/system/Window.h中:

typedef struct ANativeWindowBuffer
{…
    int width; //宽
    int height;//高
    …
    buffer_handle_t handle;/*代表内存块的句柄,比如ashmem机制。
                         可以参考本书的共享内存章节*/
…
} ANativeWindowBuffer_t;

第一个for循环里先给各buffer创建相应的实例(new NativeBuffer),其中的属性值都来源于fbDev,比如宽、高、格式等等。紧随其后的就是调用Gralloc设备的alloc()方法:

err = grDev->alloc(grDev, fbDev->width,fbDev->height, fbDev->format,

               GRALLOC_USAGE_HW_FB, &buffers[i]->handle, &buffers[i]->stride);

注意第5个参数,它代表所要申请的缓冲区的用途,定义在hardware/libhardware/include/hardware/Gralloc.h中,目前已经支持几十种,比如:

Ø  GRALLOC_USAGE_HW_TEXTURE

缓冲区将用于OpenGL ES Texture

Ø  GRALLOC_USAGE_HW_RENDER

缓冲区将用于OpenGL ES的渲染

Ø  GRALLOC_USAGE_HW_2D

缓冲区会提供给2D 硬件图形设备

Ø  GRALLOC_USAGE_HW_COMPOSER

缓冲区用于HWComposer HAL模块

Ø  GRALLOC_USAGE_HW_FB

缓冲区用于framebuffer设备

Ø  GRALLOC_USAGE_HW_VIDEO_ENCODER

缓冲区用于硬件视频编码器

等等。。。


这里是要用于在终端屏幕上显示的,所以申请的usage类型是GRALLOC_USAGE_HW_FB,对应的Gralloc中的实现是gralloc_alloc_framebuffer@Gralloc.cpp;假如是其它用途的申请,则对应gralloc_alloc_buffer@Gralloc.cpp。不过,如果底层只允许一个buffer(不支持page-flipping的情况),那么gralloc_alloc_framebuffer也同样可能只返回一个ashmem中申请的“内存空间”,真正的“帧缓冲区”要在post时才会被用到。

另外,当前可用(free)的buffer数量由mNumFreeBuffers管理,这个变量的初始值也是NUM_FRAME_BUFFERS,即总共有2个可用缓冲区。在程序后续的运行过程中,始终由mBufferHead来指向下一个将被申请的buffer(注意,不是下一个可用buffer)。也就是说每当用户向FramebufferNativeWindow申请一个buffer时(dequeueBuffer),这个mBufferHead就会增加1;一旦它的值超过NUM_FRAME_BUFFERS,则还会变成0,如此就实现了循环管理,后面dequeueBuffer时我们再详细解释。

一个本地窗口包含了很多属性值,比如各种标志(flags)、横纵坐标的密度值等等。这些数值都可以从fb设备上取到,我们需要将它赋予刚生成的FramebufferNativeWindow实例。

最后,就是履行ANativeWindow的协议了。FramebufferNativeWindow会将其成员函数填充到ANativeWindow中的函数指针中,比如:

ANativeWindow::setSwapInterval = setSwapInterval;

ANativeWindow::dequeueBuffer = dequeueBuffer;

这样子OpenGL ES才能通过一个ANativeWindow来正确地与本地窗口建立连接,下面我们就详细分析下其中的dequeueBuffer。

(2)dequeueBuffer

这个函数很短,只有二十几行,不过是FramebufferNativeWindow中的核心。OpenGL ES就是通过它来分配一个用于渲染的缓冲区的,与之相对应的是queueBuffer。


int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window,ANativeWindowBuffer** buffer)
{
    FramebufferNativeWindow*self = getSelf(window); /*Step1*/
    Mutex::Autolock_l(self->mutex); /*Step2*/
…
/*Step3. 计算mBufferHead */
    int index =self->mBufferHead++;
    if (self->mBufferHead>= self->mNumBuffers)
        self->mBufferHead =0;
 
    /*Step4. 当前没有可用缓冲区*/
    while (!self->mNumFreeBuffers){
       self->mCondition.wait(self->mutex);
}
/*Step5. 有人释放了缓冲区*/
   self->mNumFreeBuffers--;
   self->mCurrentBufferIndex = index;
    *buffer =self->buffers[index].get();
 
    return 0;
}

Step1@ FramebufferNativeWindow::dequeueBuffer, 这里先将入参中ANativeWindow 类型的变量window强制转化为FramebufferNativeWindow。因为前者是后者的父类,所以这样的转化当然是有效的。不过细心的读者可能会发现,为什么函数入参中还要特别传入一个ANativeWindow对象的内存地址,直接使用FramebufferNativeWindow的this指针不行吗?这个问题我还没有确定真正的原因是什么,一个猜测是为了兼容各种平台的需求。大家应该注意到了ANativeWindow是一个Struct数据类型,在C语言中Struct是没有成员函数的,所以我们通常是用函数指针的形式来模拟一个成员函数,比如这个dequeueBuffer在ANativeWindow的定义就是一个函数指针。而且我们没有办法确定最终填充到ANativeWindow中函数指针的实现是否有this指针,所以在参数中带入一个window变量就是必要的了。

Step2@ FramebufferNativeWindow::dequeueBuffer,获得一个Mutex锁。因为接下来的操作涉及到互斥区,自然需要有一个保护措施。这里采用的是Autolock,意味着dequeueBuffer函数结束后会自动释放Mutex。

Step3@ FramebufferNativeWindow::dequeueBuffer,前面我们介绍过mBufferHead变量,这里来看下对它的实际使用。首先index得到的是mBufferHead所代表的当前位置,然后mBufferHead增加1。由于我们是循环利用两个缓冲区的,所以如果这个变量的值超过mNumBuffers,那么就需要把它置0。也就是说在这个场景下mBufferHead的值永远只能是0或者1。

Step4@ FramebufferNativeWindow::dequeueBuffer,mBufferHead并不代表它所指向的缓冲区是可用的。假如当前的mNumFreeBuffers表明已经没有多余的缓冲区空间,那么我们就需要等待有人释放buffer。这里使用到了Condition这一同步机制,如果有不清楚的请参考本书进程章节的详细描述。可以肯定的是这里调用了mCondition.wait,那么必然有其它地方要唤醒它——具体的就是在queueBuffer()中,大家可以自己验证下是否如此。

Step5@ FramebufferNativeWindow::dequeueBuffer,一旦成功获得buffer后,要把可用的buffer计数减1(mNumFreeBuffers--),因为mBufferHead前面已经自增过了,这里就不用再特别处理。

这样子我们就完成了对Android系统中本地窗口FramebufferNativeWindow的分析,接下来就讲解另一个重要的Native Window。



GUI系统之SurfaceFlinger(4)opengl es本地窗口SurfaceTextureClient


1.1.1 SurfaceTextureClient


针对应用程序端的本地窗口是SurfaceTextureClient,和FramebufferNativeWindow一样,它必须继承ANativeWindow:

class SurfaceTextureClient

    : publicANativeObjectBase<ANativeWindow, SurfaceTextureClient,RefBase>

 

这个本地窗口当然也需要实现ANativeWindow所制定的“协议”,我们的重点是关注它与前面的FramebufferNativeWindow有什么不同。SurfaceTextureClient的构造函数只是简单地调用了init函数,后者则对ANativeWindow::dequeueBuffer等函数指针及内部变量赋了初值。由于整个函数的功能很简单,我们只摘录其中的一部分:


/*frameworks/native/libs/gui/SurfaceTextureClient.cpp*/
void SurfaceTextureClient::init() {
    /*给ANativeWindow中的函数指针赋值*/
   ANativeWindow::setSwapInterval  =hook_setSwapInterval;
    ANativeWindow::dequeueBuffer    = hook_dequeueBuffer;
    …
    /*为各内部变量赋值,因为此时用户还没有真正发起申请,所以基本是0*/
    mReqWidth = 0;
    mReqHeight = 0;
    …
    mDefaultWidth = 0;
    mDefaultHeight = 0;
    mUserWidth = 0;
    mUserHeight = 0;…
}

SurfaceTextureClient是面向Android系统中所有UI应用程序的,也就是说它承担着单个应用进程中的UI显示需求。基于这点考虑,可以推测出它的内部实现至少会有以下几点:

Ø  提供给上层(主要是java层)绘制图像的“画板”

前面说过,这个本地窗口分配的内存应该不是来自于帧缓冲区,那么具体是由谁分配的,又是如何管理的呢?

Ø  它与SurfaceFlinger间是如何分工的

显然SurfaceFlinger需要收集系统中所有应用程序绘制的图像数据,然后集中显示到物理屏幕上。在这个过程中,SurfaceTextureClient扮演了什么样的角色呢?

 

我们先来解释下这个类中的一些重要的成员变量,如下表所示:

表格 11‑4 SurfaceTextureClient部分成员变量一览

成员变量

说明

sp<ISurfaceTexture> mSurfaceTexture

这个变量是SurfaceTextureClient的核心,很多“协议”就是通过它实现的,后面会有详细讲解

BufferSlot mSlots[NUM_BUFFER_SLOTS]

从名称上可以看出,这是Client内部用于存储buffer的地方,容量NUM_BUFFER_SLOTS最多达32个。BufferSlot类内部又由一个GraphicBuffer和一个dirtyRegion组成,当有用户dequeueBuffer时就会分配真正的空间

uint32_t  mReqWidth

SurfaceTextureClient中有多组相似的宽高变量,它们之间是有区别的。这里的宽和高是指下一次dequeue时将会申请的尺寸,初始值都是1

uint32_t  mReqHeight

uint32_t mReqFormat

和上面的两变量类似,这是指下次dequeue时将会申请的buffer的像素格式,初始值是PIXEL_FORMAT_RGBA_8888

uint32_t mReqUsage

指下次dequeue时将会指定的usage类型

Rect mCrop

Crop表示“修剪”,这个变量将在下次queue时用于修剪缓冲区,可以调用setCrop来设置具体的值

int mScalingMode

同样,这个变量将用于下次queue时对缓冲区进行scale,可以调用setScalingMode来设置具体的值

uint32_t mTransform

用于下次queue时的图形翻转等操作(Transform)

uint32_t mDefaultWidth

默认情况下的缓冲区宽高值

uint32_t mDefaultHeight

uint32_t mUserWidth

如果不为零的话,就是应用层指定的值,将会覆盖前面的mDefaultWidth/ mDefaultHeight

uint32_t mUserHeight

sp<GraphicBuffer>           mLockedBuffer

这三个值需要锁的保护,接下来还会有分析

sp<GraphicBuffer>           mPostedBuffer

Region mDirtyRegion



从这些内部变量的描述中,我们可以大概了解到两点:SurfaceTextureClient中将通过mSurfaceTexture来获得buffer,而且这些缓冲区会被记录在mSlots数组中。接下来就来分析其中的实现细节。

前面SurfaceTextureClient构造函数里我们看到ANativeWindow中的函数指针赋予的是各种以hook开头的函数,这些函数内部又直接调用了SurfaceTextureClient中真正的实现,比如hook_dequeueBuffer对应的是dequeueBuffer。这就好像是“钩子”一样,所以称之为hook。


int SurfaceTextureClient::dequeueBuffer(android_native_buffer_t**buffer) {…
    Mutex::Autolocklock(mMutex);
int buf = -1;
/*Step1. 宽高计算*/
    int reqW = mReqWidth ?mReqWidth : mUserWidth;
int reqH = mReqHeight ? mReqHeight :mUserHeight;
/*Step2. dequeueBuffer得到一个缓冲区*/
    status_t result =mSurfaceTexture->dequeueBuffer(&buf, reqW, reqH,mReqFormat, mReqUsage);
    …
   sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);//注意buf只是一个int值,代表的是mSlots数组序号
…
/*Step3. requestBuffer*/
    if ((result &ISurfaceTexture::BUFFER_NEEDS_REALLOCATION) || gbuf == 0) {
        result =mSurfaceTexture->requestBuffer(buf, &gbuf);
        …
    }
    *buffer = gbuf.get();
    return OK;
}

Step1@ SurfaceTextureClient::dequeueBuffer。用于UI绘制的图形缓冲区一定有宽高属性,具体的值由mReqWidth/mReqHeight或者mUserWidth/mUserHeight决定,其中前者的优先级比后者高

Step2@ SurfaceTextureClient::dequeueBuffer。可以看到,真正执行dequeueBuffer操作的确实是mSurfaceTexture(ISurfaceTexture)。这个变量的赋值有两个来源:作为SurfaceTextureClient的构造函数参数传入,然后间接调用setISurfaceTexture来设置的;或者SurfaceTextureClient的子类通过直接调用setISurfaceTexture来生成。

在应用进程环境中,属于后面一种情况。

具体流程就是:当Java层的Surface进行init时,实际上执行的函数是Surface_init@android_view_Surface.cpp。这个JNI函数将进一步调用SurfaceComposerClient::createSurface生成一个SurfaceControl,后者是用于管理Surface的类。它将在SurfaceControl::getSurface时生成一个Surface实例,在构造时通过SurfaceControl:: getSurfaceTexture来获得一个ISurfaceTexture。而Surface类实际上又继承自SurfaceTextureClient,所以它可以调用setISurfaceTexture。

由于Android源码有多处称为Surface的地方,取名极其混乱,我们下面通过一张完整的流程图来帮助大家把这些关系理顺:


             

                                              图 11‑10 ISurfaceTexture创建流程

从这个图中可以看到,ISurfaceTexture是由ISurface::getSurfaceTexture生成的,而ISurface则是由SurfaceFlinger生成的。在这一过程中,总共使用到了三个匿名binderserver,它们所提供的接口整理如下表:

表格 11‑5 与Surface相关的三个匿名binder


匿名Binder

提供的接口

ISurfaceComposerClient

sp<ISurface> createSurface(…);

status_t  destroySurface(SurfaceID sid);

ISurface

sp<ISurfaceTexture> getSurfaceTexture(…);

ISurfaceTexture

status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);

status_t setBufferCount(int bufferCount);

status_t dequeueBuffer(…);

status_t queueBuffer(…);

void cancelBuffer(int slot);

int query(int what, int* value);

status_t setSynchronousMode(bool enabled);

status_t connect(int api, QueueBufferOutput* output);

status_t disconnect(int api);



由此可见,这三个匿名binder是一环扣一环的,也就是说我们访问的顺序只能是ISurfaceComposerClientàISurfaceàISurfaceTexture。当然,第一个匿名binder就一定是需要由一个实名binder来提供,它就是SurfaceFlinger,而SurfaceFlinger则是在ServiceManager中“注册在案”的。具体是在SurfaceComposerClient::onFirstRef()这个函数中,通过向ServiceManager查询名称为“SurfaceFlinger”的binder server来获得的。不过和其它常见binder server不同的是,SurfaceFlinger虽然在ServiceManager中注册的名称为“SurfaceFlinger”,但它在server端实现的binder接口却是ISurfaceComposer,因而SurfaceComposerClient得到的其实是ISurfaceComposer,这点大家要特别注意,否则可能会搞乱。

//我们可以从SurfaceFlinger的继承关系中看出这一区别,如下代码片断

class SurfaceFlinger :
        publicBinderService<SurfaceFlinger>, //在ServiceManager中注册为“SurfaceFlinger”
        public BnSurfaceComposer, //实现的接口却叫ISurfaceComposer,不知道为什么要这么设计。。。

 
 

绕了一大圈后,我们接着分析前面dequeueBuffer函数的实现。很显然SurfaceTextureClient只是一个中介,它间接调用mSurfaceTexture也就是ISurfaceTexture的服务。那么ISurfaceTexture在Server端又是由谁来完成的呢?

                                   
                                                          图 11‑11 ISurfaceTexture的本地端实现

因为这里面牵扯到很多新的类,我们先不做过多解释,到后面BufferQueue小节再详细分析其中的依赖关系。

当mSurfaceTexture->dequeueBuffer返回后,buf变量就是mSlots[]数组中可用的成员序号。接下来就要通过这个序号来获取真正的buffer地址,即mSlots[buf].buffer。

 

Step3@ SurfaceTextureClient::dequeueBuffer。假如返回值result中的标志包含了BUFFER_NEEDS_REALLOCATION,说明BufferQueue为这个Slot重新分配了空间,具体细节请参见下一个小节。此时我们还需要另外调用requestBuffer来确定gbuf的值,这其中又牵涉到很多东西,我们放在下面小节统一解释原因。

通过这两个小节,我们学习了显示系统中两个重要的本地窗口,即FramebufferNativewindow和SurfaceTextureClient。第一个窗口是专门为SurfaceFlinger服务的,它由Gralloc提供支持,相对逻辑上很好理解。而SurfaceTextureClient则是为应用程序服务的,同时它从本质上还是由SurfaceFlinger服务统一管理的,因而涉及到很多跨进程的通信细节。这个小节我们只是简单地勾勒出其中的框架,接下去就要分几个方面来做完整的分析了。

Ø  BufferQueue

为应用程序服务的本地窗口SurfaceTextureClient在server端的实现是BufferQueue。我们将详细解析BufferQueue的内部实现,并结合应用程序端的使用流程来理解清楚它们之间的关系。

 

Ø  Buffer、Consumer、Producer是“生产者-消费者”模型中的三个参与对象,如何协调好它们的工作是应用程序能否正常显示UI的关键。在接下来内容的安排上,我们先讲解Buffer(BufferQueue)与Producer(应用程序)间的交互,然后再专门切入Consumer(SurfaceFlinger)做详细分析


参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值