hotzst
hotzst

Reputation: 7526

JavaFX FPS cap at 60 FPS

There seems to be a common understanding that the JavaFX UI thread is caped at 60 updates per second(1, 2). It is my understanding that update means pulse.

A pulse is an event that indicates to the JavaFX scene graph that it is time to synchronize the state of the elements on the scene graph with Prism. A pulse is throttled at 60 frames per second (fps) maximum and is fired whenever animations are running on the scene graph. Even when animation is not running, a pulse is scheduled when something in the scene graph is changed. For example, if a position of a button is changed, a pulse is scheduled.

Source: https://docs.oracle.com/javafx/2/architecture/jfxpub-architecture.htm

To figure out what happens if there are for example more than 60 calls to Platform.runLater I wrote this small program:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class HighUITraffic extends Application {

    private int counter = 0;
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {


        StackPane root = new StackPane();


        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                long sheduleTime = System.currentTimeMillis();
                System.out.println("Task "+counter+" run at "+sdf.format(new Date(sheduleTime)));
                Platform.runLater(new RunInUI(counter));
                counter++;
            }
        };
        timer.schedule(task, 0, 10); // every 10ms => 100 per second

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    private static class RunInUI implements Runnable {
        private final int counter;

        public RunInUI(int counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            long executionTime = System.currentTimeMillis();
            System.out.println("Task "+counter+" executed in Application Thread at "+sdf.format(new Date(executionTime)));
        }
    }
}

My expectation were that either:

However what happened after some initial delay where the Runables queued up and where then all handled in one go by the UI thread, was that the two threads run in order:

Task 1281 run at 2016-01-25T18:37:00.269 Task 1281 executed in Application Thread at 2016-01-25T18:37:00.269 Task 1282 run at 2016-01-25T18:37:00.274 Task 1282 executed in Application Thread at 2016-01-25T18:37:00.274 Task 1283 run at 2016-01-25T18:37:00.279

And there were more than 60 calls of either thread during one second (I tried with 100 and 200, without any difference).

Therefore I am confused:

What I wanted to know in the first place is what does happen with Runables that are pushed on the UI thread if the thread has reached its cap limit. Are all Runables that have queued up executed in sequence in one run of the UI thread or is only one Runable executed and the rest has to wait? The second case would cause serious problems, when continuously pushing more Runables on the UI thread than that limit.

Upvotes: 2

Views: 2846

Answers (1)

James_D
James_D

Reputation: 209724

Pulses are capped at 60fps. Runnables submitted to the FX Application Thread are not.

As I understand it, there are two threads: the FX Application Thread, and the prism rendering thread. The FX Application Thread consumes Runnables from a queue and executes them. The prism rendering thread executes up to 60 times per second, synchronizing with the FX Application Thread (thereby temporarily preventing it from executing new runnables) and rendering the frame, then releasing the FX Application Thread.

Thus the FX Application Thread will execute your Runnable as soon as it can, and is not limited to one runnable every 1/60s; but it will not take a new runnable from the queue during a frame render. (User events are processed on the FX Application Thread in a similar manner to the Runnables you post.)

So there are a couple of ways to make bad things happen. One is to have code on the FX Application Thread (whether in an event handler or in a Runnable posted to Platform.runLater()) that takes a long time to execute. Doing this will block the prism rendering thread from synchronizing with the FX Application thread, meaning that the next frame cannot be rendered until the runnable is complete. Another is to bombard the FX Application Thread with too many Runnables, so that the queue is growing faster than they can be consumed. (Essentially, since the queue always has something available, the FX Application Thread becomes a busy thread in these circumstances, and there is no guarantee the prism rendering thread ever gets to run.)

So the direct answer to your question is that the Runnables are executed in the order they are posted, as promptly as they can be on a single thread. A frame rendering, which is capped at 60 fps, will temporarily stop consumption of the Runnables from the queue.

In pseudocode, I guess (just my intuition as to how this works, this is not based on the source code) the FX Application Thread looks something like

while (fxToolkitRunning()) {
    Runnable runnable = runnableQueue.take(); // block until something available
    acquireLock();
    runnable.run();
    releaseLock();
}

and the prism rendering thread looks something like:

while (fxToolkitRunning()) {
    while (! timeForNextFrame()) {
        sleep(timeUntilNextFrame());
    }
    acquireFXApplicationThreadLock();
    if (sceneNeedsUpdating()) {
        renderFrame();
    }
    releaseFXApplicationThreadLock();
}

Upvotes: 5

Related Questions