BootAnimation分析(二)

前文主要是论述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的库的关系:

《BootAnimation分析(二)》
《BootAnimation分析(二)》

如图所示,中间存在一层接口层,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;
}
    原文作者:Kelvin wu
    原文地址: https://zhuanlan.zhihu.com/p/22790903
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞