package bugs.performance.triangles; import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Bounds; import javafx.geometry.Pos; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToolBar; import javafx.scene.input.ScrollEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.shape.Rectangle; import javafx.scene.shape.Shape; import javafx.stage.Stage; /** * Run with -Dprism.order=j2d for best performance :-( */ public class TrianglePerformanceTest extends Application { private final static double WIDTH = 800; private final static double HEIGHT = 800; private final static double SIZE = 100; private final StackPane graphicsPane = new StackPane(); private final Pane drawingPane = new Pane(); private final Group graphics = new Group(); private ToolBar toolBar; private TextField numElemSelection; private TextField frameRate; private ToggleButton triRectButton; private ToggleButton animButton; private int numElements = 1000; private boolean showRectangles = false; private class MyAnimationTimer extends AnimationTimer { private final static long SECONDS_TO_NANOS = 1_000_000_000; private long previousTimeNanos = 0; private boolean active = false; @Override public void start() { super.start(); this.active = true; } @Override public void stop() { super.stop(); this.active = false; } @Override public void handle(long nowNanos) { if (previousTimeNanos > 0) { long deltaNanos = nowNanos - previousTimeNanos; if (deltaNanos > 0) { frameRate.setText(String.format("%d", SECONDS_TO_NANOS/deltaNanos)); } } previousTimeNanos = nowNanos; double angle = 10 * ((double)nowNanos / SECONDS_TO_NANOS); graphics.setRotate(angle); } public boolean isActive() { return active; } }; MyAnimationTimer timer = new MyAnimationTimer(); @Override public void start(Stage stage) throws Exception { stage.setTitle(getClass().getSimpleName()); BorderPane root = new BorderPane(); if (true) { // if 'false' crashes application Rectangle clip = new Rectangle(); clip.widthProperty().bind(drawingPane.widthProperty()); clip.heightProperty().bind(drawingPane.heightProperty()); drawingPane.setClip(clip); } drawingPane.getChildren().add(graphics); graphicsPane.getChildren().add(drawingPane); toolBar = new ToolBar(); root.setTop(toolBar); root.setCenter(graphicsPane); drawingPane.setOnScroll(new EventHandler() { @Override public void handle(ScrollEvent e) { double val = e.getDeltaY(); if (val < 0) { graphics.setScaleX(graphics.getScaleX()*0.8); graphics.setScaleY(graphics.getScaleY()*0.8); } else { graphics.setScaleX(graphics.getScaleX()*1.2); graphics.setScaleY(graphics.getScaleY()*1.2); } } }); { // Number of elements: Label label = new Label("Num. Elem.:"); numElemSelection = new TextField(); numElemSelection.setAlignment(Pos.BASELINE_RIGHT); numElemSelection.setPrefColumnCount(4); numElemSelection.setText(String.valueOf(numElements)); numElemSelection.setOnAction(resetConfigActionHandler); toolBar.getItems().addAll(label, numElemSelection); } { // Frame rate: Label label = new Label("Frame rate:"); frameRate = new TextField(); frameRate.setAlignment(Pos.BASELINE_RIGHT); frameRate.setPrefColumnCount(3); frameRate.setEditable(false); toolBar.getItems().addAll(label, frameRate); } toolBar.getItems().add(new Label(" ")); { // Triangles/Rectangles toggle button: triRectButton = new ToggleButton("Triangles/Rectangles"); triRectButton.setSelected(false); triRectButton.setOnAction(resetConfigActionHandler); toolBar.getItems().add(triRectButton); } { // Start/Stop animation toggle button: animButton = new ToggleButton("Start/Stop Animation"); animButton.setSelected(false); animButton.setOnAction(animationActionHandler); toolBar.getItems().add(animButton); } Scene scene = new Scene(root, WIDTH, HEIGHT); stage.setScene(scene); stage.show(); // Crashes application if the listener is attached to the graphicsPane. It works with the drawingPane. graphicsPane.boundsInLocalProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Bounds oldValue, Bounds newValue) { resetConfig(); } }); resetConfig(); } private EventHandler resetConfigActionHandler = new EventHandler() { @Override public void handle(ActionEvent e) { resetConfig(); } }; private EventHandler animationActionHandler = new EventHandler() { @Override public void handle(ActionEvent e) { boolean showAnimation = animButton.isSelected(); if (showAnimation != timer.isActive()) { if (showAnimation) { timer.start(); } else { timer.stop(); } } } }; private void resetConfig() { try { timer.stop(); numElements = Math.abs(Integer.valueOf(numElemSelection.getText())); showRectangles = triRectButton.isSelected(); boolean showAnimation = animButton.isSelected(); updateGraphics(); if (showAnimation) { timer.start(); } else { timer.stop(); } } catch (NumberFormatException e) { System.err.println("Illegal value for number of elements: " + numElemSelection.getText()); } } private void updateGraphics() { graphics.getChildren().clear(); graphics.setRotate(0.0); graphics.setScaleX(1.0); graphics.setScaleY(1.0); for (int i = 0; i < numElements; i++) { addShape(graphics, drawingPane.getWidth(), drawingPane.getHeight(), showRectangles); } } private void addShape(Group graphics, double width, double height, boolean showRectangles) { final double px0 = Math.random() * width; final double py0 = Math.random() * height; final double px1 = px0 - SIZE/2 + Math.random() * SIZE; final double py1 = py0 - SIZE/2 + Math.random() * SIZE; final double px2 = px0 - SIZE/2 + Math.random() * SIZE; final double py2 = py0 - SIZE/2 + Math.random() * SIZE; Shape shape; if (!showRectangles) { shape = new Polygon(px0, py0, px1, py1, px2, py2); } else { double boundsX = Math.min(Math.min(px0, px1), px2); double boundsY = Math.min(Math.min(py0, py1), py2); double boundsWidth = Math.max(Math.max(px0, px1), px2) - boundsX; double boundsHeight = Math.max(Math.max(py0, py1), py2) - boundsY; shape = new Rectangle(boundsX, boundsY, boundsWidth, boundsHeight); } shape.setFill(randomColor()); shape.setStroke(null); graphics.getChildren().add(shape); } private Color randomColor() { return new Color(Math.random(), Math.random(), Math.random(), 1.0); } public static void main(String[] args) { launch(args); } }