OpenGL在最底层是如何工作的?

96

我知道如何编写OpenGL/DirectX程序,也了解背后的数学和概念,但是我好奇GPU和CPU之间的低级通信如何工作。

比如说,我有一个用C语言编写的OpenGL程序,显示一个三角形并将相机旋转45度。当我编译这个程序时,它会被转换成一系列的ioctl调用,然后GPU驱动程序发送适当的命令到GPU,让其旋转三角形并设置适当的像素颜色吗?还是这个程序会被编译成“GPU程序”,加载到GPU上计算旋转等操作?或者完全不同的方式?

编辑: 几天后,我找到了这篇文章系列,基本上回答了这个问题: http://fgiesen.wordpress.com/2011/07/01/a-trip-through-the-graphics-pipeline-2011-part-1/


OpenGL规范说明命令以客户端/服务器模式发送。该规范过于广泛,无法确定OpenGL实现所使用的特定技术。 - Luca
4个回答

96

这个问题几乎无法回答,因为OpenGL本身只是一个前端API,只要实现符合规范并且结果符合要求,可以按任何方式完成。

问题可能是:OpenGL驱动程序在最低级别上如何工作。同样,这也无法一般性地回答,因为驱动程序与某些硬件紧密相关,这些硬件可能会按照开发人员设计的方式执行操作。

因此,问题应该是:"OpenGL和图形系统背后的平均情况是什么?" 让我们从下往上看:

  1. 在最底层有某种图形设备。现在,这些都是提供一组寄存器以控制其操作的GPU(哪些寄存器确切地取决于设备),具有用于着色器的程序内存、用于输入数据(顶点、纹理等)的批量内存,并且通过I/O通道连接到系统的其他部分,它接收/发送数据和命令流。

  2. 图形驱动程序跟踪GPU的状态以及使用GPU的应用程序的所有资源。此外,它负责转换或处理应用程序发送的数据(将纹理转换为GPU支持的像素格式,在GPU的机器代码中编译着色器)。此外,它为应用程序提供了一些抽象的、驱动程序相关的接口。

  3. 然后是依赖于驱动程序的OpenGL客户端库/驱动程序。在Windows上,通过opengl32.dll代理加载,而在Unix系统上,则分别位于:

    • X11 GLX模块和依赖于驱动程序的GLX驱动程序
    • /usr/lib/libGL.so 可能包含一些直接渲染的与驱动程序相关的内容

    在MacOS X中,这恰好是"OpenGL Framework"。

    正是这部分将OpenGL调用翻译成调用第2部分中描述的驱动程序特定函数的驱动程序。

  4. 最后,实际的OpenGL API库,在Windows上是opengl32.dll,在Unix上是/usr/lib/libGL.so;这主要是将命令传递给OpenGL实现。

实际通信的方式不能归纳为一种模式:

在Unix中,3<->4的连接可以通过套接字(Socket)或共享内存(Shared Memory)进行,如果需要,它可以通过网络传输。在Windows中,接口库和驱动程序客户端都加载到进程地址空间中,因此这不是太多的通信,而是简单的函数调用和变量/指针传递。在MacOS X中,这与Windows类似,只是OpenGL接口和驱动程序客户端之间没有分离(这就是为什么MacOS X很难跟上新版OpenGL的原因,它总是需要完整的操作系统升级才能提供新的框架)。

3<->2之间的通信可能通过ioctl、读/写或将某些内存映射到进程地址空间并配置MMU以在对该内存进行更改时触发某些驱动程序代码来实现。这在任何操作系统上都很相似,因为您始终必须越过内核/用户空间边界:最终您会通过一些系统调用(syscall)。

系统与GPU之间的通信通过外设总线及其定义的访问方法进行,因此使用PCI、AGP、PCI-E等,并通过端口I/O (Port-I/O)、内存映射I/O(Memory Mapped I/O)、直接内存访问(DMA)和中断(IRQs)等实现。


1
这种描述可能太低级和通用了,几乎没有OpenGL的具体内容。它大致适用于计算机中存在的任何硬件组件。 - v.shashenko
2
@v.shashenko:当然。无论你要与哪个硬件组件交互,它都遵循这个通用方案。但OpenGL不是一个特定的软件,它只是一个规范,符合规范的任何东西都是有效的实现。因为最终OpenGL实现是在与GPU交互,所以它将大致遵循实现面向硬件的API的相同脚本。我们无法比这更具体,因为没有“唯一”的OpenGL实现。每个实现都有些许不同。 - datenwolf
1
@v.shashenko:如果你想要更多细节,那么你需要询问特定的实现。例如说,X11+GLX+DRI → Mesa/AMDGPU → Linux-KMS+DRM(Direct Rendering Manager)。而每个组件都是一个相当复杂的东西,有如此多的细节,以至于你可以用详细的书籍来描述每个组件的工作方式。但这将描述Linux-AMDGPU的实现。但是Linux+NVidia则完全不同。在Windows上也是不同的。 - datenwolf
你还记得我们几天前聊了很久吗?我只是想知道如果我有问题需要咨询你,怎样联系你? - Suraj Jain
你那天所教的,解决了我很多疑惑,也带来了一些新的问题,但总体而言,现在我不再认为在屏幕上绘制窗口和图标是一种魔法了。 - Suraj Jain

24
当我编译这个程序时,它会被转换成一系列的ioctl调用,然后gpu驱动程序会发送适当的命令到gpu,其中旋转三角形和设置适当的像素以适当的颜色的所有逻辑都已连线吗?或者这个程序会被编译成一个“gpu程序”,加载到gpu中计算旋转等操作呢?对于下一部分,这取决于硬件。旧的显卡有所谓的“固定管线”。视频卡中有专门的内存空间用于矩阵,以及专门的硬件用于纹理查找、混合等。视频驱动程序将为这些单元装载正确的数据和标志,然后设置DMA来传输您的顶点数据(位置、颜色、纹理坐标等)。新的硬件在视频卡内部具有处理器核心(“着色器”),与您的CPU不同,每个核心运行得更慢,但并行工作的核心数量更多。对于这些视频卡,驱动程序准备在GPU着色器上运行的程序二进制文件。

21

你的程序并没有为特定的GPU进行编译,它只是动态地链接到将实现OpenGL的库。实际的实现可能涉及向GPU发送OpenGL命令、运行软件回退、编译着色器并将其发送到GPU,甚至使用着色器回退到OpenGL命令。图形领域相当复杂。幸运的是,链接可以使你免受大多数驱动程序复杂性的影响,让驱动程序实现者自由使用任何他们认为合适的技术。


19

C/C++编译器/链接器有且仅有一个作用:将文本文件转换为一系列特定于机器的操作码,然后在CPU上运行。OpenGL和Direct3D只是C/C++ API,它们不能神奇地将您的C/C++编译器/链接器转换为GPU的编译器/链接器。

您编写的每一行C/C++代码都将在CPU上执行。对OpenGL/Direct3D的调用将进入C/C++库中(static或dynamic取决于情况)。

"GPU程序"唯一会被使用的场合是如果您的代码明确地创建着色器。也就是说,如果您通过OpenGL/D3D调用生成并链接了着色器。为此,您需要(在运行时,而不是在C/C++编译时)生成或加载表示特定着色器语言中的着色器的字符串,并将其传递给着色器编译器。之后,您将获得一个在该API中表示该着色器的对象。最后,您将应用一个或多个着色器到某个特定的渲染命令上。所有这些步骤都在由先前提到的在CPU上运行的C/C++代码指导下显式发生。

许多着色器语言使用类似于C/C++的语法,但这并不意味着它们等同于C/C++。


最近我一直在学习OpenGL,我一直在想为什么我看到的代码中都有这些“着色器程序”作为字面字符串。就像你说的那样,它们是在运行时编译的。我在一些Android OpenGL ES代码示例中看到了一些例子。然后我也看到了iPhone的一些代码。显然,iPhone有四个矢量处理器,能够比iPhone的CPU更快地执行浮点数运算--因此,您可以将ASM编写为字面字符串,在运行时进行编译以使用这些处理器而不是主CPU。 - eggie5
1
现在默认的一组着色器程序已经实现了大部分“固定功能管线”的功能,您不需要显式地请求一个着色器来获取它。 - Ben Voigt
1
@Ben Voigt:没错,但如果没有人告诉你,你就分辨不出来了。 - Nicol Bolas
1
如果这个问题不涉及到隐藏细节,这种态度可能是一个明智的选择。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接