OpenGL ES EGL & TLS(线程局部存储) & G3D

图形学与OpenGL 2010-08-11

1. 什么是EGL

EGL是用来管理绘图表面的(Drawingsurfaces),并且提供了如下的机制

(1)与本地窗口系统进行通信

(2)查找绘图表面可用的类型和配置信息

(3)创建绘图表面

(4)同步OpenGLES2.0和其他的渲染API(OpenVG、本地窗口系统的绘图命令等)

(5) 管理渲染资源,比如材质

2. EGL 和 OpenGL ES API的联系

(1)通过解析OpenGLESAPI函数库libGLES_android.so来获取函数指针,进行调用。

(2) 通过线程局部存储机制进行联系

关于通过函数指针进行联系在前面已经分析过了。下面着重分析通过线程局部存储机制进行联系分析一下。

2.1什么是线程局部存储(TLS)

TLS让多线程程序设计更容易一些。TLS是一个机制,经由它,程序可以拥有全域变量,但处于「每一线程各不相同」的状态。也就是说,进程中的所有线程都可以拥有全域变量,但这些变量只对特定对某个线程才有意义。http://xianjunzhang.blog.sohu.com/21537031.html

2.2TLS的好处

你可能有一个多线程程序,每一个线程都对不同的文件写文件(也因此它们使用不同的文件handle)。这种情况下,把每一个线程所使用的文件handle储存在TLS中,将会十分方便。当线程需要知道所使用的handle,它可以从TLS获得。重点在于:线程用来取得文件handle的那一段代码在任何情况下都是相同的,而从TLS中取出的文件handle却各不相同。非常灵巧,不是吗?有全域变数的便利,却又分属各线程。http://xianjunzhang.blog.sohu.com/21537031.html

2.3OpenGLES中的TLS

2.3.1TLS的初始化

应用程序通过EGL调用eglGetDisplay()函数时,会调用到libEGL.so中,通过看其源码egl.so可以发现,其中有条语句

staticpthread_once_tonce_control=PTHREAD_ONCE_INIT;

staticintsEarlyInitState=pthread_once(&once_control,&early_egl_init);

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

综上,这两条语句完成了TLS的初始化。

另一处初始化时在eglInitialize函数中,同样设置成了gHooksNoContext。

2.3.2TLS的赋值

在eglMakeCurrent中,会将渲染上下文绑定到渲染面。在EGL中首先会处理完一系列和本地窗口系统的变量后,调用libGLES_android.so中的eglMakeCurrent,调用成功的话会设置TLS。将TLS指针指向前面已经初始化化好的gl_hooks_t结构体指针,这个结构体里的成员都已经指向了libGLES_android.so中的OpenGLAPI函数,至此EGL的大部分初始化工作就已经完成了。基本就可以使用OpenGLESAPI进行绘图了。

staticinlinevoidsetGlThreadSpecific(gl_hooks_tconst*value){

gl_hooks_tconst*volatile*tls_hooks=get_tls_hooks();

tls_hooks[TLS_SLOT_OPENGL_API]=value;

}

3. 调用OpenGL ES API函数

在通过EGL对本地窗口系统做一系列初始化之后,就需要调用真正的OpenGLESAPI进行3D绘图了,对于很多没有接触过OpenGL和计算机图形学的人来说,这部分可能是比较困难的(很多算法我都不懂。。。)。下面脱离具体的算法实现,看看要调用OpenGLESAPI,Android的3D系统做了什么。

具体分析可以参考Android源码中OpenGLES的测试代码。

在应用程序中要调用一个OpenGLESAPI,需要包含对应的头文件。比如OpenGLES1.1对应的头文件是<GLES/gl.h>。但是在具体的执行中,调用到了那个库中呢?

分析tests中的tritex测试案例。它的Andoird.mk是这样的。

LOCAL_PATH:=$(callmy-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

#weneedtoaccesstheprivateBionicheader<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中,实际上这些函数的定义。

……

voidAPI_ENTRY(glClearColor)(GLclampfred,GLclampfgreen,GLclampfblue,GLclampfalpha){

CALL_GL_API(glClearColor,red,green,blue,alpha);

}

voidAPI_ENTRY(glClearDepthf)(GLclampfdepth){

CALL_GL_API(glClearDepthf,depth);

}

……

gl.cpp中去掉条件编译

#defineGET_TLS(reg)\

"mov"#reg",#0xFFFF0FFF\n"\

            "ldr   " #reg ", [" #reg ", #-15] \n"

#define API_ENTRY(_api) __attribute__((naked)) _api

#define CALL_GL_API(_api, ...)                              \

asmvolatile(\

GET_TLS(r12)\

"ldrr12,[r12,%[tls]]\n"\

"cmpr12,#0\n"\

"ldrnepc,[r12,%[api]]\n"\

"bxlr\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__)\

return0;//placategcc'swarnings.neverreached.

CALL_GL_API这个带参数的宏。它的意思是获取TLS_SLOT_OPENGL_API的TLS,如果它的值不是NULL,就跳到相应的OpenGLESAPI的地址去执行。这个地方为什么会跳过去呢??

因为从线程局部存储保存的线程指针,指向了一个gl_hooks_t指针,而这个指针指向的结构体里的成员已经在EGL中被初始化为了libGLES_android.so里的函数地址了。所以就可以跳过去了。

从以上分析可以看出libGLESv1_CM.so只是一个wrapper,对OpenGL ES API进行了一个简单的包裹,真正的实现还是在libGLES_andoird.so中的。对于libGLESV2.so是同样的道理。

这样做的好处:

1.两套OpenGLESAPI对应一个实现库,便于维护

2.可以对OpenGLESAPI进行更改,而不需要改变对外的接口,易于程序移植和兼容。

4.需要注意的问题

OpenGL ES API很多函数并没有实现。软件加速库不支持OpenGL ES2.0

Loader.cpp中的init_api函数通过dlsym函数,对so文件进行解析,返回了函数的指针,在对每个函数进行跟踪的过程中发现,原来glShaderSource并没有在openGL ES的源码中实现,而且发现很多函数都没有在OpenGL ES的源码中实现。

I/libagl(2445):ineglChooseConfig

I/libagl(2445):ineglCreateWindowSurface

I/libagl(2445):ineglCreateContext

I/libagl(2445):ineglMakeCurrent

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL(2445):calledunimplementedOpenGLESAPI

E/libEGL ( 2445): called unimplemented OpenGL ES API

5.学习OpenGLES应用程序,了解基本的OpenGLESAPI

学习OpenGLESAPI是为了了解基本的API函数的工作原理和一些3D术语,从而配合三星s3c6410(0718)文档,实现自己的OpenGLES硬件加速库。

(1)VertexArrays/BufferObjects

(2)VertexShader

实现对定点通用的一些操作

Vertexshaderscanbeusedfortraditionalvertex-basedoperationssuchastransformingthepositionbyamatrix,computingthelightingequationtogenerateaper-vertexcolor,andgeneratingortransformingtexturecoordinates.Alternately,becausethevertexshaderisspecifiedbytheapplication,vertexshaderscanbeusedtodocustomvertextransformations

(3)PrimitiveAssembly

将VertexShader中的数据汇编成可以画出来的几何图形,比如三角形、直线等

(4)Rasterizition

将前一步的数据进行绘制,同时将前几个阶段的图元转换成二维的片段,传递给fragmentshader

(5)FragmentShader

对Fragment进行操作。

(6)Per-FragmentShader

对每个片段进行判断(是否可见)和处理(混合、抖动处理)。

(7) 在FrameBuffer中进行显示。

6 s3c6410 驱动分析

6410的g3d的驱动代码位于Kernel/drivers/media/video/Samsung/g3d中的s3c_fimg3d.c中。

从s3c_fimg3d.c中我们可以获取什么知识??AlmostNothing!

我这里有从网上下载的另一个版本的6410的g3d的驱动,比较全。

点击打开

小小分析:

在驱动中的s3c_g3d_probe中,

主要做了get_resoucerequest_mem_regionioremap等操作

/*getthememoryregionforthepostprocessordriver*/

res=platform_get_resource(pdev,IORESOURCE_MEM,0);

这句话获取了s3c-g3d的IO资源。

s3c_g3dprobe()called

res.start=72000000,res.end=72ffffff,rest.name=s3c-g3d,res.flags=512

beforeioremap:

s3c_g3d_mem->start=72000000,s3c_g3d_mem->end=72ffffff,s3c_g3d_mem->name=s3c-g3d,s3c_g3d_mem->flags=-2147483648

afterioremap:

s3c_g3d_base=d5000000

s3c_g3dversion:0x1050000

S3C G3D Init : Done

应用程序怎么访问g3d的寄存器呢?是通过mmap操作来访问的,下面是一个通过应用程序访问g3d寄存器的一个测试。

intmain()

{

structs3c_3d_mem_allocmem_para;

intfd=open("/dev/s3c-g3d",O_RDWR|O_SYNC);

chararg[100];

inti;

if(fd==-1)

{

printf("opens3c-g3derror\n");

return0;

}

printf("testmmapofdefault\n");

structstatfile_stat;

if(fstat(fd,&file_stat)<0)

{

printf("fstatwrong");

exit(1);

}

printf("filesize:%d\n",file_stat.st_size);

void*sfp;

if((sfp=mmap(NULL,2048,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED)

{

printf("mmapwrong!");

exit(0);

}

printf("sfp=%p\n",sfp);

printf("version=%x\n",*((int*)(sfp+0x10)));//偏移为10的寄存器是g3d版本寄存器

return0;

}

打印信息:

/#./test

ins3c_g3d_open******

testmmapofdefault

filesize: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/下,如果不存在,可以自己动手新建。格式是“dpyimpltag”比如自己添加的硬件加速库是libGLES_mmoid.so,则需要在此文件里这样编写

01mmoid

修改后要重启才可生效。

2.以上只是方法,要实现硬件加速,必须自己编写libGLES_mmoid.so。这个过程最复杂。需要对g3d的驱动进行调用,从上面的测试例子中可以看出,驱动中只是做了个简单的封装,对g3d的调用实际上是从应用程序层次进行调用的。这样的话,需要自己编写对g3d调用的实现库,实际上是对g3d驱动的封装。

3. 在opengl api中实现对上述封装库的调用。

7s3c6410文档分析

在熟悉s3c6410的g3d文档,搞清楚各个寄存器的使用方法ing。编写调用g3d驱动的实现库。

8.遇到的问题

(1)没有可供分析的sample代码,只能自己分析。

(2)OpenGLES专业术语和算法比较多,短时间不好掌握

(3)这部分OpenGLES的实现是不是ip厂商会提供呢?在0718上是不是有已经实现了的厂商提供的二进制OpenGLES硬件加速代码了呢?

(4)OpenGLES的很多操作都是Pipeline,想要实现部分函数的硬件加速,是不是其他函数也能影响到呢?

相关推荐