What approach should be used for JavaFX Canvas multithreading?

I am writing a JavaFX application that receives data points on a socket and renders them in real time. The problem is that JavaFX rendering is too slow. I have a Swing implementation that is fast enough, but I need to use JavaFX instead.

The constraints I'm working on are as follows:

  • The render control should only update with the JavaFX Application Stream (I believe this is required for all JavaFX and Swing applications).
  • The visualization should update smoothly from the perspective of the human eye. Enough 10 updates per second. Once every second is not enough.
  • The incoming data rate is high enough (about 50 events per second, which is not that high in other contexts) and it is expensive enough to handle events for the incoming data to be received and processed in a thread other than a JavaFX application thread so that the GUI is not blocked ( I find this is a fairly common requirement for many graphics applications).

So far, my approach has been to use the Canvas JavaFX node as the render control and for the receive thread to schedule Canvas updates for later use on the JavaFX application thread, for example.

    public void onEvent(Event event) {
        ....do processing... 
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                graphics.setFill(...);
                graphics.fillRect(...);
                }});
    }

      

I thought of several approaches that can speed this up:

  • Use WritableImage instead of Canvas for rendering. The downside is that the WritableImage / PixelWriter doesn't seem to have many drawing methods, for example doesn't even have a fillRect. I think I will have to implement my own versions, and my versions will probably be slower.
  • Have a Canvas object owned by the thread that processes the incoming data. Copy from this canvas to canvas which is a node in the scene graph in the JavaFX application thread. A copy will probably be done with the code along these lines sceneCanvas.getGraphicsContext2D().drawImage(processingCanvas.snapshot(SnapshotParameters(), null) 0, 0);

    . The downside to this is that I think it is not thread safe and it seems that calling a snapshot is relatively expensive.
  • Render to AWT BufferedImage on a stream that processes the incoming data and then copies from the BufferedImage to the canvas using SwingFXUtils.toFXImage (). The downside to this is that the threading semantics seem unclear and it seems a little silly to use AWT.

Can you suggest some potential approaches?

Thank!

+3


source to share


1 answer


I guess the main problem is that your code is forcing too many drawing tasks into the queue of your FX application thread. Typically 60 draws per second is sufficient, which is the refresh rate of your monitor. If you receive more "input" events than this, you will draw more often than necessary, wasting CPU. Therefore, you must separate data processing from drawing.

One solution is to use AnimationTimer

. His method handle

will be called every frame of the animation, so typically 60 times per second. The animation timer handles redrawing when new data is processed.



// generic task that redraws the canvas when new data arrives
// (but not more often than 60 times per second).
public abstract class CanvasRedrawTask<T> extends AnimationTimer {
    private final AtomicReference<T> data = new AtomicReference<T>(null);
    private final Canvas canvas;

    public CanvasRedrawTask(Canvas canvas) {
        this.canvas = canvas;
    }

    public void requestRedraw(T dataToDraw) {
        data.set(dataToDraw);
        start(); // in case, not already started
    }

    public void handle(long now) {
        // check if new data is available
        T dataToDraw = data.getAndSet(null);
        if (dataToDraw != null) {
            redraw(canvas.getGraphicsContext2D(), dataToDraw);
        }
    }

    protected abstract void redraw(GraphicsContext context, T data);
}

// somewhere else in your concrete canvas implementation
private final RedrawTask<MyData> task = new RedrawTask<MyData>(this) {
    void redraw(GraphicsContext context, MyData data) {
        // TODO: redraw canvas using context and data
    }
}

// may be called by a different thread
public void onDataReceived(...) {
    // process data / prepare for redraw task
    // ...

    // handover data to redraw task
    task.requestRedraw(dataToDraw);
}

      

+2


source







All Articles