这个问题没有完美的答案,但我建议您考虑一下实时计算机图形和 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.