2

My application uses canvases to draw diagrams, placed in multiple tab panes. On wide screen monitors, after creating more than 10 tabs I got exceptions like below. I think this comes from the Canvas high video memory usage.

My question is, if is any way to release the Canvas memory usage. I would trigger this for the canvases in the un-selected tabs in the tab pane. I tried to do canvas.setSize(0,0), but this seems not to work always. It would be great if this would work on canvas.setDisable(true).

Code to reproduce on a monitor with high resolution. Decrease the number of tabs to 5 and everything works fine.


import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class CanvasIssue extends Application {
    private final StackPane root = new StackPane();
    private final Scene scene = new Scene(root, 300, 250);


    @Override
    public void start(Stage stage) {
        stage.setTitle("Sample Canvas");

        TabPane tabPane = new TabPane();
        for ( int i = 0; i < 30; i++) {
            tabPane.getTabs().add(new Tab("Tab " + tabPane.getTabs().size(), setupCanvasPane()));
        }

        root.getChildren().add(tabPane);
        stage.setScene(scene);
        stage.sizeToScene();
        setupCanvasPane();
        stage.show();
    }
    private Pane setupCanvasPane(){
        final BorderPane pane = new BorderPane();
        final Canvas canvas = new Canvas(){
            @Override
            public boolean isResizable() {
                return true;
            }

            @Override
            public double prefWidth(double height) {
                return getWidth();
            }

            @Override
            public double prefHeight(double width) {
                return getHeight();
            }
        };
        pane.setCenter( canvas );
        canvas.widthProperty().bind(pane.widthProperty());
        canvas.heightProperty().bind(pane.heightProperty());
        pane.widthProperty().addListener((o,p,c)-> paint(canvas));
        paint( canvas );
        return pane;
    }

    public void paint( Canvas canvas ){
        GraphicsContext gr = canvas.getGraphicsContext2D();
        gr.clearRect( 0,0, canvas.getWidth(), canvas.getHeight() );
        gr.setFill( Color.web("#ffffff") );
        gr.fillRect( 0,0,canvas.getWidth(), canvas.getHeight());
        gr.setFont( Font.font( "Monospaced"));
        gr.setFill( Color.BLACK );
        gr.fillText("This is a", 50, 70 );
        gr.setFill( Color.RED );
        gr.fillText("demo", 50, 88 );
    }



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


The exception I get by higher number of tabs:

java.lang.NullPointerException: Cannot invoke "com.sun.prism.RTTexture.createGraphics()" because "<local9>" is null
    at com.sun.javafx.sg.prism.NGCanvas$RenderBuf.validate(NGCanvas.java:214)
    at com.sun.javafx.sg.prism.NGCanvas.initCanvas(NGCanvas.java:644)
    at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:607)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2313)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2207)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2233)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2066)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2313)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2207)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2233)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2066)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2313)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2207)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2233)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2066)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2313)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2207)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2233)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2066)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2313)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2207)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2233)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2066)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2313)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2207)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2233)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2066)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2313)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2207)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2233)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2066)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:480)
    at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:321)
    at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:92)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
    at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
    at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:126)
    at java.base/java.lang.Thread.run(Thread.java:832)

DbSchema
  • 413
  • 5
  • 16
  • _I think this comes from the Canvas high video memory usage_ wondering why you think so? the exception is a NPE (the _exact_ reason for which you try to track down) Anyway, we would need a [mcve] to be able to help – kleopatra Apr 03 '23 at 09:15
  • Stack traces like that (NPEs with none of your code involved) are more often symptoms of threading errors. Are you using background threads in this application? As mentioned in another comment, you need to provide a [mre]. – James_D Apr 03 '23 at 10:36
  • 1
    Texture issues can also be caused by texture size limits of graphics devices. If you try to create a texture too large, it will fail. Size limit depends on the graphics card, but 4096x4096 or 8192x8192 are examples of limits which might be in place. – jewelsea Apr 03 '23 at 11:28
  • 3
    "if is any way to release the Canvas memory usage" -> the same as releasing the memory for any other heap object in java, which is to remove all [strong references](https://stackoverflow.com/questions/9809074/java-difference-between-strong-soft-weak-phantom-reference), which will make the object eligible for garbage collection. – jewelsea Apr 03 '23 at 11:32
  • I don't have a specific part of code to reproduce this issue. The application which is getting this is called [DbSchema](https://dbschema.com). It can be downloaded and tested for free. Start a new model (connected or not to a database) and create 10 layouts, on a wide screen monitor. You will find the exception in Help / Output logs. The same issue does not happen on normal monitors. I checked carefully any possible issue with the threading, everything is fine. – DbSchema Apr 03 '23 at 13:10
  • thanks for the [mcve] - I can reproduce it with tab counts > 40 (normal monitor, win10): run and maximize window. Don't see anything obviously wrong in the code, so might be a bug that you might consider reporting. – kleopatra Apr 05 '23 at 11:01
  • I have such a large 21:9 UltraWide™ WUHD (5120 x 2160) Monitor and the app starts crashing for me when I go beyond 12 tabs in full-screen mode. – mipa Apr 05 '23 at 12:28
  • This issue could be as well because of hardware limitations. There is some documentation about it on: https://wiki.openjdk.org/pages/viewpage.action?pageId=20415996 Setting for example -Dprism.maxvram=2048M might help. Oher way would be reusing one canvas. Also take advice from other commentators. – Allesio Apr 06 '23 at 08:40

1 Answers1

2

What I would try to do is to virtualize the Tab so that the canvas is only allocated if this tab becomes the selected one and remove the canvas from the scene graph when another tab is selected. So only one possibly large canvas exists at a time.

mipa
  • 10,369
  • 2
  • 16
  • 35