屏幕上可见的帧缓冲区由一个像素数据的二维数组表示。直接在可显示缓冲区上更新像素由一个严重的问题——用户在部分更新帧缓冲区时看到伪像或者闪烁的现象
为了解决这个问题,引入了双缓冲区。
OpenGLES应用程序中,这种活动通过EGL函数eglSwapBuffers控制:
eglSwapBuffers(exContext->eglDisplay, esContext->eglSurface);
EGL提供以下机制:
与设备的原生窗口系统通信
查询绘图表面的可用类型和配置
创建绘图表面
在OpenGLES3.0和其它图形渲染API(如桌面OpenGL和OpenVG——硬件加速矢量图形的跨平台API,活着窗口系统的原生绘图命令)之间同步渲染
管理纹理贴图等渲染资源
与窗口系统通信
Apple提供自己的EGL API的iOS 实现,称为EAGL
因为每个窗口系统都有不同语义,所以EGL提供基本的不透明类型——EGLDisplay,该类封装了所有系统相关性,用于和原生窗口系统接口交互。
任何使用EGL的应用程序必须执行的第一个操作是创建和初始化与本地EGL显示的连接。
一、初始化EGL
EGLConfig config;
EGLint majorVersion;
EGLint minorVersion;
EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE
};
// 获取宽和高
esContext->width = ANativeWindow_getWidth(esContext->eglNativeWindow);esContext->height = ANativeWindow_getHeight(esContext->eglNativeWindow);
// 打开与EGL显示服务器的连接,默认为EGL_DEFAULT_DISPLAY
esContext->eglDisplay = eglGetDisplay(esContext->eglNativeDisplay);
// 如果显示连接不可用,则返回EGL_NO_DISPLAY标志,此时无法创建窗口
if (esContext->eglDisplay == EGL_NO_DISPLAY) {
return GL_FALSE;
}
// 初始化egl并回传版本号
if (!eglInitialize(esContext->eglDisplay, &majorVersion, &minorVersion)) {
return GL_FALSE;
}
成功打开连接后,需要初始化EGL,这通过调用如下函数完成:
EGLAPI EGLBoolean EGLAPIENTRY eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);
dpy —— 指定EGL显示连接
major —— 返回EGL的主版本号
minor —— 返回EGL的副版本号
二、确定可用表面配置
一旦初始化了EGL,就可以确定可用渲染表面的类型和配置,有两种方法:
查询每个表面配置,找出最好的选择
指定一组需求,让EGL推荐最佳配置
通常使用第二种方法更简单,而且最有可能得到匹配。
任何一种情况下,EGL将返回一个EGLConfig,这是包含有关特定表面及其特性(每个颜色变量分量的位数、与EGLConfig相关的深度缓冲区(如果存在))的EGL内部数据结构的标识符。
EGLint numConfigs = 0;
EGLint attribList[] = {
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_ALPHA_SIZE, (flags & ES_WINDOW_ALPHA) ? 8 : EGL_DONT_CARE,
EGL_DEPTH_SIZE, (flags & ES_WINDOW_DEPTH) ? 8 : EGL_DONT_CARE,
EGL_STENCIL_SIZE, (flags & ES_WINDOW_STENCIL) ? 8 : EGL_DONT_CARE,
EGL_SAMPLE_BUFFERS, (flags & ES_WINDOW_MULTISAMPLE) ? 1 : 0,
EGL_RENDERABLE_TYPE, getContextRenderableType(esContext->eglDisplay),
EGL_NONE
};
// 选择Config
if (!eglChooseConfig(esContext->eglDisplay, attribList, &config, 1, &numConfigs)) {
return GL_FALSE;
}
if (numConfigs < 1) {
return GL_FALSE;
}
三、查询EGLConfig属性
EGLConfig包含关于EGL启用的表面的所有信息。可用颜色、与配置相关的其它缓冲区(深度和模板缓冲区等)、表面类型和其它特性
// Android需要获取EGL_NATIVE_VISUAL_ID的值并将其放如ANativeWindow_setBuffersGeometry函数中
EGLint format = 0;
eglGetConfigAttrib(esContext->eglDisplay, config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(esContext->eglNativeWindow, 0, 0, format);
四、创建屏幕上的渲染区域:EGL窗口
EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface(EGLDisplay dpy,
EGLConfig config,
EGLNativeWindowType win,
const EGLint *attrib_list);
该函数用于创建一个窗口,以我们得到的原生显示管理器的连接和前一步得到的EGLConfig为参数。它需要原生窗口系统事先创建一个窗口。
创建窗口示例:
// 创建一个EGL窗口表面示例
EGLint list[] = {
EGL_RENDER_BUFFER, EGL_BACK_BUFFER, EGL_NONE
};
EGLSurface window = eglCreateWindowSurface(esContext->eglDisplay, config,
esContext->eglNativeWindow, list);
if (window == EGL_NO_SURFACE) {
switch (eglGetError()) {
case EGL_BAD_MATCH:
break;
case EGL_BAD_CONFIG:
break;
case EGL_BAD_NATIVE_WINDOW:
break;
case EGL_BAD_ALLOC:
break;
}
}
上面的代码是创建一个绘图场景,但是我们还必须完成两个步骤,才能成功地用OpenGLES绘图。然而窗口不是唯一可用的渲染表面,也可以使用屏幕外渲染区域,也就是离屏渲染。
屏幕外渲染区域(离屏渲染):EGL Pbuffer
OpenGLES3.0除了可以进行屏幕渲染,也可以在不可见的屏幕外渲染,也就是在像素缓冲区上进行渲染,支持硬件加速。Pbuffer最常用于纹理贴图。
创建Pbuffer 和 创建EGL窗口非常相似,只有少数微笑的不同。
创建Pbuffer需要和窗口一样找到EGLConfig,并作一些修改:需要扩增EGL_SURFACE_TYPE的值,使其包含EGL_PBUFFER_BIT。
创建EGL像素缓冲区示例如下:
EGLint bufferList[] = {
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
};
const EGLint MaxConfigs = 10;
EGLConfig configs[MaxConfigs];
EGLint numConfigs;
if (!eglChooseConfig(esContext->eglDisplay, bufferList, configs, MaxConfigs, &numConfigs)) {
return GL_FALSE;
}
EGLSurface pbuffer;
EGLint attribufferList[] = {
EGL_WIDTH, 512,
EGL_HEIGHT, 512,
EGL_LARGEST_PBUFFER, EGL_TRUE,
EGL_NONE
};
pbuffer = eglCreatePbufferSurface(esContext->eglDisplay, config, attribufferList);
if (pbuffer == EGL_NO_SURFACE) {
switch (eglGetError()) {
case EGL_BAD_ALLOC:
break;
case EGL_BAD_CONFIG:
break;
case EGL_BAD_PARAMETER:
break;
case EGL_BAD_MATCH:
break;
}
}
// 检查pbuffer 分配的内存大小
EGLint width;
EGLint height;
if (!eglQuerySurface(esContext->eglDisplay, pbuffer, EGL_WIDTH, &width)
|| !eglQuerySurface(esContext->eglDisplay, pbuffer, EGL_HEIGHT, &height)) {
// 不能查询 surface的信息
return GL_FALSE;
}
五、创建渲染上下文
渲染上下文是OpenGLES3.0的内部数据结构,包含操作所需要的所有状态信息。它包含了对顶点着色器和片元着色器数据的引用。
// 5.创建上下文
esContext->eglContext = eglCreateContext(esContext->eglDisplay, config,
EGL_NO_CONTEXT, contextAttribs);
// 判断上下文是否创建成功
if (esContext->eglContext == EGL_NO_CONTEXT) {
return GL_FALSE;
}
// 根据之前的配置构建上下文
if (!eglMakeCurrent(esContext->eglDisplay, esContext->eglSurface, esContext->eglSurface, esContext->eglContext)) {
return GL_FALSE;
}
六、指定某个EGLContext为当前上下文
// 指定某个EGLContext为当前上下文
if (!eglMakeCurrent(esContext->eglDisplay, esContext->eglSurface,
esContext->eglSurface, esContext->eglContext)) {
return GL_FALSE;
}
同步渲染
多个图形API在单个窗口中的渲染,此时需要使用同步渲染,需要应用程序允许多个库渲染到共享窗口。
如果不止使用OpenGLES3.0渲染,则不能简单地调用glFinish来抱着个所有渲染已经发生,你可以调用:
EGLBoolean eglWaitClient()
延迟客户端的执行,直到某个API的所有渲染完成,成功返回EGL_TRUE,失败时返回EGL_FALSE并发送EGL_BAD_CURRENT_SURFACE错误
同样,如果你需要保证原生窗口系统的渲染完成,则调用如下函数:
EGLBoolean eglWaitNative(EGLint engine)
engine,指定渲染程序等待渲染完成