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。
wl_egl_window,再通过 wl_surface_commit 提交。若使用软件渲染,则可能走 wl_shm 路径,但本文聚焦 GL 路径。二、渲染管线:从顶点到像素的固定阶段
OpenGL ES 的渲染管线可以理解为一条从顶点数据到帧缓冲像素的流水线。可编程阶段主要是顶点着色器和片元着色器;固定阶段包括图元装配、光栅化、逐片元操作等。顶点着色器负责把顶点从模型空间变换到裁剪空间,片元着色器负责计算每个片元的颜色。两者之间通过 varying(GLSL ES 3.0 中为 in/out)传递插值数据。
管线中每个阶段都有明确的输入输出。顶点着色器输出裁剪空间坐标,光栅化把图元离散为片元,片元着色器输出颜色,逐片元操作决定最终写入帧缓冲的值。理解这条链路,有助于定位“画面不显示”“颜色异常”“深度错误”等问题。
三、着色器与缓冲区对象:数据如何进入 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。
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 指定版本。
eglQueryString 获取扩展列表,确认是否支持 EGL_KHR_platform_wayland 与目标 GLES 版本,再决定渲染路径。这样可以在不同合成器与驱动上保持稳定。总结来看,EGL 负责把 Wayland 表面与 GL 上下文连接起来,渲染管线负责把顶点数据转换为像素,着色器与缓冲区对象负责数据进入 GPU 的方式,而版本选择则决定了功能边界与兼容策略。掌握这四部分,才能在 Wayland 上构建稳定、可移植的 OpenGL ES 渲染客户端。