jME and SWT

Here's a (quick 'n' dirty) DiplaySystem implementation which makes it possible to use jME with SWT (using the new GLCanvas class from Eclipse 3.2). I wrote this in order to see if I could use jME in my current project, so it's far from perfect, but hey… it works for me :slight_smile:



Comments are welcome, of course :smiley:



To use it :


display = new SWTDisplaySystem();
GLCanvas canvas = display.createGLCanvas(parent, SWT.NONE);
display.createHeadlessDisplay(width, height, bpp);
Renderer renderer = display.getRenderer(); // actually it's an LWJGLRenderer instance
... do jME stuff...
canvas.swapBuffers();



Important : you MUST call canvas.swapBuffers() at the end of your rendering function



import java.awt.Canvas;
import java.util.logging.Level;

import org.eclipse.swt.opengl.GLCanvas;
import org.eclipse.swt.opengl.GLData;
import org.eclipse.swt.widgets.Composite;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.RenderTexture;

import com.jme.renderer.Renderer;
import com.jme.renderer.RendererType;
import com.jme.renderer.TextureRenderer;
import com.jme.renderer.lwjgl.LWJGLRenderer;
import com.jme.renderer.lwjgl.LWJGLTextureRenderer;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.LoggingSystem;

public class SWTDisplaySystem extends DisplaySystem {

   private LWJGLRenderer renderer;
   private GLCanvas canvas;
   
   public GLCanvas createGLCanvas(Composite parent, int style) {
      GLData data = new GLData();
      data.doubleBuffer = true;
      canvas = new GLCanvas(parent, style, data);
      return canvas;
   }
   
   public GLCanvas getCanvas() {
      return canvas;
   }
   
   public void setCanvas(GLCanvas c) {
      canvas = c;
   }
   
   @Override
   public void createHeadlessWindow(int w, int h, int bpp) {
      if ( w <= 0 || h <= 0) {
         throw new JmeException("Invalid resolution values: " + w + " " + h);
      }
      else if ((bpp != 32) && (bpp != 24) && (bpp != 16)) {
         throw new JmeException("Invalid pixel depth: " + bpp);
      }
      
      this.width = w;
      this.height = h;
      this.bpp = bpp;
      
      DisplayMode mode = new DisplayMode(width, height);
      
      try {
         Display.setDisplayMode(mode);
         canvas.setCurrent();
         GLContext.useContext(canvas);
      }
      catch (Exception e) {
      }
      
      
      renderer = new LWJGLRenderer(width, height);
      renderer.setHeadless(true);
      
      updateStates(renderer);
      
      created = true;
      
   }

   public Renderer getRenderer() {
      return renderer;
   }

   @Override
   public void close() {
      Display.destroy();
   }

   @Override
   public Canvas createCanvas(int w, int h) {
      return null;
   }

   @Override
   public TextureRenderer createTextureRenderer(int width, int height, boolean useRGB, boolean useRGBA, boolean useDepth, boolean isRectangle, int target, int mipmaps) {
      if (!isCreated()) {
         return null;
      }
      return new LWJGLTextureRenderer(width, height, (LWJGLRenderer) getRenderer(), new RenderTexture(useRGB, useRGBA, useDepth, isRectangle, target, mipmaps));
   }

   @Override
   public TextureRenderer createTextureRenderer(int width, int height, boolean useRGB, boolean useRGBA, boolean useDepth, boolean isRectangle, int target, int mipmaps, int bpp, int alpha, int depth, int stencil, int samples) {
      if (!isCreated()) {
         return null;
      }
      return new LWJGLTextureRenderer(width, height, (LWJGLRenderer) getRenderer(), new RenderTexture(useRGB, useRGBA, useDepth, isRectangle, target, mipmaps), bpp, alpha, depth, stencil, samples);
   }

   @Override
   public void createWindow(int w, int h, int bpp, int frq, boolean fs) {
      // TODO: return an RCP Viewpart ?
      return;
   }

   @Override
   public RendererType getRendererType() {
      // TODO Auto-generated method stub
      return null;
   }

   @Override
   public boolean isClosing() {
      if (canvas == null) {
         return Display.isCloseRequested();
      }
      return false;
   }

   private DisplayMode getValidDisplayMode(int width, int height, int bpp, int freq) {
      DisplayMode[] modes;
      try {
         modes = Display.getAvailableDisplayModes();
      }
      catch (LWJGLException e) {
         e.printStackTrace();
         return null;
      }
      for (int i = 0; i < modes.length; i++) {
         if (modes[i].getWidth() == width && modes[i].getHeight() == height
               && modes[i].getBitsPerPixel() == bpp
               && (freq == 0 || modes[i].getFrequency() == freq)) {
            return modes[i];
         }
      }
      return null;
   }
   
   @Override
   public boolean isValidDisplayMode(int width, int height, int bpp, int freq) {
      return getValidDisplayMode(width, height, bpp, freq) != null;
   }

   @Override
   public void recreateWindow(int w, int h, int bpp, int frq, boolean fs) {
      return;   
   }

   @Override
   public void reset() {
      return;   
   }

   @Override
   public void setRenderer(Renderer r) {
      if (r instanceof LWJGLRenderer) {
         renderer = (LWJGLRenderer) r;
      }
      else {
         LoggingSystem.getLogger().log(Level.WARNING, "Invalid Renderer type");
      }
   }

   @Override
   public void setTitle(String title) {
      Display.setTitle(title);
   }

   @Override
   public void setVSyncEnabled(boolean enabled) {
      Display.setVSyncEnabled(enabled);
   }

   @Override
   protected void updateDisplayBGC() {
      try {
         Display.setDisplayConfiguration(gamma, brightness, contrast);
      } catch (LWJGLException e) {
         LoggingSystem.getLogger().warning("Unable to apply gamma/brightness/contrast settings: " + e.getMessage());
      }
   }
   
}

Hi, I am very interested in SWT and i am trying to combine it with StandardGame but I can't figure how to create a renderer,a cam and everythings that goes with…



Your help would be very appriciated    thomas

Hi all,

I have created a Display System plugin for using JME within SWT.



You can download it as a .jar file here: http://gonzo.uni-weimar.de/~scheffl2/jme/swtSystemProvider.jar

The jar file contains the source files.

Usage:


  • Simply add the jar file to the class path. The JME configuration dialog should list the display system as a choice in the drop down menu.


  • Look at the contained file HelloSWT.java as an example.


  • I had to download the SWT classes as a separate package from the eclipse homepage, it wouldn't work with the jars provided by my eclipse installation.



This is a screenshot of the included example:


See http://www.jmonkeyengine.com/jmeforum/index.php?topic=3003.0 for infos on the plugin system

One problem: When I use the SWT elements, JME stops updating the display.
This is the update code:


protected void simpleUpdate() {
      
      //if the window was not closed
      if (!shell.isDisposed()) {
         //update the openGL canvas
         canvas.swapBuffers();
         //update SWT
              swtdisplay.readAndDispatch();
       }
         
      //rotate the box
      Quaternion q=new Quaternion().fromAngleAxis(0.01f, new Vector3f(0.3f,1,0.6f).normalize());
      box.setLocalRotation(box.getLocalRotation().mult(q));
      
}  



Any ideas?

OK, I have updated the JAR file with some new stuff.



The problem with the event loop is fixed now, I have stolen generously from the monkeyworld code, I hope this is OK.



I have implemented SWTMouseInput and SWTKeyInput.

MouseInput is not finished yet, motion is reported, but buttons not yet.

KeyInput is finished, but Key event reporting is seriously stinky in SWT. When more than one alphanumeric key is pressed, only the last released key generates a keyReleased event. This gives problems with the WASD-control of 1st Person input.

This is an old SWT bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=50020

It seems to work OK with the arrow keys, so not all hope is lost.

Another update…

Unfortunately SWT elements cannot grab the mouse. This practically prevents the first person handler from working, because the mouse leaves the GLCanvas and the 1PH does not get any more mouse values. I have created a  workaround: the 1PH is activated while a mouse button is pressed in the GLCanvas. The mouse cursor gets hidden and constantly moved back to the initial position.



Another problem is the mouse wheel: SWT does not provide an interface to capture wheel events.

Hmmm. I didn't think that SWT has so many quirks. I have to use it for integration reasons, otherwise I wouldn't bother.

SWT does support the mouse wheel since 3.1 (thats ages ago ;)) with SWT.MouseWheel.



Check out this example:


addListener(SWT.MouseWheel, new Listener() {
   public void handleEvent(Event event) {
      int c = event.count;
                /* IMPORTANT: Have a look at the other attributes of the event */
   }
});

/* This works only if the Canvas has keyboard focus, so this is also needed: */
addMouseTrackListener(new MouseTrackAdapter()) {
   public void mouseEnter(MouseEvent e) {
      forceFocus();
   }
});

ok, thanks for the info. I have integrated that, but as I don't need it I am not sure if it works correctly.

I never used SWT before, so there are probably other things I could do better. If you see something that yo want to see corrected, just drop me a line here

I don't know why, but I decided today that I'd upgrade my jME version from 0.10 to CVS head, and of course it broke this specific part of my project :slight_smile:



The problem I get is that every time a RenderState (be it a TextureState or whatever) is created through a DisplaySystem.getDisplaySystem().getRenderer().createXXXState(), it's null…

Here is the updated code for SWTDisplaySystem (I started from scratch with the new LWJGLDisplaySystem, and most of the code is unchanged):



package com.jme.system.lwjgl;

import java.awt.Canvas;
import java.awt.Toolkit;
import java.nio.ByteBuffer;
import java.util.logging.Level;

import org.eclipse.swt.opengl.GLCanvas;
import org.eclipse.swt.opengl.GLData;
import org.eclipse.swt.widgets.Composite;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.Pbuffer;
import org.lwjgl.opengl.PixelFormat;

import com.jme.image.Image;
import com.jme.renderer.RenderContext;
import com.jme.renderer.Renderer;
import com.jme.renderer.TextureRenderer;
import com.jme.renderer.lwjgl.LWJGLRenderer;
import com.jme.renderer.lwjgl.LWJGLTextureRenderer;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.ImageUtils;
import com.jme.util.LoggingSystem;
import com.jme.util.WeakIdentityCache;
import com.jmex.awt.JMECanvas;
import com.jmex.awt.lwjgl.LWJGLCanvas;

public class SWTDisplaySystem extends DisplaySystem {

   private GLCanvas glCanvas;
   private LWJGLRenderer renderer;

    private Pbuffer headlessDisplay;
    private JMECanvas canvas;

    private RenderContext currentContext = null;
    private WeakIdentityCache<Object, RenderContext> contextStore = new WeakIdentityCache<Object, RenderContext>();

   public GLCanvas createGLCanvas(Composite parent, int style) {
      GLData data = new GLData();
      data.doubleBuffer = true;
      glCanvas = new GLCanvas(parent, style, data);
      return glCanvas;
   }

   public GLCanvas getCanvas() {
      return glCanvas;
   }

   public void setCanvas(GLCanvas c) {
      glCanvas = c;
   }

   
    /**
     * <code>createHeadlessWindow</code> will create a GLCanvas and use it for
     * context.
     *
     * @see com.jme.system.DisplaySystem#createHeadlessWindow(int, int, int)
     */
   public void createHeadlessWindow(int w, int h, int bpp) {
      if ( w <= 0 || h <= 0) {
         throw new JmeException("Invalid resolution values: " + w + " " + h);
      }
      else if ((bpp != 32) && (bpp != 24) && (bpp != 16)) {
         throw new JmeException("Invalid pixel depth: " + bpp);
      }

      this.width = w;
      super.height = h;
      super.bpp = bpp;

      DisplayMode mode = new DisplayMode(width, height);

        try {
         Display.setDisplayMode(mode);
         glCanvas.setCurrent();
         GLContext.useContext(glCanvas);
      }
      catch (Exception e) {
         LoggingSystem.getLogger().log(Level.SEVERE, "Cannot initialize headless display: " + e.getLocalizedMessage(), e);
      }
      
      renderer = new LWJGLRenderer( width, height );
        switchContext(this);
        renderer.setHeadless( true );
        updateStates( renderer );

        created = true;
      
   }
   
   
    /**
     * Constructor instantiates a new <code>SWTDisplaySystem</code> object.
     * During instantiation confirmation is made to determine if the LWJGL API
     * is installed properly. If not, a JmeException is thrown.
     */
    public SWTDisplaySystem() {
        super();
        LoggingSystem.getLogger().log( Level.INFO,
                "SWT Display System created." );
    }

    ... (rest of the code is unchanged)



Any ideas? I'm kinda stuck :'(
Thanks!

OK, found the problem : I have to implement a SWTSystemProvider (which I did) and declare it as a service (in META-INF/services/com.jme.system.SystemProvider) so that DisplaySystem.getSystemProviderMap() finds it.

I guess all I will have to do then will be to call DisplaySystem.getDisplaySystem("SWT") instead of DisplaySystem.getDisplaySystem() upon initialization.



The problem is now that the SWTSystemProvider cannot be found by the Service.providers() method, but I guess it's just a classpath problem, as usual…

If you need inspiration, I just uploaded my current version of my swtSystemProvider.jar. You can find it under

http://www.uni-weimar.de/~scheffl2/jme/swtSystemProvider.jar

Features:


  • nearly complete keyboard support

  • mouse support

  • fullscreen-bug fixed

  • contains simpler example


Nice work, cleaner than mine :slight_smile:

The problem I have right now is that for some reason the Service Provider mechanism fails to find the SWT provider (even though the service exists in META-INF/services). More precisely, it can find it when I call directly Service.provider(ServiceProvider.class) in my code, but not with DisplaySystem.getSystemProvidersMap().

Classpath issue is my best guess (a problem I often have with RCP applications, sadly). Maybe I'll go your way and package the SWT additions to jME in a separate JAR or eclipse plugin and see if it works (it should). I'll let you know this evening (at work right now…)

it works for me outside of a jar - i use that for development.

Hey wooyay,



Thanks again for your code, it helped :slight_smile: I made some small modifications to SWTGame to use it in a RCP application (nothing fancy).

My only problem right now is the lousy framerate : 62-63 fps with nothing to display… Drops to 20~ fps with one terrainblock and skydome, 8fps with shadows… (with no optimisations though).

Do you have the same problem for standalone SWT apps, or should I look into specific RCP issues ? (maybe I can increase the thread priority…)



Thanks !

the 60 frame limit comes from the SWT event loop - the swt event loop triggers the JME display update. This does not waste CPU time, the event loop simply blocks until it is time to refresh.

Apart from that my frame rate is OK. Have you tried rendering the same scene with standard game?

Silly me for not thinking of the SWT loop… Display.syncExec() should have rung a bell :confused:

Anyway I found the culprit: the size 256 TerrainBlock I used for my prototype. Switched to a TerrainPage and guess what, FPS is back to nearly 60 :slight_smile:

Thanks for your help!

The Librarian said:

Nice work, cleaner than mine :)
The problem I have right now is that for some reason the Service Provider mechanism fails to find the SWT provider (even though the service exists in META-INF/services). More precisely, it can find it when I call directly Service.provider(ServiceProvider.class) in my code, but not with DisplaySystem.getSystemProvidersMap().
Classpath issue is my best guess (a problem I often have with RCP applications, sadly). Maybe I'll go your way and package the SWT additions to jME in a separate JAR or eclipse plugin and see if it works (it should). I'll let you know this evening (at work right now...)


The Librarian,
did you ever solve the issue with the Service Provider mechanism? I have the same problem, I have even failed with having the swtSystemProvider as a seperate JAR file.

I have some ambition to develop a jMonkeyEngine-eclipse-plugin, since there really isn't any good 3D toolkits for the RCP. However, I am totaly fresh at jMonkeyEngine.

Thanks.

Hi,



Yes, using a separate jar solved the issue. But it didn't work the first time, and I can't remember what fixed it :confused:

A separate jME/SWT Eclipse plugin is the way to go indeed. I guess I could just package mine, or maybe you could use code from MonkeyWorld3D (which just went the RCP way, oh yeah!). I haven't had the time to compare their code with wooyay's or mine though…



Maybe we should check all our different use scenarios and make a common plugin. My guess is we're all using the same idea with different coding styles. Shame:)





PS: Jr Member at last :wink: Kids, hijacking threads with sfera and making fun of darkfrog is the way to go!

maybe you should take a look at my package - it is a SystemProvider packaged in a jar. I coded this for reusability, so if it lacks something you need, I can add it

http://gonzo.uni-weimar.de/~scheffl2/jme/swtSystemProvider.jar

There is an example in the jar file called HelloSWT.java.

You can use SWT layouts to style the canvas - to make it fit to the window size for example.



I updated the package to my current version - it simplifies the way the canvas can be used.

To be clear, I've been using wooyay's code with some modifications (such as double click support, etc.)

I also changed the package to com.jme…



I think it should be included in jME. SWT is widely used (admitedly, not for games), look at MonkeyWorld3D :wink: And the RCP framework just rocks.

The corresponding RCP plugin could then be maintained outside of jME (it's just a different package after all).



jME maintainers, your opinion?

Whatever we decide on, I'll be very interested on one common SWT/jME Bridge. Bringing together all the goodies from the different implementations would help a lot. It just doesn't make any sense that everyone is creating his own.