3. EGL 核心概念
EGL 是 OpenGL ES 与底层原生窗口系统之间的“胶水层”。它不负责绘制三角形,而是负责回答三个关键问题:画到哪里、用什么规则画、画完如何呈现。要理解 EGL,必须先厘清它的一组核心术语:Display、Config、Context、Surface,以及 Surface 的几种具体形态——Window Surface、Pbuffer、Pixmap。它们之间不是并列关系,而是层层依赖、协同工作的对象模型。
一、核心对象逐一定义
Display(显示连接):代表 EGL 与底层窗口系统之间的一个连接。在 Android 上通常对应默认显示设备,在 X11 上对应一个 X Display 连接,在 Wayland 上对应 wl_display。它是一切 EGL 资源的根句柄,创建 Config、Context、Surface 之前必须先获得可用的 Display。多屏或离屏渲染场景下,可能存在多个 Display。
Config(配置):描述帧缓冲的“规格清单”,包括颜色缓冲位数(R/G/B/A)、深度缓冲位数、模板缓冲位数、多重采样样本数、Surface 类型(WINDOW / PBUFFER / PIXMAP)、渲染 API(OpenGL ES 版本)等。Config 不是画布本身,而是创建 Context 和 Surface 时必须匹配的“能力契约”。选择 Config 的本质是:在硬件支持范围内,找到满足渲染需求且性能合适的一组参数。
Context(上下文):保存 OpenGL ES 的全部渲染状态,例如当前绑定的着色器程序、纹理、混合模式、视口、矩阵栈等。可以把 Context 理解为“画笔和调色板的状态集合”。Context 本身不存储像素,它必须绑定到一个 Surface 才能进行绘制。一个 Context 在同一时刻只能绑定到一个绘制 Surface,但可以切换绑定到不同 Surface。
Surface(表面):真正承载像素的缓冲区,是渲染输出的目标。EGL 中 Surface 是一个抽象概念,具体分为 Window Surface、Pbuffer、Pixmap 三类。Context 通过 eglMakeCurrent 与 Surface 绑定后,OpenGL ES 的绘制命令才会写入该 Surface 对应的缓冲区。
二、三种 Surface 形态对比
Window Surface 是“可上屏”的表面,通常由原生窗口创建,支持双缓冲与 Swap 呈现,适合实时显示。Pbuffer 是“离屏像素缓冲”,不直接显示,常用于离屏渲染、生成纹理、GPU 计算中间结果。Pixmap 是“绑定到原生像素图的表面”,在部分平台上用于将渲染结果直接写入系统位图,但现代移动平台支持有限,Android 上基本不使用。
| 术语 | 本质 | 是否可显示 | 典型用途 | 关键 API |
|---|---|---|---|---|
| Display | 与窗口系统的连接 | — | 获取 EGL 资源入口 | eglGetDisplay / eglInitialize |
| Config | 帧缓冲规格模板 | — | 匹配 Context/Surface 能力 | eglChooseConfig |
| Context | 渲染状态集合 | 否 | 保存 GL 状态、执行绘制 | eglCreateContext |
| Window Surface | 可上屏双缓冲表面 | 是 | 实时画面显示 | eglCreateWindowSurface |
| Pbuffer | 离屏像素缓冲 | 否 | 离屏渲染、纹理生成 | eglCreatePbufferSurface |
| Pixmap | 原生像素图表面 | 否 | 写入系统位图(平台受限) | eglCreatePixmapSurface |
三、对象关系与生命周期
下图展示了 EGL 核心对象之间的依赖与绑定关系。Display 是根,Config 由 Display 查询得到;Context 和 Surface 都依赖 Config 创建;Context 通过 eglMakeCurrent 绑定到 Surface 后,OpenGL ES 才能向该 Surface 写入像素;Window Surface 通过 eglSwapBuffers 将后台缓冲提交到屏幕。
四、典型初始化流程与代码骨架
下面是一段典型的 EGL 初始化骨架,展示了各术语在代码中的出现顺序。注意每一步的失败处理都不可省略,尤其是 Config 匹配失败和 Context 创建失败,在低端设备或多版本 GL 共存时非常常见。
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, &major, &minor);
const EGLint configAttribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 16,
EGL_NONE
};
EGLConfig config;
EGLint numConfigs;
eglChooseConfig(display, configAttribs, &config, 1, &numConfigs);
const EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLContext context = eglCreateContext(display, config,
EGL_NO_CONTEXT, contextAttribs);
EGLSurface surface = eglCreateWindowSurface(display, config,
nativeWindow, NULL);
eglMakeCurrent(display, surface, surface, context);
// ... OpenGL ES 绘制 ...
eglSwapBuffers(display, surface);
五、常见误区与复盘要点
- 误区一:把 Config 当成 Surface。Config 只是规格描述,不持有像素;真正持有像素的是 Surface。选择 Config 时若
EGL_SURFACE_TYPE未包含EGL_WINDOW_BIT,后续创建 Window Surface 会直接失败。 - 误区二:Context 可以脱离 Surface 绘制。没有
eglMakeCurrent绑定 Surface 时,任何 GL 绘制命令都不会产生有效输出,甚至触发错误。 - 误区三:Pbuffer 与 FBO 混为一谈。Pbuffer 是 EGL 层面的离屏表面,FBO 是 GL 层面的帧缓冲对象。两者都能离屏渲染,但层级不同,现代应用中 FBO 更常用,Pbuffer 多用于跨上下文共享或特定平台需求。
- 误区四:忘记
eglSwapBuffers。Window Surface 默认双缓冲,绘制完成后必须 Swap,否则屏幕看不到新内容。 - 复盘建议:排查 EGL 问题时,按 Display → Config → Context → Surface → MakeCurrent → Swap 的顺序逐段检查返回值,并打印
eglGetError(),能快速定位是配置不匹配、上下文版本冲突还是表面创建失败。