OPENGL ES 2.0 知识串讲――EGL详解
Posted 伯努力不努力
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OPENGL ES 2.0 知识串讲――EGL详解相关的知识,希望对你有一定的参考价值。
上一节我们初步学习了 OpenGL ES、EGL、GLSL 的相关概念,了解了它们的功能,以及它们之间的关联。我们知道了 EGL 是绘制 API(比如 OpenGL ES)与 底层平台窗口系统之间的接口,用于与手机设备打交道,比如获取绘制 buffer。 而 OpenGL ES 与 GLSL 的主要功能,就是往这块 buffer 上绘制图片。由于绘制的第一步就是获取绘制 buffer,而这完全通过 EGL 来实现的,那么这一节,我们来仔细研究一下,EGL 是如何跟手机产生关联,并如何从手机那里获取一块 buffer 用于绘制。
EGL API 总览
上一节,我们提到 OpenGL ES 其实是一个图形学库,由 109 个 API 组成,只要明白了这 109 个 API 的意义和用途,就掌握了 OpenGL ES。
这个道理在 EGL 上也同样适用。EGL 包含了 34 个 API。
首先,有 7 个 API,用于与手机关联并获取手机支持的配置信息。我们知道现在手机种类是各种各样,从操作系统来说,有 ios、android 等。同是 Android 手机,手机品牌和型号也是各种各样。当然我们不排除有两款手机,只是外形不同,而内部却完全一样,但是总的来说,大部分手机与手机的内部,还是存在着一定的区别的。所以,当我们使用 EGL 与某款手机硬件进行关联的时候,首先要做的,就是查看一下这款手机支持什么样的配置信息。而所谓的配置信息,就是手机支持多少种格式的绘制 buffer,每种格式对应着的 RGBA 是如何划分的,以及是否支持 depth、stencil 等。关于 RGBA 的格式,等我们在讲纹理图片的时候 再详细进行说明。而关于 depth、stencil,我们也将在讲 OpenGL ES 相应 API 的时 候进行解释说明。
然后,有 16 个 API,用于根据需要生成手机支持的 surface 和 context,对 surface 和 context 进行关联,并将 surface 对应的绘制 buffer 显示到手机屏幕上。 当我们知道了手机支持什么样格式的绘制 buffer 之后,就要根据我们所写的图形程序的需要,去对这些格式进行筛选,找到一个能满足我们需求,且手机支持的格式,然后通过 EGL 生成一块该格式的绘制 buffer。生成绘制 buffer 的过程,其实是我们通过 API 生成一块 surface,surface 是一个抽象概念,但是这个 surface 包含了绘制 buffer,假如我们所选择的格式是支持 RGBA、depth、stencil 的。那 么 surface 对应的绘制 buffer 有一块 Color buffer,到时候会用于保存图片的颜色信息,保存的方式在上一节我们做过介绍,就是 buffer 会对应上百万个像素点, 每个像素点有自己的颜色值,将这些颜色值按照像素点的顺序保存在 color buffer 中,就形成了一张完整的 color buffer。绘制 buffer 还有一块 depth buffer,depth buffer 也按照同样的方法,按照顺序保存了所有像素点的 depth 值;以及一块 stencil buffer,同理可知,stencil buffer 也是按照顺序保存了所有像素点的 stencil 值。
EGL 还会根据格式生成一块 context,context 也是一块 buffer。我们知道 OpenGL ES 是状态集,那么在绘制中会牵扯到各种各样的状态,这些状态全部都有默认值,可以通过 OpenGL ES 对这些状态进行改变。这些状态值就会保存在 context 中。比如 OpenGL ES 所用到的混合模式、纹理图片、program 还有各种 BO 等信息。
当然 EGL 可以创建多个 surface 和 context,每个 surface 在创建的时候就是 包含了对应的绘制 buffer,每个 context 创建的时候内部都是默认值。然后我们可以根据自己的需要选择启动任意一套 surface 和 context,然后对选中的 surface 和 context 进行操作。一个进程同一时间只能启动有相同格式的一块 surface 和一块对应于 OpenGL ES 的 context,一块 context 同时也只能被一个进程启动。
之后,有 3 个 API,用于指定使用哪个版本的 OpenGL ES,并与 OpenGL ES 建立关联。由于 EGL 生成的绘制 buffer 终归还是要提供给 OpenGL ES 使用,所以, 需要通过 EGL 来指定使用哪个版本的 OpenGL ES。
OpenGL ES 发展到现在已经有了好几个版本,从最早的 1.1 版本(在 1.1 中 使用的还是固定管线,还不存在 shader 的概念),到现在最普遍的 2.0 版本(2.0 版本中将 OpenGL ES 发展为上一节我们详细介绍的管线,加入了可编程模块 shader),以及目前市面上很多手机已经支持的 3.0 和 3.1 版本(shader 依然存在, 只是变的更加复杂)。所以在 EGL 中需要指定使用哪个版本的 OpenGL ES。
再之后,有 6 个 API,用于操作 EGL 上纹理,以及与多线程相关的高级功能。 纹理图片,又称纹理贴图,一般都是在 OpenGL ES 中,当顶点已经固定,具体形状已经成型的时候,将其贴上去,把虚拟的形状变成一个可以看见的物体。比如我们用 OpenGL ES 绘制,用顶点坐标勾勒出一个球形,然后把世界地图作为纹理贴上去,那么这个球看上去就变成了地球。所以按照理解纹理应该就是在绘制的时候进行使用,但是在 EGL 中偶尔也会使用到。
EGL 是用于在手机中生成绘制 buffer 提供给 OpenGL ES 进行绘制的,那么有时候也会设计到多线程操作,每个 thread 可以拥有自己的 surface 和 context,但是也要满足刚才我们所说的限制。一个 thread 同一时间只能启动有相同格式的 一块 surface 和一块对应于 OpenGL ES 的 context,一块 context 同时也只能被一 个 thread 启动。
最后还有 2 个 API,分别是用于初始化某个版本的 EGL,以及检测在执行上述 EGL API 的时候是否产生错误和产生了什么错误。
这些 API 有一些属于基本 API,就是在任何手机图形程序中都会使用到的 API, 这一节会把这一部分的 API 做详细介绍。剩下一些属于进阶版的 API,由于我们 这几节课主要还是为了讲解 OpenGL ES,那些进阶版的 API 会放到后面的课程进行补充学习。
EGL API 详解
EGLint eglGetError(void);
当我们调用 EGL 的 API 的时候,大部分 API 可以通过返回值判断这个 API 执 行的成功还是失败。比如当返回值的类型是 EGLBoolean 的时候,返回 EGL_TRUE 代表着成功,而 EGL_FALSE 代表着失败。而比如当创建 context 的时候,返回一个正常的 context 代表着成功,返回 EGL_NO_CONTEXT 则代表着失败。但是,当失败的时候,可能我们还需要更详细的信息,来判断为什么失败,是传入参数有误,还是发生了别的冲突之类的情况。所以我们需要 eglGetError 这个函数,它的功能是用于返回当前 thread 如果 EGL 的 API 出错的话,最近一个错误所对应的错误代码。
这个函数的输入为空。因为这个 API 是针对目前结果进行判断的,所以不需要任何输入。其实在 GPU driver 中,当执行 EGL API 的时候,如果出错了,会将错误代码写在寄存器中,然后通过这个函数直接去到寄存器去取即可,所以不需要任何输入。
这个函数的输出是可以用来判断详细错误信息的错误代码。比如当返回 EGL_SUCCESS 的时候,说明截至到目前为止,所有的 EGL API 都运行正常,没有出错。除此之外还有 15 个错误代码,标志着 15 种错误情况,这 15 种情况等我们说到对应 API 的时候再进行具体的解释说明。
有一种特殊情况,假如在调用这个 API 之前,出现过不止一次错误,那么调用这个 API 获取的将是最近一次错误的错误代码,并将该错误代码的标记重置。然后再调用一次,获取的将是倒数第二次错误的错误代码,以此类推,直至所有被标记的错误代码全部被重置后,再调用这个 API,则返回EGL_SUCCESS。
EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id);
这个函数的功能是用于从 EGL 运行的操作系统中获取一个 Display 的 handle。
这个函数的输入是 display_id,这个 display_id 我们可以看作是从操作系统中得知的 Display 的 ID。假如一个手机是由多个显示屏,那么不同的 ID 可能就会对应于不同的屏幕,而究竟哪个屏幕对应哪个 ID,我们需要从操作系统中得知, 然后将它传给这个函数。并且由于 EGL 可以运行在多种操作系统上,所以针对不同的操作系统,这里的输入值 display_id 的格式也不同。
但无论是任何操作系统,如果 display_id 为 EGL_DEFAULT_DISPLAY,会得到一个默认的 Display。
这个函数的输出是用于显示图片绘制的 Display 的 handle。绝大多数 EGL 的 API 都会与这个 Display 的 handle 有关,所有 EGL 相关的对象,比如 surface、context 等,都与这个值有关,且存在于这个 display 的命名空间中。大多数情况下,这 个 display 对应着一块物理的屏幕。
另外,无论调用这个函数多少次,只要 display_id 不变,那么返回值就不变, 如果没有一个 Display 是对应这个 display_id 的,那么就会返回 EGL_NO_DISPLAY,不报任何错误。
**EGLBoolean eglInitialize(EGLDisplay dpy, EGLint major, EGLint minor);
这个函数的功能是用于针对某 display 初始化某版本的 EGL。
这个函数输入的第一个参数是 EGLDisplay,我们刚才已经说了,EGL 的绝大 多数 API都会与这个 Display 的 handle 相关,EGL 所有的对象,都存在于这个 display 的命名空间中。所以我们需要针对这个 display,对 EGL 进行初始化,初始化的时候还需要指定 EGL 的版本。因为发展到现在 EGL 也是存在很多版本,目前使用的比较多的是 EGL1.4 版本。所以这个函数的第二个和第三个参数,也就是用于指定 EGL 的版本号,其中 major 对应着大号码,minor 对应着小号码,所以 1.4 在 这里,major 是 1,minor 是 4。而当 major 和 minor 为 NULL 的时候,则不对 EGL 进行初始化。
这个函数的输出是对 EGL 初始化的结果,当 EGL 初始化成功的时候,返回 EGL_TRUE。失败的话返回 EGL_FALSE。我们刚才在说 API eglGetError 的时候,就说了,成功的情况只有一个,但是错误的情况却千千万,仅靠一个返回值 EGL_FALSE 是无法判断失败在哪里了。所以我们还需要借用 eglGetError 这个 API 来抓取错误代码,进行判断。假如这个函数返回 EGL_FALSE 的时候,我们调用 eglGetError 来看下错误代码,假如获取的错误代码是 EGL_BAD_DISPLAY,则说明第一个输入参数 dpy 并非一个合法的 EGLDisplay;假如获取的错误代码是 EGL_NOT_INITIALIZED,则说明虽然 dpy 是合法的 EGLDisplay,但是依然无法针对 其初始化 EGL。
另外,可以针对一个已经初始化 EGL 过的 display 重新进行初始化,唯一的 结果就是返回 EGL_TRUE,并且更新 EGL 的版本号。一个在某个 thread 已经初始 化 EGL 过的 display,可以直接使用在另外一个 thread 中,无需再进行初始化。
**EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig configs, EGLint config_size, EGLint num_config);
这个函数的功能是用于获取某 display 支持的配置信息。刚才已经介绍过, 不同的手机支持的配置信息可能是不同的。假如一个手机具备两个屏幕,那么这两个屏幕分别对应于两个 display,这两个 display 支持的配置信息可能也是不同的。那么在这里,我们就以 display 为单位,查询这个 display 支持的配置信息。
这个函数输入的第一个参数是 EGLDisplay。用于查看特定 Display 的配置信息。第二个参数是一个指针,一般会被预留一定的空间,内部为空,在调用这个 API 的时候,将 display 支持的配置信息存储在这个指针中,这个参数与第三个参数相关。第三个参数是我们想要获取的手机配置信息的最大个数,假如 display 支持 50 种配置信息,而我们只想要 20 个,那么第三个参数传入 20,则第二个参数中的指针只会保存 20 个配置信息的内容,如果 display 支持 50 个配置信息, 但是我们想要 100 个,那么第三个参数传入 100,第二个参数中的指针还是只会保存 50 个配置信息的内容。第四个参数是 display 支持配置信息的个数,一般会传入一个变量的地址,然后将配置信息的个数写在变量中。所以虽然看上去是 4 个输入参数,其实应该是两个输入参数,两个输出参数,第一和第三位输入参数, 第二和第四个参数经过这个 API,把 display 对应的配置信息,get 出来了。
这个函数的输出是获取配置信息的结果,当成功的时候,返回 EGL_TRUE。 失败的话返回 EGL_FALSE。比如,当输入参数 dpy 是一个合法的 EGLDisplay,但是这个 display 并没有通过 eglInitialize 被初始化的时候,返回 EGL_FALSE,通过 eglGetError 获取的错误代码为 EGL_NOT_INITIALIZED。如果第四个参数传入的不是一个变量的地址,而是 NULL,那么,返回 EGL_FALSE,通过 eglGetError 获取的错误代码为 EGL_BAD_PARAMETER。
另外,如果第二个参数传入的不是一个指针,而是 NULL,那么就不会有配置信息返回,不过第四个参数依然会返回 display 支持的配置信息的数量。
**EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig configs, EGLint config_size, EGLint num_config);
这个函数的功能是用于获取与需求匹配,且某 display 支持的配置信息。刚才已经介绍过如何获取 display 支持的配置信息,那么 display 可能支持多种配置信息,但我们在写程序的时候,其实只需要选择一种与我们所写的图形程序匹配的即可。那么我们会制定一个需求,这个需求写在一个指针中,以 key-value 对的形式存在,key 是 EGLConfig 的属性,比如 EGL_RED_SIZE,代表所需要配置信息中红色分量的尺寸,EGL_STENCIL_SIZE,代表所需要配置信息中 stencil 分量 的尺寸, EGL_RENDERABLE_TYPE,代表所需要配置信息中的绘制 API 类型。value 就是这些 key 所对应的我们所需要的值。这里的需求信息并非需要把 EGLConfig的属性完全定义一遍,只需要定义一些我们需要的信息,然后通过 eglChooseConfig 这个 API,会遍历该 display 所支持的所有配置信息,然后获取到所有与需求信息匹配,且 display 支持的配置信息。
这个函数输入的第一个参数是 EGLDisplay。用于查看特定 Display 的配置信息。第二个参数是一个保存了需求信息的指针,指针中已经定义好了需求。第三个参数是一个指针,一般会被预留一定的空间,内部为空,然后用于在这个 API 中,将与需求信息符合,且 display 支持的配置信息存储在这个指针中,这个参数与第四个参数相关。第四个参数是我们想要获取的匹配配置信息的最大个数, 假如 display 中匹配配置信息有 50 个,而我们只想要 20 个,那么第四个参数传入 20,第三个参数中的指针只会保存 20 个配置信息的内容,如果 display 中匹配配置信息有 50 个,但是我们想要 100 个,那么第四个参数传入 100,第三个参数中的指针还是只会保存 50 个配置信息的内容。第五个参数是 display 匹配配置信息的个数,一般会传入一个变量的地址,然后将匹配配置信息的个数写在变量中。所以虽然看上去是五个输入参数,其实应该是三个输入参数,两个输出参数, 第一和第二和第四位输入参数,第三和第五个参数经过这个 API,把 display 匹配的配置信息,get 出来了。
这个函数的输出是获取配置信息的结果,当成功的时候,返回 EGL_TRUE。 失败的话返回 EGL_FALSE。比如,当第二个输入参数,在制定需求的时候,使用到了一个非法的 EGLConfig 的属性,或者某个 EGLConfig 属性对应的 value 不识别或者超出了范围,那么在执行了这个 API 之后,返回 EGL_FALSE,通过 eglGetError 获取的错误代码为 EGL_BAD_ATTRIBUTE 。
另外,在定义第二个参数,书写需求的时候,EGLConfig 的属性与 value 应该 一一对应,然后在结尾的地方写上 EGL_NONE。如果在需求中,没有定义到某个 EGLConfig 属性,那么就按照默认值处理。如果在需求中,针对某个 EGLConfig 属 性,对应的 value 为 EGL_DONT_CARE,则在匹配 display 配置信息的时候忽略这个属性,EGL_DONT_CARE 这个值可以制定给任何属性,除了 EGL_LEVEL。
如果第二个参数传入的不是一个指针,而是 NULL,或者第二个参数的第一个值就是 EGL_NONE,那么就按照 EGLConfig 默认的标准对配置信息进行选择和排序。
我们来简单的介绍一下这个默认的标准,刚才我们介绍了,如果在需求中, 没有定义到某个 EGLConfi 属性,那么就按照默认值处理。比如假如我们没有在需求中定义 EGL_RED_SIZE,那么其实相当于我们需求的 EGL_RED_SIZE 为 0,然后在手机的配置信息中,我们会过滤出所有 EGL_RED_SIZE 大于等于 0 的配置信息,依此类推,当所有的属性都过滤完毕之后,如果没有配置信息满足要求,那么依然返回 EGL_TRUE,但是最后一个参数将返回 0。如果超过一条配置信息满足要求,那么我们需要对这些配置信息进行排序, 由于配置信息有很多属性,我们会按照属性来对配置信息进行排序,且属性与属性之间也是有优先级的,优先级最高的是属性 EGL_CONFIG_CAVEAT,但是由于这个属性默认值为 EGL_DONT_CARE,那么我们除非特别需求,否则就会跳过这个属性。假如我们对这个属性进行特殊需求,比如在需求中定义这个属性对应的 value 为 NULL,那么就会对手机配置信息中的这个属性进行排序,排序的顺序 是 EGL_NONE、EGL_SLOW_CONFIG, EGL_NON_CONFORMANT_CONFIG。优先级第二的属性是 EGL_COLOR_BUFFER_TYPE,在这里我们就不对这些属性的默认值、 优先级、排序方式进行一一说明了。
EGLBoolean eglBindAPI(EGLenum api);
这个函数的功能是设置当前 thread 的绘制 API,后面创建的 surface 和 context 要与这个 API 相匹配。
这个函数输入的参数是绘制 API。比如 EGL_OPENGL_API、EGL_OPENGL_ES_API, 或者 EGL_OPENVG_API。需要注意的是,从这里开始 OpenGL 的 API 与 OpenGL ES 的 api 将开始分离,OpenGL 的 API 主要用于 PC 端的绘制,OpenGL ES 的 API 主 要用于移动端的绘制,所以在这里,我们一般是传入 EGL_OPENGL_ES_API 这个参数。OpenVG 是另外一种绘制 API,在这里我们用不到,也就不进行详细的描述。
这个函数如果成功,则返回 EGL_TRUE。如果失败,则返回 EGL_FALSE 。 我们可以通过错误代码判断失败的原因。假如传入参数不是刚才我们所介绍的三种 API,或者设备不支持我们传入的参数 API,那么错误代码为 EGL_BAD_PARAMETER。
*EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint attrib_list);
这个函数的功能是用于根据需求,创建一个 on-Screen 的 rendering surface, 可以提供给绘制 API,比如 OpenGL ES 进行绘制。surface 一共有三种,这个只是其中的一种,另外还有两种分别是通过 eglCreatePbufferSurface 以 及 eglCreatePixmapSurface 来创建。我们对这三种 surface 进行一下对比。EGL 和 OpenGL ES 支持两种绘制模式,back buffer 和 single buffer,windows surface 和 pbuffersurface 都是使用的 back buffer,顾名思义,也就是一块显存(GPU)中的 buffer,当绘制完毕的时候,由于 windows surface 于 window 有关联,那么可以使用 eglswapbuffer 将其转移到 window 上进行显示。而 pbuffer 于 window 没有关联,也就无法显示。而 pixmapsurface 是使用的 single buffer,single buffer 可以看作是保存在系统内存中的位图,OpenGL ES 不支持将其转移到 windows 进行显示,所以 pixmapsurface 也是不可显示的。
由于另外两种 surface 都是不可显示的 surface,且使用的比较少,这里也就不具体介绍它们的创建函数。
这个函数输入的第一个参数是 EGLDisplay。用于指定一个特定的 Display 进行 surface 创建。第二个参数是用于创建 surface 的配置信息,一般我们会把 eglChooseConfig 得到的已经匹配好的配置信息传入,刚才我们也已经知道了,匹配好的配置信息可能有很多个,不过它们已经排好序,那么我们就可以直接取第一个作为这里的输入参数。第三个参数是一个平台相关的参数,是 native window 的 handle。第四个参数,类似于 eglChooseConfig 的第二个参数,是需求信息, 格式也类似,都是 key-value 对,刚才的 key 是 EGLConfig 的属性,这里的 key 是 EGL_RENDER_BUFFER 等属性,它也是提供了一个接口,可以给一些特殊的平台创建 surface 的时候规定一些特殊的属性,类似 EGL extension。这里我们拿一个属性进行解释,比如 EGL_RENDER_BUFFER,它就定义了绘制 API 绘制的时候应该会绘制到哪个 buffer 中,可以绘制到 single buffer,也可以绘制到 back buffer, 我们已经知道了 windowsurface 创建的是 backbuffer,那么如果绘制到 single buffer,则相当于直接绘制到屏幕上,如果绘制到 back buffer,那么就会先绘制到 back buffer,再通过 eglswapbuffer 转移到屏幕上。
这个参数也可以直接写成 NULL,或者第一个值就是 EGL_NONE,那么所有的属性对应的 value 就按照默认值处理,比如 EGL_RENDER_BUFFER 的默认值为 back buffer。
这个函数如果成功,则输出是创建的 rendering surface 的 handle。如果失败, 则返回 EGL_NO_SURFACE。同样的,我们也可以通过错误代码判断失败的原因。 假如是第三个参数 native window 的 handle 与第二个参数 display 的配置信息 EGLConfig 不匹配,那么错误代码为 EGL_BAD_MATCH。 如果第二个参数,在配置信息中显示不支持绘制到 window,也就是在配置信息的属性 EGL_SURFACE_TYPE 中不包含 EGL_WINDOW_BIT 这一位的时候,当然默认这个属性是包含这一位的。错误代码也是 EGL_BAD_MATCH。
如果第二个参数,我们需求中的 color 和 alpha 信息与第四个参数我们额外的需求不匹配,错误代码也是 EGL_BAD_MATCH。这种情况确实很有可能发生, 比如我们在 eglChooseConfig 的时候没有强调使用什么样子的 color 或 alpha 信息, 然后根据手机自动匹配,可能匹配一个普通的 color 和 alpha 信息,但是我们在 这个 API 中又通过第四个参数增加了对 color 和 alpha 的需求,那么很有可能就不匹配,也就出现了这种错误。
其他的,如果第二个参数 EGLConfig 不是一个合法的 config,那么错误代码 EGL_BAD_CONFIG。
如果第三个参数 win 不是一个合法的 native window 的 handle,那么错误代 码 EGL_BAD_NATIVE_WINDOW。
如果已经使用第三个参数的 win 创建过一个 windowsurface,或者是其他的任何情况导致在创建 windowsurface 的时候分配资源失败,那么错误代码 EGL_BAD_ALLOC。
*EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint attrib_list);
这个函数的功能是用于根据需求,针对当前的绘制 API 创建一个 rendering context。
rendering surface 需要与 rendering context 进行搭配使用的,我们知道 context 中是可以保存 OpenGL ES 状态集信息的,所以如果一对 surface 和 context 兼容, 那么 context 就可以使用自己内部保存的信息往 rendering surface 上进行绘制。
通过这个 API 可以在 context 中针对绘制 API 初始化一套状态集。
这个函数输入的第一个参数是 EGLDisplay。用于针对哪个 Display 进行 context 的创建。第二个参数是用于创建 context 的配置信息,一般我们会把 eglChooseConfig 得到的已经匹配好的配置信息传入,和创建 surface 的时候类似, 匹配好的配置信息可能有很多个,不过它们已经排好序,那么我们一般会直接取第一个作为这里的输入参数。第三个参数可以是另外一个 context 的 handle,那么新创建的 context 就可以与该 context 共享所有可以共享的数据,如果该 context 之前已经与其他 context 进行了共享,那么它们三个或者多个之间,都可以进行共享,在 OpenGL ES 对应的 Context 之间,共享的东西可以有纹理、program 和 BO 等信息。如果第三个参数为 NULL,那么该 context 暂时不与其他 context 共享。 第四个参数,类似于 eglCreateWindowSurface 的第四个参数,是需求信息,格式也类似,都是 key-value 对,这里的 key 只有一个,就是 EGL_CONTEXT_CLIENT_VERSION 属性,该属性只针对 OpenGL ES API 对应的 context 有效,也就是用于指定该 context 是针对 OpenGL ES 的哪个版本。如果 value 为 1, 则针对 OpenGL ES 1.x 版本,如果 value 为 2,则针对 OpenGL ES 2.x 版本,如果 value 为 3,则针对 OpenGL ES 3.x 版本。这个参数也可以直接写成 NULL,或者第一个值就是 EGL_NONE,那么属性对应的 value 就按照默认值处理,而 EGL_CONTEXT_CLIENT_VERSION 的默认值为 1。
这个函数如果成功,则输出是创建的 rendering context 的 handle。如果失败, 则返回 EGL_NO_CONTEXT 。同样的,我们也可以通过错误代码判断失败的原因。 假如当前绘制 API 为 EGL_NONE 的时候,也就是当前设备不支持 OpenGL ES,且没有设置当前的绘制 API 的时候,错误代码为 EGL_BAD_MATCH。 如果第二个参数,不是一个合法的 EGLConfig,或者不支持参数四指定的具体版本的绘制 API,则错误代码为 EGL_BAD_CONFIG。这种情况确实很有可能发生,比如我们在 eglChooseConfig 的时候没有强调使用什么样子的 EGL_RENDERABLE_TYPE,那么默认为 EGL_OPENGL_ES_BIT,但是参数四指定了具体的版本,也就是如果参数四指定的是 1,那么在 config 中 EGL_RENDERABLE_TYPE 需要是 EGL_OPENGL_ES_BIT;如果参数四指定的是 2,那么在 config 中 EGL_RENDERABLE_TYPE 需要是 EGL_OPENGL_ES2_BIT;如果参数四指定的是 3,那么在 config 中 EGL_RENDERABLE_TYPE 需要是 EGL_OPENGL_ES3_BIT。 假如第三个参数 share context,不是 NULL ,而是一个合法的但与当前绘制 API不匹配的 context,错误代码为 EGL_BAD_CONTEXT。
如果第三个参数制定的 share_context 是针对另外一个 display 的话,那么错误代码为 EGL_BAD_MATCH。
如果没有足够的资源用于生成这个 context,那么错误代码为EGL_BAD_ALLOC。
EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
这个函数的功能是用于 enable surface 和 context,也就是将一个指定的 context绑定到当前的绘制thread上,与读、写的surface关联上。make current 之后,就可以调用 OpenGL ES 的 API 对 context 中的状态集进行设定,然后进而往 surface 中绘制内容,再从 surface 中把内容读取出来。
总结一下,一个 native window handle 只能创建一个 rendering surface,而一个 display 可以创建多个 rendering surface。context 与 native window 无关,也就是 display 可以创建多个 context,每个 context 对应一种绘制 API,只要 surface 和 context 的格式匹配,两者就可以进行关联,但是同一时间,一个 surface 只能 和一个 context 进行关联,一个 thread 中,一种绘制 API 也只能有一个 context。
这个函数输入的第一个参数是 EGLDisplay。用于指定操作特定的 Display。第二个参数是一个 surface,该 surface 用于写入以及其他除了读取和复制之外的所有操作。第三个参数也是一个 surface,该 surface 用于读取和复制。需要注意的是第二个参数和第三个参数可以是同一个参数,一般我们也会将它们设置为同一个参数。第四个参数就是传入我们将会 enable 的 context,如果当前 thread 已经有了一个相同绘制 API 的 context,那么之前的这个 context 就会先进行 flush 操 作,把未执行的命令全部执行完毕,然后将其设置为 disable 状态,再把新传入的 context 设置为 enable 状态。
这个函数如果成功,则返回 EGL_TRUE。如果失败,则返回 EGL_FALSE。同样的,我们也可以通过错误代码判断失败的原因。
假如第一个参数 dpy 不是一个合法的 EGLDisplay handle,那么错误代码EGL_BAD_DISPLAY。
假如第二个或者第三个参数的 surface 与 context 不匹配的话,错误代码为EGL_BAD_MATCH。
假如第二个、第三个、第四个参数的 surface 和 context 中的其中一个目前被别的 thread 使用,那么错误代码为 EGL_BAD_ACCESS。
假如第二个或者第三个参数的 surface 不是合法的 surface,那么错误代码为EGL_BAD_SURFACE。
如果第四个参数 ctx 不是一个合法的 context,那么错误代码为EGL_BAD_CONTEXT。
假如第二个或者第三个参数的 surface 对应的 windows 不再合法,那么错误代码为 EGL_BAD_NATIVE_WINDOW。
假如第二个或者第三个参数的 surface 不匹配,则错误代码 EGL_BAD_MATCH。
假如当前 thread 目前针对该绘制 API 已经有 context,且该 context 存在未 flush的命令,而且旧的 surface 又突然变成不合法的了,那么错误代码 EGL_BAD_CURRENT_SURFACE。
第二个或者第三个参数的 surface 在 context 需要的时候,会分配 buffer,但是假如无法针对这个 API 再分配 buffer 了,则错误代码 EGL_BAD_ALLOC。一旦分配成功,这个 buffer 会一直伴随着 surface,直到 surface 被删除。
假如这个 API 调用成功,但是之后,draw surface 被破坏掉了,那么剩下的绘制命令还是会执行,context 的内容依然会被更新,但是写入 surface 的内容会变成未定义。
假如这个 API 调用成功,但是之后,read surface 被破坏掉了,那么读取的数据(比如使用 glReadPixel 读取)为未定义。
如果想要释放当前的 context,也就是将当前的 context disable,那么将第二个和第三个参数设置为 EGL_NO_SURFACE,第四个参数设置为 EGL_NO_CONTEXT 即可。设置完之后原本 context 中未运行的绘制 API 会被 flush,然后将 context 设置为 disable。在运行这种情况的时候,第一个参数 dpy 可以被传入一个未初 始化的 display,而除了这种情况,假如 dpy 传入一个合法的,但是未初始化的 display,错误代码为 EGL_NOT_INITIALIZED。
如果 context 为 EGL_NO_CONTEXT,但是 surface 不是 EGL_NO_SURFACE。或者 surface 是 EGL_NO_SURFACE,而 context 不是 EGL_NO_CONTEXT,那么错误代 码 EGL_BAD_MATCH。
OpenGL ES 中有一个概念叫做视口,视口会有一个视口大小的,这个视口大小是用于表示在一定尺寸的绘制 buffer 中,有多大一块空间会被显示出来。当这个 API 运行成功,且 current context 是针对 OpenGL ES 的 API 的时候,这个视口大小和裁剪大小都会被设置为 surface 的尺寸。关于视口和裁剪,我们会在下面讲 OpenGL ES API 的时候进行详细说明,这里只要知道 makecurrent 之后,由于 surface 对应 windows,那么 surface 从 window 获取到尺寸,然后再被 context 得到,作为 OpenGL ES 绘制的某个值的初始状态。
EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface);
当 OpenGL ES 把内容绘制到 surface 上之后,可以通过这个函数,把 surface 中 color buffer 的内容显示出来。我们还记得 surface 中可能有 color buffer、depth buffer、stencil buffer,而被展示的只是 color buffer。也就是通过这个函数,让我们看到了手机上不停变换显示的图片。
这个函数输入的第一个参数是 EGLDisplay。用于指定特定的 display 进行显示。第二个参数是一个 surface,就是指定显示特定的 surface 中的内容。
这个函数如果成功,则返回 EGL_TRUE。如果失败,则返回 EGL_FALSE。
假如这个 surface 的属性 EGL_SWAP_BEHAVIOR 不是 EGL_BUFFER_PRESERVED, 那么 surface 中 color buffer 的内容为未定义。
EGLBoolean eglTerminate(EGLDisplay dpy);
这个函数的功能是用于将特定 display 对应的 EGL 相关的资源释放,比如与 这个 display 关联的 surface、context 等。如果某个 surface 和 context 在被释放的时候,依然被使用着,那么它们并没有被真正的释放掉。如果继续使用它们用于 OpenGL ES 绘制的话,并不会导致程序瘫痪,而只会使得绘制结果不确定,并且绘制命令出错。只有当使用 API eglReleaseThread,把整个 thread 释放掉,或者使用 eglMakeCurrent 把该 surface 和 context 设置为非当前使用的资源,它们才会被真正的释放掉。
当调用了 API eglTerminate 之后,surface 和 context 等会变成 invalid,如果在eglTerminate 之后,通过其他的 EGL API 再继续使用这些资源,会得到错误代码 EGL_BAD_SURFACE 或者 EGL_BAD_CONTEXT。
这个 API 与 eglInitialize 相对应。
这个函数输入的第一个参数是 EGLDisplay。用于特定释放哪个 Display 对应的资源。
这个函数的输出是资源释放的结果,当成功的时候,返回 EGL_TRUE。失败的话返回 EGL_FALSE。比如,当输入参数 dpy 不是一个合法的 EGLDisplay 的时候, 返回 EGL_FALSE,通过 eglGetError 获取的错误代码为 EGL_BAD_DISPLAY 。
如果一个 display 已经被 terminate 了,或者尚未 init。那么它本身其实并没有相对应的 EGL 的相关资源,这个时候对这个 display 进行 terminate 其实并没有意义,虽然是被允许的,但是唯一的结果就是会返回 EGL_TRUE。
如果一个 display 已经被 terminate 了,这个时候可以对它进行 re-init,只是 re-init 之后,那些已经被标记为删除的资源不变,依然保持标记为删除,使用 它们仍然是不合法的。
总结一下,一个 display 随时随地可以被初始化或者终止。而所有的 display 都是起始于终止状态。当调用了 eglInitialize,且成功之后,display 会变成初始成功;调用了 eglTerminate,且成功之后,display 就变成终止状态。
在 display 处 于 终 止 状 态 , 只 有 eglMakeCurrent 和 eglReleaseThread , eglInitialize 和 eglTerminate 这 4 个 EGL 的 API 还可以被正常工作,而前两个 API 是用于进一步的清除这些资源。而除了这 4 个 api,如果再调用任何其他 api,虽然 display 本身还是 valid,但是这个时候它已经被终止了,那么会产生错误代码 EGL_NOT_INITIALIZED。
EGL API 总结
EGL 的 API 还有很多,这一节只是把其中最重要也是最常用的 11 个 API 拿出来进行了讲解,最后总结一下 EGL 使用的大概流程如下:
先获取 display 的 handle,对 display 进行 EGL 初始化。从设备上获取匹配的配置信息,再绑定一个绘制 API 用于之后的绘制。根据获取 display 的 handle、 配置信息以及当前绘制 API 生成 surface 和 context,再把它们绑定在一起,绑定在当前 thread 上,下面就可以使用绘制 API 进行绘制。绘制完成之后,可以把绘制的 surface 中的 color buffer 拿出来显示。最后,记得把 display 上 EGL 相关资源进行释放。
除此之外还有很多重要的 API,比如和配置信息相关的 eglGetConfigAttrib, 用于获取配置信息中具体属性对应的值。比如对 surface 和 context 用完之后的删除 API eglDestroySurface 和 eglDestroyContext 等。
这些内容等我们以后讲 EGL 补充学习的时候再进行详细说明。
以上是关于OPENGL ES 2.0 知识串讲――EGL详解的主要内容,如果未能解决你的问题,请参考以下文章
OPENGL ES 2.0 知识串讲 ——OPENGL ES 详解I(绑定 SHADER)
OPENGL ES 2.0 知识串讲 ——OPENGL ES 详解II(传入绘制信息)
OPENGL ES 2.0 知识串讲 ——OPENGL ES 详解III(纹理)
OPENGL ES 2.0 知识串讲 ——OPENGL ES 详解II(传入绘制信息)