【问题标题】:FPS lock not preciseFPS锁定不精确
【发布时间】:2016-10-26 12:29:57
【问题描述】:

我正在制作游戏,并且正在实施FPS 上限。但它并没有真正起作用,因为它不是很精确。

这是我的代码

public static volatile int FPS_CAP = 60;

@Override
public void run() {
    long lastTime = System.nanoTime();
    double amountOfTicks = 60.0;
    double ns = 1000000000 / amountOfTicks;
    double delta = 0;
    long timer = System.currentTimeMillis(), lastRender;
    while (running) {
        long now = System.nanoTime();
        delta += (now - lastTime) / ns;
        lastTime = now;
        while (delta >= 1) {
            tick();
            delta--;
        }
        lastRender = System.currentTimeMillis();
        draw.render();
        draw.fps++;

        if (FPS_CAP != -1) {
            try {
                int nsToSleep = (int) ((1000 / FPS_CAP) - (System.currentTimeMillis() - lastRender));

                if (nsToSleep > 1 / FPS_CAP) {
                    Thread.sleep(nsToSleep);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        if (System.currentTimeMillis() - timer > 1000) {
            timer += 1000;
            draw.lastFPS = draw.fps;
            draw.fps = 0;
            // updates = 0;
        }
    }
}

应该将 FPS 上限设置为 60 FPS,但是结果是:

如您所见,它确实不准确,有时比 60 低很多,有时又高一点!有时达不到 60 的问题不是我的 PC 硬件,因为解锁时我得到 1700-2000 FPS。我希望它尽可能精确。

提前致谢。

【问题讨论】:

  • 嗯,这可能是因为System.currentTimeMillis 并不总是非常准确?虽然是以毫秒为单位的时间,但它的分辨率取决于操作系统,它可以以 10 或 15 毫秒的步长增加。在 60 fps 时,您说的是每帧 16.7 毫秒,这样可能会有所不同吗?
  • Java 不够精确,无法实现这种精确机制。所有渲染库实际上都依赖于 C 或 C++ 层来管理实时精度。
  • @Arjan 不可能是因为这个,因为当我将其上限设置为 100(即每帧 10 毫秒)时也不准确。
  • @GuillaumeF。有什么解决办法吗?
  • 建议“不要使用 Java...”是愚蠢的。有许多 Java 游戏库,应该指出一个可以很好地满足他的需求的库。我也很好奇新的 Java 8 时间库中是否有任何用处? docs.oracle.com/javase/8/docs/api/java/time/…

标签: java graphics2d frame-rate


【解决方案1】:

Java 不够精确,无法实现这种精确机制。所有渲染库实际上都依赖于 C 或 C++ 层来管理实时精度。

在您的情况下,最好的解决方法是避免使用Thread.sleep()

您可以依赖 Timer 事件在 TimerTask 期间运行更新。您的游戏每秒会有大约 60 次心跳。

如果 60fps 对您有好处,另一种解决方案是在渲染屏幕之前等待 VSync。大多数 LCD 屏幕为 60 或 120fps。 VSync 应该由您的图形库(swing、JavaFX 或其他)管理。

最后一个解决方案,您可以查看专用引擎(如 JMonkey)的代码作为参考。

【讨论】:

【解决方案2】:

首先,我看到您将System.currentTimeMilis()System.nanoTime() 混合使用并不是一个好主意,只使用其中一个。最好只使用System.nanoTime(),因为您的精度很高。

导致您的问题的原因是 Thread.sleep() 不够精确。所以你需要避免睡觉。把你的睡眠密码改成这个;

lastRender = System.nanoTime(); //change it to nano instead milli
draw.render();
draw.fps++;

if (FPS_CAP > 0) {
    while ( now - lastRender < (1000000000 / FPS_CAP))
    {
        Thread.yield();

        //This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
        //You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
        //FYI on some OS's this can cause pretty bad stuttering. 
        try {Thread.sleep(1);} catch(Exception e) {}

        now = System.nanoTime();
    }
}

关于如何启用VSYNC,你的应用需要全屏,每次渲染后你应该调用Toolkit.sync()

【讨论】:

  • 嗯,我知道没有完美的解决方案,所以好的,谢谢,现在非常准确,谢谢你说同步方法是全屏的。
  • 也放 Thread.sleep(0, 100);这样它就变得更加准确,并且仍然不会占用太多 CPU。
  • Thread.sleep(millis, nanos) 实际上检查 nanos 参数是否大于 500000 并相应地增加毫秒参数。所以你实际上是在调用Thread.sleep(0); :D 检查Thread.java 的源代码现在问题是Thread.sleep(int)native method。所以调用Thread.sleep(0); 不是在休眠,而是在等待本地调用完成。
  • @Onur 这并不完全正确。相关代码是这样的,if (nanos &gt;= 500000 || (nanos != 0 &amp;&amp; millis == 0)) {millis++;} sleep(millis);。您正在描述 OR 运算符左侧的条件,它确实是 false,但右侧部分是 true。所以Thread.sleep(0, 100); 使它也增加millis,它调用本机Thread.sleep(1)。其中,@DavidKenz 也意味着它并不比调用Thread.sleep(1); 更准确。
  • @Arjan 好吧,看来我真的不会改变:p,但感谢您的信息!
【解决方案3】:

JavaFX 基于pulse mechanism

脉冲是一个事件,它向 JavaFX 场景图指示它 是时候同步场景图上元素的状态了 棱镜。脉冲被限制为最大每秒 60 帧 (fps) 并在动画在场景图上运行时触发。甚至 当动画没有运行时,当有东西进入时会安排一个脉冲 场景图发生了变化。例如,如果一个按钮的位置是 改变,安排一个脉冲。

当触发脉冲时,场景图上元素的状态为 同步到渲染层。一个脉冲使应用 开发人员一种异步处理事件的方法。这个重要的 功能允许系统在脉冲上批处理和执行事件。

。 . .

Glass Windowing Toolkit 负责执行脉冲 事件。它使用高分辨率的本地定时器来制作 执行。

要了解脉冲机制的实现,最好学习JavaFX源代码。一些关键类是:

Timer.java WinTimer.java win/Timer.h win/Timer.cpp

以上链接适用于通用 Timer 类和特定于窗口的计时器实现。 JavaFX 源代码包括其他平台的实现,例如 OS X、GTK、iOS、Android 等。

一些实现(例如似乎是 OS X)允许 Timer 实现的 vsync 同步,而其他实现(例如似乎是 Windows)不允许 vsync 同步。不过,这些系统可能会变得复杂,所以我想在某些情况下,垂直同步可能是通过硬件图形管道而不是通过计时器来实现的。

Windows 本机代码基于timeSetEvent 调用。

默认情况下,JavaFX 帧速率上限为 60fps,尽管它是adjustable via undocumented properties

如果您没有使用 JavaFX(而且您似乎没有使用),您仍然可以检查 JavaFX source code 并在那里了解它的实现,以防您想移植任何概念或代码以用于您的应用。您还可以将 JavaFX 计时器机制硬塞到非 JavaFX 应用程序中,方法是使您的应用程序成为 JavaFX 应用程序类的子类或创建 JFXPanel 以启动 JavaFX 工具包,然后根据 AnimationTimer 实现您的计时器回调。

【讨论】:

  • 我不能使用未记录的属性,它只是不起作用,但可能是因为它没有记录对吗?
  • 可能,我想对他们的支持可能随时被取消。他们过去曾为我工作过。它们是特定于 JavaFX 的,因此它们仅适用于 JavaFX 应用程序。
猜你喜欢
  • 2016-07-23
  • 2018-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-28
相关资源
最近更新 更多