AWT / Swing rendering with LWGJL 3.1.X

Hello to all,

I’ve been annoyed by the update to LWJGL 3.1.2 as all the native links to AWT have been removed. The board effect is that i had no mean with LWJGL3 rendering to display JMonkey application within an AWT / Swing canvas.

I’ve proposed an AppState dedicated to the use of an AWT component as a rendering support for JMonkey. This implementation is inspired by the @javasabr implementation of JavaFX link with JMonkey but does not use other libraries than JDK / JMonkey.

I’ve submitted a pull request (#828) for the AWT rendering related classes and you can test it with the following example:

package sample;

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.JFrame;

import com.jme3.app.LegacyApplication;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AWTComponentAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.system.AWTContext;
import com.jme3.system.AWTFrameProcessor;
import com.jme3.system.AWTTaskExecutor;
import com.jme3.system.AppSettings;

/**
 * <p>
 * This example illustrates the use of an AWT component as the target of a JMonkey rendering.
 * </p>
 * @author Julien Seinturier - COMEX SA - <a href="http://www.seinturier.fr">http://www.seinturier.fr</a>
 *
 */
public class AWTComponentRenderingSample {

	/**
	 * Create a JMonkey simple application with basic capabilities.
	 * @return the created application.
	 */
	public static LegacyApplication createApplication(AppSettings settings){

	    final SimpleApplication application = new SimpleApplication() {

	        protected Geometry player;
	        Boolean isRunning = true;

	        @Override
	        public void update() {
	          AWTTaskExecutor.getInstance().execute();
	          super.update();
	        }

	        
	        @Override
	        public void simpleInitApp() {
	            Box b = new Box(1, 1, 1);
	            player = new Geometry("Player", b);
	            Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
	            mat.setColor("Color", ColorRGBA.Green);
	            player.setMaterial(mat);
	            rootNode.attachChild(player);
	            initKeys(); // load my custom keybinding
	        }

	        /** Custom Keybinding: Map named actions to inputs. */
	        private void initKeys() {
	            /** You can map one or several inputs to one named mapping. */
	            inputManager.addMapping("Pause", new KeyTrigger(keyInput.KEY_P));
	            inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_J));
	            inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_K));
	            inputManager.addMapping("Rotate", new KeyTrigger(KeyInput.KEY_SPACE), // spacebar!
	                    new MouseButtonTrigger(MouseInput.BUTTON_LEFT));        // left click!
	            /** Add the named mappings to the action listeners. */
	            inputManager.addListener(actionListener, "Pause");
	            inputManager.addListener(analogListener, "Left", "Right", "Rotate");
	        }

	        /** Use this listener for KeyDown/KeyUp events */
	        private final ActionListener actionListener = new ActionListener() {

				public void onAction(String name, boolean isPressed, float tpf) {
					if (name.equals("Pause") && !isPressed) {
		                isRunning = !isRunning;
		            }
				}
	        	
	        };

	        /** Use this listener for continuous events */
	        private final AnalogListener analogListener = new AnalogListener() {
	        	
	            @Override
	            public void onAnalog(String name, float value, float tpf) {
	                if (isRunning) {
	                    if (name.equals("Rotate")) {
	                        player.rotate(0, value, 0);
	                    }
	                    if (name.equals("Right")) {
	                        player.move((new Vector3f(value, 0, 0)));
	                    }
	                    if (name.equals("Left")) {
	                        player.move(new Vector3f(-value, 0, 0));
	                    }
	                } else {
	                    System.out.println("Press P to unpause.");
	                }
	            }
	        };
	    };

	    application.setSettings(settings);
    application.setShowSettings(false);
	    
	    return application;
	  }
	
	
	/**
	 * Run the example.
	 */
	public void runSample() {
		
		// Create settings that are compatible with AWT rendering
		AppSettings settings = new AppSettings(true);
	    settings.setFullscreen(false);
	    settings.setCustomRenderer(AWTContext.class);
		
	    // Create the component that will support the rendering.
	    // Any subclass of Component can be used.
	    Canvas component = new Canvas();
	    component.setSize(800,  600);
	    
	    // Create the app state dedicated to AWT component rendering
	    AWTComponentAppState appState = new AWTComponentAppState(component);
	    
		// Create a demo application    
		LegacyApplication application = createApplication(settings);
		
		// Attach the app state to the application
		application.getStateManager().attach(appState);
		
		// Start the application
	    Thread thread = new Thread() {
	      @Override
	      public void run() {
	        try {
	          application.start();
	        } catch (Exception e) {
	          System.err.println(e.getMessage());
	          e.printStackTrace(System.err);
	        }
	      }
	    };
	    
	    thread.start();
	    
	    JFrame frame = new JFrame();
	    frame.setTitle("JMonkey AWT Component rendering Example");
	    frame.setSize(new Dimension(800, 600));
	    frame.setPreferredSize(new Dimension(800, 600));
	    
	    frame.getContentPane().setLayout(new BorderLayout());
	    frame.getContentPane().add(component, BorderLayout.CENTER);
	    
	    
	    frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
	    frame.addWindowListener(new WindowListener(){

	      @Override
	      public void windowOpened(WindowEvent e) {}

	      @Override
	      public void windowClosing(WindowEvent e) {
	        System.out.print("Stopping JME Application");
	        application.stop(true);
	        System.out.println(" [OK]");
	        System.exit(0);
	      }

	      @Override
	      public void windowClosed(WindowEvent e) {}

	      @Override
	      public void windowIconified(WindowEvent e) {}

	      @Override
	      public void windowDeiconified(WindowEvent e) {}

	      @Override
	      public void windowActivated(WindowEvent e) {}

	      @Override
	      public void windowDeactivated(WindowEvent e) {}
	    });
	    
	    
	    frame.setVisible(true);
	    
	}
	
	/**
	 * The main method.
	 * @param args the command line arguments
	 */
	public static void main(String[] args) {
		
	  AWTComponentRenderingSample sample = new AWTComponentRenderingSample();
		
	  sample.runSample();
	  
	}
	
}

At this time, the problem is that an AWT binding is present within the sources but i’ts only handle LWJGL2 rendering. The implementation i propose could be used with any underlying rendering.

Maybe you can test this example and say to me if this work is interesting for you.

Thanks a lot,

Julien

4 Likes

AWT and LWJGL3 don’t tend to play nicely together, especially on OSX as I recall. Have you tested this on OSX?

I have yet to get JME running on OSX with LWJGL3. It hard crashes my session. AWT and LWJGL3 don’t play nice on OSX even if it does work due to threading issues however.

That’s what I was thinking - haven’t checked in on that in a while though, so I wasn’t sure if something had changed with JDK 9.

I have no problems with running javaFX + LWJGL3 on OSX. Why do you need to use AWT?

2 Likes

Same problem here, I just can’t get jME to run on OSX unless I use the JOGL renderer. Although there is nothing wrong with using JOGL it is a few lines of hand waving to get jME apps to run.

I have problem with your sample. When I try to run it (from NetBeans 8.1), it starts with a window with the following message:

Uncaught exception thrown in Thread[jME3 Main,5,main]
UnsupportedOperationException: Unsupported renderer: LWJGL-OpenGL33

Am I missing something? I am new to GitHub, so to try your code I have created every file you changed in your push. This files have been created by NetBeans in new packages inside my project.
jme3 swing

Error log in NetBeans is this:
run:
abr 14, 2018 6:16:34 PM com.jme3.system.JmeDesktopSystem initialize
INFORMACIÓN: Running on jMonkeyEngine 3.2-stable

  • Branch: HEAD
  • Git Hash: f85624a
  • Build Date: 2018-01-21
    abr 14, 2018 6:16:35 PM com.jme3.system.lwjgl.LwjglContext printContextInitInfo
    INFORMACIÓN: LWJGL 2.9.3 context running on thread jME3 Main
  • Graphics Adapter: nvd3dumx,nvwgf2umx,nvwgf2umx
  • Driver Version: 9.18.13.4195
  • Scaling Factor: 1
    abr 14, 2018 6:16:35 PM com.jme3.app.LegacyApplication handleError
    GRAVE: Uncaught exception thrown in Thread[jME3 Main,5,main]
    java.lang.UnsupportedOperationException: Unsupported renderer: LWJGL-OpenGL33
    at com.jme3.system.lwjgl.LwjglContext.initContextFirstTime(LwjglContext.java:239)
    at com.jme3.system.lwjgl.LwjglContext.internalCreate(LwjglContext.java:377)
    at com.jme3.system.lwjgl.LwjglOffscreenBuffer.initInThread(LwjglOffscreenBuffer.java:92)
    at com.jme3.system.lwjgl.LwjglOffscreenBuffer.run(LwjglOffscreenBuffer.java:154)
    at java.lang.Thread.run(Thread.java:745)

BUILD STOPPED (total time: 18 seconds)

Adding more information:
jME3.2-stable
JDK 1.8

Running on Lubuntu 16.04.4 LTS (I also tried on Windows 7 Enterprise Edition)

Hello,

I’ve submitted the PR #828 for the integration of an AWTAppstate that enables to display JMonkey rendering within an AWT/SWING component.

This PR is opened for a while now and the team does not want to merge it without the return of members concerning the proposed modification.

Is somebody can check the PR et say if it can be merged or not ?

Thanks a lot guys,

Julien

1 Like

Has anyone gotten LWJGL3 to run in a Swing Window?

I have the same issue trying to run the sample on my MacOSX

  • MacOs Big Sur - version 11.2.3 (20D91)
  • jMonkeyEngine 3.3.2-stable
  • org.jmonkeyengine:jme3-lwjgl3
  • jdk-11.0.2
  • LWJGL 3.2.3 build 13
  • NetBeans 12.3 with Gradle

The window crash at start with the error :

WARNING: NSWindow drag regions should only be invalidated on the Main Thread! This will throw an exception in the future.
....
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 
'nextEventMatchingMask should only be called from the Main Thread!'
*** First throw call stack:

I tried several thing by changing settings first :

Configuration.GLFW_CHECK_THREAD0.set(false); // need to disable to work on macos
Configuration.MEMORY_ALLOCATOR.set("jemalloc"); // use jemalloc
System.setProperty("prism.lcdtext", "false"); // JavaFx
System.setProperty(LWJGLBufferAllocator.PROPERTY_CONCURRENT_BUFFER_ALLOCATOR, "true");

I also tried to add some jvmArgs but nothing happened at the start

'-XstartOnFirstThread'
'-Dorg.lwjgl.macosx.nsloop'

Someone could advice

Is somebody can check the PR et say if it can be merged or not ?

it was merged to the “master” branch on 8 February 2019 and thus included in JME v3.3.0-stable.

This merge is still not working as of everything i can test.

You need to set ‘setShowSettings’ to false on OSX. AWT and lwjgl3 are incompatible on OSX. Since the settings dialog is written using swing it will crash if you try to use it.

2 Likes

Is there a way to launch the separate window on mac from a swing application with LWJGL3. Such as a JavaFX window from a Swing Application? Looking to get all of this running on the M1 Macs.