4. OpenGL ES基础

在 Wayland 显示集成中,OpenGL ES 并不是孤立存在的。Wayland 负责窗口、输入与缓冲提交,而 OpenGL ES 负责把像素画进缓冲区。两者之间的桥梁是 EGL:它把本地窗口系统(Wayland 的 wl_surface)与图形 API 的上下文连接起来。理解 EGL 上下文创建、渲染管线、着色器与缓冲区对象,以及版本差异,是后续实现 Wayland 客户端渲染的基础。

一、EGL 上下文创建:把 Wayland 表面接入 GL

EGL 的核心职责是管理显示连接、配置、表面和上下文。典型流程是:先通过 eglGetPlatformDisplay 绑定 Wayland 显示,再初始化 EGL,选择匹配的配置,创建窗口表面,最后创建上下文并设为当前。Wayland 下常用 EGL_PLATFORM_WAYLAND_KHR,并把 wl_display 作为原生显示传入。

EGLDisplay dpy = eglGetPlatformDisplay(
    EGL_PLATFORM_WAYLAND_KHR, wl_display, NULL);
eglInitialize(dpy, &major, &minor);

EGLConfig cfg;
EGLint attr[] = {
    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_NONE
};
eglChooseConfig(dpy, attr, &cfg, 1, &n);

EGLSurface surf = eglCreateWindowSurface(
    dpy, cfg, (EGLNativeWindowType)wl_egl_window, NULL);
EGLContext ctx = eglCreateContext(
    dpy, cfg, EGL_NO_CONTEXT, ctx_attr);
eglMakeCurrent(dpy, surf, surf, ctx);

这里的关键点是:wl_egl_window 是 Wayland 提供的 EGL 原生窗口封装,它把 wl_surface 与 EGLSurface 关联起来。若没有它,EGL 无法知道该把渲染结果提交到哪个 Wayland 表面。上下文创建失败时,应优先检查 eglGetError 返回的错误码,常见原因包括配置不匹配、缺少 EGL_KHR_platform_wayland 扩展、或未正确设置 EGL_RENDERABLE_TYPE。

在 Wayland 合成器中,客户端通常使用 EGL 渲染到 wl_egl_window,再通过 wl_surface_commit 提交。若使用软件渲染,则可能走 wl_shm 路径,但本文聚焦 GL 路径。

二、渲染管线:从顶点到像素的固定阶段

OpenGL ES 的渲染管线可以理解为一条从顶点数据到帧缓冲像素的流水线。可编程阶段主要是顶点着色器和片元着色器;固定阶段包括图元装配、光栅化、逐片元操作等。顶点着色器负责把顶点从模型空间变换到裁剪空间,片元着色器负责计算每个片元的颜色。两者之间通过 varying(GLSL ES 3.0 中为 in/out)传递插值数据。

OpenGL ES 渲染管线与 EGL 上下文 顶点数据 VBO / VAO 顶点着色器 MVP 变换 图元装配 三角形 / 线段 光栅化 生成片元 片元着色器 纹理 / 光照 逐片元操作 深度 / 混合 帧缓冲 FBO / 默认 EGLSurface wl_egl_window EGL 负责创建上下文与表面;GL 管线负责把顶点数据转换为帧缓冲像素,最终提交到 Wayland 表面

管线中每个阶段都有明确的输入输出。顶点着色器输出裁剪空间坐标,光栅化把图元离散为片元,片元着色器输出颜色,逐片元操作决定最终写入帧缓冲的值。理解这条链路,有助于定位“画面不显示”“颜色异常”“深度错误”等问题。

三、着色器与缓冲区对象:数据如何进入 GPU

着色器是运行在 GPU 上的小程序,使用 GLSL ES 编写。顶点着色器通常处理位置、法线、纹理坐标;片元着色器通常处理纹理采样、光照计算和颜色混合。缓冲区对象(VBO)用于存储顶点属性数据,顶点数组对象(VAO)则记录属性指针与 VBO 的绑定关系,减少重复设置。

// 顶点着色器(GLSL ES 3.0)
#version 300 es
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoord;
out vec2 vTexCoord;
uniform mat4 uMVP;
void main() {
    gl_Position = uMVP * vec4(aPos, 1.0);
    vTexCoord = aTexCoord;
}

// 片元着色器
#version 300 es
precision mediump float;
in vec2 vTexCoord;
out vec4 fragColor;
uniform sampler2D uTexture;
void main() {
    fragColor = texture(uTexture, vTexCoord);
}

创建 VBO 与 VAO 的典型步骤是:glGenBuffers 生成缓冲区,glBindBuffer 绑定目标,glBufferData 上传数据;然后 glGenVertexArrays 生成 VAO,绑定后设置 glVertexAttribPointer 并启用属性。绘制时只需绑定 VAO 并调用 glDrawArrays 或 glDrawElements。

常见误区:在 GLSL ES 3.0 中仍使用 attribute 和 varying,会导致编译失败;片元着色器缺少 precision 声明也可能报错。另一个误区是忘记 glEnableVertexAttribArray,导致属性未启用而画面空白。

四、版本差异与选择:GLES 2.0 与 3.0 的取舍

OpenGL ES 2.0 与 3.0 在语法和功能上有明显差异。2.0 使用 attribute/varying,3.0 使用 in/out;3.0 支持整数纹理、实例化、多重渲染目标等特性。选择版本时,需要综合考虑设备覆盖率、驱动兼容性和功能需求。

对比项 OpenGL ES 2.0 OpenGL ES 3.0
着色器变量 attribute / varying in / out
纹理格式 基础 RGBA 整数、浮点、深度纹理
实例化渲染 需扩展 原生支持
MRT 需扩展 原生支持
设备覆盖 极广 较广,旧设备可能缺失

在 Wayland 集成中,若目标设备较新,优先选择 GLES 3.0 以获得更好的性能和功能;若需要兼容旧设备,则使用 GLES 2.0 并配合扩展。EGL 配置中的 EGL_RENDERABLE_TYPE 必须与所选版本匹配:2.0 用 EGL_OPENGL_ES2_BIT,3.0 用 EGL_OPENGL_ES3_BIT_KHR。创建上下文时,通过 EGL_CONTEXT_CLIENT_VERSION 指定版本。

实践建议:在 Wayland 客户端启动时,先查询 eglQueryString 获取扩展列表,确认是否支持 EGL_KHR_platform_wayland 与目标 GLES 版本,再决定渲染路径。这样可以在不同合成器与驱动上保持稳定。

总结来看,EGL 负责把 Wayland 表面与 GL 上下文连接起来,渲染管线负责把顶点数据转换为像素,着色器与缓冲区对象负责数据进入 GPU 的方式,而版本选择则决定了功能边界与兼容策略。掌握这四部分,才能在 Wayland 上构建稳定、可移植的 OpenGL ES 渲染客户端。