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