android中的graphic系统是个相当庞大复杂的系统,在学习前首先心中对整个系统得有一个大概的轮廓,然后再阅读代码时就能对应轮廓的各个方面。
一个典型的android app activity的显示流程为:
将所有的layer进行合成,显示到显示屏上。
这里需要注意,对几乎所有的app而言,在屏幕上任何时候起码有三个layer:屏幕最顶端的status bar,屏幕最下面的navigation bar,还有就是app的UI部分。一些特例情况下,app的layer可能多于或少于3个,例如对全屏显示的app就没有status bar,而对launcher,还有个为了wallpaper显示的layer。status bar和navigation bar是由系统进行去render,因为不是普通app的组成部分嘛。而app的UI部分对应的layer当然是自己去render,所以就有了第4条中的所有layer进行“合成”。
那么我们首先如何去向这个surface去绘图,去render呢?肯定有工具,也就是API吧?
那么app是如何去画图的呢?
android提供了两种方式:Canvas ,OpenGL ES。这两种方式在我认为都应该算是标准,提供了一套固定的API,实现和平台无关。java中类似于给你提供了一个interface接口,这个接口你可以用任何方式去实现,只要满足功能要求即可。不同的人可以有不同的实现方式,对10个数进行排序,A可以用冒泡法,B可以用选择法,最终结果只要保证把这10个数排好序了,唯一差别就是实现难度和效率问题。
android提供了Canvas 2D API用来进行普通图形的绘制的,类似TextView这种应该都是用Canvas API来完成的。而Canvas这个”标准”的具体实现是由/extern/skia库来完成的,真正干活的是skia。上层Canvas调用的API到下层其实封装了skia的实现。
OpenGL ES相关的API是为了3D图形的绘制而准备的。
android上有个EGL库,EGL和OpenGL ES是什么关系?代码在frameworks\native\opengl\libs\,
//编译的库文件为/system/lib/libEGL.so
LOCAL_MODULE:= libEGL
surfacefling这个非常重要的系统服务依赖EGL库,包含了EGL头文件,使用了EGL中的函数,
LOCAL_SHARED_LIBRARIES := \
libcutils \
liblog \
libdl \
libhardware \
libutils \
libEGL \
libGLESv1_CM \
libGLESv2 \
libbinder \
libui \
libgui \
libpowermanager
LOCAL_MODULE:= libsurfaceflinger
在SurfaceFlinger::init()中,调用了egl开头的函数
void SurfaceFlinger::init() {
ALOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");
status_t err;
Mutex::Autolock _l(mStateLock);
// initialize EGL for the default display
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(mEGLDisplay, NULL, NULL);
.....
}
在eglGetDisplay()中调用了egl_init_drivers(),
EGLDisplay eglGetDisplay(EGLNativeDisplayType display)
{
clearError();
uintptr_t index = reinterpret_cast(display);
if (index >= NUM_DISPLAYS) {
return setError(EGL_BAD_PARAMETER, EGL_NO_DISPLAY);
}
if (egl_init_drivers() == EGL_FALSE) {
return setError(EGL_BAD_PARAMETER, EGL_NO_DISPLAY);
}
EGLDisplay dpy = egl_display_t::getFromNativeDisplay(display);
return dpy;
}
关于egl_init_drivers()网上很多资料,代码也不多,主要做的事情:
1.首先在frameworks\native\opengl\libs\EGL\egl_entries.in中保存了EGL的相关API,函数都是以egl开头;在frameworks\native\opengl\libs\entries.in中保存了OpenGL ES相关API,函数都是以gl开头。函数egl_init_drivers()需要做的就是找到EGL和OpenGL ES本地的实现,然后对这些函数进行赋值。
char const * const egl_names[] = {
#include "egl_entries.in"
NULL
};
char const * const gl_names[] = {
#include "../entries.in"
NULL
};
2.在/vendor/lib/egl或/system/lib/egl下(不是/system/lib/下),寻找libGLES.so,如果未找到,则寻找libGLES_*.so,上面两种库只要找到一个,则打开库,这个库里包含了EGL和OpenGL ES具体实现的库,然后“取出”库中具体的函数实现进行赋值。
但是较新版本的android已经不使用软件实现,即libagl,所以即使找到libGLES_android.so也不会去用。
if (!strcmp(e->d_name, "libGLES_android.so")) {
// always skip the software renderer
continue;
}
如果libGLES.so和libGLES_*.so都未找到,需要分别去加载/vendor/lib/egl或/system/lib/egl下libEGL.so, libGLESv1_CM.so, libGLESv2.so这三个库或者libEGL_*.so, libGLESv1_CM_*.so, libGLESv2_*.so,然后分别对EGL、OpenGL ES V1、OpenGL ES V2的具体实现去赋值(那意思是libGLES_*.so中其实囊括了三个库所有的具体实现)。至此,就将OpenGL ES“标准”和“实现”挂钩了。
总结下,android中OpenGL ES的实现方式有2种:
一种是软件实现,用cpu去绘图,这就是所谓的agl(libGLES_android.so),代码路径在frameworks\native\opengl\libagl
,即the software OpenGL ES library
;
另一种是硬件厂商根据自己GPU提供的实现,一般都不开放源代码,就是上面介绍的需要去/vendor/lib/egl或/system/lib/egl找的几个库,但是只要把API 函数赋值上正确的实现函数即可。
此外由于OpenGL ES的实现是系统无关的,所以EGL库的另一个作用就是将OpenGL ES和本地窗口系统结合起来,很多书上都这么写的,举个例子好理解,如果你要画个纹理多边形,调用OpenGL ES接口,如果要把图形render到屏幕,需要调用EGL接口。例如在使用OpenGL ES前首先需要调用EGL的相关函数去搭建好OpenGL ES的本地环境等,EGL是android使用OpenGL ES API绘图的助手!
从android 4.0开始,支持了硬件加速的Canvas,应该就是修改了Canvas的具体实现,不用skia了,而改调用EGL和OpenGL ES API了。
那么我们平时写app时为何不用调用上面的Canvas和OpenGL ES,也能出现漂亮的UI呢?因为我们使用的都是android上层封装好的类,例如TextView就是用了Canvas,而GlSurfaceView就是使用了OpenGL ES,android已经帮我们做了大部分的工作。当然完全可以不用调用上层的Java类,而用c++/c去直接调用Canvas和OpenGL ES。
画图问题解决了,已经将图形render到layer上了,如何去合成呢?
首先介绍个概念,hardware overlay,
In computing, hardware overlay, a type of video overlay, provides a method of rendering an image to a display screen with a dedicated memory buffer inside computer video hardware.
The technique aims to improve the display of a fast-moving video image — such as a computer game, a DVD, or the signal from a TV card.
Most video cards manufactured since about 1998 and most media players support hardware overlay.
The overlay is a dedicated buffer into which one app can render (typically video), without incurring the significant performance cost of checking for clipping and
overlapping rendering by other apps. The framebuffer has hardware support for importing and rendering the buffer contents without going through the GPU.
hardware overlay 是提供一种机制,直接render到display screen的硬件内存中,提高显示效率吧。
而android对layer的合成主要包括2部分:在GPU中合成和在display的硬件中进行buffer的合成。
在GPU中进行合成,既是利用OpenGL ES进行合成,需要注意,画图的时候我们用了OpenGL ES,这里合成时也用了,功能真是强大,开始一直奇怪为何SurfaceFlinger也要去调用EGL的函数,原来是需要用OpenGL ES去合成layer;在display的硬件中进行合成,也就是hardware overlay机制。
那么android是如何使用这两种合成机制的呢?这里就是Hardware Composer的功劳。处理流程为:
SurfaceFlinger给HWC提供layer list,询问如何处理这些layer;
HWC将每个layer标记为overlay或者GLES composition,然后回馈给SurfaceFlinger;
SurfaceFlinger需要去处理那些GLES的合成,而不用去管overlay的合成,最后将overlay的layer和GLES合成后的buffer发送给HWC处理。
借用google一张图说明,可以将上面讲的很多概念展现,很清晰。
在我认为使用overlay后,可以将SurfaceFlinger的工作减轻,即少一些GLES的合成,HWC承担了部分OpenGL ES 和GPU的工作, 从而减少了功耗。
注:如果屏幕上的画面基本不变化,这时候用GLES 合成的效率要高于overlay(overlay主要是为了render快速变化的图形等);
android 4.4支持4个oveylay,如果要合成超过4个layer,系统就会对剩余的使用GLES合成,所以app的layer个数对手机的功耗影响挺大。