How to improve JavaFX graphic drawing performance?

Situation:

I created an application that needs to draw simple rectangles (size 1x1 - 3x3), depending on some variables stored in an array of the corresponding size (size 900x900 - 300x300) with a frequency of 20, 40 or even 60 FPS.

Here is my drawing method:

protected void drawCurrentState()
    {   
        for (int i=0; i<gameLogic.size; i++)
        {
            for (int j=0; j<gameLogic.size; j++)
            {
                if (gameLogic.previousStepTable == null || gameLogic.previousStepTable[i][j] != gameLogic.gameTable[i][j])
                {
                    if (gameLogic.gameTable[i][j].state)
                        graphicsContext.setFill(gameLogic.gameTable[i][j].color); 
                    else
                        graphicsContext.setFill(Color.WHITE); 

                    graphicsContext.fillRect(leftMargin + (j * (cellSize + cellMargin)),
                        topMargin + (i * (cellSize + cellMargin)), cellSize, cellSize);
                }

            }
        }
    }

As you probably noticed, the drawing is executed only when it is needed (when the state has changed from the previous step). All other operations (states of variable calculations in gameLogic, etc.) are practically timeless and do not affect performance.

Problem:

JavaFX performs this operation incredibly slowly! 10 iterations (which should be drawn for 0.5 s at a speed of 20FPS) are pulled out after 5-10 seconds, which is obviously unacceptable in this case.

Question:

(, 40 60 )?

?

+4
2

, :

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.image.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.nio.IntBuffer;

public class CanvasUpdater extends Application {
    private static final int CELL_SIZE = 2;
    private static final int BOARD_SIZE = 900;

    private static final int W = BOARD_SIZE * CELL_SIZE;
    private static final int H = BOARD_SIZE * CELL_SIZE;

    private static final double CYCLE_TIME_IN_MS = 5_000;

    private final WritablePixelFormat<IntBuffer> pixelFormat = 
            PixelFormat.getIntArgbPreInstance();

    private Canvas canvas = new Canvas(W, H);
    private GraphicsContext gc = canvas.getGraphicsContext2D();
    private int[] buffer = new int[W * H];

    @Override
    public void start(Stage stage) {
        final long start = System.nanoTime();
        AnimationTimer timer = new AnimationTimer() {
            @Override
            public void handle(long now) {
                double t = 
                        ((now - start) % (1_000_000 * CYCLE_TIME_IN_MS * 2)) / 
                        (1_000_000.0 * CYCLE_TIME_IN_MS);
                if (t > 1) t = 1 - (t - 1);
                Color c1 = Color.RED.interpolate(Color.BLUE, t);
                Color c2 = Color.BLUE.interpolate(Color.RED, t);

                gc.clearRect(0, 0, W, H);

//                Draw using fillRect
//
//                for (int i = 0; i < W; i += CELL_SIZE) {
//                    for (int j = 0; j < H; j += CELL_SIZE) {
//                        gc.setFill(
//                                (i/CELL_SIZE + j/CELL_SIZE) % 2 == 0
//                                        ? c1
//                                        : c2
//                        );
//                        gc.fillRect(i, j, CELL_SIZE, CELL_SIZE);
//                    }
//                }

//                Draw using setColor
//
//                PixelWriter p = gc.getPixelWriter();
//                for (int i = 0; i < W; i += CELL_SIZE) {
//                    for (int j = 0; j < H; j += CELL_SIZE) {
//                        Color c =
//                                (i/CELL_SIZE + j/CELL_SIZE) % 2 == 0
//                                        ? c1
//                                        : c2;
//                        for (int dx = 0; dx < CELL_SIZE; dx++) {
//                            for (int dy = 0 ; dy < CELL_SIZE; dy++) {
//                                p.setColor(i + dx, j + dy, c);
//                            }
//                        }
//                    }
//                }

//              Draw using buffer
//
                int ci1 = toInt(c1);
                int ci2 = toInt(c2);

                for (int i = 0; i < W; i += CELL_SIZE) {
                    for (int j = 0; j < H; j += CELL_SIZE) {
                        int ci =
                                (i/CELL_SIZE + j/CELL_SIZE) % 2 == 0
                                        ? ci1
                                        : ci2;
                        for (int dx = 0; dx < CELL_SIZE; dx++) {
                            for (int dy = 0 ; dy < CELL_SIZE; dy++) {
                                buffer[i + dx + W * (j + dy)] = ci;
                            }
                        }
                    }
                }

                PixelWriter p = gc.getPixelWriter();
                p.setPixels(0, 0, W, H, pixelFormat, buffer, 0, W);
            }
        };
        timer.start();

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

    private int toInt(Color c) {
        return
                (                      255  << 24) |
                ((int) (c.getRed()   * 255) << 16) |
                ((int) (c.getGreen() * 255) << 8)  |
                ((int) (c.getBlue()  * 255));
    }

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

JavaFX PulseLogger -Djavafx.pulseLogger=true. , PixelWriter 50 , 100 , setColor PixelWriter.

FillRect

//PULSE: 81 [217ms:424ms]
//T15 (58 +0ms): CSS Pass
//T15 (58 +0ms): Layout Pass
//T15 (58 +0ms): Update bounds
//T15 (58 +155ms): Waiting for previous rendering
//T15 (214 +0ms): Copy state to render graph
//T12 (214 +0ms): Dirty Opts Computed
//T12 (214 +209ms): Painting
//T12 (424 +0ms): Presenting
//Counters:
//Nodes rendered: 2
//Nodes visited during render: 2

pixelwriter setColor

//PULSE: 33 [370ms:716ms]
//T15 (123 +0ms): CSS Pass
//T15 (123 +0ms): Layout Pass
//T15 (123 +0ms): Update bounds
//T15 (123 +244ms): Waiting for previous rendering
//T15 (368 +0ms): Copy state to render graph
//T12 (368 +0ms): Dirty Opts Computed
//T12 (368 +347ms): Painting
//T12 (715 +0ms): Presenting
//Counters:
//Nodes rendered: 2
//Nodes visited during render: 2

pixelwriter

//PULSE: 270 [33ms:37ms]
//T15 (28 +0ms): CSS Pass
//T15 (28 +0ms): Layout Pass
//T15 (28 +0ms): Update bounds
//T15 (28 +0ms): Waiting for previous rendering
//T15 (28 +0ms): Copy state to render graph
//T12 (29 +0ms): Dirty Opts Computed
//T12 (29 +7ms): Painting
//T12 (36 +0ms): Presenting
//Counters:
//Nodes rendered: 2
//Nodes visited during render: 2
+5

: . .

- , BufferedImage, , . , .

int[] rgbArray, BufferedImage#setRGB .

+1

All Articles