从应用程序的角度看 OpenGL 图形系统的接口,主要包括两大部分,一部分是 EGL,它为 OpenGL 渲染准备环境;另一部分是 OpenGL,它执行图形渲染。通过这些接口构造渲染环境,并执行渲染的过程,可以参考 在 Android 中使用 OpenGL。
对于 Android OpenGL 图形系统的实现的分析,从 EGL context 的创建开始。先来看一下获取 Display 的过程。首先来看 EGLContext.getEGL()
:
返回的 EGL
实例为 EGLImpl
,即我们在应用中使用的 EGL
实际为 com.google.android.gles_jni.EGLImpl
。
获取 Display
然后来看 eglGetDisplay()
。EGLImpl
的定义位于 frameworks/base/opengl/java/com/google/android/gles_jni/EGLImpl.java
,其中 eglGetDisplay()
定义如下:
这里主要通过调用本地层方法 _eglGetDisplay()
得到 Display,然后创建 EGLDisplayImpl
。_eglGetDisplay()
返回底层的 Display 对象句柄。
EGLDisplayImpl
的实现很简单:
EGLDispaly
实际是一个标记接口:
可见 EGLDisplayImpl
仅仅包装了本地层返回的 Display 对象句柄。
本地层 _eglGetDisplay()
的实现位于 frameworks/base/core/jni/com_google_android_gles_jni_EGLImpl.cpp
:
这里通过调用 EGL 库的 eglGetDisplay()
获得 Display。eglGetDisplay()
的定义位于 frameworks/native/opengl/libs/EGL/eglApi.cpp
:
在这个函数中,首先根据需要执行 egl_init_drivers()
初始化驱动库。然后通过 egl_display_t::getFromNativeDisplay(display)
获得 Dispaly。
egl_display_t::getFromNativeDisplay(display)
的定义(位于 frameworks/native/opengl/libs/EGL/egl_display.cpp
)如下:
在这个函数中,最为关键的就是,在 disp.dpy
为 EGL_NO_DISPLAY
时,通过 cnx->egl.eglGetDisplay()
初始化它了。
EGLDisplay
的定义如下:
它仅是 void 指针的 typedef。
总结一下获取 Display 的整个过程
- 通过
frameworks/native/opengl/libs/EGL
初始化图形驱动; - 通过厂商提供的设备特有的 EGL 库接口初始化 Display。
Android 图形驱动初始化
接下来更详细地看一下图形驱动初始化。这通过 egl_init_drivers()
完成,该函数定义 (位于frameworks/native/opengl/libs/EGL/egl.cpp
) 如下:
图形驱动初始化通过 Loader::open(egl_connection_t* cnx)
完成,初始化的结果将存储于全局结构 egl_connection_t gEGLImpl
和 gl_hooks_t gHooks[2]
中。
来看一下 gl_hooks_t
的定义(位于 frameworks/native/opengl/libs/hooks.h
):
其中 __eglMustCastToProperFunctionPointerType
定义 (位于frameworks/native/opengl/include/EGL/egl.h
) 如下:
__eglMustCastToProperFunctionPointerType
是函数指针类型。struct gl_hooks_t
的 struct gl_ext_t ext
即为函数指针表,它们用来描述 EGL 扩展接口。
struct gl_hooks_t
内的 struct gl_t
结构体,其结构体成员在另外一个文件,即 entries.in
中定义,该文件位于 frameworks/native/opengl/libs/entries.in
:
配合 frameworks/native/opengl/libs/hooks.h
中 GL_ENTRY
宏的定义:
可以看到 struct gl_hooks_t
的 struct gl_t gl
的所有成员都是函数指针,即它是一个函数表,一个 OpenGL 接口函数的函数表。
上面看到的 struct egl_t
与 struct gl_hooks_t
的 struct gl_t gl
定义类似,只是它的结构体成员来自于另外一个文件 frameworks/native/opengl/libs/EGL/egl_entries.in
:
EGL_ENTRY
宏的定义与 GL_ENTRY
宏的完全相同。struct egl_t
同样为一个函数表,只是它是 EGL 接口的函数表。
再来看 egl_connection_t
的定义,位于 frameworks/native/opengl/libs/EGL/egldefs.h
:
这个结构包含了主、次版本号; 4 个指针;两个函数表的指针以及一个函数表。
Loader::open(egl_connection_t* cnx)
初始化图形驱动,主要是初始化这些函数表和指针。Loader::open(egl_connection_t* cnx)
的定义如下:
这个函数中,执行步骤如下:
- 为设备是模拟器的情况,设置系统属性
qemu.gles
。 - 加载设备特有的图形驱动库,包括 EGL 库,OpenGL ES 1.0 和 2.0 的库。
- 加载图形驱动 Wrapper,它们都位于
/system/lib64
或/system/lib
。
egl_connection_t
中的几个指针中,dso
实际为 struct driver_t
指针,该结构定义(位于 frameworks/native/opengl/libs/EGL/Loader.h
)如下:
struct driver_t
包含设备生产商提供的设备特有 EGL 和 OpenGL ES 实现库的句柄,如果 EGL 接口和 OpenGL 接口由单独的库实现,它包含一个库的句柄,即这个单独的库,如果 EGL 接口由不同的库实现,它则包含所有这些库的句柄。
egl_connection_t
的 libEgl
、libGles2
和 libGles1
为动态链接库句柄,其中 libEgl
指向 Android 的 EGL Wrapper 库;libGles1
指向 Android 的 GLESv1_CM
Wrapper 库;libGles2
指向 Android 的 GLESv2
Wrapper 库。
然后来看 Loader::load_driver()
:
Loader::load_driver()
通过三个步骤完成驱动库加载:
第一步,找到驱动库文件的路径。
checkGlesEmulationStatus()
函数,在不是运行于模拟器中时,返回 -1;在运行于模拟器中,但不支持 GPU 硬件模拟时返回 0;在运行于模拟器中,通过宿主机端的 OpenGL ES 实现来支持 GPU 硬件模拟时,返回 1;在运行于模拟器中,通过 Android 客户系统端的生产商驱动的 OpenGL ES 实现来支持 GPU 硬件模拟时,返回 2。对于运行环境的这种判断,主要依据两个系统属性,即 ro.kernel.qemu
和 qemu.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 库文件——的查找按照如下的顺序进行:
/vendor/lib64/egl
或/vendor/lib/egl
目录下文件名符合lib%s.so
模式,即/vendor
下文件名完全匹配的库文件,比如/vendor/lib64/egl/libGLES.so
/system/lib64/egl
或/system/lib/egl
目录下文件名符合lib%s.so
模式,即/system
下文件名完全匹配的库文件,比如/system/lib64/egl/libGLES.so
/vendor/lib64/egl
或/vendor/lib/egl
目录下文件名符合lib%s_*.so
模式,即/vendor
下文件名前缀匹配的库文件,比如对于 Pixel 设备的/vendor/lib64/egl/libEGL_adreno.so
/system/lib64/egl
或/system/lib/egl
目录下文件名符合lib%s_*.so
模式,即/system
下文件名前缀匹配的库文件。
Android 会优先采用 /vendor/
下设备供应商提供的图形驱动库,优先采用库文件名完全匹配的图形驱动库。
第二步,通过 dlopen()
加载库文件。
第三步,初始化函数表。
初始化函数表主要通过 dlsym()
根据函数名,一个个找到对应的地址,并赋值给函数指针来完成。EGL 接口函数名来自于 egl_names
,OpenGL ES 接口函数名来自于 gl_names
,它们的定义位于 frameworks/native/opengl/libs/EGL/egl.cpp
:
这是两个字符串数组,数组项同样来自于另外的两个文件,gl_names
的数组项来自于 egl_entries.in
,gl_names
的数组项来自于 entries.in
,这两个文件也正是前面看到的 struct egl_t
结构和 struct gl_hooks_t
的 struct gl_t
结构的结构成员的来源。Android 通过这种方式建立函数表与函数名表中相同函数的项之间的对应关系,即对于相同的函数,它们对应的项在函数表中的偏移,与在函数名表中的偏移相同。
对于特定的进程,Android 图形驱动初始化的过程只执行一次。在 egl_init_drivers_locked()
及 Loader::load_driver()
中加日志,重新编译 frameworks/native/opengl/libs
,替换设备中的 /system/lib64/libEGL.so
和 /system/lib/libEGL.so
,重新启动设备,可以看到如下的日志输出:
根据这些日志的进程号查找响应的进程:
可见 Android 中需要用到 OpenGL ES 图形系统的进程主要有 surfaceflinger
,cameraserver
和所有的 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
文件中存在如下的内容:
即 frameworks/native/opengl/libs/
下的源码被编译为了四个动态链接库,分别为 libEGL.so
、libGLESv1_CM
、libGLESv2
和 libGLESv3
,其中 libGLESv2
和 libGLESv3
完全相同。在frameworks/base/core/jni/Android.mk
中可以清晰地看到 framework JNI 对这些库的依赖:
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
文件中:
这个文件包含了另外两个文件,gl_api.in
和 glext_api.in
。其中 gl_api.in
文件位于 frameworks/native/opengl/libs/GLES_CM/gl_api.in
,内容如下:
glext_api.in
文件位于 frameworks/native/opengl/libs/GLES_CM/glext_api.in
,内容如下:
配和前面的 API_ENTRY
和 CALL_GL_API
宏定义,可见 gl.cpp
中主要是定义了 OpenGL ES 函数接口,这些函数的实现为,借助于前面初始化的函数表,通过汇编代码,跳转到相应的 OpenGL ES 函数实现。
GLESv2 Wrapper 位于 frameworks/native/opengl/libs/GLES2
,其 OpenGL ES 接口实现在 gl2.cpp
文件中:
这里的实现与前面看到的 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.so
、libGLESv1_CM_emulation.so
和 libEGL_emulation.so
。此时,OpenGL ES 图形库结构如下:
对于真实的物理 Android 设备,OpenGL ES 图形库结构如下:
打赏
Done.
Android OpenGL 图形系统分析系列文章
在 Android 中使用 OpenGL
Android 图形驱动初始化
EGL Context 创建
Android 图形系统之图形缓冲区分配
Android 图形系统之gralloc