多线程内的 OpenGL VBO

发布于 2024-12-27 14:00:49 字数 1426 浏览 2 评论 0原文

我正在用 C++/OpenGL 开发一个程序,它可以绘制整个世界的地形。我有一个以图块形式存储的海拔高度数据库。每次启动程序时,都会加载一个图块。然后,当人移动时,应该加载另一个图块,这不会每帧都发生,可能每 5 分钟一次。

我将初始图块加载到显卡内存中:

glBindBufferARB(GL_ARRAY_BUFFER_ARB, VertexBuffer[idx]);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, VBOsz * 3 * sizeof(float), tile_data, GL_STATIC_DRAW_ARB);

...有法线、颜色和索引缓冲区

并且我绘制它们:

glBindBufferARB(GL_ARRAY_BUFFER_ARB, VertexBuffer[idx]);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, VBOsz * 3 * sizeof(float), tile_data, GL_STATIC_DRAW_ARB);


glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);

glBindBufferARB(GL_ARRAY_BUFFER_ARB, VertexBuffer[idx]);
glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));

...

glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, IndexBuffer[idx]);
glDrawElements(GL_TRIANGLES, IndexBuffersz, GL_UNSIGNED_INT, BUFFER_OFFSET(0));

glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);

因为我希望程序尽可能平滑,所以我无法计算顶点+颜色+普通+其他纹理同一帧,因为创建一个图块大约需要20秒。

因此,我决定创建一个加载器线程来检查何时需要加载新图块,然后加载它。当这一切完成后,它应该只交换 VBO(因此是 [idx]。

所以对于加载线程,我知道我需要第二个 OpenGL 上下文,我创建了一个并在它们之间共享列表。这个想法是工作,但在加载线程中,当我发送新的VBO数据时,我需要这个函数: wglMakeCurrent

只有当所有数据都已加载时,我才能将上下文设置为渲染线程(主程序的线程)。被画这么长的时间,这使得该程序毫无用处。

我需要改变这个概念吗?

升级到 OpenGL 3 可以解决这个问题吗?

I am developing a program in C++/OpenGL which draws terrain of the entire world. I have a database of altitude heights stored as tiles. Every time I start the program, a tile is loaded. Then as the person moves, another tile should load, this does not happen every frame, maybe once every 5 minutes.

I load the initial tile in the video card's memory:

glBindBufferARB(GL_ARRAY_BUFFER_ARB, VertexBuffer[idx]);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, VBOsz * 3 * sizeof(float), tile_data, GL_STATIC_DRAW_ARB);

...There are normals, color and index buffers

And I draw them:

glBindBufferARB(GL_ARRAY_BUFFER_ARB, VertexBuffer[idx]);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, VBOsz * 3 * sizeof(float), tile_data, GL_STATIC_DRAW_ARB);


glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);

glBindBufferARB(GL_ARRAY_BUFFER_ARB, VertexBuffer[idx]);
glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));

...

glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, IndexBuffer[idx]);
glDrawElements(GL_TRIANGLES, IndexBuffersz, GL_UNSIGNED_INT, BUFFER_OFFSET(0));

glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);

Since I want the program to be as smooth as possible, I can't calculate the vertex+color+normal+ other textures the same frame, as it takes about 20 seconds to create a tile.

So I decided to make a loader thread that would check for when a new tile needs to be loaded and then load it. When it would be all done, it should just swap the VBO(hence the [idx].

So for a loader thread, I know that I need a second OpenGL context, I created one and I share the lists between them. The idea is working, but in the loader thread, when I send the new VBO data, I need this function: wglMakeCurrent

Only when all the data has been loaded, I can set the context to the rendering thread(main program's thread). This causes nothing to be drawn for that amount of time, which makes the program useless.

Do you have any ideas on a solution? Do I need to change the concept?

I am using OpenGL 2.1. Will upgrading to OpenGL 3 solve the problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

白色秋天 2025-01-03 14:00:49

这其实并没有那么复杂。

您只需创建两个缓冲区对象:一个用于当前渲染,另一个用于将来。当未使用的缓冲区充满数据时,渲染线程会从该缓冲区切换到渲染。先前使用的缓冲区将成为未使用的缓冲区。

您可以通过两种方式之一上传数据。一种方法是让数据创建线程创建一个数据数组,渲染线程将使用 glBufferData 将其上传到缓冲区对象。显然,这需要一些同步,但您需要的是同步代码:最终必须在数据准备好时通知渲染线程,以便它可以使用新数据进行渲染。

另一种方法是告诉您的渲染线程需要开始生成数据。此时,它将映射未使用的缓冲区对象并将映射的指针传递给数据创建线程。该线程将直接将数据生成到该映射指针中。当它完成时,它通知渲染线程,渲染线程将取消映射缓冲区,然后使用数据进行渲染。

这两种方法都不需要多个上下文或通过 OpenGL 代码进行线程化。

请注意,使缓冲区变大或变小可以最好地提高性能。选择尺寸并坚持使用;您不想使用 glBufferData 调整缓冲区大小。

This is really not that complicated.

You just make two buffer objects: one that you're using for current rendering and one that you will be using in the future. When the unused buffer is full of data, the rendering thread switches to rendering from that one. The previously used buffer becomes the unused one.

You can upload the data in one of two ways. One way is for your data creation thread to create an array of data that the rendering thread will upload to the buffer object with glBufferData. Obviously this will require some synchronization, but it's sync code that you need: your render thread must ultimately be informed when data is ready so that it can render with the new data.

The other way is for your rendering thread to be told that data needs to start being generated. At which point, it will map the unused buffer object and pass the mapped pointer to the data creation thread. That thread will generate data directly into that mapped pointer. When it finishes, it informs the render thread, which will unmap the buffer and then render with the data.

Neither method requires multiple contexts or threading through OpenGL code.

Note that performance will be best served by not making the buffers bigger and smaller. Pick a size and stick with it; you don't want to resize buffers with glBufferData.

命硬 2025-01-03 14:00:49

我已经为此类任务提供了答案

简而言之,您创建了两个共享上下文。然后,按照Damon的建议,使上下文在其自己的线程上成为当前上下文,仅在线程执行开始时一次。两个上下文将同时在不同线程上处于当前状态(一个线程,一个上下文)。

然后,辅助线程不会用于渲染,而是用于加载资源(我的意思是,实际上加载地形数据、纹理......并在每个数据上创建相应的OpenGL对象,例如纹理和缓冲区对象)。这是在主要上下文绘制时发生的。

本质上,您不需要担心在应用程序中引入指针并阻塞渲染线程上传数据;但以创建另一个上下文为代价。让驱动程序同步上下文状态:如果它可以顺利地执行该操作,您的应用程序将从中受益;至少,你的代码会更干净。

我在该主题上的其他贡献:

I have already an answer for this kind of tasks.

In few words, you create two sharing contextes. Then, as suggested by Damon, make the contextes current on their own thread, only once at the beginning of the thread execution. The two contextes will be current at the same time on different threads (one thread, one context).

Then, the secondary thread won't be used for rendering, but for loading resources (I mean, actually loading terrain data, textures... and create a corresponding OpenGL object on each data, such as textures and buffer objects). This happens while the main context is drawing.

Essentially, you don't need to worry about bringing a pointer around the application and blocking the rendering thread for uploading data; but at the cost of creating another context. Let the driver to synchronize context states: if it can perform that operations smoothly your application will will benefit of it; at least, you code will be cleaner.

Other contributions of mine on the subject:

风吹雨成花 2025-01-03 14:00:49

您只需在每个线程中调用一次 wglMakeCurrent 即可。这工作可靠,这就是我正在做的事情(尽管使用 OpenGL 3.3)。这标志着一个上下文属于一个线程。它会一直保持这种状态,直到您以不同的方式告诉 OpenGL,因此在开始时执行一次并忘记(事实上,如果您使用它们在各自的线程中创建上下文,则根本不需要调用它,但无论如何执行只是为了100% 安全,而且我更喜欢在启动之前创建所有上下文,这样不会那么混乱...)。

顺便说一句,您不必担心函数指针,只需使用您在渲染线程中使用的相同函数指针(假设您已在那里正确初始化它)。
从技术上讲,使用另一个上下文中的函数指针是无效的。然而,WGL 善意地保证(隐藏在小字中!)函数指针对于具有相同像素格式的所有上下文都是相同的。这样,你就可以走了。

使用单个上下文的另一种方法是在渲染线程中使用 glMapBuffer 并将指针传递给工作线程。然后,完成后(例如,发出信号量信号),glUnmapBuffer 再次在渲染线程中。
有些人更喜欢这个,因为它不涉及上下文杂乱,并且可能在一些旧的有缺陷的驱动程序上工作得更好。我不喜欢它,因为需要额外的同步。这是口味问题,效果是一样的。

You only need to call wglMakeCurrent exactly once in each thread. This works reliably, it is what I'm doing (though with OpenGL 3.3). This marks one context belonging to one thread. It stays that way until you tell OpenGL differently, so do it once at the beginning and forget (in fact, you don't need to call it at all if you create contexts in their respective threads using them, but do it anyway just to be 100% safe, also I prefer creating all contexts before starting up, it's not as messy...).

You need not worry about the function pointer, by the way, just use the same one you've used in the render thread (assuming you've properly initialized it there).
Technically, it is invalid to use a function pointer from another context. However, WGL kindly guarantees (hidden in the small print!) that function pointers are identical for all contexts having the same pixel format. Thus, you're good to go.

An alternative that works with a single context is to glMapBuffer in the render thread and pass the pointer to the worker thread. Then, upon completion (signalling a semaphore, for example), glUnmapBuffer, again in the render thread.
Some people prefer this, as it does not involve context juggling and presumably works better on some old buggy drivers. I don't like it because of the extra synchronization needed. It's a matter of taste, same effect.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文