EGL接口解析与理解

发表于2015-12-07
评论1 4.7k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

本文介绍:EGL接口与初始化介绍,Android系统上EGL的一些理解


.EGL 介绍

通俗上讲,OpenGL是一个操作GPUAPI,它通过驱动向GPU发送相关指令,控制图形渲染管线状态机的运行状态。但OpenGL需要本地视窗系统进行交互,这就需要一个中间控制层,最好与平台无关。

EGL——因此被独立的设计出来,它作为OpenGL ES和本地窗口的桥梁。

EGL OpenGL ES(嵌入式)和底层 Native 平台视窗系统之间的接口。EGL API 是独立于OpenGL ES各版本标准的独立API 主要作用是为OpenGL指令创建 Context 绘制目标Surface 、配置Framebuffer属性、Swap提交绘制结果。此外,EGLGPU厂商和OS窗口系统之间提供了一个标准配置接口。

一般来说,OpenGL ES 图形管线的状态被存储于 EGL 管理的一个Context。而Frame Buffers 和其他绘制 Surfaces 通过 EGL API进行创建、管理和销毁。 EGL 同时也控制和提供了对设备显示和可能的设备渲染配置的访问。

EGL标准是C的,在Android系统Java层封装了相关API

 

二.EGL 数据类型与初始化(C标准、egl.h

EGL 包含了自己的一组数据类型,同时也提供了对一组平台相关的本地数据类型的支持。这些 Native 数据类型定义在 EGL 系统的头文件中。EGL中的一些类型和define抽象了平台的窗口与显存类型,如果理解了这些类型,即可对EGL进行相关配置,有些复杂的类型不比太过纠结底层如何实现。

标准 EGL 数据类型如下所示:


EGLBoolean ——EGL_TRUE =1, EGL_FALSE=0
EGLint ——int 数据类型
EGLDisplay ——系统显示 ID 或句柄,可以理解为一个前端的显示窗口
EGLConfig ——SurfaceEGL配置,可以理解为绘制目标framebuffer的配置属性
EGLSurface ——系统窗口或 frame buffer 句柄 ,可以理解为一个后端的渲染目标窗口。
EGLContext ——OpenGL ES 图形上下文,它代表了OpenGL状态机;如果没有它,OpenGL指令就没有执行的环境。

下面几个类型比较复杂,通过例子可以更深入的理解。这里要说明的是这几个类型在不同平台其实现是不同的,EGL只提供抽象标准


NativeDisplayType——Native 系统显示类型标识你所开发设备的物理屏幕
NativeWindowType ——Native 系统窗口缓存类型标识系统窗口
NativePixmapType ——Native 系统 frame buffer可以作为 Framebuffer 的系统图像(内存)数据类型,该类型只用于离屏渲染.


EGL Displays

EGLDisplay 是一个关联系统物理屏幕的通用数据类型,表示显示设备句柄,也可以认为是一个前端显示窗。为了使用系统的显示设备, EGL 提供了 EGLDisplay 数据类型,以及一组操作设备显示的 API
下面的函数原型用于获取 Native Display

EGLDisplay eglGetDisplay (NativeDisplayType display);


其 中 display 参数是 native 系统的窗口显示 ID 值。如果你只是想得到一个系统默认的 Display ,你可以使用 EGL_DEFAULT_DISPLAY 参数。如果系统中没有一个可用的 native display ID 与给定的 display 参数匹配,函数将返回 EGL_NO_DISPLAY ,而没有任何 Error 状态被设置。
由于设置无效的 display 值不会有任何错误状态,在你继续操作前请检测返回值。下面是一个使用 EGL API 获取系统 Display 的例子:
m_eglDisplay = eglGetDisplay( system.display);
if (m_eglDisplay == EGL_NO_DISPLAY || eglGetError() != EGL_SUCCESS))
throw error_egl_display;

Initialization 初始化

每个 EGLDisplay 在使用前都需要初始化。初始化 EGLDisplay 的同时,你可以得到系统中 EGL 的实现版本号。了解当前的版本号在向后兼容性方面是非常有价值的。在移动设备上,通过动态查询 EGL 版本号,你可以为新旧版本的 EGL 附加额外的特性或运行环境。基于平台配置,软件开发可用清楚知道哪些 API 可用访问,这将会为你的代码提供最大限度的可移植性。
下面是初始化 EGL 的函数原型:


EGLBoolean eglInitialize (EGLDisplay dpy, EGLint *major, EGLint *minor);


其中 dpy 应该是一个有效的 EGLDisplay 。函数返回时, major minor 将被赋予当前 EGL 版本号。比如 EGL1.0 major 返回 1 minor 则返回 0 。给 major minor NULL 是有效的,如果你不关心版本号。
eglQueryString() 函数是另外一个获取版本信息和其他信息的途径。通过 eglQueryString() 获取版本信息需要解析版本字符串,所以通过传递一个指针给 eglInitializ() 函数比较容易获得这个信息。注意在调用 eglQueryString() 必须先使用 eglInitialize() 初始化 EGLDisplay ,否则将得到 EGL_NOT_INITIALIZED 错误信息。
下面是获取 EGL 版本字符串信息的函数原型:


onst char * eglQueryString (EGLDisplay dpy, EGLint name);


参数name可以是EGL_VENDOR, EGL_VERSION, 或者EGL_EXTENSIONS 。这个函数最常用来查询有哪些 EGL 扩展被实现。所有 EGL 扩展都是可选的,如果你想使用某个扩展特性,请检查该扩展是否被实现了,而不要想当然假定已经实现了。如果没有扩展被实现,将返回一个 Null 字符串,如果给定的 name 参数无效,则会得到 EGL_BAD_PARAMETER. 错误信息。

初始化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)

3. 选择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_sizeConfig,结果保存在config[]中,系统的总Config个数保存 在num_config中。

可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数。
Config有众多的Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。

  1. 构造Surface
    Surface实际上就是一个FrameBuffer,也就是渲染目的地

通过EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig confg, NativeWindow win, EGLint *cfg_attr)

来创建一个可实际显示的Surface

系统通常还支持另外两种SurfacePixmapSurfacePBufferSurface,这两种都不是可显示的SurfacePixmapSurface是保存在系统内存中的位图,PBuffer则是保存在显存中的帧

对于这两种surfaceAndroid系统中,支持PBufferSurface
Surface也有一些attribute,基本上都可以故名思意,

EGL_HEIGHT EGL_WIDTH EGL_LARGEST_PBUFFER EGL_TEXTURE_FORMAT EGL_TEXTURE_TARGET EGL_MIPMAP_TEXTURE EGL_MIPMAP_LEVEL

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

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

boolean eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context)

该接口将申请到的displaydrawsurface)和 context进行了绑定。也就是说,在context下的OpenGLAPI指令将drawsurface)作为其渲染最终目的地。而display作为drawsurface)的前端显示。调用后,当前线程使用的EGLContexcontext

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

此接口的理解,后面会详细介绍。

EGL Configurations 属性配置

EGLConfigs 是一个用来描述 EGL surface 配置信息的数据类型。要获取正确的渲染结果, Surface 的格式是非常重要的。根据平台的不同, surface 配置可能会有限制,比如某个平台或者设备支持 16 位色深显示,或是不支持 stencil buffer ,还有其他的功能限制或精度的差异。

下面是获取系统可用的 EGL 配置信息的函数原型:


EGLBoolean eglGetConfigs (EGLDisplay dpy, EGLConfig *configs,EGLint config_size, EGLint *num_config);

参数 configs 将包含在你的平台上有效的所有 EGL framebuffer 配置列表。支持的配置总数将通过 num_config 返回。实际返回的 configs 的配置个数依赖于程序传入的 config_size 。如果 config_size < num_config ,则不是所有的配置信息都将被返回。如果想获取系统支持的所有配置信息,最好的办法就是先给 eglGetConfig 传一个 NULL configs 参数, num_config 将得到系统所支持的配置总数,然后用它来给 configs 分配合适的内存大小,再用得到的 configs 来调用 eglGetConfig
下面是如果使用 eglGetConfig() 函数的例子:

EGLConfig *configs_list;
EGLint num_configs;
// Main Display
m_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if( m_eglDisplay == EGL_NO_DISPLAY || eglGetError() != EGL_SUCCESS )
return FALSE;
if( eglInitialize( m_eglDisplay, NULL, NULL ) == EGL_FALSE || eglGetError() != EGL_SUCCESS )
return FALSE;
// find out how many configurations are supported
if ( eglGetConfigs( m_eglDisplay, NULL, 0, &num_configs) == EGL_FALSE || eglGetError() != EGL_SUCCESS )
return FALSE;
configs_list = malloc(num_configs * sizeof(EGLConfig));
if (configs_list == (EGLConfig *)0)
return FALSE;
// Get Configurations
if( eglGetConfigs( m_eglDisplay, configs_list, num_configs, &num_configs) == EGL_FALSE || eglGetError() != EGL_SUCCESS )
return FALSE;

由于当前终端平台的限制,通常只有很少的配置可用。系统支持的配置通常是利用系统硬件提供最好的性能。有时候,当移植游戏或应用到多个平台,它们的 EGL 配置可能会有细微的差别,我们希望作为通用的移植问题来直接处理这些问题。

选择一个 EGL Configuration
基 于 EGL 的属性,可以得到一个和需求接近的Config,但也可以选择自己需要的Config,只要平台支持。不是所有的Config都是有效的,也就是不是所有Config都会支持 eglChooseConfig() 函数将适配一个所期望的配置,并且尽可能接近一个有效的系统配置。

下面是选择一个 EGL 配置的函数原型:

EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list,

EGLConfig *configs, EGLint config_size, EGLint * num_config);

参数 attrib_list 指定了选择配置时需要参照的属性。参数 configs 将返回一个按照 attrib_list 排序的平台有效的所有 EGL framebuffer 配置列表。参数 config_size 指定了可以返回到 configs 的总配置个数。参数 num_config 返回了实际匹配的配置总数。
下面是如果使用 eglChoosetConfig() 函数的例子:


EGLint attrs[3] = { EGL_DEPTH_SIZE, 16, EGL_NONE };
EGLint num_configs;
EGLConfigs *configs_list;
// Get the display device
if ((eglDisplay = eglGetDisplay(EGL_NO_DISPLAY)) == EGL_NO_DISPLAY)
{
return eglGetError();
}
// Initialize the display
if (eglInitialize(eglDisplay, NULL, NULL) == EGL_FALSE)
{
return eglGetError();
}
// Obtain the total number of configurations that match
if (eglChooseConfig(eglDisplay, attrs, NULL, 0, &num_configs) == EGL_FALSE)
{
return eglGetError();
}
configs_list = malloc(num_configs * sizeof(EGLConfig));
if (configs_list == (EGLConfig *)0)
return eglGetError();
// Obtain the first configuration with a depth buffer of 16 bits
if (!eglChooseConfig(eglDisplay, attrs, &configs_list, num_configs, &num_configs))
{
return eglGetError();
}

 

下面是官方提供的Config属性各标准与默认值。

下面一个初始化EGL的例子,包括:

EGLBoolean initializeWindow(EGLNativeWindow nativeWindow)

{

const EGLint configAttribs[] =

{

EGL_RENDER_TYPE, EGL_WINDOW_BIT,

EGL_RED_SIZE, 8,

EGL_GREEN_SIZE, 8,

EGL_BLUE_SIZE, 8,

EGL_DEPTH_SIZE, 24,

EGL_NONE

};

const EGLint contextAttribs[] =

{

EGL_CONTEXT_CLIENT_VERSION, 2,

EGL_NONE

};

EGLDisplay dpy;

dpy = eglGetNativeDispay(EGL_DEFAULT_DISPLAY);

if(dpy == EGL_NO_DISPLAY)

{

return EGL_FALSE;

}

EGLint major, minor;

if(!eglInitialize(dpy, &major, &minor))

{

return EGL_FALSE;

}

EGLConfig config;

EGLint numConfigs;

if(!eglChooseConfig(dpy, configAttribs, &config, 1,

&numConfigs)) {

return EGL_FALSE;

}

EGLSurface window;

window = eglCreateWindowSurface(dpy, config, nativeWindow, NULL);

if(window == EGL_NO_SURFACE)

{

return EGL_FALSE;

}

EGLContext context;

context = eglCreateContext(dpy, config, EGL_NO_CONTEXT,

contextAttribs);

if(context == EGL_NO_CONTEXT)

{

return EGL_FALSE;

}

if(!eglMakeCurrent(dpy, window, window, context))

{

return EGL_FALSE;

}

return EGL_TRUE;

}

 


. eglSwapBuffers接口实现说明

一般性嵌入式平台

 

利用双缓冲进行Swap的时候,DisplaySurface进行实际意义上的地址交换,来实现eglSwapBuffers的标准, 如上图的右侧所示。

上图的左侧表示,单缓冲Framebuffer的形式,Surface永远都在后端, 显示的永远是Display,在GPU出现后已不使用。

 

Android平台

为了实现eglSwapBufferseglSurface其实代表了一个从NativeWindow 申请到的一个BufferDequeue操作)。当调用eglSwapBuffers时,对于一般应用窗口而言,NativeWindow将该SurfaceBuffer 提交回去给SurfaceFlingerQueue操作),然后又重新从NativeWindow中重新Dequeue出来一个新的BuffereglSurface。而eglDisplay并不代表实际的意义。我们只是从接口上感觉是,surfacedisplay进行了交换。

 

4.   一个有趣现象的EGL分析

 

Android系统对于window也就是surface的管理是很严格的,因为一片nativewindow申请的内存其实是很大的。1080*1920的一个显示RGBA32 buffer就在8M左右。当应用在后台时,nativewindow申请的buffer一般都会回收掉。

我们经常发现有的游戏,或者应用的界面,在转到Android系统后台再返回前台时, 界面或者游戏内容还在, 这是如何做到的呢?

 

EGL的角度来看,这个问题就变得很简单。

EglContext代表了OpenGL的状态机, 只要eglContext还在就表明OpenGL的管线状态还在保持,只是渲染流程停止了。所以程序在后台时,eglContext还在。

EglSurface代表了渲染目标后端的Buffer, 当应用和游戏进入后台时,当前的EglContextEglSurface进行了解绑。eglSurfaceBuffer将被回收。

当应用或游戏重新进入前台时, 新的EglSurface将从NativeWindow中申请出来(新的Buffer),此时只要利用eglMakeCurrent重新把之前的eglContext和新的EglSurface进行绑定就可以了,渲染线程继续运行,界面马上又绘制出来。

 

eglDisplayeglContexteglSurface解绑的API

eglMakeCurrentdisplay0,  00

调用后,虽然之前的eglContext还在, 但此时调用OpenGL API将不起作用。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台