这个算是安卓音视频学习中必学的一块内容,并没有偏离安卓音视频学习的主线。我也是现学现卖,这个系列的文章讲解的就是如何使用安卓端的OPENGLES,本系列文章参考自《OpenGL-ES 3.0 Programming Guide》,讲解的比较基础,望高手海涵。
《OpenGL-ES 3.0 Programming Guide》全书以C语言api实现,所以我们使用的是native层环境开发,并且使用的OPENGLES3.0版本,所以需要简单的CMake基础,C/C++基础。
EGL是一系列跨平台的接口,主要提供了以下三个功能:
- 可以和设备的本地窗口进行交互
- 查询surface可用的类型和配置参数
- 管理渲染源
EGL为OPENGLES创建了上下文环境,可以方便的进行各种操作,本质上仍然是一系列的接口。
检查错误
EGL中大多数函数执行成功后返回EGL_TRUE,失败返回EGL_FALSE。除了这些,EGL环境还记录了错误码来指示错误原因,开发者必须查询这些错误码:
1 | EGLint eglGetError() |
这个函数返回的是最近一次发生错误时的错误码,假如一直没有错误,则会返回EGL_SUCCESS。这个地方算是EGL设计的时候比较谨慎的一个设计,它没有为每个函数设置一个返回值,假如那样的话每个函数执行必须检查错误,包括开发者自认为没有错误的代码。采用这种查询错误码设计后,开发者可以不用检查一定不会错误的函数返回值,只在一些特定的代码执行后来查询错误码即可,减少工作量。
这里我在EGLManager中汇总了一些错误码的信息:
1 | static void logError() { |
在后边的代码编写中将会用到这些错误码。
与本地窗口交互
EGL为本地窗口和OpenGL环境创建了一个胶水层,首先EGL需要打开和本地窗口的驾六通道,然后决定使用哪个surface来绘制图形。不同的操作系统提供的窗口操作语法不同,E所有GL提供了一个包装类–EGLDisplay–来包装系统的窗口接口,方便开发者调用。建立本地窗口连接总共需要两步:
1 | EGLint majorVersion; |
eglGetDisplay
1 | EGLDisplay eglGetDisplay(EGLNativeDisplayType displayEd) |
该函数打开了一个和EGL display的连接,其中的参数EGLNativeDisplayType用来指定连接类型,使用EGL_DEFAULT_DISPLAY来创建一个系统的默认连接,通常情况下这个一定会成功。
假如连接不成功,该函数将返回EGL_NO_DISPLAY,表明OPEGL不可用。
eglInitialize
1 | EGLBoolean eglInitialize(EGLDisplay display,EGLint *majorVersion,EGLint *minorVersion) |
EGLdisplay打开成功后,需要初始化EGL,上面的函数就是为刚才打开的display初始化EGL内部的数据,传入的参数将会写入EGL的主要版本号和次要版本号。返回值是一个boolean值,成功是EGLTRUE,错误时EGL_FALSE。假如失败可以查询错误码(logerror()):
1 | EGL_BAD_DIAPLAY --display不可用 |
配置EGL的surface
初始化EGL后,接下来就是选取开发者需要的类型和配置,EGL提供了两种方式:
- 查询所有的surface配置,找出合适的一个
- 提供一系列的要求参数给EGL来找出最合适的一个
通常情况下推荐使用第二种。以上两种方式都会范湖第一个EGLconfig,这个结构体包含了一个surface和他的所有特征值。开发者可以通过eglGetConfigAttrib
函数来查询这些特征值对应的值。
查询所有支持的配置
1 | EGLBooean eglGetConfigs(EGLDisplay display,EGLConfig *configs,EGLint maxReturnConfigs,EGLint *numConfigs) |
这里有两种使用eglGetConfigs的方法:
第一种,直接传入configs参数为NULL,这样函数返回值为EGL_TRUE,并且设置numConfigs为EGL所有可用的EGLConfigs的数量,这样就可以根据numConfigs来初始化configs的大小,确保能分配到足够的内存来装载所有的EGLConfig。
第二种,直接创建一个eglConfig数组,将它作为参数传递给函数,设置maxReturnConfigs为你想要的配置数量,最后一个参数将会被函数设置为返回的配置数量。
查询配置的参数
选择好EGLCinfig后,可以查询配置中的任何属性的值,可能我们并不关心这些值,但我们需要知道查询这些值的方法:
1 | EGLBoolean eglGetConfigAttrib(EGLDisplay display,EGLConfig config,EGLint attribute,EGLint *value) |
display是刚才创建的EGLDisplay,config是上边选出的EGLConfig,attribute是我们要查询的属性,函数会把value设置为属性对应的值。返回值EGL_TRUE表示查询成功,EGL_FALSE表示查询错误,这是可以调用lgoerr()方法来查询错误信息,一般错误码是EGL_BAD_ATTRIBUTE,表示属性没有。
让EGL选取配置文件
上面介绍的方法是比较繁琐的,假如开发者已经确定一些属性,那就可以用这些属性来让EGL选取出最合适的配置,开发者指定的属性越多,最后返回的EGLConfig越准确。
1 | EGLBoolean eglChooseConfig( EGLDisplay display, |
第三个参数是开发者提供的限制返回EGLConfig的数量,最后一个参数是函数设置的实际返回的数量。
函数执行成功则返回EGL_TRUE,执行失败则返回EGL_FALSE,这时候可以lgoerr()来查询错误码,一般是EGL_BAD_ATTRIBUTE。
假如返回的结果个数很多的话,是有一个排序的,不过按照上面的写的属性来选择EGLConig,最后选取第一个来使用没问题。
创建屏幕渲染
关键函数是:
1 | EGLSurface gleCreateWindowSurface( EGLDisplay display, |
display和config都是前边介绍过的需要传入的参数。
window在安卓中是需要传入的一个参数,类型是ANativeWindow类型,这个window可以是java层的任何surface,包GLSurfaceView,TextureView或者SurfaceView。以TextureView的创建介绍一下:
给一个TextureView设置TextureView.SurfaceTextureListener后,在第一个回调方法中设置给jni一个surface作为参数:
1 | //java层 |
1 | //native层 |
EGLManager的构造函数:
1 | EglManager::EglManager(ANativeWindow *sur) : nativeWindow(sur) { |
attriblist假如设置为NULL,则会选择默认参数。
创建渲染上下文环境
到了这一步就比较简单了:
1 | EGLContext eglCreateContext( EGLDisplay display, |
前两个参数是我们前边讲解过如何获取的,第三个可以设置EGL_NO_CONTEXT,这样就不和其他EGL共享这个环境,最后一个参数是我们需要管理的OPENGL的版本:
1 | const EGLint attrList[] = { |
函数调用后我们需要检查错误码,来获取函数是否执行成功了。
绑定当前上下文环境
1 | EGLBoolean eglMakeCurrent( EGLDisplay display, |
EGL环境是一个双缓冲环境,包含一个前台区域和后台区域,每次绘制的时候先将后台区域绘制完成,再讲后台数据交换到前台,这就是双缓冲,所有第二个和第三个参数都是EGLSurface。为了在创建多个EGLContext的情况下都能是OPENGL正常工作,必须调用elgMakeCurrent方法来绑定这个环境,即使只有单个EGLContex,也必须调用这个方法。
走到这一步EGLContext环境就创建好了,接下来我们就可以直接使用OPENGL了。