4

According to this bug report attempting to create a Canvas that is too large for the graphics system fails "silently" by only dumping a NullPointerException stack trace to the console. However, in my application the canvas size can be based on user input so I need to detect this. But since the NPE is caught in a background JavaFX thread, we can't rely on it to detect the issue. Any idea how I could programmatically detect that the Canvas creation failed, from within the application thread?

Results will vary based on hardware, but a large enough size should exercise the problem

public class Test extends Application {
    
    public static void main(String[] args) {
        launch(args);
    }
    
    public void start(Stage stage) {
        Button button = new Button("Create large canvas");
        HBox hbox = new HBox(button);
        button.setOnAction(e -> { 
            hbox.getChildren().add(new Canvas(500,50000));
            // Did I get an NPE or can the Canvas render?
            });
        stage.setScene(new Scene(hbox));
        stage.show();
    }
}

In my case this leads to (as visible on the console):

java.lang.NullPointerException
    at javafx.graphics/com.sun.javafx.sg.prism.NGCanvas$RenderBuf.validate(NGCanvas.java:213)
    at javafx.graphics/com.sun.javafx.sg.prism.NGCanvas.initCanvas(NGCanvas.java:641)
    at javafx.graphics/com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:604)
    at javafx.graphics/com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
    at javafx.graphics/com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
    ...
SDJ
  • 4,083
  • 1
  • 17
  • 35
  • what's wrong with surrounding the canvas creation with a try-catch block? – kleopatra Oct 05 '20 at 11:31
  • okay, now I see what you mean (note to self: do run the mcve ;) .. looks like the graphics/context of the canvas is created lazily at the time it gets inserted into the scenegraph (vs. at creation time of the canvas) .. hmm .. – kleopatra Oct 05 '20 at 12:13
  • the reason for not being able to somehow hook into the exception (a custom UncaughtExceptionHandler is what I tried) is that any exception in ther renderer pipeline _is_ caught (in PresentingPainter.run), doing nothing but the printout you are seeing (would consider that a bug .. similar in effect to the one described in https://stackoverflow.com/q/63317670/203657 for application startup). No idea how to hack around this one .. – kleopatra Oct 05 '20 at 12:36
  • You could break the Canvas into small tiles that you assemble. – Gilbert Le Blanc Oct 05 '20 at 12:51
  • @GilbertLeBlanc yeah probably, if we could somehow detect how small is small-enough at runtime ;) The app simply seems to block after trying with a not small-enough (and I suspect that many just-so-small will again block) – kleopatra Oct 05 '20 at 13:03

2 Answers2

2

You need to access information about the specific graphics driver and card that are being used. You can do this by implementing a check for D3D and/or OpenGL.

I've debugged a similar issue to this for a long time. There is no way to detect and handle an exception for this in the rendering pipeline. You'll need to prepare for it and circumvent it.

JavaFX Canvas objects are rendering by applying a texture made up of the stuff you draw. Textures are limited in size based on the specific graphics hardware you are running. You can get a quick readout of a specific system's maximum texture size by running with the VM argument -Dprism.verbose=true. The line "Maximum supported texture size: x" is what you are looking for.

Here are the limits produced by the DirectX drivers (information available at https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits and https://learn.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-limits)

11+: 16384
10: 8192
9.1: 2048
9.3: 4096

OpenGL looks like it is more specific to the hardware and can return the limit by calling: GetIntegerv(GL_MAX_TEXTURE_SIZE) (https://community.khronos.org/t/what-are-the-limits-on-texture-size-for-opengl/36759)

These specify the largest size a single dimension of a texture can be.

You'd obviously need to use D3D and/or OpenGL depending on what you are running on to get these. On Windows, JavaFX will use D3D, otherwise it will use OpenGL. There are probably cases where it uses software rendering as well that you might have to account for. You could hack around this by running with -Dprism.verbose=true and reading the stderr for the max texture size line.

An alternative is to set a limit based on the screen size of the device running the program. Based on the limits I have seen, this should be a pretty safe option. For this you can either subdivide into several different Canvas objects, or make the Canvas size only as large as the visible area (could be done with a ScrollPane using the viewport)

I'd keep an upper limit on the total size as well if you go with subdivisions. If you subdivide and run into this exception again, you've likely run out of vram (you can request a different amount using -Dprism.maxvram=amountyouwant)

1

Hello from 2023) JavaFX still producing that bug. despite having DirectX v12 DirectX v12

and

-Dprism.maxvram=20G

Still having Prims NPE error

java.lang.NullPointerException: Cannot invoke "com.sun.prism.RTTexture.createGraphics()" because "<local9>" is null
    at javafx.graphics@20.0.1/com.sun.javafx.sg.prism.NGCanvas$RenderBuf.validate(NGCanvas.java:214)
    at javafx.graphics@20.0.1/com.sun.javafx.sg.prism.NGCanvas.initCanvas(NGCanvas.java:644)
    at javafx.graphics@20.0.1/com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:607)

for Canvas larger than 8192px ((

Yo Man
  • 11
  • 2