Procházet zdrojové kódy

Added jme3-lwjgl3 module which ultimately adds support for LWJGL 3.x and GLFW.

Daniel Johansson před 10 roky
rodič
revize
89f10eca58
25 změnil soubory, kde provedl 3158 přidání a 28 odebrání
  1. 3 0
      common.gradle
  2. 6 0
      jme3-core/src/main/resources/joystick-mapping.properties
  3. 2 20
      jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java
  4. 1 1
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java
  5. 21 7
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
  6. 12 0
      jme3-lwjgl3/build.gradle
  7. 140 0
      jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglAL.java
  8. 58 0
      jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java
  9. 66 0
      jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglEFX.java
  10. 213 0
      jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwJoystickInput.java
  11. 116 0
      jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java
  12. 189 0
      jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java
  13. 458 0
      jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java
  14. 83 0
      jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java
  15. 99 0
      jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java
  16. 97 0
      jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java
  17. 369 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java
  18. 289 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
  19. 12 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java
  20. 78 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java
  21. 14 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java
  22. 203 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java
  23. 139 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java
  24. 489 0
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java
  25. 1 0
      settings.gradle

+ 3 - 0
common.gradle

@@ -17,6 +17,9 @@ repositories {
     maven {
         url "http://nifty-gui.sourceforge.net/nifty-maven-repo"
     }
+    maven {
+        url "https://oss.sonatype.org/content/repositories/snapshots"
+    }
 }
 
 dependencies {

+ 6 - 0
jme3-core/src/main/resources/joystick-mapping.properties

@@ -63,3 +63,9 @@ Xbox\ 360\ Wireless\ Receiver.AXIS_RX=z
 Xbox\ 360\ Wireless\ Receiver.AXIS_RY=rz
 Xbox\ 360\ Wireless\ Receiver.z=AXIS_RX
 Xbox\ 360\ Wireless\ Receiver.rz=AXIS_RY
+
+# Microsoft PC-joystick driver
+Microsoft\ PC-joystick\ driver.12=POV +Y
+Microsoft\ PC-joystick\ driver.13=POV +X
+Microsoft\ PC-joystick\ driver.14=POV -Y
+Microsoft\ PC-joystick\ driver.15=POV -X

+ 2 - 20
jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java

@@ -48,9 +48,8 @@ import java.util.logging.Logger;
 /**
  * Utility class to register, extract, and load native libraries.
  * <br>
- * Register your own libraries via the 
- * {@link #registerNativeLibrary(java.lang.String, com.jme3.system.Platform, java.lang.String, boolean) }
- * method, for each platform. 
+ * Register your own libraries via the {@link #registerNativeLibrary(String, Platform, String, String)} method, for
+ * each platform.
  * You can then extract this library (depending on platform), by
  * using {@link #loadNativeLibrary(java.lang.String, boolean) }.
  * <br>
@@ -125,23 +124,6 @@ public final class NativeLibraryLoader {
     }
     
     static {
-        // LWJGL
-        registerNativeLibrary("lwjgl", Platform.Windows32, "native/windows/lwjgl.dll");
-        registerNativeLibrary("lwjgl", Platform.Windows64, "native/windows/lwjgl64.dll");
-        registerNativeLibrary("lwjgl", Platform.Linux32,   "native/linux/liblwjgl.so");
-        registerNativeLibrary("lwjgl", Platform.Linux64,   "native/linux/liblwjgl64.so");
-        registerNativeLibrary("lwjgl", Platform.MacOSX32,  "native/macosx/liblwjgl.dylib");
-        registerNativeLibrary("lwjgl", Platform.MacOSX64,  "native/macosx/liblwjgl.dylib");
-        
-        // OpenAL
-        // For OSX: Need to add lib prefix when extracting
-        registerNativeLibrary("openal", Platform.Windows32, "native/windows/OpenAL32.dll");
-        registerNativeLibrary("openal", Platform.Windows64, "native/windows/OpenAL64.dll");
-        registerNativeLibrary("openal", Platform.Linux32,   "native/linux/libopenal.so");
-        registerNativeLibrary("openal", Platform.Linux64,   "native/linux/libopenal64.so");
-        registerNativeLibrary("openal", Platform.MacOSX32,  "native/macosx/openal.dylib", "libopenal.dylib");
-        registerNativeLibrary("openal", Platform.MacOSX64,  "native/macosx/openal.dylib", "libopenal.dylib");
-        
         // BulletJme
         registerNativeLibrary("bulletjme", Platform.Windows32, "native/windows/x86/bulletjme.dll");
         registerNativeLibrary("bulletjme", Platform.Windows64, "native/windows/x86_64/bulletjme.dll");

+ 1 - 1
jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java

@@ -40,7 +40,6 @@ import com.jme3.input.lwjgl.JInputJoyInput;
 import com.jme3.input.lwjgl.LwjglKeyInput;
 import com.jme3.input.lwjgl.LwjglMouseInput;
 import com.jme3.system.AppSettings;
-import com.jme3.system.JmeContext.Type;
 import com.jme3.system.JmeSystem;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
@@ -207,6 +206,7 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna
                                           + "Must set with JmeContext.setSystemListner().");
         }
 
+        registerNatives();
         loadNatives();
         logger.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion());
         if (!initInThread()) {

+ 21 - 7
jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java

@@ -52,12 +52,8 @@ import com.jme3.renderer.opengl.GLRenderer;
 import com.jme3.renderer.opengl.GLTiming;
 import com.jme3.renderer.opengl.GLTimingState;
 import com.jme3.renderer.opengl.GLTracer;
-import com.jme3.system.AppSettings;
-import com.jme3.system.JmeContext;
-import com.jme3.system.JmeSystem;
-import com.jme3.system.NativeLibraryLoader;
-import com.jme3.system.SystemListener;
-import com.jme3.system.Timer;
+import com.jme3.system.*;
+
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -166,7 +162,25 @@ public abstract class LwjglContext implements JmeContext {
             }
         }
     }
-    
+
+    protected void registerNatives() {
+        // LWJGL
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Windows32, "native/windows/lwjgl.dll");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Windows64, "native/windows/lwjgl64.dll");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Linux32, "native/linux/liblwjgl.so");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Linux64, "native/linux/liblwjgl64.so");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.MacOSX32, "native/macosx/liblwjgl.dylib");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.MacOSX64, "native/macosx/liblwjgl.dylib");
+
+        // For OSX: Need to add lib prefix when extracting
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.Windows32, "native/windows/OpenAL32.dll");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.Windows64, "native/windows/OpenAL64.dll");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.Linux32, "native/linux/libopenal.so");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.Linux64, "native/linux/libopenal64.so");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.MacOSX32, "native/macosx/openal.dylib", "libopenal.dylib");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.MacOSX64, "native/macosx/openal.dylib", "libopenal.dylib");
+    }
+
     protected void loadNatives() {        
         if (JmeSystem.isLowPermissions()) {
             return;

+ 12 - 0
jme3-lwjgl3/build.gradle

@@ -0,0 +1,12 @@
+if (!hasProperty('mainClass')) {
+    ext.mainClass = ''
+}
+
+dependencies {
+    compile project(':jme3-core')
+    compile project(':jme3-desktop')
+    compile 'org.lwjgl:lwjgl:3.0.0b-SNAPSHOT'
+    compile group: 'org.lwjgl', name: 'lwjgl-platform', version: '3.0.0b-SNAPSHOT', classifier: 'natives-windows'
+    compile group: 'org.lwjgl', name: 'lwjgl-platform', version: '3.0.0b-SNAPSHOT', classifier: 'natives-linux'
+    compile group: 'org.lwjgl', name: 'lwjgl-platform', version: '3.0.0b-SNAPSHOT', classifier: 'natives-osx'
+}

+ 140 - 0
jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglAL.java

@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */package com.jme3.audio.lwjgl;
+
+import com.jme3.audio.openal.AL;
+import com.jme3.system.NativeLibraryLoader;
+import com.jme3.system.Platform;
+import org.lwjgl.openal.AL10;
+import org.lwjgl.openal.AL11;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+public final class LwjglAL implements AL {
+
+    public LwjglAL() {
+    }
+
+    public String alGetString(int parameter) {
+        return AL10.alGetString(parameter);
+    }
+
+    public int alGenSources() {
+        return AL10.alGenSources();
+    }
+
+    public int alGetError() {
+        return AL10.alGetError();
+    }
+
+    public void alDeleteSources(int numSources, IntBuffer sources) {
+        if (sources.position() != 0) throw new AssertionError();
+        if (sources.limit() != numSources) throw new AssertionError();
+        AL10.alDeleteSources(sources);
+    }
+
+    public void alGenBuffers(int numBuffers, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numBuffers) throw new AssertionError();
+        AL10.alGenBuffers(buffers);
+    }
+
+    public void alDeleteBuffers(int numBuffers, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numBuffers) throw new AssertionError();
+        AL10.alDeleteBuffers(buffers);
+    }
+
+    public void alSourceStop(int source) {
+        AL10.alSourceStop(source);
+    }
+
+    public void alSourcei(int source, int param, int value) {
+        AL10.alSourcei(source, param, value);
+    }
+
+    public void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency) {
+        if (data.position() != 0) throw new AssertionError();
+        if (data.limit() != size) throw new AssertionError();
+        AL10.alBufferData(buffer, format, data, frequency);
+    }
+
+    public void alSourcePlay(int source) {
+        AL10.alSourcePlay(source);
+    }
+
+    public void alSourcePause(int source) {
+        AL10.alSourcePause(source);
+    }
+
+    public void alSourcef(int source, int param, float value) {
+        AL10.alSourcef(source, param, value);
+    }
+
+    public void alSource3f(int source, int param, float value1, float value2, float value3) {
+        AL10.alSource3f(source, param, value1, value2, value3);
+    }
+
+    public int alGetSourcei(int source, int param) {
+        return AL10.alGetSourcei(source, param);
+    }
+
+    public void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numBuffers) throw new AssertionError();
+        AL10.alSourceUnqueueBuffers(source, buffers);
+    }
+
+    public void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numBuffers) throw new AssertionError();
+        AL10.alSourceQueueBuffers(source, buffers);
+    }
+
+    public void alListener(int param, FloatBuffer data) {
+        AL10.alListenerfv(param, data);
+    }
+
+    public void alListenerf(int param, float value) {
+        AL10.alListenerf(param, value);
+    }
+
+    public void alListener3f(int param, float value1, float value2, float value3) {
+        AL10.alListener3f(param, value1, value2, value3);
+    }
+
+    public void alSource3i(int source, int param, int value1, int value2, int value3) {
+        AL11.alSource3i(source, param, value1, value2, value3);
+    }
+
+}

+ 58 - 0
jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java

@@ -0,0 +1,58 @@
+package com.jme3.audio.lwjgl;
+
+import com.jme3.audio.openal.ALC;
+import org.lwjgl.openal.ALC10;
+import org.lwjgl.openal.ALContext;
+
+import java.nio.IntBuffer;
+
+import static org.lwjgl.openal.ALC10.alcGetContextsDevice;
+import static org.lwjgl.openal.ALC10.alcGetCurrentContext;
+
+public class LwjglALC implements ALC {
+
+    private ALContext context;
+
+    public void createALC() {
+        context = ALContext.create();
+    }
+
+    public void destroyALC() {
+        if (context != null) {
+            context.destroy();
+        }
+    }
+
+    public boolean isCreated() {
+        return context != null;
+    }
+
+    public String alcGetString(final int parameter) {
+        final long context = alcGetCurrentContext();
+        final long device = alcGetContextsDevice(context);
+        return ALC10.alcGetString(device, parameter);
+    }
+
+    public boolean alcIsExtensionPresent(final String extension) {
+        final long context = alcGetCurrentContext();
+        final long device = alcGetContextsDevice(context);
+        return ALC10.alcIsExtensionPresent(device, extension);
+    }
+
+    public void alcGetInteger(final int param, final IntBuffer buffer, final int size) {
+        if (buffer.position() != 0) throw new AssertionError();
+        if (buffer.limit() != size) throw new AssertionError();
+
+        final long context = alcGetCurrentContext();
+        final long device = alcGetContextsDevice(context);
+        final int value = ALC10.alcGetInteger(device, param);
+        //buffer.put(value);
+    }
+
+    public void alcDevicePauseSOFT() {
+    }
+
+    public void alcDeviceResumeSOFT() {
+    }
+
+}

+ 66 - 0
jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglEFX.java

@@ -0,0 +1,66 @@
+package com.jme3.audio.lwjgl;
+
+import com.jme3.audio.openal.EFX;
+import org.lwjgl.openal.EXTEfx;
+
+import java.nio.IntBuffer;
+
+public class LwjglEFX implements EFX {
+
+    public void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numSlots) throw new AssertionError();
+        EXTEfx.alGenAuxiliaryEffectSlots(buffers);
+    }
+
+    public void alGenEffects(int numEffects, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numEffects) throw new AssertionError();
+        EXTEfx.alGenEffects(buffers);
+    }
+
+    public void alEffecti(int effect, int param, int value) {
+        EXTEfx.alEffecti(effect, param, value);
+    }
+
+    public void alAuxiliaryEffectSloti(int effectSlot, int param, int value) {
+        EXTEfx.alAuxiliaryEffectSloti(effectSlot, param, value);
+    }
+
+    public void alDeleteEffects(int numEffects, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numEffects) throw new AssertionError();
+        EXTEfx.alDeleteEffects(buffers);
+    }
+
+    public void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numEffectSlots) throw new AssertionError();
+        EXTEfx.alDeleteAuxiliaryEffectSlots(buffers);
+    }
+
+    public void alGenFilters(int numFilters, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numFilters) throw new AssertionError();
+        EXTEfx.alGenFilters(buffers);
+    }
+
+    public void alFilteri(int filter, int param, int value) {
+        EXTEfx.alFilteri(filter, param, value);
+    }
+
+    public void alFilterf(int filter, int param, float value) {
+        EXTEfx.alFilterf(filter, param, value);
+    }
+
+    public void alDeleteFilters(int numFilters, IntBuffer buffers) {
+        if (buffers.position() != 0) throw new AssertionError();
+        if (buffers.limit() != numFilters) throw new AssertionError();
+        EXTEfx.alDeleteFilters(buffers);
+    }
+
+    public void alEffectf(int effect, int param, float value) {
+        EXTEfx.alEffectf(effect, param, value);
+    }
+    
+}

+ 213 - 0
jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwJoystickInput.java

@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.input.lwjgl;
+
+import com.jme3.input.*;
+import com.jme3.input.event.JoyAxisEvent;
+import com.jme3.input.event.JoyButtonEvent;
+import org.lwjgl.opengl.GL11;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import static org.lwjgl.glfw.GLFW.*;
+
+/**
+ * @author Daniel Johansson (dannyjo)
+ * @since 3.1
+ */
+public class GlfwJoystickInput implements JoyInput {
+
+    private static final Logger LOGGER = Logger.getLogger(InputManager.class.getName());
+
+    private boolean initialized = false;
+    private RawInputListener listener;
+    private Map<Integer, GlfwJoystick> joysticks = new HashMap<Integer, GlfwJoystick>();
+
+    public void setJoyRumble(int joyId, float amount) {
+        if (joyId >= joysticks.size()) {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    @Override
+    public Joystick[] loadJoysticks(final InputManager inputManager) {
+        // TODO: Implement
+
+        for (int i = 0; i < GLFW_JOYSTICK_LAST; i++) {
+            if (glfwJoystickPresent(i) == GL11.GL_TRUE) {
+                final String name = glfwGetJoystickName(i);
+                final GlfwJoystick joystick = new GlfwJoystick(inputManager, this, i, name);
+                joysticks.put(i, joystick);
+
+                final FloatBuffer floatBuffer = glfwGetJoystickAxes(i);
+
+                int axisIndex = 0;
+                while (floatBuffer.hasRemaining()) {
+                    floatBuffer.get();
+
+                    final String logicalId = JoystickCompatibilityMappings.remapComponent(joystick.getName(), convertAxisIndex(axisIndex));
+                    final JoystickAxis joystickAxis = new DefaultJoystickAxis(inputManager, joystick, axisIndex, convertAxisIndex(axisIndex), logicalId, true, false, 0.0f);
+                    joystick.addAxis(axisIndex, joystickAxis);
+                    axisIndex++;
+                }
+
+                final ByteBuffer byteBuffer = glfwGetJoystickButtons(i);
+
+                int buttonIndex = 0;
+                while (byteBuffer.hasRemaining()) {
+                    byteBuffer.get();
+                    final String logicalId = JoystickCompatibilityMappings.remapComponent(joystick.getName(), String.valueOf(buttonIndex));
+                    joystick.addButton(new DefaultJoystickButton(inputManager, joystick, buttonIndex, String.valueOf(buttonIndex), logicalId));
+                    buttonIndex++;
+                }
+            }
+        }
+
+        return joysticks.values().toArray(new GlfwJoystick[joysticks.size()]);
+    }
+
+    private String convertAxisIndex(final int index) {
+        if (index == 0) {
+            return "pov_x";
+        } else if (index == 1) {
+            return "pov_y";
+        } else if (index == 2) {
+            return "z";
+        } else if (index == 3) {
+            return "rz";
+        }
+
+        return String.valueOf(index);
+    }
+
+    public void initialize() {
+        initialized = true;
+    }
+
+    public void update() {
+        for (final Map.Entry<Integer, GlfwJoystick> entry : joysticks.entrySet()) {
+            // Axes
+            final FloatBuffer axisValues = glfwGetJoystickAxes(entry.getKey());
+
+            for (final JoystickAxis axis : entry.getValue().getAxes()) {
+                final float value = axisValues.get(axis.getAxisId());
+                listener.onJoyAxisEvent(new JoyAxisEvent(axis, value));
+            }
+
+            // Buttons
+            final ByteBuffer byteBuffer = glfwGetJoystickButtons(entry.getKey());
+
+            for (final JoystickButton button : entry.getValue().getButtons()) {
+                final boolean pressed = byteBuffer.get(button.getButtonId()) == GLFW_PRESS;
+                listener.onJoyButtonEvent(new JoyButtonEvent(button, pressed));
+            }
+        }
+    }
+
+    public void destroy() {
+        initialized = false;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    public long getInputTimeNanos() {
+        return 0;
+    }
+
+    protected class GlfwJoystick extends AbstractJoystick {
+
+        private JoystickAxis povAxisX;
+        private JoystickAxis povAxisY;
+
+        public GlfwJoystick(InputManager inputManager, JoyInput joyInput, int joyId, String name) {
+            super(inputManager, joyInput, joyId, name);
+        }
+
+        public void addAxis(final int index, final JoystickAxis axis) {
+            super.addAxis(axis);
+
+            if (index == 0) {
+                povAxisX = axis;
+            } else if (index == 1) {
+                povAxisY = axis;
+            }
+        }
+
+        @Override
+        protected void addButton(JoystickButton button) {
+            super.addButton(button);
+        }
+
+        @Override
+        public JoystickAxis getXAxis() {
+            return povAxisX;
+        }
+
+        @Override
+        public JoystickAxis getYAxis() {
+            return povAxisY;
+        }
+
+        @Override
+        public JoystickAxis getPovXAxis() {
+            return povAxisX;
+        }
+
+        @Override
+        public JoystickAxis getPovYAxis() {
+            return povAxisY;
+        }
+
+        @Override
+        public int getXAxisIndex() {
+            return povAxisX.getAxisId();
+        }
+
+        @Override
+        public int getYAxisIndex() {
+            return povAxisY.getAxisId();
+        }
+    }
+}
+
+
+

+ 116 - 0
jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java

@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.lwjgl;
+
+import com.jme3.input.KeyInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.system.lwjgl.LwjglTimer;
+import com.jme3.system.lwjgl.LwjglWindow;
+import org.lwjgl.glfw.GLFWKeyCallback;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.logging.Logger;
+
+import static org.lwjgl.glfw.GLFW.*;
+
+public class LwjglKeyInput implements KeyInput {
+
+    private static final Logger logger = Logger.getLogger(LwjglKeyInput.class.getName());
+
+    private LwjglWindow context;
+    private RawInputListener listener;
+    private boolean initialized;
+    private GLFWKeyCallback keyCallback;
+    private Queue<KeyInputEvent> keyInputEvents = new LinkedList<KeyInputEvent>();
+
+    public LwjglKeyInput(LwjglWindow context) {
+        this.context = context;
+    }
+
+    public void initialize() {
+        if (!context.isRenderable()) {
+            return;
+        }
+
+        glfwSetKeyCallback(context.getWindowHandle(), keyCallback = new GLFWKeyCallback() {
+            @Override
+            public void invoke(long window, int key, int scancode, int action, int mods) {
+                final KeyInputEvent evt = new KeyInputEvent(scancode, (char) key, GLFW_PRESS == action, GLFW_REPEAT == action);
+                evt.setTime(getInputTimeNanos());
+                keyInputEvents.add(evt);
+            }
+        });
+
+        glfwSetInputMode(context.getWindowHandle(), GLFW_STICKY_KEYS, 1);
+
+        initialized = true;
+        logger.fine("Keyboard created.");
+    }
+
+    public int getKeyCount() {
+        return 0; // TODO: How do we figure this out?
+    }
+
+    public void update() {
+        if (!context.isRenderable()) {
+            return;
+        }
+
+        while (!keyInputEvents.isEmpty()) {
+            listener.onKeyEvent(keyInputEvents.poll());
+        }
+    }
+
+    public void destroy() {
+        if (!context.isRenderable()) {
+            return;
+        }
+
+        keyCallback.release();
+        logger.fine("Keyboard destroyed.");
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    public long getInputTimeNanos() {
+        return (long) (glfwGetTime() * LwjglTimer.LWJGL_TIME_TO_NANOS);
+    }
+}

+ 189 - 0
jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java

@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.lwjgl;
+
+import com.jme3.cursors.plugins.JmeCursor;
+import com.jme3.input.MouseInput;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.system.lwjgl.LwjglTimer;
+import com.jme3.system.lwjgl.LwjglWindow;
+import org.lwjgl.glfw.GLFWCursorPosCallback;
+import org.lwjgl.glfw.GLFWMouseButtonCallback;
+import org.lwjgl.glfw.GLFWScrollCallback;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.logging.Logger;
+
+import static org.lwjgl.glfw.GLFW.*;
+
+public class LwjglMouseInput implements MouseInput {
+
+    private static final Logger logger = Logger.getLogger(LwjglMouseInput.class.getName());
+
+    private LwjglWindow context;
+    private RawInputListener listener;
+    private boolean cursorVisible = true;
+    private int mouseX;
+    private int mouseY;
+    private int mouseWheel;
+    private boolean initialized;
+    private GLFWCursorPosCallback cursorPosCallback;
+    private GLFWScrollCallback scrollCallback;
+    private GLFWMouseButtonCallback mouseButtonCallback;
+    private Queue<MouseMotionEvent> mouseMotionEvents = new LinkedList<MouseMotionEvent>();
+    private Queue<MouseButtonEvent> mouseButtonEvents = new LinkedList<MouseButtonEvent>();
+
+    public LwjglMouseInput(LwjglWindow context) {
+        this.context = context;
+    }
+
+    public void initialize() {
+        glfwSetCursorPosCallback(context.getWindowHandle(), cursorPosCallback = new GLFWCursorPosCallback() {
+            @Override
+            public void invoke(long window, double xpos, double ypos) {
+                int xDelta;
+                int yDelta;
+                int x = (int) Math.round(xpos);
+                int y = (int) Math.round(ypos);
+
+                if (mouseX == 0) {
+                    mouseX = x;
+                }
+
+                if (mouseY == 0) {
+                    mouseY = y;
+                }
+
+                xDelta = x - mouseX;
+                yDelta = y - mouseY;
+                mouseX = x;
+                mouseY = y;
+
+                if (xDelta != 0 || yDelta != 0) {
+                    final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(x, y * -1, xDelta, yDelta * -1, mouseWheel, 0);
+                    mouseMotionEvent.setTime(getInputTimeNanos());
+                    mouseMotionEvents.add(mouseMotionEvent);
+                }
+            }
+        });
+
+        glfwSetScrollCallback(context.getWindowHandle(), scrollCallback = new GLFWScrollCallback() {
+            @Override
+            public void invoke(final long window, final double xOffset, final double yOffset) {
+                mouseWheel += yOffset;
+
+                final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(mouseX, mouseY, 0, 0, mouseWheel, (int) Math.round(yOffset));
+                mouseMotionEvent.setTime(getInputTimeNanos());
+                mouseMotionEvents.add(mouseMotionEvent);
+            }
+        });
+
+        glfwSetMouseButtonCallback(context.getWindowHandle(), mouseButtonCallback = new GLFWMouseButtonCallback() {
+            @Override
+            public void invoke(final long window, final int button, final int action, final int mods) {
+                final MouseButtonEvent mouseButtonEvent = new MouseButtonEvent(button, action == GLFW_PRESS, mouseX, mouseY);
+                mouseButtonEvent.setTime(getInputTimeNanos());
+                mouseButtonEvents.add(mouseButtonEvent);
+            }
+        });
+
+        setCursorVisible(cursorVisible);
+        logger.fine("Mouse created.");
+        initialized = true;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    public int getButtonCount() {
+        return 2; // TODO: How to determine this?
+    }
+
+    public void update() {
+        while (!mouseMotionEvents.isEmpty()) {
+            listener.onMouseMotionEvent(mouseMotionEvents.poll());
+        }
+
+        while (!mouseButtonEvents.isEmpty()) {
+            listener.onMouseButtonEvent(mouseButtonEvents.poll());
+        }
+    }
+
+    public void destroy() {
+        if (!context.isRenderable()) {
+            return;
+        }
+
+        cursorPosCallback.release();
+        scrollCallback.release();
+        mouseButtonCallback.release();
+
+        logger.fine("Mouse destroyed.");
+    }
+
+    public void setCursorVisible(boolean visible) {
+        cursorVisible = visible;
+
+        if (!context.isRenderable()) {
+            return;
+        }
+
+        if (cursorVisible) {
+            glfwSetInputMode(context.getWindowHandle(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
+        } else {
+            glfwSetInputMode(context.getWindowHandle(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
+        }
+    }
+
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    public long getInputTimeNanos() {
+        return (long) (glfwGetTime() * LwjglTimer.LWJGL_TIME_TO_NANOS);
+    }
+
+    public void setNativeCursor(final JmeCursor jmeCursor) {
+        if (jmeCursor != null) {
+            final ByteBuffer byteBuffer = org.lwjgl.BufferUtils.createByteBuffer(jmeCursor.getImagesData().capacity());
+            byteBuffer.asIntBuffer().put(jmeCursor.getImagesData().array());
+            final long cursor = glfwCreateCursor(byteBuffer, jmeCursor.getXHotSpot(), jmeCursor.getYHotSpot());
+            glfwSetCursor(context.getWindowHandle(), cursor);
+        }
+    }
+}

+ 458 - 0
jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java

@@ -0,0 +1,458 @@
+package com.jme3.renderer.lwjgl;
+
+import com.jme3.renderer.RendererException;
+import com.jme3.renderer.opengl.GL;
+import com.jme3.renderer.opengl.GL2;
+import com.jme3.renderer.opengl.GL3;
+import com.jme3.renderer.opengl.GL4;
+import com.jme3.system.NativeLibraryLoader;
+import com.jme3.system.Platform;
+import org.lwjgl.opengl.*;
+
+import java.nio.*;
+
+public class LwjglGL implements GL, GL2, GL3, GL4 {
+
+    private static void checkLimit(Buffer buffer) {
+        if (buffer == null) {
+            return;
+        }
+        if (buffer.limit() == 0) {
+            throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error");
+        }
+        if (buffer.remaining() == 0) {
+            throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
+        }
+    }
+    
+    public void resetStats() {
+    }
+    
+    public void glActiveTexture(int param1) {
+        GL13.glActiveTexture(param1);
+    }
+
+    public void glAlphaFunc(int param1, float param2) {
+        GL11.glAlphaFunc(param1, param2);
+    }
+
+    public void glAttachShader(int param1, int param2) {
+        GL20.glAttachShader(param1, param2);
+    }
+
+    public void glBindBuffer(int param1, int param2) {
+        GL15.glBindBuffer(param1, param2);
+    }
+
+    public void glBindTexture(int param1, int param2) {
+        GL11.glBindTexture(param1, param2);
+    }
+
+    public void glBlendFunc(int param1, int param2) {
+        GL11.glBlendFunc(param1, param2);
+    }
+
+    public void glBufferData(int param1, long param2, int param3) {
+        GL15.glBufferData(param1, param2, param3);
+    }
+    
+    public void glBufferData(int param1, FloatBuffer param2, int param3) {
+        checkLimit(param2);
+        GL15.glBufferData(param1, param2, param3);
+    }
+
+    public void glBufferData(int param1, ShortBuffer param2, int param3) {
+        checkLimit(param2);
+        GL15.glBufferData(param1, param2, param3);
+    }
+
+    public void glBufferData(int param1, ByteBuffer param2, int param3) {
+        checkLimit(param2);
+        GL15.glBufferData(param1, param2, param3);
+    }
+
+    public void glBufferSubData(int param1, long param2, FloatBuffer param3) {
+        checkLimit(param3);
+        GL15.glBufferSubData(param1, param2, param3);
+    }
+
+    public void glBufferSubData(int param1, long param2, ShortBuffer param3) {
+        checkLimit(param3);
+        GL15.glBufferSubData(param1, param2, param3);
+    }
+
+    public void glBufferSubData(int param1, long param2, ByteBuffer param3) {
+        checkLimit(param3);
+        GL15.glBufferSubData(param1, param2, param3);
+    }
+
+    public void glClear(int param1) {
+        GL11.glClear(param1);
+    }
+
+    public void glClearColor(float param1, float param2, float param3, float param4) {
+        GL11.glClearColor(param1, param2, param3, param4);
+    }
+
+    public void glColorMask(boolean param1, boolean param2, boolean param3, boolean param4) {
+        GL11.glColorMask(param1, param2, param3, param4);
+    }
+
+    public void glCompileShader(int param1) {
+        GL20.glCompileShader(param1);
+    }
+
+    public void glCompressedTexImage2D(int param1, int param2, int param3, int param4, int param5, int param6, ByteBuffer param7) {
+        checkLimit(param7);
+        GL13.glCompressedTexImage2D(param1, param2, param3, param4, param5, param6, param7);
+    }
+
+    public void glCompressedTexImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, ByteBuffer param8) {
+        checkLimit(param8);
+        GL13.glCompressedTexImage3D(param1, param2, param3, param4, param5, param6, param7, param8);
+    }
+
+    public void glCompressedTexSubImage2D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, ByteBuffer param8) {
+        checkLimit(param8);
+        GL13.glCompressedTexSubImage2D(param1, param2, param3, param4, param5, param6, param7, param8);
+    }
+
+    public void glCompressedTexSubImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, int param9, ByteBuffer param10) {
+        checkLimit(param10);
+        GL13.glCompressedTexSubImage3D(param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
+    }
+
+    public int glCreateProgram() {
+        return GL20.glCreateProgram();
+    }
+
+    public int glCreateShader(int param1) {
+        return GL20.glCreateShader(param1);
+    }
+
+    public void glCullFace(int param1) {
+        GL11.glCullFace(param1);
+    }
+
+    public void glDeleteBuffers(IntBuffer param1) {
+        checkLimit(param1);
+        GL15.glDeleteBuffers(param1);
+    }
+
+    public void glDeleteProgram(int param1) {
+        GL20.glDeleteProgram(param1);
+    }
+
+    public void glDeleteShader(int param1) {
+        GL20.glDeleteShader(param1);
+    }
+
+    public void glDeleteTextures(IntBuffer param1) {
+        checkLimit(param1);
+        GL11.glDeleteTextures(param1);
+    }
+
+    public void glDepthFunc(int param1) {
+        GL11.glDepthFunc(param1);
+    }
+
+    public void glDepthMask(boolean param1) {
+        GL11.glDepthMask(param1);
+    }
+
+    public void glDepthRange(double param1, double param2) {
+        GL11.glDepthRange(param1, param2);
+    }
+
+    public void glDetachShader(int param1, int param2) {
+        GL20.glDetachShader(param1, param2);
+    }
+
+    public void glDisable(int param1) {
+        GL11.glDisable(param1);
+    }
+
+    public void glDisableVertexAttribArray(int param1) {
+        GL20.glDisableVertexAttribArray(param1);
+    }
+
+    public void glDrawArrays(int param1, int param2, int param3) {
+        GL11.glDrawArrays(param1, param2, param3);
+    }
+
+    public void glDrawBuffer(int param1) {
+        GL11.glDrawBuffer(param1);
+    }
+    
+    public void glDrawRangeElements(int param1, int param2, int param3, int param4, int param5, long param6) {
+        GL12.glDrawRangeElements(param1, param2, param3, param4, param5, param6);
+    }
+
+    public void glEnable(int param1) {
+        GL11.glEnable(param1);
+    }
+
+    public void glEnableVertexAttribArray(int param1) {
+        GL20.glEnableVertexAttribArray(param1);
+    }
+
+    public void glGenBuffers(IntBuffer param1) {
+        checkLimit(param1);
+        GL15.glGenBuffers(param1);
+    }
+
+    public void glGenTextures(IntBuffer param1) {
+        checkLimit(param1);
+        GL11.glGenTextures(param1);
+    }
+
+    public void glGetBoolean(int param1, ByteBuffer param2) {
+        checkLimit(param2);
+        GL11.glGetBooleanv(param1, param2);
+    }
+    
+    public void glGetBufferSubData(int target, long offset, ByteBuffer data) {
+        checkLimit(data);
+        GL15.glGetBufferSubData(target, offset, data);
+    }
+
+    public int glGetError() {
+        return GL11.glGetError();
+    }
+    
+    public void glGetInteger(int param1, IntBuffer param2) {
+        checkLimit(param2);
+        GL11.glGetIntegerv(param1, param2);
+    }
+
+    public void glGetProgram(int param1, int param2, IntBuffer param3) {
+        checkLimit(param3);
+        GL20.glGetProgramiv(param1, param2, param3);
+    }
+
+    public void glGetShader(int param1, int param2, IntBuffer param3) {
+        checkLimit(param3);
+        GL20.glGetShaderiv(param1, param2, param3);
+    }
+
+    public String glGetString(int param1) {
+        return GL11.glGetString(param1);
+    }
+    
+    public String glGetString(int param1, int param2) {
+        return GL30.glGetStringi(param1, param2);
+    }
+
+    public boolean glIsEnabled(int param1) {
+        return GL11.glIsEnabled(param1);
+    }
+
+    public void glLineWidth(float param1) {
+        GL11.glLineWidth(param1);
+    }
+
+    public void glLinkProgram(int param1) {
+        GL20.glLinkProgram(param1);
+    }
+
+    public void glPixelStorei(int param1, int param2) {
+        GL11.glPixelStorei(param1, param2);
+    }
+
+    public void glPointSize(float param1) {
+        GL11.glPointSize(param1);
+    }
+
+    public void glPolygonMode(int param1, int param2) {
+        GL11.glPolygonMode(param1, param2);
+    }
+
+    public void glPolygonOffset(float param1, float param2) {
+        GL11.glPolygonOffset(param1, param2);
+    }
+
+    public void glReadBuffer(int param1) {
+        GL11.glReadBuffer(param1);
+    }
+
+    public void glReadPixels(int param1, int param2, int param3, int param4, int param5, int param6, ByteBuffer param7) {
+        checkLimit(param7);
+        GL11.glReadPixels(param1, param2, param3, param4, param5, param6, param7);
+    }
+    
+    public void glReadPixels(int param1, int param2, int param3, int param4, int param5, int param6, long param7) {
+        GL11.glReadPixels(param1, param2, param3, param4, param5, param6, param7);
+    }
+
+    public void glScissor(int param1, int param2, int param3, int param4) {
+        GL11.glScissor(param1, param2, param3, param4);
+    }
+
+    public void glStencilFuncSeparate(int param1, int param2, int param3, int param4) {
+        GL20.glStencilFuncSeparate(param1, param2, param3, param4);
+    }
+
+    public void glStencilOpSeparate(int param1, int param2, int param3, int param4) {
+        GL20.glStencilOpSeparate(param1, param2, param3, param4);
+    }
+
+    public void glTexImage2D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, ByteBuffer param9) {
+        checkLimit(param9);
+        GL11.glTexImage2D(param1, param2, param3, param4, param5, param6, param7, param8, param9);
+    }
+
+    public void glTexImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, int param9, ByteBuffer param10) {
+        checkLimit(param10);
+        GL12.glTexImage3D(param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
+    }
+
+    public void glTexParameterf(int param1, int param2, float param3) {
+        GL11.glTexParameterf(param1, param2, param3);
+    }
+
+    public void glTexParameteri(int param1, int param2, int param3) {
+        GL11.glTexParameteri(param1, param2, param3);
+    }
+
+    public void glTexSubImage2D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, ByteBuffer param9) {
+        checkLimit(param9);
+        GL11.glTexSubImage2D(param1, param2, param3, param4, param5, param6, param7, param8, param9);
+    }
+
+    public void glTexSubImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, int param9, int param10, ByteBuffer param11) {
+        checkLimit(param11);
+        GL12.glTexSubImage3D(param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11);
+    }
+
+    public void glUniform1(int param1, FloatBuffer param2) {
+        checkLimit(param2);
+        GL20.glUniform1fv(param1, param2);
+    }
+
+    public void glUniform1(int param1, IntBuffer param2) {
+        checkLimit(param2);
+        GL20.glUniform1iv(param1, param2);
+    }
+
+    public void glUniform1f(int param1, float param2) {
+        GL20.glUniform1f(param1, param2);
+    }
+
+    public void glUniform1i(int param1, int param2) {
+        GL20.glUniform1i(param1, param2);
+    }
+
+    public void glUniform2(int param1, IntBuffer param2) {
+        checkLimit(param2);
+        GL20.glUniform2iv(param1, param2);
+    }
+
+    public void glUniform2(int param1, FloatBuffer param2) {
+        checkLimit(param2);
+        GL20.glUniform2fv(param1, param2);
+    }
+
+    public void glUniform2f(int param1, float param2, float param3) {
+        GL20.glUniform2f(param1, param2, param3);
+    }
+
+    public void glUniform3(int param1, IntBuffer param2) {
+        checkLimit(param2);
+        GL20.glUniform3iv(param1, param2);
+    }
+
+    public void glUniform3(int param1, FloatBuffer param2) {
+        checkLimit(param2);
+        GL20.glUniform3fv(param1, param2);
+    }
+
+    public void glUniform3f(int param1, float param2, float param3, float param4) {
+        GL20.glUniform3f(param1, param2, param3, param4);
+    }
+
+    public void glUniform4(int param1, FloatBuffer param2) {
+        checkLimit(param2);
+        GL20.glUniform4fv(param1, param2);
+    }
+
+    public void glUniform4(int param1, IntBuffer param2) {
+        checkLimit(param2);
+        GL20.glUniform4iv(param1, param2);
+    }
+
+    public void glUniform4f(int param1, float param2, float param3, float param4, float param5) {
+        GL20.glUniform4f(param1, param2, param3, param4, param5);
+    }
+
+    public void glUniformMatrix3(int param1, boolean param2, FloatBuffer param3) {
+        checkLimit(param3);
+        GL20.glUniformMatrix3fv(param1, param2, param3);
+    }
+
+    public void glUniformMatrix4(int param1, boolean param2, FloatBuffer param3) {
+        checkLimit(param3);
+        GL20.glUniformMatrix4fv(param1, param2, param3);
+    }
+
+    public void glUseProgram(int param1) {
+        GL20.glUseProgram(param1);
+    }
+
+    public void glVertexAttribPointer(int param1, int param2, int param3, boolean param4, int param5, long param6) {
+        GL20.glVertexAttribPointer(param1, param2, param3, param4, param5, param6);
+    }
+
+    public void glViewport(int param1, int param2, int param3, int param4) {
+        GL11.glViewport(param1, param2, param3, param4);
+    }
+
+    public int glGetAttribLocation(int param1, String param2) {
+        // NOTE: LWJGL requires null-terminated strings
+        return GL20.glGetAttribLocation(param1, param2 + "\0");
+    }
+
+    public int glGetUniformLocation(int param1, String param2) {
+        // NOTE: LWJGL requires null-terminated strings
+        return GL20.glGetUniformLocation(param1, param2 + "\0");
+    }
+
+    public void glShaderSource(int param1, String[] param2, IntBuffer param3) {
+        checkLimit(param3);
+        GL20.glShaderSource(param1, param2);
+    }
+
+    public String glGetProgramInfoLog(int program, int maxSize) {
+        return GL20.glGetProgramInfoLog(program, maxSize);
+    }
+
+    public String glGetShaderInfoLog(int shader, int maxSize) {
+        return GL20.glGetShaderInfoLog(shader, maxSize);
+    }
+
+    @Override
+    public void glBindFragDataLocation(int param1, int param2, String param3) {
+        GL30.glBindFragDataLocation(param1, param2, param3);
+    }
+
+    @Override
+    public void glBindVertexArray(int param1) {
+        GL30.glBindVertexArray(param1);
+    }
+
+    @Override
+    public void glGenVertexArrays(IntBuffer param1) {
+        checkLimit(param1);
+        GL30.glGenVertexArrays(param1);
+    }
+
+    @Override
+    public void glPatchParameter(int count) {
+        GL40.glPatchParameteri(GL40.GL_PATCH_VERTICES,count);
+    }
+    
+    @Override
+    public void glDeleteVertexArrays(IntBuffer arrays) {
+        checkLimit(arrays);
+        ARBVertexArrayObject.glDeleteVertexArrays(arrays);
+    }
+}

+ 83 - 0
jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java

@@ -0,0 +1,83 @@
+package com.jme3.renderer.lwjgl;
+
+import com.jme3.renderer.RendererException;
+import com.jme3.renderer.opengl.GLExt;
+import org.lwjgl.opengl.*;
+
+import java.nio.Buffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+public class LwjglGLExt implements GLExt {
+
+    private static void checkLimit(Buffer buffer) {
+        if (buffer == null) {
+            return;
+        }
+        if (buffer.limit() == 0) {
+            throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error");
+        }
+        if (buffer.remaining() == 0) {
+            throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
+        }
+    }
+
+    @Override
+    public void glBufferData(int target, IntBuffer data, int usage) {
+        checkLimit(data);
+        GL15.glBufferData(target, data, usage);
+    }
+
+    @Override
+    public void glBufferSubData(int target, long offset, IntBuffer data) {
+        checkLimit(data);
+        GL15.glBufferSubData(target, offset, data);
+    }
+
+    @Override
+    public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) {
+        ARBDrawInstanced.glDrawArraysInstancedARB(mode, first, count, primcount);
+    }
+
+    @Override
+    public void glDrawBuffers(IntBuffer bufs) {
+        checkLimit(bufs);
+        GL20.glDrawBuffers(bufs);
+    }
+
+    @Override
+    public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) {
+        ARBDrawInstanced.glDrawElementsInstancedARB(mode, indices_count, type, indices_buffer_offset, primcount);
+    }
+
+    @Override
+    public void glGetMultisample(int pname, int index, FloatBuffer val) {
+        checkLimit(val);
+        ARBTextureMultisample.glGetMultisamplefv(pname, index, val);
+    }
+
+    @Override
+    public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) {
+        ARBTextureMultisample.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations);
+    }
+
+    @Override
+    public void glVertexAttribDivisorARB(int index, int divisor) {
+        ARBInstancedArrays.glVertexAttribDivisorARB(index, divisor);
+    }
+
+    @Override
+    public Object glFenceSync(int condition, int flags) {
+        return ARBSync.glFenceSync(condition, flags);
+    }
+    
+    @Override
+    public int glClientWaitSync(final Object sync, final int flags, final long timeout) {
+        return ARBSync.glClientWaitSync((Long) sync, flags, timeout);
+    }
+
+    @Override
+    public void glDeleteSync(final Object sync) {
+        ARBSync.glDeleteSync((Long) sync);
+    }
+}

+ 99 - 0
jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java

@@ -0,0 +1,99 @@
+package com.jme3.renderer.lwjgl;
+
+import com.jme3.renderer.RendererException;
+import com.jme3.renderer.opengl.GLFbo;
+import org.lwjgl.opengl.EXTFramebufferBlit;
+import org.lwjgl.opengl.EXTFramebufferMultisample;
+import org.lwjgl.opengl.EXTFramebufferObject;
+
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+
+/**
+ * Implements GLFbo via GL_EXT_framebuffer_object.
+ * 
+ * @author Kirill Vainer
+ */
+public class LwjglGLFboEXT implements GLFbo {
+
+    private static void checkLimit(Buffer buffer) {
+        if (buffer == null) {
+            return;
+        }
+        if (buffer.limit() == 0) {
+            throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error");
+        }
+        if (buffer.remaining() == 0) {
+            throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
+        }
+    }
+    
+    @Override
+    public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
+        EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
+    }
+    
+    @Override
+    public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
+        EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
+    }
+    
+    @Override
+    public void glBindFramebufferEXT(int param1, int param2) {
+        EXTFramebufferObject.glBindFramebufferEXT(param1, param2);
+    }
+    
+    @Override
+    public void glBindRenderbufferEXT(int param1, int param2) {
+        EXTFramebufferObject.glBindRenderbufferEXT(param1, param2);
+    }
+    
+    @Override
+    public int glCheckFramebufferStatusEXT(int param1) {
+        return EXTFramebufferObject.glCheckFramebufferStatusEXT(param1);
+    }
+    
+    @Override
+    public void glDeleteFramebuffersEXT(IntBuffer param1) {
+        checkLimit(param1);
+        EXTFramebufferObject.glDeleteFramebuffersEXT(param1);
+    }
+    
+    @Override
+    public void glDeleteRenderbuffersEXT(IntBuffer param1) {
+        checkLimit(param1);
+        EXTFramebufferObject.glDeleteRenderbuffersEXT(param1);
+    }
+    
+    @Override
+    public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
+        EXTFramebufferObject.glFramebufferRenderbufferEXT(param1, param2, param3, param4);
+    }
+    
+    @Override
+    public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
+        EXTFramebufferObject.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5);
+    }
+    
+    @Override
+    public void glGenFramebuffersEXT(IntBuffer param1) {
+        checkLimit(param1);
+        EXTFramebufferObject.glGenFramebuffersEXT(param1);
+    }
+    
+    @Override
+    public void glGenRenderbuffersEXT(IntBuffer param1) {
+        checkLimit(param1);
+        EXTFramebufferObject.glGenRenderbuffersEXT(param1);
+    }
+    
+    @Override
+    public void glGenerateMipmapEXT(int param1) {
+        EXTFramebufferObject.glGenerateMipmapEXT(param1);
+    }
+    
+    @Override
+    public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
+        EXTFramebufferObject.glRenderbufferStorageEXT(param1, param2, param3, param4);
+    }
+}

+ 97 - 0
jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java

@@ -0,0 +1,97 @@
+package com.jme3.renderer.lwjgl;
+
+import com.jme3.renderer.RendererException;
+import com.jme3.renderer.opengl.GLFbo;
+import org.lwjgl.opengl.GL30;
+
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+
+/**
+ * Implements GLFbo via OpenGL3+.
+ * 
+ * @author Kirill Vainer
+ */
+public class LwjglGLFboGL3 implements GLFbo {
+
+    private static void checkLimit(Buffer buffer) {
+        if (buffer == null) {
+            return;
+        }
+        if (buffer.limit() == 0) {
+            throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error");
+        }
+        if (buffer.remaining() == 0) {
+            throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
+        }
+    }
+    
+    @Override
+    public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
+        GL30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
+    }
+    
+    @Override
+    public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
+        GL30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
+    }
+    
+    @Override
+    public void glBindFramebufferEXT(int param1, int param2) {
+        GL30.glBindFramebuffer(param1, param2);
+    }
+    
+    @Override
+    public void glBindRenderbufferEXT(int param1, int param2) {
+        GL30.glBindRenderbuffer(param1, param2);
+    }
+    
+    @Override
+    public int glCheckFramebufferStatusEXT(int param1) {
+        return GL30.glCheckFramebufferStatus(param1);
+    }
+    
+    @Override
+    public void glDeleteFramebuffersEXT(IntBuffer param1) {
+        checkLimit(param1);
+        GL30.glDeleteFramebuffers(param1);
+    }
+    
+    @Override
+    public void glDeleteRenderbuffersEXT(IntBuffer param1) {
+        checkLimit(param1);
+        GL30.glDeleteRenderbuffers(param1);
+    }
+    
+    @Override
+    public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
+        GL30.glFramebufferRenderbuffer(param1, param2, param3, param4);
+    }
+    
+    @Override
+    public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
+        GL30.glFramebufferTexture2D(param1, param2, param3, param4, param5);
+    }
+    
+    @Override
+    public void glGenFramebuffersEXT(IntBuffer param1) {
+        checkLimit(param1);
+        GL30.glGenFramebuffers(param1);
+    }
+    
+    @Override
+    public void glGenRenderbuffersEXT(IntBuffer param1) {
+        checkLimit(param1);
+        GL30.glGenRenderbuffers(param1);
+    }
+    
+    @Override
+    public void glGenerateMipmapEXT(int param1) {
+        GL30.glGenerateMipmap(param1);
+    }
+    
+    @Override
+    public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
+        GL30.glRenderbufferStorage(param1, param2, param3, param4);
+    }
+}

+ 369 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java

@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.lwjgl;
+
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeCanvasContext;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static org.lwjgl.glfw.GLFW.glfwDestroyWindow;
+
+public class LwjglCanvas extends LwjglWindow implements JmeCanvasContext {
+
+    protected static final int TASK_NOTHING = 0,
+                               TASK_DESTROY_DISPLAY = 1,
+                               TASK_CREATE_DISPLAY = 2,
+                               TASK_COMPLETE = 3;
+    
+//    protected static final boolean USE_SHARED_CONTEXT =
+//                Boolean.parseBoolean(System.getProperty("jme3.canvas.sharedctx", "true"));
+    
+    protected static final boolean USE_SHARED_CONTEXT = false;
+    
+    private static final Logger logger = Logger.getLogger(LwjglCanvas.class.getName());
+    private Canvas canvas;
+    private int width;
+    private int height;
+
+    private final Object taskLock = new Object();
+    private int desiredTask = TASK_NOTHING;
+
+    private Thread renderThread;
+    private boolean runningFirstTime = true;
+    private boolean mouseWasGrabbed = false;
+    
+    private boolean mouseWasCreated = false;
+    private boolean keyboardWasCreated = false;
+
+    private long window;
+
+    private class GLCanvas extends Canvas {
+        @Override
+        public void addNotify(){
+            super.addNotify();
+
+            if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED) {
+                return; // already destroyed.
+            }
+
+            if (renderThread == null){
+                logger.log(Level.FINE, "EDT: Creating OGL thread.");
+
+                // Also set some settings on the canvas here.
+                // So we don't do it outside the AWT thread.
+                canvas.setFocusable(true);
+                canvas.setIgnoreRepaint(true);
+
+                renderThread = new Thread(LwjglCanvas.this, THREAD_NAME);
+                renderThread.start();
+            }else if (needClose.get()){
+                return;
+            }
+
+            logger.log(Level.FINE, "EDT: Telling OGL to create display ..");
+            synchronized (taskLock){
+                desiredTask = TASK_CREATE_DISPLAY;
+//                while (desiredTask != TASK_COMPLETE){
+//                    try {
+//                        taskLock.wait();
+//                    } catch (InterruptedException ex) {
+//                        return;
+//                    }
+//                }
+//                desiredTask = TASK_NOTHING;
+            }
+//            logger.log(Level.FINE, "EDT: OGL has created the display");
+        }
+
+        @Override
+        public void removeNotify(){
+            if (needClose.get()){
+                logger.log(Level.FINE, "EDT: Application is stopped. Not restoring canvas.");
+                super.removeNotify();
+                return;
+            }
+
+            // We must tell GL context to shutdown and wait for it to
+            // shutdown, otherwise, issues will occur.
+            logger.log(Level.FINE, "EDT: Telling OGL to destroy display ..");
+            synchronized (taskLock){
+                desiredTask = TASK_DESTROY_DISPLAY;
+                while (desiredTask != TASK_COMPLETE){
+                    try {
+                        taskLock.wait();
+                    } catch (InterruptedException ex){
+                        super.removeNotify();
+                        return;
+                    }
+                }
+                desiredTask = TASK_NOTHING;
+            }
+            
+            logger.log(Level.FINE, "EDT: Acknowledged receipt of canvas death");
+            // GL context is dead at this point
+
+            super.removeNotify();
+        }
+    }
+
+    public LwjglCanvas(){
+        super(Type.Canvas);
+        canvas = new GLCanvas();
+    }
+
+    public void create(boolean waitFor){
+        if (renderThread == null){
+            logger.log(Level.FINE, "MAIN: Creating OGL thread.");
+
+            renderThread = new Thread(LwjglCanvas.this, THREAD_NAME);
+            renderThread.start();
+        }
+        // do not do anything.
+        // superclass's create() will be called at initInThread()
+        if (waitFor) {
+            waitFor(true);
+        }
+    }
+
+    public Canvas getCanvas(){
+        return canvas;
+    }
+    
+    @Override
+    protected void runLoop(){
+        if (desiredTask != TASK_NOTHING){
+            synchronized (taskLock){
+                switch (desiredTask){
+                    case TASK_CREATE_DISPLAY:
+                        logger.log(Level.FINE, "OGL: Creating display ..");
+                        restoreCanvas();
+                        listener.gainFocus();
+                        desiredTask = TASK_NOTHING;
+                        break;
+                    case TASK_DESTROY_DISPLAY:
+                        logger.log(Level.FINE, "OGL: Destroying display ..");
+                        listener.loseFocus();
+                        pauseCanvas();
+                        break;
+                }
+                desiredTask = TASK_COMPLETE;
+                taskLock.notifyAll();
+            }
+        }
+        
+        if (renderable.get()){
+            int newWidth = Math.max(canvas.getWidth(), 1);
+            int newHeight = Math.max(canvas.getHeight(), 1);
+
+            if (width != newWidth || height != newHeight){
+                width = newWidth;
+                height = newHeight;
+                if (listener != null){
+                    listener.reshape(width, height);
+                }
+            }
+        }
+        
+        super.runLoop();
+    }
+
+    private void pauseCanvas(){
+        if (mouseInput != null) {
+            mouseInput.setCursorVisible(true);
+            mouseWasCreated = true;
+        }
+
+/*
+        if (Mouse.isCreated()){
+            if (Mouse.isGrabbed()){
+                Mouse.setGrabbed(false);
+                mouseWasGrabbed = true;
+            }
+            mouseWasCreated = true;
+            Mouse.destroy();
+        }
+        if (Keyboard.isCreated()){
+            keyboardWasCreated = true;
+            Keyboard.destroy();
+        }
+*/
+
+        renderable.set(false);
+        destroyContext();
+    }
+
+    /**
+     * Called to restore the canvas.
+     */
+    private void restoreCanvas(){
+        logger.log(Level.FINE, "OGL: Waiting for canvas to become displayable..");
+        while (!canvas.isDisplayable()){
+            try {
+                Thread.sleep(10);
+            } catch (InterruptedException ex) {
+                logger.log(Level.SEVERE, "OGL: Interrupted! ", ex);
+            }
+        }
+        
+        logger.log(Level.FINE, "OGL: Creating display context ..");
+
+        // Set renderable to true, since canvas is now displayable.
+        renderable.set(true);
+        createContext(settings);
+
+        logger.log(Level.FINE, "OGL: Display is active!");
+
+        try {
+            if (mouseWasCreated){
+//                Mouse.create();
+//                if (mouseWasGrabbed){
+//                    Mouse.setGrabbed(true);
+//                    mouseWasGrabbed = false;
+//                }
+            }
+            if (keyboardWasCreated){
+//                Keyboard.create();
+//                keyboardWasCreated = false;
+            }
+        } catch (Exception ex){
+            logger.log(Level.SEVERE, "Encountered exception when restoring input", ex);
+        }
+
+        SwingUtilities.invokeLater(new Runnable(){
+            public void run(){
+                canvas.requestFocus();
+            }
+        });
+    }
+    
+/*
+    */
+/**
+     * Makes sure the pbuffer is available and ready for use
+     *//*
+
+    protected void makePbufferAvailable() throws LWJGLException{
+        if (pbuffer != null && pbuffer.isBufferLost()){
+            logger.log(Level.WARNING, "PBuffer was lost!");
+            pbuffer.destroy();
+            pbuffer = null;
+        }
+        
+        if (pbuffer == null) {
+            pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null);
+            pbuffer.makeCurrent();
+            logger.log(Level.FINE, "OGL: Pbuffer has been created");
+            
+            // Any created objects are no longer valid
+            if (!runningFirstTime){
+                renderer.resetGLObjects();
+            }
+        }
+        
+        pbuffer.makeCurrent();
+        if (!pbuffer.isCurrent()){
+            throw new LWJGLException("Pbuffer cannot be made current");
+        }
+    }
+    
+    protected void destroyPbuffer(){
+        if (pbuffer != null){
+            if (!pbuffer.isBufferLost()){
+                pbuffer.destroy();
+            }
+            pbuffer = null;
+        }
+    }
+*/
+
+    /**
+     * This is called:
+     * 1) When the context thread ends
+     * 2) Any time the canvas becomes non-displayable
+     */
+    protected void destroyContext(){
+        // invalidate the state so renderer can resume operation
+        if (!USE_SHARED_CONTEXT){
+            renderer.cleanup();
+        }
+
+        if (window != 0) {
+            glfwDestroyWindow(window);
+        }
+
+        // TODO: Destroy input
+
+
+        // The canvas is no longer visible,
+        // but the context thread is still running.
+        if (!needClose.get()){
+            renderer.invalidateState();
+        }
+    }
+
+    /**
+     * This is called:
+     * 1) When the context thread starts
+     * 2) Any time the canvas becomes displayable again.
+     */
+    @Override
+    protected void createContext(final AppSettings settings) {
+        // In case canvas is not visible, we still take framerate
+        // from settings to prevent "100% CPU usage"
+        allowSwapBuffers = settings.isSwapBuffers();
+        
+        if (renderable.get()){
+            if (!runningFirstTime){
+                // because the display is a different opengl context
+                // must reset the context state.
+                if (!USE_SHARED_CONTEXT){
+                    renderer.cleanup();
+                }
+            }
+
+            super.createContext(settings);
+        }
+
+        // At this point, the OpenGL context is active.
+        if (runningFirstTime) {
+            // THIS is the part that creates the renderer.
+            // It must always be called, now that we have the pbuffer workaround.
+            initContextFirstTime();
+            runningFirstTime = false;
+        }
+    }
+}

+ 289 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java

@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.lwjgl;
+
+import com.jme3.input.lwjgl.GlfwJoystickInput;
+import com.jme3.input.lwjgl.LwjglKeyInput;
+import com.jme3.input.lwjgl.LwjglMouseInput;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.RendererException;
+import com.jme3.renderer.lwjgl.LwjglGL;
+import com.jme3.renderer.lwjgl.LwjglGLExt;
+import com.jme3.renderer.lwjgl.LwjglGLFboEXT;
+import com.jme3.renderer.lwjgl.LwjglGLFboGL3;
+import com.jme3.renderer.opengl.*;
+import com.jme3.renderer.opengl.GL;
+import com.jme3.system.*;
+import org.lwjgl.Sys;
+import org.lwjgl.glfw.GLFW;
+import org.lwjgl.opengl.*;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static org.lwjgl.opengl.GL.createCapabilities;
+import static org.lwjgl.opengl.GL11.GL_TRUE;
+import static org.lwjgl.opengl.GL11.glGetInteger;
+
+/**
+ * A LWJGL implementation of a graphics context.
+ */
+public abstract class LwjglContext implements JmeContext {
+
+    private static final Logger logger = Logger.getLogger(LwjglContext.class.getName());
+
+    protected static final String THREAD_NAME = "jME3 Main";
+
+    protected AtomicBoolean created = new AtomicBoolean(false);
+    protected AtomicBoolean renderable = new AtomicBoolean(false);
+    protected final Object createdLock = new Object();
+
+    protected AppSettings settings = new AppSettings(true);
+    protected Renderer renderer;
+    protected LwjglKeyInput keyInput;
+    protected LwjglMouseInput mouseInput;
+    protected GlfwJoystickInput joyInput;
+    protected Timer timer;
+    protected SystemListener listener;
+
+    public void setSystemListener(SystemListener listener) {
+        this.listener = listener;
+    }
+
+    protected void printContextInitInfo() {
+        logger.log(Level.INFO, "LWJGL {0} context running on thread {1}\n" +
+                        " * Graphics Adapter: GLFW {2}",
+                new Object[]{Sys.getVersion(), Thread.currentThread().getName(), GLFW.glfwGetVersionString()});
+    }
+
+    protected int determineMaxSamples() {
+        // If we already have a valid context, determine samples using current context.
+        if (GLFW.glfwExtensionSupported("GL_ARB_framebuffer_object") == GL_TRUE) {
+            return glGetInteger(ARBFramebufferObject.GL_MAX_SAMPLES);
+        } else if (GLFW.glfwExtensionSupported("GL_EXT_framebuffer_multisample") == GL_TRUE) {
+            return glGetInteger(EXTFramebufferMultisample.GL_MAX_SAMPLES_EXT);
+        }
+
+        return Integer.MAX_VALUE;
+    }
+
+    protected void registerNatives() {
+        // LWJGL
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Windows32, "native/windows/lwjgl32.dll");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Windows64, "native/windows/lwjgl.dll");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Linux32, "native/linux/liblwjgl32.so");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Linux64, "native/linux/liblwjgl.so");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.MacOSX32, "native/macosx/liblwjgl.dylib");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.MacOSX64, "native/macosx/liblwjgl.dylib");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Windows32, "native/windows/jemalloc32.dll");
+        NativeLibraryLoader.registerNativeLibrary("lwjgl", Platform.Windows64, "native/windows/jemalloc.dll");
+
+        // OpenAL
+        // For OSX: Need to add lib prefix when extracting
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.Windows32, "native/windows/OpenAL32.dll");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.Windows64, "native/windows/OpenAL.dll");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.Linux32, "native/linux/libopenal32.so");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.Linux64, "native/linux/libopenal.so");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.MacOSX32, "native/macosx/openal.dylib", "libopenal.dylib");
+        NativeLibraryLoader.registerNativeLibrary("openal", Platform.MacOSX64, "native/macosx/openal.dylib", "libopenal.dylib");
+    }
+
+    protected void loadNatives() {
+        if (JmeSystem.isLowPermissions()) {
+            return;
+        }
+        if ("LWJGL".equals(settings.getAudioRenderer())) {
+            NativeLibraryLoader.loadNativeLibrary("openal", true);
+        }
+        if (settings.useJoysticks()) {
+            //NativeLibraryLoader.loadNativeLibrary("jinput", true);
+            //NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true);
+        }
+        if (NativeLibraryLoader.isUsingNativeBullet()) {
+            NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
+        }
+        NativeLibraryLoader.loadNativeLibrary("lwjgl", true);
+    }
+
+    protected int getNumSamplesToUse() {
+        int samples = 0;
+        if (settings.getSamples() > 1) {
+            samples = settings.getSamples();
+            final int supportedSamples = determineMaxSamples();
+            if (supportedSamples < samples) {
+                logger.log(Level.WARNING,
+                        "Couldn't satisfy antialiasing samples requirement: x{0}. "
+                                + "Video hardware only supports: x{1}",
+                        new Object[]{samples, supportedSamples});
+
+                samples = supportedSamples;
+            }
+        }
+        return samples;
+    }
+
+    protected void initContextFirstTime() {
+        final GLCapabilities capabilities = createCapabilities(settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3));
+
+        if (!capabilities.OpenGL20) {
+            throw new RendererException("OpenGL 2.0 or higher is required for jMonkeyEngine");
+        }
+
+        if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2)
+                || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
+            GL gl = new LwjglGL();
+            GLExt glext = new LwjglGLExt();
+            GLFbo glfbo;
+
+            if (capabilities.OpenGL30) {
+                glfbo = new LwjglGLFboGL3();
+            } else {
+                glfbo = new LwjglGLFboEXT();
+            }
+
+            if (settings.getBoolean("GraphicsDebug")) {
+                gl = new GLDebugDesktop(gl, glext, glfbo);
+                glext = (GLExt) gl;
+                glfbo = (GLFbo) gl;
+            }
+
+            if (settings.getBoolean("GraphicsTiming")) {
+                GLTimingState timingState = new GLTimingState();
+                gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class);
+                glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class);
+                glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class);
+            }
+
+            if (settings.getBoolean("GraphicsTrace")) {
+                gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class);
+                glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class);
+                glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class);
+            }
+
+            renderer = new GLRenderer(gl, glext, glfbo);
+            renderer.initialize();
+        } else {
+            throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer());
+        }
+
+        if (capabilities.GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) {
+            ARBDebugOutput.glDebugMessageCallbackARB(new LwjglGLDebugOutputHandler(), 0); // User param is zero. Not sure what we could use that for.
+        }
+
+        renderer.setMainFrameBufferSrgb(settings.getGammaCorrection());
+        renderer.setLinearizeSrgbImages(settings.getGammaCorrection());
+
+        // Init input
+        if (keyInput != null) {
+            keyInput.initialize();
+        }
+
+        if (mouseInput != null) {
+            mouseInput.initialize();
+        }
+
+        if (joyInput != null) {
+            joyInput.initialize();
+        }
+
+        renderable.set(true);
+    }
+
+    public void internalDestroy() {
+        renderer = null;
+        timer = null;
+        renderable.set(false);
+        synchronized (createdLock) {
+            created.set(false);
+            createdLock.notifyAll();
+        }
+    }
+
+    public void internalCreate() {
+        synchronized (createdLock) {
+            created.set(true);
+            createdLock.notifyAll();
+        }
+
+        //if (renderable.get()) {
+            initContextFirstTime();
+        //} else {
+//            assert getType() == Type.Canvas;
+//        }
+    }
+
+    public void create() {
+        create(false);
+    }
+
+    public void destroy() {
+        destroy(false);
+    }
+
+    protected void waitFor(boolean createdVal) {
+        synchronized (createdLock) {
+            while (created.get() != createdVal) {
+                try {
+                    createdLock.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+    }
+
+    public boolean isCreated() {
+        return created.get();
+    }
+
+    public boolean isRenderable() {
+        return renderable.get();
+    }
+
+    public void setSettings(AppSettings settings) {
+        this.settings.copyFrom(settings);
+    }
+
+    public AppSettings getSettings() {
+        return settings;
+    }
+
+    public Renderer getRenderer() {
+        return renderer;
+    }
+
+    public Timer getTimer() {
+        return timer;
+    }
+
+}

+ 12 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java

@@ -0,0 +1,12 @@
+package com.jme3.system.lwjgl;
+
+/**
+ * @author Daniel Johansson
+ * @since 2015-08-11
+ */
+public class LwjglDisplay extends LwjglWindow {
+
+    public LwjglDisplay() {
+        super(Type.Display);
+    }
+}

+ 78 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java

@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2009-2015 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system.lwjgl;
+
+import org.lwjgl.opengl.ARBDebugOutput;
+import org.lwjgl.opengl.GLDebugMessageARBCallback;
+
+import java.util.HashMap;
+
+class LwjglGLDebugOutputHandler extends GLDebugMessageARBCallback {
+
+    private static final HashMap<Integer, String> constMap = new HashMap<Integer, String>();
+    private static final String MESSAGE_FORMAT =
+            "[JME3] OpenGL debug message\r\n" +
+                    "       ID: %d\r\n" +
+                    "       Source: %s\r\n" +
+                    "       Type: %s\r\n" +
+                    "       Severity: %s\r\n" +
+                    "       Message: %s";
+
+    static {
+        constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_API_ARB, "API");
+        constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_APPLICATION_ARB, "APPLICATION");
+        constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_OTHER_ARB, "OTHER");
+        constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_SHADER_COMPILER_ARB, "SHADER_COMPILER");
+        constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_THIRD_PARTY_ARB, "THIRD_PARTY");
+        constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB, "WINDOW_SYSTEM");
+
+        constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB, "DEPRECATED_BEHAVIOR");
+        constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_ERROR_ARB, "ERROR");
+        constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_OTHER_ARB, "OTHER");
+        constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PERFORMANCE_ARB, "PERFORMANCE");
+        constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PORTABILITY_ARB, "PORTABILITY");
+        constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB, "UNDEFINED_BEHAVIOR");
+
+        constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_HIGH_ARB, "HIGH");
+        constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_MEDIUM_ARB, "MEDIUM");
+        constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_LOW_ARB, "LOW");
+    }
+
+    @Override
+    public void invoke(int source, int type, int id, int severity, int length, long message, long userParam) {
+        String sourceStr = constMap.get(source);
+        String typeStr = constMap.get(type);
+        String severityStr = constMap.get(severity);
+
+        System.err.println(String.format(MESSAGE_FORMAT, id, sourceStr, typeStr, severityStr, message));
+    }
+}

+ 14 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java

@@ -0,0 +1,14 @@
+package com.jme3.system.lwjgl;
+
+import com.jme3.system.JmeContext;
+
+/**
+ * @author Daniel Johansson
+ * @since 2015-08-11
+ */
+public class LwjglOffscreenBuffer extends LwjglWindow {
+
+    public LwjglOffscreenBuffer() {
+        super(JmeContext.Type.OffscreenSurface);
+    }
+}

+ 203 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java

@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.lwjgl;
+
+import com.jme3.math.FastMath;
+import com.jme3.system.Timer;
+import org.lwjgl.glfw.GLFW;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Timer</code> handles the system's time related functionality. This
+ * allows the calculation of the framerate. To keep the framerate calculation
+ * accurate, a call to update each frame is required. <code>Timer</code> is a
+ * singleton object and must be created via the <code>getTimer</code> method.
+ *
+ * @author Mark Powell
+ * @version $Id: LWJGLTimer.java,v 1.21 2007/09/22 16:46:35 irrisor Exp $
+ */
+public class LwjglSmoothingTimer extends LwjglTimer {
+    private static final Logger logger = Logger.getLogger(LwjglSmoothingTimer.class
+            .getName());
+
+    private long lastFrameDiff;
+
+    //frame rate parameters.
+    private long oldTime;
+
+    private float lastTPF, lastFPS;
+
+    public static int TIMER_SMOOTHNESS = 32;
+
+    private long[] tpf;
+
+    private int smoothIndex;
+
+    private final static long LWJGL_TIMER_RES = 1;
+    private final static float INV_LWJGL_TIMER_RES = ( 1f / LWJGL_TIMER_RES );
+    private static float invTimerRezSmooth;
+
+    public final static long LWJGL_TIME_TO_NANOS = (1000000000 / LWJGL_TIMER_RES);
+
+    private long startTime;
+
+    private boolean allSmooth = false;
+
+    /**
+     * Constructor builds a <code>Timer</code> object. All values will be
+     * initialized to it's default values.
+     */
+    public LwjglSmoothingTimer() {
+        reset();
+
+        //print timer resolution info
+        logger.log(Level.FINE, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES);
+    }
+
+    public void reset() {
+        lastFrameDiff = 0;
+        lastFPS = 0;
+        lastTPF = 0;
+
+        // init to -1 to indicate this is a new timer.
+        oldTime = -1;
+        //reset time
+        startTime = (long) (GLFW.glfwGetTime() * LWJGL_TIME_TO_NANOS);
+
+        tpf = new long[TIMER_SMOOTHNESS];
+        smoothIndex = TIMER_SMOOTHNESS - 1;
+        invTimerRezSmooth = ( 1f / (LWJGL_TIMER_RES * TIMER_SMOOTHNESS));
+
+        // set tpf... -1 values will not be used for calculating the average in update()
+        for ( int i = tpf.length; --i >= 0; ) {
+            tpf[i] = -1;
+        }
+    }
+
+    /**
+     * @see Timer#getResolution() 
+     */
+    public long getResolution() {
+        return LWJGL_TIMER_RES;
+    }
+
+    /**
+     * <code>getFrameRate</code> returns the current frame rate since the last
+     * call to <code>update</code>.
+     *
+     * @return the current frame rate.
+     */
+    public float getFrameRate() {
+        return lastFPS;
+    }
+
+    public float getTimePerFrame() {
+        return lastTPF;
+    }
+
+    /**
+     * <code>update</code> recalulates the frame rate based on the previous
+     * call to update. It is assumed that update is called each frame.
+     */
+    public void update() {
+        long newTime = (long) (GLFW.glfwGetTime() * LWJGL_TIME_TO_NANOS);
+        long oldTime = this.oldTime;
+        this.oldTime = newTime;
+        if ( oldTime == -1 ) {
+            // For the first frame use 60 fps. This value will not be counted in further averages.
+            // This is done so initialization code between creating the timer and the first
+            // frame is not counted as a single frame on it's own.
+            lastTPF = 1 / 60f;
+            lastFPS = 1f / lastTPF;
+            return;
+        }
+
+        long frameDiff = newTime - oldTime;
+        long lastFrameDiff = this.lastFrameDiff;
+        if ( lastFrameDiff > 0 && frameDiff > lastFrameDiff *100 ) {
+            frameDiff = lastFrameDiff *100;
+        }
+        this.lastFrameDiff = frameDiff;
+        tpf[smoothIndex] = frameDiff;
+        smoothIndex--;
+        if ( smoothIndex < 0 ) {
+            smoothIndex = tpf.length - 1;
+        }
+
+        lastTPF = 0.0f;
+        if (!allSmooth) {
+            int smoothCount = 0;
+            for ( int i = tpf.length; --i >= 0; ) {
+                if ( tpf[i] != -1 ) {
+                    lastTPF += tpf[i];
+                    smoothCount++;
+                }
+            }
+            if (smoothCount == tpf.length)
+                allSmooth  = true;
+            lastTPF *= ( INV_LWJGL_TIMER_RES / smoothCount );
+        } else {
+            for ( int i = tpf.length; --i >= 0; ) {
+                if ( tpf[i] != -1 ) {
+                    lastTPF += tpf[i];
+                }
+            }
+            lastTPF *= invTimerRezSmooth;
+        }
+        if ( lastTPF < FastMath.FLT_EPSILON ) {
+            lastTPF = FastMath.FLT_EPSILON;
+        }
+
+        lastFPS = 1f / lastTPF;
+    }
+
+    /**
+     * <code>toString</code> returns the string representation of this timer
+     * in the format: <br>
+     * <br>
+     * jme.utility.Timer@1db699b <br>
+     * Time: {LONG} <br>
+     * FPS: {LONG} <br>
+     *
+     * @return the string representation of this object.
+     */
+    @Override
+    public String toString() {
+        String string = super.toString();
+        string += "\nTime: " + oldTime;
+        string += "\nFPS: " + getFrameRate();
+        return string;
+    }
+}

+ 139 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java

@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.lwjgl;
+
+import com.jme3.system.Timer;
+import org.lwjgl.glfw.GLFW;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Timer</code> handles the system's time related functionality. This
+ * allows the calculation of the framerate. To keep the framerate calculation
+ * accurate, a call to update each frame is required. <code>Timer</code> is a
+ * singleton object and must be created via the <code>getTimer</code> method.
+ *
+ * @author Mark Powell
+ * @version $Id: LWJGLTimer.java,v 1.21 2007/09/22 16:46:35 irrisor Exp $
+ */
+public class LwjglTimer extends Timer {
+
+    private static final Logger logger = Logger.getLogger(LwjglTimer.class.getName());
+
+    //frame rate parameters.
+    private long oldTime;
+    private long startTime;
+
+    private float lastTPF, lastFPS;
+
+    private final static long LWJGL_TIMER_RES = 1;
+    private final static float INV_LWJGL_TIMER_RES = ( 1f / LWJGL_TIMER_RES );
+    public final static long LWJGL_TIME_TO_NANOS = (1000000000 / LWJGL_TIMER_RES);
+
+    /**
+     * Constructor builds a <code>Timer</code> object. All values will be
+     * initialized to it's default values.
+     */
+    public LwjglTimer() {
+        reset();
+        logger.log(Level.FINE, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES);
+    }
+
+    public void reset() {
+        startTime = (long) (GLFW.glfwGetTime() * LWJGL_TIME_TO_NANOS);
+        oldTime = getTime();
+    }
+
+    @Override
+    public float getTimeInSeconds() {
+        return getTime() * INV_LWJGL_TIMER_RES;
+    }
+
+    /**
+     * @see Timer#getTime() 
+     */
+    public long getTime() {
+        return ((long) (GLFW.glfwGetTime() * LWJGL_TIME_TO_NANOS) - startTime);
+    }
+
+    /**
+     * @see Timer#getResolution() 
+     */
+    public long getResolution() {
+        return LWJGL_TIMER_RES;
+    }
+
+    /**
+     * <code>getFrameRate</code> returns the current frame rate since the last
+     * call to <code>update</code>.
+     *
+     * @return the current frame rate.
+     */
+    public float getFrameRate() {
+        return lastFPS;
+    }
+
+    public float getTimePerFrame() {
+        return lastTPF;
+    }
+
+    /**
+     * <code>update</code> recalulates the frame rate based on the previous
+     * call to update. It is assumed that update is called each frame.
+     */
+    public void update() {
+        long curTime = getTime();
+        lastTPF = (curTime - oldTime) * (1.0f / LWJGL_TIMER_RES);
+        lastFPS = 1.0f / lastTPF;
+        oldTime = curTime;
+    }
+
+    /**
+     * <code>toString</code> returns the string representation of this timer
+     * in the format: <br>
+     * <br>
+     * jme.utility.Timer@1db699b <br>
+     * Time: {LONG} <br>
+     * FPS: {LONG} <br>
+     *
+     * @return the string representation of this object.
+     */
+    @Override
+    public String toString() {
+        String string = super.toString();
+        string += "\nTime: " + oldTime;
+        string += "\nFPS: " + getFrameRate();
+        return string;
+    }
+}

+ 489 - 0
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java

@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system.lwjgl;
+
+import com.jme3.input.JoyInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.TouchInput;
+import com.jme3.input.lwjgl.GlfwJoystickInput;
+import com.jme3.input.lwjgl.LwjglKeyInput;
+import com.jme3.input.lwjgl.LwjglMouseInput;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext;
+import com.jme3.system.JmeSystem;
+import org.lwjgl.PointerBuffer;
+import org.lwjgl.Sys;
+import org.lwjgl.glfw.*;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static org.lwjgl.glfw.GLFW.*;
+import static org.lwjgl.opengl.GL11.GL_FALSE;
+import static org.lwjgl.opengl.GL11.GL_TRUE;
+import static org.lwjgl.system.MemoryUtil.NULL;
+
+/**
+ * A wrapper class over the GLFW framework in LWJGL 3.
+ *
+ * @author Daniel Johansson (dannyjo)
+ * @since 3.1
+ */
+public abstract class LwjglWindow extends LwjglContext implements Runnable {
+
+    private static final Logger LOGGER = Logger.getLogger(LwjglWindow.class.getName());
+
+    protected AtomicBoolean needClose = new AtomicBoolean(false);
+    protected final AtomicBoolean needRestart = new AtomicBoolean(false);
+    protected boolean wasActive = false;
+    protected boolean autoFlush = true;
+    protected boolean allowSwapBuffers = false;
+    private long window = -1;
+    private final JmeContext.Type type;
+
+    private GLFWErrorCallback errorCallback;
+    private GLFWWindowSizeCallback windowSizeCallback;
+    private GLFWWindowFocusCallback windowFocusCallback;
+
+    public LwjglWindow(final JmeContext.Type type) {
+        if (!JmeContext.Type.Display.equals(type) && !JmeContext.Type.OffscreenSurface.equals(type) && !JmeContext.Type.Canvas.equals(type)) {
+            throw new IllegalArgumentException("Unsupported type '" + type.name() + "' provided");
+        }
+
+        this.type = type;
+    }
+
+    /**
+     * @return Type.Display or Type.Canvas
+     */
+    public JmeContext.Type getType() {
+        return type;
+    }
+
+    /**
+     * Set the title if its a windowed display
+     *
+     * @param title
+     */
+    public void setTitle(final String title) {
+        if (created.get() && window != -1) {
+            glfwSetWindowTitle(window, title);
+        }
+    }
+
+    /**
+     * Restart if its a windowed or full-screen display.
+     */
+    public void restart() {
+        if (created.get()) {
+            needRestart.set(true);
+        } else {
+            LOGGER.warning("Display is not created, cannot restart window.");
+        }
+    }
+
+    /**
+     * Apply the settings, changing resolution, etc.
+     *
+     * @param settings
+     */
+    protected void createContext(final AppSettings settings) {
+        glfwSetErrorCallback(errorCallback = new GLFWErrorCallback() {
+            @Override
+            public void invoke(int error, long description) {
+                final String message = Callbacks.errorCallbackDescriptionString(description);
+                listener.handleError(message, new Exception(message));
+            }
+        });
+
+        if (glfwInit() != GL_TRUE) {
+            throw new IllegalStateException("Unable to initialize GLFW");
+        }
+
+        glfwDefaultWindowHints();
+        glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
+
+        // TODO: Add support for monitor selection
+        long monitor = NULL;
+
+        if (settings.isFullscreen()) {
+            monitor = glfwGetPrimaryMonitor();
+        }
+
+        final ByteBuffer videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
+
+        if (settings.getWidth() <= 0 || settings.getHeight() <= 0) {
+            settings.setResolution(GLFWvidmode.width(videoMode), GLFWvidmode.height(videoMode));
+        }
+
+        window = glfwCreateWindow(settings.getWidth(), settings.getHeight(), settings.getTitle(), monitor, NULL);
+
+        if (window == NULL) {
+            throw new RuntimeException("Failed to create the GLFW window");
+        }
+
+        glfwWindowHint(GLFW_RESIZABLE, settings.isResizable() ? GL_TRUE : GL_FALSE);
+        glfwWindowHint(GLFW_DEPTH_BITS, settings.getDepthBits());
+        glfwWindowHint(GLFW_STENCIL_BITS, settings.getStencilBits());
+        glfwWindowHint(GLFW_SAMPLES, settings.getSamples());
+        glfwWindowHint(GLFW_STEREO, settings.useStereo3D() ? GL_TRUE : GL_FALSE);
+
+        int frameRateCap = settings.getFrameRate();
+
+        if (!autoFlush) {
+            frameRateCap = 20;
+        }
+
+        if (frameRateCap > 0) {
+            glfwWindowHint(GLFW_REFRESH_RATE, frameRateCap);
+        }
+
+        // Not sure how else to support bits per pixel
+        if (settings.getBitsPerPixel() == 24) {
+            glfwWindowHint(GLFW_RED_BITS, 8);
+            glfwWindowHint(GLFW_GREEN_BITS, 8);
+            glfwWindowHint(GLFW_BLUE_BITS, 8);
+        } else if (settings.getBitsPerPixel() == 16) {
+            glfwWindowHint(GLFW_RED_BITS, 5);
+            glfwWindowHint(GLFW_GREEN_BITS, 6);
+            glfwWindowHint(GLFW_BLUE_BITS, 5);
+        }
+
+        glfwWindowHint(GLFW_ALPHA_BITS, settings.getAlphaBits());
+
+        glfwSetWindowFocusCallback(window, windowFocusCallback = new GLFWWindowFocusCallback() {
+            @Override
+            public void invoke(final long window, final int focused) {
+                final boolean focus = (focused == 1);
+
+                if (wasActive != focus) {
+                    if (!wasActive) {
+                        listener.gainFocus();
+                        timer.reset();
+                    } else {
+                        listener.loseFocus();
+                    }
+
+                    wasActive = !wasActive;
+                }
+
+            }
+        });
+
+        // Center the window
+        if (!settings.isFullscreen() && Type.Display.equals(type)) {
+            glfwSetWindowPos(window, (GLFWvidmode.width(videoMode) - settings.getWidth()) / 2, (GLFWvidmode.height(videoMode) - settings.getHeight()) / 2);
+        }
+
+        // Make the OpenGL context current
+        glfwMakeContextCurrent(window);
+
+        // Enable vsync
+        if (settings.isVSync()) {
+            glfwSwapInterval(1);
+        } else {
+            glfwSwapInterval(0);
+        }
+
+
+        // Make the window visible
+        if (Type.Display.equals(type)) {
+            glfwShowWindow(window);
+        }
+
+        // Add a resize callback which delegates to the listener
+        glfwSetWindowSizeCallback(window, windowSizeCallback = new GLFWWindowSizeCallback() {
+            @Override
+            public void invoke(final long window, final int width, final int height) {
+                settings.setResolution(width, height);
+                listener.reshape(width, height);
+            }
+        });
+
+        allowSwapBuffers = settings.isSwapBuffers();
+
+        // TODO: When GLFW 3.2 is released and included in LWJGL 3.x then we should hopefully be able to set the window icon.
+    }
+
+    /**
+     * Destroy the context.
+     */
+    protected void destroyContext() {
+        try {
+            if (renderer != null) {
+                renderer.cleanup();
+            }
+
+            errorCallback.release();
+            windowSizeCallback.release();
+            windowFocusCallback.release();
+
+            if (window != 0) {
+                glfwDestroyWindow(window);
+            }
+            //glfwTerminate();
+        } catch (Exception ex) {
+            listener.handleError("Failed to destroy context", ex);
+        }
+    }
+
+    public void create(boolean waitFor) {
+        if (created.get()) {
+            LOGGER.warning("create() called when display is already created!");
+            return;
+        }
+
+        new Thread(this, THREAD_NAME).start();
+        if (waitFor)
+            waitFor(true);
+    }
+
+    /**
+     * Does LWJGL display initialization in the OpenGL thread
+     */
+    protected boolean initInThread() {
+        try {
+            if (!JmeSystem.isLowPermissions()) {
+                // Enable uncaught exception handler only for current thread
+                Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+                    public void uncaughtException(Thread thread, Throwable thrown) {
+                        listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown);
+                        if (needClose.get()) {
+                            // listener.handleError() has requested the
+                            // context to close. Satisfy request.
+                            deinitInThread();
+                        }
+                    }
+                });
+            }
+
+            timer = new LwjglTimer();
+
+            // For canvas, this will create a pbuffer,
+            // allowing us to query information.
+            // When the canvas context becomes available, it will
+            // be replaced seamlessly.
+            createContext(settings);
+            printContextInitInfo();
+
+            created.set(true);
+            super.internalCreate();
+        } catch (Exception ex) {
+            try {
+                if (window != -1) {
+                    //glfwSetWindowShouldClose(window, GL_TRUE);
+                    glfwDestroyWindow(window);
+                }
+            } catch (Exception ex2) {
+                LOGGER.log(Level.WARNING, null, ex2);
+            }
+
+            listener.handleError("Failed to create display", ex);
+            return false; // if we failed to create display, do not continue
+        }
+
+        listener.initialize();
+        return true;
+    }
+
+    /**
+     * execute one iteration of the render loop in the OpenGL thread
+     */
+    protected void runLoop() {
+        // If a restart is required, lets recreate the context.
+        if (needRestart.getAndSet(false)) {
+            try {
+                createContext(settings);
+            } catch (Exception ex) {
+                LOGGER.log(Level.SEVERE, "Failed to set display settings!", ex);
+            }
+
+            LOGGER.fine("Display restarted.");
+        }
+
+        if (!created.get()) {
+            throw new IllegalStateException();
+        }
+
+        listener.update();
+
+        // All this does is call swap buffers
+        // If the canvas is not active, there's no need to waste time
+        // doing that ..
+        if (renderable.get()) {
+            // calls swap buffers, etc.
+            try {
+                if (allowSwapBuffers && autoFlush) {
+                    glfwSwapBuffers(window);
+                }
+            } catch (Throwable ex) {
+                listener.handleError("Error while swapping buffers", ex);
+            }
+        }
+
+        if (glfwGetWindowAttrib(window, GLFW_FOCUSED) == GL_TRUE) {
+            glfwPollEvents();
+        }
+
+        // Subclasses just call GLObjectManager clean up objects here
+        // it is safe .. for now.
+        if (renderer != null) {
+            renderer.postFrame();
+        }
+    }
+
+    /**
+     * De-initialize in the OpenGL thread.
+     */
+    protected void deinitInThread() {
+        destroyContext();
+
+        listener.destroy();
+        LOGGER.fine("Display destroyed.");
+        super.internalDestroy();
+    }
+
+    public void run() {
+        if (listener == null) {
+            throw new IllegalStateException("SystemListener is not set on context!"
+                    + "Must set with JmeContext.setSystemListner().");
+        }
+
+        registerNatives();
+        loadNatives();
+        LOGGER.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion());
+
+        if (!initInThread()) {
+            LOGGER.log(Level.SEVERE, "Display initialization failed. Cannot continue.");
+            return;
+        }
+
+        while (true) {
+            if (glfwWindowShouldClose(window) == GL_TRUE) {
+                listener.requestClose(false);
+            }
+
+            runLoop();
+
+            if (needClose.get()) {
+                break;
+            }
+        }
+
+        deinitInThread();
+    }
+
+    public JoyInput getJoyInput() {
+        if (joyInput == null) {
+            joyInput = new GlfwJoystickInput();
+        }
+        return joyInput;
+    }
+
+    public MouseInput getMouseInput() {
+        if (mouseInput == null) {
+            mouseInput = new LwjglMouseInput(this);
+        }
+        return mouseInput;
+    }
+
+    public KeyInput getKeyInput() {
+        if (keyInput == null) {
+            keyInput = new LwjglKeyInput(this);
+        }
+
+        return keyInput;
+    }
+
+    public TouchInput getTouchInput() {
+        return null;
+    }
+
+    public void setAutoFlushFrames(boolean enabled) {
+        this.autoFlush = enabled;
+    }
+
+    public void destroy(boolean waitFor) {
+        needClose.set(true);
+
+        if (waitFor) {
+            waitFor(false);
+        }
+    }
+
+    public long getWindowHandle() {
+        return window;
+    }
+
+    private ByteBuffer[] imagesToByteBuffers(Object[] images) {
+        ByteBuffer[] out = new ByteBuffer[images.length];
+        for (int i = 0; i < images.length; i++) {
+            BufferedImage image = (BufferedImage) images[i];
+            out[i] = imageToByteBuffer(image);
+        }
+        return out;
+    }
+
+    private ByteBuffer imageToByteBuffer(BufferedImage image) {
+        if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) {
+            BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE);
+            Graphics2D g = convertedImage.createGraphics();
+            double width = image.getWidth() * (double) 1;
+            double height = image.getHeight() * (double) 1;
+            g.drawImage(image, (int) ((convertedImage.getWidth() - width) / 2),
+                    (int) ((convertedImage.getHeight() - height) / 2),
+                    (int) (width), (int) (height), null);
+            g.dispose();
+            image = convertedImage;
+        }
+
+        byte[] imageBuffer = new byte[image.getWidth() * image.getHeight() * 4];
+        int counter = 0;
+        for (int i = 0; i < image.getHeight(); i++) {
+            for (int j = 0; j < image.getWidth(); j++) {
+                int colorSpace = image.getRGB(j, i);
+                imageBuffer[counter + 0] = (byte) ((colorSpace << 8) >> 24);
+                imageBuffer[counter + 1] = (byte) ((colorSpace << 16) >> 24);
+                imageBuffer[counter + 2] = (byte) ((colorSpace << 24) >> 24);
+                imageBuffer[counter + 3] = (byte) (colorSpace >> 24);
+                counter += 4;
+            }
+        }
+        return ByteBuffer.wrap(imageBuffer);
+    }
+}

+ 1 - 0
settings.gradle

@@ -15,6 +15,7 @@ include 'jme3-desktop'
 include 'jme3-blender'
 include 'jme3-jogl'
 include 'jme3-lwjgl'
+include 'jme3-lwjgl3'
 
 // Other external dependencies
 include 'jme3-jbullet'