Search code examples
javajavafxgluongraalvmgraalvm-native-image

Javafx .exe crashes while invoking javafx.scene.effect.DropShadow


My native javafx app crashes at runtime when invoking javafx.scene.effect.DropShadow. The app only works if I comment out the relevant code. How can I do it with the DropShadow effect?

error log:

[Di. Feb. 02 18:58:50 MEZ 2021][INFO] ==================== RUN TASK ====================
[Di. Feb. 02 18:58:50 MEZ 2021][FINE] PB Command for run until end: c:\Users\...\target\client\x86_64-windows\Cleptomania.exe
[Di. Feb. 02 18:58:50 MEZ 2021][FINE] Start process run until end...
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB] java.lang.RuntimeException: Could not create peer  Merge for renderer com.sun.scenario.effect.impl.prism.ps.PPSRenderer@7464688f
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.impl.Renderer.getPeerInstance(Renderer.java:254)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.CoreEffect.getPeer(CoreEffect.java:66)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.CoreEffect.getPeer(CoreEffect.java:92)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.CoreEffect.filterImageDatas(CoreEffect.java:106)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.Merge.filterImageDatas(Merge.java:39)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.Merge.filter(Merge.java:172)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.DelegateEffect.filter(DelegateEffect.java:70)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.impl.prism.PrEffectHelper.render(PrEffectHelper.java:166)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.EffectFilter.render(EffectFilter.java:61)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.renderEffect(NGNode.java:2384)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2069)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:479)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:328)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:91)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.lang.Thread.run(Thread.java:834)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:519)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.oracle.svm.core.windows.WindowsJavaThreads.osThreadStartRoutine(WindowsJavaThreads.java:138)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB] java.lang.IllegalStateException: Operation requires resource lock
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.prism.impl.ManagedResource.assertLocked(ManagedResource.java:96)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.prism.impl.BaseTexture.assertLocked(BaseTexture.java:267)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.prism.impl.ps.BaseShaderContext.setTexture(BaseShaderContext.java:695)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.prism.impl.ps.BaseShaderContext.validateTextureOp(BaseShaderContext.java:591)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.prism.impl.ps.BaseShaderContext.validateTextureOp(BaseShaderContext.java:507)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.prism.impl.BaseGraphics.drawTextureRaw(BaseGraphics.java:727)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.impl.prism.ps.PPSOneSamplerPeer.filterImpl(PPSOneSamplerPeer.java:117)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.impl.prism.ps.PPSEffectPeer.filter(PPSEffectPeer.java:54)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.LinearConvolveCoreEffect.filterImageDatas(LinearConvolveCoreEffect.java:85)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.LinearConvolveCoreEffect.filterImageDatas(LinearConvolveCoreEffect.java:41)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.FilterEffect.filter(FilterEffect.java:195)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.Offset.filter(Offset.java:160)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.Merge.filter(Merge.java:148)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.DelegateEffect.filter(DelegateEffect.java:70)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.scenario.effect.impl.prism.PrEffectHelper.render(PrEffectHelper.java:166)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.EffectFilter.render(EffectFilter.java:61)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.renderEffect(NGNode.java:2384)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2069)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:479)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:328)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:91)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at java.lang.Thread.run(Thread.java:834)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:519)
[Di. Feb. 02 18:58:59 MEZ 2021][INFO] [SUB]     at com.oracle.svm.core.windows.WindowsJavaThreads.osThreadStartRoutine(WindowsJavaThreads.java:138)

Update - Example code

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

public class SampleApp extends Application {

    @Override
    public void start(Stage stage) throws Exception 
    {
        VBox root = new VBox();     
        root.getChildren().add(getButtonBox(root)); 

        Scene scene = new Scene(root, 600, 600);  
        stage.setScene(scene);
        stage.setOnCloseRequest(new EventHandler<WindowEvent>() 
        {
            @Override
            public void handle(WindowEvent t) 
            {
                Platform.exit();
                System.exit(0);
            }
        });
        stage.show();
    }
    
    private VBox getButtonBox(Pane pane)
    {
        VBox box = new VBox();
        
        Button btn = new Button("No Effect");
        btn.setOnAction(action -> setChild(pane, false));
        
        Button btnEff = new Button("With Effect");
        btnEff.setOnAction(action -> setChild(pane, true));
        
        box.getChildren().addAll(btn, btnEff);
        return box;
    }
    
    private void setChild(Pane pane, boolean withEffect)
    {   
        VBox box = new VBox();
        Text text = new Text("Hello");
        text.setFill(Color.WHITE);
        text.setFont(Font.font("Courier New", FontWeight.BOLD, 40));
        box.getChildren().add(text);
        box.setAlignment(Pos.CENTER);
        box.setBackground(new Background(new BackgroundFill(Color.BLUE, new CornerRadii(5,5,5,5,false) ,Insets.EMPTY)));
        box.setTranslateZ(-4);
        if(withEffect)      
        {
            DropShadow dropShadow = new DropShadow();
            dropShadow.setRadius(5.0);
            dropShadow.setSpread(1.0); 
            box.setEffect(dropShadow);
        }
        if(pane.getChildren().size() > 1)   
            pane.getChildren().set(1, box);
        else
            pane.getChildren().add(box);
    }
    
    public static void main(String args[]) 
    {
        launch(args);
    }
}

Solution

  • TL;DR

    The class com.sun.scenario.effect.impl.prism.PrMergePeer is missing from the reflection list. Add it, build the native image again, and it will work.

    Explanation

    If your application runs fine on JVM/Hotspot (i.e. via mvn javafx:run), and fails on runtime via native-image (i.e. via mvn client:run), usually it is a configuration issue, that might be easy to fix.

    Typically, it has to do with classes missing from the reflection list, and this causes a class not found exception.

    However, sometimes, a missing class that can't be called reflectively doesn't throw an exception, and the application keeps running, but with some unexpected behaviour. This seems the case with the DropShadow effect.

    This makes things harder to track. The class not found exception already gives a hint of what class is missing, but in the current case, the runtime exception itself doesn't.

    As explained here, https://docs.gluonhq.com/#_jni_and_reflection, the Client plugin creates a reflection configuration file under target/client/$arch-$os/gvm/reflectionconfig-$arch-$os.json, with a set of JavaFX classes that are used reflectively.

    However, this set is not complete, and sometimes some classes are missing. Once these identified, there are two options:

    • Add them to the reflectionList in the pom, like:
    <plugin>
        <groupId>com.gluonhq</groupId>
        <artifactId>client-maven-plugin</artifactId>
        <version>0.1.36</version>
        <configuration>
             <target>${client.target}</target>
             <mainClass>${main.class}</mainClass>
            <reflectionList>
                <list>com.sun.scenario.effect.impl.prism.PrMergePeer</list>
            </reflectionList>
        </configuration>
    </plugin>
    
    • Create a file reflectionconfig.json under src/main/resources/META-INF/substrate/config, and add the class (and the required fields/methods):
    {
      "name":"com.sun.scenario.effect.impl.prism.PrMergePeer",
      "methods":[{"name":"<init>", "parameterTypes":["com.sun.scenario.effect.FilterContext", "com.sun.scenario.effect.impl.Renderer",  "java.lang.String"] }]
    }
    

    The latter is the preferred option as it only includes the required methods and fields for a given class, while the former includes all methods and fields.

    In any case, we still need to discover the missing class. We could inspect the involved JavaFX classes from the stacktrace, or we could use the GraalVM image agent that, luckily, will do it for us.

    GraalVM image agent

    GraalVM has a Java agent that creates the reflection config file (among others), but requires running the app on Hotspot first.

    This can be combined as follow:

    • Create the folder src/main/resources/META-INF/native-image.

    • Add the agent to the javafx-maven-plugin:

    <plugin>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-maven-plugin</artifactId>
            <version>0.0.5</version>
            <configuration>
                <mainClass>${main.class}</mainClass>
                <options>
                    <option>-agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image</option>
                </options>
            </configuration>
        </plugin>
    
    • Run mvn javafx:run, and explore all possible scenarios (so the agent can discover during runtime the classes and resources used).

    • Explore the files added to src/main/resources/META-INF/native-image.

    In the case of the failing DropShadow effect, the only related class that was missing from the config file generated by the Client plugin was com.sun.scenario.effect.impl.prism.PrMergePeer.

    • Finally, run again mvn client:build client:run, to create and run the native-image.

    If you keep src/main/resources/META-INF/native-image, the config files in it will be used, in combination with those already created by the Client plugin. Optionally, you can remove them, and simply use one of the two options explained above: add the class to the reflectionList or create a json file.