简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .

[TOC]


本文初发于 “偕臧的小站“,同步转载于此。


Wayland 架构

若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。

这就是我们现在对 X 的理解。

  1. 内核是从一个输入设备中获取一个事件,并通过 evdev 输入驱动将其发送到 X。内核通过驱动设备将不同设备的事件协议翻译成 linux evdev 输入事件标准,依靠此来完成所有的工作。
  2. X 服务器确定事件影响到哪个窗口,并将其发送给在该窗口上选择了相关事件的客户端。X 服务器实际上并不知道如何正确地做这件事,因为窗口在屏幕上的位置是由合成器控制的,并且可能会以很多 X 服务器不理解的方式进行转换(缩小、旋转、晃动等)。
  3. 客户端依据这个事件决定该如何操作。通常情况下,UI 通常根据事件来改变,也许是一个复选框被点击了,或者指向了一个必须被显示的按钮。因此,客户端会向X服务器发送一个渲染请求。
  4. 当X服务器收到渲染请求时,它将其发送给驱动程序,让它对硬件进行编程以进行渲染。X服务器还计算出渲染的边界区域,并将其作为damage事件发送给合成器。
  5. damage事件告诉合成器,窗口中发生了一些变化,它必须重新合成该窗口可见的屏幕部分。合成器负责根据它的场景图和X窗口的内容来渲染整个屏幕内容。且它是必须通过X服务器来渲染。
  6. X服务器接收到来自合成器的渲染请求后,要么将合成器的后缓冲区复制到前缓冲区,要么进行翻页。在一般情况下,X服务器必须做这一步,这样它就可以考虑到重叠的窗口,这可能需要剪接,并确定是否可以翻页。然而,对于一个总是全屏的合成器来说,这又是一个不必要的上下文切换。

如上所述,这种方法都有瑕疵。X服务器没有信息来决定哪个窗口应该接收事件,也不能将屏幕坐标转换为窗口局部坐标。而且即使X已经把最终绘制屏幕的责任交给了合成管理器,X仍然控制着前面的缓冲区和模式设置。过去X服务器处理的大部分复杂问题,现在都可以在内核或自带的库中找到(KMS、evdev、mesa、fontconfig、freetype、cairo、Qt等)。总的来说,X服务器现在只是一个中间人,在应用程序和合成器之间多引入了一步,在合成器和硬件之间多引入了一步。

在wayland中,合成器就是显示服务器。我们把KMS和evdev的控制权转移到合成器上。wayland协议让合成器直接向客户端发送输入事件,让客户端直接向合成器发送损坏事件。

  1. 内核获得一个事件并将其发送到合成器。这类似于X的情况,这很好,因为我们可以重复使用内核中所有的输入驱动。
  2. 合成器通过它的场景图来决定哪个窗口应该接收这个事件。场景图对应于屏幕上的内容,而合成器了解它可能已经对场景图中的元素转换。因此合成器可以选择合适的窗口,并通过应用逆向变换,将屏幕坐标转换为窗口局部坐标。只要能计算出输入事件的逆向变换,就可以应用于窗口的变换类型只限于合成器能做的事情。
  3. 如在X案例中,当客户端接收到事件时,它会更新UI作为响应。但在wayland的情况下,渲染发生在客户端,客户端只需向合成器发送一个请求,指示被更新的区域。
  4. 合成器收集客户端的damage请求,然后重新合成屏幕效果。且合成器可以直接发出ioctl,与KMS一起进行翻页。

Wayland 渲染

在上面的概述中,有漏掉了一个细节,那就是客户端在wayland下究竟是如何渲染的。通过将X服务器从画面中移除,我们也移除了X客户端通常的渲染机制。但是我们在X下的DRI2已经使用了另一种机制:直接渲染。通过直接渲染,客户端和服务器共享一个视频内存缓冲区。客户端链接到一个渲染库,比如OpenGL,它知道如何对硬件进行编程,并直接渲染到缓冲区。而合成器则可以在合成桌面时,将缓冲区作为纹理使用。在初始设置之后,客户端只需要告诉合成器使用哪个缓冲区,以及何时何地的将新内容渲染到缓冲区中。

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

  1. 把新的内容渲染到一个新的缓冲区里 然后告诉合成器使用哪个缓冲区来代替旧的缓冲区。应用程序可以在每次需要更新窗口内容时分配一个新的缓冲区,也可以保留两个(或更多)缓冲区并在它们之间循环。缓冲区的管理完全由应用程序控制。
  2. 将新的内容渲染之前告诉合成器使用的缓冲区。虽然可以直接渲染到合成器共享的缓冲区中,但这可能会与合成器发生竞赛。可能发生的情况是,重新绘制窗口内容可能会被合成器重新绘制桌面所中断。如果应用程序在清除窗口后但在渲染内容之前被打断,那么合成器将从一个空白缓冲区中进行纹理处理。其结果是,应用程序窗口将在空白窗口或半渲染的内容之间闪烁。避免这种情况的传统方法是将新的内容渲染到后置缓冲区,然后从那里复制到合成器表面。后面的缓冲区可以即时分配,并且刚好足够容纳新内容,或者应用程序可以保留一个缓冲区。同样这也是由应用程序所控制的。

无论在哪种情况下,应用程序都必须告诉合成器表面的哪个区域存放新内容。当应用程序直接渲染到共享缓冲区时,需要注意到合成器有新内容。但在交换缓冲区时,合成器也不会认为有什么变化,只有需要应用程序提出请求才会重新绘制桌面。即使应用程序传递给合成器一个新的缓冲区,也可能只有一小部分缓冲区是不同的,比如一个闪烁的光标或一个旋转器。


Wayland的 硬件支持

通常情况下,硬件启用包括 modesetting / display 和 EGL / GLES2。除此之外,Wayland还需要一种在进程之间高效共享缓冲区的方式。这有两个方面,客户端和服务器端。

在客户端,我们定义了一个Wayland EGL平台。在EGL模型中,它由原生类型(EGLNativeDisplayType、EGLNativeWindowType和EGLNativePixmapType)和创建这些类型的方法组成。换句话说,它是将EGL栈及其缓冲区共享机制与通用Wayland API绑定的耦合代码。EGL堆栈有望提供Wayland EGL平台的实现。完整的API在wayland-egl.h头中。mesa EGL协议栈中的开源实现在platform_wayland.c

在引擎下,EGL堆栈寄希望于被一个特定厂商的协议扩展所定义,让客户端EGL堆栈与合成器通信缓冲区细节,以及共享缓冲区。wayland-egl.h API的意义在于将这些抽象掉,让客户端为Wayland曲面创建一个EGLSurface并开始渲染。开源栈使用了 drm Wayland 扩展,让客户端发现要使用的 drm 设备并进行认证,然后与合成器共享 drm(GEM)缓冲区。

Wayland的服务器端是垂直领域的合成器和核心用户体验,典型的是将任务切换器、应用启动器、锁屏整合在一个单体应用中。服务器运行在模式设置 API(内核模式设置、OpenWF Display或类似)之上,并使 EGL / GLES2 合成器和硬件覆盖(如果有的话)混合合成最终的UI。启用模式设置、EGL/GLES2和覆盖是标准硬件启动的一部分。启用Wayland的额外要求是EGL_WL_bind_wayland_display扩展,它允许合成器从通用Wayland共享缓冲区创建EGLImage。它类似于EGL_KHR_image_pixmap扩展,可从X像素图创建EGLImage。

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