Mesa, also called Mesa3D and The Mesa 3D Graphics Library, is an open source software implementation of OpenGL, Vulkan, and other graphics API specifications. Mesa translates these specifications to vendor-specific graphics hardware drivers.
Mesa 实际上是一个库,它实现了多种图形 API 规范,这其中就包括了最著名的 OpenGL。Mesa 底层直接使用图形硬件驱动。
Mesa implements a translation layer between a graphics API such as OpenGL and the graphics hardware drivers in the operating system kernel.
上面这句话基本上点到了 Mesa 的实质。Mesa 实际上就是一个转换层,它提供了图形 API(比如:OpenGL)到图形硬件(比如:显卡 GPU)驱动之间的一个转换。
它诞生之时,是在 CPU 上进行所有渲染的,后续又发展为通过 display server 非直接的渲染。但其内部架构在设计上支持使用显卡硬件 3D 渲染的。
可以说,在适配 DRI 架构时,正式奠定了 Mesa 的位置:
With adapting to DRI, the Mesa library finally took over the role of the front end component of a full scale OpenGL framework with varying backend components that could offer different degrees of 3D hardware support while not dropping the full software rendering capability.
Mesa (或Mesa3D)是一个 OpenGL/Vulkan 的实现,以及为所有开源图形驱动提供各种 GL 的入口点, 它是一个项目的名字。由它编译出来的库是下面这些:
或者这些文件:
其中需要注意的是这几个文件:libGL.so,libEGL.so,libGLESv2.so,根据名字很显然,他们分别实现了对应的API。
Mesa有两个作用:
- 对接各种GPU硬件,将应用层对GL API的调用转换到对硬件GPU的调用上;
- 各种 GL API 的纯软实现,当没有可用的硬件时,它可以提供传软件的 GL API 的实现;
作者:keyou
链接:https://juejin.cn/post/6844903841096269837
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
mesa解析1 线程本地存储 libGL.so原理_mesa libglapi.so-CSDN博客
TLS机制:
openGL维护了一个状态机,或者叫上下文;gl开头的api函数,如glUniform4fv,会修改这个上下文
系统中,如果有多个基于openGL的程序在运行,则每个程序有一个独立的上下文。每个程序调用gl api都作用于自己的上下文。
但是,libGL.so是一个共享库,每个基于openGL的程序都将其映射到自己的地址空间,共享库的代码段在系统运行期间是不变的。要同一份二进制程序,在不同程序中访问到各程序自己的gl上下文,线程本地存储(TLS / thread local storage)机制就是为了解决这个问题
每个线程有独立的地址空间,并且其中有一块用作线程本地存储(TLS)。操作系统在上下文切换时,会将TLS指针放进一个特殊的寄存器;共享库读取这个寄存器,就可以得到该线程的TLS指针,而上下文在TLS段中具体什么位置,是编译器决定的。
也就是说,约定所有程序的上下文指针都存在TLS段中特定位置,则libGL.so中读取TLS指针,加上特定偏移,就可以得到gl上下文
libGL.so基本原理
应用程序调用gl开头的函数,这些函数的定义都在libGL.so中
每个api函数的入口实现是统一的:读取TLS指针,从TLS段中读取分发表指针,从分发表中特定偏移处读取实际的实现函数的指针,通过这个函数指针进行调用
也就是说,libGL.so这个共享库是一个空壳,一个中介。在使用这个库提供的gl api函数之前,应用程序要先创建上下文和分发表,并将上下文和分发表的指针写进TLS中
上下文和分发表的定义在驱动中,如r600_dri.so,api函数的实际实现,也都在这个驱动中。
应用程序通过egl的接口,来创建gl上下文,设定上下文(将上下文和分发表注册到TLS中),之后,应用程序就可以调用libGL.so中的gl api函数了
细节上,上下文和分发表的注册功能,由libglapi.so完成,所以,libelg是调用libglapi.so中的函数,来设置上下文和分发表,libGL.so是调用libglapi.so中的函数(不过是内联调用),来获取分发表
egl以dlopen的方式,打开驱动,如r600_dri.so,从驱动中以dlsym的方式,获取驱动函数表,调用驱动函数,来创建上下文。
正向的说:egl以dlopen方式打开驱动,获取并调用驱动中的函数,来创建上下文和分发表,用glapi的接口,设置上下文和分发表到TLS;然后用户调用gl的api函数时,libGL.so中api函数会从TLS获取分发表,从分发表读取驱动函数并调用,以实现该api的功能,如glUniform4fv
————————————————
版权声明:本文为CSDN博主「xueshuangbai」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xueshuangbai/article/details/47024505
MESA : GL Dispatch
OpenGL函数的分发机制相当复杂,这个文章试图解释其中的一些议题,并且给读者介绍Mesa的分发层实现。
1 GL Dispatch的复杂之处
每一个GL应用至少拥有一个名叫GL context的对象,这个对象是所有GL function隐含的内置参数,储存了所有应用当前状态下的所有GL状态。例如纹理、VBO之类的上下文信息都存放在context对象中。一个应用可以包含多个context,当前时刻哪个context被使用是由一个和窗口相关的函数去指定的(glXMakeContextCurrent)。
例如在使用GLX实现OpenGL With X-Windows的时候,每一个GL的函数指针都是通过glXGetProcAddress函数获得的,获得的函数指针都是contex independent 的,无论是哪个上下文对象被激活,都是相同的函数被调用。
这是GL Dispatch复杂的原因之一!
一个应用包含两个GL contexts。一个context是直接渲染上下文,其中函数调用直接路由到应用程序地址空间中加载的驱动程序(One context is a direct rendering context where function calls are routed directly to a driver loaded within the application’s address space),另外一个context间接渲染上下文,其中函数调用将会被转化为GLX协议并且发送给服务端(The other context is an indirect rendering context where function calls are converted to GLX protocol and sent to a server)。在这两种情况下,类似glVertex3fv这样的gl函数都需要做出相同的正确的事情。
高度优化的驱动或者GLX协议实现会希望更具当前的状态更改GL函数的行为,例如,glFogCoordf函数会根据fog是否enable而产生不同的影响。
在多线程的环境中,每一个线程包含不同的GL context是完全可能的。这个意味着可怜的glVertex3fv函数必须要去知道哪个contex是当前线程的contex,函数在什么时候被调用。
2 Mesa实现的概览
Mesa 每个线程使用两个指针,第一个指针记录了当前线程使用的context地址,第二个指针记录了一个与contex对应的dispatch table地址。dispatch table储存了一个真实实现此功能的函数指针。每个时刻,一个新的context被创建在一个线程中,这两个指针都会被更新。
其实 glVertex3fv这样的函数实现在概念上很简单:
- 获取当前的dispatch table指针
- 从dispatch table指针中获取确实实现glVertex3fv的函数指针
- 调用真实的函数指针
这个函数可以被声明的非常简单,甚至就几行C代码。见src/mesa/glapi/glapitemp.h可以看到类似的代码。
void glVertex3f(GLfloat x, GLfloat y, GLfloat z)
{
const struct _glapi_table * const dispatch = GET_DISPATCH();
(*dispatch->Vertex3f)(x, y, z);
}
但是上述这个简单的实现存在问题,他给每次调用此GL函数带来很大的开销。
在多线程的环境里,一个原始的GET_DISPATCH()函数会包含一个调用_glapi_get_dispatch()
or_glapi_tls_Dispatch
来优化开销。
3 优化
近一些年产生了很多优化的方法来减少因为GL dispatch带来的性能损失,这个章节将会描述这些优化方法,列出了每种优化的好处,以及在什么时候可以用此优化在什么时候不能。
3.1 ELF TLS
从2.4.20 Linux Kernel以来,每个线程都会被分配一个全局的储存区域,可以使用GCC的一些扩展(ELF TLS)指令使得某些变量存放在此区域。如果可以把dispatch table指针存放在这个区域,那么调用pthread_getspecific 函数和_glapi_Dispatch函数就可以避免了,这些函数会产生大量的软件开销。
mesa不支持2.4.20以前的linux内核,所以理论上完全支持ELF TLS。
dispatch table 指针被存放在一个新的变量中 名为 _glapi_tls_Dispatch。使用一个新的变量名意味着libGL可以实现两种不同的接口,这使得libGL可以使用任意一个接口的直接渲染驱动程序。一旦正确声明了指针,GET_DISPACH 就变成了一个简单的变量引用。
extern __THREAD_INITIAL_EXEC struct _glapi_table *_glapi_tls_Dispatch;
#define GET_DISPATCH() _glapi_tls_Dispatch
https://blog.csdn.net/qq_30599505/article/details/126494369
DRI是包含现代 Linux 图形堆栈的框架,它允许非特权用户空间程序向图形硬件发出命令,而不会与其他程序发生冲突。DRI 的主要用途是为 OpenGL 实现的 Mesa 提供硬件加速,也适用于为没有图形界面的framebuffer console提供OpenGL加速。
DRI实现分散在X Server及其关联的clients库、Mesa 3D和DRM中。所有的源代码都是开源免费的。
概述
在经典的 X Window 系统架构中,X Server 是唯一能够独占访问图形硬件的进程,因此也是在framebuffer进行实际渲染的进程。X clients所做的就是与 X Server通信以分派渲染命令。这些命令是独立于硬件的,这意味着 X11 协议提供了一个提取图形设备的 API,因此 X clients不需要担心底层硬件的细节。任何依赖硬件的代码都位于设备相关 X 中,X Server 中管理video card或显卡驱动的部分,通常也称为视频或图形驱动程序。
3D渲染的兴起把这种框架的缺点暴露了出来。3D图形应用程序倾向于生成大量的命令和数据,这些命令和数据都必须发送到X Server进行渲染。随着 X client和 X Server 之间的进程间通信 (IPC) 数量的增加,3D 渲染性能受到了影响。X 驱动程序开发人员为了利用最新显卡的 3D 硬件功能,需要新的无 IPC 架构。Xclients应该直接访问图形硬件,而不是依赖第三方进程来执行此操作,从而节省所有 IPC 开销,这种方法称为“direct rendering”。DRI最初是为了允许任何X clients使用这种“direct rendering”方法执行3D渲染。
架构
DRI基础架构包含3个主要组件:
- DRI client是一个可以直接执行’direct rendering’的X client - 需要一个硬件特定的驱动程序来管理video card或者显卡驱动以便可以在其上可以渲染。这些 DRI 驱动程序通常作为客户端动态链接共享库提供。
- X Server提供了一个X11扩展协议(DRI扩展),DRI 客户端使用该扩展与窗口系统和 DDX 驱动程序进行协调。作为 DDX 驱动程序的一部分,X Server进程也同样动态链接到与DRI client相同的DRI驱动程序,但是会使用GLX扩展为X clients提供硬件3D加速渲染以进行间接渲染(如远程X clients不能直接使用渲染)。对于2D渲染,DDX 驱动程序还必须考虑使用相同图形设备的 DRI 客户端。
- 对video card或显卡驱动的访问由DRM内核模块管理。但是X Server DDX驱动和每个X Client DRI驱动必须使用DRM来访问显卡。 DRM 为显卡的共享资源(例如命令队列、卡寄存器、视频内存、DMA 引擎等资源)提供同步,确保多个用户空间进程的并发访问不会互相干扰。 DRM还有安全保护的作用,它不允许任何X客户端访问执行3D渲染所需的硬件。
DRI1阶段
在最初的 DRI 架构中,由于当时video card的内存大小,屏幕back-buffer和front-buffer的单例被DRI client和X Server所共享。渲染的数据会被先写入back-buffer,然后在v-blank间隔时间会与front-buffer进行交换。为了把数据渲染到back-buffer,DRI进程应该确保裁剪渲染数据只保留可视窗口大小。
与 X Server的同步是通过信号和称为 SAREA 的共享内存缓冲区完成的。对 DRM 设备的访问通道是唯一的,因此任何 DRI clients都必须在渲染操作开始时锁定它,其他client期间无法访问,必须等到当前渲染操作结束时释放锁。另一个缺点是在当前 DRI 进程在设备上释放锁后,操作涉及的内存不会常驻,因此上传到显存的任何数据(如纹理)都会丢失,以供即将进行的操作使用,从而对图形性能产生重大影响。
DRI2阶段
由于像 Compiz 这样的合成窗口管理器越来越受欢迎,DRI 必须重新设计以便X client在进行直接渲染时也同样支持重定向到"offscreen-pixmaps"。常规的 X client已经将 X 服务器提供的单独像素图作为渲染目标(所谓的"offscreen-pixmaps"),但 DRI client继续直接在共享的back-buffer中进行渲染,有效地绕过了合成窗口管理器。最终的解决方案是改变DRI处理渲染buffer的方式,这会导致具有一组完全不同的DRI扩展接口,并且DRM也发生了巨大变化。新的扩展名为“DRI2”,虽然它不是更高版本,而是一个不同的扩展,甚至与原始 DRI 不兼容——事实上,两者在 X Server 中共存了很长时间。
在DRI2中,每个DRI client都拥有自己私有的 back-buffer来通过硬件加速渲染器窗口内容,而不是共享back-buffer。然而,DRI client将其与一个假的"front-buffer"进行交换。假的"front-buffer"是合成窗口管理器最终合成屏幕缓冲区的buffer之一,,在 VBLANK 间隔与实际的front-buffer进行交换。
为了处理所有这些新的buffer,DRM必须必须要有新功能,特别是显存管理器。DRI2最初使用的是还不成熟的TTM内存管理器,但是DRM内存管理器还是最终选择了GEM。新的DRI2内部buffer管理模型还解决了最初 DRI 中存在的两个主要性能瓶颈:
- DRI2 clinet在使用它进行渲染的时候不再锁住整个DRM设备,因为现在每个client都获得了一个独立于其他进程的单独渲染buffer.
- DRI2 client可以在显存中分配自己的buffer(带有纹理、顶点列表等),并且根据需要可以常驻,从而减少显存带宽消耗。
在DRI2中,为窗口分配私有offscreen-buffers(back-buffer、fake-front-buffer、 depth-buffer、stencil-buffer等)由X Server自己完成。DRI client通过调用 DRI2 扩展中 DRI2GetBuffers 和 DRI2GetBuffersWithFormat 等接口来检索这些buffers以便渲染到窗口中。在内部,DRI2 使用 GEM 名称——一种由 GEM API 提供的全局句柄,允许访问 DRM 设备的两个进程引用同一个缓冲区。
X Server 负责窗口渲染buffer分配的原因是 GLX 扩展允许多个 X client在同一个窗口中协同进行 OpenGL 渲染。 这样,X Server在整个渲染过程中管理渲染buffer的整个生命周期,并知道何时可以安全地回收它们。当调整窗口大小时,X server负责分配新的渲染buffer来匹配新的窗口大小,并使用 InvalidateBuffers 事件通知DRI clinet渲染到窗口中的更改,以便他们检索 GEM新缓冲区的名称。
DRI2 扩展为 DRI client提供了其他核心操作,如DRI2Connect接口可以找出他们应该使用哪个 DRM 设备和驱动程序 或DRI2Authenticate接口通过 X Server进行身份验证以便能够使用 DRM 设备的渲染和缓冲设施。使用 DRI2CopyRegion 和 DRI2SwapBuffers 请求在屏幕中呈现渲染buffer。DRI2CopyRegion 可用于在fake-front-buffer和real-front-buffer之间进行复制,但它不提供与v-black间隔的任何同步,因此会导致撕裂。 另一方面,DRI2SwapBuffers在back-buffer和front-buffer之间执行v-blank同步交换。
DRI3阶段
虽然 DRI2 比DRI 有了显着的改进,不过扩展也引入了一些新的问题。2013年,为了解决新的问题,DRI3作为DRI的第3次迭代被开发了。
DRI3 与 DRI2 相比的主要区别在于:
- DRI3 client为自己分配渲染缓冲区,而不是依赖 X Server进行分配(这是DRI2 支持的方法)。
- DRI3 摆脱了旧的基于 GEM 名称(全局 GEM 句柄)的不安全 GEM buffer共享机制(用于在 DRI client和 X Server之间传递buffer对象),转而采用基于 PRIME DMA-BUF(使用文件描述符) 的更安全和通用的机制。
client的缓冲区分配打破了 GLX 的假设,因为多个 GLX 应用程序不再可能在同一个窗口中协同渲染。 从好的方面来说,DRI client在其生命周期内负责自己的缓冲区这一事实带来了许多优势,例如DRI3 client很容易确保渲染buffer的大小始终与窗口的当前大小相匹配,从而消除由于client和server之间缺乏缓冲区大小同步而困扰窗口大小调整。还实现了更好的性能,因为 DRI3 client节省了等待 X server发送渲染buffer的额外耗时。DRI3 client(尤其是合成器窗口管理器)可以利用保留前一帧的buffer区并重用它们仅对渲染窗口中变化的部分进行重绘。DRI3 扩展不再需要修改以支持新的特定缓冲区格式,因为现在直接在 DRI client驱动程序和 DRM kernel驱动程序之间处理。另一方面,文件描述符的使用允许kernel对任何未使用的 GEM buffer对象执行安全清理。
从技术上将,DRI3包含2个扩展:“DRI3”扩展和“Present”扩展。DRI3 扩展的主要目的是实现在 DRI client和 X Server之间共享直接渲染buffer的机制。DRI client分配和使用 GEM buffer对象作为渲染目标,而 X server使用一种称为“pixmap”的 X11 对象表示这些渲染缓冲区。DRI3 提供了两种接口:DRI3PixmapFromBuffer 和 DRI3BufferFromPixmap,前者是从 GEM buffer对象(在“DRI client空间”中)创建pixmap (在“X server空间”中),后者是反向操作。在这些 DRI3 接口中,GEM buffer对象作为 DMA-BUF 文件描述符而不是 GEM 名称传递。DRI3 还提供了一种在 DRI client和 X server之间共享同步对象的方法,允许对共享buffer进行序列化访问。和 DRI2不同,DRI3Open(每个 DRI 客户端必须请求知道要使用哪个 DRM 设备的操作)将已打开的文件描述符返回到设备节点而不是设备节点文件名,并且任何所需的身份验证过程已经提前执行 X sever。
DRI3 没有提供在屏幕上显示渲染缓冲区的机制,而是依赖另一个扩展,即 Present 扩展来做到这一点。Present 之所以如此命名,是因为它的主要任务是在屏幕上“Present ”缓冲区,这意味着它使用client应用程序提供的渲染缓冲区的内容来处理帧缓冲区的更新。屏幕更新必须在适当的时间完成,通常是在 VBLANK 间隔期间,以避免出现撕裂等显示问题。Present 还处理屏幕更新到 VBLANK 间隔的同步。它还使用事件让 X client知道每个缓冲区真正显示在屏幕上的时刻,因此client可以将其渲染过程与当前屏幕刷新率同步。
Present 接受任何 X pixmap 作为屏幕更新的来源.由于pixmap 是标准的 X 对象,Present 不仅可以由执行直接渲染的 DRI3 客户端使用,而且可以由任何 X 客户端以任何方式在pixmap 上渲染。例如大多数基于非 GL 的 GTK+ 和 Qt 应用程序过去使用 XRender 进行双缓冲pixmap 渲染。这些应用程序也可以使用 Present 扩展来实现高效且无撕裂的屏幕更新。这就是 Present 作为独立扩展而不是 DRI3 的一部分的原因。
除了允许非 GL X client与 VBLANK 同步之外,Present 还有其他优势。DRI3 图形性能更好,因为 Present 在交换缓冲区方面比 DRI2 更有效。基于 Present 提供的新功能, DRI2 中许多不可用的 OpenGL 扩展被支持。
Present 为 X client提供了两个主要接口:PresentPixmap:使用pixmap 的部分或全部内容更新窗口的区域;PresentSelectInput :设置与client想要通知的窗口的Present事件的类型 。窗口可以通知 X client的三个Present事件:1、当PresentPixmap 的调用完成时(PresentCompleteNotify); 2、当 PresentPixmap 使用的pixmap 准备好被重用时(PresentIdleNotify ); 3、当窗口配置(主要是窗口大小)发生变化时 (PresentConfigureNotify)。PresentPixmap 执行直接复制 (blit) 到front-buffer还是整个back-buffer与front-buffer的交换是 Present 扩展实现的内部细节,而不是 像DRI2那样X cleint的显式选择。
采用
几个开源 DRI 驱动程序,包括用于 ATI Mach64、ATI Rage128、ATI Radeon、3dfx Voodoo3 到 Voodoo5、Matrox G200 到 G400、SiS 300 系列、Intel i810 到 i965、S3 Savage、VIA UniChrome 图形芯片组和 Nvidia的nouveau。一些图形供应商使用的闭源 DRI 驱动程序,包括 ATI 和 PowerVR Kyro。
各种版本的 DRI 已由各种操作系统实现,其中包括 Linux kernel、FreeBSD、NetBSD、OpenBSD 和 OpenSolaris。
历史
该项目由 Precision Insight(由 Silicon Graphics 和 Red Hat 资助)的 Jens Owen 和 Kevin E. Martin 发起,它最初是作为 XFree86 4.0的一部分广泛使用的,现在是 X.Org Server 的一部分。它目前由自由软件社区维护。
DRI2 的工作始于 2007 年 X 开发者峰会,由 Kristian Høgsberg 提出。Høgsberg 自己编写了新的 DRI2 扩展以及对 Mesa 和 GLX 的修改。2008 年 3 月,DRI2 基本完成,但它无法进入 X.Org Server 版本 1.5,不得不从 2009 年 2 月开始等到版本 1.6。DRI2 扩展正式包含在 2009 年 10 月的 X11R7.5 版本中。 DRI2 协议 (2.0) 的第一个公共版本于 2009 年 4 月发布。从那以后进行了多次修订,最近的版本是 2012 年 7 月的 2.8 版。
由于 DRI2 的一些限制,Keith Packard 和 Emma Anholt 在 2012 年 X.Org 开发者大会上提出了一个名为 DRI-Next 的新扩展,该扩展在 Linux.conf.au 2013 上再次被提议为 DRI3000,DRI3 和 Present 扩展是在 2013 年开发的,并于 2013 年 12 月合并到 X.Org Server 1.15 版本中。DRI3 协议 (1.0) 的第一个也是唯一一个版本于 2013 年 11 月发布。下面5个图是DRI框架变化过程:
图1:2D drivers在X Server
图2:通过GLX间接渲染
图3:早期DRI:由X-display-server以root身份执行mode-setting
图4:所有访问都通过DRM进行
图5:在Linux内核3.12中引入了渲染节点;DRM 和 KMS 驱动程序被拆分。Wayland 通过 EGL 实现直接渲染