浏览代码

* 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

sha..rd 14 年之前
父节点
当前提交
61aea1e2c5

+ 10 - 4
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 <V> Future<V> enqueue(Callable<V> callable) {
         AppTask<V> task = new AppTask<V>(callable);
         taskQueue.add(task);
@@ -495,6 +503,4 @@ public class Application implements SystemListener {
         return viewPort;
     }
 
-    
-
 }

+ 3 - 2
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) {

+ 0 - 1
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) {

+ 14 - 16
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 <code>false</code>, 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.
      *

+ 5 - 0
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. 
+    }
+
 }

+ 3 - 1
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;

+ 0 - 181
engine/src/lwjgl-ogl/com/jme3/input/lwjgl/LwjglJoyInput.java

@@ -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;
-    }
-
-}

+ 16 - 4
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.");
     }

+ 23 - 1
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) {

+ 53 - 60
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){

+ 107 - 126
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);
         }

+ 86 - 2
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);

+ 43 - 49
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);
+    }
 
 }

+ 10 - 28
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 {

+ 13 - 1
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