OpenGL ES EGL & TLS(线程局部存储) & G3D
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\
libuiLOCAL_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
endifLOCAL_CFLAGS += -DLOG_TAG=\"libGLESv1\"
LOCAL_CFLAGS+=-DGL_GLEXT_PROTOTYPES-DEGL_EGLEXT_PROTOTYPES
LOCAL_CFLAGS += -fvisibility=hiddenifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true)
LOCAL_CFLAGS+=-DHAVE_ARM_TLS_REGISTER
endifinclude $(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.0Loader.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 API5.学习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,想要实现部分函数的硬件加速,是不是其他函数也能影响到呢?