10

This is quite a simple question:

What is the preferred way of getting the frame rate of a JavaFX application?

Google turns up a result from 2009, but that example pertains to JavaFX 1.x and operates in a weird way (some sort of external meter) to begin with. Not being able to find a better example, I am posting here.

I want to be able to query my JavaFX application (or if necessary, the current scene) and get what the current FPS is.

Update: Feb. 8th 2015

Various solutions to the problem are posted below as answers. I also found that this question was referenced by the following blog post: http://tomasmikula.github.io/blog/2015/02/08/measuring-fps-with-reactfx.html

Which says that (as a result of the verbosity of the solutions below) measuring FPS was added to ReactFX 2.0 milestone 2. Cool how things travel.

brcolow
  • 1,042
  • 2
  • 11
  • 33
  • There's a class called `com.sun.javafx.perf.PerformanceTracker` but I can't find the javadoc. I downloaded a bouncing ball program a while ago that uses it. – brian Feb 02 '15 at 22:41
  • 1
    There won't be any Javadoc, as it's not part of the public API. – James_D Feb 02 '15 at 23:11
  • "There won't be any Javadoc, as it's not part of the public API." - and it was the only reliable way to get FPS. It was removed under Java9 altogether and it did actually work. The naive applications given here don't get anywhere near the correct FPS. An animation call returning doesn't mean that the graphics card is managing to deliver what's being asked of it. By changing my screen resolution on a Java game, I can run via NVidia software, and via other performance monitors report anywhere between 130-300FPS. But naive implementations always show 250FPS - whatever the reality is. – wax_lyrical May 22 '18 at 08:25
  • @wax_lyrical I think you are looking for something entirely different to what the OP is asking. The OP, AIUI, is asking how frequently JavaFX is re-rendering the scene graph (so, e.g. you can monitor whether *your* rendering code is preventing the JavaFX toolkit from rendering at its nominal target of 60fps). Your external performance monitors are going to measure the performance of your graphics card, which is entirely different. – James_D May 22 '18 at 14:39
  • Hi James. He asks how to measure FPS. The normal interpretation of FPS is how many frames the GPU is rendering to the screen/sec. Surely you're not disputing that? Not how many times a loop is calls draw. The old PerformanceTracker used to agree (AFAIR) with my external monitors. If PRISM is pulsing at 60FPS, then that's what the OP should see. But if he has used -Djavafx.animation.fullspeed=true, one would expect him to get some agreement between internal and external metrics. All your metric is reporting is how often I run a loop? – wax_lyrical May 22 '18 at 17:38
  • The intent of the question was indeed to check how often JavaFX is re-drawing the frame. How that relates to what the GPU is actually doing is, IMHO, out of scope of the question. – brcolow Jan 05 '21 at 19:16

3 Answers3

14

You can use an AnimationTimer.

The AnimationTimer's handle method is called once on each frame, and the value passed in is the current time in nanoseconds (a best approximation). So you can track how long since the previous frame.

Here's an implementation that tracks the times of the last 100 frames and computes the frame rate using them:

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class SimpleFrameRateMeter extends Application {

    private final long[] frameTimes = new long[100];
    private int frameTimeIndex = 0 ;
    private boolean arrayFilled = false ;

    @Override
    public void start(Stage primaryStage) {

        Label label = new Label();
        AnimationTimer frameRateMeter = new AnimationTimer() {

            @Override
            public void handle(long now) {
                long oldFrameTime = frameTimes[frameTimeIndex] ;
                frameTimes[frameTimeIndex] = now ;
                frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length ;
                if (frameTimeIndex == 0) {
                    arrayFilled = true ;
                }
                if (arrayFilled) {
                    long elapsedNanos = now - oldFrameTime ;
                    long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
                    double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
                    label.setText(String.format("Current frame rate: %.3f", frameRate));
                }
            }
        };

        frameRateMeter.start();

        primaryStage.setScene(new Scene(new StackPane(label), 250, 150));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • With that code I get values between 68 and 73. I thought the AnimationTimer is supposed to run at a stable 60 fps? – Roland Feb 03 '15 at 01:08
  • 1
    I get stable values around 60... I don't see a bug but there could be one. Note the animation timer runs once per frame: JavaFX aims to run at 60fps but there is no guarantee as to the frame rate. Interestingly, with any method I've tried the frame rate *increases* when I resize the frame, which seems weird (but I guess not impossible). – James_D Feb 03 '15 at 01:10
  • 4
    I adapted this answer using ReactFX: http://tomasmikula.github.io/blog/2015/02/08/measuring-fps-with-reactfx.html – Tomas Mikula Feb 08 '15 at 05:05
  • Bug: arrayFilled never resets to false, so after the first time that is true, it will be true per each frame and not when the array is filled. – Yan.F Oct 16 '17 at 18:03
  • @Yan.F That's the intended behavior. The flag is supposed to indicate if the array has been filled at some point (which is all that is needed). – James_D Oct 16 '17 at 18:04
  • @James_D If you supposed to use the average once it should stop after the array is filled, if not you need to set the arrayFilled to false. – Yan.F Oct 16 '17 at 18:08
  • @Yan.F But I don't want to use the average once. I want it to start computing the average as soon as it has filled the array once, and then to update the value every time it reads a single timestamp subsequent to that. The logic is exactly what I want it to be. – James_D Oct 16 '17 at 18:10
3

I just copied James_D program and changed to using the PerformanceTracker. The options I copied from the program I had downloaded earlier called JavaFXBalls3. The options don't seem to make a difference.

Press any key to to see what the label says. Mine's always near 60. Maybe a more complicated scene would be lower. AFAIK 60 is the max for animation.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

import com.sun.javafx.perf.PerformanceTracker;
import java.security.AccessControlException;
import javafx.animation.AnimationTimer;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;


public class FPS extends Application {
    public static void main(String[] args) { launch(args); }

    private static PerformanceTracker tracker;

    @Override
    public void start(Stage stage) {
        VBox root = new VBox(20);
        Label label1 = new Label();
        Label label2 = new Label();
        root.getChildren().addAll(label1, label2);

        Scene scene = new Scene(root, 200, 100);

        try {
            System.setProperty("prism.verbose", "true");
            System.setProperty("prism.dirtyopts", "false");
            //System.setProperty("javafx.animation.fullspeed", "true");
            System.setProperty("javafx.animation.pulse", "10");
        } catch (AccessControlException e) {}

        scene.setOnKeyPressed((e)->{
            label2.setText(label1.getText());
        });
        stage.setScene(scene);
        stage.show();


        tracker = PerformanceTracker.getSceneTracker(scene);
        AnimationTimer frameRateMeter = new AnimationTimer() {

            @Override
            public void handle(long now) {
                label1.setText(String.format("Current frame rate: %.3f fps", getFPS()));
            }
        };

        frameRateMeter.start();
    }

    private float getFPS () {
        float fps = tracker.getAverageFPS();
        tracker.resetAverageFPS();
        return fps;
    }

}
brian
  • 10,619
  • 4
  • 21
  • 79
  • 1
    Thank you for that, though I am averse to using classes in the private api, it is nice to know that that class exists! – brcolow Feb 02 '15 at 23:08
  • I would think that api would only be used for testing. I'm not sure if it offers anything more useful. I can see the source code but not the javadoc. I think it just has a few helper methods, but nothing really crucial. – brian Feb 02 '15 at 23:15
  • Trying to compile very similar code gives me: `Error:(X, Y) java: package com.sun.javafx.perf is not visible (package com.sun.javafx.perf is declared in module javafx.graphics, which does not export it to the unnamed module)` So I guess this answer does not work anymore. – nhooyr Jan 21 '18 at 07:10
  • Indeed nhooyr, this private API class will not be visible if using a Java 9+ JDK with jigsaw (JPMS). You can add some command-line hackery to export it to, in your case, the un-named module but eventually this will not be possible and thus it is, IMO, best not to use private API classes. – brcolow Jun 20 '19 at 20:02
3

James_D gave a naive implementation that gives the instantaneous FPS and he suggested a more sophisticated approach. My attempt at that is the following:

public class FXUtils
{
    private static long lastUpdate = 0;
    private static int index = 0;
    private static double[] frameRates = new double[100];

    static
    {
        AnimationTimer frameRateMeter = new AnimationTimer()
        {
            @Override
            public void handle(long now)
            {
                if (lastUpdate > 0)
                {
                    long nanosElapsed = now - lastUpdate;
                    double frameRate = 1000000000.0 / nanosElapsed;
                    index %= frameRates.length;
                    frameRates[index++] = frameRate;
                }

                lastUpdate = now;
            }
        };

        frameRateMeter.start();
    }

    /**
     * Returns the instantaneous FPS for the last frame rendered.
     *
     * @return
     */
    public static double getInstantFPS()
    {
        return frameRates[index % frameRates.length];
    }

    /**
     * Returns the average FPS for the last 100 frames rendered.
     * @return
     */
    public static double getAverageFPS()
    {
        double total = 0.0d;

        for (int i = 0; i < frameRates.length; i++)
        {
            total += frameRates[i];
        }

        return total / frameRates.length;
    }
}
brcolow
  • 1,042
  • 2
  • 11
  • 33
  • A more efficient approach is just to store the actual time values (`now`) in a `long[]`. Then (once the array has been filled up once), just grab the old value and subtract it from the new value, and replace it. The difference between the two is the time for the last `n` frames. I'll update my answer... – James_D Feb 02 '15 at 23:48
  • You increase the index after the modulo operation => ArrayIndexOutOfBoundsException – Roland Feb 03 '15 at 01:24
  • I am not getting an exception. It works because I am using fetch then increment, not increment then fetch (i.e. index++ vs. ++index). – brcolow Feb 03 '15 at 01:26
  • I do, found the problem. You get it by calling getInstantFPS() in the brief event between your increment to 100 and the next modulo operation. It must never be the size of the array, ie 100. – Roland Feb 03 '15 at 01:33
  • Changing getInstantFPS() to: return frameRates[index % frameRates.length]; should fix it. Thank you for that catch! Will update my answer now. – brcolow Feb 03 '15 at 01:35