-
Bug
-
Resolution: Duplicate
-
P4
-
None
-
jfx17
-
x86_64
-
linux_ubuntu
ADDITIONAL SYSTEM INFORMATION :
Tested on Ubuntu 20.04. Works fine on Windows 11.
A DESCRIPTION OF THE PROBLEM :
It appears that on Ubuntu Linux, the frame rate is equal to 60 divided by the number of Stages containing an active animation, e.g. some graphics continuously being updated with an AnimationTimer. If only 1 Stage with animation is active, the frame rate is about 60FPS; for 2 Stages with both animations it is goes down to 30FPS; for 3 Stages with active animations: 20FPS; for 4 Stages with active animations: 15FPS; etc. etc.
The frame rate clearly changes when enabling/disabling all the animations in a Stage.
It is worth noting that the number of animated graphics in a Stage does not seem to affect the frame rate. The issue appears as soon as there is one animation.
The issue does not appear on Windows, tested on Windows 11 and Windows 10.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
See attached source code. Run it to create simple Stages with animations. Observe that when creating an additional Stage, the frame rate does not drop until adding and animating the first widget.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The expecting result can be observed when running on Windows, the creation of graphic object has an initial on the frame rate but quickly the frame rate goes back to 60FPS.
ACTUAL -
On Ubuntu Linux, the frame rate is equal to: 60 / N, where N is the number of Stages containing at least one active animation.
---------- BEGIN SOURCE ----------
package us.ihmc.example;
import java.util.concurrent.TimeUnit;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.NumberBinding;
import javafx.beans.value.ObservableBooleanValue;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class MultiWindowPerformanceDrop extends Application
{
@Override
public void start(Stage primaryStage) throws Exception
{
primaryStage.setScene(new Scene(newAnimationPane(), 720, 480));
primaryStage.show();
}
public static void main(String[] args)
{
launch();
}
/**
* Creates a new Stage with all the controls populated.
*/
public static void newStage()
{
Stage stage = new Stage();
Pane animationPane = newAnimationPane();
stage.setScene(new Scene(animationPane, 720, 480));
stage.setOnCloseRequest(e -> animationPane.getChildren().clear());
stage.show();
}
/**
* Creates a new root pane with controls and animations used for the test.
*/
public static Pane newAnimationPane()
{
FlowPane pane = new FlowPane();
// Button to add an animate-able widget to the pane
Button add = new Button("Add gizmo");
// Button to remove the last widget added.
Button remove = new Button("Remove gizmo");
// Toggle the animation state for all the widgets contained in the pane.
ToggleButton animate = new ToggleButton("Animate");
// Button for creating another stage with a separate animation pane.
Button createStage = new Button("Create Stage");
pane.getChildren().addAll(add, remove, animate, new FPSDisplay(), createStage);
add.setOnAction(e ->
{
pane.getChildren().add(new AnimatedGizmo(animate.selectedProperty()));
});
remove.setOnAction(e ->
{
int lastChildIndex = pane.getChildren().size() - 1;
Node lastNode = pane.getChildren().get(lastChildIndex);
if (lastNode instanceof AnimatedGizmo)
pane.getChildren().remove(lastChildIndex);
});
createStage.setOnAction(e ->
{
newStage();
});
return pane;
}
/**
* A little widget for keeping track of the current frame rate.
*/
private static class FPSDisplay extends HBox
{
private final Label fpsLabel = new Label();
/** For filtering the frame rate update to 2Hz. */
private long timeIntervalBetweenUpdates = TimeUnit.MILLISECONDS.toNanos(500);
/** To keep track of the last time the frame rate was computed. */
private long timeLast = -1;
/** Number of rendered frames since last time we computed frame rate. */
private int frameCounter = 0;
/** Timer used to compute the frame rate. */
private final AnimationTimer timer = new AnimationTimer()
{
@Override
public void handle(long timeNow)
{
if (timeLast == -1)
{ // First call ever, initialize.
timeLast = timeNow;
frameCounter = 0;
}
else
{
// Increment number of frame rendered since timeLast.
frameCounter++;
if (timeNow - timeLast >= timeIntervalBetweenUpdates)
{ // It is time to compute our new averaged frame rate.
// Duration since the last time we computed the frame rate.
double timeElapsedInSeconds = (timeNow - timeLast) * 1.0e-9;
// Our averaged frame rate.
double framesPerSecond = frameCounter / timeElapsedInSeconds;
fpsLabel.setText(String.format("%6.2f FPS", framesPerSecond));
timeLast = timeNow;
frameCounter = 0;
}
}
}
};
public FPSDisplay()
{
setPrefSize(50, 50);
setAlignment(Pos.CENTER);
fpsLabel.setMinWidth(70);
getChildren().add(fpsLabel);
parentProperty().addListener((o, oldValue, newValue) ->
{
if (newValue != null)
timer.start();
else
timer.stop();
});
}
}
/**
* A little widget to simulate some graphic animation happening.
*/
private static class AnimatedGizmo extends AnchorPane
{
/** We use a triangle that will rotate to simulate an animation. */
private final Polygon triangle = new Polygon();
/** The transform used to rotate the triangle. */
private final Rotate rotate = new Rotate();
/** The animation. */
private final AnimationTimer timer = new AnimationTimer()
{
@Override
public void handle(long now)
{
rotate.setAngle(rotate.getAngle() + 1.0);
}
};
public AnimatedGizmo(ObservableBooleanValue animate)
{
setPrefSize(50, 50);
double radius = 0.5;
double cos60 = Math.sqrt(3) / 2.0;
double sin60 = 0.5;
// Drawing a triangle
triangle.getPoints().addAll(0.0, -radius, cos60 * radius, sin60 * radius, -cos60 * radius, sin60 * radius);
triangle.setStroke(null);
triangle.setFill(Color.BLUE);
// Position the triangle in the center of the pane
DoubleBinding halfWidth = widthProperty().divide(2.0);
DoubleBinding halfHeight = heightProperty().divide(2.0);
Translate translate = new Translate();
translate.xProperty().bind(halfWidth);
translate.yProperty().bind(halfHeight);
// Scale the triangle to fill the pane
Scale scale = new Scale();
NumberBinding scaleFactor = Bindings.min(halfWidth, halfHeight).divide(radius);
scale.xProperty().bind(scaleFactor);
scale.yProperty().bind(scaleFactor);
triangle.getTransforms().addAll(translate, scale, rotate);
animate.addListener((o, oldValue, newValue) ->
{
rotate.setAngle(0.0);
if (newValue && getParent() != null)
timer.start();
else
timer.stop();
});
parentProperty().addListener((o, oldValue, newValue) ->
{
rotate.setAngle(0.0);
if (newValue != null && animate.get())
timer.start();
else
timer.stop();
});
getChildren().add(triangle);
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
I have not found any workaround at this moment. This issue makes any multi-window JavaFX application really slow on Linux.
FREQUENCY : always
Tested on Ubuntu 20.04. Works fine on Windows 11.
A DESCRIPTION OF THE PROBLEM :
It appears that on Ubuntu Linux, the frame rate is equal to 60 divided by the number of Stages containing an active animation, e.g. some graphics continuously being updated with an AnimationTimer. If only 1 Stage with animation is active, the frame rate is about 60FPS; for 2 Stages with both animations it is goes down to 30FPS; for 3 Stages with active animations: 20FPS; for 4 Stages with active animations: 15FPS; etc. etc.
The frame rate clearly changes when enabling/disabling all the animations in a Stage.
It is worth noting that the number of animated graphics in a Stage does not seem to affect the frame rate. The issue appears as soon as there is one animation.
The issue does not appear on Windows, tested on Windows 11 and Windows 10.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
See attached source code. Run it to create simple Stages with animations. Observe that when creating an additional Stage, the frame rate does not drop until adding and animating the first widget.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The expecting result can be observed when running on Windows, the creation of graphic object has an initial on the frame rate but quickly the frame rate goes back to 60FPS.
ACTUAL -
On Ubuntu Linux, the frame rate is equal to: 60 / N, where N is the number of Stages containing at least one active animation.
---------- BEGIN SOURCE ----------
package us.ihmc.example;
import java.util.concurrent.TimeUnit;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.NumberBinding;
import javafx.beans.value.ObservableBooleanValue;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class MultiWindowPerformanceDrop extends Application
{
@Override
public void start(Stage primaryStage) throws Exception
{
primaryStage.setScene(new Scene(newAnimationPane(), 720, 480));
primaryStage.show();
}
public static void main(String[] args)
{
launch();
}
/**
* Creates a new Stage with all the controls populated.
*/
public static void newStage()
{
Stage stage = new Stage();
Pane animationPane = newAnimationPane();
stage.setScene(new Scene(animationPane, 720, 480));
stage.setOnCloseRequest(e -> animationPane.getChildren().clear());
stage.show();
}
/**
* Creates a new root pane with controls and animations used for the test.
*/
public static Pane newAnimationPane()
{
FlowPane pane = new FlowPane();
// Button to add an animate-able widget to the pane
Button add = new Button("Add gizmo");
// Button to remove the last widget added.
Button remove = new Button("Remove gizmo");
// Toggle the animation state for all the widgets contained in the pane.
ToggleButton animate = new ToggleButton("Animate");
// Button for creating another stage with a separate animation pane.
Button createStage = new Button("Create Stage");
pane.getChildren().addAll(add, remove, animate, new FPSDisplay(), createStage);
add.setOnAction(e ->
{
pane.getChildren().add(new AnimatedGizmo(animate.selectedProperty()));
});
remove.setOnAction(e ->
{
int lastChildIndex = pane.getChildren().size() - 1;
Node lastNode = pane.getChildren().get(lastChildIndex);
if (lastNode instanceof AnimatedGizmo)
pane.getChildren().remove(lastChildIndex);
});
createStage.setOnAction(e ->
{
newStage();
});
return pane;
}
/**
* A little widget for keeping track of the current frame rate.
*/
private static class FPSDisplay extends HBox
{
private final Label fpsLabel = new Label();
/** For filtering the frame rate update to 2Hz. */
private long timeIntervalBetweenUpdates = TimeUnit.MILLISECONDS.toNanos(500);
/** To keep track of the last time the frame rate was computed. */
private long timeLast = -1;
/** Number of rendered frames since last time we computed frame rate. */
private int frameCounter = 0;
/** Timer used to compute the frame rate. */
private final AnimationTimer timer = new AnimationTimer()
{
@Override
public void handle(long timeNow)
{
if (timeLast == -1)
{ // First call ever, initialize.
timeLast = timeNow;
frameCounter = 0;
}
else
{
// Increment number of frame rendered since timeLast.
frameCounter++;
if (timeNow - timeLast >= timeIntervalBetweenUpdates)
{ // It is time to compute our new averaged frame rate.
// Duration since the last time we computed the frame rate.
double timeElapsedInSeconds = (timeNow - timeLast) * 1.0e-9;
// Our averaged frame rate.
double framesPerSecond = frameCounter / timeElapsedInSeconds;
fpsLabel.setText(String.format("%6.2f FPS", framesPerSecond));
timeLast = timeNow;
frameCounter = 0;
}
}
}
};
public FPSDisplay()
{
setPrefSize(50, 50);
setAlignment(Pos.CENTER);
fpsLabel.setMinWidth(70);
getChildren().add(fpsLabel);
parentProperty().addListener((o, oldValue, newValue) ->
{
if (newValue != null)
timer.start();
else
timer.stop();
});
}
}
/**
* A little widget to simulate some graphic animation happening.
*/
private static class AnimatedGizmo extends AnchorPane
{
/** We use a triangle that will rotate to simulate an animation. */
private final Polygon triangle = new Polygon();
/** The transform used to rotate the triangle. */
private final Rotate rotate = new Rotate();
/** The animation. */
private final AnimationTimer timer = new AnimationTimer()
{
@Override
public void handle(long now)
{
rotate.setAngle(rotate.getAngle() + 1.0);
}
};
public AnimatedGizmo(ObservableBooleanValue animate)
{
setPrefSize(50, 50);
double radius = 0.5;
double cos60 = Math.sqrt(3) / 2.0;
double sin60 = 0.5;
// Drawing a triangle
triangle.getPoints().addAll(0.0, -radius, cos60 * radius, sin60 * radius, -cos60 * radius, sin60 * radius);
triangle.setStroke(null);
triangle.setFill(Color.BLUE);
// Position the triangle in the center of the pane
DoubleBinding halfWidth = widthProperty().divide(2.0);
DoubleBinding halfHeight = heightProperty().divide(2.0);
Translate translate = new Translate();
translate.xProperty().bind(halfWidth);
translate.yProperty().bind(halfHeight);
// Scale the triangle to fill the pane
Scale scale = new Scale();
NumberBinding scaleFactor = Bindings.min(halfWidth, halfHeight).divide(radius);
scale.xProperty().bind(scaleFactor);
scale.yProperty().bind(scaleFactor);
triangle.getTransforms().addAll(translate, scale, rotate);
animate.addListener((o, oldValue, newValue) ->
{
rotate.setAngle(0.0);
if (newValue && getParent() != null)
timer.start();
else
timer.stop();
});
parentProperty().addListener((o, oldValue, newValue) ->
{
rotate.setAngle(0.0);
if (newValue != null && animate.get())
timer.start();
else
timer.stop();
});
getChildren().add(triangle);
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
I have not found any workaround at this moment. This issue makes any multi-window JavaFX application really slow on Linux.
FREQUENCY : always
- duplicates
-
JDK-8291958 vsync happens once for each stage instead of once per pulse
-
- Open
-
- relates to
-
JDK-8291958 vsync happens once for each stage instead of once per pulse
-
- Open
-