Android 圖形驅動初始化 頂 原 薦

從應用程序的角度看 OpenGL 圖形系統的接口,主要包括兩大部分,一部分是 EGL,它爲 OpenGL 渲染準備環境;另一部分是 OpenGL,它執行圖形渲染。通過這些接口構造渲染環境,並執行渲染的過程,可以參考 在 Android 中使用 OpenGL

對於 Android OpenGL 圖形系統的實現的分析,從 EGL context 的創建開始。先來看一下獲取 Display 的過程。首先來看 EGLContext.getEGL()

public abstract class EGLContext
{
    private static final EGL EGL_INSTANCE = new com.google.android.gles_jni.EGLImpl();
    
    public static EGL getEGL() {
        return EGL_INSTANCE;
    }

    public abstract GL getGL();
}

返回的 EGL 實例爲 EGLImpl,即我們在應用中使用的 EGL 實際爲 com.google.android.gles_jni.EGLImpl

獲取 Display

然後來看 eglGetDisplay()EGLImpl 的定義位於 frameworks/base/opengl/java/com/google/android/gles_jni/EGLImpl.java,其中 eglGetDisplay() 定義如下:

    public synchronized EGLDisplay eglGetDisplay(Object native_display) {
        long value = _eglGetDisplay(native_display);
        if (value == 0) {
            return EGL10.EGL_NO_DISPLAY;
        }
        if (mDisplay.mEGLDisplay != value)
            mDisplay = new EGLDisplayImpl(value);
        return mDisplay;
    }
. . . . . .
    private native long _eglGetDisplay(Object native_display);

這裏主要通過調用本地層方法 _eglGetDisplay() 得到 Display,然後創建 EGLDisplayImpl_eglGetDisplay() 返回底層的 Display 對象句柄。

EGLDisplayImpl 的實現很簡單:

package com.google.android.gles_jni;

import javax.microedition.khronos.egl.*;

public class EGLDisplayImpl extends EGLDisplay {
    long mEGLDisplay;

    public EGLDisplayImpl(long dpy) {
        mEGLDisplay = dpy;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EGLDisplayImpl that = (EGLDisplayImpl) o;

        return mEGLDisplay == that.mEGLDisplay;

    }

    @Override
    public int hashCode() {
        /*
         * Based on the algorithm suggested in
         * http://developer.android.com/reference/java/lang/Object.html
         */
        int result = 17;
        result = 31 * result + (int) (mEGLDisplay ^ (mEGLDisplay >>> 32));
        return result;
    }
}

EGLDispaly 實際是一個標記接口:

package javax.microedition.khronos.egl;

public abstract class EGLDisplay
{
}

可見 EGLDisplayImpl 僅僅包裝了本地層返回的 Display 對象句柄。

本地層 _eglGetDisplay() 的實現位於 frameworks/base/core/jni/com_google_android_gles_jni_EGLImpl.cpp

static jlong jni_eglGetDisplay(JNIEnv *_env, jobject _this, jobject native_display) {
    return reinterpret_cast<jlong>(eglGetDisplay(EGL_DEFAULT_DISPLAY));
}
. . . . . .
{"_eglGetDisplay",   "(" OBJECT ")J", (void*)jni_eglGetDisplay },

這裏通過調用 EGL 庫的 eglGetDisplay() 獲得 Display。eglGetDisplay() 的定義位於 frameworks/native/opengl/libs/EGL/eglApi.cpp

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) {
        return setError(EGL_BAD_PARAMETER, EGL_NO_DISPLAY);
    }

    EGLDisplay dpy = egl_display_t::getFromNativeDisplay(display);
    return dpy;
}

在這個函數中,首先根據需要執行 egl_init_drivers() 初始化驅動庫。然後通過 egl_display_t::getFromNativeDisplay(display) 獲得 Dispaly。

egl_display_t::getFromNativeDisplay(display) 的定義(位於 frameworks/native/opengl/libs/EGL/egl_display.cpp)如下:

EGLDisplay egl_display_t::getDisplay(EGLNativeDisplayType display) {

    Mutex::Autolock _l(lock);

    // get our driver loader
    Loader& loader(Loader::getInstance());

    egl_connection_t* const cnx = &gEGLImpl;
    if (cnx->dso && disp.dpy == EGL_NO_DISPLAY) {
        EGLDisplay dpy = cnx->egl.eglGetDisplay(display);
        disp.dpy = dpy;
        if (dpy == EGL_NO_DISPLAY) {
            loader.close(cnx->dso);
            cnx->dso = NULL;
        }
    }

    return EGLDisplay(uintptr_t(display) + 1U);
}

在這個函數中,最爲關鍵的就是,在 disp.dpyEGL_NO_DISPLAY 時,通過 cnx->egl.eglGetDisplay() 初始化它了。

EGLDisplay 的定義如下:

typedef void *EGLDisplay;

它僅是 void 指針的 typedef。

總結一下獲取 Display 的整個過程

  • 通過 frameworks/native/opengl/libs/EGL 初始化圖形驅動;
  • 通過廠商提供的設備特有的 EGL 庫接口初始化 Display。

Android 圖形驅動初始化

接下來更詳細地看一下圖形驅動初始化。這通過 egl_init_drivers() 完成,該函數定義 (位於frameworks/native/opengl/libs/EGL/egl.cpp) 如下:

egl_connection_t gEGLImpl;
gl_hooks_t gHooks[2];
. . . . . .
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;
}

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;
}

圖形驅動初始化通過 Loader::open(egl_connection_t* cnx) 完成,初始化的結果將存儲於全局結構 egl_connection_t gEGLImplgl_hooks_t gHooks[2] 中。

來看一下 gl_hooks_t 的定義(位於 frameworks/native/opengl/libs/hooks.h):

// 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
. . . . . .
#undef GL_ENTRY
#undef EGL_ENTRY
#define GL_ENTRY(_r, _api, ...) _r (*_api)(__VA_ARGS__);
#define EGL_ENTRY(_r, _api, ...) _r (*_api)(__VA_ARGS__);

struct egl_t {
    #include "EGL/egl_entries.in"
};

struct gl_hooks_t {
    struct gl_t {
        #include "entries.in"
    } gl;
    struct gl_ext_t {
        __eglMustCastToProperFunctionPointerType extensions[MAX_NUMBER_OF_GL_EXTENSIONS];
    } ext;
};
#undef GL_ENTRY
#undef EGL_ENTRY

其中 __eglMustCastToProperFunctionPointerType 定義 (位於frameworks/native/opengl/include/EGL/egl.h) 如下:

/* This is a generic function pointer type, whose name indicates it must
 * be cast to the proper type *and calling convention* before use.
 */
typedef void (*__eglMustCastToProperFunctionPointerType)(void);

__eglMustCastToProperFunctionPointerType 是函數指針類型。struct gl_hooks_tstruct gl_ext_t ext 即爲函數指針表,它們用來描述 EGL 擴展接口。

struct gl_hooks_t 內的 struct gl_t 結構體,其結構體成員在另外一個文件,即 entries.in 中定義,該文件位於 frameworks/native/opengl/libs/entries.in

GL_ENTRY(void, glActiveShaderProgram, GLuint pipeline, GLuint program)
GL_ENTRY(void, glActiveShaderProgramEXT, GLuint pipeline, GLuint program)
GL_ENTRY(void, glActiveTexture, GLenum texture)
GL_ENTRY(void, glAlphaFunc, GLenum func, GLfloat ref)
GL_ENTRY(void, glAlphaFuncQCOM, GLenum func, GLclampf ref)
. . . . . .

配合 frameworks/native/opengl/libs/hooks.hGL_ENTRY 宏的定義:

#define GL_ENTRY(_r, _api, ...) _r (*_api)(__VA_ARGS__);

可以看到 struct gl_hooks_tstruct gl_t gl 的所有成員都是函數指針,即它是一個函數表,一個 OpenGL 接口函數的函數表。

上面看到的 struct egl_tstruct gl_hooks_tstruct gl_t gl 定義類似,只是它的結構體成員來自於另外一個文件 frameworks/native/opengl/libs/EGL/egl_entries.in

EGL_ENTRY(EGLDisplay, eglGetDisplay, NativeDisplayType)
EGL_ENTRY(EGLBoolean, eglInitialize, EGLDisplay, EGLint*, EGLint*)
EGL_ENTRY(EGLBoolean, eglTerminate, EGLDisplay)
EGL_ENTRY(EGLBoolean, eglGetConfigs, EGLDisplay, EGLConfig*, EGLint, EGLint*)
EGL_ENTRY(EGLBoolean, eglChooseConfig, EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *)
. . . . . .

EGL_ENTRY 宏的定義與 GL_ENTRY 宏的完全相同。struct egl_t 同樣爲一個函數表,只是它是 EGL 接口的函數表。

再來看 egl_connection_t 的定義,位於 frameworks/native/opengl/libs/EGL/egldefs.h

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;
};

這個結構包含了主、次版本號; 4 個指針;兩個函數表的指針以及一個函數表。

Loader::open(egl_connection_t* cnx) 初始化圖形驅動,主要是初始化這些函數表和指針。Loader::open(egl_connection_t* cnx) 的定義如下:

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;
}

#ifndef EGL_WRAPPER_DIR
#if defined(__LP64__)
#define EGL_WRAPPER_DIR "/system/lib64"
#else
#define EGL_WRAPPER_DIR "/system/lib"
#endif
#endif

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");
    }
}

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;
}

這個函數中,執行步驟如下:

  1. 爲設備是模擬器的情況,設置系統屬性 qemu.gles
  2. 加載設備特有的圖形驅動庫,包括 EGL 庫,OpenGL ES 1.0 和 2.0 的庫。
  3. 加載圖形驅動 Wrapper,它們都位於 /system/lib64/system/lib

egl_connection_t 中的幾個指針中,dso 實際爲 struct driver_t 指針,該結構定義(位於 frameworks/native/opengl/libs/EGL/Loader.h)如下:

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

struct driver_t 包含設備生產商提供的設備特有 EGL 和 OpenGL ES 實現庫的句柄,如果 EGL 接口和 OpenGL 接口由單獨的庫實現,它包含一個庫的句柄,即這個單獨的庫,如果 EGL 接口由不同的庫實現,它則包含所有這些庫的句柄。

egl_connection_tlibEgllibGles2libGles1 爲動態鏈接庫句柄,其中 libEgl 指向 Android 的 EGL Wrapper 庫;libGles1 指向 Android 的 GLESv1_CM Wrapper 庫;libGles2 指向 Android 的 GLESv2 Wrapper 庫。

然後來看 Loader::load_driver()

/* This function is called to check whether we run inside the emulator,
 * and if this is the case whether GLES GPU emulation is supported.
 *
 * Returned values are:
 *  -1   -> not running inside the emulator
 *   0   -> running inside the emulator, but GPU emulation not supported
 *   1   -> running inside the emulator, GPU emulation is supported
 *          through the "emulation" host-side OpenGL ES implementation.
 *   2   -> running inside the emulator, GPU emulation is supported
 *          through a guest-side vendor driver's OpenGL ES implementation.
 */
static int
checkGlesEmulationStatus(void)
{
    /* We're going to check for the following kernel parameters:
     *
     *    qemu=1                      -> tells us that we run inside the emulator
     *    android.qemu.gles=<number>  -> tells us the GLES GPU emulation status
     *
     * Note that we will return <number> if we find it. This let us support
     * more additionnal emulation modes in the future.
     */
    char  prop[PROPERTY_VALUE_MAX];
    int   result = -1;

    /* First, check for qemu=1 */
    property_get("ro.kernel.qemu",prop,"0");
    if (atoi(prop) != 1)
        return -1;

    /* We are in the emulator, get GPU status value */
    property_get("qemu.gles",prop,"0");
    return atoi(prop);
}
. . . . . . 
void Loader::init_api(void* dso,
        char const * const * api,
        __eglMustCastToProperFunctionPointerType* curr,
        getProcAddressType getProcAddress)
{
    const ssize_t SIZE = 256;
    char scrap[SIZE];
    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) {
            // Try without the OES postfix
            ssize_t index = ssize_t(strlen(name)) - 3;
            if ((index>0 && (index<SIZE-1)) && (!strcmp(name+index, "OES"))) {
                strncpy(scrap, name, index);
                scrap[index] = 0;
                f = (__eglMustCastToProperFunctionPointerType)dlsym(dso, scrap);
                //ALOGD_IF(f, "found <%s> instead", scrap);
            }
        }
        if (f == NULL) {
            // Try with the OES postfix
            ssize_t index = ssize_t(strlen(name)) - 3;
            if (index>0 && strcmp(name+index, "OES")) {
                snprintf(scrap, SIZE, "%sOES", name);
                f = (__eglMustCastToProperFunctionPointerType)dlsym(dso, scrap);
                //ALOGD_IF(f, "found <%s> instead", scrap);
            }
        }
        if (f == NULL) {
            //ALOGD("%s", name);
            f = (__eglMustCastToProperFunctionPointerType)gl_unimplemented;

            /*
             * GL_EXT_debug_label is special, we always report it as
             * supported, it's handled by GLES_trace. If GLES_trace is not
             * enabled, then these are no-ops.
             */
            if (!strcmp(name, "glInsertEventMarkerEXT")) {
                f = (__eglMustCastToProperFunctionPointerType)gl_noop;
            } else if (!strcmp(name, "glPushGroupMarkerEXT")) {
                f = (__eglMustCastToProperFunctionPointerType)gl_noop;
            } else if (!strcmp(name, "glPopGroupMarkerEXT")) {
                f = (__eglMustCastToProperFunctionPointerType)gl_noop;
            }
        }
        *curr++ = f;
        api++;
    }
}

void *Loader::load_driver(const char* kind,
        egl_connection_t* cnx, uint32_t mask)
{
    class MatchFile {
    public:
        static String8 find(const char* kind) {
            String8 result;
            int emulationStatus = checkGlesEmulationStatus();
            switch (emulationStatus) {
                case 0:
#if defined(__LP64__)
                    result.setTo("/system/lib64/egl/libGLES_android.so");
#else
                    result.setTo("/system/lib/egl/libGLES_android.so");
#endif
                    return result;
                case 1:
                    // Use host-side OpenGL through the "emulation" library
#if defined(__LP64__)
                    result.appendFormat("/system/lib64/egl/lib%s_emulation.so", kind);
#else
                    result.appendFormat("/system/lib/egl/lib%s_emulation.so", kind);
#endif
                    return result;
                default:
                    // Not in emulator, or use other guest-side implementation
                    break;
            }

            String8 pattern;
            pattern.appendFormat("lib%s", kind);
            const char* const searchPaths[] = {
#if defined(__LP64__)
                    "/vendor/lib64/egl",
                    "/system/lib64/egl"
#else
                    "/vendor/lib/egl",
                    "/system/lib/egl"
#endif
            };

            // first, we search for the exact name of the GLES userspace
            // driver in both locations.
            // i.e.:
            //      libGLES.so, or:
            //      libEGL.so, libGLESv1_CM.so, libGLESv2.so

            for (size_t i=0 ; i<NELEM(searchPaths) ; i++) {
                if (find(result, pattern, searchPaths[i], true)) {
                    return result;
                }
            }

            // for compatibility with the old "egl.cfg" naming convention
            // we look for files that match:
            //      libGLES_*.so, or:
            //      libEGL_*.so, libGLESv1_CM_*.so, libGLESv2_*.so

            pattern.append("_");
            for (size_t i=0 ; i<NELEM(searchPaths) ; i++) {
                if (find(result, pattern, searchPaths[i], false)) {
                    return result;
                }
            }

            // we didn't find the driver. gah.
            result.clear();
            return result;
        }

    private:
        static bool find(String8& result,
                const String8& pattern, const char* const search, bool exact) {
            if (exact) {
                String8 absolutePath;
                absolutePath.appendFormat("%s/%s.so", search, pattern.string());
                if (!access(absolutePath.string(), R_OK)) {
                    result = absolutePath;
                    return true;
                }
                return false;
            }

            DIR* d = opendir(search);
            if (d != NULL) {
                struct dirent cur;
                struct dirent* e;
                while (readdir_r(d, &cur, &e) == 0 && e) {
                    if (e->d_type == DT_DIR) {
                        continue;
                    }
                    if (!strcmp(e->d_name, "libGLES_android.so")) {
                        // always skip the software renderer
                        continue;
                    }
                    if (strstr(e->d_name, pattern.string()) == e->d_name) {
                        if (!strcmp(e->d_name + strlen(e->d_name) - 3, ".so")) {
                            result.clear();
                            result.appendFormat("%s/%s", search, e->d_name);
                            closedir(d);
                            return true;
                        }
                    }
                }
                closedir(d);
            }
            return false;
        }
    };


    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();

    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;
    }

    if (mask & EGL) {
        ALOGD("EGL loaded %s", driver_absolute_path);
        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;
        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++;
        }
    }

    if (mask & GLESv1_CM) {
        ALOGD("GLESv1_CM loaded %s", driver_absolute_path);
        init_api(dso, gl_names,
            (__eglMustCastToProperFunctionPointerType*)
                &cnx->hooks[egl_connection_t::GLESv1_INDEX]->gl,
            getProcAddress);
    }

    if (mask & GLESv2) {
      ALOGD("GLESv2 loaded %s", driver_absolute_path);
      init_api(dso, gl_names,
            (__eglMustCastToProperFunctionPointerType*)
                &cnx->hooks[egl_connection_t::GLESv2_INDEX]->gl,
            getProcAddress);
    }

    return dso;
}

Loader::load_driver() 通過三個步驟完成驅動庫加載: 第一步,找到驅動庫文件的路徑。

    class MatchFile {
    public:
        static String8 find(const char* kind) {
            String8 result;
            int emulationStatus = checkGlesEmulationStatus();
            switch (emulationStatus) {
                case 0:
#if defined(__LP64__)
                    result.setTo("/system/lib64/egl/libGLES_android.so");
#else
                    result.setTo("/system/lib/egl/libGLES_android.so");
#endif
                    return result;
                case 1:
                    // Use host-side OpenGL through the "emulation" library
#if defined(__LP64__)
                    result.appendFormat("/system/lib64/egl/lib%s_emulation.so", kind);
#else
                    result.appendFormat("/system/lib/egl/lib%s_emulation.so", kind);
#endif
                    return result;
                default:
                    // Not in emulator, or use other guest-side implementation
                    break;
            }

            String8 pattern;
            pattern.appendFormat("lib%s", kind);
            const char* const searchPaths[] = {
#if defined(__LP64__)
                    "/vendor/lib64/egl",
                    "/system/lib64/egl"
#else
                    "/vendor/lib/egl",
                    "/system/lib/egl"
#endif
            };

            // first, we search for the exact name of the GLES userspace
            // driver in both locations.
            // i.e.:
            //      libGLES.so, or:
            //      libEGL.so, libGLESv1_CM.so, libGLESv2.so

            for (size_t i=0 ; i<NELEM(searchPaths) ; i++) {
                if (find(result, pattern, searchPaths[i], true)) {
                    return result;
                }
            }

            // for compatibility with the old "egl.cfg" naming convention
            // we look for files that match:
            //      libGLES_*.so, or:
            //      libEGL_*.so, libGLESv1_CM_*.so, libGLESv2_*.so

            pattern.append("_");
            for (size_t i=0 ; i<NELEM(searchPaths) ; i++) {
                if (find(result, pattern, searchPaths[i], false)) {
                    return result;
                }
            }

            // we didn't find the driver. gah.
            result.clear();
            return result;
        }

    private:
        static bool find(String8& result,
                const String8& pattern, const char* const search, bool exact) {
            if (exact) {
                String8 absolutePath;
                absolutePath.appendFormat("%s/%s.so", search, pattern.string());
                if (!access(absolutePath.string(), R_OK)) {
                    result = absolutePath;
                    return true;
                }
                return false;
            }

            DIR* d = opendir(search);
            if (d != NULL) {
                struct dirent cur;
                struct dirent* e;
                while (readdir_r(d, &cur, &e) == 0 && e) {
                    if (e->d_type == DT_DIR) {
                        continue;
                    }
                    if (!strcmp(e->d_name, "libGLES_android.so")) {
                        // always skip the software renderer
                        continue;
                    }
                    if (strstr(e->d_name, pattern.string()) == e->d_name) {
                        if (!strcmp(e->d_name + strlen(e->d_name) - 3, ".so")) {
                            result.clear();
                            result.appendFormat("%s/%s", search, e->d_name);
                            closedir(d);
                            return true;
                        }
                    }
                }
                closedir(d);
            }
            return false;
        }
    };


    String8 absolutePath = MatchFile::find(kind);
    if (absolutePath.isEmpty()) {
        // this happens often, we don't want to log an error
        return 0;
    }

checkGlesEmulationStatus() 函數,在不是運行於模擬器中時,返回 -1;在運行於模擬器中,但不支持 GPU 硬件模擬時返回 0;在運行於模擬器中,通過宿主機端的 OpenGL ES 實現來支持 GPU 硬件模擬時,返回 1;在運行於模擬器中,通過 Android 客戶系統端的生產商驅動的 OpenGL ES 實現來支持 GPU 硬件模擬時,返回 2。對於運行環境的這種判斷,主要依據兩個系統屬性,即 ro.kernel.qemuqemu.gles

只有在 checkGlesEmulationStatus() 返回 0,即運行於模擬器,但不支持 OpenGL ES 的 GPU 硬件模擬時,EGL 和 OpenGL ES 庫採用軟件實現 /system/lib64/egl/libGLES_android.so/system/lib/egl/libGLES_android.so

如果是物理設備,或者模擬器開啓了 OpenGL ES 的 GPU 硬件模擬,對特定圖形驅動庫文件——EGL 庫文件或 OpenGL ES 庫文件——的查找按照如下的順序進行:

  1. /vendor/lib64/egl/vendor/lib/egl 目錄下文件名符合 lib%s.so 模式,即 /vendor 下文件名完全匹配的庫文件,比如 /vendor/lib64/egl/libGLES.so
  2. /system/lib64/egl/system/lib/egl 目錄下文件名符合 lib%s.so 模式,即 /system 下文件名完全匹配的庫文件,比如 /system/lib64/egl/libGLES.so
  3. /vendor/lib64/egl/vendor/lib/egl 目錄下文件名符合 lib%s_*.so 模式,即 /vendor 下文件名前綴匹配的庫文件,比如對於 Pixel 設備的 /vendor/lib64/egl/libEGL_adreno.so
  4. /system/lib64/egl/system/lib/egl 目錄下文件名符合 lib%s_*.so 模式,即 /system 下文件名前綴匹配的庫文件。

Android 會優先採用 /vendor/ 下設備供應商提供的圖形驅動庫,優先採用庫文件名完全匹配的圖形驅動庫。

第二步,通過 dlopen() 加載庫文件。

    const char* const driver_absolute_path = absolutePath.string();

    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;
    }

第三步,初始化函數表。

    if (mask & EGL) {
        ALOGD("EGL loaded %s", driver_absolute_path);
        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;
        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++;
        }
    }

    if (mask & GLESv1_CM) {
        ALOGD("GLESv1_CM loaded %s", driver_absolute_path);
        init_api(dso, gl_names,
            (__eglMustCastToProperFunctionPointerType*)
                &cnx->hooks[egl_connection_t::GLESv1_INDEX]->gl,
            getProcAddress);
    }

    if (mask & GLESv2) {
      ALOGD("GLESv2 loaded %s", driver_absolute_path);
      init_api(dso, gl_names,
            (__eglMustCastToProperFunctionPointerType*)
                &cnx->hooks[egl_connection_t::GLESv2_INDEX]->gl,
            getProcAddress);
    }

    return dso;
}

初始化函數表主要通過 dlsym() 根據函數名,一個個找到對應的地址,並賦值給函數指針來完成。EGL 接口函數名來自於 egl_names,OpenGL ES 接口函數名來自於 gl_names,它們的定義位於 frameworks/native/opengl/libs/EGL/egl.cpp

#undef GL_ENTRY
#undef EGL_ENTRY
#define GL_ENTRY(_r, _api, ...) #_api,
#define EGL_ENTRY(_r, _api, ...) #_api,

char const * const gl_names[] = {
    #include "../entries.in"
    NULL
};

char const * const egl_names[] = {
    #include "egl_entries.in"
    NULL
};

#undef GL_ENTRY
#undef EGL_ENTRY

這是兩個字符串數組,數組項同樣來自於另外的兩個文件,gl_names 的數組項來自於 egl_entries.ingl_names 的數組項來自於 entries.in,這兩個文件也正是前面看到的 struct egl_t 結構和 struct gl_hooks_tstruct gl_t 結構的結構成員的來源。Android 通過這種方式建立函數表與函數名錶中相同函數的項之間的對應關係,即對於相同的函數,它們對應的項在函數表中的偏移,與在函數名錶中的偏移相同。

對於特定的進程,Android 圖形驅動初始化的過程只執行一次。在 egl_init_drivers_locked()Loader::load_driver() 中加日誌,重新編譯 frameworks/native/opengl/libs,替換設備中的 /system/lib64/libEGL.so/system/lib/libEGL.so,重新啓動設備,可以看到如下的日誌輸出:

11-27 13:26:19.440   475   475 D libEGL  : EGL loaded /vendor/lib64/egl/libEGL_adreno.so
11-27 13:26:19.628   475   475 D libEGL  : GLESv1_CM loaded /vendor/lib64/egl/libGLESv1_CM_adreno.so
11-27 13:26:19.648   475   475 D libEGL  : GLESv2 loaded /vendor/lib64/egl/libGLESv2_adreno.so
11-27 13:26:21.761   477   547 D libEGL  : EGL loaded /vendor/lib64/egl/libEGL_adreno.so
11-27 13:26:21.776   477   547 D libEGL  : GLESv1_CM loaded /vendor/lib64/egl/libGLESv1_CM_adreno.so
11-27 13:26:21.785   477   547 D libEGL  : GLESv2 loaded /vendor/lib64/egl/libGLESv2_adreno.so
09-14 09:21:47.715   614   614 D libEGL  : EGL loaded /vendor/lib/egl/libEGL_adreno.so
09-14 09:21:47.792   614   614 D libEGL  : GLESv1_CM loaded /vendor/lib/egl/libGLESv1_CM_adreno.so
09-14 09:21:47.819   614   614 D libEGL  : GLESv2 loaded /vendor/lib/egl/libGLESv2_adreno.so
09-14 09:21:48.492   612   612 D libEGL  : EGL loaded /vendor/lib64/egl/libEGL_adreno.so
09-14 09:21:48.496   613   613 D libEGL  : EGL loaded /vendor/lib/egl/libEGL_adreno.so
09-14 09:21:48.503   612   612 D libEGL  : GLESv1_CM loaded /vendor/lib64/egl/libGLESv1_CM_adreno.so
09-14 09:21:48.510   613   613 D libEGL  : GLESv1_CM loaded /vendor/lib/egl/libGLESv1_CM_adreno.so
09-14 09:21:48.513   612   612 D libEGL  : GLESv2 loaded /vendor/lib64/egl/libGLESv2_adreno.so
09-14 09:21:48.523   613   613 D libEGL  : GLESv2 loaded /vendor/lib/egl/libGLESv2_adreno.so

根據這些日誌的進程號查找響應的進程:

system    475   1     151868 22568 SyS_epoll_ 71808e632c S /system/bin/surfaceflinger
root      612   1     2144132 81252 poll_sched 7627a2044c S zygote64
root      613   1     1578176 69960 poll_sched 00f5045700 S zygote
cameraserver 614   1     123228 45404 binder_thr 00efbf4658 S /system/bin/cameraserver

可見 Android 中需要用到 OpenGL ES 圖形系統的進程主要有 surfaceflingercameraserver 和所有的 Android Java 應用程序。對於普通的 Java 應用程序,這些驅動庫會先由 zygote 加載完成,後續 systemserver 指示 zygote 爲普通的 Java 應用程序 fork 出新的進程,新的進程繼承 zygote 加載的那些庫。

圖形驅動 Wrapper

Android 中圖形驅動 Wrapper 是圖形接口的一箇中轉。Android 中使用 EGL 和 OpenGL ES 接口的應用程序,可能是通過 framework 提供的 Java 接口,也可能通過 NDK 提供的本地層接口,直接調用圖形驅動 Wrapper,這些 Wrapper 再將調用轉給設備特有的 EGL 和 OpenGL ES 實現庫中的函數。

通過 Android.mk 文件可以看到這樣的依賴關係。frameworks/native/opengl/libs/Android.mk 文件中存在如下的內容:

include $(CLEAR_VARS)

LOCAL_SRC_FILES:= 	       \
	EGL/egl_tls.cpp        \
	EGL/egl_cache.cpp      \
	EGL/egl_display.cpp    \
	EGL/egl_object.cpp     \
	EGL/egl.cpp 	       \
	EGL/eglApi.cpp 	       \
	EGL/getProcAddress.cpp.arm \
	EGL/Loader.cpp 	       \
#

LOCAL_SHARED_LIBRARIES += libbinder libcutils libutils liblog libui
LOCAL_MODULE:= libEGL
LOCAL_LDFLAGS += -Wl,--exclude-libs=ALL
LOCAL_SHARED_LIBRARIES += libdl
. . . . . .
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= 		\
	GLES_CM/gl.cpp.arm 	\
#

LOCAL_CLANG := false
LOCAL_SHARED_LIBRARIES += libcutils liblog libEGL
LOCAL_MODULE:= libGLESv1_CM

LOCAL_SHARED_LIBRARIES += libdl
. . . . . .
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \
	GLES2/gl2.cpp   \
#

LOCAL_CLANG := false
LOCAL_ARM_MODE := arm
LOCAL_SHARED_LIBRARIES += libcutils libutils liblog libEGL
LOCAL_MODULE:= libGLESv2

LOCAL_SHARED_LIBRARIES += libdl
. . . . . .
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \
	GLES2/gl2.cpp   \
#

LOCAL_CLANG := false
LOCAL_ARM_MODE := arm
LOCAL_SHARED_LIBRARIES += libcutils libutils liblog libEGL
LOCAL_MODULE:= libGLESv3
LOCAL_SHARED_LIBRARIES += libdl
. . . . . .

frameworks/native/opengl/libs/ 下的源碼被編譯爲了四個動態鏈接庫,分別爲 libEGL.solibGLESv1_CMlibGLESv2libGLESv3,其中 libGLESv2libGLESv3 完全相同。在frameworks/base/core/jni/Android.mk 中可以清晰地看到 framework JNI 對這些庫的依賴:

LOCAL_SHARED_LIBRARIES := \
. . . . . .
    libsqlite \
    libEGL \
    libGLESv1_CM \
    libGLESv2 \
    libvulkan \
    libETC1 \
. . . . . .

EGL 的 Wrapper,如我們前面看到的,位於 frameworks/native/opengl/libs/EGL,其 EGL API 實現位於 frameworks/native/opengl/libs/EGL/eglApi.cpp

GLESv1_CM 的 Wrapper 位於 frameworks/native/opengl/libs/GLES_CM,其接口實現在 gl.cpp 文件中:

#elif defined(__arm__)

    #define GET_TLS(reg) "mrc p15, 0, " #reg ", c13, c0, 3 \n"

    #define API_ENTRY(_api) __attribute__((noinline)) _api

    #define CALL_GL_API(_api, ...)                              \
         asm volatile(                                          \
            GET_TLS(r12)                                        \
            "ldr   r12, [r12, %[tls]] \n"                       \
            "cmp   r12, #0            \n"                       \
            "ldrne pc,  [r12, %[api]] \n"                       \
            :                                                   \
            : [tls] "J"(TLS_SLOT_OPENGL_API*4),                 \
              [api] "J"(__builtin_offsetof(gl_hooks_t, gl._api))    \
            : "r12"                                             \
            );

#elif defined(__aarch64__)

    #define API_ENTRY(_api) __attribute__((noinline)) _api

    #define CALL_GL_API(_api, ...)                                  \
        asm volatile(                                               \
            "mrs x16, tpidr_el0\n"                                  \
            "ldr x16, [x16, %[tls]]\n"                              \
            "cbz x16, 1f\n"                                         \
            "ldr x16, [x16, %[api]]\n"                              \
            "br  x16\n"                                             \
            "1:\n"                                                  \
            :                                                       \
            : [tls] "i" (TLS_SLOT_OPENGL_API * sizeof(void*)),      \
              [api] "i" (__builtin_offsetof(gl_hooks_t, gl._api))   \
            : "x16"                                                 \
        );

#elif defined(__i386__)
. . . . . .
#define CALL_GL_API_RETURN(_api, ...) \
    CALL_GL_API(_api, __VA_ARGS__) \
    return 0;


extern "C" {
#pragma GCC diagnostic ignored "-Wunused-parameter"
#include "gl_api.in"
#include "glext_api.in"
#pragma GCC diagnostic warning "-Wunused-parameter"
}

#undef API_ENTRY
#undef CALL_GL_API
#undef CALL_GL_API_RETURN

這個文件包含了另外兩個文件,gl_api.inglext_api.in。其中 gl_api.in 文件位於 frameworks/native/opengl/libs/GLES_CM/gl_api.in,內容如下:

void API_ENTRY(glAlphaFunc)(GLenum func, GLfloat ref) {
    CALL_GL_API(glAlphaFunc, func, ref);
}
void API_ENTRY(glClearColor)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
    CALL_GL_API(glClearColor, red, green, blue, alpha);
}
void API_ENTRY(glClearDepthf)(GLfloat d) {
    CALL_GL_API(glClearDepthf, d);
}
void API_ENTRY(glClipPlanef)(GLenum p, const GLfloat *eqn) {
    CALL_GL_API(glClipPlanef, p, eqn);
}
void API_ENTRY(glColor4f)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
    CALL_GL_API(glColor4f, red, green, blue, alpha);
}
. . . . . .

glext_api.in 文件位於 frameworks/native/opengl/libs/GLES_CM/glext_api.in,內容如下:

void API_ENTRY(glEGLImageTargetTexture2DOES)(GLenum target, GLeglImageOES image) {
    CALL_GL_API(glEGLImageTargetTexture2DOES, target, image);
}
void API_ENTRY(glEGLImageTargetRenderbufferStorageOES)(GLenum target, GLeglImageOES image) {
    CALL_GL_API(glEGLImageTargetRenderbufferStorageOES, target, image);
}
void API_ENTRY(glBlendEquationSeparateOES)(GLenum modeRGB, GLenum modeAlpha) {
    CALL_GL_API(glBlendEquationSeparateOES, modeRGB, modeAlpha);
}
void API_ENTRY(glBlendFuncSeparateOES)(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) {
    CALL_GL_API(glBlendFuncSeparateOES, srcRGB, dstRGB, srcAlpha, dstAlpha);
}

配和前面的 API_ENTRYCALL_GL_API 宏定義,可見 gl.cpp 中主要是定義了 OpenGL ES 函數接口,這些函數的實現爲,藉助於前面初始化的函數表,通過彙編代碼,跳轉到相應的 OpenGL ES 函數實現。

GLESv2 Wrapper 位於 frameworks/native/opengl/libs/GLES2,其 OpenGL ES 接口實現在 gl2.cpp 文件中:

#elif defined(__arm__)

    #define GET_TLS(reg) "mrc p15, 0, " #reg ", c13, c0, 3 \n"

    #define API_ENTRY(_api) __attribute__((noinline)) _api

    #define CALL_GL_API(_api, ...)                              \
         asm volatile(                                          \
            GET_TLS(r12)                                        \
            "ldr   r12, [r12, %[tls]] \n"                       \
            "cmp   r12, #0            \n"                       \
            "ldrne pc,  [r12, %[api]] \n"                       \
            :                                                   \
            : [tls] "J"(TLS_SLOT_OPENGL_API*4),                 \
              [api] "J"(__builtin_offsetof(gl_hooks_t, gl._api))    \
            : "r12"                                             \
            );

#elif defined(__aarch64__)

    #define API_ENTRY(_api) __attribute__((noinline)) _api

    #define CALL_GL_API(_api, ...)                                  \
        asm volatile(                                               \
            "mrs x16, tpidr_el0\n"                                  \
            "ldr x16, [x16, %[tls]]\n"                              \
            "cbz x16, 1f\n"                                         \
            "ldr x16, [x16, %[api]]\n"                              \
            "br  x16\n"                                             \
            "1:\n"                                                  \
            :                                                       \
            : [tls] "i" (TLS_SLOT_OPENGL_API * sizeof(void*)),      \
              [api] "i" (__builtin_offsetof(gl_hooks_t, gl._api))   \
            : "x16"                                                 \
        );

#elif defined(__i386__)
. . . . . .
#define CALL_GL_API_RETURN(_api, ...) \
    CALL_GL_API(_api, __VA_ARGS__) \
    return 0;



extern "C" {
#pragma GCC diagnostic ignored "-Wunused-parameter"
#include "gl2_api.in"
#include "gl2ext_api.in"
#pragma GCC diagnostic warning "-Wunused-parameter"
}

#undef API_ENTRY
#undef CALL_GL_API
#undef CALL_GL_API_RETURN

這裏的實現與前面看到的 GLESv1_CM 的 Wrapper 的接口實現類似。

Android OpenGL ES 圖形庫結構

Android 的 OpenGL ES 圖形系統涉及多個庫,根據設備類型的不同,這些庫有着不同的結構。

對於模擬器,沒有開啓 OpenGL ES 的 GPU 硬件模擬的情況,Android OpenGL ES 圖形庫結構如下:

當爲模擬器開啓了 OpenGL ES 的 GPU 硬件模擬,實際的 EGL 和 OpenGL ES 實現庫會採用由 android-7.1.1_r22/device/generic/goldfish-opengl 下的源碼編譯出來的幾個庫文件,即 libGLESv2_emulation.solibGLESv1_CM_emulation.solibEGL_emulation.so。此時,OpenGL ES 圖形庫結構如下:

2017-09-16 11-19-05屏幕截圖.png

對於真實的物理 Android 設備,OpenGL ES 圖形庫結構如下:

Done.

Android OpenGL 圖形系統分析系列文章

在 Android 中使用 OpenGL Android 圖形驅動初始化 EGL Context 創建

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章