Android如何加载硬件OpenGLES库

本文介绍了Android系统如何加载硬件OpenGLES库,包括libEGL.so、libGLESv1_CM.so和libGLESv2.so的作用。详细阐述了libEGL库加载EGL和OpenGLES API的过程,涉及TLS的初始化、egl.cfg配置文件的使用以及软硬件库的选择。同时讨论了eglMakeCurrent()在TLS中的作用,以及如何通过TLS调用OpenGLES API。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注:文章部分引用自http://blog.youkuaiyun.com/andyhuabing/article/details/7176049

最近需要移植一个硬件的GPU 3D功能到Android系统里,哪么学习Android与OpenGLES的关系哪是必需的了,首先就是如何加载软/硬OpenGLES的库。

1:首先在没有加进硬件3D代码之前,Android默认使用的是软件OpenGLES API的库,也就是libGLES_android 。其中Android内部依赖以下三个库去加载管理OpenGLES API。

1):libEGL.so

EGL是用来管理绘图表面的(Drawing surfaces),并且提供了如下的机制
(1)与本地窗口系统进行通信
(2)查找绘图表面可用的类型和配置信息
(3)创建绘图表面
(4)同步OpenGL ES 2.0和其他的渲染API(Open VG、本地窗口系统的绘图命令等)
(5)管理渲染资源,比如材质

EGL 和 OpenGL ES API的联系
(1)通过解析OpenGL ES API函数库 libGLES_android.so来获取函数指针,进行调用。
(2)通过线程局部存储机制进行联系。

其实Android自身的libEGL并非真正的EGL API,libEGL具有加载管理OpenGLES库,并提供真正EGL库 API的封装。

例如libGLES_android.so里就有EGL API的真正实现:frameworks/base/opengl/libagl/egl.cpp。

而硬件3D的EGL API的实现通常单独编译成一个库,例如GPU Vivante厂商的EGL API库一般就命名为libEGL_VIVANTE.so 。

 

2):libGLESv1_CM.so,libGLESv2.soz

这两个库都是对OpenGLES API的封装,其中libGLESv1_CM.so是对OpenGLES1.0库的封装,libGLESV2.so是对OpenGLES2.0库的封装。这两个库封装的对像是OpenGLES API的实现,所以用软件OpenGLES的话,部分API的实现可以在libGLES_android.so中实现,当然在frameworks/base/opengl/libagl/目录下会有原码。而硬件OpenGLES的话,API的实现一般会编译成库,例如GPU Vivante厂商的OpenGLES1.0的API库一般就命名为libGLESv1_CM_VIVANTE.so,OpenGLES2.0的API库一般命名为libGLESv2_VIVANTE.so 。

 

3):Android应用或者JNI的一层将会链接上 libEGL.so和libGLESv1_CM.so/libGLESv2.so,链接libGLESv1_CM.so/libGLESv2.s具体要看是用OpenGLES1.0还是2.0。例如frameworks/base/opengl/tests/tritex下的Android.mk就会有如下: 

LOCAL_SHARED_LIBRARIES := libcutils libEGL libGLESv1_CM libui

 

2:接下来说一下libEGL这个库是怎么样去加载EGL和OpenGLES的API的。

1):首先看看libEGL库的实现原码中的frameworks/base/opengl/libs/EGL/egl.cpp。

在libEGL的调用过程中首先eglGetDisplay()->egl_init_drivers()->egl_init_drivers_locked()。

egl_init_drivers_locked()函数完成的事情主要有如下:

(1):完成TLS的初始化。

static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static int sEarlyInitState = pthread_once(&once_control, &early_egl_init);

第二条语句中将函数指针early_egl_init作为参数传入,会执行回调,并且保证单个线程只会执行一次。在early_egl_init()中,对TLS机制进行初始化。将TLS里放入一个结构体指针,这个指针指向gHooksNoContext(gl_hooks_t类型),这个结构体里的每个函数指针被初始化为了gl_no_context。也就是现在如果通过TLS调用的OpenGL ES API都会调到gl_no_context这个函数中。

其中这里使用到了TLS的技术,其原理就是:为每一个使用该全局变量的线程都提供一个变量值的副本,每一个线程均可以独立地改变自己的副本,而不会和其它线程的副本冲突,在arm的协处理器中已支持硬件的TLS,另外软件也可以实现。

在frameworks/base/opengl/libs/hooks.h里的setGlThreadSpecific(),和getGlThreadSpecific()都使用到了Android C库bionic中的__get_tls()函数,arm芯片内核中的实现在arch\arm\kernel\entry-armv.S __kuser_get_tls,并通过kuser_get_tls_init()把TLS数据提供到用户空间。

(2):加载软硬件OpenGLES和EGL的API库

加载的类主要实现在frameworks/base/opengl/libs/EGL/Loader.cpp 。

在egl_init_drivers_locked()函数中   

// get our driver loader
Loader& loader(Loader::getInstance());

new了loader对像,并自动调用了构造函数Loader::Loader(),该函数内部将会检查/system/lib/egl/egl.cfg文件。

这里是根据配置文件加载硬件3D库,egl.cfg 文件格式是“dpy impl tag”比如自己添加的硬件加速库是libGLES_VIVANTE.so,则需要在此文件里这样编写0 1VIVANTE。另外也可以同时存在着软件和硬件加速库,其egl.cf可写成

0 0 android

0 1 VIVANTE

impl为0代表着使用软件加速库,1代表使用硬件加速库。

tag代表着实现库的后辍,所以加速库的名字不能乱取。另外通过Loader::Loader()分析,加速库可编译到一个so文件,这个so文件将包含EGL | GLESv1_CM | GLESv2三部分API的实现,例如libGLES_VIVANTE.so。另外也可以分别编译到三个so文件,例如libEGL_VIVANTE.so,libGLESv1_CM_VIVANTE.so,libGLESv2_VIVANTE.so。这些硬件的so库都是由GPU厂商提供的。

(3):给软硬件的egl_connection_t结构体赋值,并通过egl_t结构体访问EGL API库中的egl.eglGetDisplay。

这一步通过cnx->dso = loader.open(EGL_DEFAULT_DISPLAY, 0, cnx);来加载各个so库。

在Loader::load_driver中主要使用dlopen(),dlsym()函数来完成so库的加载和egl_t及gl_hooks_t结构体指针的赋值。其中使用了以下结构体

char const * const gl_names[] = {
    #include "entries.in"
    NULL
};

char const * const egl_names[] = {
    #include "egl_entries.in"
    NULL
};

填充了以下结构体的指针

struct egl_t {
    #include "EGL/egl_entries.in"
};

struct gl_hooks_t {
    struct gl_t {
        #include "entries.in"
    } gl;
    struct gl_ext_t {
        __eglMustCastToProperFunctionPointerType extensions[MAX_NUMBER_OF_GL_EXTENSIONS];
    } ext;
};

&cnx->egl及&cnx->hooks[GLESv1_INDEX]->gl和&cnx->hooks[GLESv2_INDEX]->gl。

也就是完成了给软硬件的egl_connection_t(cnx)结构体赋值。值得注意的是struct gl_ext_t结构体部分并没有在loader.open()调用的过程中赋值,也就是说OpenGLES的extension API没有赋值,有兴趣的可以研究一下egl.cpp下的eglGetProcAddress()下的函数,看看是怎么对扩展API赋值的。

另外在在egl_init_drivers_locked()函数中  EGLDisplay dpy = cnx->egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);

显然访问了EGL库的eglGetDisplay API。

 

4);这里说说我的想法,TLS技术就是通过软件或者硬件给每个线程提供一个存储访问数据的空间。在early_egl_init和eglInitialize函数中这个存入的是static int gl_no_context()这个函数指针,而在eglMakeCurrent函数中存放的是egl_connection_t结构体成员struct gl_hooks_t的指针cnx->hooks。其原理和bionic中的errno一样。

 

3:库也加载了,OpenGLES API也放在了EGLAPI gl_hooks_t gHooks[2][IMPL_NUM_IMPLEMENTATIONS];这里了。剩下的也就是把struct gl_hooks_t结构体指针也就是EGLAPI gl_hooks_t gHooks存放到TLS里,并实现从TLS中取回struct gl_hooks_t结构体指针,然后指针作偏移指向我们需要的OpenGLES API。

1);在frameworks/base/opengl/libs/EGL/egl.cpp中的eglMakeCurrent()函数中setGlThreadSpecific(c->cnx->hooks[c->version]);就是把struct gl_hooks_t结构体指针存放到TLS中。其中c->version指的是GLESv1_INDEX(OpenGLES1.0)还是GLESv2_INDEX(OpenGLES2.0),这里还不清楚这个c->version是如何被指写的,还需要学习一下。

2):剩下的就是如何从TLS中读回来了。

哪么这个功能就依赖于libGLESv1_CM.so或者libGLESv2.so了。这里以libGLESv1_CM.so以就是OpenGLES1.0为例,2.0的其实一样。

从libGLESv2.so的原码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.

 

extern "C" {
#include "gl_api.in"
#include "glext_api.in"
}

CALL_GL_API这个带参数的宏。它的意思是获取TLS_SLOT_OPENGL_API的TLS,如果它的值不是NULL,就跳到相应的OpenGL ES API("ldrne pc,  [r12, %[api]] \n" )的地址去执行。这个地方为什么会跳过去呢??因为从线程局部存储保存的线程指针,指向了gl_hooks_t指针。

其对应的C接口为

#include <GLES/gl.h>
#include <GLES/glext.h>

而OpenGLES2.0API对应的C接口为

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

值得注意的是EGL的API只是在android libEGL.so里的实现egl.cpp里直接使用egl_t egl结构体进行调用。其对应的C头文件为:

#include <EGL/egl.h>
#include <EGL/eglext.h>

 

4:这里还有些问题还不清楚。

1):回到前面

EGL 和 OpenGL ES API的联系
(1)通过解析OpenGL ES API函数库 libGLES_android.so来获取函数指针,进行调用。
(2)通过线程局部存储机制进行联系。

为什么OpenGLES API的时候要使用TLS的技术呢?难道这些API不是可重载的?

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值