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

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

The Linux graphics stack in a nutshell, part 1

December 19, 2023

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

Linux 图形开发者经常提到“现代”Linux 图形,指的是许多独立软件组件以及它们之间的相互的交互。其中包括由内核管理的显示资源、用于合成的 Wayland、加速 3D 渲染,并且不包括 X11。在这两篇系列文章中,我们将快速了解图形代码,看看它如何将应用程序数据转换为像素数据并显示在屏幕上。在这第一篇文章中,我们将关注应用程序渲染、Mesa 内部以及必要的内核功能。

应用程序渲染(Application rendering)

图形输出都来源于应用程序,它会处理和存储待可视化的格式化数据。可视化的常见数据结构是场景图(scene graph),它是一棵树状结构,每个节点存储 3D 空间中的模型(model)或其属性。模型节点包含要可视化的数据,例如游戏场景或科学模拟的元素。属性节点设置模型的方向或位置。每个属性节点都对其下的节点生效。为了将场景图渲染成屏幕图像,应用程序沿着树从上到下、从左到右遍历,设置或清除属性,并相应地渲染 3D 模型。

在下面的示例场景图中,渲染从根节点开始,该节点准备渲染器(renderer)并设置输出位置。应用程序首先沿着左侧分支渲染“Rectangle 1”,其位置为(0, 0),表面模式(surface pattern)存储在纹理(texture) 1 中。然后,应用程序返回到根节点并沿着右侧分支进入名为“Transform”的属性节点。应用程序使用 4x4 矩阵描述这个变换,例如摆放位置(positioning)或缩放(scaling),在渲染过程中应用生效。在示例中,变换节点将其所有子节点按 0.5 的比例缩放。因此,渲染“Rectangle 2”和“Rectangle 3”以它们原始大小的一半来显示,它们的位置调整为(10, 10)和(15, 15),分别使用不同的纹理 2 和 3。

b2e7243920956204ed4d6679c159f7cf.png

为了简化渲染并利用硬件加速,大多数应用程序使用标准 API 例如OpenGL或Vulkan 之一。两种 API 之间的细节有所不同,但每个 API 都提供接口来管理图形内存,填充数据,并渲染存储的信息。结果得到一个图像,应用程序可以直接显示或用作进一步处理的输入数据。

所有图形数据都存储在缓冲对象(buffer object)中,每个对象是附有句柄或 ID 的图形内存区域。例如,每个 3D 模型存储在顶点缓冲对象(vertex-buffer object)中,每个纹理存储在纹理缓冲对象(texture-buffer object)中,每个对象的表面法线(surface normal)存储在缓冲对象中,依此类推。输出图像本身也存储在缓冲对象中。因此,图形渲染在很大程度上是内存管理的一种实践。

应用程序可以以任何格式提供输入数据,只要图形着色器(graphics shader)能够处理它。着色器是包含将输入数据转换为输出图像的指令的程序,由应用程序提供并在图形卡(graphics card)上执行。

实际的着色器程序可能实现复杂的多通道算法,但在这个示例中,我们将其简化到最低要求。在着色器中,顶点变换和纹理查找可能是最常见的两种操作。我们可以将顶点视为多边形中的角(a corner of a polygon)。使用OpenGL着色语言(GLSL)编写,对顶点进行变换如下:

uniform mat4 Matrix; // 对所有矩形的顶点都相同
in vec4 inVertexCoord; // 每次调用包含不同的顶点坐标

gl_Position = Matrix * inVertexCoord;

变量 inVertexCoord 是来自应用程序场景图的输入坐标。变量 gl_Position 是应用程序输出缓冲区内的坐标。广义上说,前者的坐标位于显示的场景内,而后者的坐标位于应用程序窗口内。 Matrix 是描述两个坐标系之间变换的 4x4 矩阵。该着色器操作对场景图中的每个顶点运行。在上述应用程序场景图的示例中, inVertexCoord 至少对每个矩形的每个顶点都包含一次。然后, Matrix 包含该顶点的变换,例如将其移动到正确的位置或按照变换节点中指定的比例缩放为 0.5。

一旦顶点(vertices)被转换到输出坐标系统,着色器程序会计算所涵盖的“片段(fragments)”的值,这是图形行业术语,表示沿 Z 轴具有深度值的输出像素,可能还包含其他信息。每个片段都需要一种颜色。在 GLSL 中,着色器的texture()函数从纹理中检索颜色,如下所示:

uniform sampler2D Tex; // 当前矩形的纹理对象
in vec2 vsTexCoord; // 片段的插值纹理坐标

Color = texture(Tex, vsTexCoord);

在这里, Tex 表示纹理缓冲区。值 vsTexCoord 是纹理坐标,即在纹理内读取的位置。使用这两个值, texture() 返回一个颜色值。将其赋值给 Color 会将一个有颜色的像素写入输出缓冲区。为了用像素数据填充输出缓冲区,这段着色器代码对每个单独的片段运行。纹理缓冲区由正在绘制的模型指定,纹理坐标由 OpenGL 内部计算提供。对于示例场景图,应用程序为每个矩形使用该矩形的纹理缓冲区调用此代码。

在整个场景图上运行这些着色器指令将生成应用程序的完整输出图像。

Mesa

到目前为止,我们讨论的内容与 Linux 无关,但它包含了一个框架,可以让我们了解是如何实现。在 Linux 上,Mesa 3D库,简称 Mesa,实现了 3D 渲染接口和对各种图形硬件的支持。对于应用程序,它提供了用于桌面图形的 OpenGL 或 Vulkan,用于移动系统的OpenGL ES,以及用于计算的OpenCL。在硬件方面,Mesa 为大多数现代图形硬件实现了驱动程序。

Mesa 的驱动程序通常不会自己实现这些应用程序接口,因为 Mesa 包含大量辅助工具和抽象层。对于像 OpenGL 这样的有状态接口,Mesa的Gallium3D框架将接口和驱动程序连接在一起。这被称为状态跟踪器(state tracker)。Mesa 包含各种版本的 OpenGL、OpenGL ES 和 OpenCL 的状态跟踪器。当应用程序调用 API 时,它修改给定接口的状态跟踪器。

Mesa 内的硬件驱动程序进一步将状态跟踪器信息转换为硬件状态和渲染指令。例如,调用 OpenGL的glBindTexture()会选择 OpenGL 状态跟踪器中的当前纹理缓冲区。然后,硬件驱动程序会将纹理缓冲区对象安装到图形内存中,并将活动的着色器(active shader)程序关联到该缓冲区对象,以将其作为纹理来引用。在我们上面的示例中,纹理在着色器程序中变为 Tex。

Vulkan 是一个无状态接口,因此 Gallium3D 对于这些驱动程序无用;相反,Mesa 提供Vulkan运行时来帮助实现它们。但是,如果有 Vulkan 驱动程序可用,可能根本不需要基于 Gallium3D 的 OpenGL 支持就可以使用该硬件。Zink是一个将 Gallium3D 映射到 Vulkan 的 Mesa 驱动程序。使用 Zink,OpenGL 状态变为 Gallium3D 状态,然后通过标准 Vulkan 接口转发到硬件。原则上,这适用于任何硬件的 Vulkan 驱动程序。可以想象未来的 Mesa 驱动程序只实现 Vulkan,并依赖于 Zink 实现 OpenGL 兼容性。

除了 Gallium3D,Mesa 还为其硬件驱动程序提供了更多辅助工具,例如 winsys 或 GBM。Winsys 包装了窗口系统的细节。GBM,通用缓冲管理器,简化了缓冲对象分配。还有许多着色语言,例如 GLSL或SPIR-V,供应用程序使用。Mesa 将应用程序提供的着色器代码编译为“新接口表示 New Interface Representation”或 NIR,Mesa 驱动程序将其转换为硬件指令。为了让 Mesa 的硬件加速处理着色器指令和相关数据,它们的缓冲对象必须存储在图形卡可访问的内存位置。

内核内存管理

通常,任何可供图形硬件访问的内存都被称为图形内存;这是图形堆栈的中心资源,因为堆栈的所有组件都与其交互。在硬件一侧,图形内存有各种配置,从独立图形适配器上的专用内存到系统级芯片(SoC)板上的常规系统内存。在两个极端之间,还有具有 DMA或共享图形内存、离散设备的图形地址重映射表(GART)内存,或称为内置图形的所谓借用图形内存(stolen graphics memory)的图形芯片。

作为系统范围的资源,图形内存由内核的Direct Rendering Manager(DRM)子系统维护。为了访问 DRM 功能,Mesa 在/dev/dri 下打开图形卡的设备文件,比如/dev/dri/renderD128。按照用户空间的要求,DRM 以缓冲对象的形式公开图形内存,其中每个缓冲对象表示可用内存的一个切片。

DRM 框架为最常见的情况提供了许多内存管理器。用于 AMD、NVIDIA 和(即将推出的)Intel 独立图形卡的 DRM 驱动程序使用翻译表管理器(TTM)。它支持独立图形内存、GART 内存和系统内存。TTM 可以在这些区域之间移动缓冲对象,因此如果设备的独立内存填满,未使用的缓冲对象可以被交换到系统内存。

简单的帧缓冲(framebuffer)设备的驱动程序通常使用 SHMEM helper,它在共享内存中分配缓冲对象。在这里,常规系统内存充当设备有限资源的 shadow buffer。图形驱动程序在内部维护设备的图形内存,但在外部以系统内存的形式公开缓冲对象。这也使得可能对 USB 或 I2C 总线上的设备的缓冲对象进行内存映射,即使这些总线不支持设备内存的页面映射;阴影缓冲区可以在这种情况下用来映射。

另一种常见的分配器是 DMA helper,它在物理内存的 DMA 区域管理缓冲对象。这种设计通常用于 SoC 环境,其中图形芯片通过 DMA 操作获取和存储数据。当然,具有其他要求的 DRM 驱动程序可以选择扩展现有内存管理器之一或实现自己的内存管理器。

用于管理缓冲对象的 ioctl()接口被称为图形执行管理器(GEM, Graphics Execution Manager)。每个 DRM 驱动程序根据其硬件的功能和要求实现 GEM。GEM 接口允许将缓冲对象的内存页映射到用户空间或内核地址空间,允许将页固定在某个位置,或将它们导出给其他驱动程序。例如,用户空间中的应用程序可以通过对 DRM 设备文件的文件描述符调用 mmap()来访问缓冲对象的内存页。这个调用最终将进入 DRM 驱动程序的 GEM 代码,该代码设置内存映射。正如我们将在下文中看到的,这是软件渲染的一个有用的特性。

GEM 未提供的一个常见操作是缓冲对象的分配。每个缓冲对象都有一个特定的用例,这会影响并受到对象的分配参数、内存位置或硬件约束的影响。因此,每个 DRM 驱动程序都提供了专用的 ioctl()操作,用于缓冲对象的分配,处理这些跟硬件独立的设置。Mesa 内部的 DRM 驱动程序在调用相应的 ioctl()操作时调用这个 DRM 驱动程序的对应项。

Rendering operations

仅仅拥有用于存储输出图像、输入数据和着色器程序的缓冲对象是不够的。为了开始渲染,Mesa 指示 DRM 将所有必要的缓冲对象放入图形内存,并调用活动的着色器程序。同样,这也是跟具体硬件相关的,并由每个 DRM 驱动程序以 ioctl()操作提供。与缓冲分配一样,在 Mesa 内部的硬件驱动程序调用 DRM 驱动程序的 ioctl() 操作时发生。对于基于 Gallium3D 的 Mesa 驱动程序,这发生在驱动程序将状态跟踪器信息转换为硬件状态时。

理想情况下,图形驱动程序仅充当用户空间应用程序与硬件之间的代理。硬件渲染器异步运行于图形堆栈的其余部分,并仅在发生错误或成功完成时向驱动程序报告;就像系统 CPU 在页面错误或非法指令时通知操作系统一样。只要没有需要报告的情况,驱动程序的开销就是最小的。当然,也有例外情况;例如,较旧型号的 Intel 图形芯片不支持顶点转换,因此 Mesa 内的驱动程序必须在软件中实现它们。在树莓派上,内核的 DRM 驱动程序必须验证每个着色器的内存访问,因为 VideoCore 4 芯片不包含用于将着色器与系统隔离的 I/O MMU。

Software rendering

到目前为止,我们假设硬件支持图形渲染。如果没有这种支持或用户空间应用程序无法使用它,该怎么办呢?例如,用户空间 GUI 工具包可能更喜欢在软件中进行渲染,因为像 OpenGL 这样以硬件为中心的接口不符合其需求。还有 Plymouth,这个程序在启动时显示启动标志并提示输入磁盘加密密码,此时通常没有完整的图形栈可用。对于这些情景,DRM 提供了 dumb-buffer ioctl()接口。

通过利用 dumb buffer 缓冲区,应用程序在图形内存中分配缓冲对象,但不支持硬件加速。因此,任何返回的缓冲对象只能在软件渲染中使用。用户空间中的应用程序,如 GUI 工具包或 Plymouth,将缓冲对象的页映射到其地址空间并复制输出图像。Mesa 的软件渲染器工作方式类似:所有输入缓冲对象都存在于系统内存中,系统 CPU 处理着色器指令。输出缓冲区是一个 dumb buffer 对象,用于存储渲染后的图像。虽然这既不快速也不花哨,但足以在不支持硬件加速渲染的简单硬件上运行现代桌面环境。

我们现在已经介绍了用于渲染的应用程序图形栈。在完成对场景图的遍历后,应用程序的输出缓冲对象包含要显示的可视化场景或数据。但是,缓冲区还没有显示在屏幕上。无论是加速 buffer 还是 dumb buffer,将这些缓冲区显示在屏幕上都需要合成(compositing)和模式设置(mode setting),这构成了图形栈的另一半内容。在第二部分中,我们将查看 Wayland 合成,使用 DRM 设置显示模式以及图形栈的其他一些特性。

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

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

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

format,png

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值