原文地址
http://hi.baidu.com/snownight/blog/item/366eb07e20e087340cd7da8f.html
OpenGL ES 報告(2)
作者:雪夜刀手 http://hi.baidu.com/snownight
1. 什麼是EGL
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) 通過執行緒區域性儲存機制進行聯絡
關於通過函式指標進行聯絡在前面已經分析過了。下面著重分析通過執行緒區域性儲存機制進行聯絡分析一下。
2.1什麼是執行緒區域性儲存(TLS)
TLS 讓多執行緒程式設計更容易一些。TLS 是一個機制,經由它,程式可以擁有全域變數,但處於「每一執行緒各不相同」的狀態。也就是說,程式中的所有執行緒都可以擁有全域變數,但這些變數只對特定對某個執行緒才有意義。http://xianjunzhang.blog.sohu.com/21537031.html
2.2 TLS的好處
你可能有一個多執行緒程式,每一個執行緒都對不同的檔案寫檔案(也因此它們使用不同的檔案handle)。這種情況下,把每一個執行緒所使用的檔案handle 儲存在TLS 中,將會十分方便。當執行緒需要知道所使用的handle,它可以從TLS 獲得。重點在於:執行緒用來取得檔案handle 的那一段程式碼在任何情況下都是相同的,而從TLS中取出的檔案handle 卻各不相同。非常靈巧,不是嗎?有全域變數的便利,卻又分屬各執行緒。 http://xianjunzhang.blog.sohu.com/21537031.html
2.3 OpenGL ES中的TLS
2.3.1 TLS的初始化
應用程式通過EGL呼叫eglGetDisplay()函式時,會呼叫到libEGL.so中,通過看其原始碼egl.so可以發現,其中有條語句
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。
2.3.2 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;
}
3. 呼叫OpenGL ES API函式
在通過EGL對本地視窗系統做一系列初始化之後,就需要呼叫真正的OpenGL ES API進行3D繪圖了,對於很多沒有接觸過OpenGL和計算機圖形學的人來說,這部分可能是比較困難的(很多演算法我都不懂。。。)。下面脫離具體的演算法實現,看看要呼叫OpenGL ES API,Android 的3D系統做了什麼。
具體分析可以參考Android原始碼中OpenGL ES的測試程式碼。
在應用程式中要呼叫一個OpenGL ES API,需要包含對應的標頭檔案。比如OpenGL ES 1.1對應的標頭檔案是<GLES/gl.h>。但是在具體的執行中,呼叫到了那個庫中呢?
分析tests中的tritex測試案例。它的Andoird.mk是這樣的。
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
tritex.cpp
LOCAL_SHARED_LIBRARIES := \
libcutils \
libEGL \
libGLESv1_CM \
libui
LOCAL_MODULE:= test-opengl-tritex
PRODUCT_COPY_FILES := \
out/target/product/generic/system/bin/test-opengl-tritex:/nfs/gltest/
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)
從中可以看出,它連結的時候使用的libGLESv1_CM.so
而生成libGLESV1_CM.so的Android.mk是這樣寫的。
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
GLES_CM/gl.cpp.arm \
#
LOCAL_SHARED_LIBRARIES += libcutils libEGL
LOCAL_LDLIBS := -lpthread -ldl
LOCAL_MODULE:= libGLESv1_CM
# needed on sim build because of weird logging issues
ifeq ($(TARGET_SIMULATOR),true)
else
LOCAL_SHARED_LIBRARIES += libdl
# we need to access the private Bionic header <bionic_tls.h>
LOCAL_C_INCLUDES += bionic/libc/private
endif
LOCAL_CFLAGS += -DLOG_TAG=\"libGLESv1\"
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
LOCAL_CFLAGS += -fvisibility=hidden
ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true)
LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER
endif
include $(BUILD_SHARED_LIBRARY)
原始檔只有一個gl.cpp.
這個檔案裡的函式看起來只有兩個函式(怎麼可能!!!),其實不然。
其中一句
extern "C" {
#include "gl_api.in"
#include "glext_api.in"
}
C語言中對#include的處理類似於巨集的展開,直接把檔案包含進來進行編譯的。
gl_api.in中,實際上這些函式的定義。
……
void API_ENTRY(glClearColor)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) {
CALL_GL_API(glClearColor, red, green, blue, alpha);
}
void API_ENTRY(glClearDepthf)(GLclampf depth) {
CALL_GL_API(glClearDepthf, depth);
}
……
gl.cpp中去掉條件編譯
#define GET_TLS(reg) \
"mov " #reg ", #0xFFFF0FFF \n" \
"ldr " #reg ", [" #reg ", #-15] \n"
#define API_ENTRY(_api) __attribute__((naked)) _api
#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裡的函式地址了。所以就可以跳過去了。
從以上分析可以看出libGLESv1_CM.so只是一個wrapper,對OpenGL ES API進行了一個簡單的包裹,真正的實現還是在libGLES_andoird.so中的。
對於libGLESV2.so是同樣的道理。
這樣做的好處:
1. 兩套OpenGL ES API對應一個實現庫,便於維護
2. 可以對OpenGL ES API進行更改,而不需要改變對外的介面,易於程式移植和相容。
4. 需要注意的問題
OpenGL ES API很多函式並沒有實現。軟體加速庫不支援OpenGL ES2.0
Loader.cpp中的init_api函式通過dlsym函式,對so檔案進行解析,返回了函式的指標,在對每個函式進行跟蹤的過程中發現,原來glShaderSource並沒有在openGL ES的原始碼中實現,而且發現很多函式都沒有在OpenGL ES的原始碼中實現。
I/libagl ( 2445): in eglChooseConfig
I/libagl ( 2445): in eglCreateWindowSurface
I/libagl ( 2445): in eglCreateContext
I/libagl ( 2445): in eglMakeCurrent
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
E/libEGL ( 2445): called unimplemented OpenGL ES API
5. 學習OpenGL ES 應用程式,瞭解基本的OpenGL ES API
學習OpenGL ES API是為了瞭解基本的API函式的工作原理和一些3D術語,從而配合三星s3c6410(0718)文件,實現自己的OpenGL ES硬體加速庫。
(1) Vertex Arrays/Buffer Objects
(2) Vertex Shader
實現對定點通用的一些操作
Vertex shaders can be used for traditional vertex-based operations such as transforming the position by a matrix, computing the lighting equation to generate a per-vertex color, and generating or transforming texture coordinates. Alternately, because the vertex shader is specified by the application, vertex shaders can be used to do custom vertex transformations
(3) Primitive Assembly
將Vertex Shader中的資料彙編成可以畫出來的幾何圖形,比如三角形、直線等
(4) Rasterizition
將前一步的資料進行繪製,同時將前幾個階段的圖元轉換成二維的片段,傳遞給fragment shader
(5) Fragment Shader
對Fragment進行操作。
(6) Per-Fragment Shader
對每個片段進行判斷(是否可見)和處理(混合、抖動處理)。
(7) 在FrameBuffer中進行顯示。
6 s3c6410 驅動分析
6410的g3d的驅動程式碼位於Kernel/drivers/media/video/Samsung/g3d中的s3c_fimg3d.c中。
從s3c_fimg3d.c中我們可以獲取什麼知識??Almost Nothing!
我這裡有從網上下載的另一個版本的6410的g3d的驅動,比較全。
點選開啟
小小分析:
在驅動中的s3c_g3d_probe中,
主要做了get_resouce request_mem_region ioremap等操作
/* get the memory region for the post processor driver */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
這句話獲取了s3c-g3d的IO資源。
s3c_g3d probe() called
res.start=72000000,res.end=72ffffff,rest.name=s3c-g3d,res.flags=512
before ioremap:
s3c_g3d_mem->start=72000000,s3c_g3d_mem->end=72ffffff,s3c_g3d_mem->name=s3c-g3d,s3c_g3d_mem->flags=-2147483648
after io remap:
s3c_g3d_base=d5000000
s3c_g3d version : 0x1050000
S3C G3D Init : Done
應用程式怎麼訪問g3d的暫存器呢?是通過mmap操作來訪問的,下面是一個通過應用程式訪問g3d暫存器的一個測試。
int main()
{
struct s3c_3d_mem_alloc mem_para;
int fd=open("/dev/s3c-g3d",O_RDWR| O_SYNC);
char arg[100];
int i;
if(fd==-1)
{
printf("open s3c-g3d error\n");
return 0;
}
printf("test mmap of default\n");
struct stat file_stat;
if (fstat(fd, &file_stat) < 0)
{
printf("fstat wrong");
exit(1);
}
printf("file size:%d\n", file_stat.st_size);
void *sfp;
if((sfp=mmap(NULL, 2048, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0)) == MAP_FAILED)
{
printf("mmap wrong!");
exit(0);
}
printf("sfp=%p\n",sfp);
printf("version=%x\n",*((int*)(sfp+0x10)));//偏移為10的暫存器是g3d版本暫存器
return 0;
}
列印資訊:
/ # ./test
in s3c_g3d_open******
test mmap of default
file size:0
hahahahahhahha&&&&&&&&&&&&&&&&MMAP : vma->end = 0x4001c000, vma->start = 0x4001b000, size = 4096
sfp=0x4001b000
version=1050000/*****這是在應用程式中獲取的g3d的version資訊,說明可以通過讀取暫存器獲取資料******/
硬體加速的實現方法
1. 1.修改Loader.cpp檔案裡的Loader::loader函式,新增gConfig.add( entry_t(0, 1, "mmoid") );或者修改egl.cfg檔案。此檔案位於/system/lib/egl/下,如果不存在,可以自己動手新建。格式是“dpy impl tag”比如自己新增的硬體加速庫是libGLES_mmoid.so,則需要在此檔案裡這樣編寫
0 1 mmoid
修改後要重啟才可生效。
2. 以上只是方法,要實現硬體加速,必須自己編寫libGLES_mmoid.so。這個過程最複雜。需要對g3d的驅動進行呼叫,從上面的測試例子中可以看出,驅動中只是做了個簡單的封裝,對g3d的呼叫實際上是從應用程式層次進行呼叫的。這樣的話,需要自己編寫對g3d呼叫的實現庫,實際上是對g3d驅動的封裝。
3. 在opengl api中實現對上述封裝庫的呼叫。
7 s3c6410 文件分析
在熟悉s3c6410的g3d文件,搞清楚各個暫存器的使用方法ing。編寫呼叫g3d驅動的實現庫。
8. 遇到的問題
(1)沒有可供分析的sample程式碼,只能自己分析。
(2)OpenGL ES專業術語和演算法比較多,短時間不好掌握
(3)這部分OpenGL ES的實現是不是ip廠商會提供呢?在0718上是不是有已經實現了的廠商提供的二進位制OpenGL ES硬體加速程式碼了呢?
(4)OpenGL ES的很多操作都是Pipeline,想要實現部分函式的硬體加速,是不是其他函式也能影響到呢?