1

这是针对 Android 上的 OpenGL ES 2.0 游戏的,尽管我怀疑正确的答案对于任何 opengl 情况都是通用的。

TL;DR - 一次向 GPU 发送 N 个数据然后用它进行 K 个绘图调用是否更好?还是将 K*N 数据发送到 GPU 一次,然后进行 1 次绘图调用?

更多细节我想知道我的情况的最佳实践。我有一个动态网格,我会重新计算每一帧的顶点——把它想象成一个水面——我需要在我的游戏中将这些顶点投影到 K 个不同的四边形上。(在每种情况下,投影都略有不同;保留细节,您可以将它们想象为围绕网格的 K 个不同的镜子。)K 大约为 10-25;我还在想办法。

我可以想到两个广泛的选择:

  1. 按原样绑定网格,并在不同时间调用 draw K,或者更改着色器的制服或弄乱固定功能状态以渲染到正确的四边形(在屏幕上)或纹理的不同部分(我可以稍后在渲染四边形时使用以达到相同的效果)。

  2. 将网格中的所有顶点复制 K 次,本质上是制作一个包含 K 个网格的单个顶点流,并添加一个(或少数几个)属性,指示每个网格克隆应该投影到哪个四边形(以及如何到达那里),以及使用顶点着色器进行投影。我会打一个电话来绘制,但发送 K 倍的数据。

问题:在这两个选项中,哪个通常性能更好?

(另外:有没有更好的方法来做到这一点?

我考虑了第三种选择,我将网格细节渲染到纹理,并将我的 K 克隆几何体创建为一种虚拟流,我可以一劳永逸地绑定它,在顶点着色器中查找纹理为每个顶点找出它真正代表的顶点;但有人告诉我,顶点着色器中的纹理支持很差,或者在 OpenGL ES 2.0 中被禁止,我宁愿避开这条路线。)

4

2 回答 2

1

这个问题没有完美的答案,但我建议您考虑一下实时计算机图形和 OpenGL 管道的本质。尽管需要“GL”来产生与按顺序执行一致的结果,但现实情况是 GPU 是高度并行的野兽。如果您实际上同时进行许多不相关的任务(有些甚至将整个管道分成离散的图块),它们会采用许多最有效的技巧。例如,GDDR 内存的延迟非常高,因此为了提高效率,GPU 需要能够调度其他作业,以在为刚刚开始的作业获取内存时保持流处理器(着色器单元)忙碌。

如果您在每帧重新计算网格的一部分,那么您几乎肯定会希望更多的绘制调用而不是每帧的大量 CPU->GPU 数据传输。用不必要的数据传输使总线饱和甚至会困扰 PCI Express 硬件(它比几个额外的绘制调用所增加的开销要慢得多),它只会在嵌入式 OpenGL ES 系统上变得更糟。话虽如此,您没有理由不能简单地glBufferSubData (...)仅在网格中受影响的部分进行流式传输,并在一次绘制调用中继续绘制整个网格。

如果您拆分(或分割其中的数据)缓冲区和/或绘制调用,您可能会获得更好的缓存一致性,具体取决于您的实际用例场景。确定哪种方法更适合您的情况的唯一方法是在目标硬件上分析您的软件。但所有这些都没有从大局来看,那就是:“我为什么要在 CPU 上做这个?!”


听起来您真正想要的只是vertex instancing。如果您可以通过传递实例 ID 重新设计您的算法以完全在顶点着色器中工作,那么您应该会看到我迄今为止提出的所有解决方案都有巨大的改进(真正的实例化实际上介于您在解决方案 1 和2) :)

实例化的实际概念非常简单,无论您的 OpenGL API 的特定版本是否在 API 级别支持它,都会给您带来好处(您始终可以使用顶点属性和额外的顶点缓冲区数据手动实现它)。问题是,如果您正确实施实例化,您根本不必复制数据。识别每个单独顶点所需的额外数据是静态的,您始终可以更改着色器统一并进行额外的绘制调用(这可能是您对 OpenGL ES 2.0 所做的,因为它不提供glDrawElementsInstanced)而无需触及任何顶点数据。

您当然不必复制顶点K*N次,您的缓冲区空间复杂度将更像 O ( K + K*M),其中 M 是您必须添加的新组件的数量,以唯一标识每个顶点,以便您可以计算 GPU 上的所有内容。对于“实例”,您可能需要对四边形 1-4 中的每个顶点进行编号,并根据您正在处理的顶点在着色器中以不同方式处理顶点。在这种情况下,M 系数为 1,无论您需要动态计算每一帧的四边形实例有多少,它都不会改变;N 将确定 OpenGL ES 2.0 中的绘制调用次数,而不是数据的大小。如果 OpenGL ES 2.0 支持 gl_VertexID,则不需要这些额外的存储空间 :(

Instancing is the best way to make effective use of the highly-parallel GPU and avoid CPU/GPU synchronization and slow bus transfers. Even though OpenGL ES 2.0 does not support instancing in the API sense, multiple draw calls using the same vertex buffer where the only thing you change between calls are a couple of shader uniforms is often preferable to computing your vertices on the CPU and uploading new vertex data every frame or having your vertex buffer's size depend directly on the number of instances you intend to draw (yuck). You'll have to try it out and see what your hardware likes.

于 2013-08-07T21:04:35.767 回答
0

实例化将是您正在寻找的,但不幸的是它不适用于 OpenGL ES 2.0。如果您的所有资产都可以放入 GPU,我会赞成将所有顶点发送到 GPU 并进行一次绘制调用。我有将绘图调用从 100+ 减少到 1 的经验,性能从 15 fps 降低到 60 fps。

于 2013-08-07T21:27:00.487 回答