分享

Android SurfaceFlinger之OpenGL库加载过程

 开花结果 2022-05-25 发布于北京
  • http://blog.csdn.net/ieearth/article/details/55518607

    1、egl_init_drivers

    Android中OpenGL库加载从egl_init_drivers函数开始,源码位置在frameworks/native/opengl/libs/EGL/egl.cpp。

    static pthread_mutex_t sInitDriverMutex = PTHREAD_MUTEX_INITIALIZER;
    
    EGLBoolean egl_init_drivers() {
        EGLBoolean res;
        pthread_mutex_lock(&sInitDriverMutex);
        res = egl_init_drivers_locked();
        pthread_mutex_unlock(&sInitDriverMutex);
        return res;
    }

    初始化过程中使用了mutex进行同步保护,下面分析egl_init_drivers_locked。

    2、egl_init_drivers_locked

    // this mutex protects:
    //    d->disp[]
    //    egl_init_drivers_locked()
    //
    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) {
            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_init_drivers_locked实现中可以看出OpenGL库最终是通过loader对象完成的,首先看一下sEarlyInitState是何方神圣。

    3、sEarlyInitState

    static void early_egl_init(void)
    {
        int numHooks = sizeof(gHooksNoContext) / sizeof(EGLFuncPointer);
        EGLFuncPointer *iter = reinterpret_cast<EGLFuncPointer*>(&gHooksNoContext);
        for (int hook = 0; hook < numHooks; ++hook) {
            *(iter++) = reinterpret_cast<EGLFuncPointer>(gl_no_context);
        }
    
        setGLHooksThreadSpecific(&gHooksNoContext);
    }
    
    static pthread_once_t once_control = PTHREAD_ONCE_INIT;
    static int sEarlyInitState = pthread_once(&once_control, &early_egl_init);

    sEarlyInitState为pthread_once的返回值,成功时返回0。pthread_once保证多线程时early_egl_init只执行一次,early_egl_init用来设置线程特有的数据(pthread_setspecific/pthread_getspecific),这也是一种数据共享方式。EGLFuncPointer为函数指针,如下所示:

    typedef void (*__eglMustCastToProperFunctionPointerType)(void);
    typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer;
    
        1
        2

    gHooksNoContext是个有趣的数据结构,如下所示:

    // maximum number of GL extensions that can be used simultaneously in
    // a given process. this limitation exists because we need to have
    // a static function for each extension and currently these static functions
    // are generated at compile time.
    #define MAX_NUMBER_OF_GL_EXTENSIONS 256
    
    struct gl_hooks_t {
        struct gl_t {
            #include "entries.in"
        } gl;
        struct gl_ext_t {
            __eglMustCastToProperFunctionPointerType extensions[MAX_NUMBER_OF_GL_EXTENSIONS];
        } ext;
    };
    
    extern gl_hooks_t gHooksNoContext;

    上面的gl_t结构体实际上包含了同OpenGL函数一致的函数指针,通过“entries.in”和GL_ENTRY进行扩展,如下所示:

    #define GL_ENTRY(_r, _api, ...) _r (*(_api))(__VA_ARGS__);
    
    // entries.in
    GL_ENTRY(void, glActiveShaderProgram, GLuint pipeline, GLuint program)
    GL_ENTRY(void, glActiveShaderProgramEXT, GLuint pipeline, GLuint program)
    ...
    ...

    gl_no_context用于检查OpenGL函数调用是否设置了Context,其中用到了设置线程键的egl_tls_t类和收集函数调用栈帧的CallStack类。

    static int gl_no_context() {
        if (egl_tls_t::logNoContextCall()) {
            char const* const error = "call to OpenGL ES API with "
                    "no current context (logged once per thread)";
            if (LOG_NDEBUG) {
                ALOGE(error);
            } else {
                LOG_ALWAYS_FATAL(error);
            }
            char value[PROPERTY_VALUE_MAX];
            property_get("debug.egl.callstack", value, "0");
            if (atoi(value)) {
                CallStack stack(LOG_TAG);
            }
        }
        return 0;
    }

    随后,将修改过的gHooksNoContext通过setGLHooksThreadSpecific设置线程键,而__get_tls则是通过汇编实现的(不同的硬件有不同的汇编代码),如下所示:

    void setGLHooksThreadSpecific(gl_hooks_t const *value) {
        setGlThreadSpecific(value);
    }
    
    void setGlThreadSpecific(gl_hooks_t const *value) {
        gl_hooks_t const * volatile * tls_hooks = get_tls_hooks();
        tls_hooks[TLS_SLOT_OPENGL_API] = value;
    }
    
    inline gl_hooks_t const * volatile * get_tls_hooks() {
        volatile void *tls_base = __get_tls();
        gl_hooks_t const * volatile * tls_hooks =
                reinterpret_cast<gl_hooks_t const * volatile *>(tls_base);
        return tls_hooks;
    }
    
    // Well-known TLS slots. What data goes in which slot is arbitrary unless otherwise noted.
    enum {
      TLS_SLOT_SELF = 0, // The kernel requires this specific slot for x86.
      TLS_SLOT_THREAD_ID,
      TLS_SLOT_ERRNO,
    
      // These two aren't used by bionic itself, but allow the graphics code to
      // access TLS directly rather than using the pthread API.
      TLS_SLOT_OPENGL_API = 3,
      TLS_SLOT_OPENGL = 4,
    
      // This slot is only used to pass information from the dynamic linker to
      // libc.so when the C library is loaded in to memory. The C runtime init
      // function will then clear it. Since its use is extremely temporary,
      // we reuse an existing location that isn't needed during libc startup.
      TLS_SLOT_BIONIC_PREINIT = TLS_SLOT_OPENGL_API,
    
      TLS_SLOT_STACK_GUARD = 5, // GCC requires this specific slot for x86.
      TLS_SLOT_DLERROR,
    
      // Fast storage for Thread::Current() in ART.
      TLS_SLOT_ART_THREAD_SELF,
    
      // Lets TSAN avoid using pthread_getspecific for finding the current thread
      // state.
      TLS_SLOT_TSAN,
    
      BIONIC_TLS_SLOTS // Must come last!
    };

    4、egl_connection_t

    在分析Loader前先来看一下egl_connection_t,其结构如下所示:

    extern egl_connection_t gEGLImpl;
    extern gl_hooks_t gHooks[2];
    
    struct egl_connection_t {
        enum {
            GLESv1_INDEX = 0,
            GLESv2_INDEX = 1
        };
    
        inline egl_connection_t() : dso(0) { }
        void *              dso;
        gl_hooks_t *        hooks[2];
        EGLint              major;
        EGLint              minor;
        egl_t               egl;
    
        void*               libEgl;
        void*               libGles1;
        void*               libGles2;
    };

    从上面的egl_connection_t结构体及前面的egl_init_drivers_locked函数定义可以看出,包括两个hook,GLESv1和GLESv2,而dso则实际上指向了libEGL、libGLESv1和libGLESv2(从后面的Loader分析中可以看出),egl_t类似于gl_t,包含了一系列egl函数指针。下面着重分析Loader。

    5、Loader-open

    Loader是个单实例,如下所示:

    class Loader : public Singleton<Loader>

    从Loader::open开始,包括两部分,load_driver和load_wrapper,如下所示:

    void* Loader::open(egl_connection_t* cnx)
    {
        void* dso;
        driver_t* hnd = 0;
    
        setEmulatorGlesValue();
    
        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");
    
        cnx->libEgl   = load_wrapper(EGL_WRAPPER_DIR "/libEGL.so");
        cnx->libGles2 = load_wrapper(EGL_WRAPPER_DIR "/libGLESv2.so");
        cnx->libGles1 = load_wrapper(EGL_WRAPPER_DIR "/libGLESv1_CM.so");
    
        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;
    }

    在分析load_driver和load_wrapper之前,先来看一下driver_t,这是个Loader类的内部结构体,如下所示:

    struct driver_t {
            explicit driver_t(void* gles);
            ~driver_t();
            status_t set(void* hnd, int32_t api);
            void* dso[3];
        };

    driver_t的dso分别保存了EGL、GLESv1_CM和GLESv2三个库。open函数中还调用了setEmulatorGlesValue,这个函数就是检查、设置一些模拟器属性(是否在模拟器中运行?在模拟中运行时是否有GPU支持?),比较简单,代码如下:

    static void setEmulatorGlesValue(void) {
        char prop[PROPERTY_VALUE_MAX];
        property_get("ro.kernel.qemu", prop, "0");
        if (atoi(prop) != 1) return;
    
        property_get("ro.kernel.qemu.gles",prop,"0");
        if (atoi(prop) == 1) {
            ALOGD("Emulator has host GPU support, qemu.gles is set to 1.");
            property_set("qemu.gles", "1");
            return;
        }
    
        // for now, checking the following
        // directory is good enough for emulator system images
        const char* vendor_lib_path =
    #if defined(__LP64__)
            "/vendor/lib64/egl";
    #else
            "/vendor/lib/egl";
    #endif
    
        const bool has_vendor_lib = (access(vendor_lib_path, R_OK) == 0);
        if (has_vendor_lib) {
            ALOGD("Emulator has vendor provided software renderer, qemu.gles is set to 2.");
            property_set("qemu.gles", "2");
        } else {
            ALOGD("Emulator without GPU support detected. "
                  "Fallback to legacy software renderer, qemu.gles is set to 0.");
            property_set("qemu.gles", "0");
        }
    }

    6、Loader-load_driver

    void *Loader::load_driver(const char* kind, egl_connection_t* cnx, uint32_t mask)
    {
    class MatchFile {};
    ...
    }

    EGL驱动需要提供一个单一的库libGLES.so,或者是三个分开的库libEGL.so、libGLESv1_CM.so和libGLESv2.so,模拟器中的软描画则需要提供单一的库libGLES_android.so,为了兼容旧版本的“egl.cfg”文件配置,这几个库后面还可能加一个名称如_emulation。在load_driver函数中还有个内部类MatchFile,用于在某个lib目录中查找特定的OpenGL库是否存在,存在的话进而通过dlopen打开,dlopen成功后再通过dlsym逐个获取OpenGL所有API的地址并将它们保存下来。

    7、load_wrapper

    load_wrapper相对load_driver来说较简单,就是dlopen一个so,如下所示:

    static void* load_wrapper(const char* path) {
        void* so = dlopen(path, RTLD_NOW | RTLD_LOCAL);
        ALOGE_IF(!so, "dlopen("%s") failed: %s", path, dlerror());
        return so;
    }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多