Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

Canvas Rendering Degrades Proportionally With Size?

ShindohApr 18 2014 — edited Apr 21 2014

Can someone explain to me how I can avoid the performance decline of drawing on a Canvas, as it's size gets bigger?
What's the reason for this?

The same stroke on a Canvas is executed more and more slowly, as the size of the Canvas increases.

It also seems that, despite "dirty-area" rendering techniques used by JavaFX, rendering of the scene becomes slower as the size of Nodes increases (ex.: the Canvas),
even when the bulk of it is offscreen. Speed is acceptable when the Canvas completely fills my screen with it's size, so the whole screen already needs to be redrawn,
but as I increase the size, so the Node/Canvas already extends outside of the Window, rendering performance continually decreases as the Node gets bigger.

This is remedied, when setCache(true) is applied.
This does however NOT improve drawing performance on the Canvas, where low latencies are critical for a Painting App.
Which seems to be implied, as the documentation advises NOT to Cache, if the Node changes frequently.
Surprisingly then, still setting Cache to True on the currently drawn on Canvas, plus CacheHint = SPEED, seems to improve speed a little.

Anyway, back to the question: Why is drawing on a Canvas becoming slower with size, although the same stroke, covering the same amount of area, is executed?

Apologies if I sound confusing or repeated myself, I am extremely tired. Thank You in advance, and good night.

This post has been answered by jsmith on Apr 20 2014
Jump to Answer

Comments

jsmith

Create an sscce which reproduces the issue and log it against the Graphics component of the Runtime project in the JavaFX issue tracker, then link a reference to the ticket here.

Include full environment details in your report - OS, Java version, video card make, model and version, resolutions used, hardware details, etc.

Shindoh

Hi jsmith,

jsmith wrote:

Create an sscce which reproduces the issue and log it against the Graphics component of the Runtime project in the JavaFX issue tracker, then link a reference to the ticket here.

Include full environment details in your report - OS, Java version, video card make, model and version, resolutions used, hardware details, etc.


JavaFX Issue Tracker Ticket: https://javafx-jira.kenai.com/browse/RT-36743

My Environment:
- Windows 7 64bit
- Java 7
- Resolution: 1600 X 900
- RAM: 8GB
- CPU: Intel Core i7-2675QM 2.20GHz
- GFX: Radeon HD 6775M

As context, I am working on a Painting Application.

The following is simply a StackPane with a Canvas of size 10000 X 10000.
This extreme size is just for emphasis purposes, as I don't know how powerful Your machine is.
For art, most artists wouldn't go above 3000 X 3000, where latency is already getting pretty ugly for my machine, with this issue.

The Canvas has a MouseEventHandler attached, which allows You to freely draw on it.

You will notice, that as You decrease the Canvas size in the code, drawing will become more and more smooth.

After some testing, it seems like the whole Canvas is redrawn, for every small modification done in some area on it.
Even when the bulk of it is off-screen.

That would imply that:
a.) Not just the area the stroke occurred in is redrawn, but the whole Canvas. And...
b.) Not just the portion of the Canvas visible on the screen is redrawn, but the whole Canvas.

Depth of my Knowledge:
I am not a professional in Computer Graphics Programming, so those are my humble guesses.
I am also still a Beginning JavaFX & Java Learner, with about 4 Months Java, and 2 Months JavaFX experience.
I am not trying to waste anyone's time or to troll anyone. A simple guide in the right direction would also be appreciated.

Here is the SSCCE:

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class CanvasPerformanceDecline extends Application
{        
    @Override    
    public void start(Stage primaryStage)    
    {                
        //Create Canvas and get GraphicsContext:        
        final Canvas          canvas          = new Canvas( 10000, 10000 );        
        final GraphicsContext graphicsContext = canvas.getGraphicsContext2D();                

        //Allow DRAWING on Canvas with Mouse:        
        canvas.addEventHandler( MouseEvent.ANY,
                                
                                new EventHandler<MouseEvent>()                                
                                {                                    
                                    double pmouseX;                                    
                                    double pmouseY;   
                                                                    
                                    @Override                                    
                                    public void handle(MouseEvent mouseEvent)                                    
                                    {                                        
                                        EventType eventType = mouseEvent.getEventType();
                                                                                
                                        if (eventType == MouseEvent.MOUSE_PRESSED)                                        
                                        {                                            
                                            pmouseX = mouseEvent.getX();                                            
                                            pmouseY = mouseEvent.getY();                                        
                                        }                                        
                                        if (eventType == MouseEvent.MOUSE_DRAGGED)                                        
                                        {                                            
                                            double mouseX = mouseEvent.getX();                                            
                                            double mouseY = mouseEvent.getY();                                                                                                                                                            
                                            //Stroke Line:                                            
                                            graphicsContext.strokeLine( pmouseX, pmouseY,mouseX,mouseY);
                                                                                        
                                            pmouseX = mouseX;                                            
                                            pmouseY = mouseY;                                        
                                        }                                    
                                   } //END - public void handle(...)
                                
                              }); //END - canvas.addEventHandler(...)
  
                     
        //PREPARE SCENE & STAGE:        
        StackPane root    = new StackPane();                  
                  root.getChildren().add(canvas); 
              
        Scene     scene   = new Scene(root, 300, 250);   
            
                  primaryStage.setTitle("CanvasPerformanceDecline");        
                  primaryStage.setScene(scene);        
                  primaryStage.show();
     
    } //END - public void start(...)  

        
    public static void main(String[] args)    
    {        
        launch(args);    
    }
    
} //END - class CanvasPerformanceDecline

Thanks for Your time and attention.

Message was edited by: Shindoh Added Link to JavaFX Issue Tracker Ticket

jsmith
Answer

That is a nice descriptive reply Shindoh.  You have a talent for this stuff.

Replicating your issue

Your application doesn't even run on my machine (Java 8u5, Win 7, 64 bit, ATI Radeon HD 4600), unless I drop the canvas size to 8Kx8K (probably because I am using an older graphics card with limited texturing capabilities).  As I drop the canvas size I notice performance improvements as you outline, from 8K being really slow to update and pretty unusable to 1K being pretty snappy.

The exception I get for a 10Kx10K canvas is =>

java.lang.NullPointerException

    at com.sun.javafx.sg.prism.NGCanvas$RenderBuf.validate(NGCanvas.java:199)

    at com.sun.javafx.sg.prism.NGCanvas.initCanvas(NGCanvas.java:598)

    at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:575)

    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)

    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)

    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)

    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)

    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)

    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)

    at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:469)

    at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:324)

    at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:89)

    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)

    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)

    at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)

    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

    at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:129)

    at java.lang.Thread.run(Thread.java:745)

Some suggestions

10Kx10K is a really large canvas.

Creating such thing is a really a kind of brute force solution.

I think you might need to get more clever about you handle such situations.

I think relying on the library to handle optimization of such canvases is not the way to go.

Sure, the library might handle it OK, but your tests show otherwise, so you will have to work out a different solution.

What you can do is take some domain specific knowledge from your application to help optimize how the canvas is used so that you can get by with a smaller canvas.

For example, consider the technique involved in Writing a Tile Engine in JavaFX. This is way for rendering old-school style graphical games such as Pokemon or Zelda on to a canvas.  There is a backing data format which is the TileMap, there is some control logic which keeps track of the co-ordinates of the TileMap currently visible and there is a renderer which just renders the currently visible tile co-ordinates to the screen.  You could apply a similar approach to your application - this would allow you to keep the size of the canvas limited to just the size of the visible screen portion.  Of course the nature of TileMaps make them particularly well optimized for this approach, so the solution will not be directly transferable to your application.

Another project which demostrates rendering to an almost infinitely large area is the Grezi project which defines a zoomable user interface for JavaFX, though it uses the SceneGraph and not Canvas for this.

The question may be "Why should I should I put extra, more complicated logic in my code to efficiently draw on a canvas?" i.e. "Why can't the library take care of such things for me?".  I think the answers are:

1. The current canvas implementation on various platforms may not be optimized completely for dirty region processing and overflow of visible region to offscreen areas.

2. The canvas implementation can't make use of any domain specific drawing optimizations which your application may know of.

3. You may benefit from a MVC style architecture for your drawing anyway.

4. The canvas needs to provide a very general purpose solution which makes it work well in many situations but may make it not optimal for your particular situation.

See for example => The best optimizer is between your ears.

Another option which you might want to try is to use the SceneGraph instead of a Canvas.  The library optimization around large dimension scene graphs and dirty area/offscreen area processing may be more efficient in the case of SceneGraphs versus Canvases, especially in the case of reasonably sparsely populated scene graphs.

Regardless of what you do, if you want to render area for 4K screens, I think that JavaFX (and even some of the other computing pipeline components such as native libraries, graphics drivers, hardware to screen interfaces etc), are not particularly well optimized for processing such high resolutions at this time.  This will likely change over time, but expect teething issues if you try to render at such resolutions at this time.

One other thing I recall is that, at a lower level, JavaFX often renders content to textures on a video card.  Most video cards have some kind of limit to the maximum texture size, and when this limit is hit, there needs to be some extra translation layer somewhere in the library code which maps content to multiple video card textures and when this rendering path is invoked, things slow down markedly.  This limit might commonly be around 4Kx4K of 8Kx8K (I'm not sure).

My guess is that this is probably not the answer you wanted ;-)

Marked as Answer by Shindoh · Sep 27 2020
Shindoh

jsmith wrote:

That is a nice descriptive reply Shindoh.  You have a talent for this stuff.

Replicating your issue

Your application doesn't even run on my machine (Java 8u5, Win 7, 64 bit, ATI Radeon HD 4600), unless I drop the canvas size to 8Kx8K (probably because I am using an older graphics card with limited texturing capabilities).  As I drop the canvas size I notice performance improvements as you outline, from 8K being really slow to update and pretty unusable to 1K being pretty snappy.

The exception I get for a 10Kx10K canvas is =>

java.lang.NullPointerException

    at com.sun.javafx.sg.prism.NGCanvas$RenderBuf.validate(NGCanvas.java:199)

    at com.sun.javafx.sg.prism.NGCanvas.initCanvas(NGCanvas.java:598)

    at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:575)

    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)

    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)

    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)

    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)

    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)

    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)

    at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:469)

    at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:324)

    at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:89)

    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)

    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)

    at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)

    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

    at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:129)

    at java.lang.Thread.run(Thread.java:745)

Some suggestions

10Kx10K is a really large canvas.

Creating such thing is a really a kind of brute force solution.

I think you might need to get more clever about you handle such situations.

I think relying on the library to handle optimization of such canvases is not the way to go.

Sure, the library might handle it OK, but your tests show otherwise, so you will have to work out a different solution.

What you can do is take some domain specific knowledge from your application to help optimize how the canvas is used so that you can get by with a smaller canvas.

For example, consider the technique involved in Writing a Tile Engine in JavaFX. This is way for rendering old-school style graphical games such as Pokemon or Zelda on to a canvas.  There is a backing data format which is the TileMap, there is some control logic which keeps track of the co-ordinates of the TileMap currently visible and there is a renderer which just renders the currently visible tile co-ordinates to the screen.  You could apply a similar approach to your application - this would allow you to keep the size of the canvas limited to just the size of the visible screen portion.  Of course the nature of TileMaps make them particularly well optimized for this approach, so the solution will not be directly transferable to your application.

Another project which demostrates rendering to an almost infinitely large area is the Grezi project which defines a zoomable user interface for JavaFX, though it uses the SceneGraph and not Canvas for this.

The question may be "Why should I should I put extra, more complicated logic in my code to efficiently draw on a canvas?" i.e. "Why can't the library take care of such things for me?".  I think the answers are:

1. The current canvas implementation on various platforms may not be optimized completely for dirty region processing and overflow of visible region to offscreen areas.

2. The canvas implementation can't make use of any domain specific drawing optimizations which your application may know of.

3. You may benefit from a MVC style architecture for your drawing anyway.

4. The canvas needs to provide a very general purpose solution which makes it work well in many situations but may make it not optimal for your particular situation.

See for example => The best optimizer is between your ears.

Another option which you might want to try is to use the SceneGraph instead of a Canvas.  The library optimization around large dimension scene graphs and dirty area/offscreen area processing may be more efficient in the case of SceneGraphs versus Canvases, especially in the case of reasonably sparsely populated scene graphs.

Regardless of what you do, if you want to render area for 4K screens, I think that JavaFX (and even some of the other computing pipeline components such as native libraries, graphics drivers, hardware to screen interfaces etc), are not particularly well optimized for processing such high resolutions at this time.  This will likely change over time, but expect teething issues if you try to render at such resolutions at this time.

One other thing I recall is that, at a lower level, JavaFX often renders content to textures on a video card.  Most video cards have some kind of limit to the maximum texture size, and when this limit is hit, there needs to be some extra translation layer somewhere in the library code which maps content to multiple video card textures and when this rendering path is invoked, things slow down markedly.  This limit might commonly be around 4Kx4K of 8Kx8K (I'm not sure).

My guess is that this is probably not the answer you wanted ;-)

First of all, THANKS for the compliment! It means a lot to me. I always give my best.


And...
"My guess is that this is probably not the answer you wanted ;-)"

Of course I would have preferred to hear something like: "Oh, the slowdown is because of this and that, just change this part or set this to that." lol
I already learned a valuable lesson a while ago, with "Don't re-invent the wheel", which I always try to keep in mind when I do things now.
So before I went ahead and invested some more serious time in finding ways to optimize it myself, I had to make sure I didn't miss something.

There's also not that much info on Optimizing JavaFX Applications, I think most of what I found is in fact written by You.
That's one point that made me feel strange. There's almost no official information on optimizing JavaFX application performance,
yet here I am with this weird slowdown I can't explain. All other big nodes render just fine.
I thought it can't just be me who is experiencing it, that would be too weird - I had to be missing something. So I had to ask.

In fact You helped me a great deal with Your elaborate reply, for which I can't thank You enough!

You did...:
a.) Make me stop looking for something I think I am missing, thus giving me the confidence to now start working on a solution of my own.
b.) Ignite 2 sparks of new ideas in me:
     1.) How to improve performance by I think an order of magnitude by using the Tiling approach You mentioned, in combination with setCache(), setVisible()/isVisible(), and dirty rectangles.

     2.) How to solve another problem I had, unrelated to this, but which also has to do with Canvas and drawing.
c.) You made me aware of the Graphics Card Texture Size limitation, which could have severely backfired on me later and would have totally taken me by surprise, had You not told me about it now.

Plus, all of this combined gave me another huge boost of motivation (knowing exactly what I am going to do next), which I needed, after spending almost half a year in solitude and self-study.
Sometimes You just need someone to talk to, to get fresh ideas, in order to move on / forward, it seems.

Thanks for being around, jsmith.
I know it's mainly You who is answering most people's questions regarding JavaFX.
At least on my JavaFX searches I kept stumbling upon You. You're also jewelsea, right?
Thanks for the continued dedication of helping people out.

(Hope I'm not too sentimental here, but I can't help it. : )

1 - 4
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on May 19 2014
Added on Apr 18 2014
4 comments
6,602 views