【问题标题】:Fastest way to upload streaming points, and removing occasionally上传流点的最快方式,偶尔删除
【发布时间】:2013-08-18 16:01:19
【问题描述】:

所以我有一个系统(使用 OpenGL 4.x),我从外部源接收点流(可能带有颜色和/或正常)。我需要将这些点绘制为 GL_POINTS,运行自定义可切换着色器进行着色(颜色可以通过程序生成,或者来自顶点颜色或法线方向)。

流包括以相当规则的间隔(4 到 10 赫兹)接收一组任意计数(通常从 1k 到 70k 点)的点(有或没有法线或颜色),我需要添加这些点到我目前的积分并绘制所有积分到目前为止收到的积分。

我保证我的顶点类型不会改变,我在流式传输开始时被告知,所以我要么使用交错顶点:pos+normal+color、pos+normal、pos+color ,或者只是pos。

我当前的解决方案是在指定最大顶点数的配置文件中分配适当顶点类型的交错顶点 VBO(与周围的 VAO)(使用 DYNAMIC 提示分配)。

随着新点的出现,我通过 glBufferSubData 填充了我当前未填充的 VBO。我保留了当前边界 VBO 到目前为止有多少个顶点的计数(activePoints),如果我当前的更新组的顶点数超过我的边界缓冲区(因为我限制每个 VBO 的顶点数),然后我分配一个新的 VBO 并填充从 0 开始并以我的更新组中剩余的点数(未添加到最后一个缓冲区)结束的范围,如果我还有剩余点我一次又一次地这样做。很少有更新组跨越 2 个以上的缓冲区。

渲染时,我使用 glDrawArrays(m_DrawMode,0,numVertices) 渲染我的所有 VBO (-1),其中 numVertices 等于最大缓冲区允许大小,我的边界缓冲区使用 glDrawArrays(m_DrawMode,startElem,numElems) 到说明它没有被有效顶点完全填充。

当然,在某些时候,我会得到比交互式绘制更多的点,所以我有一个 LRU 机制,可以根据需要释放最旧的(根据 LRU alg)VBO 集。

有没有更优化的方法来做到这一点?缓冲区孤儿?流媒体提示?地图与子数据?还有什么?

第二个问题是我现在被要求删除点(不定期),一次从 10 到 2000 不等。但是这些点在我最初收到它们的顺序中是不规则的。我可以找出它们当前退出的缓冲区中的偏移量,但它更多的是分散而不是范围。我一直在通过找到它们在正确缓冲区中的偏移量并一个接一个地调用范围为 1 的 glBufferSubData 来“删除它们”(它们很少在缓冲区中彼此并排),并将 pos 更改为远离的某个地方他们永远不会被看到。最终我猜应该从这些删除请求中删除缓冲区,但我目前不这样做。

有什么更好的方法来处理这个问题?

【问题讨论】:

  • 我强烈建议您使用glDrawElements (...),而不是将未使用的顶点物理归零。然后你将一个包含所有实际有数据的点的数组传递给它。您可以在内部保留未使用或空闲顶点的列表,但您肯定会浪费总线带宽将空闲顶点归零。让它们保持原样,但承认它们不会被渲染/包含垃圾:)
  • 那么您是否建议我创建一个删除并创建一个充满索引的新 VBO(或实际上是 IBO),每次说需要删除缓冲区中 300k 中的 4 个点?然后用 glDrawElements 绘制,与 glDrawArrays 相对?
  • 不完全。我建议您为顶点缓冲区分配一个大小相等的 IBO,最初它将用 0-n 的值顺序填充(其中 n 是您的 VBO 大小)。当您想删除随机位置的单个顶点或一系列连续顶点时,只需使用 glBufferSubData (...) 并将 IndexArray [n] 中的值替换为指向有效顶点的索引。这样,您不必重新定义顶点位置或添加 32 位 w 坐标以在渲染时“隐藏”已删除的顶点,您只需重新定义 16 位索引即可。
  • 您不想为此操作创建或删除新的 IBO。与顶点缓冲区相同维度的 16 位 IBO 将比添加 w 坐标消耗更少的内存。理论上,使用巨大的元素数组和glDrawElements (...) 进行渲染可能会更慢,但您肯定会减少删除随机分布顶点的情况的费用。而且您不会增加定义(或更新)顶点位置的费用,因为您不需要每个顶点额外增加 32 位。

标签: opengl vbo


【解决方案1】:

映射可能比glBufferSubData 更有效,尤其是在必须“删除”点时。显式刷新可能特别有帮助。此外,映射允许您将缓冲区的填充工作卸载到另一个线程。
务必确保访问位正确(或性能极差),尤其是如果您所做的只是写入,则不要映射“读取”区域。

您可能知道,从顶点缓冲区中删除点并不容易。对于“少数”点(例如 10 或 20),我只需设置 w = 0,这会将它们移动到无穷大并像以前一样继续绘制整个事物。如果您的剪辑平面不是无限远,这只会丢弃它们。通过显式刷新,您甚至不需要在内存中保留单独的副本。
对于“很多”点(例如 1,000),您可以考虑使用glCopyBufferSubData 删除“孔”。在 GPU 上移动内存速度很快,对于数千个点来说,这可能是值得的。然后,您需要为每个顶点缓冲区维护一个计数,以便在删除一些点后绘制更少的点。

要“删除”整个顶点缓冲区,您应该将它们孤立(并重用)。届时,OpenGL 将代表自己做正确的事情,这是保持绘制和重用内存的最有效方式。

使用 glDrawElements 而不是 Andon M. Coleman 评论中建议的 glDrawArrays 通常是一个好建议,但在这种情况下不会帮助您。想要这样做的原因是转换后缓存通过按索引标记顶点来工作,因此绘图元素利用了转换后缓存,而绘图数组则没有。但是,转换后缓存仅对复杂几何图形有用,例如三角形列表或三角形条带。您正在绘制点,因此在任何情况下都不会使用转换后缓存——但使用索引会增加 GPU 和 PCIe 总线上的内存带宽。

【讨论】:

  • "...但在这种情况下不会帮助您。"是这样吗?我的想法是在元素数组中设置一个 16 位索引(可以存储在 VBO 中)比在顶点缓冲区中设置一个 32 位浮点分量 (w) 为 0.0 便宜(或者在最坏的情况下)情况下,如问题中所述将整个顶点归零)。并不是每次绘制调用都必须将元素数组 x'ferred 到 GPU,因此用虚拟值替换元素索引(类似于原始重启索引的工作方式)似乎(无论如何对我来说)它会减少总线开销.
  • @AndonM.Coleman:嗯,您必须将不同的索引集上传到“归零”点,这与上传新缓冲区相同。 PCIe 带宽很大,但延迟有一个“非常非零”的固定最小值,这意味着进行任何类型的传输都是痛苦的,但无论您上传更多或更少的数据都没有什么影响。另一方面,如果您使用索引(即使对于没有“洞”的完整缓冲区),您必须始终上传索引,而不是在每次绘制调用时而是在每次更改时上传,并且索引会占用 GPU 上的内存。到目前为止,我很确定他们宁愿伤害也不要帮助。
  • 所以在我只是向我的边境 VBO 添加点的情况下。它已经包含了它的 100k 限制(我强加的)中的 10k 点,我想在此更新中添加 60k 点。你会说映射是最好的,而不是那个缓冲区上的 subData。如果有的话,什么时候适合孤立(删除并立即重新创建)缓冲区以进行添加更新。
  • 只要你只追加点,你就可以映射GL_COPY_READ_BUFFER,并且可以选择将指针交给工作线程以将数据推入。完成后,它会发出一个事件信号。同时,主线程可以继续渲染。完成后,取消映射缓冲区并执行glCopyBufferSubData,类似于 ARB_copy_buffer 规范中的代码示例。当你想丢弃缓冲区时,你会做孤立的事情,因为它包含太多“洞”(用无洞副本替换它)或者因为它从 LRU 管理器的架子上掉下来了。映射可以避免同步如果...
  • ...如果操作正确,可以避免污染缓存。 SubData 没有太多选择——它必须同步,而且很可能也无法避免污染缓存。此外,它不能卸载到另一个线程。
猜你喜欢
  • 1970-01-01
  • 2015-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-29
  • 1970-01-01
相关资源
最近更新 更多