在英特尔显卡上,glTexSubImage2D非常缓慢

10

我的显卡是Mobile Intel 4 Series。我正在更新一个纹理,每帧更改一次数据,这是我的主循环:

for(;;) {
    Timer timer;

    glBindTexture(GL_TEXTURE2D, tex);
    glBegin(GL_QUADS); ... /* draw textured quad */ ... glEnd();
    glTexSubImage2D(GL_TEXTURE2D, 0, 0, 0, 512, 512,
        GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
    swapBuffers();

    cout << timer.Elapsed();
}

每次迭代需要120毫秒。但是,在glTexSubImage2D之前插入glFlush可以将迭代时间缩短到2毫秒。

问题不在像素格式上。我已经尝试了像素格式BGRA、RGBA和ABGR_EXT以及像素类型UNSIGNED_BYTE、BYTE、UNSIGNED_INT_8_8_8_8和UNSIGNED_INT_8_8_8_8_EXT。纹理的内部像素格式为RGBA。

调用顺序很重要。例如,将纹理上传移动到四边形绘制之前可以修复性能问题。

我还在GeForce GT 420M卡上尝试过这个问题,并且它在那里运行得很快。我的真实应用程序在非Intel显卡上存在性能问题,通过glFlush调用可以解决这些问题,但我还没有将它们提炼成一个测试用例。

有什么想法可以调试这个问题吗?


当然在GT420上它运行得很快。毕竟它是GT420。但是让它在GMA上运行更快,甚至nVidia也会(应该)更快。 - Calvin1602
1
@genpfault:Windows 7 SP1,驱动程序版本8.15.10.2281。 - Stefan Monov
有没有关于如何调试这个问题的想法?这里没有错误。性能无法保证。没有什么可以调试的。这只是英特尔图形卡和OpenGL驱动程序的一个怪癖,不过如此而已。你找到了一种使它更快的方法,所以你应该使用它。 - Nicol Bolas
@Nicol:嘿,我们不要纠结于“bug”这个词 :) 如果出现没有明显原因的减速,那么我想知道是什么原因导致的,以便避免它。 - Stefan Monov
1
@StefanMonov:你假设存在一个合理的原因,然后在将来避免它。实际上并不一定如此,因为NVIDIA(和可能的ATI)显卡没有这个问题。而且由于你正在处理英特尔臭名昭著的OpenGL驱动程序,更不太可能存在逻辑上的原因。在这种情况下,你能做的最好的事情就是认识到你做了一些导致性能异常的事情,尝试重新排列你的代码以使其不发生,并记录你所做的事情以备将来参考。 - Nicol Bolas
3个回答

5
一个问题是glTexImage2D会对纹理对象进行完全重新初始化。如果只有数据发生变化,但格式保持不变,则使用glTexSubImage2D以加快速度(只是提醒)。
另一个问题是,尽管其名称为立即模式,即glBegin(…)…glEnd()的绘图调用不是同步的,即调用在GPU完成绘图之前很长时间返回。添加glFinish()将进行同步。但是,任何修改仍需要排队操作的数据的调用也将执行。因此,在您的情况下,glTexImage2D(和glTexSubImage2D)必须等待绘图完成。
通常最好在绘图函数的开头或通过缓冲对象在单独的线程中在SwapBuffers块期间执行所有易失性资源上传。缓冲对象出现的原因正是为了允许异步但紧密的操作。

顺便说一下,他一直在使用glTexSubImage2D - Christian Rau
@ChristianRau:谢谢...啊,我不应该在工作时在StackOverflow上回答问题,太容易分心了 ;) - datenwolf
谢谢,我没有意识到glTexSubImage2D需要等待绘制完成。有用的信息。然而,这仍然不能导致从2ms到120ms的减速。 - Stefan Monov

3

我猜您实际上是在为一个或多个四边形使用该纹理?

上传纹理是最昂贵的操作之一。由于您的纹理数据每帧都会发生变化,因此上传是不可避免的,但您应该尝试在着色器不使用纹理时进行上传。请记住,glBegin(GL_QUADS); ... glEnd();并不实际绘制四边形,它请求GPU渲染四边形。在渲染完成之前,纹理将被锁定。根据实现方式,这可能会导致纹理上传等待(类似于glFlush),但也可能会导致上传失败,这种情况下,您已经浪费了几兆字节的PCIe带宽,并且驱动程序必须重试。

听起来您已经有了一个解决方案:在帧开始时上传所有新纹理。那么您的问题是什么?

注意:英特尔集成图形无论如何都非常慢。


谢谢,一开始上传纹理听起来很合理。至于“我的问题是什么”,我想知道在显卡内部发生了什么导致从2ms到120ms的减速。你提到的“上传失败”是新的对我来说很有趣,但我不认为它可以解释这样的减速。 - Stefan Monov

1

当您进行绘制调用(glDrawElements,其他)时,驱动程序会将此调用简单地添加到缓冲区中,并在GPU可以消耗这些命令时让其执行。

如果必须在glSwapBuffers时完全消耗此缓冲区,则意味着GPU在此之后处于空闲状态,等待您发送新的命令。

驱动程序通过让GPU滞后一帧来解决这个问题。这是glTexSubImage2D阻塞的第一个原因:驱动程序等待GPU不再使用它(在上一帧中)才开始传输,以便您永远不会获得半更新的数据。

另一个原因是glTexSubImage2D是同步的。它也会在整个传输过程中阻塞。

  • 您可以通过保留两个纹理来解决第一个问题:一个用于当前帧,一个用于上一帧。将纹理上传到前者,但使用后者进行绘制。
  • 您可以通过使用GL_TEXTURE_BUFFER缓冲对象来解决第二个问题,该对象允许异步传输。
在您的情况下,我怀疑在 glSwapBuffer 之前调用 glTexSubImage2D 会在驱动程序中添加额外的同步,而在 glSwapBuffer 之前绘制四边形只是将命令附加到缓冲区中。120ms 可能是驱动程序的错误,即使是 Intel GMA 也不需要 120ms 来上传一个 512x512 的纹理。

是的,我可能会执行“2个纹理”的优化,因为听起来不错,但我更感兴趣的是找出导致我在问题中提到的巨大减速的原因。不能仅仅归咎于同步。谢谢。 - Stefan Monov
等一下,我觉得你的技巧实际上不会加快速度。glTexSubImage2D在数据传输完成之前是不会返回的,对吗?所以,当它返回时,数据已经传输完毕了。然后绘制就不需要等待任何东西了。我漏掉了什么? - Stefan Monov
glTexSubImage2D 在传输期间会阻塞,是的。但是如果没有双缓冲,它在 GPU 使用它来渲染上一帧的内容时也会阻塞。 - Calvin1602
“它还会在GPU使用它来渲染上一帧的内容时阻塞。” - 我不明白。上一帧已经结束了,它已经被swapBuffers清除了。你能为我分解一下这个解释吗? - Stefan Monov

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