Browse Source

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

Daniel Johansson 10 năm trước cách đây
mục cha
commit
89f10eca58
25 tập tin đã thay đổi với 3158 bổ sung28 xóa
  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'