Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to improve JavaFX Clip performance #2

Open
yososs opened this issue Dec 17, 2020 · 3 comments
Open

How to improve JavaFX Clip performance #2

yososs opened this issue Dec 17, 2020 · 3 comments

Comments

@yososs
Copy link

yososs commented Dec 17, 2020

With the following changes, the JavaFX clip benchmark fps is now
My macbook has improved to 28fps.

JavaFX's GraphicsContext implementation allocates a canvas-sized texture for each call to the clip method, making multiple calls costly. This can be confirmed with the following jvm option.

-Dprism.printallocs=true

It can be improved to a practical fps by performing clips all at once as follows.

		gc.save();
		gc.beginPath();
		for (int y = 5; y <= height - 25; y += 25) {
			for (int x = 5; x <= width - 25; x += 25) {
				gc.save();
				gc.translate(x, y);
				gc.moveTo(10, 10);
				gc.arc(10, 10, 10, 10, 0, 360);
				gc.arc(10, 10, 5, 5, 0, 360);
				gc.restore();
			}
		}
		gc.clip();

		gc.setFillRule(FillRule.EVEN_ODD);
		for (int y = 5; y <= height - 25; y += 25) {
			for (int x = 5; x <= width - 25; x += 25) {
				gc.setFill(Color.color((double) x / width, 0.5 * (1 - x / width), (double) y / height));
				gc.save();
				gc.translate(x, y);
				gc.fillRect(0, 0, 20, 20);
				gc.restore();
			}
		}
		gc.restore();

In addition, specifying the following jvm option will increase by a few fps.

-Dprism.marlin.profile=speed

@bourgesl
Copy link

I looked at the initial test (without patch) and found the hotspot: createTexture/disposeTexture from clip's NGCanvas$RenderBuf.validate ():

...............com.sun.javafx.sg.prism.NGNode.doRender () | 45 693 ms (-0 %) | 45 693 ms (-0 %) | 52
-- | -- | -- | --
................com.sun.javafx.sg.prism.NGCanvas.renderContent () | 45 693 ms (-0 %) | 45 693 ms (-0 %) | 52
.................com.sun.javafx.sg.prism.NGCanvas.renderStream () | 45 693 ms (-0 %) | 45 693 ms (-0 %) | 52
..................com.sun.javafx.sg.prism.NGCanvas.initClip () | 38 649 ms (-0 %) | 38 649 ms (-0 %) | 107
...................com.sun.javafx.sg.prism.NGCanvas$RenderBuf.validate () | 34 682 ms (-0 %) | 34 682 ms (-0 %) | 128
....................com.sun.prism.es2.ES2ResourceFactory.createRTTexture () | 34 080 ms (-0 %) | 34 080 ms (-0 %) | 130
.....................com.sun.prism.es2.ES2ResourceFactory.createRTTexture () | 34 080 ms (-0 %) | 34 080 ms (-0 %) | 130
......................com.sun.prism.es2.ES2RTTexture.create () | 34 080 ms (-0 %) | 34 080 ms (-0 %) | 130
.......................com.sun.prism.es2.GLContext.createFBO () | 32 093 ms (-0 %) | 32 093 ms (-0 %) | 136
........................com.sun.prism.es2.GLContext.nCreateFBO[native] () | 32 093 ms (-0 %) | 32 093 ms (-0 %) | 136
........................Self time | 0,0 ms (0 %) | 0,0 ms (0 %) | 136
.......................com.sun.prism.es2.GLContext.createTexture () | 1 690 ms (-0 %) | 1 690 ms (-0 %) | 18
........................com.sun.prism.es2.GLContext.nCreateTexture[native] () | 1 690 ms (-0 %) | 1 690 ms (-0 %) | 18
........................Self time | 0,0 ms (0 %) | 0,0 ms (0 %) | 18
.......................com.sun.prism.es2.GLContext.bindFBO () | 296 ms (-0 %) | 296 ms (-0 %) | 2
........................com.sun.prism.es2.GLContext.nBindFBO[native] () | 296 ms (-0 %) | 296 ms (-0 %) | 2
........................Self time | 0,0 ms (0 %) | 0,0 ms (0 %) | 2
....................com.sun.prism.es2.ES2Graphics.clear () | 405 ms (-0 %) | 405 ms (-0 %) | 4
.....................com.sun.prism.es2.ES2Graphics.clearBuffers () | 405 ms (-0 %) | 405 ms (-0 %) | 4
......................com.sun.prism.es2.GLContext.clearBuffers () | 405 ms (-0 %) | 405 ms (-0 %) | 4
.......................com.sun.prism.es2.GLContext.nClearBuffers[native] () | 405 ms (-0 %) | 405 ms (-0 %) | 4
....................com.sun.prism.es2.ES2RTTexture.createGraphics () | 196 ms (-0 %) | 196 ms (-0 %) | 2
... 

The hotspot is GLContext.createFBO () called by ES2ResourceFactory.createRTTexture () so it means every clip is creating / disposing a texture without any caching (GC): that would be a great win to reuse a texture cache for the clip (its own resource ?)

It confirms why your patch is faster: perform only 1 GLContext.createFBO () call instead of many ?

Finally the hotspot concerns the clip handling (texture + GPU shader) and not the Marlin renderer having its own path clipper.

@yososs
Copy link
Author

yososs commented Jan 14, 2021

I think there are two reasons why some JavaFX Clip processing is slow.

  • (A) A mask image is generated for each call to clip.
  • (B) The mask image is not the minimum but canvas size

My patch avoids A's problem. B's problem hasn't been solved in my workaround. If A and B can be optimized, I think the FPS will be better than my workaround.

@PavelTurk
Copy link

PavelTurk commented Jun 23, 2024

I found out that the problem is in using doubles as parameters for setting clip shape. Try variant 1 and 2:

public class JavaFxTest7 extends Application {

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

  @Override
  public void start(Stage primaryStage) {
      Canvas canvas = new Canvas(600, 600);
      GraphicsContext gc = canvas.getGraphicsContext2D();
      gc.setFill(Color.GREEN);

      var timeLine = new Timeline(new KeyFrame(Duration.millis(1000 / 50), (e) -> {
        gc.clearRect(0, 0, 600, 600);
        for (var i = 0; i < 2000; i++) {
          //variant #1
          var x = ThreadLocalRandom.current().nextInt(0, 600);
          var y = ThreadLocalRandom.current().nextInt(0, 600);
          //variant #2
//          var x = ThreadLocalRandom.current().nextDouble(0, 600);
//          var y = ThreadLocalRandom.current().nextDouble(0, 600);
          gc.save();
          gc.beginPath();
          gc.rect(x, y, canvas.getWidth() - x, canvas.getHeight() - y);
          gc.closePath();
          gc.clip();
          gc.fillText(String.valueOf(i), (int) x, (int) y + 20);
          gc.restore();
        }

      }));
      timeLine.setCycleCount(Timeline.INDEFINITE);
      timeLine.play();

      Pane root = new Pane();
      root.getChildren().add(canvas);
      primaryStage.setScene(new Scene(root));
      primaryStage.show();
  }
}

I opened a bug and can share the link when they give ID to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants