Android 系统使用OpenGL的标准接口来支持3D图形功能,包含框架层及本地代码两个主要部分,这里先介绍本地代码部分。
源代码目录为:frameworks\base\opengl\libs
在这个代码路径下面会编译生成三个库: libEGL , libGLESv1_CM.so , libGLESv2.so ,那么这三个库之间是个什么关系呢?
首先说明一下主要实现的功能:
EGL是用来管理绘图表面的(Drawing surfaces),并且提供了如下的机制
(1) 与本地窗口系统进行通信
(2) 查找绘图表面可用的类型和配置信息
(3) 创建绘图表面
(4) 同步OpenGL ES 2.0和其他的渲染API(Open VG、本地窗口系统的绘图命令等)
(5) 管理渲染资源,比如材质
2. EGL 和 OpenGL ES API的联系
(1) 通过解析OpenGL ES API函数库 libGLES_android.so来获取函数指针,进行调用。
(2) 通过线程局部存储机制进行联系
3. libGLESv1_CM.so 及 libGLESv2.so 都是一个简单的wrapper, 针对OpenGl ES API进行封装
这里使用到了线程局部存储机制,这是一个什么东东呢?
概念:线程局部存储(Thread Local Storage,TLS)用来将数据与一个正在执行的指定线程关联起来。
进程中的全局变量与函数内定义的静态(static)变量,是各个线程都可以访问的共享变量。在一个线程修改的内存内容,对所有线程都生效。这是一个优点也是一个缺点。说它是优点,线程的数据交换变得非常快捷。说它是缺点,一个线程死掉了,其它线程也性命不保; 多个线程访问共享数据,需要昂贵的同步开销,也容易造成同步相关的BUG。
如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。
功能:它主要是为了避免多个线程同时访存同一全局变量或者静态变量时所导致的冲突,尤其是多个线程同时需要修改这一变量时。为了解决这个问题,我们可以通过TLS机制,为每一个使用该全局变量的线程都提供一个变量值的副本,每一个线程均可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。而从全局变量的角度上来看,就好像一个全局变量被克隆成了多份副本,而每一份副本都可以被一个线程独立地改变。
好了,介绍完毕,下面介绍系统中代码如何体现的:
由于OpenGL是一个其于上下文Context 环境开发的,所以每个线程需要保存自已的运行环境,如果没有的话则会报错:
"call to OpenGL ES API with no current context logged once per thread"
如何创建TLS:
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static int sEarlyInitState = pthread_once(&once_control, &early_egl_init);
这两条语句会先于eglGetDisplay函数执行。第二条语句中将函数指针early_egl_init作为参数传入,会执行回调,并且保证单个线程只会执行一次。在early_egl_init()中,对TLS机制进行初始化。将TLS里放入一个结构体指针,这个指针指向gHooksNoContext(gl_hooks_t类型),这个结构体里的每个函数指针被初始化为了gl_no_context。也就是现在如果通过TLS调用的OpenGL ES API都会调到gl_no_context这个函数中。
综上,这两条语句完成了TLS的初始化。
另一处初始化时在eglInitialize函数中,同样设置成了gHooksNoContext。
TLS的赋值
在eglMakeCurrent中,会将渲染上下文绑定到渲染面。在EGL中首先会处理完一系列和本地窗口系统的变量后,调用libGLES_android.so中的eglMakeCurrent,调用成功的话会设置TLS。将TLS指针指向前面已经初始化化好的gl_hooks_t结构体指针,这个结构体里的成员都已经指向了libGLES_android.so中的OpenGL API函数,至此EGL的大部分初始化工作就已经完成了。基本就可以使用OpenGL ES API进行绘图了。
static inline void setGlThreadSpecific(gl_hooks_t const *value) {
gl_hooks_t const * volatile * tls_hooks = get_tls_hooks();
tls_hooks[TLS_SLOT_OPENGL_API] = value;
}
我们来看看在OpenGl中如何使用TLS的: (frameworks\base\opengl\libs\GLES_CM\gl.cpp)
#define GET_TLS(reg) \
"mov " #reg ", #0xFFFF0FFF \n" \
"ldr " #reg ", [" #reg ", #-15] \n"
#define CALL_GL_API(_api, ...) \
asm volatile( \
GET_TLS(r12) \
"ldr r12, [r12, %[tls]] \n" \
"cmp r12, #0 \n" \
"ldrne pc, [r12, %[api]] \n" \
"bx lr \n" \
: \
: [tls] "J"(TLS_SLOT_OPENGL_API*4), \
[api] "J"(__builtin_offsetof(gl_hooks_t, gl._api)) \
: \
);
#define CALL_GL_API_RETURN(_api, ...) \
CALL_GL_API(_api, __VA_ARGS__) \
return 0; // placate gcc's warnings. never reached.
CALL_GL_API这个带参数的宏。它的意思是获取TLS_SLOT_OPENGL_API的TLS,如果它的值不是NULL,就跳到相应的OpenGL ES API的地址去执行。这个地方为什么会跳过去呢??因为从线程局部存储保存的线程指针,指向了一个gl_hooks_t指针,而这个指针指向的结构体里的成员已经在EGL中被初始化为了libGLES_android.so里的函数地址了。所以就可以跳过去了。
那么在哪里初始化gl_hooks_t指针的呢? (frameworks\base\opengl\libs\EGL\Loader.cpp)
FILE* cfg = fopen("/system/lib/egl/egl.cfg", "r");
if (cfg == NULL) { // 如果找不到这个配置则默认加载软件3D库即 libGLES_android.so
// default config
LOGD("egl.cfg not found, using default config");
gConfig.add( entry_t(0, 0, "android") );
}
否则根据配置文件加载硬件3D库,egl.cfg 文件格式是“dpy impl tag”比如自己添加的硬件加速库是libGLES_mali.so,则需要在此文件里这样编写
0 1 mali
修改后要重启才可生效。
加载就是利用dlopen、dlsym加载并赋值:
void* dso = dlopen(driver_absolute_path, RTLD_NOW | RTLD_LOCAL);
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++;
}
char const * const gl_names[] = {
#include "entries.in"
NULL
};
char const * const egl_names[] = {
#include "egl_entries.in"
NULL
};
这里面包含的函数头文件在:gl2_api.in 及 gl_api.in 中对应于不同的版本,对应用C头文件接口是: include/RGL/egl.h 及
include\GLES\中的gl.h和glext.h,这些接口供外部调用OpenGL本库所使用接口,这些接口基本上和OpenGL标准接口一致。
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
插一句如何知道哪些OpenGL函数没有实现呢?
在init_api函数添加打印即可知道哪些函数没有实现:
if (f == NULL) {
//LOGD("%s", name);
f = (__eglMustCastToProperFunctionPointerType)gl_unimplemented;
}
最麻烦的事情也就是实现以上两个头文件,扩展OpenGl函数最好不要使用,需要进行GPU编程,一般由芯片公司提供。
目前手机上使用最广范的GPU CORE当属 Imagination Technologies 公司开发的 PowerVR graphics core
ARM公司收购的 Falanx公司提供 Mali, Mali-200, Mali-400
Vivante公司提供的 GPU core