我应该缓存OpenGL状态,例如当前绑定的缓冲区,还是OpenGL已经自动处理了?

4

一个典型的OpenGL调用可能如下所示:

GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_SOME_BUFFER, buffer);
...

我读到绑定缓冲区和其他类似的函数可能会非常昂贵。是否值得在绑定之前保存当前绑定的缓冲区并进行检查?就像这样:

void StateManager::bindBuffer(GLenum bufType, GLuint bufID) {
    if (this->m_currentBuffer[bufType] != bufID) {
        glBindBuffer(bufType, bufID);
        this->m_currentBuffer[bufType] = bufID;
    }
}

这种方法的想法是,如果bufID已经绑定,则不会执行昂贵的glBindBuffer调用。这种方法是否值得一试?我认为OpenGL很可能已经实现了这样的优化,但我现在看到在几个项目中都使用了这种模式,所以我对此产生了疑问。我只是感兴趣,因为这将是一个很简单的实现,但如果它对性能没有太大/任何影响,那么我将跳过它(避免过早优化)。


如果你要实现这个功能,你应该正确地实现它。没有单一的“当前缓冲区”;每个bufType都有自己的当前缓冲区。 - Nicol Bolas
当然,但是这个想法仍然存在。不过我会编辑帖子的。 - 64_
曾经我问过一个类似的问题,但是对答案并不十分满意,所以没有标记为重复。http://stackoverflow.com/questions/22094960/storing-opengl-state - lisyarus
@BDL 谢谢,很有趣。 - 64_
就我个人而言,如果驱动程序在这种情况下不执行此操作以更快的速度执行,我会感到非常惊讶,因为如果通过平均检查可以使其快1%,那么他们现在比竞争对手快1%,否则他们将一直保持不变。这是一个简单而快速的性能优化...如果不是这样,那么执行它也不会带来太多好处。 - jcoder
显示剩余2条评论
2个回答

5

这高度依赖于平台和供应商。

你询问是否“OpenGL会实现...”。如你已经了解,OpenGL是一个API规范。有许多不同的实现,它们是否检查冗余状态更改完全取决于实现决策,这可能会因实现而异。

你甚至不应该期望给定的实现对所有状态片段都采用相同的处理方式。

由于这个话题与我的过去经验有些相关,我很想写一篇小论文,包括一些抱怨。但我认为这并不适合在这里,所以下面只列出了一些可能影响特定情况下给定OpenGL实现是否测试冗余状态更改的考虑因素:

  • 实际更改状态的代价有多大?如果非常便宜,则检查冗余更改可能根本不值得。
  • 检查冗余更改的代价有多大?通常不多,但我们正在观察每个小细节都很重要的软件片段。
  • 重要的应用程序/基准测试是否经常冗余更改此状态?
  • 应用程序责任与OpenGL实现责任的哲学如何?

是的,这对每个人都不利。对于你作为应用程序编写者而言,你想要在各个供应商/平台上获得理想的性能,真的没有简单的解决方案。如果你在代码中添加检查,它们将在OpenGL实现中拥有相同的检查时变得毫无用处,并增加额外的开销。如果您在代码中没有检查,并且不能轻松避免首先进行这些冗余状态更改,则可能会在OpenGL实现不检查的平台上表现较差。


我明白了。这基本上是我所怀疑的。顺便问一下,您知道DirectX或Vulkan是否存在类似的问题吗?我认为它们必须存在,因为实现具体性。 - 64_
@user3183526:Vulkan是低级别的。如果您将某些状态绑定到命令缓冲区,则应该预计会产生绑定成本。 - Nicol Bolas

3
状态缓存是个坏主意的原因很简单:你做错了。你总是处于错误的危险之中。
当然,你纠正了我指出的错误,即不同的缓冲绑定有不同的状态。也许你正在使用一个哈希表,使查找非常快,即使在你编写缓存时不存在的新扩展adds a new buffer binding point出现。
但这只是关于对象绑定特性的冰山一角。
例如,你是否意识到GL_ELEMENT_ARRAY_BUFFER实际上不是上下文状态?它真正的是VAO状态,每次绑定新的VAO时,该缓冲绑定都会发生变化。因此,你的VAO缓存现在还必须改变阴影元素缓冲绑定。
另外,你是否知道删除一个对象会自动将其从当前绑定到上下文的任何绑定点解除绑定?即使是附加到另一个绑定到上下文的对象的对象也是如此;被删除的对象会自动分离。

但是这只适用于 某些对象类型,而且即使是这样,它也仅适用于在删除对象时的当前上下文。其他上下文将不受影响。

我的观点是:正确缓存状态非常困难。如果您做错了,将会在应用程序中创建大量非常微妙的错误。但如果您只是让OpenGL完成它的工作并构造代码以使多个绑定不发生,那么您就没有问题。


我明白了。但是当前绑定的GL_ELEMENT_ARRAY_BUFFER会在绑定不同的vao时自动绑定,对吗?所以在这种情况下,你不会得到任何速度提升,但也不会出现任何错误。在第二个例子中,是否可以将当前缓存设置为无效数字(例如对于缓冲区为0),以便在删除某些内容时绑定下一个对象。无论如何,如果未针对指定的缓冲区类型执行此操作,则最坏的情况是将其绑定多几次,这并不是什么大问题,对吧? - 64_
@user3183526:“在这种情况下,你没有获得任何速度提升,但也没有遇到任何错误。” 假设您在绑定缓冲区A时绑定了VAO Q。然后您切换到使用缓冲区B的VAO R。然后您尝试绑定缓冲区A。好吧,您的缓存认为A已经被绑定,因此不会再次绑定它。这意味着VAO R仍在使用缓冲区B,而您想要将其更改为使用缓冲区A。 - Nicol Bolas
不可能只是在删除某些内容时设置...吗?” 你可以这样做。但我的总体观点是:在我提到这个问题之前,你是否意识到了它的存在?你如何确定已经发现了所有这些问题?如果你不是确定,那么它就不可靠。破损的代码比运行缓慢的代码更糟糕。 - Nicol Bolas

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