报告分为两大部分,第一部分是3.22号在中心五楼会议室讨论后的一些需要深入调研的点,第二部分是android对2D的调用关系。
第二部分:
Android中2D图形引擎流程(以下说明以android2.0及以上版本):
上图是android的框架图,在LIBRARIES中有三个模块是比较重要的。分别是Surface Manager(界面管理),OpenGL|ES,SGL(SGL就是上文提到的skia graphic layer,是android中为应用提供的一套2D图形库)。
Android的图形系统采用client和server架构。
Client server
因此我们对其的理解可以分成两个部分,应用和界面:
应用:2D接口会调用skia图形引擎,而skia是软件实现的。3D接口会调用OpenGL,OpenGL的库可以通过软件实现,如果存在硬件库的话则调用硬件。软件库libagl.so,硬件库libhgl.so.
此部分的调研疑问:所有资料几乎都说skia是软件实现,没法使用硬件,但是在台湾的一篇介绍skia的博文中却提到:skia它可以搭配OpenGL/ES与特定的硬体特征,强化显示的效果。这部分存在矛盾,需进一步验证(这部分的疑惑已找到,可以参考这篇文章http://blog.csdn.net/yili_xie/archive/2009/11/12/4803565.aspx)。
界面:界面主要是通过sufaceFlinger实现的,在2.0及以上版本,sufaceFlinger直接调用OpenGL ES API。此时如果存在硬件库则系统自动调用硬件库的api实现硬件加速。
如果系统没有支持OpenGL ES的硬件,但是能够支持一些简单的2D功能。则系统会调用软件库,OPEN GLES软件库中有一个copybit的适配文件,此时我们可以在hardware中实现copybit这个文件。系统在自动调用OpenGL ES软件库的时候,,系统通过适配文件找到copybit从而将一些功能通过硬件实现(这些功能主要包括内存的拷贝,放大缩小,旋转)。
如果系统没有支持OpenGL ES的硬件,甚至连简单2D硬件都没有,sufaceFlinger通过open gl es软件库实现。
----------------------------------------------------------------------------------
文章摘自如下:
Android apk 里面的画图分为2D和3D两种:2D是由Skia 来实现的,也就是我们在框架图上看到的SGL,SGL也会调用部分opengl 的内容来实现简单的3D效果;3D部分是由OpenGL|ES实现的,OpenGL|ES是Opengl的嵌入式版本,我们先了解一下Android apk的几种画图方式,然后再来来看一看这一整套的图形体系是怎么建立的。
首先画图都是针对提供给应用 程序的一块内存填充数据 , 没去研究过一个Activity是否就对应着底层的一个surface,但是应该都是对这块surface内存进行操作。因此说穿了就是我们要么调用2D 的API画图,要么调用3D的API画图,然后将画下来的图保存在这个内存中,最后这个内存里面的内容会被Opengl渲染以后变为可以在屏幕上的像素信 息。
一 、Apk应用主要关心的还是这些API的使用,在Android apk里面画图有2种方式 [2D]:
1、Simple Graphics in View
就是直接使用Android已经实现的一些画图操作,比如说images,shapes,colors,pre-defined animation等等,这些简单的画图操作实际上是由skia来提供的2D图形操作。使用这些预定义好的操作,我们可以实现诸如贴一张背景图啊,画出简 单地形状阿,实现一些简单的动画之类的操作。这里的简单可以这么理解,就是我们在这里没有一笔一画地构造出一个图形出来,我们只是把我们的Graphic 资源放入View体系中,由系统 来将这些Graphic画出来。举个例子:我们现在在Activity里面绑定一个ImageView,我们可以设置 这 个ImageView的内容是我们的picture,然后我们可以让这个picture整体颜色上来点蓝色调,然后我们还可以为这个ImageView加 入一个预定义动画,这样当系统要显示这个View的时候就会显示我们的picture,并且会有动画,并带有一个蓝色调,我们并没有自己去定义画图操作, 而是将这些内容放入View中,由系统来将这些内容画出来。这种方式只能画静态或者极为简单的2D图画,对于实时性很强的动画,高品质的游戏都是没法实现 的。
2、Canvas
首先我们要明白这个Canvas是一个2D的概念,是在Skia中定义的。也就是说在这个方式下还是说的画2D图形。我们可以把这个Canvas理解成系 统提供给我们的一块内存区域(但实际上它只是一套画图的API,真正的内存是下面的Bitmap),而且它还提供了一整套对这个内存区域进行操作的方法, 所有的这些操作都是画图API。也就是说在这种方式下我们已经能一笔一划或者使用Graphic来画我们所需要的东西了,要画什么要显示什么都由我们自己 控制。这种方式根据环境还分为两种:一种就是使用普通View的canvas画图,还有一种就是使用专门的SurfaceView的canvas来画图。 两种的主要是区别就是可以在SurfaceView中定义一个专门的线程来完成画图工作,应用程序不需要等待View的刷图,提高性能。前面一种适合处理 量比较小,帧率比较小的动画,比如说象棋游戏之类的;而后一种主要用在游戏,高品质动画方面的画图。下面是这两种方式的典型sequence :
2.1、View canvas
(1) 定义一个自己的View :class your_view extends View{} ;
(2) 重载View的onDraw方法:protected void onDraw(Canvas canvas){} ;
(3) 在onDraw方法中定义你自己的画图操作 ;
ps: 可以定义自己的Btimap:
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
但是必须将这个Bitmap放入View的canvas中,画的图才能显示出来:public void drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint) ;
invalidate()这个函数被调用后,系统会在不久的将来重新调用onDraw()函数,这个时间很短但并不是可预知的,我曾经在onDraw() 中写了一个if语句,利用validate让系统死循环调用onDraw刷两张图,几乎上这两张图是刷在一起的。
2.2、Surface View Canvas
(1) 定义一个自己的SurfaceView : class your_surfaceview extends SurfaceView implements SurfaceHolder.Callback() {} ;
(2) 实现SurfaceHolder.Callback的3个方法surfaceCreated() surfaceChanged() surfaceDestroyed() ;
(3) 定义自己的专注于画图的线程 : class your_thread extends Thread() {} ;
(4) 重载线程的run()函数 [一般在run中定义画图操作,在surfaceCreated中启动这个线程]
(5) 画图的过程一般是这样的:
SurfaceHolder surfaceHolder = getHolder() ; //取得holder,这个holder主要是对surface操作的适配,用户不具备对surface操作的权限
surfaceHolder.addCallback(this) ; //注册实现好的callback
Canvas canvas = surfaceHolder.lockCanvas() ; //取得画图的Canvas
/*---------------------------------画图
**-------------------------------- 画图结束*/
surfaceHolder.unlockCanvasAndPost() ; //提交并显示
所有前面3种方式的画图的一些例子在SDK上都有,写得也比较清楚,我这里就不说了,这里写一下我调这些代码过程一些小经验,应该主要涉及的是Activity这方面,应该以后都用得到:
首先是关于Eclipse的 :
(1) ctrl + shift + O 可以自动添加需要的依赖包,这功能挺实用的觉得,还有alt + /是语法补全 ;
(2) 代码中右键比较实用的功能有很多,我记得的是F3找类的声明,F4找类的继承关系 ;
(3) 断点调试比较方便的,在Eclipse的右上交可以选择阅读代码的方式,还能经如debug模式,我现在用到的两个打log的方式:
Log.e("class", "value : "+ classname) ; //检测class是否为空指针
Log.e(this.getClass().getName(), "notice message") ;
然后是关于Activity的 :
(1) 首先尽量把UI的设计放在XML中实现,而不要放在代码中实现,这样方便设计,修改和移植 ;
(2) 所有使用到的component都必须在manifest中声明,不然程序中找不到相应的conponet的时候会报错 ;
(3) 一般每一个Activity都对应于一个类,和一个相应的布局文件xml ;
(4) 每一个Activity只有使用setContentView()绑定内容后才会显示,而且你才能从这个内容(比如xml中)获取到你需要的元素 ;
(5) res/drawable和res/raw中的元素的区别是drawable中的元素像素可能会被系统优化,而raw中的不会被优化 ;
(6) 当多个Activity都从res/drawable中获得同一个元素,如果其中一个修改它的属性,所有其他的Activity中这个元素的相应属性都会改变 ;
(7) res/anim中保存的是动画相关的xml ;
下面我们总结以下2D画图用到的包 :
android.view //画图是在View中进行的
android.view.animation //定义了一些简单的动画效果Tween Animation 和 Frame. Animation
android.graphics //定义了画图比较通用的API,比如canvas,paint,bitmap等
android.graphics.drawable //定义了相应的Drawable(可画的东西),比如说BitmapDrawable,PictureDrawable等
android.graphics.drawable.shapes //定义了一些shape
二、了解了2D,我们再来看看3D的画图方式。3D画图SDK上讲得很简单,只是提了一个通用的方式,就是继承一个View,然后在这个View里面获得 Opengl的句柄进行画图,道理应该来说是和2D一样的,差别就是一个是使用2D的API画图,一个是使用3D的。不过因为3D openGl|ES具有一套本身的运行机制,比如渲染的过程控制等,因此Android为我们提供了一个专门的用在3D画图上的 GLSurfaceView。这个类被放在一个单独的包android.opengl里面,其中实现了其他View所不具备的操作:
(1) 具有OpenGL|ES调用过程中的错误跟踪,检查工具,这样就方便了Opengl编程过程的debug ;
(2) 所有的画图是在一个专门的Surface上进行,这个Surface可以最后被组合到android的View体系中 ;
(3) 它可以根据EGL的配置来选择自己的buffer类型,比如RGB565,depth=16 (这里有点疑问,SurfaceHolder的类型是SURFACE_TYPE_GPU,内存就是从EGL分配过来的?)
(4) 所有画图的操作都通过render来提供,而且render对Opengl的调用是在一个单独的线程中
(5) Opengl的运行周期与Activity的生命周期可以协调
下面我们再看看利用GLSurface画3D图形的一个典型的Sequence
(1) 选择你的EGL配置(就是你画图需要的buffer类型) [optional] :
setEGLConfigChooser(boolean)
setEGLConfigChooser(EGLConfigChooser)
setEGLConfigChooser(int, int, int, int, int, int)
(2) 选择是否需要Debug信息 [optional] :
setDebugFlags(int)
setGLWrapper(GLSurfaceView.GLWrapper).
(3) 为GLSurfaceView注册一个画图的renderer : setRenderer(GLSurfaceView.Renderer)
(4) 设置reander mode,可以为持续渲染或者根据命令 来渲染,默认是continuous rendering [optional]: setRenderMode(int)
这里有一个要注意的地方就是必须将Opengl的运行和Activity的生命周期绑定在一起,也就是说Activity pause的时候,opengl的渲染也必须pause。另外GLSurfaceView还提供了一个非常实用的线程间交互的函数 queueEvent(Runnable),可以用在主线程和render线程之间的交互,下面就是SDK提供的范例:
class MyGLSurfaceView extends GLSurfaceView {
private MyRenderer mMyRenderer;
public void start() {
mMyRenderer = ...;
setRenderer(mMyRenderer);
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
queueEvent(new Runnable() {
// This method will be called on the rendering
// thread:
public void run() {
mMyRenderer.handleDpadCenter();
}});
return true;
}
return super.onKeyDown(keyCode, event);
}
}
GLSurfaceView是Android提供的一个非常值得学习 的类,它实际上是一个如何在View中添加画图线程的例子,如何在Java 中使用线程的例子,如何添加事件队列的例子,一个使用SurfaceView画图的经典Sequence,一个如何定义Debug信息的例子,觉得把它看懂了可以学到很多知识 ,具体的源码在:/framworks/base/opengl/java/android/opengl/GLSurfaceView.java 。
3D的内容基本到这里基本讲完了,剩下的主要是如何使用Opengl API的问题了,可以看看API demo中简单的立方体,复杂的可以看看它那个魔方的实现。下面我们总结一下3D画图需要用到的包:
Android.opengl //主要定义了GLSurfaceView
javax.microedition.khronos.egl //java层的egl接口
javax.microedition.khronos.opengles //opengl API
三、了解了2D和3D基本的画图方法,我们再回过头来看看整个Android对Opengl和Skia的调用层次关系
3.1、首先来看2D,2D是主要使用的图形引擎,毕竟3D受制于其过高的硬件要求在手机上使用还是比较少,而且Skia也能部分实现类似于3D的效果, 因此可以说SKia实现了Android平台上绝大多数的图形工作。下面我们来看看从应用层到底层对skia的调用关系:
Android对skia的调用是一个比较经典 的调用过程,应用程序的几个包是在SDK中提供的;JNI放在框架的JNI目录下面的Graphic目录;skia是作为一个第三方组件放在external目录下面。我们可以稍微了解一下skia的结构:
这里主要涉及到的3个库:
libcorecg.so 包含/skia/src/core的部分内容,比如其中的Region,Rect是在SurfaceFlinger里面计算可是区域的操作基本单位
libsgl.so 包含/skia/src/core|effects|images|ports|utils的部分和全部内容,这个实现了skia大部分的图形效果,以及图形格式的编解码
libskiagl.so 包含/skia/src/gl里面的内容,主要用来调用opengl实现部分效果
另外我看到/skia/src中有两个目录animator和view没有写入makefile的编译路径中,我觉得这两个目录是很重要的,不知道是现在Android还没使用到,还是用其他的方式加载进去的。
要想在底层使用skia的接口来画图需要全面了解skia的一整套机制,实际上skia开源到现在还没多久,在网上能找到的资料是也是很粗浅的,如果将来真需要在这方面下功夫肯定是需要一定的工作量的。
3.2、Android对3D的调用曾经让我迷惑了一段时间,因为在framewoks/base/core/jni这个目录一直没找到跟opengl相 关的内容,后面去仔细看看opengl里面的内容才知道Android把opengl的本地实现,JNI,java接口都放在/frameworks /base/opengl下面了,而且它内部还带了一个工具可以生成JNI代码。
我们来看看opengl的目录结构:
/include 包含egl和gles所有的头文件
/java/android/opengl 这个目录包含的就是我们3D画图要使用到的GLSurfaceView
/java/com/google/android/gles_jni 这个目录包含一些自动生成的文件
/java/javax/microedition/khronos/egl 这就是应用层使用到的egl接口
/java/javax/microedition/khronos/opengl 这就是应用层使用到的opengl接口
/libagl 这个就是opengl主要的实现了
/libs 这里面包含两个库的实现,一个是libegl.so 还有一个是libGL|ES_CM.so
/tools 在我的理解这就是一个jni的生成工具
Opengl编程谁都知道是一个大工程,我觉得现在对3D的需求应该是很低的,很多效果我们使用skia也可以实现。所以我觉得将来的重点应该还是放在skia上面。
http://oss.org.cn/ossdocs/gnu_linux/lfs/lfs-5.0/appendixa/binutils.html
Binutils 是一组开发工具,包括连接器,汇编器和其他用于目标文件和档案的工具。
安装下列程序: addr2line, ar, as, c++filt, gprof, ld, nm, objcopy, objdump, ranlib, readelf, size, strings 和 strip
安装下列库文件: libiberty.a, libbfd.[a,so] 和 libopcodes.[a,so]
addr2line 把程序地址转换为文件名和行号。在命令行中给它一个地址和一个可执行文件名,它就会使用这个可执行文件的调试信息指出在给出的地址上是哪个文件以及行号。
ar 建立、修改、提取归档文件。归档文件是包含多个文件内容的一个大文件,其结构保证了可以恢复原始文件内容。
as 主要用来编译GNU C编译器gcc输出的汇编文件,产生的目标文件由连接器ld连接。
c++filt 连接器使用它来过滤 C++ 和 Java 符号,防止重载函数冲突。
gprof 显示程序调用段的各种数据。
ld 是连接器,它把一些目标和归档文件结合在一起,重定位数据,并链接符号引用。通常,建立一个新编译程序的最后一步就是调用ld。
nm 列出目标文件中的符号。
objcopy把一种目标文件中的内容复制到另一种类型的目标文件中.
objdump 显示一个或者更多目标文件的信息。显示一个或者更多目标文件的信息。使用选项来控制其显示的信息。它所显示的信息通常只有编写编译工具的人才感兴趣。
ranlib 产生归档文件索引,并将其保存到这个归档文件中。在索引中列出了归档文件各成员所定义的可重分配目标文件。
readelf 显示ebf格式可执行文件的信息。
size 列出目标文件每一段的大小以及总体的大小。默认情况下,对于每个目标文件或者一个归档文件中的每个模块只产生一行输出。
strings 打印某个文件的可打印字符串,这些字符串最少4个字符长,也可以使用选项-n设置字符串的最小长度。默认情况下,它只打印目标文件初始化和可加载段中的可打印字符;对于其它类型的文件它打印整个文件的可打印字符,这个程序对于了解非文本文件的内容很有帮助。
strip 丢弃目标文件中的全部或者特定符号。
libiberty 包含许多GNU程序都会用到的函数,这些程序有: getopt, obstack, strerror, strtol 和 strtoul.
libbfd 二进制文件描述库.
libopcodes 用来处理opcodes的库, 在生成一些应用程序的时候也会用到它, 比如objdump.Opcodes是文本格式可读的处理器操作指令.
Binutils 依赖于: Bash, Coreutils, Diffutils, GCC, Gettext, Glibc, Grep, Make, Perl, Sed, Texinfo.
http://www.armfans.net/thread-2536-1-1.html
Android OpenGL ES 分析与实践
1. OpenGL ES 简介
Android 3D引擎采用的是OpenGL ES。OpenGL ES是一套为手持和嵌入式系统设计的3D引擎API,由Khronos公司维护。在PC领域,一直有两种标准的3D API进行竞争,OpenGL 和 DirectX。一般主流的游戏和显卡都支持这两种渲染方式,DirectX在Windows平台上有很大的优势,但是OpenGL具有更好的跨平台性。
由于嵌入式系统和PC相比,一般说来,CPU、内存等都比PC差很多,而且对能耗有着特殊的要求,许多嵌入式设备并没有浮点运算协处理器,针对嵌入式系统的以上特点,Khronos对标准的OpenGL系统进行了维护和改动,以期望满足嵌入式设备对3D绘图的要求。
2. Android OpenGL ES简介
Android系统使用OpenGL的标准接口来支持3D图形功能,android 3D图形系统也分为java框架和本地代码两部分。本地代码主要实现的OpenGL接口的库,在Java框架层,javax.microedition.khronos.opengles是java标准的OpenGL包,android.opengl包提供了OpenGL系统和Android GUI系统之间的联系。
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.1 OpenGL ES测试代码
frameworks/base/opengl/tests下有OpenGL的本地测试代码。包括angeles、fillrate等14个测试代码,这些代码都可以通过终端进行本地调用测试(模拟器中使用adb shell)。在本文中,主要使用了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.2 OpenGL ES的编译
编译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 使用OpenGL ES画图必经的步骤
1、获取Display,Display代表显示器。
函数原型:
EGLDisplay eglGetDisplay(NativeDisplayType display);
display参数是native系统的窗口显示ID值,一般为 EGL_DEFAULT_DISPLAY 。该参数实际的意义是平台实现相关的,在X-Window下是XDisplay ID,在MS Windows下是Window DC。
2、初始化egl库。
函数原型:
EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);
其中dpy应该是一个有效的 EGLDisplay 。函数返回时,major和minor将被赋予当前EGL版本号。
3、选择一个合适的EGL Configuration FrameBuffer,实际指的是FrameBuffer的参数
函数原型:
EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list,EGLConfig *configs, EGLint config_size,
EGLint *num_config);
参数attrib_list:指定了选择配置时需要参照的属性。
参数configs: 将返回一个按照attrib_list排序的平台有效的所有EGL framebuffer配置列表。
参数config_size:指定了可以返回到configs的总配置个数。
参数num_config: 返回了实际匹配的配置总数。
4、创建一个可实际显示的EGLSurface,实际上就是一个FrameBuffer
函数原型:
EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config,
NativeWindowType win,
const EGLint *attrib_list);
5、创建Context
函数原型:
EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config,
EGLContext share_context,
const EGLint *attrib_list);
6、绑定Display、Surface、Context
函数原型:
EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw,
EGLSurface read, EGLContext ctx);
3.4 OpenGL ES 执行过程
运行android操作系统之后,输入logcat命令,然后执行gltest中的test-opengl-tritex,屏幕上打印了以下信息
D/libEGL ( 1962): egl.cfg not found, using default config
D/libEGL ( 1962): loaded /system/lib/egl/libGLES_android.so
可以看出,在执行OpenGL调用的过程中,会自动加载libGLES_android.so动态链接库。后面将会通过分析和修改源码的方式,了解OpenGL ES系统的调用过程。
通过3.3中的说明,我们在tritex测试程序中插入一些调试信息,查看OpenGL ES的调用过程。
在调用eglGetDisplay之前会执行early_egl_init函数,这是一个静态的函数。
在eglGetDisplay中会去初始化驱动,最终调用到egl_init_drivers_locked函数中。这个函数的主要内容如下
EGLBoolean egl_init_drivers_locked()
{
if (sEarlyInitState) {
// initialized by static ctor. should be set here.
return EGL_FALSE;
}
// get our driver loader
Loader& loader(Loader::getInstance());
// dynamically load all our EGL implementations for all displays
// and retrieve the corresponding EGLDisplay
// if that fails, don't use this driver.
// TODO: currently we only deal with EGL_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) {
EGLDisplay dpy = cnx->egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
LOGE_IF(dpy==EGL_NO_DISPLAY, "No EGLDisplay for software EGL!");
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) {
char value[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) {
EGLDisplay dpy = cnx->egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
LOGE_IF(dpy==EGL_NO_DISPLAY, "No EGLDisplay for hardware EGL!");
d->disp[IMPL_HARDWARE].dpy = dpy;
if (dpy == EGL_NO_DISPLAY) {
loader.close(cnx->dso);
cnx->dso = NULL;
}
}
} else {
LOGD("3D hardware acceleration is disabled");
}
}
if (!gEGLImpl[IMPL_SOFTWARE].dso && !gEGLImpl[IMPL_HARDWARE].dso) {
return EGL_FALSE;
}
return EGL_TRUE;
}
由此代码可以看出,egl_init_drivers_locked函数主要的工作就是填充gEGLImp数组变量,这个变量是egl_connection_t类型。还有一个工作就是填充gDisplay数组(只有一个元素)的disp[IMPL_HARDWARE].dpy以及disp[IMPLSOFTWAREWARE].dpy,填充的来源来自gEGLImpl【soft or hard】.egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
在Loader.cpp中的Loader:pen中会加载对应的硬件和软件加速的驱动(动态链接库)。软件的对应的是/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,
struct egl_connection_t
{
void * dso;
gl_hooks_t * hooks[2];
EGLint major;
EGLint minor;
egl_t egl;
};到处都有他的身影,对这几个变量进行一下解释。
struct soinfo
{
const char name[SOINFO_NAME_LEN];
Elf32_Phdr *phdr;
int phnum;
unsigned entry;
unsigned base;
unsigned size;
// buddy-allocator index, negative for prelinked libraries
int ba_index;
unsigned *dynamic;
unsigned wrprotect_start;
unsigned wrprotect_end;
soinfo *next;
unsigned flags;
const char *strtab;
Elf32_Sym *symtab;
unsigned nbucket;
unsigned nchain;
unsigned *bucket;
unsigned *chain;
unsigned *plt_got;
Elf32_Rel *plt_rel;
unsigned plt_rel_count;
Elf32_Rel *rel;
unsigned rel_count;
unsigned *preinit_array;
unsigned preinit_array_count;
unsigned *init_array;
unsigned init_array_count;
unsigned *fini_array;
unsigned fini_array_count;
void (*init_func)(void);
void (*fini_func)(void);
#ifdef ANDROID_ARM_LINKER
/* ARM EABI section used for stack unwinding. */
unsigned *ARM_exidx;
unsigned ARM_exidx_count;
#endif
unsigned refcount;
struct link_map linkmap;
};
看一下load_driver中到底做了什么手脚。
1.首先调用dlopen打开动态链接库,返回值是void*,这个void*指向的是什么内容呢?追踪到bionic/linker/Dlfcn.c中。其中调用了find_library函数,这个函数是一个奇怪的函数,因为它虽然叫做find_library,在其实现中,不但在系统的so链表中去查找指定的文件名的动态链接库信息,而且对其动态链接库进行加载并返回。至此我们明白了,这个void* 指向的是一个soinfo类型的结构体
这是man dlopen的说明。一个标准的linux函数。
The function dlopen() loads the dynamic library file named by the null-
terminated string filename and returns an opaque "handle" for the
dynamic library. If filename is NULL, then the returned handle is for
the main program. If filename contains a slash ("/"), then it is
interpreted as a (relative or absolute) pathname.
2. 由上一步的分析,我们知道了egl_connection_t的第一个变量dso,是指向的一个soinfo结构体(discover/decompress shared
object的缩写???)
Printf("HAHA Let me print the so infomation\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的返回值,查找第二个参数指定的函数名的地址并返回
The function dlsym() takes a "handle" of a dynamic library returned by
dlopen() and the null-terminated symbol name, returning the address
where that symbol is loaded into memory. If the symbol is not found,
in the specified library or any of the libraries that were automati-
cally loaded by dlopen() when that library was loaded, dlsym() returns
NULL. (The search performed by dlsym() is breadth first through the
dependency tree of these libraries.) Since the value of the symbol
could actually be NULL (so that a NULL return from dlsym() need not
indicate an error), the correct way to test for an error is to call
dlerror() to clear any old error conditions, then call dlsym(), and
then call dlerror() again, saving its return value into a variable, and
check whether this saved value is not NULL.
getProcAddress = (getProcAddressType)dlsym(dso, "eglGetProcAddress");
Printf("eglGetProcAddress's location is %x\n",getProcAddress);
打印信息如下,可以和刚才的打印信息比较一下。我们确实找到了一个函数。
eglGetProcAddress's location is acc930b1
Printf("curr=%x,it's address is %x\n",curr,f);
打印如下
eglGetProcAddress's location is acc930b1
*api=eglGetDisplay
curr=ac708a60,it's address is acc931a5
*api=eglInitialize
curr=ac708a64,it's address is acc93c9d
*api=eglTerminate
curr=ac708a68,it's address is acc93cdd
*api=eglGetConfigs
curr=ac708a6c,it's address is acc93d41
*api=eglChooseConfig
curr=ac708a70,it's address is acc9472d
*api=eglGetConfigAttrib
curr=ac708a74,it's address is acc94325
*api=eglCreateWindowSurface
curr=ac708a78,it's address is acc94689
*api=eglCreatePixmapSurface
curr=ac708a7c,it's address is acc945d5
*api=eglCreatePbufferSurface
curr=ac708a80,it's address is acc9451d
*api=eglDestroySurface
curr=ac708a84,it's address is acc93a1d
*api=eglQuerySurface
curr=ac708a88,it's address is acc94341
*api=eglCreateContext
curr=ac708a8c,it's address is acc9415d
*api=eglDestroyContext
curr=ac708a90,it's address is acc93d09
*api=eglMakeCurrent
curr=ac708a94,it's address is acc93a6d
*api=eglGetCurrentContext
curr=ac708a98,it's address is acc93055
*api=eglGetCurrentSurface
curr=ac708a9c,it's address is acc941a1
*api=eglGetCurrentDisplay
curr=ac708aa0,it's address is acc93061
*api=eglQueryContext
curr=ac708aa4,it's address is acc942ed
*api=eglWaitGL
curr=ac708aa8,it's address is acc9307d
*api=eglWaitNative
curr=ac708aac,it's address is acc93081
*api=eglSwapBuffers
curr=ac708ab0,it's address is acc93bf5
*api=eglCopyBuffers
curr=ac708ab4,it's address is acc93d71
*api=eglGetError
curr=ac708ab8,it's address is acc93125
*api=eglQueryString
curr=ac708abc,it's address is acc9373d
*api=eglGetProcAddress
curr=ac708ac0,it's address is acc930b1
*api=eglSurfaceAttrib
curr=ac708ac4,it's address is acc93d89
*api=eglBindTexImage
curr=ac708ac8,it's address is acc93da5
*api=eglReleaseTexImage
curr=ac708acc,it's address is acc93dc1
*api=eglSwapInterval
curr=ac708ad0,it's address is acc93ddd
*api=eglBindAPI
curr=ac708ad4,it's address is acc93df9
*api=eglQueryAPI
curr=ac708ad8,it's address is acc93085
*api=eglWaitClient
curr=ac708adc,it's address is acc930e5
*api=eglReleaseThread
curr=ac708ae0,it's address is acc9308d
*api=eglCreatePbufferFromClientBuffer
curr=ac708ae4,it's address is acc941e5
*api=eglLockSurfaceKHR
curr=ac708ae8,it's address is acc93091
*api=eglUnlockSurfaceKHR
curr=ac708aec,it's address is acc93095
*api=eglCreateImageKHR
curr=ac708af0,it's address is acc94201
*api=eglDestroyImageKHR
curr=ac708af4,it's address is acc93e15
*api=eglSetSwapRectangleANDROID
curr=ac708af8,it's address is acc93c51
*api=eglGetRenderBufferANDROID
curr=ac708afc,it's address is acc94125
egl_connection_t的第二个变量是一个指针数组,类型是gl_hooks_t,从名字可以看出,它指向的是一组函数指针。跟踪一下
struct gl_hooks_t {
struct gl_t {
#include "entries.in"
} gl;
struct gl_ext_t {
void (*extensions[MAX_NUMBER_OF_GL_EXTENSIONS])(void);
} ext;
};
这个entries.ini文件里全部是函数的一些原型。。
证明了猜想。
cnx->hooks[GLESv1_INDEX] = &gHooks[GLESv1_INDEX][IMPL_SOFTWARE];
cnx->hooks[GLESv2_INDEX] = &gHooks[GLESv2_INDEX][IMPL_SOFTWARE];
这里将egl_connecttion_t变量指向了全局的gHooks,这些函数指针从哪里赋值的呢?跟踪发现,是在LoadDriver时,也是从
libGLES_android.so中查找出GLESV1_CM和GLESV2两组函数来对其进行了赋值操作。
major和minor是版本号。
最后一个变量egl_t egl。这个变量非常重要。在load_driver中可以看到它的身影(通过loader::open间接调用的)。
struct egl_t {
#include "EGL/egl_entries.in"
};
egl_t中也是一组函数指针,其中包含了OpenGL ES中底层的实现。所以如果要实现硬件加速的话,这里面的函数都要实现。
egl_t* egl = &cnx->egl;
__eglMustCastToProperFunctionPointerType* curr =
(__eglMustCastToProperFunctionPointerType*)egl;
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++;
}
上面的这些语句完成了将驱动(libGLES_android.so)中的函数地址赋值给了cnx->egl指向的egl_t结构。因此有了这些操作,下面
就可以通过egl_connection_t::egl来访问EGL中的一些底层的函数了,比如初始化等的操作。
要实现硬件加速,需要实现的是与libGLES_android.so对应的那些函数。android会自动完成对其的加载、初始化、调用。
上面的一大部分功能都是在loader:pen中进行调用的,回到egl_init_drivers_locked函数中。
其在调用了loader:pen函数完成了对libGLES_android.so中的库进行加载的操作之后,真正开始 初始化操作了,所以说上面的一大部分操作,其实都是一些预初始化,真正的初始化操作还没有开始。。。。
真正的初始化操作来自这里EGLDisplay dpy = cnx->egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
通过刚才的分析,我们很容易知道,这个egl.eglGetDisplay函数到底要去哪里找。有bet。
我们上面打印出了
*api=eglGetDisplay
curr=ac708a60,it's address is acc931a5
这个东东在libGLES_android.so的源码里。
eglGetDisplay在内部的实现并不是通过返回值的方式进行传递的,而是用的全局变量。
eglGetDisplay完成的事情主要有,加载真正的OpenGL ES 的动态链接库文件,里面包含各种openGL的操作,这个函数库在libGLES_andorid.so中,加载动态链接库之后,会将其中的一些函数指针的值传递到全局的gEGLImpl中,通过这个全局的结构体,可以访问libGLES_android.so中的所有的函数。
下面分析应用程序中调用eglInitialize函数,会完成的操作。
eglInitialize
在这个函数中,主要调用了libGLES_android中的eglIniitalize函数,eglGetConfigs函数,获取配置信息。
3.5 使用OpenGL ES硬件加速功能的方法
使用硬件加速功能的OpenGL ES的方法。
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
修改后腰重启才可生效。
现在已经可以使用硬件加速功能了。
Test OpenGL and OpenVG performance; Game using OpenGL
http://forum.xda-developers.com/archive/index.php/t-511363.html
很多硬件加速性能的对比