超出最大FPS

时间:2015-02-09 22:52:21

标签: java frame-rate

我正在尝试计算我的程序的FPS。虽然给定的最大FPS超限,但我无法解释原因。我将MAX_FPS设置为60进行了测试,并且我总是显示63

{...}
int frameCount = 0;
long time = System.currentTimeMillis();
while(running) {
    try {
        {...} // do something
        Thread.sleep(1000 / MAX_FPS);
        frameCount++;
        if (System.currentTimeMillis() - time >= 1000) {
            {...} // display FPS w/ frameCount
            frameCount = 0;
            time = System.currentTimeMillis();
        }
    } catch (InterruptedException ex) {
        System.err.println(ex.toString());
    }
}
{...}

3 个答案:

答案 0 :(得分:2)

1000/60在整数数学中是16,而1000/16是大约62.5。假设线程睡眠时间接近16毫秒(可能稍微少一些),并且如果你的循环没有花那么长的时间来执行迭代,并且倒数第二帧的时间是999毫秒,允许另一个更新潜入那么循环经常可以每秒实现63次迭代。

答案 1 :(得分:2)

您可能希望改用System.nanoTime()。不是为了任何额外的精度,而是因为它是一种更准确的测量经过时间的方法。请参阅Kevin Bourillion对How do I measure time elapsed in Java?的回答。

答案 2 :(得分:1)

根据NESPowerGlove的回答,你达到目标FPS时遇到的问题是你必须选择一个整数值来调用Thread.sleep() - 如果你选择16,你就是'得到大约63fps;如果你选择17,你将获得58fps左右。

我可以想到两个解决方案:

  1. 实施某种负反馈循环来控制FPS。例如,我发现以下PI(比例+积分)控制器使FPS 保持在 60周围:

    final long sleepTime = (long) (1000.0 / TARGET_FPS);
    
    System.out.println("Sleep time is " + sleepTime);
    long time = System.nanoTime();
    long delta = 0;
    double cumulativeFpsError = 0.0;
    for (int frameCount = 0; true; ++frameCount) {
      Thread.sleep((long) (sleepTime + delta));
      long elapsed = System.nanoTime() - time;
      if (elapsed >= 1_000_000_000) {
        double fps = (double) frameCount / elapsed * 1e9;
        System.out.println(fps);
    
        cumulativeFpsError += (fps - 60);
        delta += (fps - TARGET_FPS) * 0.55 + 0.14 * cumulativeFpsError;
        frameCount = 0;
        time += elapsed;
      }
    }
    
  2. 0.550.14的值是通过反复试验找到的。可能有更好的价值(但至少看起来大致稳定)。 fps输出:

    61.08042479827653
    61.817816897275485
    58.42717726642977
    62.0654826347193
    58.43759367657694
    62.07263954479402
    58.444556146850026
    62.05489635777375
    58.4438970272065
    62.05784933619571
    58.45590905841833
    62.069491426232766
    58.44381852435569
    62.07438904528996
    ...
    

    实际上,积分项根本没有太大作用 - 大概是因为睡眠值只能以1号的步长变化。

    1. 使用不同的方法睡眠更精确的时间。例如,How to suspend a java thread for a small period of time, like 100 nanoseconds?提出了一些替代方案,例如轮询System.nanoTime(),直到超过某个截止日期。 e.g。

      long time = System.nanoTime();
      for (int frameCount = 0; true; ++frameCount) {
        long end = System.nanoTime() + (long) (1_000_000_000.0 / TARGET_FPS);
        while (System.nanoTime() < end);
        long elapsed = System.nanoTime() - time;
        if (elapsed >= 1_000_000_000) {
          double fps = (double) frameCount / elapsed * 1e9;
          System.out.println(fps);
      
          frameCount = 0;
          time = System.nanoTime();
        }
      }
      
    2. 这似乎效果更好,FPS徘徊在60以下:

      58.99961555850502
      59.99966304189236
      59.99942898543434
      59.99968068169941
      59.99968770162551
      59.99919595077507
      59.99945862488483
      59.999679241714766
      59.99753134157542
      59.99963898217224
      59.999265728986
      ...
      

      这样做的缺点可能是while循环会很热。

      当然,您可以采用某种混合方法,在大多数等待时使用Thread.sleep,然后使用热while循环来获得更精确的延迟。