在 OpenGL 中使用持久映射的缓冲区存储,仅在绘制之前和交换之后接触,是不是真的需要进一步的同步?

Posted

技术标签:

【中文标题】在 OpenGL 中使用持久映射的缓冲区存储,仅在绘制之前和交换之后接触,是不是真的需要进一步的同步?【英文标题】:With persistently mapped buffer storage in OpenGL, touched only before draw and after swap, is any further synchronization really needed?在 OpenGL 中使用持久映射的缓冲区存储,仅在绘制之前和交换之后接触,是否真的需要进一步的同步? 【发布时间】:2016-03-03 21:36:46 【问题描述】:

我编写了一个简单的 C 程序 (on github),它使用 OpenGL 从分配有 glBufferStorage 的缓冲区中绘制一堆三角形,如下所示:

glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
GLbitfield bufferStorageFlags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
glBufferStorage(GL_ARRAY_BUFFER, vboSize, 0, bufferStorageFlags);
vert *triData = glMapBufferRange(GL_ARRAY_BUFFER, 0, vboSize, bufferStorageFlags);

我知道在使用glBufferStorageMAP_PERSISTENT_BIT 时同步是我的责任,但我不确定我需要保护什么。 我唯一一次触摸triData 是在调用glDrawArrays 之前和调用SDL_GL_SwapWindow 之后,所以我知道最后一帧的绘制完成了,我还没有调用此帧的绘制开始。 即使禁用了垂直同步,这似乎也能正常工作。

The wiki 说:

交换默认帧缓冲区上的后缓冲区和前缓冲区可能 导致某种形式的同步......如果仍有命令 影响尚未完成的默认帧缓冲区。 交换缓冲区仅在技术上需要同步到最后一个命令 这会影响默认帧缓冲区,但它可能会执行完整的 glFinish​.

但是我读过的每一篇关于这个主题的文章都大量使用了GLsync 指针,虽然也许他们只是假设我可能想以更复杂的方式使用缓冲区? 现在,我是否相信 SDL_GL_SwapWindow 提供了足够的同步?

【问题讨论】:

【参考方案1】:

前面的答案是正确的,即使在使用交换之后也需要同步。但我想更清楚一点,这不仅仅是理论上的问题。

交换操作通常不是同步的。让渲染在显示之前获得 1-2 帧是很常见的。这样做是为了减少 GPU 暂时进入空闲状态的“气泡”。如果您的交换调用是同步的,则 GPU 在返回时将不可避免地处于空闲状态,因为之前提交的所有工作都已完成。即使您立即再次开始渲染,该工作也需要一点时间才能真正到达 GPU 执行。因此,有时 GPU 什么都不做,至少只要您的渲染完全受 GPU 限制,就会损害性能。

现在,您显然不希望渲染超出显示太远。不希望的副作用是响应用户输入的延迟增加(这对游戏来说很重要),以及排队渲染命令的过多内存使用。因此,在这种情况发生之前需要进行节流。这种限制通常作为交换操作的一部分应用,但几乎任何时候都可能发生。

因此,如果您测量交换调用返回所用的挂钟时间,很常见的情况是它足够长以表明它正在阻塞。但这并不表明调用本身是同步的。它可能只是在前一帧完成之前一直处于阻塞状态,以防止渲染超出显示范围。

【讨论】:

“交换操作通常不是同步的。让渲染提前显示 1-2 帧是很常见的。”但我在这里不这样做,假设我不打算这样做。我将交换放在渲染循环的末尾。交换是否算作绘制调用的显式同步? 一点也不。正如我试图解释的那样,交换调用(它是窗口系统集成的一部分,而不是 OpenGL 本身)可以是异步的,就像您进行的 OpenGL 调用一样。 好吧,我开始意识到交换并不像我想象的那样工作。我将调用 glFinish 以确保绘制调用已刷新。【参考方案2】:

这是我的favorite advice 关于任何多线程/异步代码:

如果多线程代码不能立即证明是正确的,那么它几乎肯定是错误的。

您无法证明 OpenGL 不会从您正在写入的值中读取。因此,即使没有明显的问题,它也是错误的。

是的,您需要进行显式同步。即使您连贯地映射了缓冲区,您仍然无法更改其中的值,而 OpenGL 可能正在读取它们。您必须等到最后一次调用从该数据读取数据后,才能再次写入数据。 OpenGL 必须等待它完成的唯一方法是 glFinishglClientWaitSync

【讨论】:

【参考方案3】:

我知道在使用 glBufferStorage 时同步是我的责任,

不,不一定。使用glBufferStorage 创建的缓冲区与使用glBuffer 创建的缓冲区没有什么不同,只是不能重新指定它。

您只需要在与MAP_PERSISTENT_BIT(与glBufferStorage 相同的扩展名ARB_buffer_storage 包含在同一扩展名中)进行映射时进行手动同步。

【讨论】:

啊,谢谢,但我使用的是 MAP_PERSISTENT_BIT,我编辑了问题以使其更清楚。

以上是关于在 OpenGL 中使用持久映射的缓冲区存储,仅在绘制之前和交换之后接触,是不是真的需要进一步的同步?的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL阴影映射,具有多个glDrawElements的帧缓冲区

如何在 WebGL 中实现阴影映射?

OpenGL - 使用 glDrawElements 错误地映射纹理

我的OpenGL学习进阶之旅介绍一下 映射缓冲区对象和复制缓冲区对象

OpenGL 纹理映射内存泄漏

在OpenGL ES(Android)中使用带纹理的索引缓冲区有什么意义吗?