Schwere Rendering-Aufgabe (im Canvas-Bereich) in JavaFX blockiert GUI

Ich möchte eine Anwendung erstellen, die viele Renderings in einer Zeichenfläche ausführt. Die normale JavaFX-Methode blockiert die GUI: Es ist wirklich schwierig, die Schaltfläche im folgenden Anwendungscode zu drücken (mit Java 8 ausgeführt).

Ich habe das Web durchsucht, aber JavaFX unterstützt kein Hintergrund-Rendering: Alle Rendering-Vorgänge (wie beispielsweise strokeLine) werden in einem Puffer gespeichert und später im JavaFX-Anwendungsthread ausgeführt. Ich kann also nicht mal zwei Leinwände verwenden und diese dann nach dem Rendern austauschen.

Auch der javafx.scene.Node.snapshot (SnapshotParameters, WritableImage) kann nicht zum Erstellen eines Bildes in einem Hintergrundthread verwendet werden, da er im JavaFX-Anwendungsthread ausgeführt werden muss und daher auch die GUI blockiert.

Haben Sie Ideen für eine nicht blockierende Benutzeroberfläche mit vielen Rendering-Vorgängen? (Ich möchte nur Schaltflächen usw. drücken, während das Rendern im Hintergrund ausgeführt oder regelmäßig angehalten wird.)

package canvastest;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;

public class DrawLinieTest extends Application
{
    int             interations     = 2;

    double          lineSpacing     = 1;

    Random          rand            = new Random(666);

    List<Color>     colorList;

    final VBox      root            = new VBox();

    Canvas          canvas          = new Canvas(1200, 800);

    Canvas          canvas2         = new Canvas(1200, 800);

    ExecutorService executorService = Executors.newSingleThreadExecutor();

    Future<?>       drawShapesFuture;

    {
        colorList = new ArrayList<>(256);
        colorList.add(Color.ALICEBLUE);
        colorList.add(Color.ANTIQUEWHITE);
        colorList.add(Color.AQUA);
        colorList.add(Color.AQUAMARINE);
        colorList.add(Color.AZURE);
        colorList.add(Color.BEIGE);
        colorList.add(Color.BISQUE);
        colorList.add(Color.BLACK);
        colorList.add(Color.BLANCHEDALMOND);
        colorList.add(Color.BLUE);
        colorList.add(Color.BLUEVIOLET);
        colorList.add(Color.BROWN);
        colorList.add(Color.BURLYWOOD);

    }

    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage)
    {
        primaryStage.setTitle("Drawing Operations Test");

        System.out.println("Init...");

        // inital draw that creates a big internal operation buffer (GrowableDataBuffer)
        drawShapes(canvas.getGraphicsContext2D(), lineSpacing);
        drawShapes(canvas2.getGraphicsContext2D(), lineSpacing);

        System.out.println("Start testing...");
        new CanvasRedrawTask().start();

        Button btn = new Button("test " + System.nanoTime());
        btn.setOnAction((ActionEvent e) ->
        {
            btn.setText("test " + System.nanoTime());
        });

        root.getChildren().add(btn);
        root.getChildren().add(canvas);

        Scene scene = new Scene(root);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void drawShapes(GraphicsContext gc, double f)
    {
        System.out.println(">>> BEGIN: drawShapes ");

        gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());

        gc.setLineWidth(10);

        gc.setLineCap(StrokeLineCap.ROUND);

        long time = System.nanoTime();

        double w = gc.getCanvas().getWidth() - 80;
        double h = gc.getCanvas().getHeight() - 80;
        int c = 0;

        for (int i = 0; i < interations; i++)
        {
            for (double x = 0; x < w; x += f)
            {
                for (double y = 0; y < h; y += f)
                {
                    gc.setStroke(colorList.get(rand.nextInt(colorList.size())));
                    gc.strokeLine(40 + x, 10 + y, 10 + x, 40 + y);
                    c++;
                }
            }
        }

        System.out.println("<<< END: drawShapes: " + ((System.nanoTime() - time) / 1000 / 1000) + "ms");
    }

    public synchronized void drawShapesAsyc(final double f)
    {
        if (drawShapesFuture != null && !drawShapesFuture.isDone())
            return;
        drawShapesFuture = executorService.submit(() ->
        {
            drawShapes(canvas2.getGraphicsContext2D(), lineSpacing);

            Platform.runLater(() ->
            {
                root.getChildren().remove(canvas);

                Canvas t = canvas;
                canvas = canvas2;
                canvas2 = t;

                root.getChildren().add(canvas);
            });

        });
    }

    class CanvasRedrawTask extends AnimationTimer
    {
        long time = System.nanoTime();

        @Override
        public void handle(long now)
        {
            drawShapesAsyc(lineSpacing);
            long f = (System.nanoTime() - time) / 1000 / 1000;
            System.out.println("Time since last redraw " + f + " ms");
            time = System.nanoTime();
        }
    }
}

BEARBEITE Bearbeitet den Code, um zu zeigen, dass ein Hintergrund-Thread, der die Zeichenoperationen sendet und dann die Zeichenfläche austauscht, das Problem nicht löst! WeilAlle Rendering-Vorgänge (wie beispielsweise strokeLine) werden in einem Puffer gespeichert und später im JavaFX-Anwendungsthread ausgeführt.

Antworten auf die Frage(4)

Ihre Antwort auf die Frage