OpenGL之EGL

OpenGL ES的javax.microedition.khronos.openges包定义了平台无关的GL绘制指令,EGL(javax.microedition.khronos.egl)则定义了控制dispays,contexts以及surfaces的统一的平台接口.

image EGL 是 OpenGL ES 渲染 API 和本地窗口系统(native platform window system)之间的一个中间接口层,它主要由系统制造商实现。EGL提供如下机制:

  • 与设备的原生窗口系统通信
  • 查询绘图表面的可用类型和配置
  • 创建绘图表面
  • 在OpenGL ES 和其他图形渲染API之间同步渲染
  • 管理纹理贴图等渲染资源

为了让OpenGL ES能够绘制在当前设备上,我们需要EGL作为OpenGL ES与设备的桥梁。

image

  • Display(EGLDisplay)是对实际显示设备的抽象
  • Surface(EGLSurface)是对用来存储图像的内存区域FrameBuffer的抽象,包括Color Buffer, Stencil Buffer, Depth Buffer.
  • Context(EGLContext)存储OpenGLES绘图的一些状态信息

使用EGL绘制的一般步骤:

  1. 获取EGLDisplay对象
  2. 初始化与EGLDisplay之间的连接
  3. 获取EGLConfig实例
  4. 创建EGLContext实例
  5. 创建EGLSurface实例
  6. 连接EGLContext和EGLSurface
  7. 使用GL指令绘制图形
  8. 断开并释放与EGLSurface关联的EGLContext对象
  9. 删除EGLSurface对象
  10. 删除EGLContext对象
  11. 终止与EGLDisplay之间的连接

源码: 本地代码:

  • frameworks/native/opengl/libs/EGL:Android EGL框架,负责加载OpenGL函数库和EGL本地实现。
  • frameworks/native/opengl/libagl:Android提供的OpenGL软件库

JNI代码:

  • frameworks/base/core/jni/com_google_android_gles_jni_EGLImpl.cpp:EGL本地代码的JNI调用接口
  • frameworks/base/core/jni/com_google_android_gles_jni_GLImpl.cpp
  • frameworks/base/core/jni/android_opengl_GLESXXX.cpp:OpenGL功能函数的JNI调用接口

JAVA代码:

  • frameworks/base/opengl/java/javax/microedition/khronos/egl
  • frameworks/base/opengl/java/javax/microedition/khronos/opengles
  • frameworks/base/opengl/java/com/google/android/gles_jni/
  • frameworks/base/opengl/java/android/opengl :EGL和OpenGL的Java层接口,提供给应用开发者,通过JNI方式调用底层函数。
OpenGL ES/EGL Wrapper 库

前面我们已经介绍过 OpenGL ES/EGL Wrapper 库是一个将 OpenGL ES API 和 OpenGL ES API 具体实现绑定在一起的库,它对应的源码路径是:/frameworks/native/opengl/libs/,其中:

libGLESv1_CM.so:OpenGL ES 1.x API 的 Wrapper 库 libGLESv2.so:OpenGL ES 2.0 的 Wrapper 库 libGLESv3.so:OpenGL ES 3.0 的 Wrapper 库 其中因为 OpenGL ES 3.0 API 是兼容 OpenGL ES 2.0 API 的,所以 libGLESv2.so 库本质上和 libGLESv3.so 库是一样的。

OpenGL ES/EGL 实现库

如果Android系统平台支持 OpenGL ES 硬件加速渲染,那么 OpenGL ES/EGL 实现库由系统厂商以.so的共享链接库的形式提供,例如,Nexus 9 平板中的厂商提供的 OpenGL ES/EGL 实现库为:

1
2
flounder:/vendor/lib64/egl # ls
libEGL_tegra.so libGLESv1_CM_tegra.so libGLESv2_tegra.so

如果Android系统平台不支持 OpenGL ES 硬件加速渲染,那么它就会默认启用软件模拟渲染,这时 OpenGL ES/EGL 实现库就是由 AOSP 提供,链接库的存在的路径为: /system/lib64/egl/libGLES_android.so。而 libGLES_android.so 库在 Android 7.1 系统对应的实现源码路径为:/frameworks/native/opengl/libagl/ 。

Android 7.1 中加载 OpenGL ES 库的过程

Android 中图形渲染所采用的方式(硬件 or 软件)是在系统启动之后动态确定的,而确定渲染方式的这个源码文件就是 /frameworks/native/opengl/libs/EGL/Loader.cpp 。

####### Android 7.1 OpenGL ES 库和 EGL 库加载说明 How Android finds OpenGL libraries, and the death of egl.cfg 这篇文章中提到了非常关键的一点,就是从 Android Kitkat 4.4 之后,Android 中加载 OpenGL ES/EGL 库的方法发生了变化了(但是整个加载过程都是由 /frameworks/native/opengl/libs/EGL/Loader.cpp 程序所决定的,也就是说 Loader.cpp 文件发生了变化)。

在 Android 4.4 之前,加载 OpenGL ES 库是由 /system/lib/egl/egl.cfg 文件所决定的,通过读取这个配置文件来确定是加载 OpenGL ES 软件模拟实现的库,还是OpenGL ES 硬件加速实现的库。

但是,在Android 4.4 之后,Android 不再通过读取 egl.cfg 配置文件的方式来加载 OpenGL ES 库,新的加载 OpenGL ES 库的规则,如下所示:

从 /system/lib/egl 或者 /vendor/lib/egl/ 目录下加载 libGLES.so 库文件或者 libEGL_vendor.so,libGLESv1_CM_vendor.so,libGLESv2_vendor.so 库文件。 为了向下兼容旧的库的命名方式,同样也会加载 /system/lib/egl 或者 /vendor/lib/egl/ 目录下的 libGLES_.so 或者 libEGL_.so,libGLESv1CM.so,libGLESv2_.so 库文件。 3.2 硬件加速渲染 or 软件模拟渲染? 前面我们提到 OpenGL ES 库的实现方式有两种,一种是硬件加速实现,一种是软件模拟实现,那么系统是怎么确定加载那一种 OpenGL ES 库的呢?

Android 7.1 源码中负责加载 OpenGL ES/EGL 库部分的代码位于:/frameworks/native/opengl/libs/EGL/Loader.cpp 文件中,这个文件中代码的主要入口函数是 Loader::open() 函数,而决定加载硬件加速渲染库还是软件模拟渲染库主要涉及到下面两个函数:

setEmulatorGlesValue() checkGlesEmulationStatus() 下面就来简要的分析一下 Android 系统是如何选择加载硬件加速渲染库还是软件模拟渲染库:

  1. 首先,Loader::open() 入口函数会调用 setEmulatorGlesValue() 从 property 属性系统中获取一些属性值来判断当前 Android 系统是否在 Emulator 环境中运行,并根据读取出来的信息来重新设置新的属性键值对,setEmulatorGlesValue() 函数的代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    static void setEmulatorGlesValue(void) {
    char prop[PROPERTY_VALUE_MAX];
    property_get("ro.kernel.qemu", prop, "0"); //读取 ro.kernel.qemu 属性值,判断Android系统是否运行在 qemu 中
    if (atoi(prop) != 1) return;

    property_get("ro.kernel.qemu.gles", prop, "0"); //读取 ro.kernel.qemu.gles 属性值,判断 qemu 中 OpenGL ES 库的实现方式
    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);
    //如果存在 vendor_lib_path 这个路径,那么就说明厂商提供了 OpenGL ES库自己的软件模拟渲染库,而不是 Android 系统自己编译得到的软件模拟渲染库
    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"); //最后,默认采取的是方案就是调用传统的Android系统自己编译得到软件模拟渲染库
    }
    }
  2. 在 load_system_driver() 函数中,内部类 MatchFile 类中会调用 checkGlesEmulationStatus() 函数来检查 Android 系统是否运行在模拟器中,以及在模拟器中是否启用了主机硬件加速的功能,然后根据 checkGlesEmulationStatus() 函数的返回状态值来确定要加载共享链接库的文件绝对路径。load_system_driver() 和 checkGlesEmulationStatus() 函数代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    static void* load_system_driver(const char* kind) {
    ATRACE_CALL();
    class MatchFile {
    public:
    //这个函数作用是返回需要加载打开的 OpenGL ES 和 EGL API 实现库文件的绝对路径
    static String8 find(const char* kind) {
    String8 result;
    int emulationStatus = checkGlesEmulationStatus(); //检查 Android 系统是否运行在模拟器中,以及在模拟器中是否启用了主机硬件加速的功能
    switch (emulationStatus) {
    case 0: //Android 运行在模拟器中,使用系统软件模拟实现的 OpenGL ES API 库 libGLES_android.so
    #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: // Android 运行在模拟器中,通过主机系统中实现 OpenGL ES 加速渲染,通过 libGLES_emulation.so 库将 OpenGL ES API 指令重定向到 host 中执行
    // 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
    };

    ......
    }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;

/* Check if hardware acceleration disabled explicitly */
property_get("debug.egl.hw", prop, "1"); //读取 debu.egl.hw 属性值,判断3D硬件加速功能是否被关闭了
if (!atoi(prop)) {
ALOGD("3D hardware acceleration is disabled");
return 0;
}

/* First, check for qemu=1 */
property_get("ro.kernel.qemu", prop, "0"); //读取ro.kernel.qemu,判断是否运行在 qemu 中
if (atoi(prop) != 1)
return -1;

/* We are in the emulator, get GPU status value */
property_get("qemu.gles", prop, "0"); // 如果 Android 系统运行在 qemu 中,就返回 qemu.gles 的值,根据这个值就可以确定加载的是那种 OpenGL ES 库了
return atoi(prop);
}
  1. 总结一下上面代码的功能就是,首先判断 Android 是否在 qemu 虚拟机中运行,如果不是,那么就直接去加载厂商存放库的路径中去加载 OpenGL ES 实现库(不管是硬件加速实现的,还是软件模拟实现的);如果是在 qemu 中运行,那么就要根据返回的 emulationStatus 值 来确定是加软件模拟实现的 OpenGL ES API 库 libGLES_android.so,还是加载 libGLES_emulation.so库将 OpenGL ES 指令重定向到 Host 系统中去执行。
OpenGL ES/EGL 库加载和解析过程

正如Android 系统图形栈: OpenGL ES 和 EGL 介绍这篇文章中分析的那样,在进行 OpenGL 编程时,最先开始需要获取 Display,这将调用 eglgGetDisplay() 函数被调用。在 eglGetDisplay() 里则会调用 egl_init_drivers() 初始化驱动:装载各个库进行解析,将 OpenGL ES/EGL API 函数接口和具体的实现绑定在一起,并将结果保存在 egl_connection_t 类型的全局变量 gEGLImpl 的结构体的成员变量中。

下面以 SurfaceFlinger 进程为例进行分析,整个 OpenGL ES/EGL 库的加载和解析流程如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
st=>start: Start
op1=>operation: SurfaceFlinger::init()
sub=>subroutine: Your Subroutine
op2=>operation: eglGetDisplay(EGL_DEFAULT_DISPLAY)
op3=>operation: egl_init_drivers()
op4=>operation: egl_init_drivers_locked();
op5=>operation: loader.open()
op6=>operation: load_driver()
op7=>operation: load_system_driver()
op8=>operation: Loader::init_api()
e=>end

st->op1->op2->op3->op4->op5->op6->op7->op8->e

gEGLImpl 全局变量 struct egl_connection_t 类型的 gEGLImpl 全局变量是一个非常重要变量,它里面的成员指向了打开的 OpenGL ES/EGL Wrapper 库和 OpenGL ES/EGL 实现库: /frameworks/native/opengl/libs/EGL/egl.cpp

1
2
3
egl_connection_t gEGLImpl;
gl_hooks_t gHooks[2];
gl_hooks_t gHooksNoContext;

其中 egl_connection_t 的定义: /frameworks/native/opengl/libs/EGL/egldefs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 ES API 对象
EGLint major; // 主版本好
EGLint minor;
egl_t egl; //dui x

void* libEgl;
void* libGles1;
void* libGles2;
};

下面就对其中的主要成员进行一个说明:

  • hooks:这是一个 gl_hook_t* 类型的指针数组,它最终将 OpenGL ES API 和实现库钩在一起。
  • egl:这是一个 egl_t 类型的成员变量,它最终将 EGL API 和 EGL 实现库了钩在一起。 那么 gl_hook_t 和 egl_t 是什么呢?

gl_hook_t 和 egl_t 的定义如下所示: /frameworks/native/opengl/libs/hooks.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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 是一个 typedef 的函数指针类型,它的返回值是void,参数也是void
__eglMustCastToProperFunctionPointerType extensions[MAX_NUMBER_OF_GL_EXTENSIONS];
} ext;
};

从上面的定义中我们发现,egl_t 和 gl_hooks_t 这两个结构体中主要就是一个 include 语句,那么它们包含的是什么呢?

#include “EGL/egl_entries.in 包含的文件路径如下所示: /frameworks/native/opengl/libs/EGL/egl_entries.in

1
2
3
4
5
6
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 宏定义的,根据 EGL_ENTRY 宏定义: /frameworks/native/opengl/libs/EGL/hooks.h

1
2
3
4
//下面的两个宏定义分别是 OpenGL 和 EGL API 函数的函数指针变量
//这些函数变量最后会和具体的 OpenGL 和 EGL API 的实现绑定在一起
#define GL_ENTRY(_r,_api,...) _r (*_api)(__VA_ARGS__);
#define EGL_ENTRY(_r,_api,...) _r(*_api)(__VA_ARGS__);

我们可以将下面的这个宏定义展开成如下的形式:

1
2
3
EGL_ENTRY(EGLDisplay, eglGetDisplay, NativeDisplayType)
//展开后的形式如下所示,它实际上就是 EGL API 函数声明
EGLDisplay eglGetDisplay(NativeDisplayType)

#include “entries.in” 包含的文件路径为: /frameworks/native/opengl/libs/entries.in

1
2
3
...
GL_ENTRY(void, glReadPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * pixels)
...

和上面的 EGL_ENTRY 一样,GL_ENTRY 都是通过宏定义的形式来将 OpenGL ES 的 API 函数接口进行声明,例如,上面的宏定义声明可以展开成下面的形式:

1
2
3
GL_ENTRY(void, glReadPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * pixels)
// 展开后的形式如下所示,它实际上就是 OpenGL ES API 函数声明
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * pixels)

最后,通过 entries.in 和 egl_entries.in 这两个文件,我们就可以得到 OpenGL ES 和 EGL 中的所有 API 函数接口的说明。

SurfaceFlinger 初始化成员变量 mEGLDisplay 在 SurfaceFlinger 类中有一个 EGLDisplay 类型的成员变量 mEGLDisplay,它是 EGL 中用来构建 OpenGL ES 渲染环境所需的参数。

SurfaceFlinger 中调用 eglGetDisplay() 初始化 mEGLDisplay 的代码如下: /frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

1
2
3
4
5
6
7
8
9
10
11
12
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
// 调用 eglGetDisplay 函数获取默认的显示设备
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(mEGLDisplay, NULL, NULL);
...

紧接着在 eglGetDisplay() 中调用 egl_init_drivers() /frameworks/native/opengl/libs/EGL/eglApi.cpp

1
2
3
4
5
6
7
8
9
10
11
EGLDisplay eglGetDisplay(EGLNativeDisplayType display)
{
...
// 调用egl_init_drivers() 加载 OpenGL ES 库和 EGL 库
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() 函数中使用了 pthread_mutex_lock 上锁保护,继续调用 egl_init_drivers_locked() 函数 /frameworks/native/opengl/libs/EGL/egl.cpp

1
2
3
4
5
6
7
8
EGLBoolean egl_init_drivers() {
EGLBoolean res;
pthread_mutex_lock(&sInitDriverMutex);
// 使用了 pthread_mutex_lock 上锁保护,继续调用 egl_init_drivers_locked
res = egl_init_drivers_locked();
pthread_mutex_unlock(&sInitDriverMutex);
return res;
}

下面就是对 egl_init_drivers_locked() 函数进行一个分析:

egl_init_drivers_locked() 函数代码如下所示: /frameworks/native/opengl/libs/EGL/egl.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//在该文件起始位置定义的全局变量
egl_connection_t gEGLImpl; // 描述EGL实现内容的结构体对象
gl_hooks_t gHooks[2]; // gl_hooks_t 是包含 OpenGL ES API 函数声明对应的函数指针结构体
gl_hooks_t gHooksNoContext;
pthread_key_t gGLWrapperKey = -1;

static EGLBoolean egl_init_drivers_locked() {
if (sEarlyInitState) {
// initialized by static ctor. should be set here.
return EGL_FALSE;
}

// 得到 Loader 对象单例
// get our driver loader
Loader& loader(Loader::getInstance());

// gEGLImple 是一个全局变量,数据类型为 egl_connection_t 结构体类型
// dynamically load our EGL implementation
egl_connection_t* cnx = &gEGLImpl;

// cnx->dso 本质上是一个 (void *)类型的指针,它指向的对象是 EGL 共享库打开之后的句柄
if (cnx->dso == 0) {
// >= 将cnx中的 hooks 数组中指向OpenGL ES API 函数指针结构体指的数组成员,用 gHooks 中的成员的地址去初始化
//也就是说 gEGLImpl 中 hook 数组指向 gHooks 数组,最终指向同一个 OpenGL ES API 函数指针的实现
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];

// >= 最后通过loader对象的open函数开始加载 OpenGL ES 和 EGL wrapper 库
cnx->dso = loader.open(cnx);
}

return cnx->dso ? EGL_TRUE : EGL_FALSE;
}

在这个函数中,有一个非常关键的 egl_connection_t 指针指向一个全局变量 gEGLImpl,当第一次初始化加载 OpenGL ES 实现库和 EGL 实现库时,还需要将 gEGLImpl 中的 hooks 数组中的两个指针指向一个全局的 gl_hooks_t 数组 gHooks(这就是两个指针钩子,最终初始化完成后将分别勾住 OpenGL ES 1.0 和 OpenGL ES 2.0 的实现库),接着调用 Loader 类的实例的 open() 函数完成从 OpenGL ES 实现库中完成符号解析工作。

通过 Loader 类加载和解析 OpenGL ES 库和 EGL 库 Loader::open() 函数的代码如下所示: /frameworks/native/opengl/libs/EGL/Loader.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// >= Loader 类对象构造完成后,就在 /EGL/egl.cpp 文件中的 egl_init_drivers_locked() 中被调用
void* Loader::open(egl_connection_t* cnx)
{
ATRACE_CALL();

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

open() 函数主要负责 OpenGL ES 库加载前的准备工作,具体的加载细节,则是通过调用 load_driver() 去完成的。

Loader::load_driver() 函数代码如下所示: /frameworks/native/opengl/libs/EGL/Loader.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
void *Loader::load_driver(const char* kind,
egl_connection_t* cnx, uint32_t mask)
{
ATRACE_CALL();

void* dso = nullptr;
if (mGetDriverNamespace) {
android_namespace_t* ns = mGetDriverNamespace();
if (ns) {
dso = load_updated_driver(kind, ns); //加载 OpenGL ES 实现库,放回打开的共享链接库的句柄
}
}
if (!dso) {
dso = load_system_driver(kind);
if (!dso)
return NULL;
}

// 解析 EGL 库,并将wrapper 库 libEGL.so 中的函数 API 指针和具体的实现绑定在一起
if (mask & EGL) {
getProcAddress = (getProcAddressType)dlsym(dso, "eglGetProcAddress");

ALOGE_IF(!getProcAddress,
"can't find eglGetProcAddress() in EGL driver library");

egl_t* egl = &cnx->egl; //将 egl 指针指向描述当前系统支持 OpenGL ES和 EGL 全局变量的 gEGLImpl
__eglMustCastToProperFunctionPointerType* curr =
(__eglMustCastToProperFunctionPointerType*)egl;
char const * const * api = egl_names; //egl_names 是定义在 egl.cpp 文件中的一个数组,数组中的元素是 EGL API 函数指针
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; //这一步就是最关键的将共享链接库中的 EGL API 的实现和上层调用的 API 函数指针绑定在一起
api++; //指向下一个需要绑定的 api 函数
}
}

// 解析 OpenGL ES 库中的 OpenGL ES 1.x API 符号
if (mask & GLESv1_CM) {
// 调用 init_api 实现 OpenGL API 和对应实现函数的绑定
init_api(dso, gl_names, // gl_names 是定义在 egl.cpp 文件中的一个数组,数组中的元素是 OpenGL ES API 函数指针
(__eglMustCastToProperFunctionPointerType*)
&cnx->hooks[egl_connection_t::GLESv1_INDEX]->gl, //gl成员变量是一个结构体变量,结构体中的是 OpenGL ES API 函数指针
getProcAddress);
}

// 解析 OpenGL ES 库中的 OpenGL ES 2.0 API 符号
if (mask & GLESv2) {
init_api(dso, gl_names,
(__eglMustCastToProperFunctionPointerType*)
&cnx->hooks[egl_connection_t::GLESv2_INDEX]->gl,
getProcAddress);
}

return dso;
}

Loader::load_driver() 它主要实现了两个功能:

通过 load_system_driver() 函数查找 OpenGL ES/EGL 实现库,并在指定的存放路径中找到共享链接库文件并打开它。 调用 init_api()解析打开的 OpenGL ES/EGL 共享链接库,将 OpenGL ES/EGL API 函数指针和共享链接库中实现的对应的函数符号绑定在一起,这样调用 OpenGL ES/EGL API 就会调用到具体实现的OpenGL ES/EGL 共享链接库中对应函数。 具体 load_system_driver() 函数和 init_api() 函数的实现就不一一展开了,大家可以到 /frameworks/native/opengl/libs/EGL/Loader.cpp 文件中查看,应该写得非常直接清楚了。

整个加载和解析的目的就是将 OpenGL ES/EGL API 和具体实现函数绑定在一起,这样当程序调用 OpenGL ES/EGL API 时,就会调用到实际的实现函数了。然后,在硬件平台上的 Android 系统加载的是硬件厂商提供的 OpenGL ES/EGL 实现库,而在 qemu 模拟器中运行的 Android 系统中加载的则是软件模拟实现的 OpenGL ES/EGL 库或者是将 OpenGL ES 重定向到主机系统中进行硬件加速的库。

Android 系统图形栈(二):OpenGL ES 库和 EGL 库加载过程 How Android finds OpenGL libraries, and the death of egl.cfg

eglSwapBuffers接口实现说明

一般性嵌入式平台 iamge

利用双缓冲进行Swap的时候,Display和Surface进行实际意义上的地址交换,来实现eglSwapBuffers的标准, 如上图的右侧所示。上图的左侧表示,单缓冲Framebuffer的形式,Surface永远都在后端, 显示的永远是Display,在GPU出现后已不使用。

Android平台: 为了实现eglSwapBuffers, eglSurface其实代表了一个从NativeWindow 申请到的一个Buffer(Dequeue操作)。当调用eglSwapBuffers时,对于一般应用窗口而言,NativeWindow将该Surface的Buffer 提交回去给SurfaceFlinger(Queue操作),然后又重新从NativeWindow中重新Dequeue出来一个新的Buffer给eglSurface。而eglDisplay并不代表实际的意义。我们只是从接口上感觉是,surface和display进行了交换。

坚持原创技术分享,您的支持将鼓励我继续创作!