2018-11-27 15:25:59 前言这篇文章简单介绍一下在Android平台下的EGL 环境的相关内容,由于OpenGL ES 并不负责窗口管理以及上下文管理,该职责由各个平台自行完成;在Android平台下OpenGL ES 的上下文环境是依赖EGL 的API进行搭建的。 对于EGL 这个框架,谷歌已经提供了GLSurfaceView ,是一个已经封装EGL 相关处理的工具类,但是不够灵活;对于更加核心的OpengGL ES 的用法(例如多线程共享纹理)则需要开发者自行搭建EGL 开发环境。 按照惯例先上一份源码 AndroidVideo。 Java相关核心实现在 EglBase14.java 和 EglBase10.java。 Native相关实现,可以参考 egl_base.cpp。
前置知识Java层实现 在Java层,EGL 封装了两套框架,分别是: 其主要区别是: PS:由于主体流程基本一致,所以本篇以EGL14 的代码进行示例。 Native层实现 程序在Native层使用EGL 环境时。 需要引入EGL 的so库: Android.mk: LOCAL_LDLIBS += -lEGL
CMake: find_library( EGL-lib
EGL )
需要包含头文件: #include <EGL/egl.h>#include <EGL/eglext.h>
EGL环境配置整体流程获取默认的EGLDisplay 。 对EGLDisplay 进行初始化。 输入预设置的参数获取EGL 支持的EGLConfig 。 通过EGLDisplay 和EGLConfig 创建一个EGLContext 上下文环境。 创建一个EGLSurface 来连接EGL 和设备的屏幕。 在渲染线程绑定EGLSurface 和EGLContext 。 【进行OpenGL ES 的API渲染步骤】(与EGL 无关) 调用SwapBuffer 进行双缓冲切换显示渲染画面。 释放EGL 相关资源EGLSurface 、EGLContext 、EGLDisplay 。
获取显示设备首先,EGL 是需要知道绘制内容的目标在哪里,EGLDisplay 是一个封装了物理屏幕的数据类型,也可以理解为绘制目标的一个抽象。 通常通过eglGetDisplay() 方法返回EGLDisplay 来作为OpenGL ES 的渲染目标,在该方法中,一般来说都会将常量EGL_DEFAULT_DISPLAY 传进方法中,而各个手机厂商则会返回默认的显示设备。 java代码: EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);//需判断是否成功获取EGLDisplayif (eglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("Unable to get EGL14 display");}
c代码: EGLDisplay egl_display;
egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//需判断是否成功获取EGLDisplay
if (egl_display == EGL_NO_DISPLAY)
return error;
一般来说,我们需要验证eglGetDisplay() 的返回值,如果是EGL_NO_DISPLAY 的话,那么就是没有获取默认显示设备,需要返回给客户端上层处理异常。 当我们获取到了EGLDisplay ,我们需要调用eglInitialize() 对其进行初始化,该方法会返回一个bool变量代表执行是否成功。 java代码: int version[] = new int[2];if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
throw new RuntimeException("Unable to initialize EGL14");}
c代码: EGLint major, minor;
if (!eglInitialize(egl_display, &major, &minor))
return error;
方法的后面参数代表Major 和Minor 的版本,比如EGL 的版本号是1.0,那么Major 将返回1,Minor 返回0。 如果不关心版本号,这两个参数可以传入NULL 。
配置输出格式当我们获取到EGLDisplay 后,其实已经可以将OpenGL ES 的输出与设备的屏幕桥接起来了,但是还是需要指定一些配置项,例如色彩格式、像素格式、RGBA的表示以及SurfaceType等,实际上也就是指FrameBuffer的配置参数。 一般来说不同平台的EGL 标准是不同的,以下是Android平台一个比较通用的配置参数(Java的就不列举了): const EGLint config_attribs[] = {
EGL_BUFFER_SIZE, 32, //颜色缓冲区中所有组成颜色的位数
EGL_ALPHA_SIZE, 8, //颜色缓冲区中透明度位数
EGL_BLUE_SIZE, 8, //颜色缓冲区中蓝色位数
EGL_GREEN_SIZE, 8, //颜色缓冲区中绿色位数
EGL_RED_SIZE, 8, //颜色缓冲区中红色位数
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //渲染窗口支持的布局组成
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, //EGL 窗口支持的类型
EGL_NONE
};
PS:EGL 的参数配置一般都以 id,value 依次存放,对于个别的属性可以只有 id 没有 value ,并以EGL_NONE 标识结尾信息。 最终可以通过调用eglChooseConfig() 方法得到配置选项信息: java代码: EGLConfig[] configs = new EGLConfig[1];int[] numConfigs = new int[1];//检测返回值是否成功if (!EGL14.eglChooseConfig(eglDisplay, configAttributes, 0, configs, 0, configs.length,
numConfigs, 0)) {
throw new RuntimeException("eglChooseConfig failed");}//如果没有配置的Configif (numConfigs[0] < 0) {
throw new RuntimeException("Unable to find any matching EGL config");}EGLConfig eglConfig = configs[0];//对应的Config不存在if (eglConfig == null) {
throw new RuntimeException("eglChooseConfig returned null");}
c代码: EGLint num_config;
EGLConfig egl_config;
//检测返回值是否成功
if (!eglChooseConfig(egl_display, config_attribs, &egl_config, 1, &num_config))
return error;
//如果没有配置的Config
if (num_config < 0)
return error;
//对应的Config不存在
if (_egl_config == NULL)
return error;
简单看一下函数原型:eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size,EGLint *num_config) 可以知道: 第2个参数attrib_list 指的是配置参数列表,也就是上面的config_attribs[] ; 第3个参数configs 返回输出的EGLConfigs 数据,可能有多个; 第4个参数config_size 则表示最多需要输出多少个EGLConfig ; 第5个参数num_config 则代表满足配置参数的EGLConfig 的个数。 附带一个EGLConfig属性表格: 属性 | 描述 | 默认值 |
---|
EGL_BUFFER_SIZE | 颜色缓冲区中所有组成颜色的位数 | 0 | EGL_RED_SIZE | 颜色缓冲区中红色位数 | 0 | EGL_GREEN_SIZE | 颜色缓冲区中绿色位数 | 0 | EGL_BLUE_SIZE | 颜色缓冲区中蓝色位数 | 0 | EGL_LUMINANCE_SIZE | 颜色缓冲区中亮度位数 | 0 | EGL_ALPHA_SIZE | 颜色缓冲区中透明度位数 | 0 | EGL_ALPHA_MASK_SIZE | 遮挡缓冲区透明度掩码位数 | 0 | EGL_BIND_TO_TEXTURE_RGB | 绑定到 RGB 贴图使能为真 | EGL_DONT_CARE | EGL_BIND_TO_TEXTURE_RGBA | 绑定到 RGBA 贴图使能为真 | EGL_DONT_CARE | EGL_COLOR_BUFFER_TYPE | 颜色缓冲区类型 EGL_RGB_BUFFER, 或者EGL_LUMINANCE_BUFFER | EGL_RGB_BUFFER | EGL_CONFIG_CAVEAT | 配置有关的警告信息 | EGL_DONT_CARE | EGL_CONFIG_ID | 唯一的 EGLConfig 标示值 | EGL_DONT_CARE | EGL_CONFORMANT | 使用EGLConfig 创建的上下文符合要求时为真 | — | EGL_DEPTH_SIZE | 深度缓冲区位数 | 0 | EGL_LEVEL | 帧缓冲区水平 | 0 | EGL_MAX_PBUFFER_WIDTH | 使用EGLConfig 创建的PBuffer的最大宽度 | — | EGL_MAX_PBUFFER_HEIGHT | 使用EGLConfig 创建的PBuffer最大高度 | — | EGL_MAX_PBUFFER_PIXELS | 使用EGLConfig 创建的PBuffer最大尺寸 | — | EGL_MAX_SWAP_INTERVAL | 最大缓冲区交换间隔 | EGL_DONT_CARE | EGL_MIN_SWAP_INTERVAL | 最小缓冲区交换间隔 | EGL_DONT_CARE | EGL_NATIVE_RENDERABLE | 如果操作系统渲染库能够使用EGLConfig 创建渲染渲染窗口 | EGL_DONT_CARE | EGL_NATIVE_VISUAL_ID | 与操作系统通讯的可视ID句柄 | EGL_DONT_CARE | EGL_NATIVE_VISUAL_TYPE | 与操作系统通讯的可视ID类型 | EGL_DONT_CARE | EGL_RENDERABLE_TYPE | 渲染窗口支持的布局组成标示符的遮挡位EGL_OPENGL_ES_BIT, EGL_OPENGL_ES2_BIT, orEGL_OPENVG_BIT that | EGL_OPENGL_ES_BIT | EGL_SAMPLE_BUFFERS | 可用的多重采样缓冲区位数 | 0 | EGL_SAMPLES | 每像素多重采样数 | 0 | EGL_S TENCIL_SIZE | 模板缓冲区位数 | 0 | EGL_SURFACE_TYPE | EGL 窗口支持的类型EGL_WINDOW_BIT, EGL_PIXMAP_BIT,或EGL_PBUFFER_BIT | EGL_WINDOW_BIT | EGL_TRANSPARENT_TYPE | 支持的透明度类型 | EGL_NONE | EGL_TRANSPARENT_RED_VALUE | 透明度的红色解释 | EGL_DONT_CARE | EGL_TRANSPARENT_GRE EN_VALUE | 透明度的绿色解释 | EGL_DONT_CARE | EGL_TRANSPARENT_BLUE_VALUE | 透明度的兰色解释 | EGL_DONT_CARE |
创建EGL上下文环境当拿到EGLDisplay 和EGLConfig 后,就可以开始创建EGL 的上下文环境EGLContext 了。
EGLContext 的存在是因为OpenGL ES 所创建的资源对于开发者来说可见的仅仅只是一个 ID 而已,而其实际内容依赖于这个上下文。 一个EGLContext 只能在一个线程中使用,如果将EGLContext 所持有的OpengGL 资源在多线程间共享,那么需要用到共享上下文(share context)。 简单看下EGLContext 的创建代码: java代码: //指定OpenGL ES2版本int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};//创建EGLContext上下文EGLContext eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, null, contextAttributes, 0);//需要检测Context是否存在if (eglContext == EGL14.EGL_NO_CONTEXT) {
throw new RuntimeException("Failed to create EGL context");}
c代码: EGLContext egl_context;
//指定OpenGL ES2版本
const EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
//创建EGLContext上下文
egl_context = eglCreateContext(egl_display, egl_config, NULL, context_attribs);
//需要检测Context是否存在
if (egl_context == EGL_NO_CONTEXT)
return error;
函数eglCreateContext() 的第三个参数可以传入一个EGLContext 的变量,该变量的意义是指可以与正在创建的上下文环境共享OpenGL ES 资源,包括纹理、FrameBuffer 以及其他的Buffer 等资源。 如果传入NULL 代表不需要与其他的OpenGL ES 上下文共享任何资源。
连接EGl和设备屏幕当我们需要将EGL 跟设备的屏幕桥接起来时,我们需要用到EGLSurface 让EGL 有一个“桥”的功能,从而使得OpenGL ES 的输出可以渲染到设备屏幕上;
EGLSurface 其实就是一个FrameBuffer ,通过EGL 库提供的eglCreateWindowSurface() 可以创建一个可实际显示的Surface ;也可以通过EGL库提供的eglCreatePbufferSurface() 方法创建一个OffScreen 的Surface 。 java代码: //创建可显示的SurfaceEGLSurface eglSurface;int[] surfaceAttribs = {EGL14.EGL_NONE};eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface, surfaceAttribs, 0);if (eglSurface == EGL14.EGL_NO_SURFACE) {
throw new RuntimeException("Failed to create window surface");}
这里需要强调一点的是eglCreateWindowSurface() 的第三个入参surface 虽然可以传入Object ;但是在EGL14 中只支持Surface 和SurfaceTexture ,在EGL10中只支持SurfaceHolder 和SurfaceTexture 。 c代码: //创建可显示的Surface
EGLSurface egl_surface;
const EGLint attribs[] = { EGL_NONE };
egl_surface = eglCreateWindowSurface(egl_display, egl_config, window, attribs);
if (egl_surface == EGL_NO_SURFACE)
return error;
在Native层,eglCreateWindowSurface() 的第三个入参window 是需要传入一个ANativeWindow 对象,也就是本地设备屏幕的表示。 我们可以通过Surface (由SurfaceView 或者TextureView 获得或者构建出来的Surface 对象)来构建ANativeWindow 。 需要引入头文件: #include <android/native_window.h>
#include <android/native_window_jni.h>
获取ANativeWindow 的代码如下: //surface也就是一个jobject,对应java层的Surface。
ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
如果我们要做离屏渲染的话,就需要用到离屏处理的Surface ,也就是创建一个PBufferSurface ,PBufferSurface 的保存位置是在显存中的帧,具体代码可以参考。 java代码: //创建离屏SurfaceEGLSurface eglSurface;int[] surfaceAttribs = {
EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE};eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs, 0);if (eglSurface == EGL14.EGL_NO_SURFACE) {
throw new RuntimeException("Failed to create pixel buffer surface");}
c代码: //创建离屏Surface
EGLSurface egl_surface;
const EGLint attribs[] = {
EGL_WIDTH, width,
EGL_HEIGHT, height,
EGL_NONE
};
egl_surface = eglCreatePbufferSurface(egl_display, egl_config, attribs);
if (egl_surface == EGL_NO_SURFACE)
return error;
另外说一点,EGLSurface 还支持参数的查询与设置,例如我们想知道新创建的Surface 的宽高,那么可以用到下面的方法。 java代码: //查询Surface的widthint[] array = new int[1];EGL14.eglQuerySurface(eglDisplay, eglSurface, EGL14.EGL_WIDTH, array, 0);//设置Surface的widthif (!EGL14.eglSurfaceAttrib(eglDisplay, eglSurface, EGL14.EGL_WIDTH, 600))
throw new RuntimeException("eglSurfaceAttrib fail");
c代码: //查询Surface的width
EGLint value;
eglQuerySurface(_egl_display, _egl_surface, EGL_WIDTH, &value);
//设置Surface的width
if (!eglSurfaceAttrib(_egl_display, _egl_surface, EGL_WIDTH, 600))
return error;
EGL变量与线程的绑定一般来说,开发者需要为OpenGL ES 开辟一个新的线程,来执行渲染操作,并且需要为该线程绑定显示设备EGLSurface 和上下文环境EGLContext 。 每个线程都需要绑定一个上下文,才可以开始执行OpenGL ES 指令,我们可以通过eglMakeCurrent 来为该线程绑定Surface 和Context ,值得注意一点的是一个EGLContext 只能绑定到一个线程上面。 java代码: if (!EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)) {
throw new RuntimeException("detachCurrent failed");}
c代码: if (!eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context))
return error;
可以通过返回值来判断eglMakeCurrent() 是否成功,这个也是必要的。
OpenGL的渲染涉及到OpenGl ES 的绘图API和纹理相关方面的知识,本篇这里不做介绍,请关注后续相关文章。
双缓冲机制EGL 在初始化的时候默认设置的是双缓冲模式,也就是两份FrameBuffer ; 即一份缓冲用于绘制图像,一份缓冲用于显示图像,每次显示时需要交换两份缓冲。 我们需要在OpenGL ES 绘制完毕后,调用
eglSwapBuffers(egl_display, egl_surface);
将前台的FrameBuffer 和后台FrameBuffer 进行交换。
EGL的资源释放当然,EGL 在我们不需要的时候也是需要进行释放的。 我们需要将线程跟EGL 环境解除绑定: eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
然后要销毁EGLSurface : eglDestroySurface(egl_display, egl_surface);
接着清理掉上下文环境: eglDestroyContext(egl_display, egl_context);
最终关闭掉显示设备: eglTerminate(egl_display);
通过上面也就是完成了一个EGL 的资源释放工作。
最后说一些在Android平台上面,当程序切到后台的时候,需要释放EGL 环境,在应用挪回前台时,重新初始化相关环境。 不过谷歌提供了一个已经封装完善的GLSurfaceView 提供OpenGL ES 的绘制环境,普通需求下采用GLSurfaceView 进行绘制也是一个不错的选择。 有兴趣可以读读这篇文章:源码解析:Android源码GLSurfaceView源码解析。 最后放一个EGL的官方文档地址。
结语这篇文章简单介绍了一下Android平台下的EGL 环境的相关内容,并提供了基于java层EGL14 和native层的EGL 相关API的使用示例。 后续文章将介绍怎么在EGL 环境下进行OpenGL ES 相关 API的一些使用。 本文同步发布于简书、CSDN。 End!
2018-11-27 15:25:59 前言这篇文章简单介绍一下在Android平台下的EGL 环境的相关内容,由于OpenGL ES 并不负责窗口管理以及上下文管理,该职责由各个平台自行完成;在Android平台下OpenGL ES 的上下文环境是依赖EGL 的API进行搭建的。 对于EGL 这个框架,谷歌已经提供了GLSurfaceView ,是一个已经封装EGL 相关处理的工具类,但是不够灵活;对于更加核心的OpengGL ES 的用法(例如多线程共享纹理)则需要开发者自行搭建EGL 开发环境。 按照惯例先上一份源码 AndroidVideo。 Java相关核心实现在 EglBase14.java 和 EglBase10.java。 Native相关实现,可以参考 egl_base.cpp。
前置知识Java层实现 在Java层,EGL 封装了两套框架,分别是: 其主要区别是: PS:由于主体流程基本一致,所以本篇以EGL14 的代码进行示例。 Native层实现 程序在Native层使用EGL 环境时。 需要引入EGL 的so库: Android.mk: LOCAL_LDLIBS += -lEGL
CMake: find_library( EGL-lib
EGL )
需要包含头文件: #include <EGL/egl.h>#include <EGL/eglext.h>
EGL环境配置整体流程获取默认的EGLDisplay 。 对EGLDisplay 进行初始化。 输入预设置的参数获取EGL 支持的EGLConfig 。 通过EGLDisplay 和EGLConfig 创建一个EGLContext 上下文环境。 创建一个EGLSurface 来连接EGL 和设备的屏幕。 在渲染线程绑定EGLSurface 和EGLContext 。 【进行OpenGL ES 的API渲染步骤】(与EGL 无关) 调用SwapBuffer 进行双缓冲切换显示渲染画面。 释放EGL 相关资源EGLSurface 、EGLContext 、EGLDisplay 。
获取显示设备首先,EGL 是需要知道绘制内容的目标在哪里,EGLDisplay 是一个封装了物理屏幕的数据类型,也可以理解为绘制目标的一个抽象。 通常通过eglGetDisplay() 方法返回EGLDisplay 来作为OpenGL ES 的渲染目标,在该方法中,一般来说都会将常量EGL_DEFAULT_DISPLAY 传进方法中,而各个手机厂商则会返回默认的显示设备。 java代码: EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);//需判断是否成功获取EGLDisplayif (eglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("Unable to get EGL14 display");}
c代码: EGLDisplay egl_display;
egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//需判断是否成功获取EGLDisplay
if (egl_display == EGL_NO_DISPLAY)
return error;
一般来说,我们需要验证eglGetDisplay() 的返回值,如果是EGL_NO_DISPLAY 的话,那么就是没有获取默认显示设备,需要返回给客户端上层处理异常。 当我们获取到了EGLDisplay ,我们需要调用eglInitialize() 对其进行初始化,该方法会返回一个bool变量代表执行是否成功。 java代码: int version[] = new int[2];if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
throw new RuntimeException("Unable to initialize EGL14");}
c代码: EGLint major, minor;
if (!eglInitialize(egl_display, &major, &minor))
return error;
方法的后面参数代表Major 和Minor 的版本,比如EGL 的版本号是1.0,那么Major 将返回1,Minor 返回0。 如果不关心版本号,这两个参数可以传入NULL 。
配置输出格式当我们获取到EGLDisplay 后,其实已经可以将OpenGL ES 的输出与设备的屏幕桥接起来了,但是还是需要指定一些配置项,例如色彩格式、像素格式、RGBA的表示以及SurfaceType等,实际上也就是指FrameBuffer的配置参数。 一般来说不同平台的EGL 标准是不同的,以下是Android平台一个比较通用的配置参数(Java的就不列举了): const EGLint config_attribs[] = {
EGL_BUFFER_SIZE, 32, //颜色缓冲区中所有组成颜色的位数
EGL_ALPHA_SIZE, 8, //颜色缓冲区中透明度位数
EGL_BLUE_SIZE, 8, //颜色缓冲区中蓝色位数
EGL_GREEN_SIZE, 8, //颜色缓冲区中绿色位数
EGL_RED_SIZE, 8, //颜色缓冲区中红色位数
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //渲染窗口支持的布局组成
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, //EGL 窗口支持的类型
EGL_NONE
};
PS:EGL 的参数配置一般都以 id,value 依次存放,对于个别的属性可以只有 id 没有 value ,并以EGL_NONE 标识结尾信息。 最终可以通过调用eglChooseConfig() 方法得到配置选项信息: java代码: EGLConfig[] configs = new EGLConfig[1];int[] numConfigs = new int[1];//检测返回值是否成功if (!EGL14.eglChooseConfig(eglDisplay, configAttributes, 0, configs, 0, configs.length,
numConfigs, 0)) {
throw new RuntimeException("eglChooseConfig failed");}//如果没有配置的Configif (numConfigs[0] < 0) {
throw new RuntimeException("Unable to find any matching EGL config");}EGLConfig eglConfig = configs[0];//对应的Config不存在if (eglConfig == null) {
throw new RuntimeException("eglChooseConfig returned null");}
c代码: EGLint num_config;
EGLConfig egl_config;
//检测返回值是否成功
if (!eglChooseConfig(egl_display, config_attribs, &egl_config, 1, &num_config))
return error;
//如果没有配置的Config
if (num_config < 0)
return error;
//对应的Config不存在
if (_egl_config == NULL)
return error;
简单看一下函数原型:eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size,EGLint *num_config) 可以知道: 第2个参数attrib_list 指的是配置参数列表,也就是上面的config_attribs[] ; 第3个参数configs 返回输出的EGLConfigs 数据,可能有多个; 第4个参数config_size 则表示最多需要输出多少个EGLConfig ; 第5个参数num_config 则代表满足配置参数的EGLConfig 的个数。 附带一个EGLConfig属性表格: 属性 | 描述 | 默认值 |
---|
EGL_BUFFER_SIZE | 颜色缓冲区中所有组成颜色的位数 | 0 | EGL_RED_SIZE | 颜色缓冲区中红色位数 | 0 | EGL_GREEN_SIZE | 颜色缓冲区中绿色位数 | 0 | EGL_BLUE_SIZE | 颜色缓冲区中蓝色位数 | 0 | EGL_LUMINANCE_SIZE | 颜色缓冲区中亮度位数 | 0 | EGL_ALPHA_SIZE | 颜色缓冲区中透明度位数 | 0 | EGL_ALPHA_MASK_SIZE | 遮挡缓冲区透明度掩码位数 | 0 | EGL_BIND_TO_TEXTURE_RGB | 绑定到 RGB 贴图使能为真 | EGL_DONT_CARE | EGL_BIND_TO_TEXTURE_RGBA | 绑定到 RGBA 贴图使能为真 | EGL_DONT_CARE | EGL_COLOR_BUFFER_TYPE | 颜色缓冲区类型 EGL_RGB_BUFFER, 或者EGL_LUMINANCE_BUFFER | EGL_RGB_BUFFER | EGL_CONFIG_CAVEAT | 配置有关的警告信息 | EGL_DONT_CARE | EGL_CONFIG_ID | 唯一的 EGLConfig 标示值 | EGL_DONT_CARE | EGL_CONFORMANT | 使用EGLConfig 创建的上下文符合要求时为真 | — | EGL_DEPTH_SIZE | 深度缓冲区位数 | 0 | EGL_LEVEL | 帧缓冲区水平 | 0 | EGL_MAX_PBUFFER_WIDTH | 使用EGLConfig 创建的PBuffer的最大宽度 | — | EGL_MAX_PBUFFER_HEIGHT | 使用EGLConfig 创建的PBuffer最大高度 | — | EGL_MAX_PBUFFER_PIXELS | 使用EGLConfig 创建的PBuffer最大尺寸 | — | EGL_MAX_SWAP_INTERVAL | 最大缓冲区交换间隔 | EGL_DONT_CARE | EGL_MIN_SWAP_INTERVAL | 最小缓冲区交换间隔 | EGL_DONT_CARE | EGL_NATIVE_RENDERABLE | 如果操作系统渲染库能够使用EGLConfig 创建渲染渲染窗口 | EGL_DONT_CARE | EGL_NATIVE_VISUAL_ID | 与操作系统通讯的可视ID句柄 | EGL_DONT_CARE | EGL_NATIVE_VISUAL_TYPE | 与操作系统通讯的可视ID类型 | EGL_DONT_CARE | EGL_RENDERABLE_TYPE | 渲染窗口支持的布局组成标示符的遮挡位EGL_OPENGL_ES_BIT, EGL_OPENGL_ES2_BIT, orEGL_OPENVG_BIT that | EGL_OPENGL_ES_BIT | EGL_SAMPLE_BUFFERS | 可用的多重采样缓冲区位数 | 0 | EGL_SAMPLES | 每像素多重采样数 | 0 | EGL_S TENCIL_SIZE | 模板缓冲区位数 | 0 | EGL_SURFACE_TYPE | EGL 窗口支持的类型EGL_WINDOW_BIT, EGL_PIXMAP_BIT,或EGL_PBUFFER_BIT | EGL_WINDOW_BIT | EGL_TRANSPARENT_TYPE | 支持的透明度类型 | EGL_NONE | EGL_TRANSPARENT_RED_VALUE | 透明度的红色解释 | EGL_DONT_CARE | EGL_TRANSPARENT_GRE EN_VALUE | 透明度的绿色解释 | EGL_DONT_CARE | EGL_TRANSPARENT_BLUE_VALUE | 透明度的兰色解释 | EGL_DONT_CARE |
创建EGL上下文环境当拿到EGLDisplay 和EGLConfig 后,就可以开始创建EGL 的上下文环境EGLContext 了。
EGLContext 的存在是因为OpenGL ES 所创建的资源对于开发者来说可见的仅仅只是一个 ID 而已,而其实际内容依赖于这个上下文。 一个EGLContext 只能在一个线程中使用,如果将EGLContext 所持有的OpengGL 资源在多线程间共享,那么需要用到共享上下文(share context)。 简单看下EGLContext 的创建代码: java代码: //指定OpenGL ES2版本int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};//创建EGLContext上下文EGLContext eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, null, contextAttributes, 0);//需要检测Context是否存在if (eglContext == EGL14.EGL_NO_CONTEXT) {
throw new RuntimeException("Failed to create EGL context");}
c代码: EGLContext egl_context;
//指定OpenGL ES2版本
const EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
//创建EGLContext上下文
egl_context = eglCreateContext(egl_display, egl_config, NULL, context_attribs);
//需要检测Context是否存在
if (egl_context == EGL_NO_CONTEXT)
return error;
函数eglCreateContext() 的第三个参数可以传入一个EGLContext 的变量,该变量的意义是指可以与正在创建的上下文环境共享OpenGL ES 资源,包括纹理、FrameBuffer 以及其他的Buffer 等资源。 如果传入NULL 代表不需要与其他的OpenGL ES 上下文共享任何资源。
连接EGl和设备屏幕当我们需要将EGL 跟设备的屏幕桥接起来时,我们需要用到EGLSurface 让EGL 有一个“桥”的功能,从而使得OpenGL ES 的输出可以渲染到设备屏幕上;
EGLSurface 其实就是一个FrameBuffer ,通过EGL 库提供的eglCreateWindowSurface() 可以创建一个可实际显示的Surface ;也可以通过EGL库提供的eglCreatePbufferSurface() 方法创建一个OffScreen 的Surface 。 java代码: //创建可显示的SurfaceEGLSurface eglSurface;int[] surfaceAttribs = {EGL14.EGL_NONE};eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface, surfaceAttribs, 0);if (eglSurface == EGL14.EGL_NO_SURFACE) {
throw new RuntimeException("Failed to create window surface");}
这里需要强调一点的是eglCreateWindowSurface() 的第三个入参surface 虽然可以传入Object ;但是在EGL14 中只支持Surface 和SurfaceTexture ,在EGL10中只支持SurfaceHolder 和SurfaceTexture 。 c代码: //创建可显示的Surface
EGLSurface egl_surface;
const EGLint attribs[] = { EGL_NONE };
egl_surface = eglCreateWindowSurface(egl_display, egl_config, window, attribs);
if (egl_surface == EGL_NO_SURFACE)
return error;
在Native层,eglCreateWindowSurface() 的第三个入参window 是需要传入一个ANativeWindow 对象,也就是本地设备屏幕的表示。 我们可以通过Surface (由SurfaceView 或者TextureView 获得或者构建出来的Surface 对象)来构建ANativeWindow 。 需要引入头文件: #include <android/native_window.h>
#include <android/native_window_jni.h>
获取ANativeWindow 的代码如下: //surface也就是一个jobject,对应java层的Surface。
ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
如果我们要做离屏渲染的话,就需要用到离屏处理的Surface ,也就是创建一个PBufferSurface ,PBufferSurface 的保存位置是在显存中的帧,具体代码可以参考。 java代码: //创建离屏SurfaceEGLSurface eglSurface;int[] surfaceAttribs = {
EGL14.EGL_WIDTH, width,
EGL14.EGL_HEIGHT, height,
EGL14.EGL_NONE};eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs, 0);if (eglSurface == EGL14.EGL_NO_SURFACE) {
throw new RuntimeException("Failed to create pixel buffer surface");}
c代码: //创建离屏Surface
EGLSurface egl_surface;
const EGLint attribs[] = {
EGL_WIDTH, width,
EGL_HEIGHT, height,
EGL_NONE
};
egl_surface = eglCreatePbufferSurface(egl_display, egl_config, attribs);
if (egl_surface == EGL_NO_SURFACE)
return error;
另外说一点,EGLSurface 还支持参数的查询与设置,例如我们想知道新创建的Surface 的宽高,那么可以用到下面的方法。 java代码: //查询Surface的widthint[] array = new int[1];EGL14.eglQuerySurface(eglDisplay, eglSurface, EGL14.EGL_WIDTH, array, 0);//设置Surface的widthif (!EGL14.eglSurfaceAttrib(eglDisplay, eglSurface, EGL14.EGL_WIDTH, 600))
throw new RuntimeException("eglSurfaceAttrib fail");
c代码: //查询Surface的width
EGLint value;
eglQuerySurface(_egl_display, _egl_surface, EGL_WIDTH, &value);
//设置Surface的width
if (!eglSurfaceAttrib(_egl_display, _egl_surface, EGL_WIDTH, 600))
return error;
EGL变量与线程的绑定一般来说,开发者需要为OpenGL ES 开辟一个新的线程,来执行渲染操作,并且需要为该线程绑定显示设备EGLSurface 和上下文环境EGLContext 。 每个线程都需要绑定一个上下文,才可以开始执行OpenGL ES 指令,我们可以通过eglMakeCurrent 来为该线程绑定Surface 和Context ,值得注意一点的是一个EGLContext 只能绑定到一个线程上面。 java代码: if (!EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)) {
throw new RuntimeException("detachCurrent failed");}
c代码: if (!eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context))
return error;
可以通过返回值来判断eglMakeCurrent() 是否成功,这个也是必要的。
OpenGL的渲染涉及到OpenGl ES 的绘图API和纹理相关方面的知识,本篇这里不做介绍,请关注后续相关文章。
双缓冲机制EGL 在初始化的时候默认设置的是双缓冲模式,也就是两份FrameBuffer ; 即一份缓冲用于绘制图像,一份缓冲用于显示图像,每次显示时需要交换两份缓冲。 我们需要在OpenGL ES 绘制完毕后,调用
eglSwapBuffers(egl_display, egl_surface);
将前台的FrameBuffer 和后台FrameBuffer 进行交换。
EGL的资源释放当然,EGL 在我们不需要的时候也是需要进行释放的。 我们需要将线程跟EGL 环境解除绑定: eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
然后要销毁EGLSurface : eglDestroySurface(egl_display, egl_surface);
接着清理掉上下文环境: eglDestroyContext(egl_display, egl_context);
最终关闭掉显示设备: eglTerminate(egl_display);
通过上面也就是完成了一个EGL 的资源释放工作。
最后说一些在Android平台上面,当程序切到后台的时候,需要释放EGL 环境,在应用挪回前台时,重新初始化相关环境。 不过谷歌提供了一个已经封装完善的GLSurfaceView 提供OpenGL ES 的绘制环境,普通需求下采用GLSurfaceView 进行绘制也是一个不错的选择。 有兴趣可以读读这篇文章:源码解析:Android源码GLSurfaceView源码解析。 最后放一个EGL的官方文档地址。
结语这篇文章简单介绍了一下Android平台下的EGL 环境的相关内容,并提供了基于java层EGL14 和native层的EGL 相关API的使用示例。 后续文章将介绍怎么在EGL 环境下进行OpenGL ES 相关 API的一些使用。 本文同步发布于简书、CSDN。 End!
|