网络知识 娱乐 【我的OpenGL学习进阶之旅】EGL简介

【我的OpenGL学习进阶之旅】EGL简介

目录

  • 一、EGL简介
    • 1.1 OpenGL ES 命令需要渲染上下文和绘制表面
    • 1.2 EGL是什么?
    • 1.3 OpenGL ES应用程序渲染之前使用EGL执行的任务
    • 1.4 EGL版本
  • 1.5 使用OpenGL ES 3.0 编程
    • 1.6 库和包含文件
    • 1.7 EGL 命令语法
  • 二、 EGL提供的机制
  • 三、EGL 相关API介绍
    • 3.1 与窗口系统通信
    • 3.2 检查错误
    • 3.3 初始化EGL
    • 3.4 确定可用表面设置
    • 3.5 查询EGLConfig属性
    • 3.6 让EGL选择配置
    • 3.7 创建屏幕上的渲染区域:EGL窗口
    • 3.8 创建屏幕外渲染区域:EGL Pbuffer
    • 3.9 创建一个渲染上下文
    • 3.10 指定某个EGLContext为当前上下文
    • 3.11 使用 EGL 绘图的基本步骤
    • 3.12 使用EGL的绘图的一般步骤:
    • 3.11 总结
  • 四、 Android平台EGL环境
    • 4.1 介绍
    • 4.2 GLSurfaceView
  • 参考链接

在这里插入图片描述

一、EGL简介

1.1 OpenGL ES 命令需要渲染上下文和绘制表面

OpenGL ES 命令需要渲染上下文绘制表面

  • 渲染上下文
    存储相关的OpenGL ES状态
  • 绘制表面
    用于绘制图元的表面,它指定渲染所需的缓冲区类型,例如颜色缓冲区、深度缓冲区和模板缓冲区。绘制表面还要指定所需的缓冲区的位深度

OpenGL ES API 没有提及如何创建渲染上下文,或者渲染上下文如何连接到原生窗口系统。

1.2 EGL是什么?

  • EGL是Khronos渲染API(如OpenGL ES)和 原生窗口系统之间的接口。
  • 在实现OpenGL ES时,没有提供EGL的硬性要求。 开发人员应该参考平台供应商的文档,以确定支持哪个接口。
  • 目前已知的,唯一支持OpenGL ES而不支持EGL的平台是iOS。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3 OpenGL ES应用程序渲染之前使用EGL执行的任务

任何OpenGL ES应用程序都必须在开始渲染之前使用EGL执行如下任务:

  1. 查询并初始化设备商可用的显示器。
    例如:翻盖手机可能有两个液晶面板,我们可以使用OpenGL ES渲染可在某一或者两个面板上显示的表面。
  2. 创建渲染表面。
    EGL中创建的表面可以分类为屏幕上的表面或者屏幕外的表面。
    **屏幕上的表面连接到原生窗口系统,而屏幕外的表面是不显示但是可以用作渲染表面的像素缓冲区。**这些表面可以用来渲染纹理,并可以在多个Khronos API之间共享。
  3. 创建渲染上下文。
    EGL是创建OpenGL ES渲染上下文所必需的。这个上下文必须连接到合适的表面才能开始渲染。

EGL API 实现上述功能以及像电源管理、在一个进程中支持多个渲染上下文、在一个进程中跨渲染上下文共享对象(如纹理或者顶点缓冲区)这样的附加功能,并实现获得给定实现支持EGL或者OpenGL ES扩展功能函数指针的机制。

1.4 EGL版本

EGL最新的版本是EGL v1.5

  • Khronos EGL Registry

1.5 使用OpenGL ES 3.0 编程

要编写任何OpenGL ES 3.0应用程序,你都必须知道所要包含的头文件以及应用程序需要链接的库文件。

理解EGL使用的语法以及GL命令名、命令参数也很有用。

1.6 库和包含文件

OpenGL ES 3.0应用程序必须与如下库链接:

  • OpenGL ES 3.0 库
  • libGLESv2.lib
  • libEGL.lib

OpenGL ES 3.0应用程序还必须包含相对应的ES 3.0 和 EGL 头文件。
任何OpenGL ES 3.0应用程序都必须包含如下包含文件:

#include 
#include 

egl.h 是EGL头文件
gl3.h 是 OpenGL ES 3.0 头文件。

应用程序可以选择性地包含gl2ext.h,这是描述OpenGL ES 2.0/3.0 的Khronos批准的扩展列表的头文件。

#include 
  • 【Khronos EGL Registry】

  • 【 for EGL 1.5.】

  • 【 for EGL 1.5.】

  • 【Khronos OpenGL ES Registry】

  • 【 OpenGL ES 3.0 Header File.】

  • 【 OpenGL ES Extension Header File.】

  • 【 OpenGL ES 3.0 Platform-Dependent Macros.】

1.7 EGL 命令语法

所有EGL命令都以egl前缀开始,对组成命令名的每个单词使用首字母大写(例如eglCreateWindowSurface)

相类似,EGL数据类型也是从Egl前缀开始,对于组成类型名的每个单词才有首字母大写(EGLint和EGLenum除外)

下表简单说明了使用的EGL数据类型

数据类型C语言类型EGL类型
32位整数intEGLint
32位无符号整数unsignedintEGLBoolean,EGLenum
指针void *EGLConfig、EGLContext、EGLDisplay、EGLSurface、EGLClientBuffer

二、 EGL提供的机制

EGL可以用于管理绘图表面(窗口只是一种类型)。EGL提供了如下机制:

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

三、EGL 相关API介绍

EGL 提供了OpenGL ES 3.0(和其他Khronos图像API)和运行于计算机上的原生窗口系统(如GNU/Linux系统上常见的 X Window系统、Microsoft Windows或者 Mac OS X 的 Quartz)之间的“结合” 层次。

在EGL能够确定可用的绘制表面类型之前,它必须打开和窗口系统的通信渠道。 注意,Apple提供了自己的EGL API的iOS 实现,称为 EAGL。

因为每个窗口系统都有不同的语义,所以EGL提供了基本的不透明类型------EGLDisplay,该类型封装了所有系统相关性,用于和原生窗口系统接口。

任何使用EGL的应用程序必须执行的第一个操作是创建和初始化与本地EGL显示的连接。 这采用下面例子所示的两次调用序列完成。

EGLint majorVersion;
EGLint minorVersion;

EGLDisplay eglDisplay = eglGetDisplay( EGL_DEFAULT_DISPLAY );

 if ( esContext->eglDisplay == EGL_NO_DISPLAY )
 {
   // Unable to open connection to local windowing system
 }

 // Initialize EGL
 if ( !eglInitialize ( eglDisplay, &majorVersion, &minorVersion ) )
 {
    // Unable to initialize EGL;handle and recover
 }

3.1 与窗口系统通信

调用如下所示函数打开与EGL显示服务器的连接

EGLDisplay eglDisplay(EGLNativeDisplayType displayId);

displayId 指定显示连接,一般使用默认的 EGL_DEFAULT_DISPLAY,即返回与默认原生窗口的连接。

相关错误码:
EGL_NO_DISPLAY :连接不可用

定义EGLNativeDisplayType是为了匹配原生窗口系统的显示类型。但是为了方便将代码转移到不同的操作系统,应该接收EGL_DEFAULT_DISPLAY标志,返回与默认原生显示的连接。

如果显示连接不可用,则eglDisplay将返回EGL_NO_DISPLAY,这个错误表示EGL不可用,因此你无法使用OpenGL ES 3.0。

3.2 检查错误

EGL 中大部分函数成功时都是返回 EGL_TRUE,失败返回 EGL_FALSE
但是EGL所做的不仅是告诉你调用是否失败,它将记录错误,指示故障原因。

不过这个错误代码不好直接返回给你,你需要明确地查询EGL错误代码,为此可以调用如下函数获取:

在这里插入图片描述

EGLint eglGetError();

这个函数返回 特定线程中最近调用 EGL 函数的错误代码,如果返回 EGL_SUCCESS 说明没有错误。

你可能感到疑惑,为什么说这种方法比调用结束时直接返回错误代码更明智?
尽管我们从不鼓励任何人忽略函数返回代码,但是允许可选的错误代码恢复减少了已经验证为正常工作的应用程序中的多余代码。

你当然应该在开发和调试期间检查错误,在关键应用中也应该持续检查,但是一旦相信应用程序能够按照预想的方式工作,就可以减少错误检查。

3.3 初始化EGL

成功打开连接之后,需要初始化EGL,这通过调用如下函数完成:

EGLBoolean eglInitialize(EGLDisplay display, // 指定EGL显示连接
                         EGLint *majorVersion, // 指定 EGL实现返回的 主版本号,可能为NULL
                         EGLint *minorVersion); // 返回 EGL实现返回的 次版本号,可能为NULL

这个函数初始化EGL内部数据结构,返回EGL实现的主版本号和此版本号。
如果EGL无法初始化,这个调用将返回EGL_FALSE,并将EGL错误代码设置为:

  • EGL_BAD_DISPLAY :如果display没有指定有效的 EGLDisplay
  • EGL_NOT_INITIALIZED :如果 EGL 不能初始化

3.4 确定可用表面设置

一旦初始化了EGL,就可以确定可用渲染表面的类型和配置了。有两种方法:

  • 先使用 eglGetConfigs 查询每个配置,再使用 eglGetConfigAttrib 找出最好的选择
  • 指定一组需求,使用 eglChooseChofig 让 EGL 推荐最佳配置

在许多情况下,使用第二种方法更简单,而且更有可能得到用第一种方法找到的匹配。

两种方法均得到 EGLConfig 对象,这是包含有关特定表面及其特性(如每个颜色分量的位数、与EGLConfig相关的深度缓冲区)的EGL内部数据结构的标识符。 可以用 eglGetConfigAttrib函数查询EGLConfig的任何属性。

调用如下函数,可以查询底层窗口系统支持的所有EGL表面配置:

EGLBoolean eglGetConfigs(EGLDisplay display, // 指定EGL显示的连接
                         EGLConfig *configs, // 指定 configs 列表
                         EGLint maxReturnConfigs, // 指定configs的大小
                         EGLint *numConfigs); // 指定返回的configs大小

这个函数调用成功将返回EGL_TRUE。失败了,返回EGL_FALSE,并将EGL错误代码设置为:

  • EGL_NOT_INITIALIZED :如果 EGL 不能初始化
  • EGL_BAD_PARAMETER :如果numConfigs为空

3.5 查询EGLConfig属性

EGLConfig 包含对EGL启用的表面的所有信息。这包括关于可用颜色、与配置相关的其他缓冲区、表面类型和许多其他特性。

使用如下函数可以查询与 EGLConfig相关的特定属性:

EGLBoolean eglGetConfigAttrib(EGLDisplay display, // 指定EGL显示连接
                              EGLConfig config, // 指定要查询的 GLConfig
                              EGLint attribute, // 返回特定属性
                              EGLint *value); // 返回值

这个函数调用成功将返回EGL_TRUE。失败了,返回EGL_FALSE,如果attribute不是有效的属性,则还要返回EGL_BAD_ATTRIBUTE错误。

这个调用将返回相关EGLConfig的特定属性值,因此你可以完全控制所选择的配置,最终创建渲染表面。 EGL提供了另外一个函数eglChooseConfig,可以指定对你的应用程序重要的选项,并根据你的请求返回最匹配的配置。

下表展示了EGLConfig属性

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.6 让EGL选择配置

使用如下函数,让EGL选择匹配的EGLConfig:

EGLBoolean eglChooseChofig(EGLDispay display, // 指定显示的连接
                           const EGLint *attribList, // 指定 configs 匹配的属性列表,可以为 NULL
                           EGLConfig *config,   // 调用成功,返回符合条件的 EGLConfig 列表
                           EGLint maxReturnConfigs, // 最多返回的符合条件的 GLConfig 数
                           ELGint *numConfigs );  // 实际返回的符合条件的 EGLConfig 数

这个函数调用成功将返回EGL_TRUE。失败了,返回EGL_FALSE,如果attribList包含未定义的EGL属性,或者某个属性值未被承认超限,则还要返回EGL_BAD_ATTRIBUTE错误。

// 指定EGL属性: 支持5位红色和蓝色分量、6位绿色分量(常用RGB565格式)的渲染表面、
// 一个深度缓冲区和OpenGL ES 3.0 的 EGLConfig
EGLint attribList[] = {
    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
    EGL_RED_SIZE, 5,
    EGL_GREEN_SIZE, 6,
    EGL_BLUE_SIZE, 5,
    EGL_DEPTH_SIZE, 1,
    EGL_NONE
};

// 查询EGL表面配置

const EGLint MaxConfigs = 10;
EGLConfig configs[MaxConfigs]; // We'll only accept 10 configs
EGLint numConfigs;
if (!eglChooseConfig(dpy, attribList, configs, MaxConfigs, &numConfigs)) {
    // Something didn't work … handle error situation
} else {
    // Everything's okay. Continue to create a rendering surface
}


如果eglChooseConfig成功返回,则将返回一组匹配你的标准的EGLConfig。如果匹配的EGLConfig超过一个,则eglChooseConfig将按照如下顺序排列配置:

在这里插入图片描述

  1. 按照 EGL_CONFIG_CAVEAT 的值。如果没有配置注意事项(EGL_CONFIG_CAVEAT 的值为 EGL_NONE)的配置优先,然后是慢速渲染配置(EGL_SLOW_CONFIG),最后是不兼容的配置(EGL_NON_CONFORMANT_CONFIG)
  2. 按照 EGL_COLOR_BUFFER_TYPE 指定的缓冲区类型
  3. 按照颜色缓冲区位数降序排列。缓冲区的位数取决于 EGL_COLOR_BUFFER_TYPE,至少是为特定颜色通道指定的值。当缓冲区类型为 EGL_RGB_BUFFER 时,位数是 EGL_RED_SIZEEGL_GREEN_SIZEEGL_BLUE_SIZE的和。当颜色缓冲区类型为 EGL_LUMINANCE_BUFFER 时,位数是 EGL_LUMINANCE_SIZEEGL_ALPHA_SIZE 的和。
  4. 按照 EGL_BUFFER_SIZE 值的升序排列
  5. 按照 EGL_SAMPLE_BUFFERS 值的升序排列
  6. 按照 EGL_SAMPLES 数量的升序排列
  7. 按照 EGL_DEPTH_SIZE 值的升序排列
  8. 按照 EGL_STENCIL_SIZE 值的升序排列
  9. 按照 EGL_ALPHA_MASK_SIZE 的值排序(这仅适用于 OpenVG)
  10. 按照 EGL_NATIVE_VISUAL_TYPE,以实现相关的方式排序
  11. 按照 EGL_CONFIG_ID 值的升序排列
    上述列表中未提及到的参数不用于排列过程

同时,需要注意以下事项:

  1. 因为第 3 条排序规则的存在,为了匹配最佳的属性格式,必须添加额外的逻辑检查。如:我们要求的是 RGB565,但是 RGB888 会先出现在返回结果中。
  2. 如果指定属性列表时,没有设置对应的默认值,则默认的渲染表面是屏幕上的窗口(EGL_SURFACE_TYPE 属性对应的值)。

3.7 创建屏幕上的渲染区域:EGL窗口

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

EGLSurface eglCreateWindowSurface(EGLDisplay display, // 指定EGL显示连接
                                  EGLConfig config, // 符合条件的 EGLConfig
                                  EGLNatvieWindowType window, // 指定原生窗口
                                  const EGLint *attribList); // 指定窗口属性列表,可为 NULL

eglCreateWindowSurface接受单一属性,该属性用于指定所要渲染的表面是前台缓冲区还是后台缓冲区。

下表展示用eglCreateWindowSurface创建窗口的属性:

标志说明默认值
EGL_RENDER_BUFFER指定渲染所用的缓冲区:EGL_SINGLE_BUFFER或者后台缓冲区EGL_BACK_BUFFEREGL_BACK_BUFFER
EGL_GL_COLORSPACE指定 Open GL 和 Open GL ES 渲染表面时,用到的颜色空间,此属性在 EGL1.5 中被定义EGL_GL_COLORSPACE_SRGB

对于OpenGL ES 3.0窗口渲染表面,只支持双缓冲区窗口。

eglCreateWindowSurface 方法在很多情况下可能失败,失败返回EGL_NO_SURFACE。我们可以通过 eglGetError 获取相关错误。

在这里插入图片描述

下面示例创建一个EGL窗口表面:

EGLint attribList[] = {
			EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
			EGL_NONE
	};

// 这里先省略了创建原生窗口的过程
EGLSurface window = eglCreateWindowSurface(display, config, nativeWindow, attribList);

if (window == EGL_NO_SURFACE) {
	switch (eglGetError()) {
		case EGL_BAD_MATCH:
			// Check window and EGLConfig attributes to determine
			// compatibility, or verify that the EGLConfig
			// supports rendering to a window,
			break;
		case EGL_BAD_CONFIG:
			// Verify that provided EGLConfig is valid
			break;
		case EGL_BAD_NATIVE_WINDOW:
			// Verify that provided EGLNativeWindow is valid
			break;
		case EGL_BAD_ALLOC:
			// Not enough resources available. Handle and recover
			break;
	}
}

上述代码创建了一个绘图场所,但是我们还需要完成两个步骤,才能成功地用OpenGL ES 3.0 在我们的窗口上绘图。 然而,窗口并不是唯一的可用渲染表面。在完成讨论之前,我们介绍另一种渲染表面类型。

3.8 创建屏幕外渲染区域:EGL Pbuffer

除了可以用OpenGL ES 3.0 在屏幕上的窗口渲染之外,还可以渲染称作Pbuffer(像素缓冲区 Pixel buffer的简写)的不可见的屏幕外表面。

和窗口一样,pbuffer可以利用OpenGL ES 3.0 中的任何硬件加速。Pbuffer最常用于生成纹理贴图。如果你想要做的是渲染到一个纹理,那么我们建议使用帧缓冲区代替Pbuffer,因为 帧缓冲区更高效。
不过在某些帧缓冲区对象无法使用的情况下,Pbuffer仍然有用。例如:用OpenGL ES 在屏幕外表面上渲染,然后将其作为其他API(如Open VG)中的纹理。

创建Pbuffer和创建EGL窗口非常类似,只有少数微小的不同。
为了创建Pbuffer,需要和窗口一样找到EGLConfig,并作一处修改:我们需要扩增EGL_SURFACE_TYPE的值,使其包含EGL_PBUFFER_BIT

拥有适合的EGLConfig之后,就可以用如下函数创建Pbuffer

EGLSurface eglCreatePbufferSurface(EGLDisplay display, // 指定EGL显示连接
                                  EGLConfig config, // 符合条件的 EGLConfig
                                  const EGLint *attribList); // 指定像素缓冲区属性列表,可为 NULL

下表展示 EGL像素缓冲区属性。
在这里插入图片描述
在一些情况下,eglCreatePbufferSurface可能失败。和窗口创建一样,如果发生故障将返回EGL_NO_SURFACE并设置特定错误。在这种情况下,eglError返回如下表列出的某种错误:

在这里插入图片描述

  • EGL_BAD_DISPLAY EGL 显示连接错误
  • EGL_NOT_INITIALIZED EGL 显示未初始化
  • EGL_BAD_CONFIG 配置无效
  • EGL_BAD_ATTRIBUTE 如果指定了 EGL_MIPMAP_TEXTURE, EGL_TEXTURE_FORMAT, 或者 EGL_TEXTURE_TARGET,但是提供的 EGLConfig 不支持 OpenGL ES 渲染(如只支持 OpenVG 渲染),则发生该错误
  • EGL_BAD_ALLOC EGL Pbuffer 因为缺少资源而无法分配时,发生该错误
  • EGL_BAD_PARAMETER 如果属性指定的 EGL_WIDTH 或 EGL_HEIGHT 是负值,则发生该错误
  • EGL_BAD_MATCH 如果出现以下情况,则出现该错误:
    1. 提供的 EGLConfig 不支持 Pbuffer 表面
    2. Pbuffer 被用作纹理贴图(EGL_TEXTURE_FORMAT 不是 EGL_NO_TEXTURE),且指定的 EGL_WIDTH 和 EGL_HEIGHT 时无效的纹理尺寸
    3. EGL_TEXTURE_FORMAT 和 EGL_TEXTURE_TARGET 设置为 EGL_NO_TEXTURE,而其他属性没有设置成 EGL_NO_TEXTURE

下面示例创建一个EGL像素缓冲区:


	EGLint attribList[] = {
			EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,
			EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
			EGL_RED_SIZE, 5,
			EGL_GREEN_SIZE, 6,
			EGL_BLUE_SIZE, 5,
			EGL_DEPTH_SIZE, 1,
			EGL_NONE
	};

   // 查询EGL表面配置

	const EGLint MaxConfigs = 10;
	EGLConfig configs[MaxConfigs]; // We'll only accept 10 configs
	EGLint numConfigs;
	if (!eglChooseConfig(display, attribList, configs, MaxConfigs, &numConfigs)) {
		// Something didn't work … handle error situation
	} else {
		// Everything's okay. Continue to create a rendering surface
	}

    // Proceed to create a 512*512 pbuffer
	EGLSurface pbuffer;
	EGLint attribList2[] =
			{
					EGL_WIDTH, 512,
					EGL_HEIGHT, 512,
					EGL_LARGEST_PBUFFER, EGL_TRUE,
					EGL_NONE
			};

	pbuffer = eglCreatePbufferSurface(display, config, attribList2);

	if (pbuffer == EGL_NO_SURFACE) {
		switch (eglGetError()) {
			case EGL_BAD_ALLOC:
				// Not enough resources available. Handle and recover
				break;

			case EGL_BAD_CONFIG:
				// Verify that provided EGLConfig is valid
				break;

			case EGL_BAD_PARAMETER:
				// Verify that EGL_WIDTH and EGL_HEIGHT are non-negative values
				break;

			case EGL_BAD_MATCH:
				// Check window and EGLConfig attributes to determine
				// compatibility, or verify that the EGLConfig
				// supports rendering to a window,
				break;
		}
	}

	EGLint width;
	EGLint height;
	if(!eglQuerySurface( display,pbuffer,EGL_WIDTH,&width) ||
	   !eglQuerySurface( display,pbuffer,EGL_HEIGHT,&height))
	{
		//Unable to query surface information
	}

像窗口一样,Pbuffer支持所有OpenGL ES 3.0渲染机制。主要区别是,除了你无法在屏幕上显示PBuffer,是在你完成渲染时,不像窗口那样交换缓冲区,而是从PBuffer中将数值复制到应用程序,或者江PBuffer的绑定更改为纹理。

3.9 创建一个渲染上下文

渲染上下文是OpenGL ES 3.0的内部数据结构,包含操作所需的所有状态信息。OpenGL ES 3.0必须有一个可用的上下文才能绘图。

使用如下函数创建上下文:

EGLContext eglCreateContext(EGLDisplay display, // 指定EGL显示连接
                            EGLConfig config, // 前面选好的 EGLConfig
                            EGLContext shareContext, // 允许其它 EGLContext 共享数据,使用 EGL_NO_CONTEXT 表示不共享
                            const EGLint* attribList); // 指定操作的属性列表,只能接受一个属性 EGL_CONTEXT_CLIENT_VERSION

下表展示用eglCreateContext创建上下文时的属性:

标志描述默认值
EGL_CONTEXT_CLIENT_VERSION指定你所使用的OpenGL ES版本相关的上下文类型1 (指定OpenGL ES 1.x上下文)

因为我们要使用OpenGL ES 3.0,所以必须指定这个属性,以获得正确的上下文类型。

eglCreateContext成功时,它返回一个指向新创建上下文的句柄。 如果不能创建上下文,则eglCreateContext返回EGL_NO_CONTEXT,并设置原因,这可以通过eglGetError获得。这种情况下,eglGetError返回的错误是EGL_BAD_CONFIG

示例:创建一个EGL上下文

const ELGint attribList[] = {
    EGL_CONTEXT_CLIENT_VERSION, 3,
    EGL_NONE
};

EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribList);

if (context == EGL_NO_CONTEXT) {
    EGLError error = eglGetError();
    if (error == EGL_BAD_CONFIG) {
        // Handle error and recover
    }
}

3.10 指定某个EGLContext为当前上下文

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

使用如下调用,关联特定的EGLContext和某个EGLSurface

EGLBoolean eglMakeCurrent(EGLDisplay display, // 指定EGL显示连接
                          EGLSurface draw, // 指定EGL 绘图表面
                          EGLSurface read, // 指定EGL 读取表面
                          EGLContext context); // 指定连接到该表面的上下文

这个函数调用成功返回EGL_TRUE,失败时返回EGL_FALSE。

这里存在两个 EGLSurface,具有更好的灵活性,在后续一些高级的 EGL 用法中将利用它。目前我们先把它们read和draw设置为同一个值。

3.11 使用 EGL 绘图的基本步骤

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

3.12 使用EGL的绘图的一般步骤:

  1. 获取 EGLDisplay 对象:eglGetDisplay()
  2. 初始化与 EGLDisplay 之间的连接:eglInitialize()
  3. 获取 EGLConfig 对象:eglChooseConfig()
  4. 创建 EGLContext 实例:eglCreateContext()
  5. 创建 EGLSurface 实例:eglCreateWindowSurface()
  6. 连接 EGLContext 和 EGLSurface:eglMakeCurrent()
  7. 使用 OpenGL ES API 绘制图形:gl_*()
  8. 切换 front buffer 和 back buffer 送显:eglSwapBuffer()
  9. 断开并释放与 EGLSurface 关联的 EGLContext 对象:eglRelease()
  10. 删除 EGLSurface 对象
  11. 删除 EGLContext 对象
  12. 终止与 EGLDisplay 之间的连接

在这里插入图片描述

3.11 总结

本文介绍了有关EGL的知识,这是OpenGL ES 3.0用于创建表面和渲染上下文的API。现在,我们可以知道如何初始化EGL,查询各种EGL属性,用EGL创建一个屏幕上和屏幕外的渲染区域和渲染上下文。

四、 Android平台EGL环境

4.1 介绍

Android 2.0版本之后图形系统的底层渲染均由OpenGL负责,OpenGL除了负责处理3D API调用,还需负责管理显示内存及处理Android SurfaceFlinger或上层应用对其发出的2D API调用请求。

  • 本地代码:
frameworks/native/opengl/libs/EGL
Android EGL框架,负责加载OpenGL函数库和EGL本地实现。

frameworks/native/opengl/libagl
Android提供的OpenGL软件库
  • JNI代码:
frameworks/base/core/jni/com_google_android_gles_jni_EGLImpl.cpp
EGL本地代码的JNI调用接口

frameworks/base/core/jni/com_google_android_gles_jni_GLImpl.cpp
frameworks/base/core/jni/android_opengl_GLESXXX.cpp
OpenGL功能函数的JNI调用接口
  • Java代码:
frameworks/base/opengl/java/javax/microedition/khronos/egl
frameworks/base/opengl/java/javax/microedition/khronos/opengles
frameworks/base/opengl/java/com/google/android/gles_jni/
frameworks/base/opengl/java/android/opengl
EGL和OpenGL的Java层接口,提供给应用开发者,通过JNI方式调用底层函数。

首先从Native代码入手: frameworks/native/opengl/libs/EGL,该目录下文件如图所示:
在这里插入图片描述

依次解析该目录下各个文件的作用,由于文件缺少注释,只能从代码解读含义:
eglApi.cpp:提供暴露给上层的API。包含EGL对象的创建、配置、销毁等操作。

初始化EGL
OpenGL ES的初始化过程(EGL初始化)如下图所示意:

Display → Config → Surface

Context

Application → OpenGL Command

  1. 获取Display。
    获得Display要调用EGLboolean eglGetDisplay(NativeDisplay dpy),参数一般为 EGL_DEFAULT_DISPLAY 。

  2. 初始化egl。
    调用 EGLboolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor),该函数会进行一些内部初始化工作,并传回EGL版本号(major.minor)。
    开机时打出的信息:
    如下信息可以由const char * eglQueryString (EGLDisplay dpy, EGLint name);给出,name可以是EGL_VENDOR, EGL_VERSION, 或者EGL_EXTENSIONS 。该函数常用来查询当前版本EGL实现了哪些扩展,方便向下兼容。

SurfaceFlinger: EGL information:
SurfaceFlinger: vendor    : Android
SurfaceFlinger: version   : 1.4 Android META-EGL
SurfaceFlinger: extensions: EGL_KHR_get_all_proc_addresses EGL_ANDROID_presentation_time EGL_KHR_swap_buffers_with_damage EGL_ANDROID_get_native_client_buffer EGL_ANDROID_front_buffer_auto_refresh EGL_ANDROID_get_frame_timestamps EGL_KHR_image EGL_KHR_image_base EGL_KHR_gl_colorspace EGL_KHR_gl_texture_2D_image EGL_KHR_gl_texture_cubemap_image EGL_KHR_gl_renderbuffer_image EGL_KHR_fence_sync EGL_KHR_create_context EGL_KHR_config_attribs EGL_KHR_surfaceless_context EGL_EXT_create_context_robustness EGL_ANDROID_image_native_buffer EGL_KHR_wait_sync EGL_ANDROID_recordable EGL_KHR_partial_update EGL_KHR_mutable_render_buffer EGL_IMG_context_priority 
SurfaceFlinger: Client API: OpenGL_ES
SurfaceFlinger: EGLSurface: 8-8-8-8, config=0x785f32d008
SurfaceFlinger: OpenGL ES informations:
SurfaceFlinger: vendor    : ARM
SurfaceFlinger: renderer  : Mali-T860
SurfaceFlinger: version   : OpenGL ES 3.2 v1.r18p0-00cet0.e348142bb0bcdf18abb600a2670c56a1
SurfaceFlinger: extensions: GL_EXT_debug_marker GL_ARM_rgba8 GL_ARM_mali_shader_binary GL_OES_depth24 GL_OES_depth_texture GL_OES_depth_texture_cube_map GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_EXT_read_format_bgra GL_OES_compressed_paletted_texture GL_OES_compressed_ETC1_RGB8_texture GL_OES_standard_derivatives GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_EGL_image_external_essl3 GL_OES_EGL_sync GL_OES_texture_npot GL_OES_vertex_half_float GL_OES_required_internalformat GL_OES_vertex_array_object GL_OES_mapbuffer GL_EXT_texture_format_BGRA8888 GL_EXT_texture_rg GL_EXT_texture_type_2_10_10_10_REV GL_OES_fbo_render_mipmap GL_OES_element_index_uint GL_EXT_shadow_samplers GL_OES_texture_compression_astc GL_KHR_texture_compression_astc_ldr GL_KHR_texture_compression_astc_hdr GL_KHR_texture_compression_astc_sliced_3d GL_KHR_debug GL_EXT_occlusion_query_boolean GL_EXT_disjoint_timer_query GL_EXT_blend_minmax GL_EXT_discard_framebuffer GL_OES_get_prog...
SurfaceFlinger: GL_MAX_TEXTURE_SIZE = 8192
SurfaceFlinger: GL_MAX_VIEWPORT_DIMS = 8192
  1. 选择Config。
    Config实际指的是FrameBuffer的参数,
    一般用EGLboolean eglChooseConfig(EGLDisplay dpy, const EGLint * attr_list, EGLConfig * config, EGLint config_size, EGLint *num_config),其中attr_list是以EGL_NONE结束的参数数组,通常以id,value依次存放,对于个别标识性的属性可以只有 id,没有value。另一个办法是用EGLboolean eglGetConfigs(EGLDisplay dpy, EGLConfig * config, EGLint config_size, EGLint *num_config) 来获得所有config。
    这两个函数都会返回不多于config_size个Config,结果保存在config[]中,系统的总Config个数保存 在num_config中。
    可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数。Config有众多的Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。

  2. 构造Surface
    Surface实际上就是一个FrameBuffer,也就是渲染目的地,通过EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig confg, NativeWindow win, EGLint *cfg_attr)来创建一个可实际显示的Surface。
    系统通常还支持另外两种Surface:PixmapSurface和PBufferSurface,这两种都不是可显示的Surface,PixmapSurface是保存在系统内存中的位图,PBuffer则是保存在显存中的帧。
    对于这两种surface,Android系统中,支持PBufferSurface。Surface也有一些attribute,基本上都可以顾名思义,

    EGL_HEIGHT EGL_WIDTH EGL_LARGEST_PBUFFER
    EGL_TEXTURE_FORMAT EGL_TEXTURE_TARGET
    EGL_MIPMAP_TEXTURE EGL_MIPMAP_LEVEL
    

    通过eglSurfaceAttrib()设置、eglQuerySurface()读取。

  3. 创建Context
    OpenGL ES的pipeline从程序的角度看就是一个状态机,有当前的颜色、纹理坐标、变换矩阵、渲染模式等一大堆状态,这些状态作用于OpenGL API程序提交的顶点坐标等图元从而形成帧缓冲内的像素。在OpenGL的编程接口中,Context就代表这个状态机,OpenGL API程序的主要工作就是向Context提供图元、设置状态,偶尔也从Context里获取一些信息。
    可以用EGLContext eglCreateContext(EGLDisplay dpy, EGLSurface write, EGLSurface read, EGLContext * share_list)来创建一个Context。

  4. EGL变量之间的绑定
    boolean eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context)
    该接口将申请到的display,draw(surface)和 context进行了绑定。也就是说,在context下的OpenGLAPI指令将draw(surface)作为其渲染最终目的地。而display作为draw(surface)的前端显示。调用后,当前线程使用的EGLContex为context。

  5. 绘制。
    应用程序通过OpenGL API进行绘制,一帧完成之后,调用eglSwapBuffers(EGLDisplay dpy, EGLContext ctx)来显示。

    看看frameworks/native/opengl/libagl/egl.cpp对eglSwapBuffers的实现:

    EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface draw)
    {
        egl_surface_t* d = static_cast<egl_surface_t*>(draw);
    
        ...
    
        // post the surface
        d->swapBuffers();
    
        // if it's bound to a context, update the buffer
        if (d->ctx != EGL_NO_CONTEXT) {
            d->bindDrawSurface((ogles_context_t*)d->ctx);
            // if this surface is also the read surface of the context
            // it is bound to, make sure to update the read buffer as well.
            // The EGL spec is a little unclear about this.
            egl_context_t* c = egl_context_t::context(d->ctx);
            if (c->read == draw) {
                d->bindReadSurface((ogles_context_t*)d->ctx);
            }
        }
    
        return EGL_TRUE;
    }
    
  6. eglSwapBuffers之后
    应该是生成了layer供HWC合成,这部分仍在学习当中

4.2 GLSurfaceView

对于EGL这个框架,谷歌已经提供了GLSurfaceView,是一个已经封装EGL相关处理的工具类,但是不够灵活;对于更加核心的OpengGL ES的用法(例如多线程共享纹理)则需要开发者自行搭建EGL开发环境。

Android OpenGL ES 相关的包主要定义在

  • javax.microedition.khronos.opengles GL 绘图指令

  • javax.microedition.khronos.egl EGL 管理Display, surface等

  • android.opengl Android GL辅助类,连接OpenGL 与Android View,Activity

  • javax.nio Buffer类
    其中 GLSurfaceView 为 android.opengl 包中核心类:

  • 起到连接 OpenGL ES 与 Android 的 View 层次结构之间的桥梁作用。

  • 使得 Open GL ES 库适应于 Anndroid 系统的 Activity 生命周期。

  • 使得选择合适的 Frame buffer 像素格式变得容易。

  • 创建和管理单独绘图线程以达到平滑动画效果。

  • 提供了方便使用的调试工具来跟踪 OpenGL ES 函数调用以帮助检查错误。

使用过 Java ME ,JSR 239 开发过 OpenGL ES 可以看到 Android 包javax.microedition.khronos.egl ,javax.microedition.khronos.opengles 和 JSR239 基本一致,因此理论上不使用 android.opengl 包中的类也可以开发 Android 上 OpenGL ES 应用,但此时就需要自己使用 EGL 来管理 Display,Context,Surfaces 的创建,释放,捆绑

使用 EGL 实现 GLSurfaceView 一个可能的实现如下:

import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microed