4. EGL 初始化流程

EGL 是 OpenGL ES 与本地窗口系统之间的桥梁。在真正创建渲染表面、绑定上下文之前,必须先完成一套标准初始化流程。这套流程的核心目的可以概括为三件事:拿到显示连接、确认 EGL 可用并获取版本信息、筛选出符合当前渲染需求的帧缓冲配置。本节围绕五个关键 API 的调用顺序与参数含义展开,帮助你在实际集成中避免“顺序错、参数错、配置错”三类高频问题。

一、核心原理:为什么必须按顺序调用

EGL 的初始化不是一组可以随意调换的独立函数,而是一条有依赖关系的链路。eglGetDisplay 返回的 EGLDisplay 是后续所有操作的句柄;eglInitialize 负责初始化该显示连接并回传 EGL 版本号;eglQueryString 用于查询扩展、厂商、客户端 API 等字符串信息;eglGetConfigs 获取系统支持的全部配置列表;eglChooseConfig 则根据属性数组筛选出最匹配的配置。没有 Display 就无法 Initialize,没有 Initialize 就无法可靠地查询字符串或选择配置,没有 Config 就无法创建 Surface 与 Context。

调用顺序记忆口诀:先拿 Display,再 Initialize,查字符串,列配置,选配置。任何一步失败,都应立即停止并检查错误码,而不是继续向下调用。

二、五个 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 返回实际匹配数量。

EGL 初始化调用链与依赖关系 Display → Initialize → QueryString → GetConfigs → ChooseConfig eglGetDisplay 获取 EGLDisplay EGL_DEFAULT_DISPLAY eglInitialize 初始化显示连接 输出 major / minor eglQueryString 查询厂商/版本/扩展 EGL_VENDOR / EXTENSIONS eglGetConfigs 枚举全部配置 configs 可为 NULL eglChooseConfig 按属性数组筛选配置 attrib_list 以 EGL_NONE 结尾 依赖关系说明: ① 无 Display → 无法 Initialize;② 未 Initialize → QueryString 结果不可靠; ③ GetConfigs 只负责“列出全部”,ChooseConfig 才负责“按需筛选”;④ 任一失败应停止并检查 eglGetError()。 常见错误码 EGL_NO_DISPLAY / EGL_NOT_INITIALIZED EGL_BAD_ATTRIBUTE / EGL_BAD_CONFIG 关键属性 EGL_RENDERABLE_TYPE EGL_SURFACE_TYPE / 颜色位深

三、方法流程:从零到可用配置的完整步骤

  1. 获取显示连接:调用 eglGetDisplay,传入平台相关的显示标识。Android 上通常为 EGL_DEFAULT_DISPLAY;若返回 EGL_NO_DISPLAY,说明本地显示不可用。
  2. 初始化 EGL:调用 eglInitialize,传入 Display 以及两个 EGLint 指针用于接收主次版本号。返回 EGL_FALSE 时,应通过 eglGetError 获取错误码。
  3. 查询字符串信息:调用 eglQueryString 获取厂商、版本、扩展列表、客户端 API 等。这一步常用于判断是否支持 EGL_KHR_create_context 等扩展。
  4. 枚举全部配置:先以 NULL 调用 eglGetConfigs 获取配置数量,再分配数组并再次调用,得到全部 EGLConfig。
  5. 筛选目标配置:构造属性数组,调用 eglChooseConfig。属性数组必须包含渲染类型、表面类型、颜色分量、深度/模板位深等,并以 EGL_NONE 结束。
  6. 校验结果:检查 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 定位问题,避免错误被掩盖到后续阶段。
复盘建议:将初始化流程封装为独立函数,按“Display → Initialize → QueryString → GetConfigs → ChooseConfig”顺序执行,并在每一步失败时输出错误码与上下文信息。这样在跨设备适配时,可以快速定位是显示连接问题、版本问题还是配置筛选问题。

掌握这五个 API 的调用顺序与参数含义,是 EGL 渲染集成的第一道门槛。顺序正确、参数准确、配置匹配,后续的 Surface 创建、Context 绑定与交换缓冲区才能稳定进行。