diff --git a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java index 43895f3c5f..b52f4ac08e 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java +++ b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java @@ -59,7 +59,6 @@ import com.jme3.system.*; import com.jme3.util.BufferAllocatorFactory; import com.jme3.util.PrimitiveAllocator; - import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -79,7 +78,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex protected SystemListener listener; protected boolean autoFlush = true; protected AndroidInputHandler androidInput; - protected long minFrameDuration = 0; // No FPS cap + protected long minFrameDuration = 0; // No FPS cap protected long lastUpdateTime = 0; static { @@ -90,8 +89,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex } } - public OGLESContext() { - } + public OGLESContext() {} @Override public Type getType() { @@ -115,9 +113,11 @@ public GLSurfaceView createView(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // below 4.0, check OpenGL ES 2.0 support. if (info.reqGlEsVersion < 0x20000) { - throw new UnsupportedOperationException("OpenGL ES 2.0 or better is not supported on this device"); + throw new UnsupportedOperationException( + "OpenGL ES 2.0 or better is not supported on this device" + ); } - } else if (Build.VERSION.SDK_INT < 9){ + } else if (Build.VERSION.SDK_INT < 9) { throw new UnsupportedOperationException("jME3 requires Android 2.3 or later"); } @@ -127,7 +127,7 @@ public GLSurfaceView createView(Context context) { if (androidInput == null) { if (Build.VERSION.SDK_INT >= 14) { androidInput = new AndroidInputHandler14(); - } else if (Build.VERSION.SDK_INT >= 9){ + } else if (Build.VERSION.SDK_INT >= 9) { androidInput = new AndroidInputHandler(); } } @@ -137,7 +137,7 @@ public GLSurfaceView createView(Context context) { // setEGLContextClientVersion must be set before calling setRenderer // this means it cannot be set in AndroidConfigChooser (too late) // use proper openGL ES version - view.setEGLContextClientVersion(info.reqGlEsVersion>>16); + view.setEGLContextClientVersion(info.reqGlEsVersion >> 16); view.setFocusableInTouchMode(true); view.setFocusable(true); @@ -200,22 +200,35 @@ protected void initInThread() { logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); // Setup unhandled Exception Handler - Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable thrown) { - listener.handleError("Exception thrown in " + thread.toString(), thrown); - } - }); + Thread + .currentThread() + .setUncaughtExceptionHandler( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Exception thrown in " + thread.toString(), thrown); + } + } + ); timer = new NanoTimer(); GL gl = new AndroidGL(); if (settings.getBoolean("GraphicsDebug")) { - gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GLES_30.class, GLFbo.class, GLExt.class); + gl = + (GL) GLDebug.createProxy( + gl, + gl, + GL.class, + GL2.class, + GLES_30.class, + GLFbo.class, + GLExt.class + ); } if (settings.getBoolean("GraphicsTrace")) { - gl = (GL)GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class); + gl = (GL) GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class); } - renderer = new GLRenderer(gl, (GLExt)gl, (GLFbo)gl); + renderer = new GLRenderer(gl, (GLExt) gl, (GLFbo) gl); renderer.initialize(); JmeSystem.setSoftTextDialogInput(this); @@ -254,7 +267,7 @@ public void setSettings(AppSettings settings) { } if (settings.getFrameRate() > 0) { - minFrameDuration = (long)(1000d / settings.getFrameRate()); // ms + minFrameDuration = (long) (1000d / settings.getFrameRate()); // ms logger.log(Level.FINE, "Setting min tpf: {0}ms", minFrameDuration); } else { minFrameDuration = 0; @@ -312,8 +325,7 @@ public Timer getTimer() { } @Override - public void setTitle(String title) { - } + public void setTitle(String title) {} @Override public boolean isCreated() { @@ -329,7 +341,11 @@ public void setAutoFlushFrames(boolean enabled) { @Override public void onSurfaceChanged(GL10 gl, int width, int height) { if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "GL Surface changed, width: {0} height: {1}", new Object[]{width, height}); + logger.log( + Level.FINE, + "GL Surface changed, width: {0} height: {1}", + new Object[] { width, height } + ); } // update the application settings with the new resolution settings.setResolution(width, height); @@ -372,16 +388,14 @@ public void onDrawFrame(GL10 gl) { // Enforce a FPS cap if (updateDelta < minFrameDuration) { -// logger.log(Level.INFO, "lastUpdateTime: {0}, updateDelta: {1}, minTimePerFrame: {2}", -// new Object[]{lastUpdateTime, updateDelta, minTimePerFrame}); + // logger.log(Level.INFO, "lastUpdateTime: {0}, updateDelta: {1}, minTimePerFrame: {2}", + // new Object[]{lastUpdateTime, updateDelta, minTimePerFrame}); try { Thread.sleep(minFrameDuration - updateDelta); - } catch (InterruptedException e) { - } + } catch (InterruptedException e) {} } lastUpdateTime = System.currentTimeMillis(); - } } @@ -402,8 +416,7 @@ public void create() { } @Override - public void restart() { - } + public void restart() {} @Override public void destroy(boolean waitFor) { @@ -421,76 +434,99 @@ protected void waitFor(boolean createdVal) { while (renderable.get() != createdVal) { try { Thread.sleep(10); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } } @Override - public void requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener) { + public void requestDialog( + final int id, + final String title, + final String initialValue, + final SoftTextDialogInputListener listener + ) { if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "requestDialog: title: {0}, initialValue: {1}", - new Object[]{title, initialValue}); + logger.log( + Level.FINE, + "requestDialog: title: {0}, initialValue: {1}", + new Object[] { title, initialValue } + ); } final View view = JmeAndroidSystem.getView(); - view.getHandler().post(new Runnable() { - @Override - public void run() { - - final FrameLayout layoutTextDialogInput = new FrameLayout(view.getContext()); - final EditText editTextDialogInput = new EditText(view.getContext()); - editTextDialogInput.setWidth(LayoutParams.FILL_PARENT); - editTextDialogInput.setHeight(LayoutParams.FILL_PARENT); - editTextDialogInput.setPadding(20, 20, 20, 20); - editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL); - //editTextDialogInput.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); - - editTextDialogInput.setText(initialValue); - - switch (id) { - case SoftTextDialogInput.TEXT_ENTRY_DIALOG: - - editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT); - break; - - case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG: - - editTextDialogInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED); - break; - - case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG: - - editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE); - break; - - default: - break; + view + .getHandler() + .post( + new Runnable() { + @Override + public void run() { + final FrameLayout layoutTextDialogInput = new FrameLayout(view.getContext()); + final EditText editTextDialogInput = new EditText(view.getContext()); + editTextDialogInput.setWidth(LayoutParams.FILL_PARENT); + editTextDialogInput.setHeight(LayoutParams.FILL_PARENT); + editTextDialogInput.setPadding(20, 20, 20, 20); + editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL); + //editTextDialogInput.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + + editTextDialogInput.setText(initialValue); + + switch (id) { + case SoftTextDialogInput.TEXT_ENTRY_DIALOG: + editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT); + break; + case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG: + editTextDialogInput.setInputType( + InputType.TYPE_CLASS_NUMBER | + InputType.TYPE_NUMBER_FLAG_DECIMAL | + InputType.TYPE_NUMBER_FLAG_SIGNED + ); + break; + case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG: + editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE); + break; + default: + break; + } + + layoutTextDialogInput.addView(editTextDialogInput); + + AlertDialog dialogTextInput = new AlertDialog.Builder(view.getContext()) + .setTitle(title) + .setView(layoutTextDialogInput) + .setPositiveButton( + "OK", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + /* User clicked OK, send COMPLETE action + * and text */ + listener.onSoftText( + SoftTextDialogInputListener.COMPLETE, + editTextDialogInput.getText().toString() + ); + } + } + ) + .setNegativeButton( + "Cancel", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + /* User clicked CANCEL, send CANCEL action + * and text */ + listener.onSoftText( + SoftTextDialogInputListener.CANCEL, + editTextDialogInput.getText().toString() + ); + } + } + ) + .create(); + + dialogTextInput.show(); + } } - - layoutTextDialogInput.addView(editTextDialogInput); - - AlertDialog dialogTextInput = new AlertDialog.Builder(view.getContext()).setTitle(title).setView(layoutTextDialogInput).setPositiveButton("OK", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) { - /* User clicked OK, send COMPLETE action - * and text */ - listener.onSoftText(SoftTextDialogInputListener.COMPLETE, editTextDialogInput.getText().toString()); - } - }).setNegativeButton("Cancel", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) { - /* User clicked CANCEL, send CANCEL action - * and text */ - listener.onSoftText(SoftTextDialogInputListener.CANCEL, editTextDialogInput.getText().toString()); - } - }).create(); - - dialogTextInput.show(); - } - }); + ); } @Override @@ -542,11 +578,11 @@ public int getWindowXPosition() { public int getWindowYPosition() { throw new UnsupportedOperationException("not implemented yet"); } - + /** * Retrieves the dimensions of the input surface. Note: do not modify the * returned object. - * + * * @return the dimensions (in pixels, left and top are 0) */ private Rect getSurfaceFrame() { @@ -555,4 +591,16 @@ private Rect getSurfaceFrame() { Rect result = holder.getSurfaceFrame(); return result; } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } } diff --git a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java index ef2a550ee9..1e0a75daf7 100644 --- a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java @@ -50,13 +50,14 @@ import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; import com.jme3.system.AppSettings; +import com.jme3.system.Displays; import com.jme3.system.JmeContext; import com.jme3.system.JmeContext.Type; -import com.jme3.util.res.Resources; import com.jme3.system.JmeSystem; import com.jme3.system.NanoTimer; import com.jme3.system.SystemListener; import com.jme3.system.Timer; +import com.jme3.util.res.Resources; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.Callable; @@ -199,8 +200,7 @@ public void setPauseOnLostFocus(boolean pauseOnLostFocus) { @Deprecated public void setAssetManager(AssetManager assetManager) { if (this.assetManager != null) { - throw new IllegalStateException("Can only set asset manager" - + " before initialization."); + throw new IllegalStateException("Can only set asset manager" + " before initialization."); } this.assetManager = assetManager; @@ -220,13 +220,16 @@ private void initAssetManager() { if (assetCfgUrl == null) { assetCfgUrl = Resources.getResource(assetCfg); if (assetCfgUrl == null) { - logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", - assetCfg); + logger.log( + Level.SEVERE, + "Unable to access AssetConfigURL in asset config:{0}", + assetCfg + ); return; } } } - } + } if (assetCfgUrl == null) { assetCfgUrl = JmeSystem.getPlatformAssetConfigURL(); } @@ -595,7 +598,6 @@ public void reshape(int w, int h) { } } - @Override public void rescale(float x, float y) { if (renderManager != null) { @@ -668,9 +670,8 @@ public void initialize() { initAudio(); // update timer so that the next delta is not too large -// timer.update(); + // timer.update(); timer.reset(); - // user code here } @@ -684,8 +685,12 @@ public void handleError(String errMsg, Throwable t) { // Display error message on screen if not in headless mode if (context.getType() != JmeContext.Type.Headless) { if (t != null) { - JmeSystem.handleErrorMessage(errMsg + "\n" + t.getClass().getSimpleName() - + (t.getMessage() != null ? ": " + t.getMessage() : "")); + JmeSystem.handleErrorMessage( + errMsg + + "\n" + + t.getClass().getSimpleName() + + (t.getMessage() != null ? ": " + t.getMessage() : "") + ); } else { JmeSystem.handleErrorMessage(errMsg); } @@ -811,7 +816,6 @@ public void update() { } audioRenderer.update(timer.getTimePerFrame()); } - // user code here } @@ -866,6 +870,7 @@ public ViewPort getViewPort() { } private class RunnableWrapper implements Callable { + private final Runnable runnable; public RunnableWrapper(Runnable runnable) { @@ -878,4 +883,26 @@ public Object call() { return null; } } + + /** + * This call will return a list of Monitors that glfwGetMonitors() + * returns and information about the monitor, like width, height, + * and refresh rate. + * + * @return returns a list of monitors and their information. + */ + public Displays getDisplays() { + return context.getDisplays(); + } + + /** + * Use this to get the positional number of the primary + * monitor from the glfwGetMonitors() function call. + * + * @return the position of the value in the arraylist of + * the primary monitor. + */ + public int getPrimaryDisplay() { + return context.getPrimaryDisplay(); + } } diff --git a/jme3-core/src/main/java/com/jme3/system/AppSettings.java b/jme3-core/src/main/java/com/jme3/system/AppSettings.java index c4815605c1..d5653a5d71 100644 --- a/jme3-core/src/main/java/com/jme3/system/AppSettings.java +++ b/jme3-core/src/main/java/com/jme3/system/AppSettings.java @@ -86,7 +86,6 @@ public final class AppSettings extends HashMap { @Deprecated public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3"; - /** * Use LWJGL as the display system and force using the core OpenGL3.0 renderer. *

@@ -266,6 +265,7 @@ public final class AppSettings extends HashMap { public static final String JOAL = "JOAL"; static { + defaults.put("Display", 0); defaults.put("CenterWindow", true); defaults.put("Width", 640); defaults.put("Height", 480); @@ -436,7 +436,9 @@ public void load(String preferencesKey) throws BackingStoreException { put(key.substring(2), prefs.getBoolean(key, false)); break; default: - throw new UnsupportedOperationException("Undefined setting type: " + key.charAt(0)); + throw new UnsupportedOperationException( + "Undefined setting type: " + key.charAt(0) + ); } } else { // Use old method for compatibility with older preferences @@ -718,7 +720,7 @@ public void setRenderer(String renderer) { * @param clazz The custom context class. * (Default: not set) */ - public void setCustomRenderer(Class clazz){ + public void setCustomRenderer(Class clazz) { put("Renderer", "CUSTOM" + clazz.getName()); } @@ -766,7 +768,7 @@ public void setResolution(int width, int height) { /** * Set the size of the window - * + * * @param width The width in pixels (default = width of the default framebuffer) * @param height The height in pixels (default = height of the default framebuffer) */ @@ -802,8 +804,6 @@ public void setMinResolution(int width, int height) { setMinHeight(height); } - - /** * Set the frequency, also known as refresh rate, for the * rendering display. @@ -825,7 +825,7 @@ public void setFrequency(int value) { * * @param value The depth bits */ - public void setDepthBits(int value){ + public void setDepthBits(int value) { putInteger("DepthBits", value); } @@ -844,7 +844,7 @@ public void setDepthBits(int value){ * * @param value The alpha bits */ - public void setAlphaBits(int value){ + public void setAlphaBits(int value) { putInteger("AlphaBits", value); } @@ -859,7 +859,7 @@ public void setAlphaBits(int value){ * * @param value Number of stencil bits */ - public void setStencilBits(int value){ + public void setStencilBits(int value) { putInteger("StencilBits", value); } @@ -921,7 +921,7 @@ public void setVSync(boolean value) { * * @param value true to enable 3-D stereo, false to disable (default=false) */ - public void setStereo3D(boolean value){ + public void setStereo3D(boolean value) { putBoolean("Stereo3D", value); } @@ -1180,7 +1180,7 @@ public String getAudioRenderer() { * @return true if 3-D stereo is enabled, otherwise false * @see #setStereo3D(boolean) */ - public boolean useStereo3D(){ + public boolean useStereo3D() { return getBoolean("Stereo3D"); } @@ -1301,7 +1301,7 @@ public String getOpenCLPlatformChooser() { * Without this, many openGL calls might fail without notice, so turning it on is recommended for development. * Graphics Debug mode will also label native objects and group calls on supported renderers. Compatible * graphics debuggers will be able to use this data to show a better outlook of your application - * + * * @return whether the context will be run in Graphics Debug Mode or not * @see #setGraphicsDebug(boolean) */ @@ -1479,4 +1479,32 @@ public int getWindowYPosition() { public void setWindowYPosition(int pos) { putInteger("WindowYPosition", pos); } + + /** + * Gets the display number used when creating a window. + * + *

+ * This setting is used only with LWJGL3, it defines which display to use when creating a OpenGL + * window. + * + * @return the desired display used when creating a OpenGL window + */ + public int getDisplay() { + return getInteger("Display"); + } + + /** + * Sets the display number used when creating a window. The position number is the number in the + * list of monitors GlfwGetMonitors returns. + * + *

+ * This setting is used only with LWJGL3, it defines which display to use when creating a OpenGL + * window. its default value is 0. + * + * @param mon the desired display used when creating a OpenGL window + * + */ + public void setDisplay(int mon) { + putInteger("Display", mon); + } } diff --git a/jme3-core/src/main/java/com/jme3/system/DisplayInfo.java b/jme3-core/src/main/java/com/jme3/system/DisplayInfo.java new file mode 100644 index 0000000000..08c3bab6c6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/DisplayInfo.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2009-2023 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; + +/** + * This class holds information about the display that was returned by glfwGetMonitors() calls in + * the context class + * + * @author Kevin Bales + */ +public class DisplayInfo { + + /** + * displayID - display id that was return from Lwjgl3. + */ + public long displayID = 0; + + /** + * width - width that was return from Lwjgl3. + */ + public int width = 1080; + + /** + * height - height that was return from Lwjgl3. + */ + public int height = 1920; + + /** + * rate - refresh rate that was return from Lwjgl3. + */ + public int rate = 60; + + /** + * primary - indicates if the display is the primary monitor. + */ + public boolean primary = false; + + /** + * name - display name that was return from Lwjgl3. + */ + public String name = "Generic Monitor"; +} diff --git a/jme3-core/src/main/java/com/jme3/system/Displays.java b/jme3-core/src/main/java/com/jme3/system/Displays.java new file mode 100644 index 0000000000..01592866b4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/Displays.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2023 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; + +import java.util.ArrayList; + +/** + * This class holds all information about all displays that where return from the glfwGetMonitors() + * call. It stores them into an ArrayList + * + * @author Kevin Bales + */ +public class Displays { + + private ArrayList displays = new ArrayList(); + + public int addNewMonitor(long displaysID) { + DisplayInfo info = new DisplayInfo(); + info.displayID = displaysID; + displays.add(info); + return displays.size() - 1; + } + + /** + * This function returns the size of the display ArrayList + * + * @return the + */ + public int size() { + return displays.size(); + } + + /** + * Call to get display information on a certain display. + * + * @param pos the position in the ArrayList of the display information that you want to get. + * @return returns the DisplayInfo data for the display called for. + */ + public DisplayInfo get(int pos) { + if (pos < displays.size()) return displays.get(pos); + + return null; + } + + /** + * Set information about this display stored in displayPos display in the array list. + * + * @param displayPos ArrayList position of display to update + * @param name name of the display + * @param width the current width the display is displaying + * @param height the current height the display is displaying + * @param rate the current refresh rate the display is set to + */ + public void setInfo(int displayPos, String name, int width, int height, int rate) { + if (displayPos < displays.size()) { + DisplayInfo info = displays.get(displayPos); + if (info != null) { + info.width = width; + info.height = height; + info.rate = rate; + info.name = name; + } + } + } + + /** + * This function will mark a certain display as the primary display. + * + * @param displayPos the position in the ArrayList of which display is the primary display + */ + public void setPrimaryDisplay(int displayPos) { + if (displayPos < displays.size()) { + DisplayInfo info = displays.get(displayPos); + if (info != null) info.primary = true; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/system/JmeContext.java b/jme3-core/src/main/java/com/jme3/system/JmeContext.java index 8d83e0960e..c2ffe912ef 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeContext.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeContext.java @@ -41,7 +41,6 @@ * Represents a rendering context within the engine. */ public interface JmeContext { - /** * The type of context. */ @@ -77,7 +76,7 @@ public enum Type { * any drawable surface. The implementation does not provide any * display, input, or sound support. */ - Headless; + Headless, } /** @@ -102,7 +101,7 @@ public enum Type { /** * Sets the listener that will receive events relating to context * creation, update, and destroy. - * + * * @param listener the desired listener */ public void setSystemListener(SystemListener listener); @@ -225,4 +224,20 @@ public enum Type { * @throws IllegalStateException for a headless or null context */ public int getWindowYPosition(); + + /** + * This call will return a list of Monitors that glfwGetMonitors() returns and information about + * the monitor, like width, height, and refresh rate. + * + * @return returns a list of monitors and their information. + */ + public Displays getDisplays(); + + /** + * Use this to get the positional number of the primary monitor from the glfwGetMonitors() + * function call. + * + * @return the position of the value in the arraylist of the primary monitor. + */ + public int getPrimaryDisplay(); } diff --git a/jme3-core/src/main/java/com/jme3/system/NullContext.java b/jme3-core/src/main/java/com/jme3/system/NullContext.java index fd43d8d761..2f2518f8c3 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullContext.java +++ b/jme3-core/src/main/java/com/jme3/system/NullContext.java @@ -48,7 +48,7 @@ public class NullContext implements JmeContext, Runnable { protected static final Logger logger = Logger.getLogger(NullContext.class.getName()); protected static final String THREAD_NAME = "jME3 Headless Main"; - + protected AtomicBoolean created = new AtomicBoolean(false); protected AtomicBoolean needClose = new AtomicBoolean(false); protected final Object createdLock = new Object(); @@ -75,26 +75,28 @@ public SystemListener getSystemListener() { } @Override - public void setSystemListener(SystemListener listener){ + public void setSystemListener(SystemListener listener) { this.listener = listener; } - protected void initInThread(){ + protected void initInThread() { logger.fine("NullContext created."); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); } - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable thrown) { - listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); + Thread.setDefaultUncaughtExceptionHandler( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown); + } } - }); + ); timer = new NanoTimer(); renderer = new NullRenderer(); - synchronized (createdLock){ + synchronized (createdLock) { created.set(true); createdLock.notifyAll(); } @@ -102,10 +104,10 @@ public void uncaughtException(Thread thread, Throwable thrown) { listener.initialize(); } - protected void deinitInThread(){ + protected void deinitInThread() { listener.destroy(); timer = null; - synchronized (createdLock){ + synchronized (createdLock) { created.set(false); createdLock.notifyAll(); } @@ -142,7 +144,7 @@ public void sync(int fps) { } @Override - public void run(){ + public void run() { initInThread(); do { @@ -159,31 +161,27 @@ public void run(){ } @Override - public void destroy(boolean waitFor){ + public void destroy(boolean waitFor) { needClose.set(true); - if (waitFor) - waitFor(false); + if (waitFor) waitFor(false); } @Override - public void create(boolean waitFor){ - if (created.get()){ + public void create(boolean waitFor) { + if (created.get()) { logger.warning("create() called when NullContext is already created!"); return; } new Thread(this, THREAD_NAME).start(); - if (waitFor) - waitFor(true); + if (waitFor) waitFor(true); } @Override - public void restart() { - } + public void restart() {} @Override - public void setAutoFlushFrames(boolean enabled){ - } + public void setAutoFlushFrames(boolean enabled) {} @Override public MouseInput getMouseInput() { @@ -206,30 +204,28 @@ public TouchInput getTouchInput() { } @Override - public void setTitle(String title) { - } + public void setTitle(String title) {} - public void create(){ + public void create() { create(false); } - public void destroy(){ + public void destroy() { destroy(false); } - protected void waitFor(boolean createdVal){ - synchronized (createdLock){ - while (created.get() != createdVal){ + protected void waitFor(boolean createdVal) { + synchronized (createdLock) { + while (created.get() != createdVal) { try { createdLock.wait(); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } } } @Override - public boolean isCreated(){ + public boolean isCreated() { return created.get(); } @@ -237,12 +233,11 @@ public boolean isCreated(){ public void setSettings(AppSettings settings) { this.settings.copyFrom(settings); frameRate = settings.getFrameRate(); - if (frameRate <= 0) - frameRate = 60; // use default update rate. + if (frameRate <= 0) frameRate = 60; // use default update rate. } @Override - public AppSettings getSettings(){ + public AppSettings getSettings() { return settings; } @@ -259,7 +254,7 @@ public Timer getTimer() { @Override public boolean isRenderable() { return true; // Doesn't really matter if true or false. Either way - // RenderManager won't render anything. + // RenderManager won't render anything. } @Override @@ -306,4 +301,16 @@ public int getWindowXPosition() { public int getWindowYPosition() { throw new UnsupportedOperationException("null context"); } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java index 1232f04809..d1911e3482 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java @@ -31,7 +31,6 @@ */ package com.jme3.system; - import com.jme3.input.AWTKeyInput; import com.jme3.input.AWTMouseInput; import com.jme3.input.JoyInput; @@ -49,104 +48,104 @@ */ public class AWTContext implements JmeContext { - /** - * The settings. - */ - protected final AppSettings settings; - - /** - * The key input. - */ - protected final AWTKeyInput keyInput; - - /** - * The mouse input. - */ - protected final AWTMouseInput mouseInput; - - /** - * The current width. - */ - private volatile int width; - - /** - * The current height. - */ - private volatile int height; - - /** - * The background context. - */ - protected JmeContext backgroundContext; - - public AWTContext() { - this.keyInput = new AWTKeyInput(this); - this.mouseInput = new AWTMouseInput(this); - this.settings = createSettings(); - this.backgroundContext = createBackgroundContext(); - this.height = 1; - this.width = 1; - } - - /** - * @return the current height. - */ - public int getHeight() { - return height; - } - - /** - * @param height the current height. - */ - public void setHeight(final int height) { - this.height = height; - } - - /** - * @return the current width. - */ - public int getWidth() { - return width; - } - - /** - * @param width the current width. - */ - public void setWidth(final int width) { - this.width = width; - } - - /** - * @return new settings. - */ - protected AppSettings createSettings() { - final AppSettings settings = new AppSettings(true); - settings.setRenderer(AppSettings.LWJGL_OPENGL32); - return settings; - } - - /** - * @return new context/ - */ - protected JmeContext createBackgroundContext() { - return JmeSystem.newContext(settings, Type.OffscreenSurface); - } - - @Override - public Type getType() { - return Type.OffscreenSurface; - } - - @Override - public void setSettings(AppSettings settings) { - this.settings.copyFrom(settings); - this.settings.setRenderer(AppSettings.LWJGL_OPENGL32); - this.backgroundContext.setSettings(settings); - } + /** + * The settings. + */ + protected final AppSettings settings; + + /** + * The key input. + */ + protected final AWTKeyInput keyInput; + + /** + * The mouse input. + */ + protected final AWTMouseInput mouseInput; + + /** + * The current width. + */ + private volatile int width; + + /** + * The current height. + */ + private volatile int height; + + /** + * The background context. + */ + protected JmeContext backgroundContext; + + public AWTContext() { + this.keyInput = new AWTKeyInput(this); + this.mouseInput = new AWTMouseInput(this); + this.settings = createSettings(); + this.backgroundContext = createBackgroundContext(); + this.height = 1; + this.width = 1; + } + + /** + * @return the current height. + */ + public int getHeight() { + return height; + } + + /** + * @param height the current height. + */ + public void setHeight(final int height) { + this.height = height; + } + + /** + * @return the current width. + */ + public int getWidth() { + return width; + } + + /** + * @param width the current width. + */ + public void setWidth(final int width) { + this.width = width; + } + + /** + * @return new settings. + */ + protected AppSettings createSettings() { + final AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); + return settings; + } + + /** + * @return new context/ + */ + protected JmeContext createBackgroundContext() { + return JmeSystem.newContext(settings, Type.OffscreenSurface); + } + + @Override + public Type getType() { + return Type.OffscreenSurface; + } + + @Override + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + this.settings.setRenderer(AppSettings.LWJGL_OPENGL32); + this.backgroundContext.setSettings(settings); + } /** * Accesses the listener that receives events related to this context. - * + * * @return the pre-existing instance */ @Override @@ -154,87 +153,85 @@ public SystemListener getSystemListener() { return backgroundContext.getSystemListener(); } - @Override - public void setSystemListener(final SystemListener listener) { - backgroundContext.setSystemListener(listener); - } - - @Override - public AppSettings getSettings() { - return settings; - } - - @Override - public Renderer getRenderer() { - return backgroundContext.getRenderer(); - } - - @Override - public Context getOpenCLContext() { - return null; - } - - @Override - public AWTMouseInput getMouseInput() { - return mouseInput; - } - - @Override - public AWTKeyInput getKeyInput() { - return keyInput; - } - - @Override - public JoyInput getJoyInput() { - return null; - } - - @Override - public TouchInput getTouchInput() { - return null; - } - - @Override - public Timer getTimer() { - return backgroundContext.getTimer(); - } - - @Override - public void setTitle(final String title) { - } - - @Override - public boolean isCreated() { - return backgroundContext != null && backgroundContext.isCreated(); - } - - @Override - public boolean isRenderable() { - return backgroundContext != null && backgroundContext.isRenderable(); - } - - @Override - public void setAutoFlushFrames(final boolean enabled) { - // TODO Auto-generated method stub - } - - @Override - public void create(final boolean waitFor) { + @Override + public void setSystemListener(final SystemListener listener) { + backgroundContext.setSystemListener(listener); + } + + @Override + public AppSettings getSettings() { + return settings; + } + + @Override + public Renderer getRenderer() { + return backgroundContext.getRenderer(); + } + + @Override + public Context getOpenCLContext() { + return null; + } + + @Override + public AWTMouseInput getMouseInput() { + return mouseInput; + } + + @Override + public AWTKeyInput getKeyInput() { + return keyInput; + } + + @Override + public JoyInput getJoyInput() { + return null; + } + + @Override + public TouchInput getTouchInput() { + return null; + } + + @Override + public Timer getTimer() { + return backgroundContext.getTimer(); + } + + @Override + public void setTitle(final String title) {} + + @Override + public boolean isCreated() { + return backgroundContext != null && backgroundContext.isCreated(); + } + + @Override + public boolean isRenderable() { + return backgroundContext != null && backgroundContext.isRenderable(); + } + + @Override + public void setAutoFlushFrames(final boolean enabled) { + // TODO Auto-generated method stub + } + + @Override + public void create(final boolean waitFor) { String render = System.getProperty("awt.background.render", AppSettings.LWJGL_OPENGL33); backgroundContext.getSettings().setRenderer(render); backgroundContext.create(waitFor); - } + } - @Override - public void restart() { - } + @Override + public void restart() {} - @Override - public void destroy(final boolean waitFor) { - if (backgroundContext == null) throw new IllegalStateException("Not created"); - // destroy wrapped context - backgroundContext.destroy(waitFor); -} + @Override + public void destroy(final boolean waitFor) { + if (backgroundContext == null) throw new IllegalStateException("Not created"); + // destroy wrapped context + backgroundContext.destroy(waitFor); + } /** * Returns the height of the framebuffer. @@ -275,4 +272,16 @@ public int getWindowXPosition() { public int getWindowYPosition() { throw new UnsupportedOperationException("not implemented yet"); } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java index 032c8457eb..a25f25379e 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java @@ -109,9 +109,8 @@ public void destroy() { } } - public void setInputSource(AwtPanel panel){ - if (!panels.contains(panel)) - throw new IllegalArgumentException(); + public void setInputSource(AwtPanel panel) { + if (!panels.contains(panel)) throw new IllegalArgumentException(); inputSource = panel; mouseInput.setInputSource(panel); @@ -187,42 +186,41 @@ public boolean isRenderable() { public Context getOpenCLContext() { return actualContext.getOpenCLContext(); } - - public AwtPanelsContext(){ - } - public AwtPanel createPanel(PaintMode paintMode){ + public AwtPanelsContext() {} + + public AwtPanel createPanel(PaintMode paintMode) { AwtPanel panel = new AwtPanel(paintMode); panels.add(panel); return panel; } - - public AwtPanel createPanel(PaintMode paintMode, boolean srgb){ + + public AwtPanel createPanel(PaintMode paintMode, boolean srgb) { AwtPanel panel = new AwtPanel(paintMode, srgb); panels.add(panel); return panel; } - private void initInThread(){ + private void initInThread() { listener.initialize(); } - private void updateInThread(){ + private void updateInThread() { // Check if throttle required boolean needThrottle = true; - for (AwtPanel panel : panels){ - if (panel.isActiveDrawing()){ + for (AwtPanel panel : panels) { + if (panel.isActiveDrawing()) { needThrottle = false; break; } } - if (lastThrottleState != needThrottle){ + if (lastThrottleState != needThrottle) { lastThrottleState = needThrottle; - if (lastThrottleState){ + if (lastThrottleState) { System.out.println("OGL: Throttling update loop."); - }else{ + } else { System.out.println("OGL: Ceased throttling update loop."); } } @@ -230,18 +228,17 @@ private void updateInThread(){ if (needThrottle) { try { Thread.sleep(100); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } listener.update(); - - for (AwtPanel panel : panels){ + + for (AwtPanel panel : panels) { panel.onFrameEnd(); } } - private void destroyInThread(){ + private void destroyInThread() { listener.destroy(); } @@ -249,14 +246,14 @@ private void destroyInThread(){ public void setSettings(AppSettings settings) { this.settings.copyFrom(settings); this.settings.setRenderer(AppSettings.LWJGL_OPENGL2); - if (actualContext != null){ + if (actualContext != null) { actualContext.setSettings(settings); } } @Override public void create(boolean waitFor) { - if (actualContext != null){ + if (actualContext != null) { throw new IllegalStateException("Already created"); } @@ -267,8 +264,7 @@ public void create(boolean waitFor) { @Override public void destroy(boolean waitFor) { - if (actualContext == null) - throw new IllegalStateException("Not created"); + if (actualContext == null) throw new IllegalStateException("Not created"); // destroy parent context actualContext.destroy(waitFor); @@ -328,4 +324,16 @@ public int getWindowXPosition() { public int getWindowYPosition() { return inputSource.getY(); } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } } diff --git a/jme3-examples/src/main/java/jme3test/app/TestMonitorApp.java b/jme3-examples/src/main/java/jme3test/app/TestMonitorApp.java new file mode 100644 index 0000000000..c8f77387f8 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestMonitorApp.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2009-2023 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 jme3test.app; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.system.DisplayInfo; +import com.jme3.system.Displays; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Tests the capability to change which monitor the window will be created on. + * Also, shows that you can force JME to center the window. Also, it shows to to + * force JME to set the window to x,y coords. Center window and window position + * doesn't apply if in fullscreen. + * + * @author Kevin Bales + */ +public class TestMonitorApp extends SimpleApplication implements ActionListener { + + private BitmapText txt; + private BitmapText selectedMonitorTxt; + private BitmapText fullScreenTxt; + private int monitorSelected = 0; + private Displays monitors = null; + + public static void main(String[] args) { + TestMonitorApp app = new TestMonitorApp(); + AppSettings settings = new AppSettings(true); + settings.setResizable(false); + app.setShowSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL33); + settings.setDisplay(0); + settings.setResolution(800, 600); + + settings.setFullscreen(true); + + // Force JME to center the window, this only applies if it is + // not fullscreen. + settings.setCenterWindow(true); + + // If center window is not turned on, you can force JME to + // open the window at certain x,y coords. These are ignored + // if the screen is set to "fullscreen". + settings.setWindowXPosition(0); + settings.setWindowYPosition(0); + + try { + // Let's try and load the AppSetting parameters back into memory + InputStream out = new FileInputStream("TestMonitorApp.prefs"); + settings.load(out); + } catch (IOException e) { + System.out.println("failed to load settings, reverting to defaults"); + } + app.setSettings(settings); + + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setDragToRotate(true); + int numMonitors = 1; + + // If monitor is define, Jme supports multiple monitors. Setup to keys + if (monitors == null) { + inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addMapping("fullscreen", new KeyTrigger(KeyInput.KEY_F)); + inputManager.addListener(this, "down", "fullscreen"); + } + + // Get the selected monitor + monitorSelected = settings.getDisplay(); + monitors = context.getDisplays(); + if (monitors != null) numMonitors = monitors.size(); + + // Let's define the labels for users to see what is going on with Multiple + // Monitor + String labelValue = ""; + labelValue = "There are " + numMonitors + " monitor(s) hooked up to this computer."; + txt = new BitmapText(loadGuiFont()); + txt.setText(labelValue); + txt.setLocalTranslation(0, settings.getHeight(), 0); + guiNode.attachChild(txt); + + txt = new BitmapText(loadGuiFont()); + if (!settings.isFullscreen()) txt.setText( + "Window is on Monitor N/A (fullscreen only feature)" + ); else txt.setText("Window is on Monitor " + settings.getDisplay()); + + txt.setLocalTranslation(0, settings.getHeight() - 40, 0); + guiNode.attachChild(txt); + + if (monitors != null) { + selectedMonitorTxt = new BitmapText(loadGuiFont()); + // Lets display information about selected monitor + String label = + "Selected Monitor " + + "Name: " + + monitors.get(settings.getDisplay()).name + + " " + + monitorSelected + + " Res: " + + monitors.get(settings.getDisplay()).width + + "," + + monitors.get(settings.getDisplay()).height + + " refresh: " + + monitors.get(settings.getDisplay()).rate; + selectedMonitorTxt.setText(label); + selectedMonitorTxt.setLocalTranslation(0, settings.getHeight() - 80, 0); + guiNode.attachChild(selectedMonitorTxt); + + // Let's loop through all the monitors and display on the screen + for (int i = 0; i < monitors.size(); i++) { + DisplayInfo monitor = monitors.get(i); + labelValue = + "Mon : " + + i + + " " + + monitor.name + + " " + + monitor.width + + "," + + monitor.height + + " refresh: " + + monitor.rate; + txt = new BitmapText(loadGuiFont()); + txt.setText(labelValue); + txt.setLocalTranslation(0, settings.getHeight() - 160 - (40 * i), 0); + guiNode.attachChild(txt); + } + } + + // Lets put a label up there for FullScreen/Window toggle + fullScreenTxt = new BitmapText(loadGuiFont()); + if (!settings.isFullscreen()) fullScreenTxt.setText("(f) Window Screen"); else fullScreenTxt.setText( + "(f) Fullscreen" + ); + + fullScreenTxt.setLocalTranslation(00, settings.getHeight() - 240, 0); + guiNode.attachChild(fullScreenTxt); + + BitmapText infoTxt = new BitmapText(loadGuiFont()); + infoTxt.setText("Restart is required to activate changes in settings."); + infoTxt.setLocalTranslation(0, settings.getHeight() - 300, 0); + guiNode.attachChild(infoTxt); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (monitors == null) return; + + if (name.equals("down") && isPressed) { + monitorSelected++; + if (monitorSelected >= monitors.size()) monitorSelected = 0; + saveSettings(); + } else if (name.equals("up") && isPressed) { + monitorSelected--; + if (monitorSelected < 0) monitorSelected = monitors.size() - 1; + saveSettings(); + } else if (name.equals("fullscreen") && isPressed) { + settings.setFullscreen(!settings.isFullscreen()); + saveSettings(); + } + } + + /** + * This function saves out the AppSettings into a file to be loaded back in + * on start of application. + */ + public void saveSettings() { + try { + settings.setDisplay(monitorSelected); + OutputStream out = new FileOutputStream("TestMonitorApp.prefs"); + settings.save(out); + + int monitorSelected = settings.getDisplay(); + String label = + "Selected Monitor " + + monitorSelected + + " " + + monitors.get(monitorSelected).name + + " Res: " + + monitors.get(monitorSelected).width + + "," + + monitors.get(monitorSelected).height + + "refresh: " + + monitors.get(monitorSelected).rate; + selectedMonitorTxt.setText(label); + if (!settings.isFullscreen()) fullScreenTxt.setText( + "(f) Window Screen" + ); else fullScreenTxt.setText("(f) Fullscreen"); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java b/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java index 09d034581d..da88af4ff1 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java +++ b/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java @@ -61,9 +61,11 @@ public void reshape(int width, int height) { super.reshape(width, height); // Need to move text relative to app height - txt.setLocalTranslation(0, settings.getHeight(), 0); - txt.setText("Drag the corners of the application to resize it.\n" + - "Current Size: " + settings.getWidth() + "x" + settings.getHeight()); + if (txt != null) { + txt.setLocalTranslation(0, settings.getHeight(), 0); + txt.setText("Drag the corners of the application to resize it.\n" + + "Current Size: " + settings.getWidth() + "x" + settings.getHeight()); + } } @Override diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java index 009195c776..e314353bbf 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java @@ -42,7 +42,6 @@ import com.jme3.renderer.ios.IosGL; import com.jme3.renderer.opengl.*; import com.jme3.system.*; - import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -63,10 +62,10 @@ public class IGLESContext implements JmeContext { protected Timer timer; protected SystemListener listener; protected IosInputHandler input; - protected int minFrameDuration = 0; // No FPS cap + protected int minFrameDuration = 0; // No FPS cap public IGLESContext() { - logger.log(Level.FINE, "IGLESContext constructor"); + logger.log(Level.FINE, "IGLESContext constructor"); } @Override @@ -123,13 +122,13 @@ public KeyInput getKeyInput() { @Override public JoyInput getJoyInput() { - /* + /* if (androidSensorJoyInput == null) { androidSensorJoyInput = new AndroidSensorJoyInput(); } return androidSensorJoyInput; */ - return null;// new DummySensorJoyInput(); + return null; // new DummySensorJoyInput(); } @Override @@ -143,8 +142,7 @@ public Timer getTimer() { } @Override - public void setTitle(String title) { - } + public void setTitle(String title) {} @Override public boolean isCreated() { @@ -160,7 +158,7 @@ public void setAutoFlushFrames(boolean enabled) { @Override public boolean isRenderable() { logger.log(Level.FINE, "IGLESContext isRenderable"); - return true;// renderable.get(); + return true; // renderable.get(); } @Override @@ -169,18 +167,18 @@ public void create(boolean waitFor) { IosGL gl = new IosGL(); if (settings.getBoolean("GraphicsDebug")) { - gl = (IosGL)GLDebug.createProxy(gl, gl, GL.class, GLExt.class, GLFbo.class); + gl = (IosGL) GLDebug.createProxy(gl, gl, GL.class, GLExt.class, GLFbo.class); } renderer = new GLRenderer(gl, gl, gl); renderer.initialize(); - + input = new IosInputHandler(); timer = new NanoTimer(); -//synchronized (createdLock){ - created.set(true); - //createdLock.notifyAll(); + //synchronized (createdLock){ + created.set(true); + //createdLock.notifyAll(); //} listener.initialize(); @@ -196,8 +194,7 @@ public void create() { } @Override - public void restart() { - } + public void restart() {} @Override public void destroy(boolean waitFor) { @@ -217,8 +214,7 @@ protected void waitFor(boolean createdVal) { while (renderable.get() != createdVal) { try { Thread.sleep(10); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } } @@ -267,4 +263,16 @@ public int getWindowXPosition() { public int getWindowYPosition() { throw new UnsupportedOperationException("not implemented yet"); } -} \ No newline at end of file + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 0bfb63976e..5fa76754b2 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -1,505 +1,519 @@ -/* - * Copyright (c) 2009-2021 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 com.jme3.system.JmeContext.Type; -import com.jme3.system.JmeSystem; -import com.jme3.system.Platform; -import java.awt.Canvas; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.SwingUtilities; -import org.lwjgl.LWJGLException; -import org.lwjgl.input.Keyboard; -import org.lwjgl.input.Mouse; -import org.lwjgl.opengl.Display; -import org.lwjgl.opengl.Pbuffer; -import org.lwjgl.opengl.PixelFormat; - -public class LwjglCanvas extends LwjglAbstractDisplay 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(LwjglDisplay.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 Pbuffer pbuffer; - private PixelFormat pbufferFormat; - private PixelFormat canvasFormat; - - 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 shut down and wait for it to - // shut down. 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(); - canvas = new GLCanvas(); - } - - @Override - public Type getType() { - return Type.Canvas; - } - - @Override - 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); - } - } - - @Override - public void setTitle(String title) { - } - - @Override - public void restart() { - frameRate = settings.getFrameRate(); - // TODO: Handle other cases, like change of pixel format, etc. - } - - @Override - 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); - } - } - }else{ - if (frameRate <= 0){ - // NOTE: MUST be done otherwise - // Windows OS will freeze - Display.sync(30); - } - } - - super.runLoop(); - } - - private void pauseCanvas(){ - 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 (LWJGLException ex){ - logger.log(Level.SEVERE, "Encountered exception when restoring input", ex); - } - - SwingUtilities.invokeLater(new Runnable(){ - @Override - public void run(){ - canvas.requestFocus(); - } - }); - } - - /** - * It seems it is best to use one pixel format for all shared contexts. - * @see http://developer.apple.com/library/mac/#qa/qa1248/_index.html - * - * @param forPbuffer true→zero samples, false→correct number of samples - * @return a new instance - */ - protected PixelFormat acquirePixelFormat(boolean forPbuffer){ - if (forPbuffer){ - // Use 0 samples for pbuffer format, prevents - // crashes on bad drivers - if (pbufferFormat == null){ - pbufferFormat = new PixelFormat(settings.getBitsPerPixel(), - settings.getAlphaBits(), - settings.getDepthBits(), - settings.getStencilBits(), - 0, // samples - 0, - 0, - 0, - settings.useStereo3D()); - } - return pbufferFormat; - }else{ - if (canvasFormat == null){ - int samples = getNumSamplesToUse(); - canvasFormat = new PixelFormat(settings.getBitsPerPixel(), - settings.getAlphaBits(), - settings.getDepthBits(), - settings.getStencilBits(), - samples, - 0, - 0, - 0, - settings.useStereo3D()); - } - return canvasFormat; - } - } - - /** - * Makes sure the pbuffer is available and ready for use - * - * @throws LWJGLException if the buffer can't be made current - */ - 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 - */ - @Override - protected void destroyContext(){ - try { - // invalidate the state so renderer can resume operation - if (!USE_SHARED_CONTEXT){ - renderer.cleanup(); - } - - if (Display.isCreated()){ - /* FIXES: - * org.lwjgl.LWJGLException: X Error - * BadWindow (invalid Window parameter) request_code: 2 minor_code: 0 - * - * Destroying keyboard early prevents the error above, triggered - * by destroying keyboard in by Display.destroy() or Display.setParent(null). - * Therefore, Keyboard.destroy() should precede any of these calls. - */ - if (Keyboard.isCreated()){ - // Should only happen if called in - // LwjglAbstractDisplay.deinitInThread(). - Keyboard.destroy(); - } - - //try { - // NOTE: On Windows XP, not calling setParent(null) - // freezes the application. - // On Mac it freezes the application. - // On Linux it fixes a crash with X Window System. - if (JmeSystem.getPlatform() == Platform.Windows32 - || JmeSystem.getPlatform() == Platform.Windows64){ - //Display.setParent(null); - } - //} catch (LWJGLException ex) { - // logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex); - //} - - Display.destroy(); - } - - // The canvas is no longer visible, - // but the context thread is still running. - if (!needClose.get()){ - // MUST make sure there's still a context current here. - // Display is dead, make PBuffer available to the system. - makePbufferAvailable(); - - renderer.invalidateState(); - }else{ - // The context thread is no longer running. - // Destroy pbuffer. - destroyPbuffer(); - } - } catch (LWJGLException ex) { - listener.handleError("Failed make pbuffer available", ex); - } - } - - /** - * This is called: - * 1) When the context thread starts - * 2) Any time the canvas becomes displayable again. - * In the first call of this method, OpenGL context is not ready yet. Therefore, OpenCL context cannot be created. - * The second call of this method is done after "simpleInitApp" is called. Therefore, OpenCL won't be available in "simpleInitApp" if Canvas/Swing is used. - * To use OpenCL with Canvas/Swing, you need to use OpenCL in the rendering loop "simpleUpdate" and check for "context.getOpenCLContext()!=null". - */ - @Override - protected void createContext(AppSettings settings) { - // In case canvas is not visible, we still take framerate - // from settings to prevent "100% CPU usage" - frameRate = settings.getFrameRate(); - allowSwapBuffers = settings.isSwapBuffers(); - - try { - if (renderable.get()){ - if (!runningFirstTime){ - // because the display is a different opengl context - // must reset the context state. - if (!USE_SHARED_CONTEXT){ - renderer.cleanup(); - } - } - - // if the pbuffer is currently active, - // make sure to deactivate it - destroyPbuffer(); - - if (Keyboard.isCreated()){ - Keyboard.destroy(); - } - - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - } - - Display.setVSyncEnabled(settings.isVSync()); - Display.setParent(canvas); - - if (USE_SHARED_CONTEXT){ - Display.create(acquirePixelFormat(false), pbuffer); - }else{ - Display.create(acquirePixelFormat(false)); - } - if (settings.isOpenCLSupport()) { - initOpenCL(); - } - - renderer.invalidateState(); - }else{ - // First create the pbuffer, if it is needed. - makePbufferAvailable(); - } - - // 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; - } - } catch (LWJGLException ex) { - listener.handleError("Failed to initialize OpenGL context", ex); - // TODO: Fix deadlock that happens after the error (throw runtime exception?) - } - } -} +/* + * Copyright (c) 2009-2021 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.Displays; +import com.jme3.system.JmeCanvasContext; +import com.jme3.system.JmeContext.Type; +import com.jme3.system.JmeSystem; +import com.jme3.system.Platform; +import java.awt.Canvas; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; +import org.lwjgl.LWJGLException; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.Pbuffer; +import org.lwjgl.opengl.PixelFormat; + +public class LwjglCanvas extends LwjglAbstractDisplay 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(LwjglDisplay.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 Pbuffer pbuffer; + private PixelFormat pbufferFormat; + private PixelFormat canvasFormat; + + 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 shut down and wait for it to + // shut down. 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(); + canvas = new GLCanvas(); + } + + @Override + public Type getType() { + return Type.Canvas; + } + + @Override + 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); + } + } + + @Override + public void setTitle(String title) {} + + @Override + public void restart() { + frameRate = settings.getFrameRate(); + // TODO: Handle other cases, like change of pixel format, etc. + } + + @Override + 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); + } + } + } else { + if (frameRate <= 0) { + // NOTE: MUST be done otherwise + // Windows OS will freeze + Display.sync(30); + } + } + + super.runLoop(); + } + + private void pauseCanvas() { + 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 (LWJGLException ex) { + logger.log(Level.SEVERE, "Encountered exception when restoring input", ex); + } + + SwingUtilities.invokeLater( + new Runnable() { + @Override + public void run() { + canvas.requestFocus(); + } + } + ); + } + + /** + * It seems it is best to use one pixel format for all shared contexts. + * + * @see http://developer.apple.com/library/mac/#qa/qa1248/_index.html + * + * @param forPbuffer true→zero samples, false→correct number of samples + * @return a new instance + */ + protected PixelFormat acquirePixelFormat(boolean forPbuffer) { + if (forPbuffer) { + // Use 0 samples for pbuffer format, prevents + // crashes on bad drivers + if (pbufferFormat == null) { + pbufferFormat = + new PixelFormat( + settings.getBitsPerPixel(), + settings.getAlphaBits(), + settings.getDepthBits(), + settings.getStencilBits(), + 0, // samples + 0, + 0, + 0, + settings.useStereo3D() + ); + } + return pbufferFormat; + } else { + if (canvasFormat == null) { + int samples = getNumSamplesToUse(); + canvasFormat = + new PixelFormat( + settings.getBitsPerPixel(), + settings.getAlphaBits(), + settings.getDepthBits(), + settings.getStencilBits(), + samples, + 0, + 0, + 0, + settings.useStereo3D() + ); + } + return canvasFormat; + } + } + + /** + * Makes sure the pbuffer is available and ready for use + * + * @throws LWJGLException if the buffer can't be made current + */ + 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 + */ + @Override + protected void destroyContext() { + try { + // invalidate the state so renderer can resume operation + if (!USE_SHARED_CONTEXT) { + renderer.cleanup(); + } + + if (Display.isCreated()) { + /* + * FIXES: org.lwjgl.LWJGLException: X Error BadWindow (invalid Window parameter) + * request_code: 2 minor_code: 0 + * + * Destroying keyboard early prevents the error above, triggered by destroying keyboard in + * by Display.destroy() or Display.setParent(null). Therefore, Keyboard.destroy() should + * precede any of these calls. + */ + if (Keyboard.isCreated()) { + // Should only happen if called in + // LwjglAbstractDisplay.deinitInThread(). + Keyboard.destroy(); + } + + // try { + // NOTE: On Windows XP, not calling setParent(null) + // freezes the application. + // On Mac it freezes the application. + // On Linux it fixes a crash with X Window System. + if ( + JmeSystem.getPlatform() == Platform.Windows32 || + JmeSystem.getPlatform() == Platform.Windows64 + ) { + // Display.setParent(null); + } + // } catch (LWJGLException ex) { + // logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex); + // } + + Display.destroy(); + } + + // The canvas is no longer visible, + // but the context thread is still running. + if (!needClose.get()) { + // MUST make sure there's still a context current here. + // Display is dead, make PBuffer available to the system. + makePbufferAvailable(); + + renderer.invalidateState(); + } else { + // The context thread is no longer running. + // Destroy pbuffer. + destroyPbuffer(); + } + } catch (LWJGLException ex) { + listener.handleError("Failed make pbuffer available", ex); + } + } + + /** + * This is called: 1) When the context thread starts 2) Any time the canvas becomes displayable + * again. In the first call of this method, OpenGL context is not ready yet. Therefore, OpenCL + * context cannot be created. The second call of this method is done after "simpleInitApp" is + * called. Therefore, OpenCL won't be available in "simpleInitApp" if Canvas/Swing is used. To use + * OpenCL with Canvas/Swing, you need to use OpenCL in the rendering loop "simpleUpdate" and check + * for "context.getOpenCLContext()!=null". + */ + @Override + protected void createContext(AppSettings settings) { + // In case canvas is not visible, we still take framerate + // from settings to prevent "100% CPU usage" + frameRate = settings.getFrameRate(); + allowSwapBuffers = settings.isSwapBuffers(); + + try { + if (renderable.get()) { + if (!runningFirstTime) { + // because the display is a different opengl context + // must reset the context state. + if (!USE_SHARED_CONTEXT) { + renderer.cleanup(); + } + } + + // if the pbuffer is currently active, + // make sure to deactivate it + destroyPbuffer(); + + if (Keyboard.isCreated()) { + Keyboard.destroy(); + } + + try { + Thread.sleep(1000); + } catch (InterruptedException ex) {} + + Display.setVSyncEnabled(settings.isVSync()); + Display.setParent(canvas); + + if (USE_SHARED_CONTEXT) { + Display.create(acquirePixelFormat(false), pbuffer); + } else { + Display.create(acquirePixelFormat(false)); + } + if (settings.isOpenCLSupport()) { + initOpenCL(); + } + + renderer.invalidateState(); + } else { + // First create the pbuffer, if it is needed. + makePbufferAvailable(); + } + + // 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; + } + } catch (LWJGLException ex) { + listener.handleError("Failed to initialize OpenGL context", ex); + // TODO: Fix deadlock that happens after the error (throw runtime exception?) + } + } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java index 1db33942e8..bec632f993 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java @@ -1,285 +1,325 @@ -/* - * Copyright (c) 2009-2023 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.JmeContext.Type; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.lwjgl.LWJGLException; -import org.lwjgl.opengl.*; - -public class LwjglDisplay extends LwjglAbstractDisplay { - - private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); - - private final AtomicBoolean needRestart = new AtomicBoolean(false); - private PixelFormat pixelFormat; - - /** - * @param width The required display width - * @param height The required display height - * @param bpp The required bits per pixel. If -1 is passed it will return - * whatever bpp is found - * @param freq The required frequency, if -1 is passed it will return - * whatever frequency is found - * @return The {@link DisplayMode} matches with specified settings or - * return null if no matching display mode is found - */ - protected DisplayMode getFullscreenDisplayMode(int width, int height, int bpp, int freq){ - try { - DisplayMode[] modes = Display.getAvailableDisplayModes(); - for (DisplayMode mode : modes) { - if (mode.getWidth() == width - && mode.getHeight() == height - && (mode.getBitsPerPixel() == bpp || (bpp == 24 && mode.getBitsPerPixel() == 32) || bpp == -1) - // Looks like AWT uses mathematical round to convert floating point - // frequency values to int while lwjgl 2 uses mathematical floor. - // For example if frequency is 59.83, AWT will return 60 but lwjgl2 - // will return 59. This is what I observed on Linux. - Ali-RS 2023-1-10 - && (Math.abs(mode.getFrequency() - freq) <= 1 || freq == -1)) { - return mode; - } - } - } catch (LWJGLException ex) { - listener.handleError("Failed to acquire fullscreen display mode!", ex); - } - return null; - } - - @Override - protected void createContext(AppSettings settings) throws LWJGLException{ - DisplayMode displayMode; - if (settings.getWidth() <= 0 || settings.getHeight() <= 0) { - displayMode = Display.getDesktopDisplayMode(); - settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); - } else if (settings.isFullscreen()) { - displayMode = getFullscreenDisplayMode(settings.getWidth(), settings.getHeight(), - settings.getBitsPerPixel(), settings.getFrequency()); - if (displayMode == null) { - // Fall back to whatever mode is available at the specified width & height - displayMode = getFullscreenDisplayMode(settings.getWidth(), settings.getHeight(), -1, -1); - if (displayMode == null) { - throw new RuntimeException("Unable to find fullscreen display mode matching settings"); - } else { - logger.log(Level.WARNING, "Unable to find fullscreen display mode matching settings, falling back to: {0}", displayMode); - } - } - } else { - displayMode = new DisplayMode(settings.getWidth(), settings.getHeight()); - } - - int samples = getNumSamplesToUse(); - PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(), - settings.getAlphaBits(), - settings.getDepthBits(), - settings.getStencilBits(), - samples, - 0, - 0, - 0, - settings.useStereo3D()); - - frameRate = settings.getFrameRate(); - allowSwapBuffers = settings.isSwapBuffers(); - logger.log(Level.FINE, "Selected display mode: {0}", displayMode); - - boolean pixelFormatChanged = false; - if (created.get() && (pixelFormat.getBitsPerPixel() != pf.getBitsPerPixel() - ||pixelFormat.getAlphaBits() != pf.getAlphaBits() - ||pixelFormat.getDepthBits() != pf.getDepthBits() - ||pixelFormat.getStencilBits() != pf.getStencilBits() - ||pixelFormat.getSamples() != pf.getSamples())){ - renderer.resetGLObjects(); - Display.destroy(); - pixelFormatChanged = true; - } - pixelFormat = pf; - - Display.setTitle(settings.getTitle()); - Display.setResizable(settings.isResizable()); - - if (settings.isFullscreen()) { - Display.setDisplayModeAndFullscreen(displayMode); - } else { - Display.setFullscreen(false); - Display.setDisplayMode(displayMode); - } - - if (settings.getIcons() != null) { - Display.setIcon(imagesToByteBuffers(settings.getIcons())); - } - - Display.setVSyncEnabled(settings.isVSync()); - - if (created.get() && !pixelFormatChanged) { - renderer.resetGLObjects(); - Display.releaseContext(); - Display.makeCurrent(); - Display.update(); - } - - if (!created.get() || pixelFormatChanged){ - ContextAttribs attr = createContextAttribs(); - if (attr != null) { - Display.create(pixelFormat, attr); - } else { - Display.create(pixelFormat); - } - renderable.set(true); - - if (pixelFormatChanged && pixelFormat.getSamples() > 1 - && GLContext.getCapabilities().GL_ARB_multisample){ - GL11.glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); - } - } - - if (settings.isOpenCLSupport()) { - initOpenCL(); - } - } - - @Override - protected void destroyContext(){ - try { - renderer.cleanup(); - Display.releaseContext(); - Display.destroy(); - } catch (LWJGLException ex) { - listener.handleError("Failed to destroy context", ex); - } - } - - @Override - 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); - } - - @Override - public void runLoop(){ - // This method is overridden to do restart - if (needRestart.getAndSet(false)) { - try { - createContext(settings); - } catch (LWJGLException ex) { - logger.log(Level.SEVERE, "Failed to set display settings!", ex); - } - listener.reshape(settings.getWidth(), settings.getHeight()); - if (renderable.get()) { - reinitContext(); - } else { - assert getType() == Type.Canvas; - } - logger.fine("Display restarted."); - } else if (Display.wasResized()) { - int newWidth = Display.getWidth(); - int newHeight = Display.getHeight(); - settings.setResolution(newWidth, newHeight); - listener.reshape(newWidth, newHeight); - } - - super.runLoop(); - } - - @Override - public void restart() { - if (created.get()){ - needRestart.set(true); - }else{ - logger.warning("Display is not created, cannot restart window."); - } - } - - @Override - public Type getType() { - return Type.Display; - } - - @Override - public void setTitle(String title){ - if (created.get()) - Display.setTitle(title); - } - - 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); - } - -} +/* + * Copyright (c) 2009-2023 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.Displays; +import com.jme3.system.JmeContext.Type; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import org.lwjgl.opengl.*; + +public class LwjglDisplay extends LwjglAbstractDisplay { + + private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); + + private final AtomicBoolean needRestart = new AtomicBoolean(false); + private PixelFormat pixelFormat; + + /** + * @param width The required display width + * @param height The required display height + * @param bpp The required bits per pixel. If -1 is passed it will return + * whatever bpp is found + * @param freq The required frequency, if -1 is passed it will return + * whatever frequency is found + * @return The {@link DisplayMode} matches with specified settings or + * return null if no matching display mode is found + */ + protected DisplayMode getFullscreenDisplayMode(int width, int height, int bpp, int freq) { + try { + DisplayMode[] modes = Display.getAvailableDisplayModes(); + for (DisplayMode mode : modes) { + if ( + mode.getWidth() == width && + mode.getHeight() == height && + (mode.getBitsPerPixel() == bpp || + (bpp == 24 && mode.getBitsPerPixel() == 32) || + bpp == -1) && + // Looks like AWT uses mathematical round to convert floating point + // frequency values to int while lwjgl 2 uses mathematical floor. + // For example if frequency is 59.83, AWT will return 60 but lwjgl2 + // will return 59. This is what I observed on Linux. - Ali-RS 2023-1-10 + (Math.abs(mode.getFrequency() - freq) <= 1 || freq == -1) + ) { + return mode; + } + } + } catch (LWJGLException ex) { + listener.handleError("Failed to acquire fullscreen display mode!", ex); + } + return null; + } + + @Override + protected void createContext(AppSettings settings) throws LWJGLException { + DisplayMode displayMode; + if (settings.getWidth() <= 0 || settings.getHeight() <= 0) { + displayMode = Display.getDesktopDisplayMode(); + settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); + } else if (settings.isFullscreen()) { + displayMode = + getFullscreenDisplayMode( + settings.getWidth(), + settings.getHeight(), + settings.getBitsPerPixel(), + settings.getFrequency() + ); + if (displayMode == null) { + // Fall back to whatever mode is available at the specified width & height + displayMode = getFullscreenDisplayMode(settings.getWidth(), settings.getHeight(), -1, -1); + if (displayMode == null) { + throw new RuntimeException("Unable to find fullscreen display mode matching settings"); + } else { + logger.log( + Level.WARNING, + "Unable to find fullscreen display mode matching settings, falling back to: {0}", + displayMode + ); + } + } + } else { + displayMode = new DisplayMode(settings.getWidth(), settings.getHeight()); + } + + int samples = getNumSamplesToUse(); + PixelFormat pf = new PixelFormat( + settings.getBitsPerPixel(), + settings.getAlphaBits(), + settings.getDepthBits(), + settings.getStencilBits(), + samples, + 0, + 0, + 0, + settings.useStereo3D() + ); + + frameRate = settings.getFrameRate(); + allowSwapBuffers = settings.isSwapBuffers(); + logger.log(Level.FINE, "Selected display mode: {0}", displayMode); + + boolean pixelFormatChanged = false; + if ( + created.get() && + (pixelFormat.getBitsPerPixel() != pf.getBitsPerPixel() || + pixelFormat.getAlphaBits() != pf.getAlphaBits() || + pixelFormat.getDepthBits() != pf.getDepthBits() || + pixelFormat.getStencilBits() != pf.getStencilBits() || + pixelFormat.getSamples() != pf.getSamples()) + ) { + renderer.resetGLObjects(); + Display.destroy(); + pixelFormatChanged = true; + } + pixelFormat = pf; + + Display.setTitle(settings.getTitle()); + Display.setResizable(settings.isResizable()); + + if (settings.isFullscreen()) { + Display.setDisplayModeAndFullscreen(displayMode); + } else { + Display.setFullscreen(false); + Display.setDisplayMode(displayMode); + } + + if (settings.getIcons() != null) { + Display.setIcon(imagesToByteBuffers(settings.getIcons())); + } + + Display.setVSyncEnabled(settings.isVSync()); + + if (created.get() && !pixelFormatChanged) { + renderer.resetGLObjects(); + Display.releaseContext(); + Display.makeCurrent(); + Display.update(); + } + + if (!created.get() || pixelFormatChanged) { + ContextAttribs attr = createContextAttribs(); + if (attr != null) { + Display.create(pixelFormat, attr); + } else { + Display.create(pixelFormat); + } + renderable.set(true); + + if ( + pixelFormatChanged && + pixelFormat.getSamples() > 1 && + GLContext.getCapabilities().GL_ARB_multisample + ) { + GL11.glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); + } + } + + if (settings.isOpenCLSupport()) { + initOpenCL(); + } + } + + @Override + protected void destroyContext() { + try { + renderer.cleanup(); + Display.releaseContext(); + Display.destroy(); + } catch (LWJGLException ex) { + listener.handleError("Failed to destroy context", ex); + } + } + + @Override + 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); + } + + @Override + public void runLoop() { + // This method is overridden to do restart + if (needRestart.getAndSet(false)) { + try { + createContext(settings); + } catch (LWJGLException ex) { + logger.log(Level.SEVERE, "Failed to set display settings!", ex); + } + listener.reshape(settings.getWidth(), settings.getHeight()); + if (renderable.get()) { + reinitContext(); + } else { + assert getType() == Type.Canvas; + } + logger.fine("Display restarted."); + } else if (Display.wasResized()) { + int newWidth = Display.getWidth(); + int newHeight = Display.getHeight(); + settings.setResolution(newWidth, newHeight); + listener.reshape(newWidth, newHeight); + } + + super.runLoop(); + } + + @Override + public void restart() { + if (created.get()) { + needRestart.set(true); + } else { + logger.warning("Display is not created, cannot restart window."); + } + } + + @Override + public Type getType() { + return Type.Display; + } + + @Override + public void setTitle(String title) { + if (created.get()) Display.setTitle(title); + } + + 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); + } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java index 5484d13cf6..fc5c21c6ec 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java @@ -1,221 +1,233 @@ -/* - * Copyright (c) 2009-2020 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.dummy.DummyKeyInput; -import com.jme3.input.dummy.DummyMouseInput; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.lwjgl.LWJGLException; -import org.lwjgl.Sys; -import org.lwjgl.opengl.*; - -public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { - - private static final Logger logger = Logger.getLogger(LwjglOffscreenBuffer.class.getName()); - private Pbuffer pbuffer; - protected AtomicBoolean needClose = new AtomicBoolean(false); - private int width; - private int height; - private PixelFormat pixelFormat; - - protected void initInThread(){ - if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0){ - logger.severe("Offscreen surfaces are not supported."); - return; - } - - int samples = getNumSamplesToUse(); - pixelFormat = new PixelFormat(settings.getBitsPerPixel(), - settings.getAlphaBits(), - settings.getDepthBits(), - settings.getStencilBits(), - samples); - - width = settings.getWidth(); - height = settings.getHeight(); - try{ - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable thrown) { - listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); - } - }); - - pbuffer = new Pbuffer(width, height, pixelFormat, null, null, createContextAttribs()); - pbuffer.makeCurrent(); - - renderable.set(true); - - logger.fine("Offscreen buffer created."); - printContextInitInfo(); - } catch (LWJGLException ex){ - listener.handleError("Failed to create display", ex); - } finally { - // TODO: It is possible to avoid "Failed to find pixel format" - // error here by creating a default display. - } - super.internalCreate(); - listener.initialize(); - } - - protected boolean checkGLError(){ - try { - Util.checkGLError(); - } catch (OpenGLException ex){ - listener.handleError("An OpenGL error has occurred!", ex); - } - // NOTE: Always return true since this is used in an "assert" statement - return true; - } - - protected void runLoop(){ - if (!created.get()) { - throw new IllegalStateException(); - } - - if (pbuffer.isBufferLost()) { - pbuffer.destroy(); - - try { - pbuffer = new Pbuffer(width, height, pixelFormat, null); - pbuffer.makeCurrent(); - - // Context MUST be reset here to avoid invalid objects! - renderer.invalidateState(); - } catch (LWJGLException ex) { - listener.handleError("Failed to restore PBuffer content", ex); - } - } - - listener.update(); - assert checkGLError(); - - renderer.postFrame(); - - // Need to flush GL commands - // to see any result on the pbuffer's front buffer. - GL11.glFlush(); - - int frameRate = settings.getFrameRate(); - if (frameRate >= 1) { - Display.sync(frameRate); - } - } - - protected void deinitInThread(){ - renderable.set(false); - - listener.destroy(); - renderer.cleanup(); - pbuffer.destroy(); - logger.fine("Offscreen buffer destroyed."); - - super.internalDestroy(); - } - - @Override - public void run(){ - loadNatives(); - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion()); - } - initInThread(); - while (!needClose.get()){ - runLoop(); - } - deinitInThread(); - } - - @Override - public void destroy(boolean waitFor){ - needClose.set(true); - if (waitFor) - waitFor(false); - } - - @Override - public void create(boolean waitFor){ - if (created.get()){ - logger.warning("create() called when pbuffer is already created!"); - return; - } - - new Thread(this, THREAD_NAME).start(); - if (waitFor) - waitFor(true); - } - - @Override - public void restart() { - } - - @Override - public void setAutoFlushFrames(boolean enabled){ - } - - @Override - public Type getType() { - return Type.OffscreenSurface; - } - - @Override - public MouseInput getMouseInput() { - return new DummyMouseInput(); - } - - @Override - public KeyInput getKeyInput() { - return new DummyKeyInput(); - } - - @Override - public JoyInput getJoyInput() { - return null; - } - - @Override - public TouchInput getTouchInput() { - return null; - } - - @Override - public void setTitle(String title) { - } - -} +/* + * Copyright (c) 2009-2020 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.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; +import com.jme3.system.Displays; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.opengl.*; + +public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { + + private static final Logger logger = Logger.getLogger(LwjglOffscreenBuffer.class.getName()); + private Pbuffer pbuffer; + protected AtomicBoolean needClose = new AtomicBoolean(false); + private int width; + private int height; + private PixelFormat pixelFormat; + + protected void initInThread() { + if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0) { + logger.severe("Offscreen surfaces are not supported."); + return; + } + + int samples = getNumSamplesToUse(); + pixelFormat = + new PixelFormat( + settings.getBitsPerPixel(), + settings.getAlphaBits(), + settings.getDepthBits(), + settings.getStencilBits(), + samples + ); + + width = settings.getWidth(); + height = settings.getHeight(); + try { + Thread.setDefaultUncaughtExceptionHandler( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown); + } + } + ); + + pbuffer = new Pbuffer(width, height, pixelFormat, null, null, createContextAttribs()); + pbuffer.makeCurrent(); + + renderable.set(true); + + logger.fine("Offscreen buffer created."); + printContextInitInfo(); + } catch (LWJGLException ex) { + listener.handleError("Failed to create display", ex); + } finally { + // TODO: It is possible to avoid "Failed to find pixel format" + // error here by creating a default display. + } + super.internalCreate(); + listener.initialize(); + } + + protected boolean checkGLError() { + try { + Util.checkGLError(); + } catch (OpenGLException ex) { + listener.handleError("An OpenGL error has occurred!", ex); + } + // NOTE: Always return true since this is used in an "assert" statement + return true; + } + + protected void runLoop() { + if (!created.get()) { + throw new IllegalStateException(); + } + + if (pbuffer.isBufferLost()) { + pbuffer.destroy(); + + try { + pbuffer = new Pbuffer(width, height, pixelFormat, null); + pbuffer.makeCurrent(); + + // Context MUST be reset here to avoid invalid objects! + renderer.invalidateState(); + } catch (LWJGLException ex) { + listener.handleError("Failed to restore PBuffer content", ex); + } + } + + listener.update(); + assert checkGLError(); + + renderer.postFrame(); + + // Need to flush GL commands + // to see any result on the pbuffer's front buffer. + GL11.glFlush(); + + int frameRate = settings.getFrameRate(); + if (frameRate >= 1) { + Display.sync(frameRate); + } + } + + protected void deinitInThread() { + renderable.set(false); + + listener.destroy(); + renderer.cleanup(); + pbuffer.destroy(); + logger.fine("Offscreen buffer destroyed."); + + super.internalDestroy(); + } + + @Override + public void run() { + loadNatives(); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion()); + } + initInThread(); + while (!needClose.get()) { + runLoop(); + } + deinitInThread(); + } + + @Override + public void destroy(boolean waitFor) { + needClose.set(true); + if (waitFor) waitFor(false); + } + + @Override + public void create(boolean waitFor) { + if (created.get()) { + logger.warning("create() called when pbuffer is already created!"); + return; + } + + new Thread(this, THREAD_NAME).start(); + if (waitFor) waitFor(true); + } + + @Override + public void restart() {} + + @Override + public void setAutoFlushFrames(boolean enabled) {} + + @Override + public Type getType() { + return Type.OffscreenSurface; + } + + @Override + public MouseInput getMouseInput() { + return new DummyMouseInput(); + } + + @Override + public KeyInput getKeyInput() { + return new DummyKeyInput(); + } + + @Override + public JoyInput getJoyInput() { + return null; + } + + @Override + public TouchInput getTouchInput() { + return null; + } + + @Override + public void setTitle(String title) {} + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } +} 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 index 3eb749adcf..f4a5582f33 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java @@ -31,6 +31,8 @@ */ package com.jme3.system.lwjgl; +import com.jme3.system.Displays; + /** * @author Daniel Johansson */ @@ -39,4 +41,6 @@ public class LwjglDisplay extends LwjglWindow { public LwjglDisplay() { super(Type.Display); } + + } 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 index c2b5e47402..e9c8aa2411 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java @@ -32,6 +32,10 @@ package com.jme3.system.lwjgl; +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.opengl.GL11.GL_FALSE; +import static org.lwjgl.system.MemoryUtil.NULL; + import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; @@ -41,33 +45,32 @@ import com.jme3.input.lwjgl.GlfwMouseInput; import com.jme3.math.Vector2f; import com.jme3.system.AppSettings; +import com.jme3.system.Displays; import com.jme3.system.JmeContext; import com.jme3.system.JmeSystem; import com.jme3.system.NanoTimer; import com.jme3.util.BufferUtils; import com.jme3.util.SafeArrayList; -import org.lwjgl.Version; -import org.lwjgl.glfw.GLFWErrorCallback; -import org.lwjgl.glfw.GLFWFramebufferSizeCallback; -import org.lwjgl.glfw.GLFWImage; -import org.lwjgl.glfw.GLFWVidMode; -import org.lwjgl.glfw.GLFWWindowFocusCallback; -import org.lwjgl.glfw.GLFWWindowSizeCallback; -import org.lwjgl.system.Platform; - import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; +import java.nio.IntBuffer; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import java.util.Objects; 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.system.MemoryUtil.NULL; +import org.lwjgl.PointerBuffer; +import org.lwjgl.Version; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWFramebufferSizeCallback; +import org.lwjgl.glfw.GLFWImage; +import org.lwjgl.glfw.GLFWVidMode; +import org.lwjgl.glfw.GLFWWindowFocusCallback; +import org.lwjgl.glfw.GLFWWindowSizeCallback; +import org.lwjgl.system.Platform; /** * A wrapper class over the GLFW framework in LWJGL 3. @@ -79,66 +82,99 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { private static final Logger LOGGER = Logger.getLogger(LwjglWindow.class.getName()); private static final EnumSet SUPPORTED_TYPES = EnumSet.of( - JmeContext.Type.Display, - JmeContext.Type.Canvas, - JmeContext.Type.OffscreenSurface); + JmeContext.Type.Display, + JmeContext.Type.Canvas, + JmeContext.Type.OffscreenSurface + ); private static final Map RENDER_CONFIGS = new HashMap<>(); static { - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL30, () -> { - // Based on GLFW docs for OpenGL version below 3.2, - // GLFW_OPENGL_ANY_PROFILE must be used. - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL31, () -> { - // Based on GLFW docs for OpenGL version below 3.2, - // GLFW_OPENGL_ANY_PROFILE must be used. - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL32, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL33, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL40, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL41, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL42, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL43, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL44, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL45, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); - }); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL30, + () -> { + // Based on GLFW docs for OpenGL version below 3.2, + // GLFW_OPENGL_ANY_PROFILE must be used. + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL31, + () -> { + // Based on GLFW docs for OpenGL version below 3.2, + // GLFW_OPENGL_ANY_PROFILE must be used. + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL32, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL33, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL40, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL41, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL42, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL43, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL44, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL45, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); + } + ); } protected final AtomicBoolean needClose = new AtomicBoolean(false); protected final AtomicBoolean needRestart = new AtomicBoolean(false); private final JmeContext.Type type; - private final SafeArrayList windowSizeListeners = new SafeArrayList<>(WindowSizeListener.class); + private final SafeArrayList windowSizeListeners = new SafeArrayList<>( + WindowSizeListener.class + ); private GLFWErrorCallback errorCallback; private GLFWWindowSizeCallback windowSizeCallback; @@ -147,6 +183,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { private Thread mainThread; + private long monitor = NULL; private long window = NULL; private int frameRateLimit = -1; @@ -164,7 +201,6 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { private final Vector2f oldScale = new Vector2f(1, 1); public LwjglWindow(final JmeContext.Type type) { - if (!SUPPORTED_TYPES.contains(type)) { throw new IllegalArgumentException("Unsupported type '" + type.name() + "' provided"); } @@ -228,13 +264,16 @@ public void restart() { * @param settings the settings to apply when creating the context. */ protected void createContext(final AppSettings settings) { - glfwSetErrorCallback(errorCallback = new GLFWErrorCallback() { - @Override - public void invoke(int error, long description) { - final String message = GLFWErrorCallback.getDescription(description); - listener.handleError(message, new Exception(message)); - } - }); + glfwSetErrorCallback( + errorCallback = + new GLFWErrorCallback() { + @Override + public void invoke(int error, long description) { + final String message = GLFWErrorCallback.getDescription(description); + listener.handleError(message, new Exception(message)); + } + } + ); if (!glfwInit()) { throw new IllegalStateException("Unable to initialize GLFW"); @@ -247,12 +286,18 @@ public void invoke(int error, long description) { glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - RENDER_CONFIGS.computeIfAbsent(renderer, s -> () -> { - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - }).run(); + RENDER_CONFIGS + .computeIfAbsent( + renderer, + s -> + () -> { + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + } + ) + .run(); if (settings.getBoolean("RendererDebug")) { glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); @@ -268,8 +313,14 @@ public void invoke(int error, long description) { glfwWindowHint(GLFW_STENCIL_BITS, settings.getStencilBits()); glfwWindowHint(GLFW_SAMPLES, settings.getSamples()); glfwWindowHint(GLFW_STEREO, settings.useStereo3D() ? GLFW_TRUE : GLFW_FALSE); - glfwWindowHint(GLFW_REFRESH_RATE, settings.getFrequency()<=0?GLFW_DONT_CARE:settings.getFrequency()); - glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, settings.isUseRetinaFrameBuffer() ? GLFW_TRUE : GLFW_FALSE); + glfwWindowHint( + GLFW_REFRESH_RATE, + settings.getFrequency() <= 0 ? GLFW_DONT_CARE : settings.getFrequency() + ); + glfwWindowHint( + GLFW_COCOA_RETINA_FRAMEBUFFER, + settings.isUseRetinaFrameBuffer() ? GLFW_TRUE : GLFW_FALSE + ); if (settings.getBitsPerPixel() == 24) { glfwWindowHint(GLFW_RED_BITS, 8); @@ -283,51 +334,65 @@ public void invoke(int error, long description) { glfwWindowHint(GLFW_ALPHA_BITS, settings.getAlphaBits()); - // TODO: Add support for monitor selection - long monitor = NULL; + // long monitor = NULL; + /** + * Let's grab the display selected, if not found it will return + * primaryMonitor. if not full screen just use primary display data. + */ if (settings.isFullscreen()) { + monitor = getDisplay(settings.getDisplay()); + } else { monitor = glfwGetPrimaryMonitor(); } - final GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + final GLFWVidMode videoMode = glfwGetVideoMode(monitor); int requestWidth = settings.getWindowWidth(); int requestHeight = settings.getWindowHeight(); if (requestWidth <= 0 || requestHeight <= 0) { requestWidth = videoMode.width(); requestHeight = videoMode.height(); } - window = glfwCreateWindow(requestWidth, requestHeight, settings.getTitle(), monitor, NULL); + + // Lets use the monitor selected from AppSettings if FullScreen is + // set. + if (settings.isFullscreen()) window = + glfwCreateWindow(requestWidth, requestHeight, settings.getTitle(), monitor, NULL); else window = + glfwCreateWindow(requestWidth, requestHeight, settings.getTitle(), NULL, NULL); + if (window == NULL) { throw new RuntimeException("Failed to create the GLFW window"); } - glfwSetWindowFocusCallback(window, windowFocusCallback = new GLFWWindowFocusCallback() { - - @Override - public void invoke(final long window, final boolean focus) { - if (wasActive != focus) { - if (!wasActive) { - listener.gainFocus(); - timer.reset(); - } else { - listener.loseFocus(); + glfwSetWindowFocusCallback( + window, + windowFocusCallback = + new GLFWWindowFocusCallback() { + @Override + public void invoke(final long window, final boolean focus) { + if (wasActive != focus) { + if (!wasActive) { + listener.gainFocus(); + timer.reset(); + } else { + listener.loseFocus(); + } + wasActive = !wasActive; + } } - wasActive = !wasActive; } - } - }); + ); if (!settings.isFullscreen()) { if (settings.getCenterWindow()) { // Center the window - glfwSetWindowPos(window, - (videoMode.width() - requestWidth) / 2, - (videoMode.height() - requestHeight) / 2); + glfwSetWindowPos( + window, + (videoMode.width() - requestWidth) / 2, + (videoMode.height() - requestHeight) / 2 + ); } else { - glfwSetWindowPos(window, - settings.getWindowXPosition(), - settings.getWindowYPosition()); + glfwSetWindowPos(window, settings.getWindowXPosition(), settings.getWindowYPosition()); } } @@ -347,24 +412,30 @@ public void invoke(final long window, final boolean focus) { // HACK: the framebuffer seems to be initialized with the wrong size // on some HiDPI platforms until glfwPollEvents is called 2 or 3 times for (int i = 0; i < 4; i++) glfwPollEvents(); - - // Windows resize callback - glfwSetWindowSizeCallback(window, windowSizeCallback = new GLFWWindowSizeCallback() { - @Override - public void invoke(final long window, final int width, final int height) { - updateSizes(); - } - }); + // Windows resize callback + glfwSetWindowSizeCallback( + window, + windowSizeCallback = + new GLFWWindowSizeCallback() { + @Override + public void invoke(final long window, final int width, final int height) { + updateSizes(); + } + } + ); // Add a framebuffer resize callback which delegates to the listener - glfwSetFramebufferSizeCallback(window, framebufferSizeCallback = new GLFWFramebufferSizeCallback() { - - @Override - public void invoke(final long window, final int width, final int height) { - updateSizes(); - } - }); + glfwSetFramebufferSizeCallback( + window, + framebufferSizeCallback = + new GLFWFramebufferSizeCallback() { + @Override + public void invoke(final long window, final int width, final int height) { + updateSizes(); + } + } + ); allowSwapBuffers = settings.isSwapBuffers(); @@ -382,8 +453,7 @@ private void updateSizes() { glfwGetWindowSize(window, width, height); int windowWidth = width[0] < 1 ? 1 : width[0]; int windowHeight = height[0] < 1 ? 1 : height[0]; - if (settings.getWindowWidth() != windowWidth - || settings.getWindowHeight() != windowHeight) { + if (settings.getWindowWidth() != windowWidth || settings.getWindowHeight() != windowHeight) { settings.setWindowSize(windowWidth, windowHeight); for (WindowSizeListener wsListener : windowSizeListeners.getArray()) { wsListener.onWindowSizeChanged(windowWidth, windowHeight); @@ -393,8 +463,7 @@ private void updateSizes() { glfwGetFramebufferSize(window, width, height); int framebufferWidth = width[0]; int framebufferHeight = height[0]; - if (framebufferWidth != oldFramebufferWidth - || framebufferHeight != oldFramebufferHeight) { + if (framebufferWidth != oldFramebufferWidth || framebufferHeight != oldFramebufferHeight) { settings.setResolution(framebufferWidth, framebufferHeight); listener.reshape(framebufferWidth, framebufferHeight); @@ -421,14 +490,12 @@ protected void showWindow() { * @param settings settings for getting the icons */ protected void setWindowIcon(final AppSettings settings) { - final Object[] icons = settings.getIcons(); if (icons == null) return; final GLFWImage[] images = imagesToGLFWImages(icons); try (final GLFWImage.Buffer iconSet = GLFWImage.malloc(images.length)) { - for (int i = images.length - 1; i >= 0; i--) { final GLFWImage image = images[i]; iconSet.put(i, image); @@ -442,7 +509,6 @@ protected void setWindowIcon(final AppSettings settings) { * Convert array of images to array of {@link GLFWImage}. */ private GLFWImage[] imagesToGLFWImages(final Object[] images) { - final GLFWImage[] out = new GLFWImage[images.length]; for (int i = 0; i < images.length; i++) { @@ -457,10 +523,12 @@ private GLFWImage[] imagesToGLFWImages(final Object[] images) { * Convert the {@link BufferedImage} to the {@link GLFWImage}. */ private GLFWImage imageToGLFWImage(BufferedImage image) { - if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) { - - final BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); + final BufferedImage convertedImage = new BufferedImage( + image.getWidth(), + image.getHeight(), + BufferedImage.TYPE_INT_ARGB_PRE + ); final Graphics2D graphics = convertedImage.createGraphics(); final int targetWidth = image.getWidth(); @@ -502,7 +570,6 @@ protected void destroyContext() { } if (errorCallback != null) { - // We need to specifically set this to null as we might set a new callback before we reinit GLFW glfwSetErrorCallback(null); @@ -529,7 +596,6 @@ protected void destroyContext() { glfwDestroyWindow(window); window = NULL; } - } catch (final Exception ex) { listener.handleError("Failed to destroy context", ex); } @@ -557,7 +623,6 @@ public void create(boolean waitFor) { waitFor(true); } } - } /** @@ -569,14 +634,16 @@ protected boolean initInThread() { try { if (!JmeSystem.isLowPermissions()) { // Enable uncaught exception handler only for current thread - Thread.currentThread().setUncaughtExceptionHandler((thread, thrown) -> { - listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown); - if (needClose.get()) { - // listener.handleError() has requested the - // context to close. Satisfy request. - deinitInThread(); - } - }); + Thread + .currentThread() + .setUncaughtExceptionHandler((thread, 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 NanoTimer(); @@ -610,7 +677,6 @@ protected boolean initInThread() { return true; } - /** * execute one iteration of the render loop in the OpenGL thread */ @@ -624,7 +690,6 @@ protected void runLoop() { throw new IllegalStateException(); } - listener.update(); // All this does is call glfwSwapBuffers(). @@ -701,8 +766,9 @@ protected void deinitInThread() { @Override public void run() { if (listener == null) { - throw new IllegalStateException("SystemListener is not set on context!" - + "Must set with JmeContext.setSystemListener()."); + throw new IllegalStateException( + "SystemListener is not set on context!" + "Must set with JmeContext.setSystemListener()." + ); } LOGGER.log(Level.FINE, "Using LWJGL {0}", Version.getVersion()); @@ -713,7 +779,6 @@ public void run() { } while (true) { - runLoop(); if (needClose.get()) { @@ -853,4 +918,80 @@ public int getWindowYPosition() { int result = height[0]; return result; } + + /** + * Returns the Primary Monitor position number from the list of monitors + * returned by glfwGetPrimaryMonitor(). If primary monitor not found + * it will return -1 and report the error. + * + * @return returns the Primary Monitor Position. + */ + @Override + public int getPrimaryDisplay() { + long prim = glfwGetPrimaryMonitor(); + Displays monitors = getDisplays(); + for (int i = 0; i < monitors.size(); i++) { + long monitorI = monitors.get(i).displayID; + if (monitorI == prim) return i; + } + + LOGGER.log(Level.SEVERE, "Couldn't locate Primary Monitor in the list of Monitors."); + return -1; + } + + /** + * This routines return the display ID by position in an array of display returned + * by glfwGetMonitors(). + * + * @param pos the position of the display in the list of displays returned. + * @return return the displayID if found otherwise return Primary display + */ + private long getDisplay(int pos) { + Displays displays = getDisplays(); + if (pos < displays.size()) return displays.get(pos).displayID; + + LOGGER.log( + Level.SEVERE, + "Couldn't locate Display requested in the list of Displays. pos:" + + pos + + " size: " + + displays.size() + ); + return glfwGetPrimaryMonitor(); + } + + /** + * This returns an arraylist of all the Display returned by OpenGL get Monitor + * call. It will also has some limited information about each display, like: + * width, height and refresh rate. + * + * @return returns an ArrayList of all Display returned by glfwGetMonitors() + */ + + @Override + public Displays getDisplays() { + PointerBuffer displays = glfwGetMonitors(); + long primary = glfwGetPrimaryMonitor(); + Displays displayList = new Displays(); + + for (int i = 0; i < displays.limit(); i++) { + long monitorI = displays.get(i); + int monPos = displayList.addNewMonitor(monitorI); + //lets check if this display is the primary display. If use mark it as such. + if (primary == monitorI) displayList.setPrimaryDisplay(monPos); + + final GLFWVidMode modes = glfwGetVideoMode(monitorI); + String name = glfwGetMonitorName(monitorI); + + int width = modes.width(); + int height = modes.height(); + int rate = modes.refreshRate(); + displayList.setInfo(monPos, name, width, height, rate); + LOGGER.log( + Level.INFO, + "Display id: " + monitorI + " Resolution: " + width + " x " + height + " @ " + rate + ); + } + return displayList; + } } diff --git a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java index 5df00bf08f..d311219b29 100644 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java +++ b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java @@ -1,54 +1,68 @@ -/* - * 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.opencl.Context; - -/** - * A VR oriented LWJGL display. - * @author Daniel Johansson - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org - */ -public class LwjglDisplayVR extends LwjglWindowVR { - /** - * Create a new VR oriented LWJGL display. - */ - public LwjglDisplayVR() { - super(Type.Display); - } - - @Override - public Context getOpenCLContext() { - return null; - } -} +/* + * 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.opencl.Context; +import com.jme3.system.Displays; + +/** + * A VR oriented LWJGL display. + * @author Daniel Johansson + * @author reden - phr00t - https://github.com/phr00t + * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org + */ +public class LwjglDisplayVR extends LwjglWindowVR { + + /** + * Create a new VR oriented LWJGL display. + */ + public LwjglDisplayVR() { + super(Type.Display); + } + + @Override + public Context getOpenCLContext() { + return null; + } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } +}