如何在不丢失先前数据的情况下仅将新数据发送到opengl?

时间:2018-12-02 05:57:59

标签: c++ opengl

我正在处理需要使用opengl渲染的PointCloud数据。我每帧都有一个新的数据点向量。我希望我能够缓存先前发送到opengl的数据,并且仅向其发送最新的帧数据。我该怎么办?

我进行了搜索,发现了这个想法here

// Bind the old buffer to `GL_COPY_READ_BUFFER`
glBindBuffer (GL_COPY_READ_BUFFER, old_buffer);

// Allocate data for a new buffer
glGenBuffers (1, &new_buffer);
glBindBuffer (GL_COPY_WRITE_BUFFER, new_buffer);
glBufferData (GL_COPY_WRITE_BUFFER, ...);

// Copy `old_buffer_size`-bytes of data from `GL_COPY_READ_BUFFER`
//   to `GL_COPY_WRITE_BUFFER` beginning at 0.
glCopyBufferSubData (GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, old_buffer_size);

但是看起来它终于在新缓冲区中发送了以前的数据和新数据,而不是仅缓存和发送了最新数据。因此,我不确定这是否是最好的方法。如果我错了,请改正我,或者提出其他选择。

2 个答案:

答案 0 :(得分:2)

因此,您将一些数据存储在CPU内存中,并将更多数据附加到此存储中。然后,您只想将附加的数据发送到GPU,而不是整个缓冲区。

您的代码示例与该任务无关,因为glCopyBufferSubData将数据从GPU内存中的位置再次复制到GPU内存中的另一个位置。

您需要glBufferDataglBufferSubData的组合。 glBufferData在GPU中分配内存并对其进行优化。 glBufferSubData将一些数据写入已分配的GPU缓冲区。您可以将glBufferData视为C的malloc或C ++ new,而glBufferSubData就像C的特殊版本memcpy或C ++ std::copy。更准确地说,从CPU到GPU,glBufferSubDatamemcpy,从GPU到GPU,glCopyBufferSubDatamemcpy

如何一起煮饭?与C中的方法相同。在初始化时(程序启动时)一次调用glBufferData,并在需要附加数据时调用glBufferSubData。确保分配足够的空间! glBufferData分配的缓冲区以及malloc分配的缓冲区不会增加。用glBufferSubData溢出缓冲区会导致未定义的行为,并可能使您的应用程序崩溃。

尝试预测缓冲区的空间需求,仅在数据不适合缓冲区时调用glBufferData

请记住,使用已经分配的缓冲区绑定调用glBufferData会取消分配现有缓冲区并创建一个新缓冲区。

glBufferSubData不会重新分配缓冲区,但是会覆盖已经存在的数据。

让我用C翻译说明一下:

glGenBuffers(..., buf); // float* buf;
glBindBuffer(buf); // Tell opengl that we will use buf pointer, no analog in C.
glBufferData(/*non-null pointer*/); // buf = malloc(/*..*/); memcpy(to_gpu, from_cpu);
glBufferData(/*another non-null pointer*/); // free(buf); buf = malloc(/*..*/); memcpy(to_gpu, from_cpu);
glBufferSubData(...); // memcpy(to_gpu, from_cpu);

理想方法

您需要的是:

glGenBuffers(..., buf); // float* buf;
glBindBuffer(buf); // Tell opengl that we will use buf pointer, no analog in C.

// Initialization
glBufferData(/*non-null pointer*/); // buf = malloc(/*..*/); memcpy(to_gpu, from_cpu);

// Hot loop
while (needToRender) {
    if(needToAppend) {
        if (dataDoesNotFit) glBufferData(...); // Reallocate, same buffer name
        else glBufferSubData(...); // memcpy(to_gpu, from_cpu);
    }
}

在这里,我们仅在需要追加某些内容且缓冲区太小时才偶尔重新分配内存。

其他方法

我建议您用glBufferData重新分配,因为您已经将所有数据放在CPU上的单个缓冲区中。如果没有(例如,您在GPU上有一块数据,在CPU上有另一块数据,但又不是在一起),则可以使用glCopyBufferSubData重新分配:

glBufferData(/*alloc new_gpu_buffer*/);
glCopyBufferSubData(/*from old_gpu_buffer to new_gpu_buffer*/);
glDeleteBuffers(/*old_gpu_buffer*/);
glBufferSubData(/*from_cpu_buffer to new_cpu_buffer*/)p; // Add some new data from CPU.

另一种更新GPU数据的方法是mapping到CPU,因此您只需通过指针访问GPU内存即可。它可能很慢(阻塞缓冲区,使管道停滞),并且仅在特殊情况下有用。如果您知道自己的工作,请使用它。

答案 1 :(得分:2)

由于OpenGL是一个专注于绘制事物的API(暂时忽略计算着色器),并且绘制场景时通常是从一块空白画布开始的,因此您必须在整个过程中保留整个点云数据的完整积压。时间跨度,您希望能够重绘。

假设对于大量的点云数据,重绘整个集合可能需要一些时间,某种形式的机器似乎是合理的。但是,让我们先进行一些信封计算:

如今,典型的GPU完全能够以超过10 ^ 9顶点/秒的速率执行完整的顶点设置(已经在20年前,GPU能够以每秒20·10 ^ 6顶点的速度执行某些操作) 。您的典型计算机显示器的像素小于10·10 ^ 6。因此,根据信鸽原理,如果要绘制超过10·10 ^ 6的点,则会产生严重的透支或填满大部分像素;实际上,它会介于两者之间。

但是,正如我们已经看到的那样,GPU能够以交互式帧速率绘制许多点。绘制更多的图形可能会填满屏幕或遮挡数据。

如果您希望整个内容保持可读性,则需要某种形式的数据报废。对于任何可读的点云大小,您的GPU都可以很好地重绘整个内容。

考虑到需要数据回收,我建议您分配一个大缓冲区,该缓冲区能够在逐出之前保存整个点集,然后将其用作循环轮询缓冲区:在您要偏移的位置使用偏移量写入新数据时(使用glBufferSubData),在边缘可能需要将其分成两个调用,以统一的形式传递最新的写入索引,以其年龄淡出点数,然后提交一次glDrawElements调用即可一次性绘制该缓冲区的全部内容。