闲话

刚转向图形方向研究时,常会感到迷茫,不知从何处开始,因为乍看图形领域内的模块和陌生的概念似乎多出了我的预期,看似比之前认为足够复杂的Linux内核更加复杂,内容更丰富,事实似乎确实如此。

现在看似有些感觉了,回想当初的迷茫,又感觉很正常,经历过此般阵痛后方能有更深的领悟。

闲话不多说,先说说从图形架构开始吧,说到图形架构,首先需要了解的就是图形栈(Graphic Stack)了,从前甚至没有听说过这个术语,就从这里开始吧。

图形栈

图形栈其实就是图形环境中的层次结构,对于复杂的架构,优秀的设计(或者说当前流行的设计)总是层次分明的,为什么?松耦合,更灵活,依赖倒置,单一职责,接口分离…,Linux内核中也随处可见类似的设计。

关于图形栈,网上也有一些资料,但在最初切换到该领域开始分析时,看到这些让人眼花缭乱的图和理不清的关系,总会觉得头痛,现在回想起来,主要是因为:

  • 不一致。不同的人对图形栈的理解不一样,画出来的图也有些差别,如今明白,似乎确实没有完全标准的答案,本文也是根据自己的理解来描述,如果不当之处,欢迎指正。

  • 架构太复杂。整个图形栈中,涉及的模块太多,包括各种三方(甚至闭源)的模块,各种技术:3D加速、2D加速、OpenGL、Mesa、GLX、GTK2+、GTK3+、QT、Wayland、Xorg、视频加速、Cairo、Clutter、Mutter、Mir、Gnome-Shell、Unity、Compiz、合成器、窗口管理器…,各个模块间关系错综复杂,上下层关系、包含关系、并列关系等等,足以杀死柔弱的大脑细胞。

  • 认识太浅。在对图形架构有较深入的了解之前,很难理清这么多模块之间错综复杂的关系,感觉头痛最主要的原因应该还是认识太浅,还没有足够系统的知识和理解,随着了解的一步步深入,这种情况会逐渐改善,当然,这仍是一个较漫长和折腾的过程。

当前流行的图形系统(针对Xorg的环境,Wayland和其他后端暂不讨论)的层次结构,最权威的图莫过于来自XFree86官方的说明图了:

来自XFree86官方的架构图

这个图包含的内容非常全,结构也比较清晰,但是如果不熟悉其中的相关概念和术语,则很难准确理解。

下图为根据个人理解重新整理的架构图,附带相关说明,希望能让大家的理解更容易些,也作为个人后续继续深入分析的参考和指引,可以随时翻阅,后续在有新内容或是发现问题时,也会随时修正:

个人理解的Graphic Stack

相关解释如下:

  • 2D Application,2D应用程序,相对于3D应用程序而言。在Linux图形环境中的应用程序大部分为此类程序。此类程序通常基于图形ToolKit开发,常见的图形ToolKit有GTK+和QT,这个相信大家都已经耳熟能详了。
  • GTK+是Gnome图形环境默认采用的开发套件,也是当前大部分Linux图形桌面环境采用的开发套件。
  • QT是另一种主流的开发套件,也是很多开发者青睐的,相比GTK+,个人感觉基于QT的应用开发更容易,UI设计效果也更佳,这可能是当前更多人选择QT的原因之一。
  • GTK+和QT都有不同的backend。
  • 在X11(使用Xorg作为图形引擎,后文同)的环境中,默认的backend都是Xlib,Xlib是Xorg提供给客户端的绘图接口,客户端使用Xlib可以绘制基本的图形元素,比如直线、矩形等。即GTK+和QT在X11环境中默认都通过Xorg进行绘图。
  • 其他的backend各有不同,比较典型的是OpenGL的后端,即基于OpenGL的接口,利用GPU的3D加速功能,来实现绘图,这种后端能重复利用GPU来完成绘图操作,从而实现加速,理论上比Xlib后端性能更好,但是,由于使用GPU需要相关的上下文切换操作和相关的内存拷贝操作,在绘制简单的2D图形时(比如简单的直线和矩形等),未必会比Xlib后端性能好,还取决于具体的场景和硬件配置等。
  • QT本身支持OpenGL的后端,只需要在编译QT时,加上–enable-gl的参数,即可默认使用OpenGL的后端,根据个人了解的情况,在手机等终端设备上OpenGL后端的性能是有明显优势的,这也是当前QT的主流。
  • 相比之下,GTK+对OpenGL的支持就非常不到位了,GTK+当前有两个主要版本:GTK2+和GTK3+,GTK3+是当前官方主要维护和推荐使用的版本,GTK2+已经基本不再维护,但是由于以往大量的程序还是基于GTK2+开发的,所以,GTK2+短期内不会消失,但新写的应用程序都推荐使用GTK3+了。GTK3+相对于GTK2+,改动比较大,比如:将所有的绘图操作都切换至了Cairo,不再直接依赖于Xorg,这样能大大提升应用程序的移植性,看起来不错,但最大的问题在于不能保持与GTK2+的接口兼容,导致大量的程序,如果需要迁移到GTK3+的话,需要不少的移植工作量,个人曾尝试过,期间的折腾特别能体会。又扯远了,实在是内容太多,话题引出后不好收敛,继续backend的话题吧。GTK+自身并不能设置backend,但GTK2+中部分绘图操作采用了Cairo,GTK3+中所有的绘图操作都采用了Cairo,而Cairo可以支持不同的backend,特别是新版本中的Cairo中支持了OpenGL的后端,甚至还支持DRM的后端,所以,从表面上看,GTK+自身看似可以借助于Cairo来使用OpenGL,从而利用GPU来实现硬件加速,看起来有些曲折,但看似可行。但事实并非如此,经过深入分析可以发现GTK+本身无法直接使用OpenGL,这个在后面单独的文章中继续聊吧,这里就不再发散了。虽然GTK+不能支持OpenGL的后端,但从GNOME的官方答复看,他们似乎对OpenGL的兴趣不大,也许认为OpenGL并不会对GTK+的性能有多大益处,好像没有明确计划来改善。
  • 虽然如此,GTK+也并非完全不能支持OpenGL,事实上,有需求就用动力嘛,眼看QT中的OpenGL应用得如火如荼,相信有不少人也希望在GTK+中能用上OpenGL,先不论性能是否能有期望的提升,至少要看看效果吧。于是出现了GTK+中的几个扩展库(其实很早之前就有了),如gtkglext和gtkglarea,其中gtkglext看似已死,此类扩展库都需要应用程序编写时就使用相应库中的指定接口,比如创建GL的context等操作,如此编写的代码确实能用上OpenGL,但是这无法让GTK+库自身默认使用OpenGL,即无法让GTK+完全利用OpenGL,对于应用程序来说,相当于要使用新的一套接口,对于已有的应用程序来说,就只剩眼看的份了~。另一方面,Gnome3环境中默认提供了clutter库,该库使用了OpenGL的后端来绘制2D图形,Gnome3中默认的额窗口管理器Mutter即使用了clutter库,用户也可以使用clutter库进行开发,从而利用OpenGL,原则上与gtkglext和gtkglarea类似,需要重新编写代码,这里就不赘述了,后面有机会再深入聊。
  • 3D Application。3D应用程序,即利用OpenGL 3D加速绘制的3D程序。
  • 先聊聊OpenGL和Mesa的关系,OpenGL本身只是一套规范和标准,并不是实际的库或开发工具,而Mesa是OpenGL的一种开源实现,也是目前的主流,获得的Intel和AMD和大力支持,目前Intel和AMD的主要显卡都支持Mesa,并长期向社区提供相应Mesa驱动的支持和维护,仅有NVIDIA还是一贯的封闭态度,自己实现OpenGL,闭源,跟NVIDIA显卡强绑定。
  • Mesa本质上是由两部分组成:LibGL库和DRI驱动.
  • LibGL库是提供和应用程序的一套开发库,即实现了OpenGL的编程接口,用户使用LibGL库提供的开发接口开发OpenGL应用程序,对用户屏蔽了底层的实现细节。
  • DRI驱动是与硬件相关的用户态驱动,通常有硬件厂商提供,并合入社区,比如AMD的r600_dri.so,该驱动可直接访问硬件(GPU),向GPU提供需要的信息(比如VBO、纹理等,具体请参考OpenGL相关的编程手册和GPU的基本原理)后,直接利用硬件加速功能,实现绘图操作。

  • 3D应用程序通常直接基于OpenGL的接口(由LibGL库提供)开发,这种情况也叫客户端直接渲染,这种场景下,应用程序直接调用LibGL接口,而LibGL直接使用DRI驱动进行绘图,主要的绘图过程都不需要X Server的参与,效率最高,Xorg不会成为性能瓶颈;同时,主要的绘图(比如相应的几何处理、纹理处理、合成处理)操作都由GPU硬件完成,CPU基本不参与,此时CPU可以空闲来做其他任务,CPU也不会成为性能瓶颈。这种方式也是目前3D应用程序的主流开发方式。

  • GLX是X11环境中OpenGL扩展,要在X11环境中使用OpenGL,可以直接使用GLX库开发,GLX可以让应用程序直接利用GPU硬件的流水线进行渲染,而尽量减少X Server的参与和交互,详细情况在其他文章中聊。

  • Xorg。X11环境中的X Server程序,大家应该都了解,这里简单介绍下其层次结构:
  • DIX。设备无关层,向上层提供统一的接口,与底层的硬件设备无关。
  • 硬件加速层。这里分2D加速和3D加速,2D加速通过EXA/XXA架构实现,基本思路还是基于底层的DDX模块,直接访问GPU硬件,利用硬件实现加速。3D加速通过Glumor模块实现,默认没有开启,需按需通过配置打开,基本原理是利用DRI驱动实现硬件加速。
  • DDX。设备相关层,与硬件相关的驱动,由各显卡硬件厂商提供相应的驱动(用户态驱动),用于访问显卡硬件,实现2D加速。

  • DRM。Direct Render Manger,内核模块,与DRI(直接渲染框架)联合使用,为用户态程序提供直接渲染的功能,也就提供了一套可以直接访问显卡硬件的接口。DRM也分为两层,上层是公用的drm模块,对用户态提供统一的接口LibDRM库,底层是与硬件相关的驱动模块,不同的显卡驱动提供不同的实现,向上层屏蔽了实现细节,通常不用关心。同样,NVIDIA通常采用了封闭策略,而Intel和AMD都比较开放,向社区合入代码并提供维护。DRM模块知道显卡的中断、显存以及DMA相关的信息,它的主要作用是管理显存,向用户态提供显存管理的相关接口,比如用户态应用程序可以通过DRM接口(libDRM)分配显存上的内存,或者触发内存到显存的DMA操作。用户态程序可以通过DRM直接访问framebuffer。

  • 图形硬件。这个就是显卡,或者理解为GPU(其实不只是GPU,还有显存和其他相关的硬件),相较于CPU,其擅长于图像处理、数学计算、并行计算,让CPU做擅长的事,即利用GPU完成绘图的主要操作,此时就给CPU减轻了极大的负担,这便是硬件加速的本质所在,而新的显卡硬件都只有3D加速框架,没有专门针对2D的加速框架,所以,所有的2D加速本质上都是利用3D加速的硬件框架(流水线)实现的。