From 61aea1e2c5f7f44b12456a9933547a7b909de403 Mon Sep 17 00:00:00 2001 From: "sha..rd" Date: Wed, 23 Mar 2011 03:48:37 +0000 Subject: [PATCH] * Big refactoring of LWJGL display system, mainly to support updating the main loop without a render context or input devices being available. * Added test that demonstrates above functionality, by starting Application without attaching the canvas, and then constantly attaching and detaching canvas from a frame. * Deleted deprecated methods in JmeContext * Deleted deprecated class LwjglJoyInput * Audio renderer will not attempt to initialize OpenAL twice if already initialized git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7078 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- engine/src/core/com/jme3/app/Application.java | 14 +- .../core/com/jme3/app/SimpleApplication.java | 5 +- .../core/com/jme3/renderer/RenderManager.java | 1 - .../src/core/com/jme3/system/JmeContext.java | 30 ++- .../src/core/com/jme3/system/NullContext.java | 5 + .../jme3/audio/lwjgl/LwjglAudioRenderer.java | 4 +- .../com/jme3/input/lwjgl/LwjglJoyInput.java | 181 -------------- .../com/jme3/input/lwjgl/LwjglKeyInput.java | 20 +- .../com/jme3/input/lwjgl/LwjglMouseInput.java | 24 +- .../system/lwjgl/LwjglAbstractDisplay.java | 113 ++++----- .../com/jme3/system/lwjgl/LwjglCanvas.java | 233 ++++++++---------- .../com/jme3/system/lwjgl/LwjglContext.java | 88 ++++++- .../com/jme3/system/lwjgl/LwjglDisplay.java | 92 ++++--- .../system/lwjgl/LwjglOffscreenBuffer.java | 38 +-- .../src/test/jme3test/awt/TestSafeCanvas.java | 14 +- 15 files changed, 386 insertions(+), 476 deletions(-) delete mode 100644 engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglJoyInput.java diff --git a/engine/src/core/com/jme3/app/Application.java b/engine/src/core/com/jme3/app/Application.java index 5630bca08..dcb37b6c2 100644 --- a/engine/src/core/com/jme3/app/Application.java +++ b/engine/src/core/com/jme3/app/Application.java @@ -299,13 +299,17 @@ public class Application implements SystemListener { public Camera getCamera(){ return cam; } - + + /** + * Starts the application as a display. + */ public void start(){ start(JmeContext.Type.Display); } /** - * Starts the application. Creating a display and running the main loop. + * Starts the application. Creating a rendering context and executing + * the main loop in a separate thread. */ public void start(JmeContext.Type contextType){ if (context != null && context.isCreated()){ @@ -423,6 +427,10 @@ public class Application implements SystemListener { context.destroy(false); } + /** + * Enqueues a task/callable object to execute in the jME3 + * rendering thread. + */ public Future enqueue(Callable callable) { AppTask task = new AppTask(callable); taskQueue.add(task); @@ -495,6 +503,4 @@ public class Application implements SystemListener { return viewPort; } - - } diff --git a/engine/src/core/com/jme3/app/SimpleApplication.java b/engine/src/core/com/jme3/app/SimpleApplication.java index 79c0e0728..52bf8c87a 100644 --- a/engine/src/core/com/jme3/app/SimpleApplication.java +++ b/engine/src/core/com/jme3/app/SimpleApplication.java @@ -249,7 +249,9 @@ public abstract class SimpleApplication extends Application { // render states stateManager.render(renderManager); - renderManager.render(tpf); + if (context.isRenderable()){ + renderManager.render(tpf); + } simpleRender(renderManager); stateManager.postRender(); } @@ -257,7 +259,6 @@ public abstract class SimpleApplication extends Application { public void setDisplayFps(boolean show) { showFps = show; fpsText.setCullHint(show ? CullHint.Never : CullHint.Always); - } public void setDisplayStatView(boolean show) { diff --git a/engine/src/core/com/jme3/renderer/RenderManager.java b/engine/src/core/com/jme3/renderer/RenderManager.java index 383df350c..d7f1fbf52 100644 --- a/engine/src/core/com/jme3/renderer/RenderManager.java +++ b/engine/src/core/com/jme3/renderer/RenderManager.java @@ -108,7 +108,6 @@ public class RenderManager { public RenderManager(Renderer renderer) { this.renderer = renderer; this.shader = renderer.getCaps().contains(Caps.GLSL100); - } public ViewPort getPreView(String viewName) { diff --git a/engine/src/core/com/jme3/system/JmeContext.java b/engine/src/core/com/jme3/system/JmeContext.java index 0c1d35bf7..10e18f477 100644 --- a/engine/src/core/com/jme3/system/JmeContext.java +++ b/engine/src/core/com/jme3/system/JmeContext.java @@ -48,16 +48,19 @@ public interface JmeContext { public enum Type { /** * A display can represent a windowed or a fullscreen-exclusive display. - * If windowed, the graphics are rendered to a new onscreen surface - * enclosed in a system defined by the operating system. Implementations - * are encourged to not use AWT or Swing to create the OpenGL display + * If windowed, the graphics are rendered to a new on-screen surface + * enclosed in a window defined by the operating system. Implementations + * are encouraged to not use AWT or Swing to create the OpenGL display * but rather use native operating system functions to set up a native * display with the windowing system. */ Display, /** - * + * A canvas type context makes a rendering surface available as an + * AWT {@link java.awt.Canvas} object that can be embedded in a Swing/AWT + * frame. To retrieve the Canvas object, you should cast the context + * to {@link JmeCanvasContext}. */ Canvas, @@ -140,16 +143,17 @@ public interface JmeContext { public boolean isCreated(); /** - * @param enabled If enabled, the context will automatically flush - * frames to the video card (swap buffers) after an update cycle. + * @return True if the context contains a valid render surface, + * if any of the rendering methods in {@link Renderer} are called + * while this is false, then the result is undefined. */ - public void setAutoFlushFrames(boolean enabled); + public boolean isRenderable(); /** - * Creates the context and makes it active. + * @param enabled If enabled, the context will automatically flush + * frames to the video card (swap buffers) after an update cycle. */ - @Deprecated - public void create(); + public void setAutoFlushFrames(boolean enabled); /** * Creates the context and makes it active. @@ -164,12 +168,6 @@ public interface JmeContext { */ public void restart(); - /** - * Destroys the context completely, making it inactive. - */ - @Deprecated - public void destroy(); - /** * Destroys the context completely, making it inactive. * diff --git a/engine/src/core/com/jme3/system/NullContext.java b/engine/src/core/com/jme3/system/NullContext.java index ed53b7251..10590180c 100644 --- a/engine/src/core/com/jme3/system/NullContext.java +++ b/engine/src/core/com/jme3/system/NullContext.java @@ -218,4 +218,9 @@ public class NullContext implements JmeContext, Runnable { return timer; } + public boolean isRenderable() { + return true; // Doesn't really matter if true or false. Either way + // RenderManager won't render anything. + } + } diff --git a/engine/src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java b/engine/src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java index 303f0f664..6e0f8aeee 100644 --- a/engine/src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java +++ b/engine/src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java @@ -154,7 +154,9 @@ public class LwjglAudioRenderer implements AudioRenderer, Runnable { public void initInThread(){ try{ - AL.create(); + if (!AL.isCreated()){ + AL.create(); + } }catch (OpenALException ex){ logger.log(Level.SEVERE, "Failed to load audio library", ex); audioDisabled = true; diff --git a/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglJoyInput.java b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglJoyInput.java deleted file mode 100644 index 7b22c5a11..000000000 --- a/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglJoyInput.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2009-2010 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.input.lwjgl; - -import com.jme3.input.InputManager; -import com.jme3.input.JoyInput; -import com.jme3.input.Joystick; -import com.jme3.input.RawInputListener; -import com.jme3.input.event.JoyAxisEvent; -import com.jme3.input.event.JoyButtonEvent; -import com.jme3.system.lwjgl.LwjglTimer; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.lwjgl.LWJGLException; -import org.lwjgl.Sys; -import org.lwjgl.input.Controller; -import org.lwjgl.input.Controllers; - -@Deprecated -class LwjglJoyInput implements JoyInput { - - private static final Logger logger = Logger.getLogger(LwjglKeyInput.class.getName()); - - private RawInputListener listener; - private boolean enabled = false; - - public void initialize() { - try { - Controllers.create(); - if (Controllers.getControllerCount() == 0 || !Controllers.isCreated()){ - logger.warning("Joysticks disabled."); - return; - } - logger.info("Joysticks created."); - enabled = true; - } catch (LWJGLException ex) { - logger.log(Level.SEVERE, "Failed to create joysticks", ex); - } - } - - public int getJoyCount() { - return Controllers.getControllerCount(); - } - - public String getJoyName(int joyIndex) { - return Controllers.getController(joyIndex).getName(); - } - - public int getAxesCount(int joyIndex) { - return Controllers.getController(joyIndex).getAxisCount(); - } - - public int getButtonCount(int joyIndex) { - return Controllers.getController(joyIndex).getButtonCount(); - } - - private void printController(Controller c){ - System.out.println("Name: "+c.getName()); - System.out.println("Index: "+c.getIndex()); - System.out.println("Button Count: "+c.getButtonCount()); - System.out.println("Axis Count: "+c.getAxisCount()); - - int buttons = c.getButtonCount(); - for (int b = 0; b < buttons; b++) { - System.out.println("Button " + b + " = " + c.getButtonName(b)); - } - - int axis = c.getAxisCount(); - for (int b = 0; b < axis; b++) { - System.out.println("Axis " + b + " = " + c.getAxisName(b)); - } - } - - public void update() { - if (!enabled) - return; - - Controllers.poll(); - while (Controllers.next()){ - Controller c = Controllers.getEventSource(); - if (Controllers.isEventAxis()){ - int realAxis = Controllers.getEventControlIndex(); - JoyAxisEvent evt = new JoyAxisEvent(c.getIndex(), - realAxis, - c.getAxisValue(realAxis)); - listener.onJoyAxisEvent(evt); - }else if (Controllers.isEventPovX()){ - JoyAxisEvent evt = new JoyAxisEvent(c.getIndex(), - JoyInput.AXIS_POV_X, - c.getPovX()); - listener.onJoyAxisEvent(evt); - }else if (Controllers.isEventPovY()){ - JoyAxisEvent evt = new JoyAxisEvent(c.getIndex(), - JoyInput.AXIS_POV_Y, - c.getPovY()); - listener.onJoyAxisEvent(evt); - }else if (Controllers.isEventButton()){ - int btn = Controllers.getEventControlIndex(); - JoyButtonEvent evt = new JoyButtonEvent(c.getIndex(), - btn, - c.isButtonPressed(btn)); - listener.onJoyButtonEvent(evt); - } - } - Controllers.clearEvents(); - } - - public void destroy() { - if (!enabled) - return; - - Controllers.destroy(); - logger.info("Joysticks destroyed."); - } - - public boolean isInitialized() { - if (!enabled) - return false; - - return Controllers.isCreated(); - } - - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - public long getInputTimeNanos() { - return Sys.getTime() * LwjglTimer.LWJGL_TIME_TO_NANOS; - } - - public void setJoyRumble(int joyId, float amount){ - } - - public Joystick[] loadJoysticks(InputManager inputManager) { - int count = Controllers.getControllerCount(); - Joystick[] joysticks = new Joystick[count]; - for (int i = 0; i < count; i++){ - Controller c = Controllers.getController(i); - Joystick j = new Joystick(inputManager, - this, - i, - c.getName(), - c.getButtonCount(), - c.getAxisCount(), - -1,-1); - joysticks[i] = j; - } - return joysticks; - } - -} diff --git a/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglKeyInput.java b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglKeyInput.java index 9234013b2..99c8046e6 100644 --- a/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglKeyInput.java +++ b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglKeyInput.java @@ -35,6 +35,7 @@ package com.jme3.input.lwjgl; import com.jme3.input.KeyInput; import com.jme3.input.event.KeyInputEvent; import com.jme3.input.RawInputListener; +import com.jme3.system.lwjgl.LwjglAbstractDisplay; import com.jme3.system.lwjgl.LwjglTimer; import java.util.logging.Level; import java.util.logging.Logger; @@ -46,9 +47,18 @@ public class LwjglKeyInput implements KeyInput { private static final Logger logger = Logger.getLogger(LwjglKeyInput.class.getName()); + private LwjglAbstractDisplay context; + private RawInputListener listener; + public LwjglKeyInput(LwjglAbstractDisplay context){ + this.context = context; + } + public void initialize() { + if (!context.isRenderable()) + return; + try { Keyboard.create(); Keyboard.enableRepeatEvents(true); @@ -58,15 +68,14 @@ public class LwjglKeyInput implements KeyInput { } } - public boolean isKeyDown(int key){ - return Keyboard.isKeyDown(key); - } - public int getKeyCount(){ return Keyboard.KEYBOARD_SIZE; } public void update() { + if (!context.isRenderable()) + return; + Keyboard.poll(); while (Keyboard.next()){ int keyCode = Keyboard.getEventKey(); @@ -81,6 +90,9 @@ public class LwjglKeyInput implements KeyInput { } public void destroy() { + if (!context.isRenderable()) + return; + Keyboard.destroy(); logger.info("Keyboard destroyed."); } diff --git a/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglMouseInput.java b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglMouseInput.java index 58ba58a6b..f41c5cc00 100644 --- a/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglMouseInput.java +++ b/engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglMouseInput.java @@ -36,6 +36,7 @@ import com.jme3.input.event.MouseButtonEvent; import com.jme3.input.event.MouseMotionEvent; import com.jme3.input.MouseInput; import com.jme3.input.RawInputListener; +import com.jme3.system.lwjgl.LwjglAbstractDisplay; import com.jme3.system.lwjgl.LwjglTimer; import java.util.logging.Level; import java.util.logging.Logger; @@ -48,6 +49,8 @@ public class LwjglMouseInput implements MouseInput { private static final Logger logger = Logger.getLogger(LwjglMouseInput.class.getName()); + private LwjglAbstractDisplay context; + private RawInputListener listener; private boolean supportHardwareCursor = false; @@ -55,11 +58,21 @@ public class LwjglMouseInput implements MouseInput { private int curX, curY, curWheel; + public LwjglMouseInput(LwjglAbstractDisplay context){ + this.context = context; + } + public void initialize() { + if (!context.isRenderable()) + return; + try { Mouse.create(); logger.info("Mouse created."); supportHardwareCursor = (Cursor.getCapabilities() & Cursor.CURSOR_ONE_BIT_TRANSPARENCY) != 0; + + // Recall state that was set before initialization + Mouse.setGrabbed(!cursorVisible); } catch (LWJGLException ex) { logger.log(Level.SEVERE, "Error while creating mouse", ex); } @@ -74,6 +87,9 @@ public class LwjglMouseInput implements MouseInput { } public void update() { + if (!context.isRenderable()) + return; + while (Mouse.next()){ int btn = Mouse.getEventButton(); @@ -109,13 +125,19 @@ public class LwjglMouseInput implements MouseInput { } public void destroy() { + if (!context.isRenderable()) + return; + Mouse.destroy(); logger.info("Mouse destroyed."); } public void setCursorVisible(boolean visible){ - Mouse.setGrabbed(!visible); cursorVisible = visible; + if (!context.isRenderable()) + return; + + Mouse.setGrabbed(!visible); } public void setInputListener(RawInputListener listener) { diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java index c4f7af2b4..58bb743dc 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java @@ -47,16 +47,13 @@ import java.util.logging.Logger; import org.lwjgl.LWJGLException; import org.lwjgl.Sys; import org.lwjgl.opengl.Display; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL20; -import org.lwjgl.opengl.GLContext; import org.lwjgl.opengl.OpenGLException; import org.lwjgl.opengl.Util; - public abstract class LwjglAbstractDisplay extends LwjglContext implements Runnable { - private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); + private static final Logger logger = Logger.getLogger(LwjglAbstractDisplay.class.getName()); + protected AtomicBoolean needClose = new AtomicBoolean(false); protected boolean wasActive = false; protected int frameRate = 0; @@ -84,6 +81,8 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna */ protected abstract void createContext(AppSettings settings) throws LWJGLException; + + /** * Does LWJGL display initialization in the OpenGL thread */ @@ -97,42 +96,22 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna }); } + // For canvas, this wont happen until its initialized. createContext(settings); -// String rendererStr = settings.getString("Renderer"); - - logger.info("Display created."); - logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); - - logger.log(Level.INFO, "Adapter: {0}", Display.getAdapter()); - logger.log(Level.INFO, "Driver Version: {0}", Display.getVersion()); - - String vendor = GL11.glGetString(GL11.GL_VENDOR); - logger.log(Level.INFO, "Vendor: {0}", vendor); - - String version = GL11.glGetString(GL11.GL_VERSION); - logger.log(Level.INFO, "OpenGL Version: {0}", version); + if (renderable.get()) // assumes createContext will set this flag + printContextInitInfo(); - String renderer = GL11.glGetString(GL11.GL_RENDERER); - logger.log(Level.INFO, "Renderer: {0}", renderer); - - if (GLContext.getCapabilities().OpenGL20){ - String shadingLang = GL11.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION); - logger.log(Level.INFO, "GLSL Ver: {0}", shadingLang); - } - created.set(true); } catch (Exception 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. - - if (!created.get()){ + try { if (Display.isCreated()) Display.destroy(); - - return; // if we failed to create display, do not continue + } catch (Exception ex2){ + logger.log(Level.WARNING, null, ex2); } + + listener.handleError("Failed to create display", ex); + return; // if we failed to create display, do not continue } super.internalCreate(); listener.initialize(); @@ -156,26 +135,29 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna throw new IllegalStateException(); listener.update(); - assert checkGLError(); - // calls swap buffers, etc. - try { - if (autoFlush){ - Display.update(); - }else{ - Display.processMessages(); - Thread.sleep(50); - // add a small wait - // to reduce CPU usage + if (renderable.get()){ + assert checkGLError(); + + // calls swap buffers, etc. + try { + if (autoFlush){ + Display.update(); + }else{ + Display.processMessages(); + Thread.sleep(50); + // add a small wait + // to reduce CPU usage + } + } catch (Throwable ex){ + listener.handleError("Error while swapping buffers", ex); } - } catch (Throwable ex){ - listener.handleError("Error while swapping buffers", ex); } if (frameRate > 0) Display.sync(frameRate); - if (autoFlush) + if (renderable.get() && autoFlush) renderer.onFrame(); } @@ -205,16 +187,18 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna logger.log(Level.INFO, "Using LWJGL {0}", Sys.getVersion()); initInThread(); while (true){ - if (Display.isCloseRequested()) - listener.requestClose(false); - - if (wasActive != Display.isActive()){ - if (!wasActive){ - listener.gainFocus(); - wasActive = true; - }else{ - listener.loseFocus(); - wasActive = false; + if (renderable.get()){ + if (Display.isCloseRequested()) + listener.requestClose(false); + + if (wasActive != Display.isActive()) { + if (!wasActive) { + listener.gainFocus(); + wasActive = true; + } else { + listener.loseFocus(); + wasActive = false; + } } } @@ -227,15 +211,24 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna } public JoyInput getJoyInput() { - return new JInputJoyInput(); + if (joyInput == null){ + joyInput = new JInputJoyInput(); + } + return joyInput; } public MouseInput getMouseInput() { - return new LwjglMouseInput(); + if (mouseInput == null){ + mouseInput = new LwjglMouseInput(this); + } + return mouseInput; } public KeyInput getKeyInput() { - return new LwjglKeyInput(); + if (keyInput == null){ + keyInput = new LwjglKeyInput(this); + } + return keyInput; } public void setAutoFlushFrames(boolean enabled){ diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java index 1711ac346..cea518adb 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java @@ -36,6 +36,8 @@ import com.jme3.system.AppSettings; import com.jme3.system.JmeCanvasContext; import com.jme3.system.JmeContext.Type; import java.awt.Canvas; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -54,69 +56,68 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex private int width; private int height; - private AtomicBoolean reinitReq = new AtomicBoolean(false); - private final Object reinitReqLock = new Object(); - - private AtomicBoolean reinitAuth = new AtomicBoolean(false); - private final Object reinitAuthLock = new Object(); + private final AtomicBoolean needRestoreCanvas = new AtomicBoolean(false); + private final AtomicBoolean needDestroyCanvas = new AtomicBoolean(false); + private final CyclicBarrier actionRequiredBarrier = new CyclicBarrier(2); private Thread renderThread; + private boolean runningFirstTime = true; private boolean mouseWasGrabbed = false; -// private Pbuffer dummyCtx; + private boolean mouseActive, keyboardActive, joyActive; public LwjglCanvas(){ super(); canvas = new Canvas(){ - @Override public void addNotify(){ super.addNotify(); - if (renderThread == null || renderThread.getState() == Thread.State.TERMINATED){ - if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED){ - logger.log(Level.INFO, "EDT: Creating OGL thread. Was terminated."); - }else{ - logger.log(Level.INFO, "EDT: Creating OGL thread."); - } + + if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED) + return; // already destroyed. + + if (renderThread == null){ + logger.log(Level.INFO, "EDT: Creating OGL thread."); + renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); renderThread.start(); - }else{ - if (needClose.get()) - return; + }else if (needClose.get()){ + return; + } - logger.log(Level.INFO, "EDT: Sending re-init authorization.."); + logger.log(Level.INFO, "EDT: Notifying OGL that canvas is visible.."); + needRestoreCanvas.set(true); - // reinitializing canvas - synchronized (reinitAuthLock){ - reinitAuth.set(true); - reinitAuthLock.notifyAll(); - } - } + // NOTE: no need to wait for OGL to initialize the canvas, + // it can happen at any time. } @Override public void removeNotify(){ if (needClose.get()){ - logger.log(Level.INFO, "EDT: Close requested. Not re-initing."); + logger.log(Level.INFO, "EDT: Application is stopped. Not restoring canvas."); + super.removeNotify(); return; } - // request to put context into reinit mode - // this waits until reinit is authorized - logger.log(Level.INFO, "EDT: Sending re-init request.."); - synchronized (reinitReqLock){ - reinitReq.set(true); - while (reinitReq.get()){ - try { - reinitReqLock.wait(); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, "EDT: Interrupted! ", ex); - } - } - // NOTE: reinitReq is now false. + // We must tell GL context to shutdown and wait for it to + // shutdown, otherwise, issues will occur. + logger.log(Level.INFO, "EDT: Sending destroy request.."); + needDestroyCanvas.set(true); + try { + actionRequiredBarrier.await(); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "EDT: Interrupted! ", ex); + } catch (BrokenBarrierException ex){ + logger.log(Level.SEVERE, "EDT: Broken barrier! ", ex); } - logger.log(Level.INFO, "EDT: Acknowledged receipt of re-init request!"); + logger.log(Level.INFO, "EDT: Acknowledged receipt of destroy request!"); + // GL context is dead at this point + + // Reset barrier for future use + actionRequiredBarrier.reset(); + super.removeNotify(); } }; @@ -131,6 +132,12 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex } public void create(boolean waitFor){ + if (renderThread == null){ + logger.log(Level.INFO, "MAIN: Creating OGL thread."); + + renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); + renderThread.start(); + } // do not do anything. // superclass's create() will be called at initInThread() if (waitFor) @@ -151,106 +158,64 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex @Override protected void runLoop(){ - boolean reinitNeeded; - synchronized (reinitReqLock){ - reinitNeeded = reinitReq.get(); - } - - if (reinitNeeded){ - logger.log(Level.INFO, "OGL: Re-init request received!"); - listener.loseFocus(); - - boolean mouseActive = Mouse.isCreated(); - boolean keyboardActive = Keyboard.isCreated(); - boolean joyActive = Controllers.isCreated(); - - if (mouseActive) - Mouse.destroy(); - if (keyboardActive) - Keyboard.destroy(); - if (joyActive) - Controllers.destroy(); - - pauseCanvas(); - - synchronized (reinitReqLock){ - reinitReq.set(false); - reinitReqLock.notifyAll(); - } - - // we got the reinit request, now we wait for reinit to happen.. - logger.log(Level.INFO, "OGL: Waiting for re-init authorization.."); - synchronized (reinitAuthLock){ - while (!reinitAuth.get()){ - try { - reinitAuthLock.wait(); - if (Thread.interrupted()) - throw new InterruptedException(); - } catch (InterruptedException ex) { - if (needClose.get()){ - logger.log(Level.INFO, "OGL: Re-init aborted. Closing display.."); - return; - } - - logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); - } - } - // NOTE: reinitAuth becamse true, now set it to false. - reinitAuth.set(false); - } - - logger.log(Level.INFO, "OGL: Re-init authorization received. Re-initializing.."); - restoreCanvas(); - + if (needDestroyCanvas.getAndSet(false)){ + // Destroy canvas + logger.log(Level.INFO, "OGL: Received destroy request! Complying.."); try { - if (mouseActive){ - Mouse.create(); - } - if (keyboardActive){ - Keyboard.create(); + listener.loseFocus(); + pauseCanvas(); + } finally { + try { + // Required to avoid deadlock if an exception occurs + actionRequiredBarrier.await(); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); + } catch (BrokenBarrierException ex) { + logger.log(Level.SEVERE, "OGL: Broken barrier! ", ex); } - if (joyActive){ - Controllers.create(); - } - } catch (LWJGLException ex){ - listener.handleError("Failed to re-init input", ex); } + }else if (needRestoreCanvas.getAndSet(false)){ + // Put canvas back online + logger.log(Level.INFO, "OGL: Canvas is now visible! Re-initializing.."); + restoreCanvas(); + listener.gainFocus(); } + if (width != canvas.getWidth() || height != canvas.getHeight()){ width = canvas.getWidth(); height = canvas.getHeight(); if (listener != null) listener.reshape(width, height); } + super.runLoop(); } - @Override - public void destroy(boolean waitFor){ - needClose.set(true); - if (renderThread != null && renderThread.isAlive()){ - renderThread.interrupt(); - // make sure it really does get interrupted - synchronized(reinitAuthLock){ - reinitAuthLock.notifyAll(); - } - } - if (waitFor) - waitFor(false); - } - private void pauseCanvas(){ - if (Mouse.isCreated() && Mouse.isGrabbed()){ + mouseActive = Mouse.isCreated(); + keyboardActive = Keyboard.isCreated(); + joyActive = Controllers.isCreated(); + + if (mouseActive && Mouse.isGrabbed()){ Mouse.setGrabbed(false); mouseWasGrabbed = true; } + if (mouseActive) + Mouse.destroy(); + if (keyboardActive) + Keyboard.destroy(); + if (joyActive) + Controllers.destroy(); + logger.log(Level.INFO, "OGL: Destroying display (temporarily)"); Display.destroy(); + + renderable.set(false); } /** - * Called if canvas was removed and then restored unexpectedly + * Called to restore the canvas. */ private void restoreCanvas(){ logger.log(Level.INFO, "OGL: Waiting for canvas to become displayable.."); @@ -261,8 +226,12 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); } } + renderer.resetGLObjects(); logger.log(Level.INFO, "OGL: Creating display.."); + + // Set renderable to true, since canvas is now displayable. + renderable.set(true); createContext(settings); logger.log(Level.INFO, "OGL: Waiting for display to become active.."); @@ -276,26 +245,33 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex logger.log(Level.INFO, "OGL: Display is active!"); try { - if (mouseWasGrabbed){ + if (mouseActive){ Mouse.create(); - Mouse.setGrabbed(true); - mouseWasGrabbed = false; - } - - SwingUtilities.invokeLater(new Runnable(){ - public void run(){ - canvas.requestFocus(); + if (mouseWasGrabbed){ + Mouse.setGrabbed(true); + mouseWasGrabbed = false; } - }); + } + if (keyboardActive){ + Keyboard.create(); + } + logger.log(Level.INFO, "OGL: Input has been reinitialized"); } catch (LWJGLException ex) { - logger.log(Level.SEVERE, "restoreCanvas()", ex); + logger.log(Level.SEVERE, "Failed to re-init input", ex); } - listener.gainFocus(); + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + canvas.requestFocus(); + } + }); } @Override protected void createContext(AppSettings settings) { + if (!renderable.get()) + return; + frameRate = settings.getFrameRate(); Display.setVSyncEnabled(settings.isVSync()); @@ -308,6 +284,11 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex settings.getSamples()); Display.create(pf); Display.makeCurrent(); + + if (runningFirstTime){ + initContextFirstTime(); + runningFirstTime = false; + } }catch (LWJGLException ex){ listener.handleError("Failed to parent canvas to display", ex); } diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java index b27f3f6b9..4bc0ec4af 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java @@ -32,6 +32,9 @@ package com.jme3.system.lwjgl; +import com.jme3.input.lwjgl.JInputJoyInput; +import com.jme3.input.lwjgl.LwjglKeyInput; +import com.jme3.input.lwjgl.LwjglMouseInput; import com.jme3.renderer.Renderer; import com.jme3.renderer.lwjgl.LwjglGL1Renderer; import com.jme3.renderer.lwjgl.LwjglRenderer; @@ -40,6 +43,12 @@ import com.jme3.system.SystemListener; import com.jme3.system.JmeContext; import com.jme3.system.Timer; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.opengl.ContextAttribs; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GLContext; /** @@ -47,11 +56,17 @@ import org.lwjgl.opengl.GLContext; */ public abstract class LwjglContext implements JmeContext { + private static final Logger logger = Logger.getLogger(LwjglContext.class.getName()); + protected AtomicBoolean created = new AtomicBoolean(false); + protected AtomicBoolean renderable = new AtomicBoolean(false); protected final Object createdLock = new Object(); protected AppSettings settings = new AppSettings(true); protected Renderer renderer; + protected LwjglKeyInput keyInput; + protected LwjglMouseInput mouseInput; + protected JInputJoyInput joyInput; protected Timer timer; protected SystemListener listener; @@ -59,9 +74,72 @@ public abstract class LwjglContext implements JmeContext { this.listener = listener; } + protected void printContextInitInfo(){ + logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); + + logger.log(Level.INFO, "Adapter: {0}", Display.getAdapter()); + logger.log(Level.INFO, "Driver Version: {0}", Display.getVersion()); + + String vendor = GL11.glGetString(GL11.GL_VENDOR); + logger.log(Level.INFO, "Vendor: {0}", vendor); + + String version = GL11.glGetString(GL11.GL_VERSION); + logger.log(Level.INFO, "OpenGL Version: {0}", version); + + String renderGl = GL11.glGetString(GL11.GL_RENDERER); + logger.log(Level.INFO, "Renderer: {0}", renderGl); + + if (GLContext.getCapabilities().OpenGL20){ + String shadingLang = GL11.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION); + logger.log(Level.INFO, "GLSL Ver: {0}", shadingLang); + } + } + + protected ContextAttribs createContextAttribs(){ + if (settings.getBoolean("GraphicsDebug") || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){ + ContextAttribs attr; + if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){ + attr = new ContextAttribs(3, 3); + attr = attr.withProfileCore(true).withForwardCompatible(true).withProfileCompatibility(false); + }else{ + attr = new ContextAttribs(); + } + if (settings.getBoolean("GraphicsDebug")){ + attr = attr.withDebug(true); + } + return attr; + }else{ + return null; + } + } + + protected void initContextFirstTime(){ + assert renderable.get(); + + // Init renderer + if (renderer instanceof LwjglRenderer){ + ((LwjglRenderer)renderer).initialize(); + }else if (renderer instanceof LwjglGL1Renderer){ + ((LwjglGL1Renderer)renderer).initialize(); + }else{ + assert false; + } + + // Init input + if (keyInput != null) + keyInput.initialize(); + + if (mouseInput != null) + mouseInput.initialize(); + + if (joyInput != null) + joyInput.initialize(); + } + public void internalDestroy(){ renderer = null; timer = null; + renderable.set(false); synchronized (createdLock){ created.set(false); createdLock.notifyAll(); @@ -73,10 +151,8 @@ public abstract class LwjglContext implements JmeContext { if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2) || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){ renderer = new LwjglRenderer(); - ((LwjglRenderer)renderer).initialize(); }else if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL1)){ renderer = new LwjglGL1Renderer(); - ((LwjglGL1Renderer)renderer).initialize(); }else{ throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); } @@ -84,6 +160,10 @@ public abstract class LwjglContext implements JmeContext { created.set(true); createdLock.notifyAll(); } + if (renderable.get()) + initContextFirstTime(); + else + assert getType() == Type.Canvas; } public void create(){ @@ -108,6 +188,10 @@ public abstract class LwjglContext implements JmeContext { public boolean isCreated(){ return created.get(); } + + public boolean isRenderable(){ + return renderable.get(); + } public void setSettings(AppSettings settings) { this.settings.copyFrom(settings); diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java index a50747816..2673a44cb 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java @@ -47,6 +47,7 @@ import java.util.logging.Logger; import org.lwjgl.opengl.ARBMultisample; import org.lwjgl.opengl.ContextAttribs; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GLContext; import org.lwjgl.opengl.PixelFormat; public class LwjglDisplay extends LwjglAbstractDisplay { @@ -118,65 +119,21 @@ public class LwjglDisplay extends LwjglAbstractDisplay { Display.setVSyncEnabled(settings.isVSync()); if (!created.get() || pixelFormatChanged){ - if (settings.getBoolean("GraphicsDebug") || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){ - ContextAttribs attr; - if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)){ - attr = new ContextAttribs(3, 3); - attr = attr.withProfileCore(true).withForwardCompatible(true).withProfileCompatibility(false); - }else{ - attr = new ContextAttribs(); - } - if (settings.getBoolean("GraphicsDebug")){ - attr = attr.withDebug(true); - } + ContextAttribs attr = createContextAttribs(); + if (attr != null){ Display.create(pixelFormat, attr); }else{ Display.create(pixelFormat); } + renderable.set(true); - if (pixelFormatChanged && pixelFormat.getSamples() > 1){ + if (pixelFormatChanged && pixelFormat.getSamples() > 1 + && GLContext.getCapabilities().GL_ARB_multisample){ GL11.glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); } } } - private ByteBuffer[] imagesToByteBuffers(BufferedImage[] images) { - ByteBuffer[] out = new ByteBuffer[images.length]; - for (int i = 0; i < images.length; i++) { - out[i] = imageToByteBuffer(images[i]); - } - 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); - } - - public void create(boolean waitFor){ if (created.get()){ logger.warning("create() called when display is already created!"); @@ -190,6 +147,7 @@ public class LwjglDisplay extends LwjglAbstractDisplay { @Override public void runLoop(){ + // This method is overriden to do restart if (needRestart.getAndSet(false)){ try{ createContext(settings); @@ -220,5 +178,41 @@ public class LwjglDisplay extends LwjglAbstractDisplay { if (created.get()) Display.setTitle(title); } + + private ByteBuffer[] imagesToByteBuffers(BufferedImage[] images) { + ByteBuffer[] out = new ByteBuffer[images.length]; + for (int i = 0; i < images.length; i++) { + out[i] = imageToByteBuffer(images[i]); + } + return out; + } + + private ByteBuffer imageToByteBuffer(BufferedImage image) { + if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) { + BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); + Graphics2D g = convertedImage.createGraphics(); + double width = image.getWidth() * (double) 1; + double height = image.getHeight() * (double) 1; + g.drawImage(image, (int) ((convertedImage.getWidth() - width) / 2), + (int) ((convertedImage.getHeight() - height) / 2), + (int) (width), (int) (height), null); + g.dispose(); + image = convertedImage; + } + + byte[] imageBuffer = new byte[image.getWidth() * image.getHeight() * 4]; + int counter = 0; + for (int i = 0; i < image.getHeight(); i++) { + for (int j = 0; j < image.getWidth(); j++) { + int colorSpace = image.getRGB(j, i); + imageBuffer[counter + 0] = (byte) ((colorSpace << 8) >> 24); + imageBuffer[counter + 1] = (byte) ((colorSpace << 16) >> 24); + imageBuffer[counter + 2] = (byte) ((colorSpace << 24) >> 24); + imageBuffer[counter + 3] = (byte) (colorSpace >> 24); + counter += 4; + } + } + return ByteBuffer.wrap(imageBuffer); + } } diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java index 5b833d332..b9bb69468 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java @@ -42,15 +42,11 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.lwjgl.LWJGLException; import org.lwjgl.Sys; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL20; -import org.lwjgl.opengl.GLContext; import org.lwjgl.opengl.OpenGLException; import org.lwjgl.opengl.Pbuffer; import org.lwjgl.opengl.PixelFormat; import org.lwjgl.opengl.Util; - public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { private static final Logger logger = Logger.getLogger(LwjglOffscreenBuffer.class.getName()); @@ -74,6 +70,12 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { width = settings.getWidth(); height = settings.getHeight(); try{ + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); + } + }); + //String rendererStr = settings.getString("Renderer"); // if (rendererStr.startsWith("LWJGL-OpenGL3")){ // ContextAttribs attribs; @@ -86,35 +88,15 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { // attribs.withDebug(false); // Display.create(pf, attribs); // }else{ - pbuffer = new Pbuffer(width, height, pixelFormat, null); + pbuffer = new Pbuffer(width, height, pixelFormat, null, null, createContextAttribs()); // } pbuffer.makeCurrent(); - logger.info("Offscreen buffer created."); - logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); - - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - public void uncaughtException(Thread thread, Throwable thrown) { - listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); - } - }); - - String vendor = GL11.glGetString(GL11.GL_VENDOR); - logger.log(Level.INFO, "Vendor: {0}", vendor); + renderable.set(true); - String version = GL11.glGetString(GL11.GL_VERSION); - logger.log(Level.INFO, "OpenGL Version: {0}", version); - - String renderer = GL11.glGetString(GL11.GL_RENDERER); - logger.log(Level.INFO, "Renderer: {0}", renderer); - - if (GLContext.getCapabilities().OpenGL20){ - String shadingLang = GL11.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION); - logger.log(Level.INFO, "GLSL Ver: {0}", shadingLang); - } - - created.set(true); + logger.info("Offscreen buffer created."); + printContextInitInfo(); } catch (LWJGLException ex){ listener.handleError("Failed to create display", ex); } finally { diff --git a/engine/src/test/jme3test/awt/TestSafeCanvas.java b/engine/src/test/jme3test/awt/TestSafeCanvas.java index 378d38afe..caba888c7 100644 --- a/engine/src/test/jme3test/awt/TestSafeCanvas.java +++ b/engine/src/test/jme3test/awt/TestSafeCanvas.java @@ -14,7 +14,7 @@ import javax.swing.JFrame; public class TestSafeCanvas extends SimpleApplication { - public static void main(String[] args){ + public static void main(String[] args) throws InterruptedException{ AppSettings settings = new AppSettings(true); settings.setWidth(640); settings.setHeight(480); @@ -28,6 +28,10 @@ public class TestSafeCanvas extends SimpleApplication { Canvas canvas = context.getCanvas(); canvas.setSize(settings.getWidth(), settings.getHeight()); + app.startCanvas(true); + + Thread.sleep(3000); + JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { @@ -40,6 +44,14 @@ public class TestSafeCanvas extends SimpleApplication { frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); + + Thread.sleep(3000); + + frame.getContentPane().remove(canvas); + + Thread.sleep(3000); + + frame.getContentPane().add(canvas); } @Override