Transparent stages and AnimationTimer on Windows
John Hendrikx
hjohn at xs4all.nl
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.
--John
public class FrameRateTest extends Application {
private static int SIZE = 300;
@Override
public void start(Stage stage) {
System.out.println("javafx.runtime.version: " +
System.getProperties().get("javafx.runtime.version"));
Label fpsLabel = new Label();
fpsLabel.setTextFill(Color.ALICEBLUE);
Canvas canvas = new Canvas(SIZE, 100);
GraphicsContext g2d = canvas.getGraphicsContext2D();
g2d.setStroke(Color.WHITE);
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();
@Override
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) {
elementsFilled++;
}
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;
}
};
frameRateMeter.start();
fpsPane.setBackground(new Background(new
BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
Scene scene = new Scene(fpsPane);
scene.setFill(Color.BLACK);
stage = new Stage(StageStyle.TRANSPARENT);
stage.setWidth(1500);
stage.setHeight(1500);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
More information about the openjfx-dev
mailing list