glGetError和性能

时间:2015-05-31 16:12:06

标签: c++ performance opengl opengl-es

背景

在工作中,我们开发了两款产品,它们都有OpenGL 3.x +和GLES 2.0 / 3.0 +后端。团队是独立的,但确实有一些重叠,我们最近讨论了glGetError的表现。

在这两种产品中,设计都是这样的,即GL调用不应生成由glGetError记录的错误代码。为了检测这样的错误,在调试中我们有一个在每次GL调用后添加glGetError的宏,并且如果检测到任何错误它会断言,因为这意味着存在错误。在我的产品上,默认情况下启用此功能,另一方面,必须明确启用它。

这些已存在于我工作多年的产品的代码库中,我们发现它们会导致性能下降,通常在许多平台上都会达到25%左右。我们已经确定这是一个合理的价格,以便及早发现错误。另一个团队在某些情况下声称,添加这些检查会减慢他们在60FPS运行的产品的执行速度。 1FPS,使产品无法使用,这就是默认情况下不启用它们的原因。这两款产品均可在许多OpenGL / GLES平台(PC,OSX,Linux,iOS和Android)上运行。

问题

我理解glGetError降低性能背后的原因;您(可能)需要CPU / GPU同步才能使上一个操作的状态正确。根据我的理解,这应该将预期的帧时间从“MAX(CPU time, GPU time)”(假设没有其他同步点,没有排队的帧)更改为“CPU time + GPU time + synchronization overheap”(假设每个 glGetError调用导致同步点)。这是不正确的推理,还是使用glGetError还有其他原因可以降低性能?

我一直认为调试中的每次调用glGetError是合理的事情(至少在没有错误的GL调用之后)。是不是这种情况还是不被视为“最佳做法”? 是否存在某些可能导致极端性能问题的情况,例如其他团队描述的问题(例如,使用特定的GL调用和/或平台)?

3 个答案:

答案 0 :(得分:10)

glGetError()并不需要等待来自GPU的任何内容。它报告的所有错误都来自检查API调用的参数,以及驱动程序管理的内部状态。所以CPU / GPU同步不会在这里发挥作用。

可能出现延迟的唯一错误是GL_OUT_OF_MEMORY,但规范相对于此规范相当开放(“可能会生成”),因此它也不是同步的原因。

我可以想到在每次API调用后调用glGetError()可能会显着降低性能的两个原因:

  • 您进行两次OpenGL调用。调用本身以及检查和返回错误状态都有开销。虽然拨打glGetError()一次可能不会非常昂贵,但如果你拨打它数百万次就会增加。
  • 某些驱动程序在驱动程序中使用多线程。在这种情况下,glGetError()将导致驱动程序中的线程之间的同步,如果它经常发生,可能会对性能产生非常大的影响。

关于你应该做什么,你真的必须找出有效的方法。一些想法/建议:

  • 我绝对不会在发布版本中调用glGetError()。它在调试期间非常有用,但一旦您的测试/ QA完成,就会产生不必要的开销。
  • 错误很棘手。因此,如果您只想知道是否有任何错误,则无需在每次通话后拨打glGetError()。例如,您可以在每帧结束时调用一次。当然,如果您收到错误,并且想知道哪个呼叫导致它,则需要更频繁的呼叫。所以你可以拥有多种构建类型。

    • 在没有glGetError()来电的情况下发布版本。
    • 在每个帧结束时使用glGetError()调用进行测试/质量检查构建。
    • 在每次OpenGL调用后调用glGetError()调用构建。

答案 1 :(得分:4)

查询错误状态可能需要某种CPU / GPU同步,但我认为它过分了。它完全没有什么比回读渲染操作的结果还是在飞行中或待处理。错误状态是在执行命令之前验证和设置的,它通常会提醒您无效的API使用或状态设置,但不是其他。

现代OpenGL实现具有更复杂的扩展/核心功能,用于跟踪调试信息,简称为"Debug Output"。您已经标记了此OpenGL以及OpenGL ES,因此它可能不适合您软件的所有部署,但是当使用具有此功能的OpenGL或ES实现时,它应该是您的解决方案。您当然会得到错误信息,但是还会收到关于弃用和性能的警告(这实际上取决于驱动程序的详细程度,并且我看到一些驱动程序提供了非常好的警告,其他驱动程序根本没有使用该功能)。

您可以同步运行调试输出,这可能会引入您在问题中讨论的性能损失,或者异步,这往往会提高性能,但在尝试实时跟踪问题原因时稍微有点用处。没有一个适合所有解决方案,这就是为什么调试输出比glGetError (...)更加灵活和明确。

答案 2 :(得分:3)

好吧,在这种情况下,我会考虑触发完整的CPU / GPU同步(但并非不可能)。 GPU对GL客户端错误一无所知,并且GPU将使用的所有资源都由CPU管理,因此在GPU可以报告的这一点上没有太多可能出错的地方。通常情况下,如果由于某些用户错误而导致GPU侧出现“错误”,则结果只是未定义,但不会触发GL错误。

话虽如此,我不想暗示glGetError电话的开销很低。现代GL实现是多线程的。通常,GL调用自己只会将命令和数据转发到后台的其他工作线程,并尽可能早地返回,以便应用程序继续运行。查询错误意味着您必须与所有这些工作线程同步,这可能会显着落后。

  

是否存在可能导致极端性能的某些情况   诸如其他团队描述的问题

嗯,报告的性能影响是绝对可能的。但试图找出究竟是什么触发这将是非常困难的。我不知道错误检查非常糟糕的任何特定条件,我怀疑可以为这些事情导出一套简单的经验法则。复杂性太高了。

当您要求最佳实践时,我们进入opionion区域。它将始终取决于具体方案。每次GL调用后我都没有进行错误检查。我在“战略位置”进行了一些错误检查,通常在资源设置时启用,但从不在“快速路径”中。此外,我曾经在调试版本中默认启用“策略”位置进行额外检查。我还经常有一些额外的marco来进行更多的检查,以便轻松缩小发生的错误。

然而,随着时间的推移,这些检查变得越来越不实用。如今,有GL调试工具可以帮助您识别失败的GL呼叫。

另一个非常有用的概念是由ARB_debug_outputKHR_debug扩展引入的调试上下文(后者也被定义为GLES扩展,但我不知道它有多广泛可用)。这基本上允许设置GL将调用的回调,因此错误的“轮询”被通知机制替换。我强烈建议在调试版本中使用调试上下文(当然,如果可用)。甚至在发布版本中也可以选择性地启用它们甚至是一个好主意,因为它可能有助于在客户系统上进行调试,而只要它被禁用就会引入严重的开销。