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