CPU 和 GPU 在显示计算机图形时如何交互?

CPU 和 GPU 在显示计算机图形时如何交互?

这里您可以看到一个基于 OpenGL API 的小型 C++ 程序 Triangle.exe 的屏幕截图,其中包含一个旋转三角形。

在此处输入图片描述

无可否认这是一个非常基本的例子但我认为它适用于其他图形卡操作。

我只是很好奇,想知道在 Windows XP 下从双击 Triangle.exe 直到看到三角形在显示器上旋转的整个过程。发生了什么,CPU(首先处理 .exe)和 GPU(最终在屏幕上输出三角形)如何交互?

我猜测显示这个旋转三角形主要涉及以下硬件/软件:

硬件

  • 硬盘
  • 系统内存 (RAM)
  • 中央处理器
  • 视频内存
  • 图形处理器
  • 液晶显示器

软件

  • 操作系统
  • DirectX/OpenGL API
  • Nvidia 驱动程序

有人可以解释一下这个过程吗,可以借助某种流程图来说明吗?

它不应该是一个涵盖每个步骤的复杂解释(猜测这会超出范围),而应该是中级 IT 人员可以理解的解释。

我确信许多甚至自称是 IT 专业人士的人都无法正确描述这个过程。

答案1

我决定写一些关于编程方面以及组件如何相互通信的内容。也许这会对某些领域有所启发。

演示

要怎样才能将您在问题中发布的单个图像绘制到屏幕上呢?

在屏幕上绘制三角形的方法有很多种。为了简单起见,我们假设没有使用顶点缓冲区。(A顶点缓冲区是存储坐标的内存区域。)我们假设程序只是简单地告诉图形处理管道一行中的每个顶点(顶点只是空间中的坐标)。

,在绘制任何东西之前,我们必须先运行一些脚手架。我们会看到为什么之后:

// Clear The Screen And The Depth Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

// Reset The Current Modelview Matrix
glMatrixMode(GL_MODELVIEW); 
glLoadIdentity();

// Drawing Using Triangles
glBegin(GL_TRIANGLES);

  // Red
  glColor3f(1.0f,0.0f,0.0f);
  // Top Of Triangle (Front)
  glVertex3f( 0.0f, 1.0f, 0.0f);

  // Green
  glColor3f(0.0f,1.0f,0.0f);
  // Left Of Triangle (Front)
  glVertex3f(-1.0f,-1.0f, 1.0f);

  // Blue
  glColor3f(0.0f,0.0f,1.0f);
  // Right Of Triangle (Front)
  glVertex3f( 1.0f,-1.0f, 1.0f);

// Done Drawing
glEnd();

那么这有什么作用呢?

当你编写一个需要使用显卡的程序时,你通常会选择某种驱动程序接口。一些众所周知的驱动程序接口包括:

  • OpenGL
  • 直接3D
  • 通用计算架构

在本例中,我们将继续使用 OpenGL。现在,你的驱动程序接口为你提供编写程序所需的所有工具讲话到显卡(或驱动程序,然后会谈到卡上)。

这个界面必定会给你某些工具.这些工具的形状为API您可以从程序中调用它。

我们在上面的示例中看到的就是这个 API。让我们仔细看看。

脚手架

在真正进行任何实际绘画之前,你必须先进行设置你必须定义你的视口(实际渲染的区域)、你的视角(相机进入你的世界),你将使用什么抗锯齿(来平滑三角形的边缘)......

但我们不会看这些。我们只会看一眼你必须做的事情每一帧。 喜欢:

清除屏幕

图形管道不会每帧都为您清除屏幕。您必须告诉它。为什么?原因如下:

在此处输入图片描述

如果你不清除屏幕,你只会画过去每帧都会发生这种情况。这就是我们glClearGL_COLOR_BUFFER_BITset 调用的原因。另一位(GL_DEPTH_BUFFER_BIT)告诉 OpenGL 清除深度缓冲区。此缓冲区用于确定哪些像素位于其他像素的前面(或后面)。

转型

在此处输入图片描述
图片来源

变换是我们获取所有输入坐标(三角形的顶点)并应用我们的 ModelView 矩阵的部分。这是矩阵解释我们的模型(顶点)被旋转、缩放和平移(移动)。

接下来,我们应用投影矩阵。这将移动所有坐标,以便它们正确面向我们的相机。

现在我们再次使用视口矩阵进行变换。我们这样做是为了缩放模型到我们显示器的大小。现在我们有一组可以渲染的顶点了!

稍后我们再回顾转型。

绘画

要绘制三角形,我们可以简单地告诉 OpenGL 开始一个新的三角形列表通过使用常量调用glBeginGL_TRIANGLES
还可以绘制其他形式。比如三角带三角扇。这些主要是优化,因为它们需要 CPU 和 GPU 之间更少的通信来绘制相同数量的三角形。

之后,我们可以提供组成每个三角形的 3 个顶点的集合列表。每个三角形使用 3 个坐标(因为我们在 3D 空间中)。此外,我还提供了一个颜色对于每个顶点,通过调用glColor3f 呼叫glVertex3f

3 个顶点(三角形的 3 个角)之间的阴影由 OpenGL 计算自动地。它将在多边形的整个表面上插入颜色。

相互作用

现在,当您单击窗口时。应用程序只需捕获窗口消息表示点击。然后,您就可以在程序中运行所需的任何操作。

这得到很多一旦您想要开始与 3D 场景进行交互,就会变得更加困难。

首先,你必须清楚地知道用户点击了窗口的哪个像素。然后,看法考虑到这一点,你可以计算出从鼠标点击点到场景的射线方向。然后你可以计算出场景中是否有任何物体相交带着那道光芒。现在您知道用户是否点击了某个对象。

那么,如何让它旋转呢?

转型

我知道通常应用两种类型的转换:

  • 基于矩阵的变换
  • 基于骨骼的变换

不同之处在于骨头影响单一顶点。矩阵总是以相同的方式影响所有绘制的顶点。让我们看一个例子。

例子

早些时候,我们加载了单位矩阵在绘制三角形之前。单位矩阵只是提供无转变完全不会。所以,无论我画什么,都只会受到视角的影响。所以,三角形根本不会旋转。

如果我现在想旋转它,我可以自己做数学运算(在 CPU 上),然后简单地glVertex3f调用其他坐标(旋转的)。或者我可以让 GPU 完成所有工作,方法是调用glRotatef绘制之前:

// Rotate The Triangle On The Y axis
glRotatef(amount,0.0f,1.0f,0.0f);               

amount当然,只是一个固定值。如果你想动画,您必须跟踪amount并逐帧增加它。

那么,等一下,之前关于矩阵的讨论发生了什么?

在这个简单的例子中,我们不必关心矩阵。我们只需调用它glRotatef,它就会为我们处理好所有事情。

glRotateangle围绕向量 xyz产生度的旋转。当前矩阵(参见glMatrixMode)乘以旋转矩阵,用乘积替换当前矩阵,就像glMultMatrix被调用并使用以下矩阵作为其参数:

x 2 1 - c + cx y 1 - c - z sx z 1 - c + y s 0 y x 1 - c + z sy 2 1 - c + cy z 1 - c - x s 0 x z 1 - c - y sy z 1 - c + x sz 2 1 - c + c 0 0 0 0 1

好吧,谢谢你!

结论

显而易见的是,有很多讨论OpenGL。但它没有说明我们什么都没有。通讯在哪里?

OpenGL 在这个例子中告诉我们的唯一一件事是当它完成时。每个操作都会花费一定的时间。有些操作花费的时间非常长,而有些操作则非常快。

发送顶点发送到 GPU 的速度如此之快,我甚至不知道该如何表达。每一帧从 CPU 向 GPU 发送数千个顶点很可能根本不是问题。

清除屏幕可能需要一毫秒甚至更久(请记住,您通常只有大约 16 毫秒的时间来绘制每一帧),具体取决于您的视口有多大。要清除它,OpenGL 必须以您想要清除的颜色绘制每个像素,这可能是数百万个像素。

除此之外,我们几乎只能向 OpenGL 询问我们的图形适配器的功能(最大分辨率、最大抗锯齿、最大色彩深度……)。

但是我们也可以用每个像素都有特定颜色的像素来填充纹理。因此,每个像素都拥有一个值,而纹理是一个充满数据的巨大“文件”。我们可以将其加载到显卡中(通过创建纹理缓冲区),然后加载着色器,告诉着色器使用我们的纹理作为输入并对我们的“文件”运行一些极其繁重的计算。

然后,我们可以将计算结果(以新颜色的形式)“渲染”到新的纹理中。

这就是你如何以其他方式让 GPU 为你工作。我认为 CUDA 的表现与此类似,但我从未有机会使用它。

我们实际上只是稍微触及了整个主题。3D 图形编程真是一个难题。

在此处输入图片描述
图片来源

答案2

很难理解你到底不明白什么。

GPU 具有一系列由 BIOS 映射的寄存器。这些寄存器允许 CPU 访问 GPU 的内存并指示 GPU 执行操作。CPU 将值插入这些寄存器以映射 GPU 的部分内存,以便 CPU 可以访问它。然后它将指令加载到该内存中。然后它将一个值写入寄存器,告诉 GPU 执行 CPU 加载到其内存中的指令。

这些信息包括 GPU 需要运行的软件。该软件与驱动程序捆绑在一起,然后驱动程序处理 CPU 和 GPU 之间的责任分配(通过在两个设备上运行其代码的部分)。

然后,驱动程序管理 GPU 内存中的一系列“窗口”,CPU 可以从中读取和写入这些窗口。通常,访问模式涉及 CPU 将指令或信息写入映射的 GPU 内存,然后通过寄存器指示 GPU 执行这些指令或处理该信息。这些信息包括着色器逻辑、纹理等。

答案3

我只是很好奇,想知道在 Windows XP 下从双击 Triangle.exe 直到看到三角形在显示器上旋转的整个过程。发生了什么,CPU(首先处理 .exe)和 GPU(最终在屏幕上输出三角形)如何交互?

我们假设您确实知道可执行文件如何在操作系统上运行,以及该可执行文件如何从 GPU 发送到显示器,但不知道中间发生了什么。因此,让我们从硬件角度来看,并进一步扩展程序员方面回答...

CPU和GPU之间的接口是什么?

用一个司机,CPU 可以通过母板PCI 等功能连接到显卡并向其发送命令来执行一些 GPU 指令,访问/更新 GPU 内存,加载要在 GPU 上执行的代码等等……

但是,您无法真正通过代码直接与硬件或驱动程序对话;因此,这必须通过 OpenGL、Direct3D、CUDA、HLSL、Cg 等 API 来实现。前者运行 GPU 指令和/或更新 GPU 内存,而后者实际上将在 GPU 上执行代码,因为它们是物理/着色器语言。

为什么在 GPU 上运行代码而不是在 CPU 上运行?

虽然 CPU 非常适合运行我们的日常工作站和服务器程序,但人们并没有过多考虑如今游戏中那些闪亮的图形。过去,软件渲染器可以处理一些 2D 和 3D 内容,但它们非常有限。因此,GPU 就派上用场了。

GPU 针对图形中最重要的计算之一进行了优化,矩阵操控虽然 CPU 必须逐个计算矩阵运算中的每个乘法(稍后,诸如3D现在!上交所赶上后,GPU 可以一次完成所有这些乘法!并行性。

但并行计算并不是唯一的原因,另一个原因是 GPU 更接近视频内存,这使得它比通过 CPU 往返等要快得多……

这些 GPU 指令/内存/代码如何显示图形?

要实现这一切,还缺少一个部分,我们需要一个可以写入然后读取并发送到屏幕的东西。我们可以通过创建一个帧缓冲区。无论您做什么操作,您最终都会更新帧缓冲区中的像素;除了位置之外,它还保存有关颜色和深度的信息。

让我们举一个例子,假设您想在某处绘制血精灵(图像);首先,将树纹理本身加载到 GPU 内存中,这样就可以根据需要轻松重新绘制它。接下来,要实际在某处绘制精灵,我们可以使用顶点平移精灵(将其放在正确的位置),对其进行光栅化(将其从 3D 对象转换为像素)并更新帧缓冲区。为了更好地理解,以下是来自 Wikipedia 的 OpenGL 管道流程图:

这是整个图形理念的主要思想,更多的研究是读者的功课。

答案4

GPU 通常由 DMA 缓冲区驱动。也就是说,驱动程序将从用户空间程序接收到的命令编译为指令流(切换状态、以那种方式绘制、切换上下文等),然后将其复制到设备内存。然后,它通过 PCI 寄存器或类似方法指示 GPU 执行此命令缓冲区。

因此,在每次绘制调用等时,用户空间驱动程序都会编译命令,然后通过中断调用内核空间驱动程序,最后将命令缓冲区提交到设备内存并指示 GPU 开始渲染。

在游戏机上,你甚至可以自己享受做所有这些事情的乐趣,尤其是在 PS3 上。

相关内容