Java JavaFX Canvas 2D Zeichnen dauert ewig & blockiert UI Thread

Timmey92

Commodore
Registriert
Okt. 2008
Beiträge
4.569
Hallo liebe Freunde,

ich arbeite gerade an einem kleinen Projekt und nutze erstmals JavaFX. Als Teil dieses Projekts muss ein Array aus bis zu 250000 Objekten (500x500) auf einen JavaFX Canvas gezeichnet werden (es ist eine Simulation).
Das Problem ist: das dauert verdammt noch mal ewig (bis zu 10 Sekunden auf schwachen Rechnern).
Ein weiteres Problem ist: der UI Thread blockiert währenddessen, da die Zeichenoperation so lange dauert.

Mögliche Lösungsansätze meinerseits: Zeichnen in einen Puffer auf einen extra Thread auslagern und dann Ergebnis im UI Thread austauschen - wie könnte das gehen? Bisherige Versuche mit schreiben in ein BufferedImage welches anschließend auf dem Canvas als ganzes gezeichnet wird, sind gescheitert (Threading ist grundsätzlich kein Problem für mich).

Habt ihr da Ideen warum das so inperformant ist? Eine Beispielapplikation, um das Problem zu verdeutlichen, siehe in den Codeblöcken.

Danke für eure Hilfe, ist gerade enorm wichtig für mich!

Main.java
Code:
package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        ScrollPane scrollPane = (ScrollPane) root.lookup("#scrollPane");
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}
Controller.java
Code:
package sample;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ScrollPane;
import javafx.scene.paint.Color;

import java.util.Random;

public class Controller {

    @FXML
    private ScrollPane scrollPane;

    Canvas canvas;
    @FXML
    protected void draw(ActionEvent event) {
    canvas = new Canvas(500,500);
    scrollPane.setContent(canvas);
    GraphicsContext gc = canvas.getGraphicsContext2D();
        Random r = new Random();

        for(int i = 0; i<=500; i++) {
            for(int j = 0; j<=500; j++) {
                if (r.nextBoolean()) {
                    gc.setFill(Color.BLACK);
                } else {
                    gc.setFill(Color.BLUE);
                }
                gc.fillRect(i, j, i+1, j+1);
            }
        }
    }
}
sample.fxml
Code:
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.GridPane?>

<AnchorPane prefHeight="275.0" prefWidth="300.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="sample.Controller">
  <children>
    <ScrollPane fx:id="scrollPane" content="$null" prefHeight="237.0" prefWidth="300.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="38.0" />
    <Button layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onAction="#draw" text="Draw" />
  </children>
</AnchorPane>
 
Für sowas benutzt man normalerweise (wenn man so viele Objekte wirklich zeichnen muss) Hardware-Instancing.
 
Danke schon Mal für deine Antwort, was ist das und wie könnte ich das im konkreten Fall (JavaFXUI) einsetzen? Sieht mir nach einer Technik aus, die man eher im OpenGL / Direct3D Bereich anwenden kann.
 
Ich habe leider keine Erfahrung mit JavaFX, aber ich habe mal vor 7 Jahren in Java2D hardwarebeschleunigte Grafik verwendet. Da ging das am schnellsten mit einem VolatileImage. Möglicherweise gibt es inzwischen bessere Möglichkeiten.

Ich habe dein Beispiel mal etwas ausgebaut. Aktuell ist das VolatileImage aktiv. Damit dauert es bei mir 200-250ms bis das Bild dargestellt ist. Mit einem BufferedImage dauert es ca. 1700ms. Der code für's BufferedImage ist auskommentiert noch enthalten.

Zu beachten ist, dass das nur Quick+Dirty implementiert ist. VolatileImage kann seinen Inhalt jederzeit verlieren, daher sollte man noch ein paar Zeilen extra Code mit Prüfungen einbringen. Da wollte ich mich aber jetzt nicht wieder hineinarbeiten.

Außerdem wird hier awt mit JavaFX vermischt. Welche Nachteile das eventuell bringen könnte, kann ich nicht einschätzen.


Controller.java
Code:
package sample;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.util.Random;

import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.WritableImage;

public class Controller {

	@FXML
	private ScrollPane scrollPane;

	Canvas canvas;
	

	@FXML
	protected void draw(ActionEvent event) {
		GraphicsConfiguration grc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
				.getDefaultConfiguration();
		VolatileImage vImg=grc.createCompatibleVolatileImage(500, 500);
	    Graphics2D gVImg = vImg.createGraphics();
		
		BufferedImage bImg = new BufferedImage(500, 500,
				BufferedImage.TYPE_INT_ARGB);				
		Graphics2D gBImg = bImg.createGraphics();
		
		canvas = new Canvas(500, 500);
		scrollPane.setContent(canvas);
		GraphicsContext gc = canvas.getGraphicsContext2D();
		Random r = new Random();
		long start= System.currentTimeMillis();
		long now=0;
		System.out.println("Before for: 0 ms" );
		for (int i = 0; i <= 500; i++) {
			for (int j = 0; j <= 500; j++) {
				if (r.nextBoolean()) {
					gVImg.setColor(Color.BLACK);
//					gBImg.setColor(Color.BLACK);
				} else {
					gVImg.setColor(Color.BLUE);
//					gBImg.setColor(Color.BLUE);
				}
				
				gVImg.fillRect(i, j, i + 1, j + 1);
//				gBImg.fillRect(i, j, i + 1, j + 1);
			}
		}
		
		BufferedImage vImgSnapshot=vImg.getSnapshot();
		now= System.currentTimeMillis();
		System.out.println("After for " + (now-start) +" ms" );
		WritableImage wImg = new WritableImage(500, 500);
		
		wImg = SwingFXUtils.toFXImage(vImgSnapshot, wImg);
//		wImg = SwingFXUtils.toFXImage(bImg, wImg);
		
		gc.drawImage(wImg, 0, 0);
		now= System.currentTimeMillis();
		System.out.println("After gc.drawImage " + (now-start) +" ms" );		
	}
}
 
Danke danke danke, das hilft mir sooo was von weiter!
Ob ich die Prüfung überhaupt brauche weiß ich noch nicht, werd mich da noch genauer informieren wie wahrscheinlich das ist etc.
Bin bis Sonntag erst mal unterwegs, dann melde ich mich mal wie es geklappt hat, erste Tests in meinem richtigen Projekt verliefen positiv :D
 
Zuletzt bearbeitet:
Zurück
Oben