Android/Linux 子系统Graphics图形栈入门普法介绍
写在最前面
由于工作原因,最近在公司做了一个关于Android/Linux 子系统Graphics图形栈入门相关知识的培训介绍,个人感觉对于想要了解入门这块的朋友还是有一定帮助的。由于博客不能直接放入ppt,这里我就将相关的ppt转换成可以博客展示的发表出来,希望能帮助到对这一块感兴趣的朋友!
前言
由于Linux(外加Android)图形子系统过于复杂,且个人由于能力有限,这里我也仅能对Linux图形子系统做一些简单的概述,进而罗列出Linux显示子系统涉及的相关的软件技术,做到给大家一个了解的程度!
本文很多图片都是从网上搜集而来的(很多是从维基百科)。虽然编写文档最好用自己的语言表述,尽量自己画图,但是Linux(外加Android)图形子系统太复杂了,而我的个人理解能力有限,而网上的图画的实在太好,所以我觉得,再怎么努力,也画不出更好的了,因此本着为站在巨人肩膀的角度出发,就直接copy了。
一.什么是Linux图形子系统?
图形子系统是Linux操作系统中比较复杂的子系统之一:
- 对下,它要管理形态各异的、性能各异的显示相关的器件
- 对上,它要向应用程序提供易用的、友好的、功能强大的图形用户界面(GUI)。因此,它是Linux中少有的、和用户空间程序(甚至是用户)息息相关的一个子系统
二.常用Android/Linux图形子系统图形栈总览
三.Linux图形子系统图形栈概念介绍
从前面的常用Linux图形子系统图形栈总览,相信各位也看到了!图形栈的确实比较复杂,所以接下来我们将先理清局部概念,再整体框架深入的来分别介绍Linux图形子系统图形栈的一些概念(所以一口气想吃成个胖子就不太可能了)!
3.1 什么是GUI
古老物种DOS系统早已经作古,现在发扬广大的就是我们这里要说的GUI!Linux(Linux,Android)图形子系统的本质,就是是提供图形化的人机交互(human-computer interaction)界面,也即常说的GUI(Graphical User Interface)。而人机交互的本质,是人脑通过人的输出设备(动作、声音等),控制终端的输入设备,终端经过一系列的处理后,经由终端的输出设备将结果输出,人脑再通过人的输入设备接收电脑的输出,最终实现“人脑<–>终端”之间的人机交互。如下的图片对上述过程做了很好的总结:
通过上图以一个非常前卫的应用场景----虚拟现实(VR,Virtual Reality)游戏,说明了以图形化为主的人机交互过程:
-
人脑通过动作、声音(对人脑而言,是output),控制终端的输入设备,包括键盘、鼠标、操作杆、麦克风、游戏手柄(包含加速度计、陀螺仪等传感器)。
-
终端通过输入设备,接收人脑的指令,这些指令经过kernel Input subsystem、Middleware Gesture/Speech recognition等软件的处理,转换成应用程序(Game)可以识别的、有意义的信息。
-
应用程序(Game)根据输入信息,做出相应的反馈,主要包括图像和声音。对VR游戏而言,可能需要2D/3D rendering,这可以借助openGL及其相应的用户空间driver实现。
-
应用程序的反馈,经由kernel的Video subsystem(如DRM/KMS)、audio subsystem(如ALSA),输出到终端的输出设备上,包括显示设备(2D/3D)、扬声器/耳机(3D Positional Audio)、游戏手柄(力的反馈)等。
-
输出到显示设备上时,可能会经过图形加速模块(Graphics accelerator)。
3.2 什么是Windowing system(窗口系统)?
窗口系统,是GUI的一种(也是当前计算机设备、智能设备广泛使用的一种),以WIMP (windows、icons、menus、pointer)的形式,提供人机交互接口。Linux系统中有很多窗口系统的实现,如Linux上的X Window System、Wayland和Android SurfaceFlinger等,虽然形态各异,但思路大致相同,包含如下要点:
-
一般都使用client-server架构,server(称作display server,或者windows server、compositor等等)管理所有输入设备,以及用于输出的显示设备。
-
应用程序作为display server的一个client,在自己窗口(window)中运行,并根据窗口类型的不同决定绘制是由client端进行还是请求服务端进行
-
Client的送显示请求,都会提交给display server,display server响应并处理这些请求,以一定的规则混合、叠加,最终在有限的输出资源上(屏幕),显示多个应用程序的GUI()
-
Display server和自己的client之间,通过某种类型的通信协议交互,该通信协议通常称作display server protocol。
-
Display server protocol可以是基于网络的,甚至是网络透明的(network transparent),如X Window System所使用的。也可以是其它类型的,如Android SurfaceFlinger所使用的binder。
如下是Linux下两个常用的窗口系统
3.3 什么是窗口管理器 ?
前面讲过,多种窗口系统,但是只提供了实现GUI环境的基本框架,其它的UI设计所需的button、menu、window title-bar styles等基本元素,则是由第三方的应用程序提供。这些应用程序主要包括:窗口管理器(window manager)、GUI工具集(GUI widget toolkit)和桌面环境(desktop environment)。
窗口管理器(Window Manager)用一句话来概括的话那就是它是一个可以控制窗口环境中窗口属性的软件。通常它的作用如下:
- 窗口管理器核心功能是移动窗口,改变窗口大小,图标化(最小化)窗口和改变窗口层叠顺序,通俗说就是对窗口进行管理。
- 一般来说窗口管理器会对应用程序窗口加上标题栏,用以进行窗口的拖拽操作,其中还有最小化,最大化,关闭按钮,方便用户快捷的操作窗口。
- 窗口管理器还会对窗口边框进行处理,设计边框颜色和阴影效果来区分激活与非激活窗口。
- 同时大部分窗口管理器还提供了一些快捷键绑定,来实现窗口切换,工作区切换,显示桌面等功能
3.4 窗口管理器分类和常用的
根据窗口管理器绘制和更新窗口的方式,窗口管理器可以分为以下四类:
-
平铺式(Tiling window manager)
-
堆叠式窗口管理器(Stacking window manager)
-
动态窗口管理器(Dynamic window manager)
-
复合窗口管理器(Compositing window manager)
3.5 什么是GUI工具集 ?
GUI(GUI toolkits)工具集是Windowing system之上的进一步的封装。还是以X为例,它通过xlib提供给应用程序的API,仅仅可以绘制基本的图形单元(点、线、面等等),这些基本的图形单元,要组合成复杂的应用程序,还有很多很多细碎、繁杂的任务要做。因此,一些特定的操作系统,会在X的基础上,封装出一些更为便利的GUI接口,方便应用程序使用,如GTK+、QT等等。
GTK+和QT是GUI toolkits,属于软件库,类似c语言的stdio.h,win32,java里import的各种外部包,可以任开发者调用(应该是C/C++使用的库)去创建一些图形界面里面的控件,例如button,下拉菜单,窗口等。我记得JAVA里面也有类似AWT和Swing库。用这一套库开发出的图形空间将会有一套统一的风格和标准,这就是不同系统安装的不同软件有的时候会有相同的样式,因为他们可能使用了GTK或者QT的库。KDE默认使用Qt库开发,Gnome默认使用GTK+库开发,而这两套库又是基于X window server的,需要遵守x11协议,在xwindow server上运行,作为client应用实现的基础类库。接下来就要说说KDE和Gnome以及其他基于GTK和Qt开发的x软件。
3.6 什么是桌面环境,常用的有那些 ?
桌面环境(Desktop Environments)是最近发展起来的桌面图形环境,它的主要目标是为Linux/Unix操作系统提供一个更加完善的界面以及大量各类整合工具和应用程序。桌面环境是应用程序级别的封装,通过提供一系列界面一致、操作方式一致的应用程序,使系统以更为友好的方式向用户提供服务。Linux系统比较主流的桌面环境包括GNOME、KDE,Unity(各种),Xfce等等,下面我们简单介绍下:
-
KDE 桌面系统
KDE 是 K Desktop Environment 的缩写,中文译为“K桌面环境”。KDE 是基于大名鼎鼎的 Qt 的,最初于 1996 年作为开源项目公布,并在 1998 年发布了第一个版本,现在 KDE 几乎是排名第一的桌面环境了。
-
GNOME桌面环境
GNOME 即GNU网络对象模型环境 (The GNU Network Object Model Environment),GNU计划的一部分,开放源码运动的一个重要组成部分。 是一种让使用者容易操作和设定电脑环境的工具。目标是基于自由软件,为Unix或者类Unix操作系统构造一个功能完善、操作简单以及界面友好的桌面环境,他是GNU计划的正式桌面。
-
Unity桌面环境
Unity 是由 Ubuntu 的母公司 Canonical 开发的一款外壳。之所以说它是外壳,是因为 Unity 运行在 GNOME 桌面环境之上,使用了所有 GNOME 的核心应用程序。
-
Xfce桌面环境
Xfce是一个轻量级的类Unix的桌面系统,Xfce这个词的发音为X-f-c-e(即四个字母一个一个的读),Xfce是使用率仅次于KDE与Gnome的Linux桌面系统。
3.7 窗口/窗口管理器/桌面环境是什么关系 ?
套用一句比较流行的话来说,就是人生无常,大肠包小肠(开玩笑,可以认为它们是从属关系)!可以用下面的示意图来表示:
3.8 窗口/窗口管理器/桌面环境是什么关系 ?
-
OpenGL(Open Graphics Library)是为了方便应用程序的开发,而屏蔽各种硬件平台的一个稳定的、跨平台的API,它定义了渲染有关的行为和动作,它不提供具体的实现,这个由各个GPU芯片厂自己实现(注意OpenGL并不是唯一能实现相关功能的API接口,譬如vulkan也是可以的)。
-
OpenGL ES(OpenGL for Embedded Systems)是免授权费的,跨平台的,功能完善的2D和3D图形应用程序接口API,它针对多种嵌入式系统专门设计。它由精心定义的桌面OpenGL子集组成,创造了软件与图形加速间灵活强大的底层交互接口。说白了,它就是OpenGL的子集、可以应用于ES嵌入式设备上。
-
EGL 是 OpenGL ES 渲染 API 和本地窗口系统(native platform window system)之间的一个中间接口层,它主要由系统制造商实现。EGL提供如下机制:
-
与设备的原生窗口系统通信
-
查询绘图表面的可用类型和配置
-
创建绘图表面
-
在OpenGL ES 和其他图形渲染API之间同步渲染
-
管理纹理贴图等渲染资源
3.9 OpenGL接口的分类和实现有那些?
通过前面的介绍,我们了解到OpenGL只是一个API规范,但由于3D绘图的复杂性,它也是相当的复杂的。不过,归根结底,它的目的有两个:
- 对上,屏蔽硬件细节,为应用程序提供相对稳定的、平台无关的3D图像处理API(当然,也可以是2D)。
- 对下,指引硬件相关的驱动软件,实现3D图像处理相关的功能。
通常我们见到的各种libGLXXX.so就是OpenGL的实现,从分类上可以是各种不同类型的OpenGL实现,如OpenGL(for PC场景)、OpenGLES(for嵌入式场景)、OpenVG(for Flash、SVG矢量图)。
libGL的实现,既可以是基于软件的(譬如谷歌的swiftshader,swrast),也可以是基于硬件的(譬如鼎鼎大名的Arm的mail和高通的Adreno系列,但是它们都是非开源的)。其中Mesa 3D是OpenGL的一个开源本的实现,支持3D硬件加速(关于它在后面会介绍)。
OpenGL的一个重要特性,是独立于操作系统和窗口系统而存在的(这个也正常,因为它就是一个API的规范,谁用谁遵守规范就好),但是它的实现通常不能独立于操作系统和窗口存在。
3.10 OpenGL OpenGLES EGL之间的关系 ?
我们举个栗子我们来思考一下画家绘画的过程:首先要有一名懂得各种绘画技艺的画家,然后他需要一张画布,一些笔,一些颜料,一些辅助工具(尺、模板、橡皮、调色板等等),然后他在画布上绘制第一幅画,完成之后展示给人们看;在人们观赏第一幅画的时候,他可以在第二张画布上绘制第二幅画,绘制完成后收回第一幅画,将第二幅画展现给人们看;接着使用工具擦除第一幅画,在同一张画布上绘制第三幅画;周而复始,人们便看到了一幅接一幅的画。
对比 OpenGL ES/EGL,各要素的对应关系大体如下:
- 画家:编程人员
- 笔、颜料、辅助工具:OpenGL ES API
- 画布:EGL 创建的 Surface
3.11 什么是GLX,它和EGL的关系是什么 ?
在Linux的图形栈中也许我们会听到一个名词GLX,这里也简单介绍下它,GLX是OpenGL Extension to the X Window System的缩写。它作为x的扩展,是x协议和X server的一部分,已经包含在X server的代码中了。GLX提供了X window system使用的OpenGL接口,允许通过x调用OpenGL库。OpenGL 在使用时,需要与一个实际的窗口系统关联起来。在不同平台上有不同的机制以关联窗口系统,在Windows上是WGL,在Linux上是GLX,在 Apple OS上是AGL等。
如果要问EGL和GLX有什么关系,那么EGL则是OpenGL ES在嵌入式平台上(WGL,GLX,AGL)的等价物。EGL假设OS会提供窗口系统,但EGL与平台无关,并不局限于任何特定的窗口系统,所有用到本地窗口系统的地方都用屏蔽指针来处理。我觉得这就是它易于移植的关键。
四.普法Meas
Mesa在Android/Linux graphics图形栈特别是Linux中是绕不开的一个技术,这里我们从它是什么,它的作用,以及它的架构出发来简单介绍介绍!
4.1 什么是Mesa ?
在前面介绍OpenGl的实现的时候,简单介绍了下Mesa是OpenGL的一个开源实现。这里再隆重介绍下它,Mesa,也称为Mesa3D和Mesa 3D图形库,是OpenGL,Vulkan和其他图形API规范的开源软件实现。它最重要的用户是两个图形驱动程序,这些图形驱动程序主要由Intel和AMD为各自的硬件开发和资助。注意这里没有提到Nvidia,因为它没有开放GPU的相关驱动,所以有了下面的“英伟达是我们遇到的硬件厂商中最麻烦的一个”
在经过Linus Torvalds的一顿怒骂之后,英伟达破天荒开源GPU内核驱动,然后被戏称为”这是近十年来开源操作系统硬件支持方面发生的最大事件之一”。我们就当做看戏的观众,吃吃瓜就OK了!
4.2 Mesa的作用 ?
我们知道了Mesa是OpenGL的实现,它提供了如下的两个基本作用:
- 对接各种GPU硬件,将应用层对GL API的调用转换到对硬件GPU的调用上;
- 各种 GL API 的纯软实现,当没有可用的硬件时,它可以提供传软件的 GL API 的实现,Mesa还包含称为swrast的软件渲染实现,当不存在图形硬件加速器时,该渲染器允许着色器在CPU上运行,作为后备。 Gallium软件光栅化器称为软件管道(softpipe),或者在构建时支持LLVM llvmpipe,后者在运行时生成CPU代码(我们的E2000就是使用此方案)。
我它可以用于Linux,Windows,Mac等系统平台。在Windows上运行时它提供OpenGL API over DirectX的转换。它实现了 OpenGL,Vulkan以及其他的图形API。Mesa把这些API调用转换到相应驱动程序(其实并不是真正的驱动程序,是用户态的DRI驱动程序)调用上。并且支持如下的API:
- OpenGL OpenGLES
- EGL
- OpenCL
- Vulkan OpenVG
4.3 Mesa架构 ?
Gallium3D 是Mesa提出的用于简化GPU驱动开发的框架。下面是Mesa结构图,展示了mesa如何通过libGL库跟内核打交道,以及展示了新旧两种用户态设备驱动程序的实现方式。
至于Mesa的源码框架,这里就不分析了,这也不是该PPT的重点,该PPT的作用主要是为了普及一些Linux下graphic一些技术栈(其实主要是本人能力有限)!
五.什么是渲染以及硬件渲染加速 ?
这里我们用通俗的语言来说就是:根据实际情况,将2D的点,线面和3D图形以一定手段绘制出来,以一定的格式,保存在buffer中。该过程就是常说的“Rendering”。UI 组件在绘制到屏幕之前,都需要经过 Rasterization(栅格化)操作,而栅格化又是一个非常耗时的操作。Rasterization 栅格化是绘制那些 Button、Shape、Path、String、Bitmap 等显示组件最基础的操作。栅格化将这些 UI 组件拆分到显示器的不同像素上进行显示。这是一个非常耗时的操作,GPU 的引入就是为了加快栅格化。
通常情况下,2D渲染一般是由CPU完成(也可以由专门的硬件模块完成)。3D渲染也可以由CPU完成,但面临性能问题,因此大多数平台都会使用单独硬件模块(GPU或者显卡)负责3D渲染。这种通过特定功能的硬件模块,来处理那些CPU不擅长的事务的方法,称作硬件加速(Hardware acceleration),相应的硬件模块,就是硬件加速模块。而这种硬件加速模块的功能就是我们所说的硬件渲染加速了!
六.为什么GPU能渲染加速而CPU不行 ?
在回答这个问题前,我们非常有必要再来简单描述下CPU和GPU:
- CPU(Central Processing Unit,中央处理器)是计算机设备核心器件,用于执行程序代码,软件开发者对此都很熟悉
- GPU(Graphics Processing Unit,图形处理器)主要用于处理图形运算,通常所说“显卡”的核心部件就是GPU。
下面是CPU和GPU的结构对比图(从出生就决定了不同的命运):
- 从结构图可以看出,CPU的控制器较为复杂,而ALU数量较少。因此CPU擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算
- CPU是串行结构。以计算100个数字为例,对于CPU的一个核,每次只能计算两个数的和,结果逐步累加
- 和CPU不同的是,GPU就是为实现大量数学运算设计的。从结构图中可以看到,GPU的控制器比较简单,但包含了大量ALU。GPU中的ALU使用了并行设计,且具有较多浮点运算单元
- 硬件加速的主要原理,就是通过底层软件代码,将CPU不擅长的图形计算转换成GPU专用指令,由GPU完成
假设我们有如下图像处理任务,给每个像素值加1。GPU并行计算的方式简单粗暴,在资源允许的情况下,可以为每个像素开一个GPU线程,由其进行加1操作。数学运算量越大,这种并行方式性能优势越明显(就是这么简单粗暴,多生孩子好干活)。
七.什么是图层合成 ?
图层合成指以一定形式组合各个图层的绘制内容,然后送往显示设备显示的过程(注意是送往显示的过程,而不是送显)。从原理上可分为在线合成(硬件合成)与离线合成(GPU或者CPU模拟合成)两种方式。
- 离线合成(GPU合成或者CPU模拟)
先将所有图层画到一个最终层(FrameBuffer)上,再将FrameBuffer送到显示设备显示。由于合成FrameBuffer与送显示设备显示一般是异步的(线下生成FrameBuffer,需要时线上的显示设备去取),因此叫离线合成(这种合成方式在Android中被叫做Client合成)。
- 在线合成(硬件合成)
不使用FrameBuffer,在显示设备需要显示某一行的像素时,用显示控制器将所有图层与该行相关的数据取出,合成一行像素送过去。通常这种技术又叫Overlay技术。由于省去合成FrameBuffer时读图层,写FrameBuffer的步骤,大幅降低了内存传输量,减少了功耗,但这个需要硬件支持(通常这种硬件被称为DPU,显示处理单元)。
八.普法DPU
DPU(Display Processor Unit)显示处理单元,而不是当今大火的并不是机器学习的Deep-LeaningPU或者是被吹爆天的”数据处理器”。也许看到这个“生僻词”,首先就一脸懵逼了:不是CPU、GPU么,什么时候还出来个DPU了?(其实它是一直存在的,只是它默默的躲在后面不被大众知道!,通常被称为显卡三板斧之一。)
8.1 DPU的作用
当我们通常大谈显示高帧率、高动态范围显示,这些其实都需要顶级的DPU来实现。相比于GPU,相比之下,DPU显示处理单元对于用户体验的影响可能还要更大一些,因为它就是直接控制图形处理器向屏幕输出显示信号的“最后一道关卡”。首先,DPU的硬件性能高低,直接决定了其所能支持的最大屏幕分辨率、最大频率刷新率、乃至最大色彩深度(色彩丰富程度)的多少;其次,DPU运算精度的高低,还会决定屏幕上线条、文字看起来的清晰度表现;最后,对于现代的DPU来说,它们除了负责将GPU、VPU生成的视频信号“传递”给显示面板之外,还可以对其进行效果强化方面的额外处理。比如说屏幕色彩校正、视频插帧、像素级的对比度增强和显示细节修复,这些看起来很“高大上”的功能,其实也都是由那些旗舰芯片里的DPU来进行处理。
所以通常的DPU必须有如下几部分功能:
- 支持多个Source Layer overlays(通常包括鼠标,以及其它各种图层,我们的dc却只能支持两层overlays)
- Display Interface:支持多路同时的输出设备(物理显示设备,虚拟显示设备不需要实际的输出设备)
- Blender: 支持多个Blender,对应于多个Path(除了LCD外,对应于DP或HDMI投屏),这里我个人是可以处理多路输入
8.2 DPU的发展史 ?
VPU(视频处理单元,这个交由后面视频栈进行普及)和DPU,它们其实比“显卡”还要老资历,妥妥的老人一枚!
-
如期说老资历,我们不如说VPU和DPU的历史,实际上比我们熟悉的“GPU”还要古老。这是因为个人电脑最初的显卡,本来就是只有2D色彩和线条绘制能力的,也就是说它们其实根本不是“GPU”,而是全部都是“DPU”。直到后来1999年NVIDIA研发出了对CPU依赖程度更低的、拥有更强的3D处理能力的GeForce 256显示核心,它才被认为是世界上的第一款“GPU”。至于VPU,经历过486、初代奔腾时代的电脑用户都知道,当时的计算机只靠CPU性能是无法流畅播放VCD视频的,要想在电脑上“看片”,首先你需要购买一块独立的“解码卡”,而它本质上就是早期的VPU。
-
当然这块从最早的Framebuffer机制也能看出,DRM框架中最早版本中也是不存在GPU的代码。DPU最简单的功能便是将Frambuffer数据输出到显示设备上去,而Framebuffer的来源也都是来自于CPU的软件绘制,而非GPU绘制
8.3 DPU和GPU的关系 ?
在实际中DPU和GPU没有相关联,甚至完全可以是两个厂家的产品!并且DPU与GPU在Linux代码中的耦合是历史产物,完全可以独立出来。
通过上图我们可以得出,如下的结论:
-
通过上图的Linux的DRI显示框架,也能看出KMS的相对独立性,对应于系统侧的composer,而DRM则在于内容相关的应用侧。对于Android系统也是一样的,GPU对应于DRM是用来绘制的,属于应用端的进程;而DPU对应于KMS,运行于服务端,可以认为在X/wayland Service(composer)中,开机就会初始化,然后保持不变,两者的分离更加彻底。
-
所以DPU侧重于控制显示,而GPU侧重于内容
九.什么是送显?
送显:把合并后的显存送到显示驱动显示到屏幕上的过程。从技术的发展史上来看,它经过了FBDEV到DRM/KMS的转变过程!
十.Application的UI展现给用过户需要经过几步 ?
总结起来分为如下几步:
- 根据实际情况,将Application的UI渲染绘制出来,并且以一定的格式保存
- 将渲染好的UI图层数据,经过图层合成
- 将合成好的UI图层数据,最终显示在display device上.该过程一般称作送显
十一.什么是FBDEV显示方案 ?
The frame buffer device provides an abstraction for the graphics hardware. It represents the frame buffer of some video hardware and allows application software to access the graphics hardware through a well-defined interface, so the software doesn’t need to know anything about the low-level (hardware register) stuff. fbdev用于为显示图形硬件提供一层软件抽象.它代理了显示图形硬件的帧内存,并且提供了一些良好定义的接口让应用软件去访问图形硬件,而不用去关心底层图形硬件的具体控制细节。用通俗的一句话来说就是它是这一种传统的显示框架,简单,但是它只能提供最基础的显示功能,无法满足当前上层应用和底层硬件的显示需求(特别是移动操作系统的需求)。
十二.什么是DRI ?
DRI(Direct Render Infrastructure):直接渲染基础架构。DRI在当前(或者说将来)的linux图形子系统中,有着举足轻重的地位,甚至可以说是新的linux图形框架核心思想的体现。而现在的主流DRM/KMS框架都是围绕着它进行的。所以鉴于它的重要性,我们非常有必要了解它!
十三.DRI的发展史
没有无缘无故的爱,也没有无缘无故的狠,而我们的DRI也不是无缘无故的凭空冒出来的,它是Linux的技术发展过程中诞生的产物。我们这里对其捋一捋!还记得我们在前面介绍窗口的的时候说过,它们是经典的c/s架构吗?在DRI诞生前应用不能直接访问硬件,通常的软件框架是(从上到下):C<---->S<---->Driver<---->Hardware。这样考虑的原因主要有二:安全性和共享硬件资源!对稍微有经验的软件开发人员(特别是系统工程师和驱动工程师)来说,这种理念就像杀人偿命、欠债还钱一样天经地义。但直到X server+3D出现之后,一切都不好了。因为X server大喊的着:“让我来!”,给出了这样的框架:
-
基于OpenGL的3D program需要进行3D rendering的时候,需要通过X server的一个扩展(GLX,前面已经介绍过了),请求X server帮忙处理。X server再通过底层的driver(位于用户空间),通过kernel,访问硬件(如GPU)。
-
其它普通的2D rendering,如2D绘图、字体等,则直接请求X server帮忙完成。
但是任何事情都不可能满足所有人,前面的渲染框架也是如此!看着不错哦,完全满足上面的理念。但计算机游戏、图形设备硬件等开发人员不乐意了:请让我们直接访问硬件!因为很多高性能的图形设备,要求相应的应用程序直接访问硬件,才能实现性能最优。好像每个人都是对的,怎么办?妥协的结果是,为3D Rendering另起炉灶,给出一个直接访问硬件的框架,DRI就应运而生了,如下:
这里可以看到DRI好像基本都是Rendering有关的内容,那送显呢?还是由display server统一处理比较好,因为显示设备是有限的,多个应用程序的多个界面都要争取这有限的资源,server会统一管理、叠加并显示到屏幕上。而这里叠加的过程,通常称作合成(Compositor),这个在前面已经有涉及到了!
十四.什么是DRM ?
DRM(Direct Rendering Manager):即直接渲染管理器。它是为了解决多个程序对GPU(或显卡,graphics card)及相应的graphics memory资源的协同使用问题而产生的。它是一个内核级的设备驱动,可以编译到内核中也可以作为标准模块进行加载,并且对用户层提供了libdrm进行先关的接口调用(注意这里需要和Linux下面的DRM显示框架区分开来,此处的DRM是DRM显示框架的子集,它在DRM显示框架中主要被用来渲染)。它的功能有如下几点:
-
Initialize GPU card, load its firmware, etc.
-
Share the GPU command queue between multiple applications
-
Manage the memory (allocation, access)
-
统一管理、调度多个应用程序向显卡发送的命令请求,可以类比为管理CPU资源的进程管理(process management)模块。
-
统一管理显示有关的memory(memory可以是GPU专用的,也可以是system ram划给GPU的,后一种方法在嵌入式系统比较常用),该功能由GEM(Graphics Execution Manager)模块实现,主要包括:
注意这里的DRM只是DRM显示框架的一部分
十五.什么是KMS ?
KMS(Kernel Mode Setting):也称作Atomic KMS,它是一个在linux 4.2版本的kernel上,才最终定性的技术。从字面意义上理解,它要实现的功能比较简单,即:显示模式(display mode)的设置,包括屏幕分辨率(resolution)、颜色深的(color depth)、屏幕刷新率(refresh rate)等等(它在Linux graphic的显示框架中主要被用来送显)。它的出现是为了解决最开始的DRM显示框架的缺点,那时候,Mode-Setting (包括更新画面、配置 display pipeline、screen resolution、color depth、refresh rate等) 是在 Userspace 中实现的,这样做的缺点是:
-
Rendering 和 Mode-Setting 会发生竞争;
-
不统一,缺少抽象,不同的硬件平台各自为营;
后来就引入了 Kernel Mode-Setting (KMS),其实就是将 Mode-Setting 的活移回到内核,DRM driver 即负责访问 GPU 也负责访问 Display Engine,且将 KMS 作为 DRM API 的一部分,如下图左边部分:
15.1 KMS的组成
- Framebuffer:帧缓冲区对象(struct drm_framebuffer)是帧内存对象的抽象,它提供了像素源给到CRTC。帧缓冲区依赖于底层内存管理器分配内存。
- Plane:一个平面对象(struct drm_plane)表示一个图像源,从drm_framebuffer对象获取输入数据,在输出过程中与CRTC的顶部混合或覆盖。
- CRTC:一个CRTC(struct drm_crtc)表示一整个显示管线。它从drm_plane平面接收像素数据并将它们混合在一起。
- Encoder:Encoder对象(struct drm_encoder)是CRTC和连接器(drm_connector)之间的连接元素。编码器从CRTC获取像素数据,并将其转换成适合任何连接的连接器的格式。
- Connector:连接器(struct drm_connector)是显示接收器的抽象,包括als固定面板或任何其他可以以某种形式显示像素的东西。
- 最终应用通过 KMS api将上述对象连接成一条display pipeline,最终将图像显示在屏幕上。
十六.KMS和DRM的关系 ?
个人觉得抛开DRM/KMS显示系统不谈,DRM和KMS是完全独立没有任何依附或者依赖的关系的。只所以谈到DRM会牵涉到KMS或者反之。它们二者完全是历史原因,导致KMS的代码,放到DRM中实现了。目前的kernel版本(如4.2之后),KMS和DRM基本上没有什么逻辑耦合(除了代码位于相同目录,以及通过相同的设备节点提供ioctl之外),可以当做独立模块看待。并且是现有DRM后才有的KMS。并且DRM的核心点是渲染,而KMS的重点在于送显!
十七.DRM/KMS显示方案
在前面我们说道了FBDEV显示方案,知道了它是一种简单的,传统的,不能符合现在操作系统,特别是移动操作系统高刷新的显示方案。而DRM/KMS显示方案就是为了解决上述问题而来的一种新的Linux下的显示方案(当然Android最新的也引入了)。
DRM/KMS显示方案相较于FBDEV显示方案有如下的优点:
- 适应当前日益更新的显示硬件发展
- 软件上能支持更多高级的控制和特性,譬如天生支持fence等特性
- 适应移动操作系统高刷新等特性要求
此时的DRM和KMS在显示方案中才有了关联,它们之间的相互协同作用最终构建了DRM/KMMS显示方案
十八.Linux下典型窗口类型+DRM/KMS显示方案总览
十九.Wayland + Mesa + DRM/KMS显示方案简介
在详细介绍前,我们看下整体总览,后面对每个模块简单概括:
前面的框图是基于Wayland的窗口系统为例,描述了linux graphic系统在DRI框架下,通过两条路径(DRM和KMS),分别实现Rendering和送显两个显示步骤。我们这里从上层到下层的角度出发,来简单分析该显示流程(这也是当前Linux主流的graphics图形子系统):
-
Application(如3D game)根据用户动作,需要重绘界面,此时它会通过OpenGL|ES、EGL等接口,将一系列的绘图请求,提交给GPU(非3D渲染,最后也是将一系列请求转换成GL命令提交给GPU)。
- OpenGL|ES、EGL的实现,可以有多种形式,这里以Mesa 3D为例,所有的3D rendering请求,都会经过该软件库,它会根据实际情况,通过硬件或者软件的方式,响应Application的rendering请求。
- 当系统存在基于DRI的硬件rendering机制时,Mesa 3D会通过libGL-meas-DRI,调用DRI提供的rendering功能。
- libGL-meas-DRI会调用libdrm,libdrm会通过ioctl调用kernel态的DRI驱动,这里称作DRM(Direct Rendering Module)。
- Kernel的DRM模块,最终通过GPU完成rendering动作。
-
GPU绘制完成后,将rendering的结果返回给Application。
- rendering的结果是以image buffer的形式返回给应用程序(注意这里的返回并不是真的拷贝了一份,而是通过共享方式达到共享的,这种共享的方式通常是通过DMA-BUF来实现的)。
-
Application将这些绘制完成的图像buffer(可能不知一个)送给Wayland compositor,Wayland compositor会控制硬件,将buffer显示到屏幕上。
- Wayland compositor会搜集系统Applications送来的所有image buffers,并处理buffer在屏幕上的坐标、叠加方式后,直接通过ioctl,交给kernel KMS(kernel mode setting)模块,该模块会控制显示控制器将图像显示到具体的显示设备上
写在最后
到这里,Linux子系统Graphics图形栈介绍就告一段落了!编写该PPT的过程我也是小心再小心,谨慎再谨慎!但是难免其中可能会有一些概念或者理论不对,这个希望各位能多多指针!
最后要感谢网络上的各路大神,给我们提供了如此多的学习资料才让我对Linux子系统Graphics图形栈有了些皮毛的认识!路漫漫吾将上下而求索,总之,青山不改绿水长流先到这里了!