优化GPU到CPU的数据传输

3
我有点超纲了(最好的方式就是这样),但我正在探索一种优化方法,可以减少我的应用程序中GPU到CPU数据传输的量。
我的应用程序在GPU中对顶点数据进行一些修改。偶尔,CPU必须读回部分修改后的顶点数据,然后计算一些参数,这些参数通过uniforms传递回GPU着色器,形成一个循环。
将所有顶点数据传回CPU并在CPU上筛选(数百万个点)需要太长时间,因此我采取了“hack”来减少工作量以使其可用,尽管不是最优的。
我所做的:
1. CPU:读取图像 2. CPU:为每个像素生成1个顶点,Z基于颜色信息/过滤器等。 3. CPU:将所有顶点数据传输到GPU。 4. GPU:使用变换反馈实时更新GL_POINT顶点坐标,基于从CPU设置的一些统一参数。
当我只想读取矩形“部分”时,我使用glMapBufferRange将组成所需矩形的整行映射(警告:糟糕的图表)。

enter image description here

这代表GPU中的图像/顶点集合。我的“hack”涉及到读取所有蓝色和红色顶点。这是因为我只能指定1个连续的数据范围来读取回来。
有人知道一种聪明的方法可以高效地获取红色,而不需要蓝色吗?(不需要发出一系列的glMapBufferRange调用)
编辑-
使用情况是,我将图像渲染成GLPoints,并在Z中根据颜色信息进行着色和偏移(根据距离进行大小等)。然后用户可以使用鼠标光标刷修改顶点Z数据。一些刷子应用代码背后的逻辑需要知道鼠标下方区域(刷圆)的Z,例如最小/最大/平均值等,以便CPU可以通过设置一系列馈入着色器的统一变量来控制数据的着色器修改。因此,例如,用户可以说,我希望所有在光标下的点都设置为平均值。这可能完全可以在GPU中完成,但想法是一旦我得到了CPU-GPU“循环”(尽可能优化),我就可以将min/max/avg等扩展到CPU上做一些有趣的事情,这可能会很麻烦(可能)完全在GPU上完成。

干杯!Laythe


你的问题有一个假设:glMapBufferRange正在将数据复制到CPU。虽然这确实是映射的可能实现方式之一,但一般来说,映射的目的是直接访问可由GPU访问的内存。因此,映射多大的范围并不重要;重要的是GPU需要做多少同步才能使该数据可见。 - Nicol Bolas
@Nicol Bolas 你好Nicol,感谢回复。我只是想真正掌握“理论”,因为从我目前所学的内容来看,我不能仅仅依靠它在我的开发机器上工作良好(或完全工作)。我有一个Intel Iris集成卡的笔记本电脑。我看到的是通过简单地剪切图表中的白色区域(不读取那些行)而获得的巨大性能提升。这可能只是我的图形驱动程序实现的一个产物,但如果没有理论,很难判断,谢谢。 - Laythe
如果您知道红色部分的位置,也许只需要一个MapBufferRange,如果您知道精确的偏移量。 - Paltoquet
@Draykoon D 我知道每个东西的精确位置和偏移量,但问题是2D图像/顶点实际上是GPU中的1D数组,因为我只想要红色*,这意味着我想要访问GPU内多个连续的“范围”顶点数据,而不必实际发出几个(每行一个用于红色)glMapBufferRange调用。也就是说,最好有一种方法可以一次向GPU请求所有范围,而不是每行一次,这将涉及每行(红色)的CPU-GPU交互。 - Laythe
2个回答

1
要从GPU获取任何数据到CPU,您需要在任何情况下映射GPU内存,这意味着OpenGL应用程序将不得不在底层使用类似于mmap的东西。我已经检查了x86和ARM的实现情况,看起来它是页面对齐的,因此您不能在任何给定时间映射少于1个连续页面的GPU内存,因此即使您可以请求仅映射红色区域,您很可能也会得到蓝色区域(取决于您的页面和像素数据大小)。 解决方案1 只需使用glReadPixels,因为这允许您选择帧缓冲区的窗口。我假设像英特尔这样的GPU供应商会优化驱动程序,以便尽可能少地映射页面,但这并不保证,在某些情况下,您可能需要映射2页才能获得2个像素。 解决方案2 创建计算着色器或使用多个glCopyBufferSubData调用将您感兴趣的区域复制到GPU内存中的连续缓冲区中。如果您知道所需的高度和宽度,则可以在CPU端取消混合并获得2D缓冲区。
以上解决方案中哪个更好取决于您的硬件和驱动程序实现。如果GPU->CPU是瓶颈,而GPU->GPU很快,则第二种解决方案可能效果更好,但您需要进行实验。
解决方案3:如评论所建议,全部在GPU上完成。这在很大程度上取决于工作是否可以很好地并行化,但如果内存复制速度太慢,那么您就没有其他选择了。

嗨,Ed,谢谢你的回答。我怀疑我想要的并不是直接可用的。当你说GPU内存页时,我们需要多大 - 我做了一个谷歌搜索,但是被内存系统细节淹没了,很难将我看到的内容翻译成答案...干杯! - Laythe
嗨Laythe,我认为常见的大小应该是4KB,但这取决于硬件。我主要使用嵌入式GPU进行工作,但我认为前述大小是现今普遍假定的良好最小值。这就是为什么我说如果您的像素数据大小为4字节,而宽度为1080,则几乎可以占据整个像素行的原因。希望这可以帮助您!最后请告诉我哪种解决方案最终适用于您。 - Ed Jaras

-1

我猜你问这个问题是因为你不能全部在着色器中完成工作,对吧?

如果你渲染到一个帧缓冲对象,然后将其绑定为GL_READ_FRAMEBUFFER,你可以通过glReadPixels读取其中的一块。


是的,这可能是可行的,但是该循环中有许多逻辑,因此将其转换为着色器可能会有问题,但这绝对是我要研究的内容。关于glReadPixels,我不会仍然有同样的问题吗?-也就是说,我必须读取蓝色和红色才能获取红色(如上图所示)。 - Laythe
@Laythe 你可以直接使用 glReadPixels ... PS 不确定你用的是哪个 Intel HD 显卡,但我测试过的所有旧型号都无法渲染到 FBO 或纹理上,因为驱动程序不好,所以在这些显卡上,glReadPixels 是唯一可靠的方法。 - Spektre
@Spektre 我不确定这会如何帮助我的情况,因为我的问题是2D图像/顶点实际上是GPU中的1D数组,并且因为我只想要红色,这意味着我想要访问GPU内多个连续的“范围”顶点数据,而不必实际发出几个(每行一个红色)glMapBufferRange调用。也就是说,最好有一种方法可以一次向GPU请求所有范围,而不是每行一次,这将涉及每行(红色)的CPU-GPU交互。 - Laythe

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