由于性能特征经常出现这种情况,因此没有简单的答案。它在很大程度上取决于硬件架构、驱动程序优化和使用条件。
首先为您提供 tl;dr:切换渲染表面可以在相当便宜和非常昂贵之间进行。我的建议如下:
- 尝试各种方法,并在您关心的所有平台上进行基准测试。
- 如果您无法执行选项 1,但您仍希望确信您的代码将在各种架构中表现良好,请按渲染目标对渲染进行分组,并避免不必要的切换。
我不愿给出每帧多少个开关是无害的数字。主要是因为我没有它们,而且我不喜欢猜测。因为它取决于很多因素。我从一个通常非常可靠的来源得知,在至少一个平台上,每帧仅 2 或 3 个交换机就会对性能产生非常大的负面影响。除了这个非常糟糕的情况,我的直觉会告诉我,我会尽量避免切换超过 10-100 次。但这实际上只是一个猜测,您绝对有可能获得更多,特别是如果您的目标是一组有限的硬件。
您的问题听起来像是涵盖了两种不同的情况。让我分开讨论:
冗余绑定调用
根据您的描述,您似乎有部分使用模式:
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glDraw...(...);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glDraw...(...);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
在这种情况下,您进行了glBindFramebuffer() 调用,但您的所有渲染都转到同一个帧缓冲区。我希望大多数驱动程序能够检测到这些绑定调用是多余的,并且不会做任何严肃的工作。尽管有时对于驱动程序是否应该检测冗余状态变化存在哲学争议,但大多数情况下都会这样做。
在这种情况下,这取决于您对 GPU/驱动程序供应商的信任程度。除非我对它进行了基准测试,否则在这种情况下我倾向于偏执。如果在我的软件架构中有任何合理的方法可以做到这一点,我会避免冗余调用。
实际的帧缓冲开关
正如我在介绍中提到的,这里发生的事情高度依赖于 GPU 和驱动程序。只是将状态切换为点渲染到不同的目标是很便宜的。但它可能还有更多。
您经常有与活动渲染目标相关联的额外内存分配。典型示例包括用于早期深度测试的缓冲区和压缩颜色缓冲区。当您切换到不同的渲染目标时,这些分配会发生什么取决于硬件架构、驱动程序实现以及可能的其他条件:
- 只要有足够的空间,就可以为循环通过的所有渲染表面保持这些分配有效,并随着实际的渲染目标切换在它们之间切换。在这种情况下,开销会很小。
- 如果这些分配位于片上内存中,则空间可能非常有限。如果没有足够的空间将它们全部保留在芯片上,则旧表面的分配可能会被驱逐到视频内存(如果 GPU 有的话)或系统内存,然后重新加载新表面的分配。这可能有点贵。
- GPU/驱动程序可能不支持驱逐和重新加载这些分配,并且可能必须将它们的内容解析为实际缓冲区(例如,扩展压缩颜色缓冲区的内容,并将其写回完整的颜色缓冲区)。这很昂贵。
平铺架构让事情变得更加有趣,它以各种形式在移动设备上得到了非常广泛的应用。平铺架构的关键卖点是它们每个像素只能运行一次片段着色器,并且必须将每个平铺只写入帧缓冲区一次,这减少了写入帧缓冲区的整体内存带宽,也大大提高了这些写入的局部性因为一次写入整个图块。
据我所知,用于存储将为每个图块渲染的三角形的图块内存通常是片上内存。因此,如果您切换帧缓冲区,则必须:
- 执行渲染每个图块的整个过程,并将结果写回帧缓冲区。
- 将旧曲面的切片内存保存到系统内存中,并为新曲面加载之前保存的切片内存。
我不知道哪种方法最常用(如果我知道,我可能无法分享)。但它们听起来都非常昂贵,如果过于频繁地使用基于 tile 的架构,那么它的整个目的就会失效。