From 89f10eca58afd4c9645b60f8781e5578fa92b2c8 Mon Sep 17 00:00:00 2001 From: Daniel Johansson Date: Wed, 26 Aug 2015 22:24:24 +0100 Subject: [PATCH] Added jme3-lwjgl3 module which ultimately adds support for LWJGL 3.x and GLFW. --- common.gradle | 3 + .../resources/joystick-mapping.properties | 6 + .../com/jme3/system/NativeLibraryLoader.java | 22 +- .../system/lwjgl/LwjglAbstractDisplay.java | 2 +- .../com/jme3/system/lwjgl/LwjglContext.java | 28 +- jme3-lwjgl3/build.gradle | 12 + .../java/com/jme3/audio/lwjgl/LwjglAL.java | 140 +++++ .../java/com/jme3/audio/lwjgl/LwjglALC.java | 58 +++ .../java/com/jme3/audio/lwjgl/LwjglEFX.java | 66 +++ .../jme3/input/lwjgl/GlfwJoystickInput.java | 213 ++++++++ .../com/jme3/input/lwjgl/LwjglKeyInput.java | 116 +++++ .../com/jme3/input/lwjgl/LwjglMouseInput.java | 189 +++++++ .../java/com/jme3/renderer/lwjgl/LwjglGL.java | 458 ++++++++++++++++ .../com/jme3/renderer/lwjgl/LwjglGLExt.java | 83 +++ .../jme3/renderer/lwjgl/LwjglGLFboEXT.java | 99 ++++ .../jme3/renderer/lwjgl/LwjglGLFboGL3.java | 97 ++++ .../com/jme3/system/lwjgl/LwjglCanvas.java | 369 +++++++++++++ .../com/jme3/system/lwjgl/LwjglContext.java | 289 +++++++++++ .../com/jme3/system/lwjgl/LwjglDisplay.java | 12 + .../lwjgl/LwjglGLDebugOutputHandler.java | 78 +++ .../system/lwjgl/LwjglOffscreenBuffer.java | 14 + .../system/lwjgl/LwjglSmoothingTimer.java | 203 ++++++++ .../com/jme3/system/lwjgl/LwjglTimer.java | 139 +++++ .../com/jme3/system/lwjgl/LwjglWindow.java | 489 ++++++++++++++++++ settings.gradle | 1 + 25 files changed, 3158 insertions(+), 28 deletions(-) create mode 100644 jme3-lwjgl3/build.gradle create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglAL.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglEFX.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwJoystickInput.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java create mode 100644 jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java diff --git a/common.gradle b/common.gradle index 6af4c664f..da38174d3 100644 --- a/common.gradle +++ b/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 { diff --git a/jme3-core/src/main/resources/joystick-mapping.properties b/jme3-core/src/main/resources/joystick-mapping.properties index d7ce2f50a..966832036 100644 --- a/jme3-core/src/main/resources/joystick-mapping.properties +++ b/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 diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java index 2917350f6..6e83e0e6b 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java +++ b/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. *
- * 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) }. *
@@ -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"); diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java index 7cdca0a57..415084788 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java +++ b/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()) { diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index c88f7b734..70885c39b 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/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; diff --git a/jme3-lwjgl3/build.gradle b/jme3-lwjgl3/build.gradle new file mode 100644 index 000000000..c9b43e0de --- /dev/null +++ b/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' +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglAL.java b/jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglAL.java new file mode 100644 index 000000000..313bdd5ee --- /dev/null +++ b/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); + } + +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java b/jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java new file mode 100644 index 000000000..e0aed80ee --- /dev/null +++ b/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() { + } + +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglEFX.java b/jme3-lwjgl3/src/main/java/com/jme3/audio/lwjgl/LwjglEFX.java new file mode 100644 index 000000000..2ddd09ed8 --- /dev/null +++ b/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); + } + +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwJoystickInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwJoystickInput.java new file mode 100644 index 000000000..f17c08534 --- /dev/null +++ b/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 joysticks = new HashMap(); + + 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 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(); + } + } +} + + + diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java new file mode 100644 index 000000000..a39010c95 --- /dev/null +++ b/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 keyInputEvents = new LinkedList(); + + 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); + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java new file mode 100644 index 000000000..45262ffbc --- /dev/null +++ b/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 mouseMotionEvents = new LinkedList(); + private Queue mouseButtonEvents = new LinkedList(); + + 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); + } + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java new file mode 100644 index 000000000..80e7f3513 --- /dev/null +++ b/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); + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java new file mode 100644 index 000000000..00b3f688c --- /dev/null +++ b/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); + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java new file mode 100644 index 000000000..969d7ae1d --- /dev/null +++ b/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); + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java new file mode 100644 index 000000000..aa15aeb09 --- /dev/null +++ b/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); + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java new file mode 100644 index 000000000..1f1ef2ab5 --- /dev/null +++ b/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; + } + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java new file mode 100644 index 000000000..3f6491e5f --- /dev/null +++ b/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; + } + +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java new file mode 100644 index 000000000..4a13c30af --- /dev/null +++ b/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); + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java new file mode 100644 index 000000000..aa9c46511 --- /dev/null +++ b/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 constMap = new HashMap(); + 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)); + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java new file mode 100644 index 000000000..e374e1841 --- /dev/null +++ b/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); + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java new file mode 100644 index 000000000..a7960a261 --- /dev/null +++ b/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; + +/** + * Timer 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. Timer is a + * singleton object and must be created via the getTimer 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 Timer 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; + } + + /** + * getFrameRate returns the current frame rate since the last + * call to update. + * + * @return the current frame rate. + */ + public float getFrameRate() { + return lastFPS; + } + + public float getTimePerFrame() { + return lastTPF; + } + + /** + * update 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; + } + + /** + * toString returns the string representation of this timer + * in the format:
+ *
+ * jme.utility.Timer@1db699b
+ * Time: {LONG}
+ * FPS: {LONG}
+ * + * @return the string representation of this object. + */ + @Override + public String toString() { + String string = super.toString(); + string += "\nTime: " + oldTime; + string += "\nFPS: " + getFrameRate(); + return string; + } +} \ No newline at end of file diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java new file mode 100644 index 000000000..c4a0e5025 --- /dev/null +++ b/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; + +/** + * Timer 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. Timer is a + * singleton object and must be created via the getTimer 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 Timer 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; + } + + /** + * getFrameRate returns the current frame rate since the last + * call to update. + * + * @return the current frame rate. + */ + public float getFrameRate() { + return lastFPS; + } + + public float getTimePerFrame() { + return lastTPF; + } + + /** + * update 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; + } + + /** + * toString returns the string representation of this timer + * in the format:
+ *
+ * jme.utility.Timer@1db699b
+ * Time: {LONG}
+ * FPS: {LONG}
+ * + * @return the string representation of this object. + */ + @Override + public String toString() { + String string = super.toString(); + string += "\nTime: " + oldTime; + string += "\nFPS: " + getFrameRate(); + return string; + } +} \ No newline at end of file diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java new file mode 100644 index 000000000..c089bf28d --- /dev/null +++ b/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); + } +} diff --git a/settings.gradle b/settings.gradle index 89b8fc075..8b9000340 100644 --- a/settings.gradle +++ b/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'