performance swing javafx graphics

performance - Aplicación de lienzo JavaFX de sincronización



swing graphics (1)

Ordené practicar JavaFX, construí una aplicación simple que dibuja Triángulos Sierpinski .

import javafx.application.Application; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.geometry.Insets; import javafx.geometry.Point2D; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.stage.Stage; public class SierpinskiTriangles extends Application { private final int PADDING = 5; private static int numberOfLevels; public static void launch(String... args){ numberOfLevels = 8; if((args != null) && (args.length > 0)) { int num = -1; try { num = Integer.parseInt(args[0]); } catch (NumberFormatException ex) { ex.printStackTrace(); return; } numberOfLevels = (num > 0) ? num : numberOfLevels; } Application.launch(args); } @Override public void start(Stage stage) { stage.setOnCloseRequest((ae) -> { Platform.exit(); System.exit(0); }); stage.setTitle("Sierpinski Triangles (fx)"); BorderPane mainPane = new BorderPane(); mainPane.setPadding(new Insets(PADDING)); Pane triPanel = new Triangles(); BorderPane.setAlignment(triPanel, Pos.CENTER); mainPane.setCenter(triPanel); Scene scene = new Scene(mainPane); stage.setScene(scene); stage.centerOnScreen(); stage.setResizable(false); stage.show(); } class Triangles extends AnchorPane{ private static final int PANEL_WIDTH =600, PANEL_HEIGHT = 600; private static final int TRI_WIDTH= 500, TRI_HEIGHT= 500; private static final int SIDE_GAP = (PANEL_WIDTH - TRI_WIDTH)/2; private static final int TOP_GAP = (PANEL_HEIGHT - TRI_HEIGHT)/2; private int countTriangles; private long startTime; private Point2D top, left, right; private Canvas canvas; private GraphicsContext gc; Triangles(){ setPrefSize(PANEL_WIDTH, PANEL_HEIGHT); canvas = getCanvas(); gc = canvas.getGraphicsContext2D(); getChildren().add(canvas); draw(numberOfLevels); } void draw(int numberLevels) { Platform.runLater(new Runnable() { @Override public void run() { clearCanvas(); setStartPoints(); startTime = System.currentTimeMillis(); countTriangles = 0; RunTask task = new RunTask(numberLevels, top, left, right); Thread thread = new Thread(task); thread.setDaemon(true); thread.start(); } }); } private void drawTriangle( int levels, Point2D top, Point2D left, Point2D right) { if(levels < 0) {//add stop criteria return ; } gc.strokePolygon( //implementing with strokeLine did not make much difference new double[]{ top.getX(),left.getX(),right.getX() }, new double[]{ top.getY(),left.getY(), right.getY() },3 ); countTriangles++; //Get the midpoint on each edge in the triangle Point2D p12 = midpoint(top, left); Point2D p23 = midpoint(left, right); Point2D p31 = midpoint(right, top); // recurse on 3 triangular areas drawTriangle(levels - 1, top, p12, p31); drawTriangle(levels - 1, p12, left, p23); drawTriangle(levels - 1, p31, p23, right); } private void setStartPoints() { top = new Point2D(getPrefWidth()/2, TOP_GAP); left = new Point2D(SIDE_GAP, TOP_GAP + TRI_HEIGHT); right = new Point2D(SIDE_GAP + TRI_WIDTH, TOP_GAP + TRI_WIDTH); } private Point2D midpoint(Point2D p1, Point2D p2) { return new Point2D((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2); } private void updateGraphics(boolean success){ if(success) { gc.fillText("Number of triangles: "+ countTriangles,5,15); gc.fillText("Time : "+ (System.currentTimeMillis()- startTime)+ " mili seconds", 5,35); gc.fillText("Levels: "+ numberOfLevels,5,55); } System.out.println("Completed after: "+ (System.currentTimeMillis()- startTime)+ " mili seconds" +" Triangles: " + countTriangles +" Failed: "+ !success ); } private Canvas getCanvas() { Canvas canvas = new Canvas(); canvas.widthProperty().bind(widthProperty()); canvas.heightProperty().bind(heightProperty()); canvas.getGraphicsContext2D().setStroke(Color.RED); canvas.getGraphicsContext2D().setLineWidth(0.3f); return canvas; } private void clearCanvas() { gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); } class RunTask extends Task<Void>{ private int levels; private Point2D top, left; private Point2D right; RunTask(int levels, Point2D top, Point2D left, Point2D right){ this.levels = levels; this.top = top; this.left = left; this.right = right; startTime = System.currentTimeMillis(); countTriangles = 0; } @Override public Void call() { drawTriangle(levels,top, left, right); return null; } @Override protected void succeeded() { updateGraphics(true); super.succeeded(); } @Override protected void failed() { updateGraphics(false); } } } public static void main(String[] args) { launch("13"); } }


El resultado es el esperado:

Los problemas que tengo:

a. La impresión de tiempo en updateGraphics() muestra mucho antes (8 segundos en mi máquina) que se completa el dibujo de los triángulos, por lo tanto, no mide el proceso completo. ¿Cómo lo mejoro?

segundo. En mi máquina, tarda 30-35 segundos hasta que el panel esté completamente dibujado. Una aplicación de swing similar dura 4 segundos. Puede sugerir que hay algo fundamentalmente incorrecto con mi implementación de javafx.


Su Task invoca drawTriangle() en el fondo para actualizar un Canvas . El GraphicsContext asociado requiere que "una vez que un nodo Canvas se adjunta a una escena, se debe modificar en el subproceso de aplicación JavaFX". Su llamada profundamente recursiva bloquea el hilo de aplicación JavaFX, evitando una actualización de pantalla oportuna. Por el contrario, la implementación de System.out.println() su plataforma puede allow informar de manera oportuna. La disparidad de tiempo se ve incluso sin una Task en absoluto.

Felizmente para Canvas , "Si no está adjunto a ninguna escena, puede ser modificado por cualquier hilo, siempre y cuando solo se use de un hilo a la vez". Se puede sugerir un enfoque en Task . Cree una Task<Image> nocional Task<Image> que actualice un Canvas separado en segundo plano. Periódicamente, tal vez en cada nivel de recursión, copy el Canvas y publique una instantánea a través de updateValue() . El Pane adjunto puede escuchar la propiedad de value la tarea y actualizar un Canvas adjunto a través de drawImage() sin bloquear el subproceso de aplicación JavaFX.

Lamentablemente, la instantánea "Lanza IllegalStateException si este método se llama en un subproceso que no sea el subproceso de aplicación JavaFX"

En la alternativa que se muestra a continuación, CanvasTask extiende la Task<Canvas> y publica un nuevo Canvas en cada iteración de un bucle. CanvasTaskTest escucha la propiedad value y reemplaza el Canvas anterior cada vez que llega uno nuevo. El siguiente ejemplo muestra una serie de árboles fractales de profundidad creciente y el tiempo necesario para componer cada uno. Tenga en cuenta que en un GraphicsContext , "Cada llamada empuja los parámetros necesarios en el búfer donde luego serán procesados ​​en la imagen del nodo Canvas por el hilo de procesamiento al final de un pulso". Esto permite que JavaFX aproveche la canalización de representación de una plataforma, pero puede imponer una sobrecarga adicional para una gran cantidad de trazos. En la práctica, decenas de miles de trazos ralentizan la representación imperceptible, mientras que millones de trazos superpuestos pueden ser superfluos.

import javafx.application.Application; import javafx.beans.value.ObservableValue; import javafx.concurrent.Task; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.StackPane; import javafx.stage.Stage; /** * @see https://.com/a/44056730/230513 */ public class CanvasTaskTest extends Application { private static final int W = 800; private static final int H = 600; @Override public void start(Stage stage) { stage.setTitle("CanvasTaskTest"); StackPane root = new StackPane(); Canvas canvas = new Canvas(W, H); root.getChildren().add(canvas); Scene scene = new Scene(root); stage.setScene(scene); stage.show(); CanvasTask task = new CanvasTask(); task.valueProperty().addListener((ObservableValue<? extends Canvas> observable, Canvas oldValue, Canvas newValue) -> { root.getChildren().remove(oldValue); root.getChildren().add(newValue); }); Thread thread = new Thread(task); thread.setDaemon(true); thread.start(); } private static class CanvasTask extends Task<Canvas> { private int strokeCount; @Override protected Canvas call() throws Exception { Canvas canvas = null; for (int i = 1; i < 15; i++) { canvas = new Canvas(W, H); GraphicsContext gc = canvas.getGraphicsContext2D(); strokeCount = 0; long start = System.nanoTime(); drawTree(gc, W / 2, H - 50, -Math.PI / 2, i); double dt = (System.nanoTime() - start) / 1_000d; gc.fillText("Depth: " + i + "; Strokes: " + strokeCount + "; Time : " + String.format("%1$07.1f", dt) + " µs", 8, H - 8); Thread.sleep(200); // simulate rendering latency updateValue(canvas); } return canvas; } private void drawTree(GraphicsContext gc, int x1, int y1, double angle, int depth) { if (depth == 0) { return; } int x2 = x1 + (int) (Math.cos(angle) * depth * 5); int y2 = y1 + (int) (Math.sin(angle) * depth * 5); gc.strokeLine(x1, y1, x2, y2); strokeCount++; drawTree(gc, x2, y2, angle - Math.PI / 8, depth - 1); drawTree(gc, x2, y2, angle + Math.PI / 8, depth - 1); } } public static void main(String[] args) { launch(args); } }