3. Wayland协议入门
在把 OpenGL ES 渲染结果送上屏幕的过程中,Wayland 扮演的是“窗口系统与显示服务”的角色。它并不是一个图形 API,而是一套基于对象与请求的通信协议:客户端把“我要一块表面、我要提交一帧、我要移动窗口”等意图编码成请求,通过 Unix 域套接字发送给 compositor;compositor 处理后回送事件。理解这套对象模型,是后续把 EGL 表面、GL 渲染与 Wayland 表面正确绑定的前提。
一、Wayland 的整体架构
Wayland 采用典型的 C/S 架构,但与传统 X11 的“客户端直接画到显存、窗口管理器旁路”不同,Wayland 中只有 compositor 拥有最终合成与上屏的权力。客户端不直接访问显示硬件,而是把渲染好的缓冲区交给 compositor,由 compositor 决定如何合成、缩放、裁剪并提交给 DRM/KMS。
协议层由若干接口(interface)组成,每个接口有请求(request)和事件(event)。协议描述文件(XML)经 wayland-scanner 生成客户端与服务端胶水代码,最终在运行时通过 wl_display 这条连接进行消息收发。因此,Wayland 的“架构”可以理解为三层:协议定义层、连接与对象层、后端合成与显示层。
二、compositor 与 client 的角色分工
Compositor(合成器)是 Wayland 显示服务端,通常同时承担显示服务器、窗口管理器与合成器的职责。它负责:监听 socket、维护全局对象、接收客户端请求、管理表面生命周期、执行合成、通过 DRM/KMS 完成上屏,并把输入事件分发给对应客户端。常见的实现有 Weston、Mutter、KWin、Sway 等。
Client(客户端)是应用程序进程,例如一个使用 OpenGL ES 渲染的播放器或游戏。它通过 wl_display_connect() 建立连接,用 wl_registry 发现服务端能力,创建 wl_surface,把渲染好的缓冲区 attach 到表面并 commit。客户端不直接控制窗口位置与最终合成,这些由 compositor 决定。
关键认知:Wayland 客户端提交的是“表面内容 + 状态”,而不是“屏幕上的最终像素”。窗口装饰、缩放、层叠、裁剪都由 compositor 完成。这与 X11 客户端可直接绘制到窗口的模型有本质区别。
三、wl_display 与 wl_surface 核心对象
wl_display 是客户端与 compositor 之间的连接对象,也是所有其他对象的“根”。它持有 socket 文件描述符、对象 ID 映射表与事件队列。典型初始化流程如下:
struct wl_display *display = wl_display_connect(NULL);
if (!display) {
fprintf(stderr, "无法连接 Wayland compositor\n");
return -1;
}
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_roundtrip(display); /* 等待全局对象事件到达 */
/* 从 registry 绑定 wl_compositor 后创建表面 */
struct wl_surface *surface = wl_compositor_create_surface(compositor);
wl_surface 是可绘制表面对象,代表一块可以被合成的内容区域。它本身不持有像素,像素来自客户端 attach 的缓冲区(wl_buffer,常由 wl_shm 或 EGL 的 wl_drm/linux-dmabuf 提供)。表面通过 commit 把“待生效状态”变为“已提交状态”,compositor 才会使用新内容合成。
在 OpenGL ES 集成中,通常先用 wl_egl_window_create(surface, width, height) 把 wl_surface 包装成 EGL 原生窗口,再调用 eglCreateWindowSurface() 得到 EGLSurface。之后 GL 渲染完成,通过 eglSwapBuffers() 提交,EGL 实现内部会完成 buffer attach 与 surface commit。
四、对象关系与典型调用顺序
| 对象 / 接口 | 角色 | 典型操作 | 与 OpenGL ES 的关系 |
|---|---|---|---|
wl_display |
连接与对象入口 | connect、roundtrip、dispatch | EGLDisplay 的底层连接来源 |
wl_registry |
全局对象发现 | bind、监听 global 事件 | 获取 wl_compositor 等能力 |
wl_compositor |
表面工厂 | create_surface、create_region | 创建 EGL 窗口所需的 wl_surface |
wl_surface |
可绘制表面 | attach、damage、commit | 被包装为 wl_egl_window,承载 GL 帧 |
wl_buffer |
像素缓冲区 | 由 shm 或 dmabuf 创建 | EGL swap 时提交的像素载体 |
五、案例场景:一个最小 Wayland + GL 客户端
假设我们要在 Weston 上显示一个用 GL 清屏为青色的窗口,核心步骤是:连接 wl_display → 获取 registry → 绑定 wl_compositor 与 xdg_wm_base → 创建 wl_surface → 创建 xdg_surface 并设置标题 → 用 wl_egl_window_create 包装 → eglCreateWindowSurface → eglMakeCurrent → glClearColor 与 glClear → eglSwapBuffers。此时 EGL 会完成 buffer attach 与 commit,compositor 收到新内容后合成上屏。
调试提示:若 wl_display_connect 返回 NULL,先检查 WAYLAND_DISPLAY 环境变量与 XDG_RUNTIME_DIR 是否指向正确的 socket;若表面创建成功但窗口不显示,检查是否遗漏了 xdg_surface 的初始 commit 或 wl_surface_commit。
六、常见误区与复盘
- 误区一:把 wl_surface 当成“窗口”。它只是可绘制表面,窗口语义由
xdg_surface等角色扩展提供。 - 误区二:认为客户端可以直接上屏。最终合成与 page flip 只发生在 compositor 内部。
- 误区三:忽略 roundtrip。绑定全局对象后必须等待事件到达,否则可能拿到空指针。
- 误区四:混淆 EGL 与 Wayland 的提交时机。
eglSwapBuffers会触发 Wayland 侧的 attach/commit,但帧回调(frame callback)仍需按需使用以配合合成节奏。
复盘来看,掌握 Wayland 的关键不是记住所有接口,而是建立“连接—对象—表面—提交—合成”这条主线:wl_display 负责连接与对象管理,wl_surface 负责承载像素与状态,compositor 负责最终合成与显示。把这条主线与 EGL/OpenGL ES 的初始化、渲染、交换流程对齐,后续的缓冲区共享、帧同步与多输出适配就有了清晰的落点。