Linux图形栈入门概念
Mesa在图形栈中的位置
游戏引擎:
游戏引擎指的是一种软件框架,通过编程和各种工具,帮助开发者设计、构建和运行视频游戏。它相当于一个虚拟的世界创造工具,提供了各种功能模块和资源,如渲染引擎、物理引擎(碰撞检测、重力计算等)、声音引擎、动画系统等,用来处理游戏中的图形、声音、物理效果、用户输入和逻辑等方面。游戏引擎提供了强大的工具和功能,大大简化了游戏开发的过程,缩短了开发周期,使开发者能够更专注于游戏的创意和设计。
Windowing library
OpenGL 包含了一系列可以操作图形、图像的函数。然而,OpenGL 本身并不是一个 API,它仅仅是一个由 Khronos 组织并维护的规范。一般而言,OpenGL 库是由电脑显卡的供应商开发和维护的。而GLFW 是一个专门针对 OpenGL 的 C 语言库,它提供了一些渲染物体所需要的最低限度的接口。GLFW 的主要功能是创建并管理窗口和 OpenGL 上下文,同时还提供了处理手柄、键盘、鼠标输入的功能。
GUN C Library
一般人买车肯定都是买整车而不是买引擎吧?而且称呼这台车的时候一般也是称呼整车的型号或者名称,而不会称呼这台车的引擎的名称。而用过“Linux操作系统”的人一定注意到过一个现象:这些系统的名称一般被叫做Ubuntu、Debian、Fedora、CentOS、Manjaro、openSUSE……等等,并不一定有“Linux”字样。这涉及到Linux世界的一个基本事实:Linux本身并不是一个完整的操作系统,而是一个操作系统的核心程序(一般称为内核),相当于一个引擎。而大家熟知的 grub、bash、ls、cat、gcc、grep、tar、vi、nano、curl、git……等等各种命令严格来说并不属于Linux内核项目,而是GNU旗下的众多软件。将内核和这些软件工具组合到一起形成的完整操作系统(一般称为发行版),相当于各种零部件组合成整车。而细究起来会发现,这台“整车”里的多数基础零件都是来源于GNU项目,例如:
grep 来源于 https://www.gnu.org/software/grep/
tar 来源于 https://www.gnu.org/software/tar/
gzip 来源于 https://www.gnu.org/software/gzip/
wget 来源于 https://www.gnu.org/software/wget/
time 来源于 https://www.gnu.org/software/time/
bc 来源于 https://www.gnu.org/software/bc/
diff 来源于 https://www.gnu.org/software/
sed 来源于 https://www.gnu.org/software/sed/
awk 来源于 https://www.gnu.org/software/gawk/
gcc 来源于 https://www.gnu.org/software/gcc/
make 来源于 https://www.gnu.org/software/make/
C 库也只是GUN所提供的库之一。
Mesa3D
驱动程序就是驱使硬件工作的代码,最简单的例子就是点亮led灯,led厂商提供的驱动程序是以接口形式存在,可能调用open就是打开,close就是关闭,又或许另外一家厂商调用on是打开,off是关闭。那应用程序在写代码时,为了保持良好适用性,可能里面会有一段判断逻辑,如果厂商a是一套逻辑,厂商b则是另外一套逻辑,为了统一,一定会有组织出来安排规范,组织可以规定打开就必须用on,关闭就必须用off,先不考虑厂商愿不愿意改的问题。假如厂商已将驱动程序烧到设备里面,而且部分产品已经到用户手里了。又或者其他原因,厂商无法更改驱动程序,那么怎么办呢?每个应用程序都要写判断的逻辑,而且厂商那么多,写应用程序的并不想关注这么多细节,那么可以提供一套框架,把逻辑写在框架里,操作系统安装这个框架就能为用户提供统一的接口。点led灯通常对应的就是led电子屏,通常应用程序通过特定的算法控制led矩阵上不同位置的灯的亮灭展示图案,如果大家的需求都是展示文字,那么刚才提到的框架甚至可以将这一段逻辑包含进来,上层只需要将要展示的文字传给框架,框架负责安排哪个灯亮还是灭。又进一步简化用户的操作了。mesa差不多就是这样一种框架,不过他关联的设备不是led灯而是图形卡。
OpenGL就是为了统一各厂商图形化所提出的一套规范,只是规定了接口,但是具体细节不作任何要求,所以OpenGL并没有实现,只是做了规范。而Mesa3D就是OpenGL的开源实现,除此之外,他还有一个重要的功能就是在没有图像卡的主机上,可以通过CPU来完成渲染效果。没有Mesa的话,用户在写3D应用时,如果直接调用GPU驱动程序提供的接口,实现会更复杂,代码量也会及其庞大。为了获得更好的通用性,你还需要了解很多图形厂商的细节,如果你把这些都做到了,其实你自己就写了一个Mesa,如果是学习使用,那倒无妨,如果是实际开发,别人写好了为什么不用。但是他们写框架会考虑灵活性,稳定性各种因素,通常会抽象出很多东西(各种上下文),然而你的思路和他们未必一致,因此在理解他们框架上会造成一定的阻碍。虽然使用更方便但是学习框架并理解它们则需要花费大量的时间。
一言蔽之,Mesa就像一个管理驱动的大型软件,为应用层的编程提供便利,和我们日常使用的软件不同的是,它不是以图形界面展示给我们,而且采用接口的形式。且这不是我们关心的问题,因为我们的工作并不是编写应用程序,而是需要扩充Mesa的能力,让Mesa也能支持我们的卡。
个人理解,如gl.h
中的方法在Mesa中都有具体实现,Mesa分为前端和后端,前端是API的实现具体会把产生的状态和数据下发到后端,后端就是用我们自己的逻辑去适配自己的产品。举例例子不一定正确,比如说开辟一个缓冲区,前端可能就是逻辑上开辟一个缓冲区然后用一个对象或指针去绑定它,而后端是具体这个缓冲区开辟在我们硬件的什么位置,就前端而言所有的厂商逻辑都是一样的,而后端是要根据我们自己产品的实际情况去进行处理,毕竟256MB和4G的显存开辟缓冲区的实现肯定是有差异的。而我们需要做的就是向后端追加适配于我们硬件的逻辑代码,扩充Mesa的功能,使其支持我们的产品。
DRM
DRM是图形卡硬件编程的管道,主要由mesa使用,也可以理解成是Linux内核的一个子系统,负责与现代视频卡中GPU交互的接口。DRM公开了一个API(DRM Library),用户空间程序可以使用该API向GPU发送命令和数据,并执行诸如配置显示器的模式设置等操作。DRM允许多个程序同时访问3D视频卡,避免冲突。
DRM从模块上划分,可以简单分为3部分:libdrm
、KMS
、GEM
libdrm: 对底层接口进行封装,向上层提供通用的API接口,主要是对各种IOCTL接口进行封装。
KMS: Kernel Mode Setting,所谓Mode setting,其实说白了就两件事:更新画面和设置显示参数。
- 更新画面:显示buffer的切换,多图层的合成方式,以及每个图层的显示位置。
- 设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等。
GEM: Graphic Execution Manager,主要负责显示buffer的分配和释放。
DRI
在GUI
环境中,一个Application
想要将自身的UI
界面呈现给用户,需要2
个步骤:
Ⅰ.根据实际情况,将UI
绘制出来,以一定的格式,保存在buffer
中。该过程就是常说的“Rendering
”。
Ⅱ.将保存在buffer
中的UI
数据,显示在display device
上。该过程一般称作“送显”。
DRI(Direct Rendering Infrastructure)是一种用于实现硬件加速图形渲染的技术
在操作系统中,Application不应该直接访问硬件,通常的软件框架是(从上到下):Application > Server > Driver > Hardware
基于OpenGL的3D program需要进行3D rendering的时候,需要通过X server的一个扩展(GLX),请求X server帮忙处理。X server再通过底层的driver(位于用户空间),通过kernel,访问硬件(如GPU)。其它普通的2D rendering,如2D绘图、字体等,则直接请求X server帮忙完成。
看着不错哦,完全满足上面的理念。但计算机游戏、图形设备硬件等开发人员不乐意了:请让我们直接访问硬件!因为很多高性能的图形设备,要求相应的应用程序直接访问硬件,才能实现性能最优。好像每个人都是对的,怎么办?妥协的结果是3D Rendering另起炉灶,给出一个直接访问硬件的框架,DRI就应运而生了,如下:
DRI是因3D而生,但它却不仅仅是为3D而存在,这背后涉及了最近Linux图形系统设计思路的转变,即:
从以前的:X serve是宇宙的中心,其它的接口都要和我对话。
转变为:Linux kernel及其组件为中心,X server(或者Wayland compositor等)只是角落里的一员,可有可无。
最终,基于DRI的linux图形系统如下(可以说前两种并不是特别合理或者说都有缺陷):
该框架以基于Wayland的Windowing system为例(X同理),描述了linux graphic系统在DRI框架下,通过两条路径(DRM和KMS),分别实现Rendering和送显两个步骤。从应用的角度,显示流程是:
1)Application(如3D game)根据用户动作,需要重绘界面,此时它会通过OpenGL|ES、EGL等接口,将一系列的绘图请求,提交给GPU。
a)OpenGL|ES、EGL的实现,可以有多种形式,这里以Mesa 3D为例,所有的3D rendering请求,都会经过该软件库,它会根据实际情况,通过硬件或者软件的方式,响应Application的rendering请求。
b)当系统存在基于DRI的硬件rendering机制时,Mesa 3D会通过libGL-meas-DRI,调用DRI提供的rendering功能。
c)libGL-meas-DRI会调用libdrm,libdrm会通过ioctl调用kernel态的DRI驱动,这里称作DRM(Direct Rendering Module)。
d)kernel的DRM模块,最终通过GPU完成rendering动作。
2)GPU绘制完成后,将rendering的结果返回给Application。
rendering的结果是以image buffer的形式返回给应用程序。
3)Application将这些绘制完成的图像buffer(可能不知一个)送给Wayland compositor,Wayland compositor会控制硬件,将buffer显示到屏幕上。
Wayland compositor会搜集系统Applications送来的所有image buffers,并处理buffer在屏幕上的坐标、叠加方式后,直接通过ioctl,交给kernel KMS(kernel mode setting)模块,该模块会控制显示控制器将图像显示到具体的显示设备上。
总结
DRI实现了硬渲染,当图形应用程序直接与图形硬件交互,通过硬件加速进行渲染时,可以绕过显示服务器,直接在图形硬件上执行渲染操作,从而提高渲染性能和效率。
然而,图形渲染结果最终需要显示在显示设备上,例如显示器或屏幕。这就需要将渲染结果传递给显示服务器,由显示服务器负责将图像显示在屏幕上。显示服务器会处理渲染结果的分辨率、颜色空间、多屏幕管理等问题,并将渲染结果输出到适当的显示设备上。
因此,尽管渲染可以绕过显示服务器进行,但将渲染结果送显时,通常需要通过显示服务器进行处理和转发,以实现图形的正确显示和适应不同的显示设备。
GLX、EGL
GLX是在Linux上用于提供GL与窗口交互、窗口管理等等的一组API,包含在X Server的代码中。
GLX和EGL都是与OpenGL相关的图形库和扩展,但GLX主要面向X Window System的桌面平台,而EGL主要面向嵌入式系统。
硬渲染与软渲染
GLX支持直接渲染(硬渲染)(Direct Rendering) 与 间接渲染(软渲染)(Indirect Rendering)两种模式
- 直接渲染模式需要OpenGL应用程序能够直接访问GPU
- 间接渲染则需要将OpenGL指令转发至X Server,由X Server负责执行真正的OpenGL指令。(软渲染可以独立于X Server进行,但在某些基于X Window System的桌面环境中,软渲染的输出可能会通过X Server进行显示。)
软渲染是利用CPU进行图形渲染和计算的过程,适用于不具备硬件加速功能的系统和应用程序。而硬渲染是利用专门的图形硬件加速执行图形操作和渲染的过程,提供更高的渲染性能和速度,但对兼容硬件的依赖较高。选择使用哪种渲染方式取决于系统的硬件能力、应用程序的需求和性能要求。
显示服务器(窗口系统)
显示服务器(window system)可以理解为操作系统的GUI程序,这个程序接管了键盘、鼠标、显示器、显卡。 你在屏幕上看到所有的窗口、图片、文字都是由它绘制的,鼠标键盘等事件也是由它处理和分发。没有它,Linux也照样可以跑,但只能使用命令行界面。
显示服务器通过显示服务器协议与其客户端进行通信。Linux中提供了三种显示服务器协议。X11、Wayland和Mir。
在GUI系统设置中可以查看,用过的Ubuntu 20.04是X11,Ubuntu 22.04是Wayland,Mir没有使用过。
X Window System
X11
是X Window System
的一个主要版本,也是目前最常用的版本。 其他较早的版本如X10、X9等在X11之前也存在,但X11成为了最具影响力和广泛应用的版本。
“X11”采用了C/S的架构,在其设计下,整个图形视窗系统主要分为3个部分:
- X Server(X服务器): X Server一方面负责和设备驱动交互,监听显示器和键盘鼠标,另一方面响应X Client需求传递键盘、鼠标事件、(通过设备驱动)绘制图形文字等。X Server运行在本地。
- X Client(X客户端): X Client也叫X应用程序,负责实现程序逻辑,在收到设备事件后计算出绘图数据,由于本身没有绘制能力,只能向X Server发送绘制请求和绘图数据,告诉X Server在哪里绘制一个什么样的图形。X Client可以和X Server在同一个主机上,也可以通过TCP/IP网络连接。
- Window Manager(窗口管理器,简称WM),或者叫合成器(Compositor):多个X Client向X Server发送绘制请求时,各X Client程序并不知道彼此的存在,绘制图形出现重叠、颜色干扰等问题是大概率事件,这就需要一个管理者统一协调,即Window Manager,它掌管各X Client的Window(窗口)视觉外观,如形状、排列、移动、重叠渲染等。Window Manager并非X Server的一部分,而是一个特殊的X Client程序。
3个部分, X Server是整个X Window System的中心,协调X客户端和窗口管理器的通信。
通常X Server部署在本地用户主机上,监听、调控本地用户主机的硬件设备,而X Client可能部署在远端服务器/嵌入式设备上,也可能在本地。
例子Ⅰ:
- 某个X客户端进程启动,向主机B发送连接请求,目标地址可通过命令行或配置文件指定,如果给定的地址已有X Server正在监听端口,则进行下一步;
- 主机B上的X Server返回一个连接正确响应,X Server也可以配置接受或拒绝某些地址的请求;
- X客户端向X Server发送渲染请求及窗口界面数据;
- X Server一方面将窗口界面数据交给显示驱动计算渲染缓冲,另一方面综合各个X客户端的渲染请求,计算更新区域,但它并不知道如何将多个窗口“合成到一起”,于是将更新区域事件发给窗口管理器;
- 窗口管理器了解到需要在屏幕上重新合成一块区域,再向X Server发送整个屏幕的绘制请求和数据;
- X Server将绘图数据交给显示驱动计算所有渲染缓冲,并最终绘制图形;
- 运行过程中,X Server可能收到主机B上的鼠标、键盘事件,经计算后,X Server决定发给哪个X客户端(即获得焦点);
- X客户端收到鼠标、键盘事件后,回调事件处理,并计算界面该如何更新;
- 循环第3~8,直至X客户端收到关闭事件,进程终止、连接断开。
以上过程,主机A和B的CPU架构、操作系统可能都不相同。通过X Protocol(X协议)进行通信。
例子Ⅱ:
键入一个字母G,x server得到这个事件后,会将该事件转发给对应的程序(假使此时对应的应用程序就是某一个client),client接收到事件后,需要显示这个字母,然后就会发一个请求给x server,要求在xx,xx地方显示一个大小为xx颜色为xx,字体为xx的字母,x server收到请求后,就按照要求把字母c画出来了。
Wayland
Wayland是一个新的图形窗口系统方案,一套旨在取代X的新规范。与X最大的不同是,Wayland将X中的Server和窗口管理器整合到一起作为服务端,称为合成器(Compositor),架构上只分了客户端和合成器两大部件。
但是,截至2020年,大多数用于Linux的视频游戏和图形密集型应用程序仍为X11编写。另外,许多封闭源代码的图形驱动程序,例如NVIDIA GPU的驱动程序,都尚未完全提供对Wayland的支持。