测量 fps 很困难。事实上,想要测量 fps 的不同人不一定想要测量相同的东西,这使得事情变得更加困难。所以问问自己这个问题。为什么你想要 fps 数字?
在我继续深入探讨所有陷阱和潜在解决方案之前,我确实想指出,这绝不是“现代显卡”特有的问题。如果说有什么不同的话,那就是过去的情况更糟,对于 SGI 类型的机器,渲染实际上发生在可能远离客户端的图形 Susb 系统上(如物理远程)。 GL1.0实际上是根据客户端-服务器来定义的。
无论如何。回到手头的问题。
fps,意思是每秒帧数,实际上是试图用一个数字来表达应用程序性能的粗略概念,这个数字可以与屏幕刷新率等直接相关。对于第一级的性能近似值,它做得还不错。一旦你想深入研究更细粒度的分析,它就会完全崩溃。
问题实际上是,就应用程序的“流畅感”而言,最重要的是您绘制的图片何时出现在屏幕上。第二件事也很重要,那就是从触发一个动作到其效果显示在屏幕上之间需要多长时间(总延迟)。
当应用程序绘制一系列帧时,它会在 s0、s1、s2、s3... 的时间提交它们,并最终在 t0、t1、t2、t3... 的时间显示在屏幕上。
为了感觉顺畅,您需要满足以下所有条件:
- tn-sn 不太高(延迟)
- t(n+1)-t(n) 很小(低于 30ms)
- 模拟增量时间也有一个严格的限制,我将在稍后讨论。
当您测量渲染的 CPU 时间时,您最终会测量 s1-s0 以近似 t1-t0。事实证明,这,一般,与事实相差不远,因为客户端代码永远不会“走得太远”(这是假设您一直在渲染帧。其他情况请参阅下文)。事实上,当 GL 试图走得太远时,它最终会阻塞 CPU(通常在 SwapBuffer 时间)。该阻塞时间大致是 GPU 相对于 CPU 在单帧上花费的额外时间。
如果您确实想测量 t1-t0,正如您在自己的帖子中提到的那样,查询更接近它。但是……事情从来没有那么简单。第一个问题是,如果您受 CPU 限制(意味着您的 CPU 不够快,无法始终为 GPU 提供工作),那么 t1-t0 时间的一部分实际上是 GPU 空闲时间。这不会被查询捕获。您遇到的下一个问题是,根据您的环境(显示合成环境、垂直同步),查询实际上可能只测量应用程序渲染到后台缓冲区所花费的时间,这不是完整的渲染时间(因为显示尚未被渲染)。当时就更新了)。它确实可以让您粗略地了解渲染需要多长时间,但也不会很精确。进一步注意,查询还受到图形部分的异步性的影响。因此,如果您的 GPU 在部分时间处于空闲状态,则查询可能会错过该部分。 (例如,假设您的 CPU 需要很长时间(100 毫秒)来提交帧。GPU 在 10 毫秒内执行完整帧。您的查询可能会报告 10 毫秒,即使总处理时间接近 100 毫秒...)。
现在,关于“基于事件的渲染”,而不是我到目前为止讨论的连续渲染。对于这些类型的工作负载,fps 没有多大意义,因为目标不是每秒绘制尽可能多的 f。 GPU 性能的自然指标是 ms/f。也就是说,这只是图片的一小部分。什么really重要的是从您决定要更新屏幕到发生更新所花费的时间。不幸的是,这个数字很难找到:它通常在您收到触发该过程的事件时开始,并在屏幕更新时结束(您只能使用捕获屏幕输出的相机来测量......)。
问题是,两者之间,CPU 和 GPU 处理之间可能存在重叠,或者没有重叠(甚至 CPU 停止提交命令和 GPU 开始执行命令之间存在一些延迟)。这完全取决于实施情况来决定。您能做的最好的事情就是在渲染结束时调用 glFinish 来确定 GPU 已完成处理您发送的命令,并测量 CPU 上的时间。该解决方案确实会降低 CPU 端的整体性能,如果您打算在...之后立即提交下一个事件,则可能还会降低 GPU 端的整体性能。
最后讨论“模拟增量时间的硬约束”:
典型的动画使用帧之间的增量时间来向前移动动画。主要问题是,对于完全平滑的动画,您确实希望在 s1 提交帧时使用的增量时间为 t1-t0 (这样当 t1 显示时,前一帧实际花费的时间确实是 t1 -t0)。当然,问题是您在提交 s1 时不知道 t1-t0 是什么……所以您通常使用近似值。许多人只使用 s1-s0,但这可能会崩溃 - 例如SLI 类型系统在不同 GPU 之间的 AFR 渲染中可能会出现一些延迟。您还可以尝试通过查询使用 t1-t0(或更可能是 t0-t(-1))的近似值。犯这个错误的结果很可能是在 SLI 系统上出现微卡顿。
最可靠的解决方案是“锁定到 30fps,并始终使用 1/30s”。它也是在内容和硬件上允许最小余地的一种,因为你have确保您的渲染确实可以在 33 毫秒内完成...但这就是一些控制台开发人员选择做的事情(固定硬件使其变得更简单)。