Transparent stages and AnimationTimer on Windows

John Hendrikx hjohn at
Tue Sep 28 22:46:43 UTC 2021

I'm seeing some really weird issues with Transparent Stages on Windows 
when their framerate (both stability and speed) is compared with normal 
decorated/undecorated stages.

The transparent stages seem to obey different rules when it comes to 
timely firing AnimationTimers.

Where a decorated stage will show an almost exact 60 Hz in all 
circumstances (usually in a pattern of 24 frames of ~15ms then a double 
frame of ~30ms averaging out to 1/60th of a second per frame) the 
transparent stages act completely different.  Increasing the 
javafx.animation.pulse on a decorated stage to say 240 will reduce this 
variance even further (min/max will be between 16 and 18 ms) while 
keeping an exact 60 Hz.

However, on a transparent stage this all goes out the window:

Running a test with javafx.animation.pulse left unchanged, it reaches an 
unsteady 56/57 Hz with the variance being incredibly high and random (5 
- 40 ms)... while FOCUSED.  Unfocusing the stage and this becomes a more 
steady 62.5 Hz and the variance reduces to 8 - 24 ms. Refocusing the 
stage immediately sees variance spiking again and framerate dropping to 
an unsteady 56/57 Hz.

The same test with javafx.animation.pulse set to 240 on a transparent 
stage shows some odd things as well.  While focused the stage retains a 
similar behaviour (unsteady 56/57 Hz with a 5-40 ms variance).  When 
unfocused it starts running at 72-78 Hz with about an 8-24 ms variance 
(same as before but with a higher frame rate).

Setting javafx.animation.pulse to 24 for a transparent stage locks the 
framerate to an exact 24 Hz with almost no variance.  Focused/unfocused 
suddenly makes no difference any more. For decorated/undecorated stages 
this does not change the framerate (it remains rock solid at 60 Hz) but 
the variances become much larger (but show a steady repeated pattern).

So in summary:

1) Decorated/undecorated stages perform very stable, variance can be 
reduced by increasing javafx.animation.pulse -- this property seems to 
have the effect of making the animation timings more accurate (reduces 
variance) while retaining an exact 60 Hz. The variance pattern is very 
consistent and predictable.

2) Transparent stages perform much more erratic, and are extra erratic 
when they have focus.  They never reach an exact 60 Hz (measured over 
300 frames) but either go several frames under (57.5 Hz when focused) or 
over (62.5 Hz when not focused). Their variance has no pattern to it and 
seems random.

3) javafx.animation.pulse for Transparent stages seems to control the 
maximum frame rate (setting it to 24 for example will limit the fps to 
almost exactly 24 Hz with almost no variance).

Below is the code I used to test this.


public class FrameRateTest extends Application {
   private static int SIZE = 300;

   public void start(Stage stage) {
       System.out.println("javafx.runtime.version: " + 

       Label fpsLabel = new Label();

       Canvas canvas = new Canvas(SIZE, 100);

       GraphicsContext g2d = canvas.getGraphicsContext2D();

       StackPane fpsPane = new StackPane(fpsLabel, canvas);

       AnimationTimer frameRateMeter = new AnimationTimer() {
         private final long[] frameTimes = new long[SIZE];
         private final long[] frameDeltas = new long[SIZE];

         private int frameTimeIndex = 0;
         private int elementsFilled = 0;
         private long then = System.nanoTime();

         public void handle(long now) {
           long oldFrameTime = frameTimes[frameTimeIndex - 
elementsFilled % SIZE];
           long delta = now - then;

           frameTimes[frameTimeIndex] = now;
           frameDeltas[frameTimeIndex] = delta;
           frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length;

           if(elementsFilled < SIZE) {

           long elapsedNanos = now - oldFrameTime;
           long elapsedNanosPerFrame = elapsedNanos / elementsFilled;
           double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame;
           double min = Double.MAX_VALUE;
           double max = 0;

           for(int i = 0; i < elementsFilled; i++) {
             min = Math.min(min, frameDeltas[i]);
             max = Math.max(max, frameDeltas[i]);

           GraphicsContext g2d = canvas.getGraphicsContext2D();

           g2d.clearRect(frameTimeIndex, 0, 1, 100);
           g2d.strokeLine(frameTimeIndex, 100, frameTimeIndex, 100 - 
(delta) / 1000000.0);

           fpsLabel.setText(String.format(" %.1f/%.1f ms - %.1f fps", 
min / 1000000.0, max / 1000000.0, frameRate));

           then = now;


       fpsPane.setBackground(new Background(new 
BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));

       Scene scene = new Scene(fpsPane);
       stage = new Stage(StageStyle.TRANSPARENT);

   public static void main(String[] args) {

More information about the openjfx-dev mailing list