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 的“架构”可以理解为三层:协议定义层、连接与对象层、后端合成与显示层。

Wayland 架构:client 与 compositor 通过 wl_display 交换对象消息 Client 进程(应用 / OpenGL ES 程序) wl_display 连接代理:socket、对象注册表、事件分发 wl_registry 发现全局接口 wl_compositor 创建 surface wl_surface 可绘制表面:attach / damage / commit EGLSurface / GL 渲染目标 eglCreateWindowSurface(display, config, wl_egl_window, attribs) request(请求) event(事件) Unix 域套接字 Compositor 进程(Weston / Mutter / KWin) wl_display(服务端) 监听 socket、解析请求、派发事件 全局对象管理 wl_compositor / wl_shm / wl_seat / xdg_wm_base surface 状态机 pending → committed → 合成 合成与上屏 GL / Vulkan 合成 → DRM/KMS page flip 输出到显示器 核心关系:wl_display 是连接与对象入口;wl_surface 是像素提交的载体;compositor 决定最终显示结果

二、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, &registry_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 的初始化、渲染、交换流程对齐,后续的缓冲区共享、帧同步与多输出适配就有了清晰的落点。