AndroidOpenGLES分析与实践.docx
《AndroidOpenGLES分析与实践.docx》由会员分享,可在线阅读,更多相关《AndroidOpenGLES分析与实践.docx(19页珍藏版)》请在冰豆网上搜索。
AndroidOpenGLES分析与实践
AndroidOpenGLES分析与实践
作者:
雪夜刀手
1.OpenGLES简介
Android3D引擎采用的是OpenGLES。
OpenGLES是一套为手持和嵌入式系统设计的3D引擎API,由Khronos公司维护。
在PC领域,一直有两种标准的3DAPI进行竞争,OpenGL和DirectX。
一般主流的游戏和显卡都支持这两种渲染方式,DirectX在Windows平台上有很大的优势,但是OpenGL具有更好的跨平台性。
由于嵌入式系统和PC相比,一般说来,CPU、内存等都比PC差很多,而且对能耗有着特殊的要求,许多嵌入式设备并没有浮点运算协处理器,针对嵌入式系统的以上特点,Khronos对标准的OpenGL系统进行了维护和改动,以期望满足嵌入式设备对3D绘图的要求。
2.AndroidOpenGLES简介
Android系统使用OpenGL的标准接口来支持3D图形功能,android3D图形系统也分为java框架和本地代码两部分。
本地代码主要实现的OpenGL接口的库,在Java框架层,javax.microedition.khronos.opengles是java标准的OpenGL包,android.opengl包提供了OpenGL系统和AndroidGUI系统之间的联系。
Android的本地代码位于frameworks/base/opengl下,JNI代码位于frameworks/base/core/com_google_android_gles_jni_GLImpl.cpp和frameworks/base/core/com_google_android_gles_jni_EGLImpl.cpp,java类位于opengl/java/javax/microedition/khronos下
3.OpenGL的本地代码分析
3.1OpenGLES测试代码
frameworks/base/opengl/tests下有OpenGL的本地测试代码。
包括angeles、fillrate等14个测试代码,这些代码都可以通过终端进行本地调用测试(模拟器中使用adbshell)。
在本文中,主要使用了tritex这个测试用例。
在tests文件夹中执行mm,打印出以下信息
Install:
out/target/product/generic/system/bin/angeles
…
Install:
out/target/product/generic/system/bin/test-opengl-tritex
由以上信息可知,测试用例被安装在了out/target/product/generic/system/bin/目录下,将之拷贝到nfs文件系统中,以便测试。
我把这些测试用例都单独放在android的根文件系统的gltest文件夹中了。
3.2OpenGLES的编译
编译libagl下的源码生成Install:
out/target/product/generic/system/lib/egl/libGLES_android.so
编译libs下的生成了
Install:
out/target/product/generic/system/lib/libGLESv2.so
Install:
out/target/product/generic/system/lib/libGLESv1_CM.so
Install:
out/target/product/generic/system/lib/libEGL.so
3.3使用OpenGLES画图必经的步骤
1、获取Display,Display代表显示器。
函数原型:
EGLDisplayeglGetDisplay(NativeDisplayTypedisplay);
display参数是native系统的窗口显示ID值,一般为EGL_DEFAULT_DISPLAY。
该参数实际的意义是平台实现相关的,在X-Window下是XDisplayID,在MSWindows下是WindowDC。
2、初始化egl库。
函数原型:
EGLBooleaneglInitialize(EGLDisplaydpy,EGLint*major,EGLint*minor);
其中dpy应该是一个有效的EGLDisplay。
函数返回时,major和minor将被赋予当前EGL版本号。
3、选择一个合适的EGLConfigurationFrameBuffer,实际指的是FrameBuffer的参数
函数原型:
EGLBooleaneglChooseConfig(EGLDisplaydpy,constEGLint*attrib_list,EGLConfig*configs,EGLintconfig_size,
EGLint*num_config);
参数attrib_list:
指定了选择配置时需要参照的属性。
参数configs:
将返回一个按照attrib_list排序的平台有效的所有EGLframebuffer配置列表。
参数config_size:
指定了可以返回到configs的总配置个数。
参数num_config:
返回了实际匹配的配置总数。
4、创建一个可实际显示的EGLSurface,实际上就是一个FrameBuffer
函数原型:
EGLSurfaceeglCreateWindowSurface(EGLDisplaydpy,EGLConfigconfig,
NativeWindowTypewin,
constEGLint*attrib_list);
5、创建Context
函数原型:
EGLContexteglCreateContext(EGLDisplaydpy,EGLConfigconfig,
EGLContextshare_context,
constEGLint*attrib_list);
6、绑定Display、Surface、Context
函数原型:
EGLBooleaneglMakeCurrent(EGLDisplaydpy,EGLSurfacedraw,
EGLSurfaceread,EGLContextctx);
3.4OpenGLES执行过程
运行android操作系统之后,输入logcat命令,然后执行gltest中的test-opengl-tritex,屏幕上打印了以下信息
D/libEGL(1962):
egl.cfgnotfound,usingdefaultconfig
D/libEGL(1962):
loaded/system/lib/egl/libGLES_android.so
可以看出,在执行OpenGL调用的过程中,会自动加载libGLES_android.so动态链接库。
后面将会通过分析和修改源码的方式,了解OpenGLES系统的调用过程。
通过3.3中的说明,我们在tritex测试程序中插入一些调试信息,查看OpenGLES的调用过程。
在调用eglGetDisplay之前会执行early_egl_init函数,这是一个静态的函数。
在eglGetDisplay中会去初始化驱动,最终调用到egl_init_drivers_locked函数中。
这个函数的主要内容如下
EGLBooleanegl_init_drivers_locked()
{
if(sEarlyInitState){
//initializedbystaticctor.shouldbesethere.
returnEGL_FALSE;
}
//getourdriverloader
Loader&loader(Loader:
:
getInstance());
//dynamicallyloadallourEGLimplementationsforalldisplays
//andretrievethecorrespondingEGLDisplay
//ifthatfails,don'tusethisdriver.
//TODO:
currentlyweonlydealwithEGL_DEFAULT_DISPLAY
egl_connection_t*cnx;
egl_display_t*d=&gDisplay[0];
cnx=&gEGLImpl[IMPL_SOFTWARE];
if(cnx->dso==0){
cnx->hooks[GLESv1_INDEX]=&gHooks[GLESv1_INDEX][IMPL_SOFTWARE];
cnx->hooks[GLESv2_INDEX]=&gHooks[GLESv2_INDEX][IMPL_SOFTWARE];
cnx->dso=loader.open(EGL_DEFAULT_DISPLAY,0,cnx);
if(cnx->dso){
EGLDisplaydpy=cnx->egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
LOGE_IF(dpy==EGL_NO_DISPLAY,"NoEGLDisplayforsoftwareEGL!
");
d->disp[IMPL_SOFTWARE].dpy=dpy;
if(dpy==EGL_NO_DISPLAY){
loader.close(cnx->dso);
cnx->dso=NULL;
}
}
}
cnx=&gEGLImpl[IMPL_HARDWARE];
if(cnx->dso==0){
charvalue[PROPERTY_VALUE_MAX];
property_get("debug.egl.hw",value,"1");
if(atoi(value)!
=0){
cnx->hooks[GLESv1_INDEX]=&gHooks[GLESv1_INDEX][IMPL_HARDWARE];
cnx->hooks[GLESv2_INDEX]=&gHooks[GLESv2_INDEX][IMPL_HARDWARE];
cnx->dso=loader.open(EGL_DEFAULT_DISPLAY,1,cnx);
if(cnx->dso){
EGLDisplaydpy=cnx->egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
LOGE_IF(dpy==EGL_NO_DISPLAY,"NoEGLDisplayforhardwareEGL!
");
d->disp[IMPL_HARDWARE].dpy=dpy;
if(dpy==EGL_NO_DISPLAY){
loader.close(cnx->dso);
cnx->dso=NULL;
}
}
}else{
LOGD("3Dhardwareaccelerationisdisabled");
}
}
if(!
gEGLImpl[IMPL_SOFTWARE].dso&&!
gEGLImpl[IMPL_HARDWARE].dso){
returnEGL_FALSE;
}
returnEGL_TRUE;
}
由此代码可以看出,egl_init_drivers_locked函数主要的工作就是填充gEGLImp数组变量,这个变量是egl_connection_t类型。
还有一个工作就是填充gDisplay数组(只有一个元素)的disp[IMPL_HARDWARE].dpy以及disp[IMPLSOFTWAREWARE].dpy,填充的来源来自gEGLImpl【softorhard】.egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
在Loader.cpp中的Loader:
:
open中会加载对应的硬件和软件加速的驱动(动态链接库)。
软件的对应的是/system/lib/egl/libEGL_android.so,没有默认的硬件so,因此在硬件加速时,返回值hnd会指向NULL,在需要硬件加速时这个动态链接库需要进行实现。
LoadDriver函数会根据其第三个参数,决定加载egl/gles,glesv1_cm,glesv2驱动。
。
。
加载几个动态链接库的过程如下图
由我以上图表可以看出,加载驱动的时候,会尝试先从libGLES_android.so中加载EGL、GLESV1_CM、GLESV2三个部分的函数,如
果加载失败,则会尝试从libEGL_android.so,libGLESV1_cm.so,libGLESV2.so三个动态库中对应的函数。
在这部分代码中,我们可以看到一个非常重要的结构体,egl_connection_t,
structegl_connection_t
{
void*dso;
gl_hooks_t*hooks[2];
EGLintmajor;
EGLintminor;
egl_tegl;
};到处都有他的身影,对这几个变量进行一下解释。
structsoinfo
{
constcharname[SOINFO_NAME_LEN];
Elf32_Phdr*phdr;
intphnum;
unsignedentry;
unsignedbase;
unsignedsize;
//buddy-allocatorindex,negativeforprelinkedlibraries
intba_index;
unsigned*dynamic;
unsignedwrprotect_start;
unsignedwrprotect_end;
soinfo*next;
unsignedflags;
constchar*strtab;
Elf32_Sym*symtab;
unsignednbucket;
unsignednchain;
unsigned*bucket;
unsigned*chain;
unsigned*plt_got;
Elf32_Rel*plt_rel;
unsignedplt_rel_count;
Elf32_Rel*rel;
unsignedrel_count;
unsigned*preinit_array;
unsignedpreinit_array_count;
unsigned*init_array;
unsignedinit_array_count;
unsigned*fini_array;
unsignedfini_array_count;
void(*init_func)(void);
void(*fini_func)(void);
#ifdefANDROID_ARM_LINKER
/*ARMEABIsectionusedforstackunwinding.*/
unsigned*ARM_exidx;
unsignedARM_exidx_count;
#endif
unsignedrefcount;
structlink_maplinkmap;
};
看一下load_driver中到底做了什么手脚。
1.首先调用dlopen打开动态链接库,返回值是void*,这个void*指向的是什么内容呢?
追踪到bionic/linker/Dlfcn.c中。
其中调用了find_library函数,这个函数是一个奇怪的函数,因为它虽然叫做find_library,在其实现中,不但在系统的so链表中去查找指定的文件名的动态链接库信息,而且对其动态链接库进行加载并返回。
至此我们明白了,这个void*指向的是一个soinfo类型的结构体
这是mandlopen的说明。
一个标准的linux函数。
Thefunctiondlopen()loadsthedynamiclibraryfilenamedbythenull-
terminatedstringfilenameandreturnsanopaque"handle"forthe
dynamiclibrary.IffilenameisNULL,thenthereturnedhandleisfor
themainprogram.Iffilenamecontainsaslash("/"),thenitis
interpretedasa(relativeorabsolute)pathname.
2.由上一步的分析,我们知道了egl_connection_t的第一个变量dso,是指向的一个soinfo结构体(discover/decompressshared
object的缩写?
?
?
)
Printf("HAHALetmeprintthesoinfomation\n");
Printf("name=%s:
phdr=%x:
entry=%x:
base=%x:
size=%x\n",soi->name,soi->phdr,soi->entry,soi->base,soi->size);
这是上一条语句打印的一些信息。
name=libGLES_android.so:
phdr=acc80034:
entry=0:
base=acc80000:
size=1c000
3.dlsym可以根据dlopen的返回值,查找第二个参数指定的函数名的地址并返回
Thefunctiondlsym()takesa"handle"ofadynamiclibraryreturnedby
dlopen()andthenull-terminatedsymbolname,returningtheaddress
wherethatsymbolisloadedintomemory.Ifthesymbolisnotfound,
inthespecifiedlibraryoranyofthelibrariesthatwereautomati-
callyloadedbydlopen()whenthatlibrarywasloaded,dlsym()returns
NULL.(Thesearchperformedbydlsym()isbreadthfirstthroughthe
dependencytreeoftheselibraries.)Sincethevalueofthesymbol
couldactuallybeNULL(sothataNULLreturnfromdlsym()neednot
indicateanerror),thecorrectwaytotestforanerroristocall
dlerror()toclearanyolderrorconditions,thencalldlsym(),and
thencalldlerror()again,savingitsreturnvalueintoavariable,and
checkwhetherthissavedvalueisnotNULL.
getProcAddress=(getProcAddressType)dlsym(dso,"eglGetProcAddress");
Printf("eglGetProcAddress'slocationis%x\n",getProcAddress);
打印信息如下,可以和刚才的打印信息比较一下。
我们确实找到了一个函数。
eglGetProcAddress'slocationisacc930b1
Printf("curr=%x,it'saddressis%x\n",curr,f);
打印如下
eglGetProcAddress'slocationisacc930b1
*api=eglGetDisplay
curr=ac708a60,it'saddressisacc931a5
*api=eglInitialize
curr=ac708a64,it'saddressisacc93c9d
*api=eglTerminate
curr=ac708a68,it'saddressisacc93cdd
*api=eglGetConfigs
curr=ac708a6c,it'saddressisacc93d41
*api=eglChooseConfig
curr=ac708a70,it'saddressisacc9472d
*api=eglGetConfigAttrib
curr=ac708a74,it'saddressisacc94325
*api=eglCreateWindowSurface
curr=ac708a78,it'saddressisacc94689
*api=eglCreatePixmapSurface
curr=ac708a7c,it'saddressisacc945d5
*api=eglCreatePbufferSurface
curr=ac708a80,it'saddressisacc9451d
*api=eglDestroySurface
curr=ac708a84,it'saddressisacc93a1d
*api=eglQuerySurface
curr=ac708a88,it'saddressisacc94341
*api=eglCreateContext
curr=ac708a8c,it'saddressisacc9415d
*api=eglDestroyContext
curr=ac708a90,it'saddressisacc93d09
*api=eglMakeCurrent
curr=ac708a94,it'saddressisacc93a6d
*api=eglGetCurrentContext
curr=ac708a98,it'saddressisacc93055
*api=eglGetCurrentSurface
curr=ac708a