CPU-GPU内存数据流


16

我是一名新手图形程序员,最近我一直在想-模型数据(网格和材质)如何从应用程序(CPU内存)流到图形卡(GPU内存?)?假设我有一个静态模型(例如建筑物),该模型一次加载和设置一次,并且在整个应用程序生命周期中都没有改变。

  • 它的数据是否仅发送到GPU内存一次并永久存在?
  • 当实际渲染模型的每个帧时,GPU处理器是否每次都必须从GPU内存中获取其数据?我的意思是-如果我有2个模型分别渲染多次,那么,如果我先多次渲染第一个模型,然后又多次渲染第二个模型,或者如果我仅一次渲染第一个模型,第二个就渲染一次,那将很重要一直像那样交织在一起?从这个意义上讲,我可以将此问题称为“内部GPU数据流”。
  • 显然显卡的内存有限-当无法容纳渲染1帧所需的所有模型数据时,我猜想它一直在每帧从CPU RAM提取(部分)数据,对吗?

我知道互联网上有很多关于这方面的书籍和内容,但是也许您有一些关于如何管理此数据流的快速通用指南(何时发送什么和多少,何时以及如何呈现)?

编辑: 我忘了一个区别:将数据发送到GPU,并且将缓冲区设置/绑定为当前。后者会导致任何数据流吗?

Edit2: 阅读Raxvan的文章后,我想提出一些动作:

  • 通过初始化创建缓冲区(正如他所说,我可以将数据存储在CPU ram或GPU之一中)
  • 缓冲数据更新(当数据保存在CPU内存中时,我认为这很简单;当数据保存在GPU内存中时,需要从GPU提取到CPU内存(然后取回))
  • 将缓冲区绑定为活动状态(这是一种告诉API我希望缓冲区在下一次绘制调用中呈现的方式,并且它本身不会执行任何操作吗?)
  • API绘图调用(在这里我想听听您在那里实际发生的事情)

无论如何,我都不是专家,但是如果您使用带有VAO和VBO的现代(即非即时)OpenGL,那么只要您使用glBuffer系列命令之一,数据就会发送到GPU并存储在VRAM中。然后,每次绘制时,都会从VRAM中获取相关顶点并进行渲染。如果它是一个移动的模型,则倾向于静态存储它,并使用矩阵从模型空间移动到世界/相机空间。至于最后一点,我不知道如果RAM用完了会发生什么。我的猜测是,如果您用完了VRAM,则不会发送数据,可能带有错误代码。
Polar

@Polar-不完全是。GL实际上并未指定缓冲区对象存储在哪个内存中,甚至可以根据使用模式在运行时随意移动它。GL4.4在某种程度上解决了这个问题,但是指出最终,它可以提供的最好的东西是“这些愚蠢的暗示之一”。请参阅opengl.org/registry/specs/ARB/buffer_storage.txt,尤其是问题2和9。–
Maximus

1
@JimmyShelter啊,谢谢-如果我们少了“那些愚蠢的暗示”和更具体的规范,那将是很好的。
Polar

@Polar -什么是恼人的是,ARB_buffer_storage 可能已经避免,包括另一种暗示,但设计师错过了机会。哦,好吧,也许4.5最终会正确。
Maximus Minimus

2
请不要编辑您的问题以“答复”答案。而是发布一个新问题。

Answers:


12

它的数据是否仅发送到GPU内存一次并永久存在?

通常是的,但是驱动程序可以自由地执行“最佳”操作,数据可能存储在VRAM或RAM中,或者可能只是缓存在这里,这是一个解释VBO流实际发生情况的斜线

例如,如果将其标记为动态openGL缓冲区(例如VBO),则更有可能将其存储在RAM中。GPU使用直接内存访问(DMA)来直接访问ram,而无需CPU干预,这由图形卡和图形驱动器中的DMA控制器控制,并在内核模式下执行。

当实际渲染模型的每个帧时,即使模型连续渲染多个时间,GPU处理器也必须每次都从GPU内存中获取其数据吗?

就像CPU一样,允许GPU对GPU指令和内存访问操作进行重新排序(读取:无序执行),因此GPU很可能会通过访问其缓存中的内存来处理您提到的情况(通常是最近访问的) ),但有时无法做到这一点。

显然显卡的内存有限-当无法容纳渲染1帧所需的所有模型数据时,我猜想它一直在每帧从CPU RAM提取(部分)数据,对吗?

您不希望这种情况发生。但是,无论发生这种情况,GPU都会开始在RAM和VRAM之间移动内存(GPU上的命令处理器负责此操作),这会使渲染速度变慢,这将导致GPU停顿,因为它需要等待数据从/复制到V / RAM。

将数据发送到GPU,并将缓冲区设置/绑定为当前缓冲区。后者会引起任何数据流吗?

GPU包含命令缓冲区,并且所有API命令都提交给该缓冲区,请注意,这可能与将数据复制到GPU的同时发生。该命令环形缓冲器是在CPU和GPU之间的通信队列,任何命令,该命令需要将要执行需要被提交给队列,因此它可以由GPU execulated。就像绑定新缓冲区的任何操作都需要提交给gpu一样,以便它可以访问某些内存位置。

这是不建议使用glBegin / glEnd的原因之一,提交新命令需要队列同步(使用内存围栏/屏障)。

在此处输入图片说明

至于其他几点:

通过初始化创建缓冲区

您可以分配缓冲区而无需初始化,并将其保留以备后用。或者,您可以为其分配一个缓冲区并同时复制数据(谈论API级别)。

缓冲数据更新

您可以使用glMapBuffer在GPU端更新内存。是否将内存从RAM复制到RAM并不是标准的标准,并且将取决于供应商,GPU类型和驱动程序。

API绘制调用(在这里我想听听您在那里实际发生的事情)。

我在主要问题中的第二点涉及这一点。

将缓冲区绑定为活动状态(这是一种告诉API我希望此缓冲区>在下一个绘制调用中呈现的方式,并且它本身不会执行任何操作吗?)

可以将绑定视为this在任何面向对象的语言中使用指针,尽管并不完全相同,但是任何后续的API调用都将相对于该绑定缓冲区。


3

通常,cpu与gpu的界限和参与在平台上是特定的,但大多数遵循此模型:cpu有一些ram,gpu也可以移动内存(在某些情况下ram是共享的,但对于为了简单起见,让我们坚持使用单独的ram)。

第一点:您可以选择将初始化的数据保留在CPU内存或GPU内存中,这两种方法都有其优势。当渲染某些东西时,GPU必须承担繁重的工作,因此很明显,GPU内存中已经存在的数据将提供更好的性能。对于CPU,它必须先将数据发送到GPU(后者可以选择保留一段时间),然后执行渲染。

第二点:渲染方面有很多技巧,但主要的方法是使用多边形。在帧上,gpu将一个接一个地绘制由多边形组成的对象,并在完成后GPU将图片发送到显示器。没有诸如对象之类的概念,只有多边形,将它们放在一起的方式将创建图像。GPU的工作是将这些多边形从3d投影到2d并应用效果(如果需要)。多边形只能直接通过CPU-> GPU-> SCREEN或GPU-> SCREEN进行操作(如果多边形已经在GPU内存中)

第三点:例如,当渲染动画时,最好将数据保持在靠近CPU的位置,因为他会很费力,将数据保留在GPU中,将其移至CPU并返回每一帧并不是最佳选择。还有很多其他这样的例子,但总的来说,所有数据都会与进行计算的人保持联系。通常,您可能希望将尽可能多的数据移至GPU内存以获得性能。

实际数据发送到GPU是由您使用的API(directx / opengl或其他)完成的,绑定和诸如此类的东西的概念仅仅是抽象的,以便API能够理解您想要做什么。

编辑进行编辑:

  • buffer creation with initialisation:就像int a = new int[10]a[0] = 0,a[1] = 1.... etc 创建缓冲区的过程之间的区别一样,它为数据腾出了空间,而当您初始化数据时,则将所需的内容放入其中。

  • buffer data update如果它在cpu ram上,那么您就vertex * vertices可以使用它,如果它不在那儿,则必须将其从GPU移走vertex * vertices = map(buffer_id);(map是一种神话函数,应该将数据从GPU移到CPU ram,它也相反buffer_id = create_buffer(vertices);

  • binding the buffer as active这只是一个概念,他们称binding渲染是一个复杂的过程,就像调用具有10000个参数的函数一样。绑定只是他们用来告诉哪个缓冲区到达何处的术语。这个术语背后没有真正的魔力,它不会转换,移动或重新分配缓冲区,只是告诉驱动程序在下一次绘制调用时使用此缓冲区。

  • API draw call在所有的绑定和设置缓冲之后,这就是橡胶与道路相遇的地方。绘制调用将获取您指定的所有数据(或指向数据的ID),将其发送到GPU(如果需要),并告诉GPU开始计算数字。这并非在所有平台上都存在很多差异,但这并不是完全正确的,但是为了使事情简单,绘制将告诉GPU ....绘制。


2

最正确的答案是,这取决于您对其进行编程的方式,但这是一件令人担心的好事。尽管GPU的速度变得异常快,但往返于GPU RAM的带宽却并非如此,这将是您最令人沮丧的瓶颈。

它的数据是否仅发送到GPU内存一次并永久存在?

希望是。为了提高渲染速度,您希望将尽可能多的数据放置在GPU上,而不是每帧重新发送一次。VBO正是为这个目的服务的。静态和动态VBO都有,前者是静态模型的最佳选择,后者是顶点在每一帧都会变化的模型(例如,粒子系统)的最佳选择。但是,即使涉及动态VBO,您也不想每帧都重新发送所有顶点。只是那些正在变化的。

就您的建筑物而言,顶点数据将仅位于此处,唯一发生变化的是矩阵(模型/世界,投影和视图)。

对于粒子系统,我使动态VBO足够大,以存储该系统永远存在的最大粒子数。我向每一帧发送有关该帧发出的粒子的数据,以及几套制服,仅此而已。绘制时,可以在该VBO中指定起点和终点,因此不必删除粒子数据。我可以说不要画那些。

当实际渲染模型的每个帧时,GPU处理器是否每次都必须从GPU内存中获取其数据?我的意思是-如果我有2个模型分别渲染多次-如果我先多次渲染第一个模型,然后再渲染第二个模型,或者我仅一次渲染第一个模型,第二个就渲染一次,然后一直像那样交织在一起?

发送多个抽签而不是一次抽签的行为是一个更大的限制。查看实例渲染;它可能对您有很大帮助,并使该问题的答案毫无用处。我有一些尚未解决的驱动程序问题,但是如果您可以解决问题,那么问题就解决了。

显然显卡的内存有限-当无法容纳渲染1帧所需的所有模型数据时,我猜想它一直在每帧从CPU RAM提取(部分)数据,对吗?

您不想用完GPU RAM。如果这样做了,那就改变一下,以免改变。在您可能会用完的非常假想的情况下,它可能会以某种方式崩溃,但是我从未见过它发生,所以说实话我不知道。

我忘记了一个区别:将数据发送到GPU,并将缓冲区设置/绑定为当前缓冲区。后者会引起任何数据流吗?

没有任何重要的数据流,不。这要付出一些代价,但是对于您编写的每一行代码都是如此。再次找出配置成本是多少。

通过初始化创建缓冲区

Raxvan的答案听起来不错,但不太准确。在OpenGL中,创建缓冲区不会保留任何空间。如果要保留空间而不传递任何数据,则可以调用glBufferData并仅传递null。(请参阅此处的注释部分。)

缓冲数据更新

我猜你的意思是glBufferData或类似的其他功能,对吧?这是真正的数据传输发生的地方。(除非您传递空值,就像我在上一段中所说的那样。)

将缓冲区绑定为活动状态(这是一种告诉API我希望此缓冲区在下一次绘制调用中呈现的方式,并且它本身不会执行任何操作吗?)

是的,但是它可以做的更多。例如,如果绑定VAO(顶点数组对象),然后绑定VBO,则该VBO将绑定到VAO。以后,如果再次绑定该VAO,并调用glDrawArrays,它将知道要绘制哪个VBO。

请注意,尽管许多教程将为您为每个VBO创建一个VAO,但有人告诉我这不是它们的预期用途。假设您应该创建一个VAO,并将其与每个具有相同属性的VBO一起使用。我还没有尝试过,所以不能确定是好是坏。

API绘图调用

从我们的角度来看,这里发生的事情非常简单。假设您绑定了VAO,然后调用glDrawArrays。您指定一个起点和一个计数,它会为该范围内的每个顶点运行您的顶点着色器,然后将其输出向下传递到该行。但是,整个过程本身就是另一篇文章。


“然后解决问题”是的,实例化将有很大帮助,但如果没有它,我仍然必须为每个对象进行绘图调用。两种情况下都相同。所以我想知道订单是否重要。
2013年

@NPS -它很重要一些。如果订购了它们,那么您就不必继续切换绑定,是的,这可能会快一点。但是,如果您必须竭尽所能对它们进行排序,那可能会花费更多,更多的钱。取决于您的实现的变量太多了,远不止于此。
冰冷的反抗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.