Wayland文档翻译:第3章. Wayland 架构

x 与 Wayland 架构

Wayland 渲染 为 Wayland 启用硬件支持

X 与 Wayland 架构

了解 Wayland 架构以及它与 X 的区别的一个好方法是从输入设备的事件开始,一直到该事件影响的变化显示在屏幕上的点。

目前我们在 X 方面所处的情况如下:

图3.1. X 架构图

X architecture diagram

  1. 对于 X 来说,事件首先由内核从输入设备获取,并通过 evdev 输入驱动程序发送到 X 服务器。在此过程中,内核通过驱动设备并将不同设备特定的事件协议转换为 Linux evdev 输入事件标准来执行所有的重要工作。
  2. 然后,X 服务器确定事件影响的窗口,并将其发送到已选择在该窗口上为所讨论的事件进行操作的客户端。实际上,X 服务器并不知道如何正确执行此操作,因为窗口在屏幕上的位置由合成器控制,可能会以 X 服务器不理解的多种方式进行变换(缩小、旋转、抖动等)。
  3. 客户端检查事件并决定如何处理。通常情况下,界面会根据事件进行更改 - 例如,复选框被单击,或者指针进入必须突出显示的按钮。因此,客户端向 X 服务器发送了一个渲染请求。
  4. 当 X 服务器接收到渲染请求后,会将其发送到驱动程序,以便让驱动程序编程硬件进行渲染。X 服务器还计算渲染的边界区域,并将其作为损伤事件发送到合成器。
  5. 损伤事件告诉合成器窗口中的某些内容发生了变化,需要重新合成屏幕上显示该窗口的部分。合成器负责根据其场景图和 X 窗口的内容呈现整个屏幕内容。然而,它必须通过 X 服务器进行渲染。
  6. X 服务器接收来自合成器的渲染请求,并将合成器的后备缓冲区复制到前端缓冲区,或进行页面翻转。在一般情况下,X 服务器必须执行此步骤,以便可以处理重叠的窗口,这可能需要进行剪切,并确定是否可以进行页面翻转。然而,对于始终全屏的合成器来说,这是另一个不必要的上下文切换。

如上所示,这种方法存在一些问题。X 服务器没有信息来决定哪个窗口应该接收事件,也无法将屏幕坐标转换为窗口本地坐标。尽管 X 已将最终屏幕的绘制责任交给了合成器,但 X 仍然控制前端缓冲区和模式设置。X 服务器过去处理的大部分复杂性现在都可以在内核或自包含的库(KMS、evdev、mesa、fontconfig、freetype、cairo、Qt 等)中找到。总体而言,X 服务器现在只是一个中间人,在应用程序和合成器之间引入了一个额外的步骤,并在合成器与硬件之间引入了一个额外的步骤。

在 Wayland 中,合成器是显示服务器。我们将 KMS 和 evdev 的控制权交给了合成器。Wayland 协议允许合成器将输入事件直接发送到客户端,并允许客户端将损伤事件直接发送到合成器。

图3.2. Wayland 架构图

Wayland architecture diagram

  1. 内核获取事件并将其发送给合成器。这与 X 的情况类似,这非常好,因为我们可以重用内核中的所有输入驱动程序。
  2. 合成器通过其场景图查找应该接收事件的窗口。场景图对应于屏幕上的内容,合成器了解可能已经应用于场景图中元素的变换。因此,合成器可以选择正确的窗口,并通过应用反向变换将屏幕坐标转换为窗口本地坐标。可以应用于窗口的变换类型仅受到合成器的限制,只要它可以为输入事件计算反向变换。
  3. 与 X 的情况类似,当客户端接收到事件时,它会根据事件更新用户界面。但在 Wayland 的情况下,渲染发生在客户端中,客户端只需向合成器发送请求,指示已更新的区域。
  4. 合成器收集来自其客户端的损伤请求,然后重新合成屏幕。然后,合成器可以直接发出 ioctl 以与 KMS 安排页面翻转。

 

Wayland 渲染

在上述概述中,我忽略了一个细节,即在 Wayland 下客户端如何实际进行渲染。通过从图像中移除 X 服务器,我们也去掉了 X 客户端通常渲染的机制。但是,我们已经在 X 下使用 DRI2 时使用了另一种机制:直接渲染。通过直接渲染,客户端和服务器共享视频内存缓冲区。客户端链接到诸如 OpenGL 之类的渲染库,该库知道如何编程硬件,并直接渲染到缓冲区中。合成器可以将缓冲区作为纹理使用,将其合成到桌面上。在初始设置之后,客户端只需要告诉合成器使用哪个缓冲区,以及何时何地渲染了新内容。

这给应用程序留下了两种更新其窗口内容的方式:

方式一:将新内容渲染到一个新的缓冲区,并告诉合成器使用新的缓冲区代替旧的缓冲区。应用程序每次需要更新窗口内容时都可以分配一个新的缓冲区,或者可以保留两个(或更多)缓冲区并在它们之间进行循环。缓冲区的管理完全由应用程序控制。

方式二:将新内容渲染到之前告诉合成器要使用的缓冲区中。尽管可以直接在与合成器共享的缓冲区中进行渲染,但这可能会与合成器竞争。可能发生的情况是,重新绘制窗口内容可能会被合成器重新绘制桌面所打断。如果应用程序在清除窗口之后但在渲染内容之前被中断,合成器将会从空白缓冲区获取纹理。结果是应用程序窗口会在空白窗口和部分渲染内容之间闪烁。避免这种情况的传统方法是将新内容渲染到后备缓冲区,然后从那里复制到合成器表面。后备缓冲区可以即时分配,并且足够大以容纳新内容,或者应用程序可以保留一个缓冲区。同样,这也在应用程序控制之下。

无论哪种情况,应用程序都必须告诉合成器表面的哪个区域包含新内容。当应用程序直接渲染到共享缓冲区时,合成器需要注意到有新内容。但是当交换缓冲区时,合成器不会假设有任何变化,并且需要应用程序的请求才会重新绘制桌面。即使应用程序将新缓冲区传递给合成器,仍然可能只有一个小部分缓冲区是不同的,比如一个闪烁的光标或一个旋转的加载图标。

硬件支持

对于 Wayland 来说通常包括模式设置/显示以及 EGL/GLES2。此外,Wayland 还需要一种在进程之间高效共享缓冲区的方法。这方面有两个方面,客户端和服务器端。

在客户端方面,我们定义了一个 Wayland EGL 平台。在 EGL 模型中,这包括本机类型(EGLNativeDisplayType、EGLNativeWindowType 和 EGLNativePixmapType)以及创建这些类型的方法。换句话说,它是将 EGL 堆栈及其缓冲区共享机制与通用 Wayland API 绑定在一起的胶水代码。EGL 堆栈预计会提供 Wayland EGL 平台的实现。完整的 API 可以在 wayland-egl.h 头文件中找到。Mesa EGL 堆栈中的开源实现在 wayland-egl.c 和 platform_wayland.c 中。

在底层,EGL 堆栈预计会定义一个供客户端侧 EGL 堆栈与合成器通信的特定供应商协议扩展,以便共享缓冲区。wayland-egl.h API 的目的是将其抽象出来,只需让客户端为 Wayland 表面创建 EGLSurface 并开始渲染即可。开源堆栈使用 drm Wayland 扩展,该扩展允许客户端发现要使用的 drm 设备并进行身份验证,然后与合成器共享 drm(GEM)缓冲区。

Wayland 的服务器端是合成器和核心 UX,通常将任务切换器、应用程序启动器、锁屏集成在一个单块应用程序中。服务器在模式设置 API(内核模式设置、OpenWF Display 或类似方式)之上运行,并使用 EGL/GLES2 合成器和硬件覆盖层(如果可用)来合成最终的 UI。使模式设置、EGL/GLES2 和覆盖生效是标准硬件引导的一部分。Wayland 启用的额外要求是 EGL_WL_bind_wayland_display 扩展,它允许合成器从通用 Wayland 共享缓冲区创建 EGLImage。这类似于 EGL_KHR_image_pixmap 扩展,它用于从 X pixmap 创建 EGLImage。

该扩展有一个设置步骤,您需要将 EGL 显示绑定到 Wayland 显示。然后,当合成器从客户端接收到通用的 Wayland 缓冲区(通常在客户端调用 eglSwapBuffers 时),它将能够将 struct wl_buffer 指针传递给 eglCreateImageKHR 作为 EGLClientBuffer 参数,并将 EGL_WAYLAND_BUFFER_WL 作为目标。这将创建一个 EGLImage,然后合成器可以将其用作纹理,或将其传递给模式设置代码以用作覆盖平面。同样,这是由供应商特定的协议扩展实现的,在服务器端,该扩展将接收有关共享缓冲区的驱动程序特定详细信息,并在用户调用 eglCreateImageKHR 时将其转换为 EGL 图像。