EGL介绍

EGL 是 OpenGL ES 和底层 Native 平台视窗系统之间的接口。OpenGL ES 本质上是一个图形渲染管线的状态机,而 EGL 则是用于监控这些状态以及维护 Frame buffer 和其他渲染 Surface 的外部层。EGL提供如下机制:

  • 与设备的原生窗口系统通信
  • 查询绘图表面的可用类型和配置
  • 创建绘图表面
  • 在OpenGL ES 和其他图形渲染API之间同步渲染
  • 管理纹理贴图等渲染资源

OpenGL ES的javax.microedition.khronos.openges包定义了平台无关的GL绘制指令,EGL(javax.microedition.khronos.egl)则定义了控制dispays,contexts以及surfaces的统一的平台接口.

![[8.Attachments/image/95ecf7e0c1fdc7edc11479a3a5cf8544_MD5.gif]]
EGL 是 OpenGL ES 渲染 API 和本地窗口系统(native platform window system)之间的一个中间接口层,它主要由系统制造商实现。

为了让OpenGL ES能够绘制在当前设备上,我们需要EGL作为OpenGL ES与设备的桥梁。

![[8.Attachments/image/134d0f8a32e58360190215c7ae7b789e_MD5.jpg]]

  • Display(EGLDisplay)是对实际显示设备的抽象
  • Surface(EGLSurface)是对用来存储图像的内存区域FrameBuffer的抽象,包括Color Buffer, Stencil Buffer, Depth Buffer.
  • Context(EGLContext)存储OpenGLES绘图的一些状态信息

使用EGL绘制的一般步骤:

  1. 获取EGLDisplay对象
  2. 初始化与EGLDisplay之间的连接
  3. 获取EGLConfig实例
  4. 创建EGLContext实例
  5. 创建EGLSurface实例
  6. 连接EGLContext和EGLSurface
  7. 使用GL指令绘制图形
  8. 断开并释放与EGLSurface关联的EGLContext对象
  9. 删除EGLSurface对象
  10. 删除EGLContext对象
  11. 终止与EGLDisplay之间的连接

    EGL类型

    EGLBoolean

    EGL中的布尔类型。
    
    typedef unsigned int EGLBoolean;

    EGLDisplay

    不透明类型,封装了与底层系统的交互,用于充当与原生窗口之间的接口。
    
    typedef void * EGLDisplay;

    EGLint

    EGL整数类型。
    
    typedef int32_t EGLint;

    EGLNativeDisplayType

    用于匹配原生窗口系统的显示类型。
    
    typedef void * EGLNativeDisplayType;

EGL常量

布尔值

  • EGL_FALSE:条件为假
  • EGL_TRUE:条件为真

    创建窗口的属性

  • EGL_RENDER_BUFFER:指定渲染所用的缓冲区

错误代码

  • EGL_SUCCESS:没有错误
  • EGL_NOT_INITIALIZED:没有初始化
  • EGL_BAD_ACCESS:数据访问失败
  • EGL_BAD_ALLOC:内存分配失败
  • EGL_BAD_ATTRIBUTE:错误的属性
  • EGL_BAD_CONFIG:错误的配置
  • EGL_BAD_CONTEXT:错误的上下文
  • EGL_BAD_CURRENT_SURFACE:当前Surface对象错误
  • EGL_BAD_DISPLAY:错误的设备对象
  • EGL_BAD_MATCH:无法匹配
  • EGL_BAD_NATIVE_PIXMAP:错误的像素图
  • EGL_BAD_NATIVE_WINDOW:错误的本地窗口对象
  • EGL_BAD_PARAMETER:错误的参数
  • EGL_BAD_SURFACE:错误的Surface对象
  • EGL_CONTEXT_LOST:上下文丢失

    配置属性

  • EGL_ALPHA_SIZE:颜色缓冲区中的透明度分量的位数
  • EGL_ALPHA_MASK_SIZE:透明度掩码位数
  • EGL_BUFFER_SIZE:颜色缓冲区中颜色分量的位数
  • EGL_BLUE_SIZE:颜色缓冲区中的蓝色分量的位数
  • EGL_BIND_TO_TEXTURE_RGB:是否可以绑定RGB纹理
  • EGL_BIND_TO_TEXTURE_RGBA:是否可以绑定EGBA纹理
  • EGL_CONFIG_CAVEAT:注意事项
  • EGL_COLOR_BUFFER_TYPE:颜色缓冲区类型
  • EGL_CONFORMANT:创建的上下文是否兼容
  • EGL_CONFIG_ID:配置信息ID
  • EGL_DEPTH_SIZE:深度缓冲区位数
  • EGL_GREEN_SIZE:颜色缓冲区中的绿色分量的位数
  • EGL_LEVEL:帧缓冲区级别
  • EGL_LUMINANCE_SIZE:颜色缓冲区亮度位数
  • EGL_MAX_PBUFFER_HEIGHT:Pbuffer的最大高度
  • EGL_MAX_PBUFFER_PIXELS:Pbuffer的最大尺寸
  • EGL_MAX_PBUFFER_WIDTH:Pbuffer的最大宽度
  • EGL_MATCH_NATIVE_PIXMAP
  • EGL_MIN_SWAP_INTERVAL:最小缓冲区交换间隔
  • EGL_MAX_SWAP_INTERVAL:最大缓冲区交换间隔
  • EGL_NATIVE_RENDERABLE:是否可用原生渲染库渲染
  • EGL_NONE
  • EGL_NATIVE_VISUAL_ID:原生窗口系统的可视ID
  • EGL_NATIVE_VISUAL_TYPE:原生窗口系统的可视类型
  • EGL_RED_SIZE:颜色缓冲区中的红色分量的位数
  • EGL_RENDERABLE_TYPE:可渲染接口类型
  • EGL_STENCIL_SIZE:模板缓冲区位数
  • EGL_SAMPLES:每个像素的样本数量
  • EGL_SAMPLE_BUFFERS:可用多重采样缓冲区数量
  • EGL_SURFACE_TYPE:EGL表面类型
  • EGL_TRANSPARENT_TYPE:透明度类型
  • EGL_TRANSPARENT_BLUE_VALUE:透明的蓝色值
  • EGL_TRANSPARENT_GREEN_VALUE:透明的绿色值
  • EGL_TRANSPARENT_RED_VALUE:透明的红色值

    显示设备对象

  • EGL_NO_DISPLAY:当前无可用设备

    显示设备类型

  • EGL_DEFAULT_DISPLAY:默认为当前使用设备

EGL函数

eglChooseConfig

在初始化EGL时,我们需要列出并让EGL选择最合适的配置。

EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
  • dpy:已连接的设备
  • attrib_list:传入的配置信息数组
  • configs:保存返回的配置信息的数组
  • config_size:传入的配置数组的长度
  • num_config:保存返回的配置信息的数组的长度

    EGLConfig config;
    EGLint numConfigs = 0;
    EGLint attribList[] =
          {
                  EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
                  EGL_RED_SIZE, 8,
                  EGL_GREEN_SIZE, 8,
                  EGL_BLUE_SIZE, 8,
                  EGL_ALPHA_SIZE, 8,
                  EGL_DEPTH_SIZE, 16,
                  EGL_NONE
          };
    if (!eglChooseConfig(context->eglDisplay, attribList, &config, 1, &numConfigs)) {
      return GL_FALSE;
    }

    eglCreateContext

    渲染上下文是OpenGL ES的内部数据结构,包含操作所需的所有状态信息。例如程序中使用的顶点着色器或者片元着色器的引用。OpenGL ES必须有一个可用的上下文才能绘图。使用下面的函数可以创建一个上下文:

    EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext shareContext, const EGLint *attribList);
  • display:指定显示连接
  • config:指定配置对象
  • shareContext:允许多个EGL上下文共享特定的数据,EGL_NO_CONTEXT参数表示没有共享
  • attribList:指定创建上下文使用的属性列表
  • return:创建的上下文对象

    EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
    context->eglContext = eglCreateContext(context->eglDisplay, config, EGL_NO_CONTEXT, contextAttribs);
    if (context->eglContext == EGL_NO_CONTEXT) {
      return GL_FALSE;
    }

    eglCreateWindowSurface

    一旦我们有了符合渲染需求的EGLConfig,就为窗口创建做好了准备。调用如下函数可以创建一个窗口。

    EGLSurface eglCreateWindowSurface(EGLDisplay display, EGLConfig config, EGLNativeWindowType window,const EGLint *attribList);
  • display:已连接的设备对象
  • config:指定的配置对象
  • window:指定原生窗口对象
  • attriList:指定窗口的属性列表
  • return:EGL渲染区域对象
    这个函数以我们到原生显示管理器的连接和前一步获得的EGLConfig为参数。此外,它需要原生窗口系统事先创建一个窗口。因为EGL是许多不同窗口系统和OpenGL ES之间的软件接口层。最后这个函数需要一个属性列表;但是,这个列表中的属性与参数属性不完全相同,并且额外使用到了创建窗口的属性。该函数在多种情况下都有可能失败。

    context->eglSurface = eglCreateWindowSurface(context->eglDisplay, config, context->nativeWindow,
                                                  NULL);
    if (context->eglSurface == EGL_NO_SURFACE) {
      EGLint error;
      while((error = eglGetError()) != EGL_SUCCESS){
          switch(error) {
              case EGL_BAD_MATCH:{
                  //提供的原生窗口不匹配或者不支持渲染
              }
              case EGL_BAD_CONFIG:{
                  //系统不支持该配置
              }
              case EGL_BAD_NATIVE_WINDOW:{
                  //提供的原生窗口无效
              }
              case EGL_BAD_ALLOC:{
                  //无法为新的EGL分配资源或者该窗口已经被关联
              }
          }
      }
    }

    eglGetConfigAttrib

    如果我们获获取了一个EGL配置对象,我们可以通过下列函数查询该对象中指定属性的值。

    EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value);
  • dpy:已连接的设备对象
  • config:待查询的配置对象
  • attribute:需要查询的参数属性
  • value:返回的查询结果
  • return:查询结果,如果attribute不是有效属性,则产生一个EGL_BAD_ATTRIBUTE错误。

eglGetConfigs

在初始化EGL之后,我们需要给EGL选择一组配置。

EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config);
  • dpy:已连接设备对象
  • configs:保存配置信息的列表
  • size:configs的长度
  • num_config:EGL返回的配置信息数量
  • return:查询结果状态
    通常情况下,我们有两种方式使用该函数。首先,我们指定configs参数为NULL,此时EGL会查询所有可用的EGLConfigs数量并赋值给num_config,但此时不会有任何其他信息返回。

另外,我们也可以创建一个未初始化的EGLConfig,并作为函数的参数传入。此时,EGL将会查询不超过config_size数量的配置信息存入到configs,并通过num_config返回保存数据的数量。

eglGetDisplay

获得并与可用设备进行连接。

EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id);
  • display_id:当前需要连接的设备类型
  • return:已经连接上的设备对象

    EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    eglGetError

    EGL中大部分函数在成功时都会返回EGL_TRUE,否则返回EGL_FALSE。但是,我们仅从这个返回值上并不能看出错误原因是什么。如果想要明确的知道EGL的错误代码,应该调用下列函数。

    EGLint eglGetError(void);

    return:见 [EGL常量-错误代码]

eglInitialize

一般在成功打开设备连接之后需要初始化EGL。初始化过程将会对EGL内部的数据结构进行设置,然后返回EGL的主次版本号。

EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);
  • dpy:指定EGL设备对象。
  • major:设备主版本号。
  • minor:设备次版本号。

    GLint majorVersion;
    GLint minorVersion;
    if (!eglInitialize(eglDisplay, &majorVersion, &minorVersion)) {
      return EGL_FALSE;
    }

    eglMakeCurrent

    因为一个应用程序可能创建多个EGLContext用作不同的用途,所以我们需要指定关联特定的EGLContext和渲染表面——这一过程被称为“指定当前上下文”。

    EGLBoolean eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context);
  • display:指定EGL显示设备
  • draw:指定EGL绘图表面
  • read:指定EGL读取表面
  • context:指定连接到该表面的渲染上下文
  • return:函数时候执行成功

EGL的基本使用步骤

1.首先我们需要知道绘制内容的目标在哪里,EGLDisplayer是一个封装系统屏幕的数据类型,通常通过eglGetDisplay方法来返回EGLDisplay作为OpenGl ES的渲染目标,eglGetDisplay()

 if ( (mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)) == EGL14.EGL_NO_DISPLAY) {
                throw new RuntimeException("unable to get EGL14 display");
            }

2.初始化显示设备,第一参数代表Major版本,第二个代表Minor版本。如果不关心版本号,传0或者null就可以了。初始化与 EGLDisplay 之间的连接:eglInitialize()

if (!EGL14.eglInitialize(mEGLDisplay, 0, 0)) {
    throw new RuntimeException("unable to initialize EGL14");
}

3.下面我们进行配置选项,使用eglChooseConfig()方法,Android平台的配置代码如下:

int[] attribList = {
                    EGL14.EGL_RED_SIZE, 8,
                    EGL14.EGL_GREEN_SIZE, 8,
                    EGL14.EGL_BLUE_SIZE, 8,
                    EGL14.EGL_ALPHA_SIZE, 8,
                    EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
                    EGL_RECORDABLE_ANDROID, 1,
                    EGL14.EGL_NONE
            };
            EGLConfig[] configs = new EGLConfig[1];
            int[] numConfigs = new int[1];
            EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
                    numConfigs, 0);

3.接下来我们需要创建OpenGl的上下文环境 EGLContext 实例,这里值得留意的是,OpenGl的任何一条指令都是必须在自己的OpenGl上下文环境中运行,我们可以通过eglCreateContext()方法来构建上下文环境:

int[] attrib_list = {
                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL14.EGL_NONE
        };
        mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
                attrib_list, 0);

eglCreateContext中的第三个参数可以传入一个EGLContext类型的变量,改变量的意义是可以与正在创建的上下文环境共享OpenGl资源,包括纹理ID,FrameBuffer以及其他Buffer资源。如果没有的话可以填写Null.

4.通过上面四步,获取OpenGl 上下文之后,说明EGL和OpenGl ES端的环境已经搭建完毕,也就是说OpengGl的输出我们可以获取到了。下面的步骤我们讲如何将EGl和设备屏幕连接起来。如果连接呢?当然,这时候我们就要使用EGLSurface了,我们通过EGL库提供eglCreateWindowSurface可以创建一个实际可以显示的surface.当然,如果需要离线的surface,我们可以通过eglCreatePbufferSurface创建。eglCreateWindowSurface()

private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
int[] surfaceAttribs = {
            EGL14.EGL_NONE
    };
    mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface,
            surfaceAttribs, 0);

5.通过上面的步骤,EGL的准备工作做好了,一方面我们为OpenGl ES渲染提供了目标及上下文环境,可以接收到OpenGl ES渲染出来的纹理,另一方面我们连接好了设备显示屏(这里指SurfaceView或者TextureView),接下来我们讲解如何在创建好的EGL环境下工作的。首先我们有一点必须要明确,OpenGl ES 的渲染必须新开一个线程,并为该线程绑定显示设备及上下文环境(Context)。因为前面有说过OpenGl指令必须要在其上下文环境中才能执行。所以我们首先要通过 eglMakeCurrent()方法来绑定该线程的显示设备及上下文。

EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);

6.当我们绑定完成之后,我们就可以进行RenderLoop循环了。这里简单说一下,EGL的工作模式是双缓冲模式,其内部有两个FrameBuffer(帧缓冲区,可以理解为一个图像存储区域),当EGL将一个FrameBuffer显示到屏幕上的时候,另一个FrameBuffer就在后台等待OpenGl ES进行渲染输出。知道调用了eglSwapBuffers这条指令的时候,才会把前台的FrameBuffers和后台的FrameBuffer进行交换,这样界面呈现的就是OpenGl ES刚刚渲染的结构了。

mInputSurface.swapBuffers();

7.当然,在所有的操作都执行完之后,我们要销毁资源。特别注意,销毁资源必须在当前线程中进行,不然会报错滴。首先我们销毁显示设备(EGLSurface),然后销毁上下文(EGLContext),停止并释放线程,最后终止与EGLDisplay之间的链接,

 EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
                EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
                EGL14.eglReleaseThread();
                EGL14.eglTerminate(mEGLDisplay);

轻口味
16.8k 声望3.7k 粉丝

移动端十年老人,主要做IM、音视频、AI方向,目前在做鸿蒙化适配,欢迎这些方向的同学交流:wodekouwei