您的位置:首页 > 产品设计 > UI/UE

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

2016-11-01 19:29 633 查看
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、Gralloc、FramebufferNativeWindow等一系列陌生的模块感到混乱而无序。的确如此,假如不先理清这些模块的相互关系,对于我们深入研究整个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)做详细分析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: