GUI系统之SurfaceFlinger(1)OpenGLES与EGL
原文链接: http://blog.youkuaiyun.com/xuesen_lin/article/details/8954508
文章都是通过阅读源码分析出来的,还在不断完善与改进中,其中难免有些地方理解得不对,欢迎大家批评指正
转载请注明:From LXS. http://blog.youkuaiyun.com/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设备截图:
根据前面章节学习过的知识,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
表格 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)做详细分析