glDrawArrays访问冲突写入位置

3

我正在尝试可视化非常大的点云(700百万个点),但在glDrawArrays调用时,调试器会抛出访问冲突写入位置异常。我使用相同的代码来渲染较小的点云(100百万个点),一切都正常工作。我也有足够的RAM内存(32GB)来存储数据。

为了存储点云,我正在使用std :: vector<Point3D<float>>,其中Point3D是

template <class T>
union Point3D
{
    T data[3];
        struct{
            T x;
            T y;
            T z;
        };
}

顶点数组和缓冲区初始化:

glBindVertexArray(pxCloudHeader.uiVBA);

glBindBuffer(GL_ARRAY_BUFFER, pxCloudHeader.xVBOs.uiVBO_XYZ);
glBufferData(GL_ARRAY_BUFFER, pxCloudHeader.iPointsCount * sizeof(GLfloat) * 3, &p3DfXYZ->data[0], GL_STREAM_DRAW);
glVertexAttribPointer((GLuint)0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);

glBindVertexArray(0);

绘制调用:

glBindVertexArray(pxCloudHeader.uiVBA);
glDrawArrays(GL_POINTS, 0, pxCloudHeader.iPointsCount); // here exception is thrown
glBindVertexArray(0);

我也检查了是否有OpenGL错误,但我没有发现任何问题。

我在你的代码片段中没有看到glGenBuffers调用,但你可能只是省略了它。但天啊,2.8GiB的数据对于VBO来说是相当大的一块。这种情况很少见,我建议你尝试使用老式的客户端顶点数组。而且,它们甚至可以与3.3兼容性配置文件一起使用。只需不创建VBO或VAO,并将glVertexAttribPointer指向进程地址空间中的数据即可。 - datenwolf
我不确定,但我认为当你分配GL_FLOATS时,你的最大顶点数将是2 ** 32 = 4,294,967,296。如果超过这个数字,可能会出现访问冲突。你可以拆分数据。 - Thellimist
datenwolf:是的,为了更简单起见,我省略了glGenBuffers。我的目标也是实现最佳性能,而您建议的可能会解决内存问题,但它会造成性能问题。如果我错了,请纠正我。 - Amadeusz
Furkan:如果这是个问题,我期望在使用glBufferData()加载数据到缓冲区或者在std::vector分配时会抛出错误。此外,我的数据集比你给我的数字要小。将数据拆分可能会解决我的问题,但如果可能的话,我想避免这种情况。 - Amadeusz
如果您拥有如此大的内存,我假设您正在64位平台上。您能否验证sizeof(GLsizeiptr)实际上为8? 7亿 * 4 * 3 > 32位指针可以引用。当您分配缓冲区时,可能会溢出大小参数。 GLsizeiptr的大小将决定您可以分配的最大连续内存块;没有其他API限制可以查询这种情况......但是,使用3个非连续数组而不是1个交错数组可能有助于绕过这种限制。 - Andon M. Coleman
2个回答

0

我怀疑你的问题是由于GLsizeiptr的大小引起的。

这是OpenGL缓冲区对象中用于表示大小的数据类型,通常为32位。

700 million vertices * 4-bytes per-component * 3-components = 8,400,000,000 bytes

如果GL使用32位指针,尝试分配那么多字节会出现严重问题:

8400000000 & 0xFFFFFFFF = 4,105,032,704 (half as many bytes as you actually need)

如果您的实现中 sizeof(GLsizeiptr)4,那么您将别无选择,只能拆分数组。32位的GLsizeiptr只允许您存储4个连续的GiB内存,但是如果您使用3个单组件数组,则可以解决此问题。使用顶点着色器,您可以像这样重构这3个单独(足够小)的数组:
#version 330

layout (location = 0) in float x; // Vertex Attrib Ptr. 0
layout (location = 1) in float y; // Vertex Attrib Ptr. 1
layout (location = 2) in float z; // Vertex Attrib Ptr. 2

void main (void)
{
  gl_Position = vec4 (x,y,z,1.0);
}

性能将会很差,但这是一种用最小的努力来解决问题的方法。


顺便说一下,这里的系统内存数量(32 GiB)不是你最大的问题。你应该考虑你的 GPU 上的 VRAM 量,因为理想情况下缓冲区对象是设计用于存储在 GPU 上的。如果缓冲区对象的任何部分太大无法存储在 GPU 内存中,则在使用时必须通过 PCIe 总线传输。

非常感谢您指出这个问题。我的GLsizeiptr的大小为8,但我现在看到我的问题在这里。实际上,我的第一个数据结构是3个单独的数组,但我放弃了这个想法,因为我使用的一些库需要连续的XYZ数组输入,我想避免双重数据表示,但现在我看到我可能没有选择 :( - Amadeusz

0

你可以将数据分批绘制。虽然缓冲区的大小没有预定义的上限,但是在一个单一的缓冲区中存储8GB的数据太多了。我对于出现问题并不感到意外。

我建议你每个缓冲区存储大约100万个或最多几百万个点。然后使用一个固定大小的缓冲区池,足够容纳所有的数据点。

这样做甚至可能对性能有益,因为它允许你在将所有数据复制到缓冲区之前开始提交绘制调用。这将使CPU和GPU的工作更好地重叠。

考虑到你要处理的数据量,你可能还想考虑使用glMapBuffer()/glUnmapBuffer()而不是glBufferData()。这通常可以避免一次数据的复制操作。


我希望尽可能避免拆分数据,因为这会给我带来更多的同步问题。但这可能是解决方案,所以我一定会尝试。此外,感谢您提供关于glMapBuffer()/glUnmapBuffer()的好建议 - 如果它比glBufferData()更快、更安全,我一定会使用它。 - Amadeusz

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