SWTonFX – JavaFX Canvas with many clipping calls == unacceptable slow


Inspired by a twitter discussion started by Andres Almiray if NatTable could be run in JavaFX application I checked out the sources, added some missing API to SWTonFX GC implementation and voila it renders a NatTable sample as the SWT version does

swtonfx_nattable

The problem

The “only” problem is it takes an unacceptable time to do – so i fired up JavaMissionControl to see where time is spent but there was no high CPU consumption, hot methods, … nothing really problematic showed up so i started add some System.out.println() into the code and the rendering calls (stroke-line, draw-rect, draw-text, …) themselves finish in no time – that mapped quite good to the output JavaMissionControl showed to me.

I started digging deeper, going through the NatTable rendering code to see what calls they do on the SWT-GC and the only really interesting stuff I found was that they’ll make heavy use of clipping GC#setClipping(Region) – for the NatTable above I counted approximately 1,500 clipping calls who turned out to be the root of the problem.

SWT-Canvas & GC in SWTonFX

Before we proceed with the problem above, it makes sense to understand how the GC is implemented in SWTonFX. By default SWTonFX makes use of the JavaFX SceneGraph whenever possible e.g. to construct controls (frankly we simply use the controls who are part of JavaFX) but if someone attaches a SWT.Paint-Listener we detect that and embed a JavaFX canvas into the control space and provide you a GC implementation which works like this:

public class CanvasGC implements /*Internal API*/ DrawableGC {
   private Canvas canvas;

   public CanvasGC(javafx.scene.canvas.Canvas fxCanvas /* ... */) {
     this.canvas = canvas;
   }

   public void fillRectangle(int x, int y, int width, int height) {
     canvas.getGraphicsContext2D().fillRect(x, y, width, height);
   }

   // ...
}

and when a GC is created it does

public class GC extends Resource {
  private DrawableGC gc;

  public GC(Drawable drawable) {
    // ...
    gc = drawable.internal_new_GC();
  }

  public void fillRectangle(int x, int y, int width, int height) {
    gc.fillRectangle(x,y,width,height);
  }
}

which is implemented e.g. for Composite with:

public class Composite extends Scrollable {
  public DrawableGC internal_new_GC() {
    if( canvas == null ) {
      return super.internal_new_GC();
    } else {
      return new CanvasGC(canvas,
                          getFont(),
                          getBackground(),
			  getForeground());	
    }
  }
}

This architecture makes the GC stuff extremely flexible because we can have different GC implementation strategy based on the control.

JavaFX Canvas and clipping

Clipping on JavaFX canvas works a bit differently than on the GC API and the following the code I came up with to implement the behavior:

public class CanvasGC implements /*Internal API*/ DrawableGC {
  // ...

  public void setClipping(Region region) {
    setClipping((PathIterator)(region != null ? region.internal_getNativeObject().getPathIterator(null) : null));
  }

  private void setClipping(PathIterator pathIterator) {
    if( activeClip ) {
      canvas.getGraphicsContext2D().restore();
    }
    
    if( pathIterator == null ) {
      return;
    }
		
    activeClip = true;

    float coords[] = new float[6];
    GraphicsContext gc = canvas.getGraphicsContext2D();
    gc.save();
    gc.beginPath();
		
    float x = 0;
    float y = 0;
		
		
    gc.moveTo(0, 0);
		
    while( ! pathIterator.isDone() ) {
      switch (pathIterator.currentSegment(coords)) {
        case PathIterator.SEG_CLOSE:
          gc.lineTo(x, y);
          break;
        case PathIterator.SEG_CUBICTO:
          gc.bezierCurveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
          break;
        case PathIterator.SEG_LINETO:
          gc.lineTo(coords[0], coords[1]);
          break;
        case PathIterator.SEG_MOVETO:
          gc.moveTo(coords[0], coords[1]);
          x = coords[0];
          y = coords[1];
          break;
        case PathIterator.SEG_QUADTO:
          gc.quadraticCurveTo(coords[0], coords[1], coords[2], coords[3]);
          break;
        default:
          break;
      }
      pathIterator.next();
    }
    gc.clip();
    gc.closePath();
  }
}

Now I don’t know exactly why this is so damn slow in JavaFX but the fact is – on JavaFX8 this makes JavaFX Canvas unusable to implement the GC for SWTonFX – if you know there are going to be many clipping calls. Enough for today – tomorrow I tell you the solution I’m currently targeting.

Advertisement
This entry was posted in e(fx)clipse. Bookmark the permalink.

2 Responses to SWTonFX – JavaFX Canvas with many clipping calls == unacceptable slow

  1. dlemmermann says:

    I also used a lot of clipping for my FlexGanttFX framework (to keep text limited to / inside of the time bars) and noticed a heavy performance loss. Since this was only about text I was able to use the maxWidth parameter of the fillText() method on GraphicsContext. I got away this way πŸ™‚

    • Tom Schindl says:

      unfortunately this does not work for me 😦 – so my only current solution is instead of drawing on JavaFX canvas to make all drawings on a buffered image and renderer that in JavaFX – but that gives me bad font rendering (at least on OS-X) – problem is that awt is not available everywhere – e.g. embedded – I really hope OpenJFX devs will improve this situation – at least for rectangular clippings

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.