LWN:Linux图形栈简介,第二部分!

关注了就能看到更多这么棒的文章哦~

The Linux graphics stack in a nutshell, part 2

December 28, 2023

This article was contributed by Thomas Zimmermann
ChatGPT translation
https://lwn.net/Articles/955708/

在屏幕上显示应用程序的图形化输出内容需要在各个部分之间正确进行同步的合成和模式设置(mode setting),而且要保持低开销。在本系列的第二篇(也是最后一篇)文章中,我们将深入探讨 Linux 图形堆栈的这些组成部分。在第一篇中,我们跟随图形处理路径,从应用程序开始,经过 Mesa,同时利用内核的Direct Rendering Manager(DRM)子系统的内存管理功能,我们最终将应用程序的图形数据存储在一个输出缓冲区中,所以现在是将这个图像展示给用户的时候了。

Compositing

用户空间应用几乎从不自己显示输出内容,而是指示屏幕合成器(screen compositor)执行此操作。合成器是一个系统服务,它接收每个应用的输出缓冲区并将它们绘制成为屏幕上的一个图像。这个布局(layout)取决于合成器的实现,但堆叠和平铺 (stacking and tiling)是两种最常见的布局。合成器还负责收集用户输入并将其转发给当前焦点的应用程序。

合成工作,还有图形栈中几乎所有其他工作,曾经都是由X Window System提供。它实现了用于在屏幕上显示图形的网络协议。由于包括绘图、模式设置、屏幕共享,甚至打印在内的所有其他部分,X存在着软件膨胀的问题,并且难以适应图形硬件和 Linux 系统的演进;因此需要一种轻量级的替代方案。它的现代继任者是Wayland,这是另一个客户端-服务器方式的方案,其中每个应用程序充当合成器提供的显示服务的客户端。Wayland 提供的参考合成器是Weston,但 GNOME的Mutter或 KDE的KWin 则更常用。

在 Wayland 中没有绘图或打印功能;它的协议仅提供了合成工作所需的功能。Wayland surface 代表应用程序窗口;它是应用程序用来显示其输出并接收合成器输入事件的句柄。连接到 surface 的是包含可显示的像素数据以及颜色格式和大小信息的 Wayland 缓冲区。像素数据位于客户端应用程序已渲染到的输出缓冲区中。更改 surface 所关联的缓冲对象或其内容就会让应用程序向合成器发送 Wayland 协议的 surface-damage 消息,该消息会更新屏幕内容;也许会使用新缓冲对象里的内容。应用程序的输出缓冲区变成了 Wayland 合成器的输入缓冲区。

合成器中的渲染与第一部分中的应用程序所述的方式完全相同。合成器维护一个包含了表示应用程序窗口的所有 Wayland surface 的列表。这些窗口和合成器的界面元素形成了另一个场景图(scene graph)。背景包含了壁纸图像、背景图案或颜色。在背景之上,合成器绘制应用程序窗口。实现这一点的最简单方法是为每个窗口绘制一个矩形,并使用应用程序提供的缓冲对象作为纹理图像。

在应用程序窗口之上,合成器会绘制自己的用户界面,例如用户可以用来与合成器本身进行交互的任务栏。最后,最顶层是指示用户当前正在与之交互的内容的指示器(indicator);在桌面系统上通常是一个鼠标指针。与应用程序一样,合成器使用常规的用户空间接口进行渲染,例如 Mesa 的 OpenGL 或 Vulkan。

使所有这些成为可能的最后一个组成部分就是缓冲对象的传输机制。与 X 不同,Wayland 应用程序始终在与其合成器相同的 host 主机上运行。因此,具体实现代码可以自由针对此情况进行优化:不需要进行网络编码、缓冲区压缩等等。

针对传输驻留在系统内存中的缓冲对象,应用程序创建一个文件描述符,该文件描述符引用缓冲区的内存,将其发送到连接对应的 stream 套接字上(是通过一个开销很低的消息完成的),并允许合成器将文件描述符的内存页面映射到其地址空间。现在应用程序和合成器已经建立了一个低开销的通道,用于交换像素数据。应用程序绘制到共享内存区域,合成器根据这个区域的内容来进行渲染。在实践中,通常使用多个缓冲对象进行双缓冲。Wayland 的 surface-damage 消息就是低开销的同步方法。

对于软件渲染来说,通过共享内存传输数据已经足够好了,但是对于高性能的硬件渲染来说,这还是不足够的。应用程序必须在图形硬件上进行渲染,并通过缓慢的硬件总线将结果读取回共享内存区域。

为了避免这方面的性能损失,图形缓冲区必须保留在图形内存中。Wayland 提供了一个协议扩展,通过 Linux dma-buf 共享缓冲区对象,它代表一块可在硬件设备、驱动程序和用户空间程序之间共享的内存缓冲区。应用程序通过使用第一部分文章中描述的 Mesa 接口以硬件加速的方式来渲染相应的场景图(scene graph),但是,并不会传输关联到共享内存的应用,而是由应用程序发送一个 dma-buf 对象,该对象引用了仍然位于图形内存区域里的缓冲区对象(buffer object)。Wayland 合成器使用存储好的像素数据,而无需通过硬件总线读取它。

硬件加速渲染本质上是异步的,因此需要管理好同步。在应用程序向 Mesa 发送了当前帧的最终渲染命令之后,并不保证硬件已经完成渲染。这是有意为之的设计,也是为了实现高性能而必需的设计。但在硬件完成渲染之前让合成器显示缓冲区对象的内容就会导致输出内容失真(distorted output)。为了避免这种情况发生,硬件在完成渲染时会发出信号,这被称为设置围栏(fencing),与之相关的数据结构就称为围栏(fence)。围栏附加到应用程序传输给合成器的 dma-buf 对象上。合成器在使用生成出来的最终数据之前会等待围栏代表的完成信号。

Pixels to the monitor

在生成用于屏幕显示的图像之后,合成器必须要把它显示出来给用户看。DRM 的 mode-setting 代码就控制了包括从显存读取像素数据到发送给输出设备的所有工作了。流水线中的每一级都代表了一个硬件功能,共同完成像素处理以及发送给显示器。

所需的最少流水线阶段包括了帧缓冲(framebuffer)、平面(plane)、CRTC、编码器(encoder)和连接器(connector),下面对每个阶段都进行了描述。对于能正常工作的显示输出来说,每个阶段都必须至少有一个活跃实例才行。但大多数硬件提供的功能都是超过了最低限度的,并允许随时启用和禁用流水线中的各个阶段。DRM 框架提供了每个阶段的软件抽象,驱动程序可以基于其来开发。

流水线的第一个阶段是 DRM 帧缓冲(framebuffer)。这是一个缓冲区对象,用于存储合成器生成的屏幕图像,以及有关图像颜色格式和大小的信息。每个 DRM 驱动程序使用此信息对硬件进行配置,并让硬件能找到缓冲区对象的第一个字节,以便硬件知道在哪里找到像素数据。

获取像素数据的动作称为扫描输出(scanout),像素数据的缓冲区对象就被称为扫描输出缓冲区(scanout buffer)。每个帧缓冲的扫描输出缓冲区的数量取决于帧缓冲的颜色格式(color format)。许多格式,如常见的RGB格式,将所有像素数据存储在单个缓冲区中。对于其他格式,如基于YUV的格式,像素数据可能需要拆分成多个缓冲区。

根据硬件的能力,帧缓冲可以大于或小于输出的显示模式(display mode)。例如,如果显示器设置为 1920x1080 像素,它可能仅显示远大于此的帧缓冲的一部分。或者,如果帧缓冲比显示模式小,它可能仅能覆盖监视器的一小部分区域,留下一些区域为空白。因此,流水线的下一个阶段确定了整个屏幕中的扫描输出缓冲区的位置。在 DRM 术语中,这称为平面(plane)。它设置扫描输出缓冲区的位置、方向和缩放比例。根据硬件的不同,可能存在使用不同帧缓冲的多个活动平面(active plane)。所有活动平面将其像素输出传递到流水线的第三个阶段,也就是由于历史原因被称为阴极射线管控制器(CRTC, cathode-ray tube controller)的阶段。

CRTC 控制与显示模式设置相关的一切。DRM 驱动程序使用显示模式对 CRTC 硬件进行编程,并将其关联到所有活动平面和输出上。还可以有多个采用不同设置的 CRTC。确切的配置仅受硬件功能的限制。

平面是堆叠起来的(stacked),因此它们可以重叠或覆盖输出的不同部分。根据编程的显示模式和每个平面的位置,CRTC 硬件从平面中提取像素数据,在必要时把重叠的平面融合起来(blend),并将结果转发到输出去。

输出(output)是由编码器(encoder)和连接器(connector)表示的。正如其名称所示,编码器是用于输出的像素数据进行编码的硬件组件。编码器与特定连接器相关联,表示与输出设备的物理连接,例如连接好显示器的 HDMI 或 VGA 端口。连接器还提供有关输出设备支持的显示模式、物理分辨率、颜色空间等信息。同一 CRTC 上的输出在不同的输出设备上镜像显示 CRTC 的屏幕。

下图显示了一个包含额外平面(用于鼠标指针)的简单的模式设置流水线(mode-setting pipeline),以及作为扫描输出缓冲区的缓冲区对象。箭头指示了从缓冲区对象到 VGA 连接器的像素数据的逻辑流动路径。这是一个比较古老的独立显卡的典型模式设置流水线。

2d69f95c6556b352de45626cdf852451.jpeg

Pipeline setup

连接方式以及配置模式设置流水线各个阶段的设置策略,不是由 DRM 驱动程序完成的。这部分被留给了用户空间程序,我们再回到合成器来看。作为合成器初始设置的一部分,它会打开 /dev/dri 这里的设备文件,例如 /dev/dri/card1 ,并调用了相应的 ioctl() 来编配置显示流水线。它还从连接器获取可用的显示模式并选择一个合适的显示模式。

在合成器完成渲染第一个屏幕图像后,它首次为模式设置流水线进行编程。为此,它为屏幕图像的缓冲区对象创建了一个帧缓冲(framebuffer),并将帧缓冲关联到一个平面(plane)上。然后,它在 CRTC 上设置其屏幕显示的缓冲区的显示模式,把从帧缓冲到连接器的流水线的所有阶段连接起来,并启用显示功能。

要在下一帧中改变所显示的图像,不需要进行完整的模式设置(mode setting)。合成器只需用新的帧缓冲替换当前的帧缓冲就好。这被称为页面翻转(page flipping)。

模式设置流水线的各个阶段可以用多种方式连接起来。一个 CRTC 可能会被镜像到多个编码器(encoder),或者一个帧缓冲可能被多个 CRTCs 扫描输出。虽然这提供了灵活性,但也意味着并非所有的流水线阶段组合都是可以支持的。

一个符合直觉的实现可能会逐个去独立设置每个阶段。首先在 CRTC 中设置好显示模式,然后将所有缓冲区对象上传到图形内存,接着设置用于扫描输出的帧缓冲和平面,最后启用编码器和连接器。如果在此过程中发生任何错误,屏幕都会保持关闭状态的或者(更糟糕的是)可能内容处于不一致状态(distorted state)。比如,如果仅有非常少的内存,那么设备里可能无法同时存储超过一个平面的帧缓冲。因此切换模式或者甚至是简单的页面翻转(page flip)动作,都可能会失败。自从图形堆栈出现以来,显示更新失败(failing display updates)一直是一个常见的问题。

DRM 的原子模式设置(atomic mode setting)在一定程度上解决了这个问题。模式设置代码在称为 drm_atomic_state 的一个复合数据结构中,用来跟踪流水线上所有元素的完整状态(complete state),以及其中每个阶段的子状态(sub-state)。这种模式设置是原子方式的,因为它要么会把流水线上所有阶段的完整复合状态应用生效,要么就不会采用任何状态改变。为了能按这个方式来正常工作,模式设置涉及两个阶段:首先对新的 complete atomic state 进行检查;然后在成功检查后才提交(commit)这个状态。

在检查阶段,DRM 核心代码、辅助程序和 DRM 驱动程序会将提议的新状态与可用的图形硬件中限制和约束进行测试。例如,一个平面必须验证起关联的帧缓冲是否能够兼容这个颜色格式,而 CRTC 必须验证给定的显示分辨率是否受到硬件支持。如果检查成功,DRM 驱动程序会在提交(commit)阶段将新状态设置到硬件里去。如果某个阶段的状态检查失败,DRM 将停止模式设置操作并向用户空间程序返回错误。

因此,当我们的合成器打算配置新的显示模式时,它会设置流水线中所有阶段的原子状态,并一次性应用生效。如果成功,显示输出就会进行相应地更改。对于连续的页面翻转(page flip)操作,合成器会把当前状态复制出来一份,将帧缓冲更改为新的帧缓冲,并应用新的状态。再次应用页面翻转会引发内核 DRM 代码中的原子检查/原子提交系列处理(atomic-check/atomic-commit sequence),但它的开销要比完整做一次 mode-setting 操作要小。

DRM 的状态检查阶段是独立于硬件状态的,不会修改硬件状态。如果检查原子状态的动作失败了,合成器会收到一个错误代码,但显示输出保持不变。合成器还可以在提交之前验证原子状态。从而允许预先创建出一组可以支持的配置的列表。

关于原子模式设置的内部工作原理在 2015 年的 LWN 中已经详细介绍过:[第一部分](https://lwn.net/Articles/653071/) 和 [第二部分](https://lwn.net/Articles/653466/)。

额外特性

在讨论到平面的时候,是假设硬件的所有平面都是相同的。但情况并非总是如此。通常有一个平面被称为主平面(primary plane),采用 RGB 方式的颜色格式,涵盖整个显示区域。合成器会设置主平面以显示它为屏幕准备的图像。

但大多数硬件还提供了一个用于鼠标指针的附加平面,称为光标平面(cursor plane)。该平面仅覆盖一个小区域,并浮动在主平面之上。正如其名称所示,合成器使用光标平面来显示鼠标指针图像,而不需要更改主平面的屏幕图像。

在主平面和光标平面之外,还有叠加平面(overlay plane),它们的大小各不相同,通常支持类似 YUV 的颜色格式。这使它们可以使用很低的 CPU 开销来显示视频数据流。为此,视频播放器应用程序向合成器提供包含基于 YUV 的像素数据的缓冲对象。

合成器使用像素数据的帧缓冲设置叠加平面(overlay plane)。该平面会扫描输出 YUV 像素数据并在硬件中执行颜色转换为 RGB。使用 dma-buf,视频播放器可以直接将单个 YUV 帧从硬件视频解码器转发到合成器去,从而将整个视频处理交给硬件。

如果显示更新的延迟是关键问题,那么将模式设置功能移交给单个应用程序可能会有所帮助。因此,合成器将该功能出租给应用程序。在应用程序拥有活动 DRM 的所属权租约时,它完全控制模式设置流水线。这对于需要紧密协调内部显示器的输出频率和延迟以使 3D 视觉效果正常工作的 3D 头戴设备非常有用。DRM 租约会过期或被撤销,因此合成器最终仍然掌握模式设置的控制权。

虽然现代合成器采用 Wayland 作为协议,但 X 窗口系统的应用程序仍然很常见。Xwayland 是在 Wayland 会话中运行的 X 服务器。它通过在 Wayland 和 X 协议之间进行转换,使 X 应用程序能够透明地参与 Wayland 会话。这在大多数情况下都工作得很好。

Xwayland 无法模拟的常见场景是屏幕捕捉和屏幕共享。X应用程序可以访问 X 会话的整个窗口树(window tree),这使得屏幕捕获变得更加容易。但是出于安全目的,Wayland 协议不允许应用程序读取屏幕或其他应用程序的窗口。因此,Wayland 合成器提供了专用的实现方案来捕获或共享屏幕内容。PipeWire、VNC或RDP通常就是用于这方面的。

如果没有活跃的合成器,Linux 将显示文本控制台。DRM 支持内核的帧缓冲控制台(framebuffer console)用于文本输出。这种 DRM fbdev 模拟就像来自用户空间的 DRM 客户端一样,但完全在内核中运行。它还提供了跟旧的帧缓冲相同的接口,例如 /dev/fb0 。然而,Fbdev 和 DRM 的 fbdev 模拟正在逐渐退出舞台。有想法 打算将大部分控制台功能移至用户空间。

在撰写本文时,Linux 图形的一个迅速发展的主题是高动态范围(HDR, High Dynamic Range)渲染,它通过显示更丰富的颜色和光照来展示通常在传统渲染中丢失的细节。支持这一功能将使 Linux 能够满足专业图形艺术家的需求。目前,支持水平不一,但已经可以在游戏中使用HDR,而 Linux 桌面也开始实施HDR。

到目前为止,我们已经追踪了将应用程序内容放到现代 Linux 图形栈中屏幕上的路径——从渲染和内存管理到合成和模式设置。但我们只是触及了表面。这个图形栈仍在不断发展,并不断增加新功能和硬件的支持。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

format,png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值