Simple Java square animation with JavaFX is not smooth, why? - java

So I noticed something about the framerate / smoothness of my animation. The Animation is somewhat choppy. However, after doing soing testing I noticed that it becomes smooth again as soon as I trigger a resize event, be it only 0.1 px. I have the newest Java installed.
I cant work with vsync unless its opengl and it seems javafx is using a triple bugger or something. Either way the performance is really bad. My windows machine is pretty good and on the newest version.
So after my the call of the show() function I added:
Window.setWidth(Window.getWidth() + 0.1)
Problem was solved but of course I want to know what is going on under the hood and how to I truly solve this without resorting to such primitive hacks?
JavaFX Canvas Double Buffering
My code is below:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.util.Duration;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.control.Button;
public class Gui extends Application{
Stage Window;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Window = primaryStage;
Window.setTitle("Gui Tester");
Group root = new Group();
Rectangle box = new Rectangle(0, 0, 50,50);
box.setFill(Color.GREEN);
KeyValue x = new KeyValue(box.xProperty(), 900);
KeyFrame keyFrame = new KeyFrame(Duration.millis(3000), x);
Timeline timeline = new Timeline();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
root.getChildren().add(box);
GridPane grid = new GridPane();
grid.setVgap(8);
root.getChildren().add(grid);
Button newGame = new Button("START NEW GAME");
GridPane.setConstraints(newGame, 1, 1);
Button continueGame = new Button("CONTINUE");
GridPane.setConstraints(continueGame, 1, 2);
grid.getChildren().addAll(newGame, continueGame);
Scene scene = new Scene(root, 1000, 1000);
scene.getStylesheets().add("tester.css");
Window.setScene(scene);
Window.show();
Window.setWidth(Window.getWidth() + 0.1)
}
}

Tested on Mac OS X 10.11.4, Java 1.8.0_92, NVIDIA GeForce 9400, no energy saving.
Both the example shown, using Timeline, and this example, using PathTransition, start out choppy. Subsequently, they both smooth out after about one auto-reverse cycle. Using the -Xint option, which "Runs the application in interpreted-only mode" as shown here, significantly diminishes the initial stuttering, especially after a second run. Delays due to just-in-time compiler overhead may be exaggerated if the application remains confined to a single core or the animation creates a busy loop that fails to reach a safepoint, as illustrated here.

Related

JavaFX ScrollPane receiving ScrollEvents on mouse enter

I have a JavaFX application with a ScrollPane that handles resizing of nodes upon scrollEvents. However when the JavaFX stage (or Window) is not focused, I get an odd behaviour that I think might be a JFX bug, though wondering if anyone has encountered or managed to resolve it.
To replicate the issue, if you lose focus on the JavaFX window and perform some scrolling using the mouse-wheel on another window (eg your browser), and then relatively quickly move your mouse back to re-enter the JavaFX window (without clicking, scrolling or focusing upon the JavaFX window) the JavaFX window receives a bunch of scrollEvents despite no mouse-wheel action being performed.
I'm wondering if anyone has encountered this and worked out a way to somehow filter these odd scrollEvents out as it results in some strange zooming action that is unexpected given the lack of mouse-wheel scrolling!
I'm using Java & JavaFX 17 (OpenJFX), see below sample application that demonstrates, thanks!
public class ScrollEventIssueApplication extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane borderPane = new BorderPane ();
borderPane .setPrefWidth(600);
borderPane .setPrefHeight(600);
Pane content = new Pane();
content.setPrefWidth(1000);
content.setPrefHeight(1800);
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setPrefWidth(700);
scrollPane.setPrefHeight(700);
content.setOnScroll(event -> {
System.out.println("Scroll event received: " + event.getDeltaY());
});
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 1800, 900);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
It appears that this may be a platform-dependent feature of the installed pointing device and driver. I could occasionally reproduce the effect on Mac OS X 12 with JavaFX 17, but only when I also accidentally raked an errant finger or two across the mouse's multi-touch surface.
For reference, I tested the following simpler variation. Note code to enumerate the OS and Java version numbers, as well as changes to the viewport dimensions in order to show the scroll bars when set to AS_NEEDED by default.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/** #see https://stackoverflow.com/q/72485336/230513 */
public class ScrollTest extends Application {
private static final double W = 640;
private static final double H = 480;
#Override
public void start(Stage stage) {
Pane content = new StackPane();
content.getChildren().addAll(new Rectangle(W, H, Color.BLUE),
new Circle(Math.min(W, H) / 2, Color.LIGHTBLUE));
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setPrefViewportWidth(2 * W / 3);
scrollPane.setPrefViewportHeight(2 * H / 3);
content.setOnScroll(event -> System.out.println(
"dx dy: " + event.getDeltaX() + " " + event.getDeltaY()));
stage.setScene(new Scene(scrollPane));
stage.show();
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name")
+ " v" + System.getProperty("os.version")
+ "; Java v" + System.getProperty("java.version")
+ "; JavaFX v" + System.getProperty("javafx.runtime.version"));
launch(args);
}
}
I realised the issue was the mouse I was using (a Logitech MX Vertical Advanced Ergonomic) had a "smooth scrolling" option on the mouse wheel that meant to it would "continue" some small scroll events after the mouse wheel had stopped scrolling which were causing this issue. Turning off that option in the Logitech Options application resolved the issue.

JAVA FX - TimeLine Animation (Find certain x,y point during animation)

In this program, I am trying to make rect turn red when x== 600 in the for-loop. What basically happens is that the for-loop runs faster than the animation on the screen. The rectangle ends up turning red before it actually hits that certain point within the JavaFX screen.
What I would like it to do it that when it hits point x,y:(600,500), make the blue rectangle turn red.
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author Owner
*/
public class TestPoint extends Application {
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 1000, 1000);
Rectangle rect = new Rectangle();
Rectangle rectTwo = new Rectangle();
//Obstacle that other square must hit
rectTwo.setWidth(100);
rectTwo.setHeight(100);
rectTwo.setX(500);
rectTwo.setY(500);
rectTwo.setFill(Color.PINK);
//for loop that causes the animation to properly move
for (int x = 800; x >= 0; x--) {
rect.setWidth(100);
rect.setHeight(100);
rect.setX(800);
rect.setY(500);
rect.setFill(Color.BLUE);
Timeline timeline = new Timeline();
timeline.setCycleCount(1);
timeline.setAutoReverse(true);
final KeyValue kv = new KeyValue(rect.xProperty(), x);
final KeyFrame kf = new KeyFrame(Duration.seconds(8), kv);
timeline.getKeyFrames().add(kf);
timeline.play();
//if it hits the point of rectTwo, change to Color.RED
System.out.println(x);
if (x == 600) {
rect.setFill(Color.RED);
break;//end
}
}
root.getChildren().addAll(rect, rectTwo);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
You misunderstood, how Timeline works. Your code creates 201 Timeline animations running in parallel. The loop is done before the window is shown. Any updates are automatically triggered by JavaFX later.
Specifying the initial state and the target state via KeyFrames is sufficient. KeyFrames allow you to specify a handler to be executed at a specific time; this can be used to change the color. Alternatively the onFinished handler could be used for coloring the Rectangle.
rect.setWidth(100);
rect.setHeight(100);
rect.setY(500);
rect.setFill(Color.BLUE);
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(rect.xProperty(), 800)),
new KeyFrame(Duration.seconds(8),
evt -> rect.setFill(Color.RED),
new KeyValue(rect.xProperty(), 600)));
timeline.play();

JavaFX ScrollBar setOnMousePressed not working

really liking JavaFX but have come across this problem and wondered if it was a bug.
The ScrollBar.setOnMousePressed() doesn't seem to fire when it has been initialised with a handler. The code below demonstrates the problem:-
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Play extends Application {
public static void main(String[] args) {
launch(args);
}
private static int cnt;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Bug?");
Button btn = new Button("This text will get replaced by the event handlers");
ScrollBar scrollBar = new ScrollBar();
// When pressing and releasing the ScrollBar thumb, we only get decrements
// If you replace the ScrollBar with say a Button, then the code below works as you might expect.
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
scrollBar.setOnMouseReleased( event -> btn.setText("X" + cnt--));
VBox root = new VBox();
root.getChildren().add(btn);
root.getChildren().add(scrollBar);
primaryStage.setScene(new Scene(root, 350, 250));
primaryStage.show();
}
}
Note, Im running on JDK 1.8.0_66 64 Bit on Microsoft Windows 10.
A simple workaround, as suggested by James_D, is to use EventFilters instead of setOnMousePressed(), as follows:-
So,
scrollBar.addEventFilter(MouseEvent.MOUSE_PRESSED,
event -> btn.setText("X" + cnt++));
instead of
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
I believe .setOnMousePressed() should work, but doesn't because of a bug in the library. I've raised with oracle and will update the answer once oracle clarifies.

javafx rendering glitch in jre 1.8.0_60

I have a javaFX 8 application that works perfectly well in jre 1.8.0_45 but today a user came to me with a problem. After some investigation i realised that it was related to him having a more recent release of the jre, specifically 1.8.0_60.
Im reading a GIS shapefile and drawing several Paths to a Group (like 30.000 or more) in my version it was a bit slow but it worked fine. In the latest version the image appeared distorted. The paths where drawn out of place and out of scale in chunks.
correct image generated under jre 1.8.0_45
distorted image generated under jre 1.8.0_60
So i decided to make a little test application to separate the problem from anything else i might be doing. In doing so i found out that the problem wasn't only when drawing Paths on Group but also in drawing to a canvas. Also if somehow i managed to redraw the screen the image would appear fine. For example i have a checkbox binded with the visible property of the Group containing the paths so if i set it to false and then true it takes some time drawing the scene but then it appears fine. The test app is very simple if you press a button you generate a canvas with some squares 10px10p if you press the other you generate more squares and thus the rendering glitch appears.
package gisUI;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class Path2DTestApplication extends Application {
private static final int WIDTH = 10;
Group content = new Group();
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("JavaFX 1.8.0_60 rendering test");
Button button = new Button("Canvas 100 x 30");
button.setOnAction(a->doGenerateCanvas(100,30));
Button button2 = new Button("Canvas 100 x 400");
button2.setOnAction(a->doGenerateCanvas(100,400));
Button button3 = new Button("Paths 100 x 30");
button3.setOnAction(a->doGeneratePaths(100,30));
VBox vBox = new VBox();
vBox.getChildren().addAll(new HBox(button,button2,button3),content);
Group root = new Group();
root.getChildren().add(vBox);
Scene scene = new Scene(root,80*WIDTH,60*WIDTH);//, 1500, 800);//, Color.White);
stage.setScene(scene);
stage.show();
}
private void doGeneratePaths(int maxX,int maxY) {
Pane paths = new Pane();
content.getChildren().clear();
Platform.runLater(()->{
for(int i = 0;i<maxX;i++){
for(int j=0;j<maxY;j++){
paths.getChildren().add(getPath(i,j));
}
}
content.getChildren().add(paths);
});
}
private void doGenerateCanvas(int maxX,int maxY) {
content.getChildren().clear();
Platform.runLater(()->{
Canvas canvas = new Canvas(maxX*WIDTH, maxY*WIDTH);
GraphicsContext gc = canvas.getGraphicsContext2D();
int counter =0;
for(int i = 0;i<maxX;i++){
for(int j=0;j<maxY;j++){
gc.setFill(Color. rgb(255,(int) (Math.random()*255),191));
double[] xCoords = new double[]{i*WIDTH, (i+1)*WIDTH, (i+1)*WIDTH, i*WIDTH};
double[] yCoords = new double[]{j*WIDTH,(j)*WIDTH,(j+1)*WIDTH,(j+1)*WIDTH};
gc.fillPolygon(xCoords,yCoords,xCoords.length);
counter++;
}
}
System.out.println(counter +" polygons added");
content.getChildren().add(canvas);
});
}
protected Node getPath(int i,int j) {
Path path = new Path();
path.getElements().add(new MoveTo(i*WIDTH, j*WIDTH));
path.getElements().add(new LineTo((i+1)*WIDTH, j*WIDTH));
path.getElements().add(new LineTo((i+1)*WIDTH, (j+1)*WIDTH));
path.getElements().add(new LineTo(i*WIDTH, (j+1)*WIDTH));
path.getElements().add(new LineTo(i*WIDTH, j*WIDTH));
Paint currentColor =Color. rgb(255,(int) (Math.random()*255),191);
path.setFill(currentColor);
path.setStrokeWidth(0.1);
return path;
}
public static void main(String[] args) {
Application.launch(Path2DTestApplication.class, args);
}
}
Test 1: press button "Canvas 100 x 30", 3000 squares are drawn
correctly and fast
Test 2: press button "Canvas 100 x 400", 40000
squares are drawn showing the glitch.
Test 3: press button "Canvas
100 x 400" again, 40000 squares are drawn correctly and fast.
Test 4: press button "Paths 100 x 30", 3000 squares are drawn showing the
glitch.
Test 5: press button "Paths 100 x 30" again, 3000 squares are drawn correctly.
This is my first question to stakoverflow so apologies if i wasn't clear enough.
If anyone knows if this is a jre error or there is some problem with my code i would be grateful. Also any workarounds would be helpful.
Tks!!
I played around with this on my MacBook Pro (OS X 10.9.5). This has a native Retina LCD display at 2880x1800, with an attached Thunderbolt LCD display at 2560x1440. Note that the native pixel resolution is different between these two displays.
When I run the code posted, I had no issues with any of the canvas rendering. When rendering the "Paths" option for the first time, or switching from "canvas" to "paths", I saw rendering issues similar to those you describe but only if the application was displayed on the thunderbolt display. When moving to the Retina display, everything worked fine.
So the problem appears to be hardware related. This is clearly a bug, and you should report it as mentioned in a comment, but as a workaround you can switch to software rendering using the system property -Dprism.order=sw from the command line:
java -Dprism.order=sw gisUI.Path2DTestApplication
This removed all rendering errors on my system. You should be aware that this may impact performance.

JavaFX and Swing performance issues

We have been having perpetual performance issues when running JavaFX inside a JFXPanel in Swing based applications.
This seems to only be a problem when running on JDK1.7, because whenever it is possible to run JDK1.8 this works perfectly without changing any code.
The symptoms are that the application seems to render fonts in a fuzzy way and also the performance is terrible (multiple seconds to respond to keypress when typing in a TextField).
We are observing the correct rules about EDT, AWT and Platform threads, so I doubt that this can be the issue.
We are stuck having to support JDK1.7 because this is a plug-in for NetBeans which some users will be running on JDK1.7 for various good reasons and we cannot force them to upgrade.
EDIT: Here is a MCVE which recreates the problem
package javaapplication3;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test {
private static void initAndShowGUI() {
// This method is invoked on the EDT thread
JFrame frame = new JFrame("Swing and JavaFX");
final JFXPanel fxPanel = new JFXPanel();
frame.add(fxPanel);
frame.setSize(300, 200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Platform.runLater(new Runnable() {
#Override
public void run() {
initFX(fxPanel);
}
});
}
private static void initFX(JFXPanel fxPanel) {
// This method is invoked on the JavaFX thread
Scene scene = createScene();
fxPanel.setScene(scene);
}
private static Scene createScene() {
Group root = new Group();
Scene scene = new Scene(root, Color.ALICEBLUE);
TextField text = new TextField();
Label label = new Label();
VBox box = new VBox();
label.setText("This is a test label");
box.getChildren().add(label);
box.getChildren().add(text);
root.getChildren().add(box);
return (scene);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initAndShowGUI();
}
});
}
}
The tests we are performing have very simple javafx with e.g. only an AnchorPane with a TextField on it and absolutely no code behind it, and just typing in the TextField is painfully slow.
Behavior looks very much like lock contention between the Swing and JavaFX threads, but it does not seem like we can find any explanation or solution.
This is not the answer that you are looking for, but since we have the same problem with Java 7 support, the answer is that Java 7 has reached its end of life:
July 2015: Updates for Java 7 are no longer available to the public.
Oracle offers updates to Java 7 only for customers who have purchased
Java support or have Oracle products that require Java 7.
https://www.java.com/en/download/faq/java_7.xml
There are no "good reasons" if problem solving is as easy as using a different java version. You don't break things by upgrading to Java 8.

Categories