前文主要是论述native进程的binder通信的机制是如何建立起来的,这一篇就主要去讨论跟显示相关的知识。
SurfaceFlinger是作为android显示的基石,牢牢地掌控住了framebuffer,任何在android系统中需要显示的界面都需要通过SF进行管理。native的程序画图的话可不比写app那么惬意,使用的API都是gl开头的glxxx函数,都是opengl的标准接口,接口数量和参数的丰富性直接导致你读code的痛苦,理解起来会比较吃力。opengl作为一套接口标准,提供了3D画图的方法,无论是在PC、android或者一些其他的嵌入式设备中,这些接口都是一样定义的,这样就能保证程序的可移植性。egl相对于opengl就没那么常见了,这套接口及实现主要是为了适应匹配具体的系统底层画图。因为硬件及系统差异,egl的实现会对应有差异,比如说windows对egl的接口实现是针对windows底层,linux的egl实现也不相同。然后opengl的接口会对接去调用对应的egl接口,这样egl就相当于是opengl和具体系统底层之间的中间件存在。(备注:android的appUI使用的是Skia库而非opengl,因为app的UI大多是2D图像所以犯不着用opengl吧)
这里分析一下android中opengl、egl的库的关系:
如图所示,中间存在一层接口层,apk和surfaceflinger调用的是接口层的库,而接口层会转而根据平台的配置寻找真正的底层实现库对接上中间接口层的api,这种设计能灵活地动态根据平台的差异自动适配,完成从上往下call的流程。
java opengl api路径:/frameworks/base/opengl/java/android/opengl
这里主要提供了opengl和EGL的java api,供apk使用,这里的java函数基本上都是native函数
c++ EGL和OPENGL ES接口层路径:/frameworks/native/opengl/libs
从这块的Android.mk中定义的模块有如下:
LOCAL_MODULE:= libEGL
LOCAL_MODULE:= libGLESv1_CM (实现了OpenGL ES 1.x标准的API)
LOCAL_MODULE:= libGLESv2 (实现了OpenGL ES 2.x标准的API)
接口层的动态库都是在这里编译
最底层的实现库中带_mali后缀的lib是硬件实现库,由硬件厂商提供已经编译好的so;带_android后缀的lib则是软件模拟的实现库。
jni层的代码存储在frameworks/base/core/jni中,存放路径不跟opengl的java目录平级,这安排也不是很合理吧。再加上源码中叫graphic和opengl的目录到处都是,容易犯晕@。@
在硬件平台的如下路径会存在,一个配置文件,这个配置文件会提示当前的平台需要怎样加载对应的egl实现库,同时该目录下也存放了egl的硬件实现库和软件实现库
/system/lib/egl # cat egl.cfg
0 0 android
0 1 mali
egl的接口层库会通过读取该规则来做对应的加载实现库,具体的load规则在/frameworks/native/opengl/libs/EGL/Loader.cpp中可以找到,但是直接进这个文件去看怎么load的并不是很符合认知的观念,这里我就从上往下trace看是怎么调用到这里的。
入口:获取EGLDisplay
EGLDisplay 是一个关联系统物理屏幕的通用数据类型。在涉及到显示的native程序中,第一步都是获取一下显示屏
EGLDisplay eglGetDisplay(EGLNativeDisplayType display)
{
clearError();
uintptr_t index = reinterpret_cast<uintptr_t>(display);
if (index >= NUM_DISPLAYS) {
return setError(EGL_BAD_PARAMETER, EGL_NO_DISPLAY);
}
if (egl_init_drivers() == EGL_FALSE) {
//egl初始化drvier,这里就跟load具体的库相关
return setError(EGL_BAD_PARAMETER, EGL_NO_DISPLAY);
}
EGLDisplay dpy = egl_display_t::getFromNativeDisplay(display);
return dpy;
}
因为这个函数是所有opengl程序的第一步,所以在这里包含egl初始化驱动的操作也很正常,这个egl_init_drivers函数的调用频率也很高,其实就是返回是否初始化的状态
EGLBoolean egl_init_drivers() {
EGLBoolean res;
pthread_mutex_lock(&sInitDriverMutex);
res = egl_init_drivers_locked();
pthread_mutex_unlock(&sInitDriverMutex);
return res;
}
static EGLBoolean egl_init_drivers_locked() {
if (sEarlyInitState) {
// initialized by static ctor. should be set here.
return EGL_FALSE;
}
// get our driver loader
Loader& loader(Loader::getInstance());
// dynamically load our EGL implementation
egl_connection_t* cnx = &gEGLImpl;
if (cnx->dso == 0) {
//全局的connection没有初始化是就进行初始化
cnx->hooks[egl_connection_t::GLESv1_INDEX] =
&gHooks[egl_connection_t::GLESv1_INDEX];
cnx->hooks[egl_connection_t::GLESv2_INDEX] =
&gHooks[egl_connection_t::GLESv2_INDEX];
cnx->dso = loader.open(cnx);
}
return cnx->dso ? EGL_TRUE : EGL_FALSE;
}
这里围绕一个egl_connection_t数据结构展开,这里第一映像上是将opengl v1和v2的所以接口hook到这个Connection数据上来,所以先看看egl_connection_t的定义。主干还是停留在hooks数组上,其他的并不具备太多意义
struct egl_connection_t {
enum {
GLESv1_INDEX = 0,
GLESv2_INDEX = 1
};
inline egl_connection_t() : dso(0) { }
void * dso;
gl_hooks_t * hooks[2];//用于记录所有opengl的函数入口地址
EGLint major;
EGLint minor;
egl_t egl;//用于记录所有egl的函数入口地址
void* libEgl;//用于记录动态库的handle
void* libGles1;
void* libGles2;
};
再看数据gl_hooks_t,这也到了比较精髓的地方
#defineGL_ENTRY(_r, _api, ...) _r (*_api)(__VA_ARGS__);
#defineEGL_ENTRY(_r, _api, ...) _r (*_api)(__VA_ARGS__);
structegl_t {
#include "EGL/egl_entries.in"
};
structgl_hooks_t {
structgl_t {
#include "entries.in"
} gl;
structgl_ext_t {
__eglMustCastToProperFunctionPointerTypeextensions[MAX_NUMBER_OF_GL_EXTENSIONS];
} ext;
};
这里就用到了in文件,in文件的大致内容如下:
GL_ENTRY(void, glActiveShaderProgram, GLuint pipeline, GLuint program)
GL_ENTRY(void, glActiveShaderProgramEXT, GLuint pipeline, GLuint program)
GL_ENTRY(void, glActiveTexture, GLenum texture)
这里的gl_t就会扩展成一个包含了各个函数指针的结构体,这些个函数指针的名字都是opengl的标准api:
struct gl_t {
void *glActiveShaderProgram(GLuint pipeline, GLuint program);
......
}
其实到这里大致的原理就比较明显了,loader根据cfg的规则加载对应的实现库,将函数指针对接上这个hook里。
接下来调用loader.open(cnx)来填充connection
void* Loader::open(egl_connection_t* cnx)
{
void* dso;
driver_t* hnd = 0;
//step1、加载驱动,入参有connection
dso = load_driver("GLES", cnx, EGL | GLESv1_CM | GLESv2);
if (dso) {
hnd = new driver_t(dso);
} else {
// Always load EGL first
dso = load_driver("EGL", cnx, EGL);
if (dso) {
hnd = new driver_t(dso);
hnd->set( load_driver("GLESv1_CM", cnx, GLESv1_CM), GLESv1_CM );
hnd->set( load_driver("GLESv2", cnx, GLESv2), GLESv2 );
}
}
LOG_ALWAYS_FATAL_IF(!hnd, "couldn't find an OpenGL ES implementation");
#if defined(__LP64__)
cnx->libEgl = load_wrapper("/system/lib64/libEGL.so");
cnx->libGles2 = load_wrapper("/system/lib64/libGLESv2.so");
cnx->libGles1 = load_wrapper("/system/lib64/libGLESv1_CM.so");
#else
cnx->libEgl = load_wrapper("/system/lib/libEGL.so");
cnx->libGles2 = load_wrapper("/system/lib/libGLESv2.so");
cnx->libGles1 = load_wrapper("/system/lib/libGLESv1_CM.so");
#endif
LOG_ALWAYS_FATAL_IF(!cnx->libEgl,
"couldn't load system EGL wrapper libraries");
LOG_ALWAYS_FATAL_IF(!cnx->libGles2 || !cnx->libGles1,
"couldn't load system OpenGL ES wrapper libraries");
return (void*)hnd;
}
先看step1 加载驱动
void *Loader::load_driver(const char* kind,
egl_connection_t* cnx, uint32_t mask)
{
//这个类就一个方法find,输入需要寻找的库的类型,比如说是EGL或者GLES
//会根据这个类型去syste/lib下去寻找libEGL/libGLES/libGLESv1_CM/libGLESv2开头的库,然后返回这些库的绝对地址
//返回值
class MatchFile;
String8 absolutePath = MatchFile::find(kind);
if (absolutePath.isEmpty()) {
// this happens often, we don't want to log an error
return 0;
}
const char* const driver_absolute_path = absolutePath.string();
//通过dlopen来加载这样的库进来
void* dso = dlopen(driver_absolute_path, RTLD_NOW | RTLD_LOCAL);
if (dso == 0) {
const char* err = dlerror();
ALOGE("load_driver(%s): %s", driver_absolute_path, err?err:"unknown");
return 0;
}
ALOGD("loaded %s", driver_absolute_path);
//如果mask中设置了EGL的标志就从动态库中找到该函数指针
if (mask & EGL) {
getProcAddress = (getProcAddressType)dlsym(dso, "eglGetProcAddress");
ALOGE_IF(!getProcAddress,
"can't find eglGetProcAddress() in %s", driver_absolute_path);
egl_t* egl = &cnx->egl;
__eglMustCastToProperFunctionPointerType* curr =
(__eglMustCastToProperFunctionPointerType*)egl;
/*
这里的egl_names是一个字符数组,
char const * const egl_names[] = {
#include "egl_entries.in"
NULL
};
再结合
#define EGL_ENTRY(_r, _api, ...) #_api,
EGL_ENTRY这个宏的形式可以多变,可以使整个in文件时而变成函数指针时而变成字符串数组
这个数组就是所有api的名字的字符串数组
然后变量整个数组,根据这些api的名字去动态库中获取所有函数的入口赋值给curr
*/
char const * const * api = egl_names;
while (*api) {
char const * name = *api;
__eglMustCastToProperFunctionPointerType f =
(__eglMustCastToProperFunctionPointerType)dlsym(dso, name);
if (f == NULL) {
// couldn't find the entry-point, use eglGetProcAddress()
f = getProcAddress(name);
if (f == NULL) {
f = (__eglMustCastToProperFunctionPointerType)0;
}
}
*curr++ = f;
api++;
}
}
//如果mask中设置了GL的标志就从动态库中找到该函数指针,跟上面加载EGL原理类似
if (mask & GLESv1_CM) {
init_api(dso, gl_names,
(__eglMustCastToProperFunctionPointerType*)
&cnx->hooks[egl_connection_t::GLESv1_INDEX]->gl,
getProcAddress);
}
if (mask & GLESv2) {
init_api(dso, gl_names,
(__eglMustCastToProperFunctionPointerType*)
&cnx->hooks[egl_connection_t::GLESv2_INDEX]->gl,
getProcAddress);
}
return dso;
}
到此就完成了所有egl和opengl函数的对接,为上层提供的egl和opengl的api都会通过loader去得到connection,然后从connection中hook上的函数指针去调用真正的实现函数的入口。
这里举个例子,就拿getDisplay来看:
101 EGLDisplay egl_display_t::getDisplay(EGLNativeDisplayType display) {
102
103 Mutex::Autolock _l(lock);
104
105 // get our driver loader
106 Loader& loader(Loader::getInstance());
107
108 egl_connection_t* const cnx = &gEGLImpl;
109 if (cnx->dso && disp.dpy == EGL_NO_DISPLAY) {
110 EGLDisplay dpy = cnx->egl.eglGetDisplay(display);
//调用connection中的egl中的对应的eglGetDisplay函数指针完成功能
111 disp.dpy = dpy;
112 if (dpy == EGL_NO_DISPLAY) {
113 loader.close(cnx->dso);
114 cnx->dso = NULL;
115 }
116 }
117
118 return EGLDisplay(uintptr_t(display) + 1U);
119 }
EGL和OpenGL的大概的库的结构就是这样子啦。
BootAnimation的bin档主要由BootAnimation这个类提供功能,我们可以看到在下面的BootAnimation中就大量使用egl和OpenGL的接口函数
BootAnimation::BootAnimation() : Thread(false), mZip(NULL)
{
//构造一个SurfaceFlinger的客户端
mSession = new SurfaceComposerClient();
}
void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
if (err == NO_ERROR) {
//继承子Thread
run("BootAnimation", PRIORITY_DISPLAY);
}
}
这里稍微关注下linkToDeath到底是什么用:主要是告诉service,如果你挂掉了,需要通知我。不能死了不告知一声,否则我还以为你没死还去找你处理业务就不好了。这个方法应该是将此BpBinder交付过去给到service,让其留底。
通知好了SF之后,进了run方法,由于BootAnimation的基类是Thread,所以会调到两个函数,ReadyToRun和ThreadLoop
//线程运行之前的准备,关键:涉及与SF的通信拿到surface对象,gl&egl初始化
status_t BootAnimation::readyToRun() {
mAssets.addDefaultAssets();
229
230 sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
231 ISurfaceComposer::eDisplayIdMain));
232 DisplayInfo dinfo;
233 status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
234 if (status)
235 return -1;
236
237 // create the native surface
238 sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
239 dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
240
241 SurfaceComposerClient::openGlobalTransaction();
242 control->setLayer(0x40000000);
//设置surface的在sf中的层级
243 SurfaceComposerClient::closeGlobalTransaction();
244
245 sp<Surface> s = control->getSurface();
246
247 // initialize opengl and egl
248 const EGLint attribs[] = {
249 EGL_RED_SIZE, 8,
250 EGL_GREEN_SIZE, 8,
251 EGL_BLUE_SIZE, 8,
252 EGL_DEPTH_SIZE, 0,
253 EGL_NONE
254 };
255 EGLint w, h;
256 EGLint numConfigs;
257 EGLConfig config;
258 EGLSurface surface;
259 EGLContext context;
260
/*********************以下是egl画图预备姿势的套路******************************/
//step1、获得显示屏
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//step2、初始化egl
eglInitialize(display, 0, 0);
//step3、选择egl的配置
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
surface = eglCreateWindowSurface(display, config, s.get(), NULL);
context = eglCreateContext(display, config, NULL, NULL);
267 eglQuerySurface(display, surface, EGL_WIDTH, &w);
268 eglQuerySurface(display, surface, EGL_HEIGHT, &h);
269
270 if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
271 return NO_INIT;
272
273 mDisplay = display;
274 mContext = context;
275 mSurface = surface;
276 mWidth = w;
277 mHeight = h;
278 mFlingerSurfaceControl = control;
279 mFlingerSurface = s;
280
281 // If the device has encryption turned on or is in process
282 // of being encrypted we show the encrypted boot animation.
283 char decrypt[PROPERTY_VALUE_MAX];
284 property_get("vold.decrypt", decrypt, "");
285
286 bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);
287
288 if (encryptedAnimation && (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0)) {
289 mZipFileName = SYSTEM_ENCRYPTED_BOOTANIMATION_FILE;
290 }
291 else if (access(OEM_BOOTANIMATION_FILE, R_OK) == 0) {
292 mZipFileName = OEM_BOOTANIMATION_FILE;
293 }
294 else if (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) {
295 mZipFileName = SYSTEM_BOOTANIMATION_FILE;
296 }
297 return NO_ERROR;
}