我最近开始接触 OpenGL,正在研究 opengl 如何使用 X 窗口系统直接或间接渲染。
我的理解是,为了直接渲染,它使用了称为 DRI(直接渲染基础设施)的东西。然后DRI利用DRM(直接渲染管理器)直接访问硬件..(我不知道我是否理解正确)
为什么我们需要 OpenGL,为什么我们不能直接使用 DRM 访问硬件?
答案1
什么是OpenGL,什么是显卡?
OpenGL 是高级 3D 图形库。
虽然它用于与“显卡”或更确切地说是 GPU 进行对话,但它将许多支持 3D 的卡之间的许多差异抽象为更简单、更连贯的 OpenGL“概念空间”。 OpenGL 不关心计算机是否有 PCI、是否是 SoC、卡有多少个控制寄存器或任何其他不重要的硬件细节。
支持现代 OpenGL 的 GPU(例如 nvidia、ati 或移动芯片组中的 GPU)本身并不是真正的“图形”卡。
事实上,如今现代 GPU 的实际“显卡”只占其电路的 0.01%。
事实上,现代GPU是非常复杂的“3D图形引擎”。以至于GPU芯片99.99%都是这个引擎。
我们也不是在谈论像像素这样的低级基元。
例如,几乎所有过去的 GPU(大约 00 年)都有所谓的“2D blitter”引擎,即实际的物理电路,用于位图传输,即“位块传送”。
像 SNES 这样的旧游戏机具有类似的电路(传输),可以将图像精灵(一步)传输、转换、拉伸和旋转到帧缓冲存储器中。我们倾向于将该电路称为“旋转缩放器”。因为它是用硬件表达的,所以即使是 8/16 位计算机,SNES 也可以轻松实现 60fps 的流体图形(自然,精灵的数量受到给定芯片设计的限制)。
现代 GPU 甚至不再具有“2D blitter”或“rotozoomers”,因为当您在专门配置的正投影中绘制由 2 个三角形组成的 3D 四边形时,GPU 的 3D 引擎本质上可以像旧的 blitter/rotozoomer 一样工作,但是因为它由实际的 3D 电路进行处理,您拥有令人难以置信的灵活性:片段着色器、顶点着色器、Z 缓冲区、模板缓冲区、顶点数组等。可以将其视为类固醇的超级旋转缩放器。
事实上,如今大多数 GPU 都非常先进,它们可以完全自主地在屏幕上的大量实例中绘制、纹理和着色复杂的 3D 模型 - 即现代 GPU 自行绘制整个场景!
事实上,它们是计算机中自己的计算机。想一想。 GPU 实际上理解(!)模型数据(边列表、多边形/三角形列表、纹理、变换矩阵,现在甚至是张量)、模型变换、透视投影变换等等。
更重要的是,所有这些都是“可编程”(!),即您可以编写“微程序”,将它们编译成卡处理器指令并将它们“上传”(非永久)到卡本身。
OpenGL 以与编程语言无关的形式表达所有这些操作。例如,它包含完全优化的着色语言编译器,与 gcc 或 clang (llvm) 没有太大区别,但您甚至看不到片段着色器的汇编指令。根本没有必要。
但在较低级别(系统级别),所有这些模型数据和着色器程序都必须以某种方式进入卡中。您还必须“驱动”卡片(即向其发送命令:画这个,画那个,清屏)。其中一些数据(例如编译的着色器程序和模型数据)以内存块/块的形式出现。其他事物(例如命令)通常通过 GPU 寄存器工作,就像在 CPU 中一样。
在采用 PCI 技术的 PC 型计算机上,所有这一切通常是通过将卡的寄存器组和“内存窗口”(数据传输孔)从 PCI 空间映射到处理器内存空间来完成的。但在平板电脑或手机中,这可能会完全不同(许多 SoC 甚至没有 PCI)。
作为图形程序员,您对这些细节不感兴趣。
传统的“unix”“图形”
现在,传统上,“unix”(BSD、Linux、Solaris 等)并不关心图形。 X11 服务器就是为此而设计的工具。
事实上,在旧的“unix”系统中,甚至没有显示驱动程序的概念:“unix”甚至不知道,什么是显卡!
对于它和它的内核来说,它是无用的、死机的、消耗电力的,不像电传打字机或磁盘驱动器,它对此了解得很多:)。
你可能会问,X当时是怎么画画的呢?
通过 X,unix 系统实际上得到了一定程度的扩展(这个扩展非常小 - 与现代系统上的 DRI 不同),允许它将上面提到的 PCI 内存映射到正常用户模式进程的地址空间:即 X 服务器。
于是,通过这个“洞”,实际上是X说话并直接“驾驶”了这张卡。想一想——这是一种类似于微内核/FUSE 的设计。
这有效...
……有一段时间……
...但事实证明,这并没有我们想象的那么有效。
一开始就足够了,但随着卡和 GUI 变得越来越复杂,这种设计不能很好地满足负载和使用要求。
最初的设计很漂亮:
- 崩溃 X 不会导致系统崩溃。
- 程序要求X绘图,X在它们之间仲裁绘图并直接驱动硬件。
- 因为与 X 的通信是通过套接字进行的,所以绘图甚至可以在网络上工作(对于网络透明度来说就这么多了)。
“实地”情况也很简单:
- 显卡实际上只有几个组件:帧缓冲区、字符生成器、显示控制器,如果它是一块精美的卡,还有一个位块传输器。
- 应用程序大多发出简单的命令来绘制简单的矢量 2D 图形。
- X 是图形算法的“知识点”(现在它是 GPU 电路和着色器代码的组合 - 两者都位于 X 之外)。
事实证明我们需要/想要更多......
用户模式 X 的不良影响
首先,驱动硬件的用户模式程序直接引入了延迟和各种奇怪的问题。
例如,有时显卡会生成中断 - 如何将中断从内核从设备(它不关心)传播到用户模式 X 服务器进程?
有时对此中断的响应必须是即时的,但是如果 Xorg 当前已调度出去并且 ssh 目前正在处理传入流量怎么办?
或者另一件事:要绘制任何东西,您必须从程序的用户模式切换到内核模式,再切换到 X 的用户模式并返回(延迟)。事实上,情况甚至更糟,你会看到的。
许多类似的问题开始出现。
其次,X 需要它自己的驱动程序来与这些卡对话:这些不是内核驱动程序,而是类似于 FUSE 的 X 驱动程序,用于用户模式 X 与硬件对话。有点乱。
第三,缓慢但肯定地,人们发现内核,如果要保持相关性,需要知道什么图形(如果你想要图形启动屏幕,内核需要了解图形模式和显示器等 - 有些设备(例如手机)不知道甚至没有文本模式图形)。因此内核无论如何都开始开发自己的驱动程序 - 因此,在某个时候,任何卡都开始出现重复的驱动程序 - 一个用于 X,另一个用于内核。
第四,现代“显卡”不再是显卡了——它们是智能引擎,知道各种有趣的对象:片段着色器、顶点着色器、纹理、顶点缓冲区、显示缓冲区、显示器、监视器……列表太疯狂了。
在现代的类 Unix 多任务操作系统中,可能有多个应用程序同时在不同的用户下、不同的显示器上运行,许多应用程序同时使用卡中的此类硬件对象 - 即,您需要记帐(分配/解除分配)、访问控制和很快。
X 从用户空间管理应用程序之间的所有这些对象 - 但是当 X 崩溃时,这些对象会发生什么?它们保留在卡上的分配状态,但系统已丢失有关它们的所有记帐信息。解决这个问题的唯一方法是硬重置卡,这样它就会忘记一切并重新开始。
最后想想基于X的套接字绘制是如何工作的。假设您的应用程序想要绘制一个 10K 顶点的 3D 模型:
- 应用程序必须准备内存缓冲区来保存模型数据
- 它必须将模型数据格式化为正确的格式
- 然后通过unix套接字(或网络,如果你疯了的话)将其发送到X
- X必须准备内存缓冲区来接收数据
- 内核对数据进行洗牌
- X 必须准备与卡的通信
- X 已将其数据映射到卡中(即将其“发送”到卡)
- 内核对数据进行洗牌
- X 必须检索操作结果,将其打包并通过 X 套接字返回
- 内核对数据进行洗牌
- 应用程序必须接收消息中的结果
- 应用程序可以查看收到的消息并获悉操作失败
请记住,上述任何操作都可能被需要服务或运行的任何其他操作(音频播放器、ssh、磁盘等)抢占。更重要的是,所有这些来回花费的时间可能比现代 OpenGL 游戏渲染包含数百万个详细树木的帧的时间还要长(延迟)。
现代:内核不仅学会了嚼泡泡糖和走路,还学会了像鲍勃·罗斯一样画画,所有这一切都在同一时间
因此引入了两项新技术:KMS 和 DRI。
KMS - 内核模式设置 - 它使内核了解显卡及其具有:帧缓冲区、显示链接、监视器/显示器和分辨率。
DRI - 直接渲染基础设施 - 它使用 DRM(直接渲染管理器)扩展内核,意识到一些卡是复杂的 3D 图形引擎,可以包含对象:缓冲区/各种内存块(帧缓冲区、模型、纹理、内存孔) “流入”数据)、着色器和其他东西。它还兼顾了对它们的访问权限。
毕竟,内核可以对系统中的系统对象(进程、文件、内存映射)进行最好的统计。 DRM 将其扩展到图形对象。如果 X(或使用它的其他任何东西)崩溃,内核将获取该事件,并从卡本身清除所有与进程相关的资源。不再有对象泄漏和硬卡重置。
所有这些接口都是使用系统调用、文件描述符和内存映射来实现的,而不是套接字(如 X),并在需要时直接进入内核。因此,在内存中映射“漏洞”的情况下,延迟时间大大减少,几乎是瞬时的。
由于这些是系统调用和其他内核对象,而不是通过套接字发送的缓冲区,因此可以实现全硬件速度下的零复制数据传输。
最后您必须了解,在 DRI 的情况下,绘图应用程序本身(不是 X!)直接与卡通信。
这就是为什么它被称为“直接”的原因。
例如,如果您正在运行 X 并使用 DRI 应用程序(例如基于 OpenGL 的应用程序),则绘制到此窗口中的内容通常通过 DRI 上的特定应用程序私有代码路径进入卡,而不是通过 X。X 只是假装窗口映射到它的位置在 X 显示中。
现代 OpenGL 构建在 DRI 之上。然而 DRI 并不是关于绘图......
为什么不直接使用DRI呢?因为这有什么意义呢?
如果您正在使用OpenGL,那么您已经在直接使用它了。只是在这种情况下,OpenGL 库会加载到驱动 DRI 的进程地址空间中。
但对于图形应用程序程序员来说,费心 DRI 是没有意义的,它的级别太低了。 DRI 处理系统事务,例如访问权限、分配跟踪和清理、绘图上下文切换、命令多路复用、内存映射等。
DRI 中没有太多“绘图”。图形程序员通过扰乱这个子系统可以获得什么好处?从字面上看什么也没有。
此外,DRI 是强烈的 UNIX 系统特定的(所有主要 BSD 都使用 Linux 的 DRI 基础设施),因此它有点像 unix 的 Direct3D,但级别要低得多,因此这里的 Direct3D 比较并不公平。
例如,另一个直接使用 DRI 的新 API 是 Vulkan。看看 Vulkan 代码,与相同的 OpenGL 代码相比,它要复杂得多:这是因为它允许您进入比 OpenGL 低得多的级别。然而DRI的水平更低,这里也不涉及“绘图”。
另一个 API 可能在底层使用 DRI,但与 3D 图形无关并处理完全正交的视频解码世界,它是 VDAPU。 VDAPU 的内部结构与其应用程序用户(媒体播放器)完全脱节。
DRI 直接与特定卡的硬件细节相关,并且 DRM 和图形内存管理器的很大一部分位于内核中,而不是应用程序或用户空间中。
对于应用程序员,甚至图形程序员来说,直接达到 DRI 级别是没有意义的,除非他们想要执行特定的系统编程任务(例如修复 DRI)。如果他们能做到这一点,他们就可以轻松地使用开源图形驱动程序。或者在 OpenGL、Vulkan 或 VDPAU 库本身上。
OpenGL 和 Vulkan 将卡之间的差异抽象为“薄”的跨平台层,这对于图形程序员可能想要执行的任何任务来说都足够具有表现力。
图形应用程序程序员并不关心纹理分配在与其并行运行的其他应用程序中如何工作,或者哪个纹理绑定到特定的 DRM 文件描述符。
他们所关心的只是通过他们的应用程序按照他们想要的方式驱动卡。