4. EGL 初始化流程
EGL 是 OpenGL ES 与本地窗口系统之间的桥梁。在真正创建渲染表面、绑定上下文之前,必须先完成一套标准初始化流程。这套流程的核心目的可以概括为三件事:拿到显示连接、确认 EGL 可用并获取版本信息、筛选出符合当前渲染需求的帧缓冲配置。本节围绕五个关键 API 的调用顺序与参数含义展开,帮助你在实际集成中避免“顺序错、参数错、配置错”三类高频问题。
一、核心原理:为什么必须按顺序调用
EGL 的初始化不是一组可以随意调换的独立函数,而是一条有依赖关系的链路。eglGetDisplay 返回的 EGLDisplay 是后续所有操作的句柄;eglInitialize 负责初始化该显示连接并回传 EGL 版本号;eglQueryString 用于查询扩展、厂商、客户端 API 等字符串信息;eglGetConfigs 获取系统支持的全部配置列表;eglChooseConfig 则根据属性数组筛选出最匹配的配置。没有 Display 就无法 Initialize,没有 Initialize 就无法可靠地查询字符串或选择配置,没有 Config 就无法创建 Surface 与 Context。
二、五个 API 的调用顺序与参数含义
下面给出典型调用序列。注意:eglGetDisplay 的参数在不同平台上含义不同;eglInitialize 的版本号是输出参数;eglChooseConfig 的属性数组必须以 EGL_NONE 结尾。
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) { /* 处理错误 */ }
EGLint major = 0, minor = 0;
if (!eglInitialize(display, &major, &minor)) { /* 处理错误 */ }
const char* vendor = eglQueryString(display, EGL_VENDOR);
const char* version = eglQueryString(display, EGL_VERSION);
const char* exts = eglQueryString(display, EGL_EXTENSIONS);
EGLint configCount = 0;
eglGetConfigs(display, NULL, 0, &configCount);
EGLConfig* configs = (EGLConfig*)malloc(sizeof(EGLConfig) * configCount);
eglGetConfigs(display, configs, configCount, &configCount);
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 16,
EGL_NONE
};
EGLConfig chosen = NULL;
EGLint numConfig = 0;
if (!eglChooseConfig(display, attribs, &chosen, 1, &numConfig)) {
/* 处理错误 */
}
各参数的关键含义如下:eglGetDisplay 传入本地显示标识,Android 上通常使用 EGL_DEFAULT_DISPLAY;eglInitialize 的 major/minor 是输出参数,返回 EGL 实现支持的版本;eglQueryString 的第二个参数是查询项,常用 EGL_VENDOR、EGL_VERSION、EGL_EXTENSIONS、EGL_CLIENT_APIS;eglGetConfigs 的 configs 可为 NULL,此时只返回数量;eglChooseConfig 的 attrib_list 是“期望属性”,configs 是输出数组,config_size 是数组容量,num_config 返回实际匹配数量。
三、方法流程:从零到可用配置的完整步骤
- 获取显示连接:调用
eglGetDisplay,传入平台相关的显示标识。Android 上通常为EGL_DEFAULT_DISPLAY;若返回EGL_NO_DISPLAY,说明本地显示不可用。 - 初始化 EGL:调用
eglInitialize,传入 Display 以及两个EGLint指针用于接收主次版本号。返回EGL_FALSE时,应通过eglGetError获取错误码。 - 查询字符串信息:调用
eglQueryString获取厂商、版本、扩展列表、客户端 API 等。这一步常用于判断是否支持EGL_KHR_create_context等扩展。 - 枚举全部配置:先以
NULL调用eglGetConfigs获取配置数量,再分配数组并再次调用,得到全部EGLConfig。 - 筛选目标配置:构造属性数组,调用
eglChooseConfig。属性数组必须包含渲染类型、表面类型、颜色分量、深度/模板位深等,并以EGL_NONE结束。 - 校验结果:检查
num_config是否大于 0,并确认返回的EGLConfig非空。若为 0,说明没有配置满足属性要求,需要放宽条件。
四、参数关系与配置筛选对照表
下表整理了初始化流程中五个 API 的核心参数、方向与常见取值,便于快速查阅。
| API | 关键参数 | 方向 | 含义与常见取值 |
|---|---|---|---|
eglGetDisplay |
display_id |
输入 | 本地显示标识,Android 常用 EGL_DEFAULT_DISPLAY |
eglInitialize |
major / minor |
输出 | 返回 EGL 实现支持的主次版本号,如 1.4、1.5 |
eglQueryString |
name |
输入 | EGL_VENDOR、EGL_VERSION、EGL_EXTENSIONS、EGL_CLIENT_APIS |
eglGetConfigs |
configs / config_size / num_config |
输出 | configs 可为 NULL 仅查询数量;num_config 返回实际数量 |
eglChooseConfig |
attrib_list / configs / config_size / num_config |
输入/输出 | 属性数组以 EGL_NONE 结尾;num_config 返回匹配数量 |
五、案例场景:Android 上初始化 OpenGL ES 2.0
在 Android NDK 开发中,典型场景是使用 EGL_DEFAULT_DISPLAY 获取默认显示,然后初始化 EGL 1.4,查询扩展确认支持 EGL_KHR_create_context,最后选择支持 EGL_OPENGL_ES2_BIT 且颜色位深为 8/8/8/8、深度 16 的配置。若设备仅支持 ES 3.0,则应将 EGL_RENDERABLE_TYPE 改为 EGL_OPENGL_ES3_BIT_KHR,并检查扩展字符串中是否包含对应扩展。
// 查询扩展,判断是否支持 ES3
const char* exts = eglQueryString(display, EGL_EXTENSIONS);
if (strstr(exts, "EGL_KHR_create_context")) {
// 可使用 EGL_OPENGL_ES3_BIT_KHR
}
eglChooseConfig 之后打印实际选中的配置属性,例如 EGL_RED_SIZE、EGL_DEPTH_SIZE、EGL_SAMPLES,以便确认是否与预期一致。很多“画面颜色异常”或“深度测试失效”问题,根源都在配置选择阶段。六、误区与复盘
- 误区一:跳过 eglInitialize 直接查询字符串。 未初始化的 Display 上调用
eglQueryString可能返回NULL,导致后续判断失效。 - 误区二:把 eglGetConfigs 当作筛选工具。
eglGetConfigs只负责列出全部配置,不进行任何筛选;筛选必须使用eglChooseConfig。 - 误区三:属性数组忘记以 EGL_NONE 结尾。 这会导致未定义行为,甚至崩溃。务必检查数组最后一项。
- 误区四:忽略 num_config 为 0 的情况。 这说明没有配置满足要求,应放宽颜色位深、深度位深或渲染类型,而不是继续创建 Surface。
- 误区五:不检查 eglGetError。 每个 EGL 调用后都应检查返回值,失败时通过
eglGetError定位问题,避免错误被掩盖到后续阶段。
掌握这五个 API 的调用顺序与参数含义,是 EGL 渲染集成的第一道门槛。顺序正确、参数准确、配置匹配,后续的 Surface 创建、Context 绑定与交换缓冲区才能稳定进行。