闲话

今早有空,简单聊聊硬件加速相关的话题。

在接触图形架构之前,常听到硬件加速的字眼。 比如离我们最近的电脑上,就会有显卡,就有硬件加速的问题。 我自己从来不玩游戏,对显卡硬件也不太了解,也没有去了解和研究过。

那时,对硬件加速的理解仅限于字面上,却不曾理解硬件加速 和显卡之间的具体关系,显卡具体是如何实现加速的,一言以蔽之,就是不理解硬件加速的本质。

从最近几月开始分析图形架构、对图形环境进行性能优化后, 硬件加速可谓是无法回避的话题,因为我们的目的是要提升图形性能 ,而硬件加速必然可能是一大突破点,尤其在CPU性能不行的场景下,依赖GPU可能是一种好的选择。 但在深入研究之前,其实我对硬件加速的理解还是仅停留在概念和表面。

在前期对图形架构不太了解的情况下,去了解了一下硬件加速的相关概念,但一开始总觉得里面术语太多,概念太多,图形架构太复杂,涉及模块太多,被绕得眼花缭乱,难以理解,有些无奈。

反复思考后,还是发现是由于自己这方面的基础太薄导致无法深入,心急吃不了热豆腐,必须要静下心来,花时间和精力把基础打好。

什么是基础?这个还比较抽象,必须要具体化,经过这段时间的摸索,我个人理解,暂时可以将其具体化到几本书,供大家参考,每个人理解可能都不一样,没关系,这里只是抛砖,欢迎讨论。

这几本书包括:

  • OpenGL红宝书
  • OpenGL蓝宝书
  • GPU大百科
  • 计算机图形学

几本书看起来像是不搭嘎,其实内容核心都差不多,都围绕硬件加速的概念展开。 在粗看(时间有限,只能粗看了)了这几本书后,对整个图形架构和硬件加速的理解确实有了新的理解和看法,理解了硬件加速的本质。

什么是硬件加速?

硬件加速到底是什么? 加速 在哪里体现?

硬件加速的本质就是:利用GPU来完成图形相关的操作(比如绘图和数学计算),从而让CPU空闲出来做其该做的事。

就这么简单。 那为什么用GPU来做图形相关的操作就叫加速呢? CPU难道会比CPU更慢么,不至于吧?

这个细节就比较复杂了,简单来说,CPU的逻辑计算能力确实比GPU强,主频也比GPU高很多,但是所谓各有所长,CPU和GPU各自擅长的领域不同,GPU专为图形而生,在图形相关的操作方面,能力确实会比CPU表现好很多。 由于图形相关操作中,涉及复杂的数学计算,任何一副生动的图像出现在我们面前,都经历了普通人无法想象的数学运算的过程,从最简单的点、直线、曲线,到曲面、纹理、光线等,在常见的游戏场景中,我们看到的逼真的人物表情,各种场景,都是通过数学公式计算出来的。 这其中的数学公式放到任何人面前,都足以让他崩溃。 还是借伟大的霍金的一句话(具体记不清楚了,大致意思如下):

如果我的书中出现的数学公式多一个,这本书的销量就会下降一半。

说实话,没人愿意去面对这些复杂的数学公式, 至少对我来说是这样。 其实,对于CPU来说,也是如此,CPU并不擅长做这些事,想想CPU内部设计的流水线,主要是用来执行指令,可没有专门用来做数学题的硬件单元,试想,如果让CPU去画一条直线,从起始端点到结束端点之间的所有 ,CPU可能都需要去计算具体位置、像素值,直线涉及的计算工作量还好,如果是画一条曲线 、或者一张曲面呢,复杂度恐怕要呈指数级上升了,恐怕要把CPU累趴下。

而GPU天生就是用来处理这些复杂度的,就是用来解决这些问题的,就是用来干这些CPU不愿意干的脏活和累活的,其硬件设计、流水线设计都围绕着解决这些问题的初衷出发,并且随着问题的不断更新和升级,GPU的硬件设计也在飞速的变化和演进。这里不讨论GPU的内部实现细节和原理,只讲概念,闲聊嘛。

似乎有点啰嗦了,上面的讨论的结论应该比较清楚了,GPU擅长做图形相关的操作,而CPU不擅长,所以,利用GPU来做图形相关的操作就意味着利用了硬件加速,此时CPU仅专注于它擅长的工作,做起来也一点不觉得累,对CPU来说,即使图形再复杂、公式再复杂,它只会觉得都与我无关

当CPU和GPU并肩一起工作时,体现出来的就是前所未有的生产力。

如何利用硬件加速?

新问题又来了,GPU这么好,如何才能利用它呢,对于应用程序开发者来说,看到的是一套套API接口,对应普通的用户来说,看到的只是一幅幅画面,我们怎么知道哪些操作是GPU完成,哪些是CPU完成;或者说我们怎么指定GPU去做想做的事呢?

显然,对应用程序开发者来说,需要是一套专门的编程接口和规范,这就是我们经常见到的一个术语了:OpenGL

没错,OpenGL就提供了这样的接口和规范,确切的说,OpenGL定义了一套跨编程语言、跨平台的编程接口规范,它用于生成二维、三维图象。 这套接口由数百个不同的函数调用组成,用来从简单的图元绘制复杂的三维图形(二维图形可看做三维的一个特例)。

准确的说,OpenGL只是一套标准和规范,OpenGL有不同的实现(由于硬件不同,厂商不同),实现的方式不同,但对用户提供的接口保持一致,这就是规范的作用。

我们知道,目前市面上主流的显卡硬件厂商,就只有3家:NVIDIA、Intel和AMD,目前仅NVIDIA一家比较封闭,驱动啥的都全部闭源,相比而言,Intel和AMD都比较开放,而且正在逐步开放的过程中。

目前最主流的OpenGL实现应该只有两种,一种就是Mesa,Meas是目前应用最广泛的一种标准的开源的OpenGL实现,目前类Unix环境中,基本都支持Mesa,目前Intel和AMD的OpenGL实现都基于Mesa,并在持续对Mesa贡献代码;仅NVIDIA自己实现了一套单独的、跟NVIDIA硬件相关的、闭源的OpenGL,与Mesa无关,选择的是截然不同的另一条道路。

无论OpenGL的底层实现如何,对于用户(应用程序开发人员)而言,看到的只是他们向上层提供的接口,这套接口具体就是libGL库,在我们Linux环境中,在Lib相关的目录下(比如/usr/lib64)就能找到该库,名称通常叫libGL.so,用户不需要关心底层的实现细节,直接使用相应API即可。正是由于OpenGL规范的存在,保证了这套API接口一致,所以,无论是哪家厂商的硬件,对用户来说,都没有区别(当然,部分厂商针对自己硬件的一些特殊功能,提供了一些特殊的接口,这里不关注),这就是标准和规范的魅力所在。

NVIDIA的闭源的OpenGL的实现,无从考究,所以就没什么可聊了,接下来再聊聊Mesa。

聊聊Mesa

如前面所说,Mesa一种OpenGL的开源实现,由于开源,所有很有探索空间。

OpenGL的功能和接口众多,底层硬件也各有不同,所有,Mesa的实现还是比较复杂,但不是这里讨论的重点。我们仅从架构上、原理上、概念上、本质上闲聊,只想让大家能更直观的理解Mesa的本质。

Meas简单来说,就是由两个库组成,这两个库分别代表两个层次:

  1. libGL库。就是前面所说的提供给用户层标准的API。
  2. DRI驱动库。Mesa是基于DRI(直接渲染框架)实现的,关于DRI就有更多的话题的,这里也不发散了,后面再单独的文章中继续聊。简单说DRI就是直接访问显卡硬件一套框架,DRI驱动就为提供直接访问显卡硬件提供了一套接口,对上层(libGL库)屏蔽了具体的硬件的细节。说是驱动,但实际是一个用户态的动态库,可理解为用户态的驱动。 由于跟硬件紧密相关,不同的厂商的DRI驱动实现各不相同,通常我们也不需要关注具体的实现细节。对于fedora系列的版本来说,DRI驱动通常封装在mesa-dri-drivers rpm包中,安装后相应库(驱动)文件通常存放在/usr/lib64/dri(对于64位系统来说)目录中,名称通常叫xxx_dri.so,比如我的而环境中是AMD的显卡,使用的就是radeon_dri.so

    [root@loongson ~]# rpm -ql mesa-dri-drivers /etc/drirc /usr/lib64/dri/kms_swrast_dri.so /usr/lib64/dri/nouveau_dri.so /usr/lib64/dri/nouveau_vieux_dri.so /usr/lib64/dri/r200_dri.so /usr/lib64/dri/r300_dri.so /usr/lib64/dri/r600_dri.so /usr/lib64/dri/radeon_dri.so /usr/lib64/dri/radeonsi_dri.so /usr/lib64/dri/swrast_dri.so

这两个库基本就是Mesa的全部了,从这个角度看,Mesa还是挺简单的吧。

聊聊2D加速

前面在聊硬件加速时,好像模糊了一些概念,那就是2D加速3D加速 ,硬件加速还分2D和3D?从软件的角度看,确实是的。

为什么说从软件角度? 因为现代的显卡硬件的流水线和硬件单元都是针对3D图形的绘制的,那2D图形的绘制该怎么办呢? 实际上2D图形只是3D图形的一个特例,或者叫3D图形的一个截面、一个快照,如果是一个平面(2D)的图形,对于专注于3D绘图的硬件单元来说,这只是从一个指定的视角(Z轴方向)来观察一个3D图形的结果,即将该图形的Z坐标视为0即可。 所以,2D图形完全可以利用3D绘图引擎来绘制。 对显卡硬件来说,并2D和3D并没有本质的区别。

也就是说,2D图形也可以利用显卡来绘制,那么,这个就叫2D加速么,其实还不是。这里所说的2D加速说的是X Server(Xorg)中通过直接访问显卡硬件,来绘制2D图形的功能。 了解Xorg的同学们应该知道(我前面的文章中也有提及),Xorg实际分为3层,中间的一层EXA/XXA实际就提供了2D加速的功能,基本原理是,通过直接访问硬件来实现图形绘制、窗口合成等操作,达到加速效果。

那EXA中的硬件加速和Mesa中的硬件加速有什么区别呢,个人理解,Mesa利用了显卡硬件的完整的流水线(5个单元),提供给硬件的信息更原始(比如顶点、纹理等信息),那么显卡硬件做的事就更多,分担得更多,所以效果更好。而相比之下,EXA仅利用了显卡硬件流水线的最后的单元,提供给显卡硬件的信息多是经CPU加工后的数据,所以,显卡硬件做的事相对较少,而CPU的负担更重,所以加速效果不如mesa。有关EXA的具体实现,还没有深入研究,后面有机会研究后可以继续聊,这里如有不当之处,欢迎大家指正。

另外,Xorg中还提供了直接使用Mesa来实现硬件加速的功能,这就是Glamour驱动,大致原理就是在Xorg中调用DRI驱动接口来绘图,从而利用Mesa。要使用这个功能,只需要在Xorg的配置文件中打开相应配置即可,在我们环境中试过,效果还不错,但也不绝对,开启后部分指标变好了,但也有变差的指标,这也说明了一个道理:

万事无绝对。 性能好与差,也是相对的,要找到一些放诸天下皆准的调优手段基本也是不可能的,只能针对特定的应用场景、特定的应用来做针对性的调优。

硬件加速是万灵丹吗?

最后再聊聊硬件加速的效果问题。 前面说的硬件加速看似很好、很完美,那硬件加速是提升图形性能的万灵丹么? 据前面提出的理论,答案肯定是否。 这还得分应用场景,对于纯粹的3D应用场景,比如3D游戏,硬件加速必然是关键,没有显卡,CPU累死也不会有好的效果。

但对于我们使用的桌面系统中普通的平面应用程序(绝大部分都是这样的程序)来说,就不一定了。为什么?那就需要再看看硬件加速对于CPU的消耗

前面不是说硬件加速就是用GPU来绘图么,为什么对CPU还有消耗?当然有,主要的消耗有来自两个地方:

  1. CPU和GPU的上下文切换。极端的例子:如果我们要画很多的点,如果用CPU来画的话,只需要CPU计算出每个点的位置和相应的像素值即可;如果要用GPU来画的,每个点我们都需要将相应的信息提供给GPU,然后通知GPU处理,GPU处理完成后再通知CPU,这个过程就是上下文切换,这个过程涉及信息交互、状态同步、硬件状态切换等,实际的消耗是比较大的。本来CPU来做这些事,应该是很轻松的,CPU也有足够的能力来完成,但如果硬要GPU来做,虽然GPU在做实际工作的效率可能更高,但上下文切换的消耗可能远比做实际工作的消耗大很多。
  2. 内存拷贝操作。我们可以将图像的绘制理解为向framebuffer中拷贝数据的过程,framebuffer直接对应了屏幕上显示的图像,要想将图像显示到显示器上,必然会有一个内存拷贝的过程。这里面的具体细节有待继续深入研究,这里仅需要指明,如果想利用GPU来绘图,则需要向GPU提供其需要的数据,这些数据的提供过程中,会涉及到从内存到显存的的数据拷贝过程,这个过程虽然多由GPU通过DMA完成,但DMA过程也是需要CPU参与的,如果操作比较频繁,则对CPU的消耗也是不能忽略的。

所以,绘制平面图形(比如,绘制窗口、按钮、进度条等),用硬件加速不一定性能就好,还是跟实际的应用场景相关,还取决于CPU和GPU的能力,不同的应用场景、不同的硬件配置,效果都不相同,目前还看到比较官方的测试数据,甚至还很缺乏测试和评估手段,这个话题我们可以另外再聊。

这可能也是GTK一直没有直接支持OpenGL后端的原因吧。