简体   繁体   中英

javafx canvas stops displaying after a few seconds (I'm trying million ovals)

I'm trying to display a million ovals in a canvas dynamicly every second. I use a thread as the rendering thread, but my problem is, after a few secondes, the canvas freezes and stops displaying. I guess that the buffer is full so it can't display anymore, but how can I clear up the buffer? The test source code is as follow:

public class Main extends Application {

    public void start(Stage primaryStage) {
        primaryStage.setTitle("Drawing Operations Test");
        Group root = new Group();
        Canvas canvas = new Canvas(800, 800);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        drawShapes(gc);
        root.getChildren().add(canvas);
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
        Task task2 = new Task<Void>()
        {
            public synchronized Void call() throws Exception {
                while (true) {
                    Thread.sleep(1000);
                    Canvas canvas = gc.getCanvas();
                    canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getHeight(), canvas.getWidth());
                    drawShapes(canvas.getGraphicsContext2D());

                }
            }
        };
        Thread t = new Thread(task2);
        t.start();
    }

    private void drawShapes(GraphicsContext gc) {
        gc.setFill(Color.GREEN);
        gc.setStroke(Color.BLUE);
        gc.setLineWidth(1);
        double widthOval=1;
        double heightOval = 1;
        for (int i = 0; i < 500; ++i) {
            for (int j = 0; j < 500; ++j) {

                if (Math.random() < 0.5) {
                    gc.fillOval(i * widthOval, j * heightOval, widthOval, heightOval);
                }
                else {
                    gc.strokeOval(i * widthOval, j * heightOval, widthOval, heightOval);
                }
            }
        }
    }

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

Can anyone help me?

Thanks in advance,

Pierre

What is (probably) going wrong and how to fix it

Don't use a Task and a separate thread for the drawing. Modifications to the scene graph (including a canvas), must be done on the JavaFX application thread. Instead use a Timeline (which will implicitly execute it's keyframe code on the JavaFX application thread). If you must use a task, then at least use Platform.runLater to surround your calls into the JavaFX API that modify the active scene graph.

Potential scaling issue

A million ovals per second is probably a lot to try to draw in a single frame. You may wish to run some benchmark tests to see what a usable number is for your target platforms (though my sample below does seem to execute fine without a performance issue on a 2014 Macbook Pro).

Sample Code

Here is an updated sample with a Timeline that you could try. It seems to work fine for me (Java 8u60, OS X 10.9.5) - rendering 250,000 circles each second:

import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;

public class LotsaCircles extends Application {
    private static final double SIZE = 500;

    public void start(Stage stage) {
        Canvas canvas = new Canvas(SIZE, SIZE);

        Timeline timeline = new Timeline(
                new KeyFrame(
                        Duration.seconds(0), 
                        event -> drawShapes(canvas.getGraphicsContext2D())
                ),
                new KeyFrame(Duration.seconds(1))
        );
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();

        stage.setScene(new Scene(new Group(canvas)));
        stage.show();
    }

    private void drawShapes(GraphicsContext gc) {
        gc.clearRect(0, 0, SIZE, SIZE);
        gc.setFill(Color.GREEN);
        gc.setStroke(Color.BLUE);
        gc.setLineWidth(1);
        double widthOval = 1;
        double heightOval = 1;
        for (int i = 0; i < SIZE; ++i) {
            for (int j = 0; j < SIZE; ++j) {
                if (Math.random() < 0.5) {
                    gc.fillOval(i * widthOval, j * heightOval, widthOval, heightOval);
                }
                else {
                    gc.strokeOval(i * widthOval, j * heightOval, widthOval, heightOval);
                }
            }
        }
    }

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM