1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.system.lwjgl; 34 35 import com.jme3.system.AppSettings; 36 import com.jme3.system.JmeCanvasContext; 37 import com.jme3.system.JmeContext.Type; 38 import com.jme3.system.JmeSystem; 39 import com.jme3.system.Platform; 40 import java.awt.Canvas; 41 import java.util.logging.Level; 42 import java.util.logging.Logger; 43 import javax.swing.SwingUtilities; 44 import org.lwjgl.LWJGLException; 45 import org.lwjgl.input.Keyboard; 46 import org.lwjgl.input.Mouse; 47 import org.lwjgl.opengl.Display; 48 import org.lwjgl.opengl.Pbuffer; 49 import org.lwjgl.opengl.PixelFormat; 50 51 public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext { 52 53 protected static final int TASK_NOTHING = 0, 54 TASK_DESTROY_DISPLAY = 1, 55 TASK_CREATE_DISPLAY = 2, 56 TASK_COMPLETE = 3; 57 58 // protected static final boolean USE_SHARED_CONTEXT = 59 // Boolean.parseBoolean(System.getProperty("jme3.canvas.sharedctx", "true")); 60 61 protected static final boolean USE_SHARED_CONTEXT = false; 62 63 private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); 64 private Canvas canvas; 65 private int width; 66 private int height; 67 68 private final Object taskLock = new Object(); 69 private int desiredTask = TASK_NOTHING; 70 71 private Thread renderThread; 72 private boolean runningFirstTime = true; 73 private boolean mouseWasGrabbed = false; 74 75 private boolean mouseWasCreated = false; 76 private boolean keyboardWasCreated = false; 77 78 private Pbuffer pbuffer; 79 private PixelFormat pbufferFormat; 80 private PixelFormat canvasFormat; 81 82 private class GLCanvas extends Canvas { 83 @Override addNotify()84 public void addNotify(){ 85 super.addNotify(); 86 87 if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED) 88 return; // already destroyed. 89 90 if (renderThread == null){ 91 logger.log(Level.INFO, "EDT: Creating OGL thread."); 92 93 // Also set some settings on the canvas here. 94 // So we don't do it outside the AWT thread. 95 canvas.setFocusable(true); 96 canvas.setIgnoreRepaint(true); 97 98 renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); 99 renderThread.start(); 100 }else if (needClose.get()){ 101 return; 102 } 103 104 logger.log(Level.INFO, "EDT: Telling OGL to create display .."); 105 synchronized (taskLock){ 106 desiredTask = TASK_CREATE_DISPLAY; 107 // while (desiredTask != TASK_COMPLETE){ 108 // try { 109 // taskLock.wait(); 110 // } catch (InterruptedException ex) { 111 // return; 112 // } 113 // } 114 // desiredTask = TASK_NOTHING; 115 } 116 // logger.log(Level.INFO, "EDT: OGL has created the display"); 117 } 118 119 @Override removeNotify()120 public void removeNotify(){ 121 if (needClose.get()){ 122 logger.log(Level.INFO, "EDT: Application is stopped. Not restoring canvas."); 123 super.removeNotify(); 124 return; 125 } 126 127 // We must tell GL context to shutdown and wait for it to 128 // shutdown, otherwise, issues will occur. 129 logger.log(Level.INFO, "EDT: Telling OGL to destroy display .."); 130 synchronized (taskLock){ 131 desiredTask = TASK_DESTROY_DISPLAY; 132 while (desiredTask != TASK_COMPLETE){ 133 try { 134 taskLock.wait(); 135 } catch (InterruptedException ex){ 136 super.removeNotify(); 137 return; 138 } 139 } 140 desiredTask = TASK_NOTHING; 141 } 142 143 logger.log(Level.INFO, "EDT: Acknowledged receipt of canvas death"); 144 // GL context is dead at this point 145 146 super.removeNotify(); 147 } 148 } 149 LwjglCanvas()150 public LwjglCanvas(){ 151 super(); 152 canvas = new GLCanvas(); 153 } 154 155 @Override getType()156 public Type getType() { 157 return Type.Canvas; 158 } 159 create(boolean waitFor)160 public void create(boolean waitFor){ 161 if (renderThread == null){ 162 logger.log(Level.INFO, "MAIN: Creating OGL thread."); 163 164 renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); 165 renderThread.start(); 166 } 167 // do not do anything. 168 // superclass's create() will be called at initInThread() 169 if (waitFor) 170 waitFor(true); 171 } 172 173 @Override setTitle(String title)174 public void setTitle(String title) { 175 } 176 177 @Override restart()178 public void restart() { 179 frameRate = settings.getFrameRate(); 180 // TODO: Handle other cases, like change of pixel format, etc. 181 } 182 getCanvas()183 public Canvas getCanvas(){ 184 return canvas; 185 } 186 187 @Override runLoop()188 protected void runLoop(){ 189 if (desiredTask != TASK_NOTHING){ 190 synchronized (taskLock){ 191 switch (desiredTask){ 192 case TASK_CREATE_DISPLAY: 193 logger.log(Level.INFO, "OGL: Creating display .."); 194 restoreCanvas(); 195 listener.gainFocus(); 196 desiredTask = TASK_NOTHING; 197 break; 198 case TASK_DESTROY_DISPLAY: 199 logger.log(Level.INFO, "OGL: Destroying display .."); 200 listener.loseFocus(); 201 pauseCanvas(); 202 break; 203 } 204 desiredTask = TASK_COMPLETE; 205 taskLock.notifyAll(); 206 } 207 } 208 209 if (renderable.get()){ 210 int newWidth = Math.max(canvas.getWidth(), 1); 211 int newHeight = Math.max(canvas.getHeight(), 1); 212 if (width != newWidth || height != newHeight){ 213 width = newWidth; 214 height = newHeight; 215 if (listener != null){ 216 listener.reshape(width, height); 217 } 218 } 219 }else{ 220 if (frameRate <= 0){ 221 // NOTE: MUST be done otherwise 222 // Windows OS will freeze 223 Display.sync(30); 224 } 225 } 226 227 super.runLoop(); 228 } 229 pauseCanvas()230 private void pauseCanvas(){ 231 if (Mouse.isCreated()){ 232 if (Mouse.isGrabbed()){ 233 Mouse.setGrabbed(false); 234 mouseWasGrabbed = true; 235 } 236 mouseWasCreated = true; 237 Mouse.destroy(); 238 } 239 if (Keyboard.isCreated()){ 240 keyboardWasCreated = true; 241 Keyboard.destroy(); 242 } 243 244 renderable.set(false); 245 destroyContext(); 246 } 247 248 /** 249 * Called to restore the canvas. 250 */ restoreCanvas()251 private void restoreCanvas(){ 252 logger.log(Level.INFO, "OGL: Waiting for canvas to become displayable.."); 253 while (!canvas.isDisplayable()){ 254 try { 255 Thread.sleep(10); 256 } catch (InterruptedException ex) { 257 logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); 258 } 259 } 260 261 logger.log(Level.INFO, "OGL: Creating display context .."); 262 263 // Set renderable to true, since canvas is now displayable. 264 renderable.set(true); 265 createContext(settings); 266 267 logger.log(Level.INFO, "OGL: Display is active!"); 268 269 try { 270 if (mouseWasCreated){ 271 Mouse.create(); 272 if (mouseWasGrabbed){ 273 Mouse.setGrabbed(true); 274 mouseWasGrabbed = false; 275 } 276 } 277 if (keyboardWasCreated){ 278 Keyboard.create(); 279 keyboardWasCreated = false; 280 } 281 } catch (LWJGLException ex){ 282 logger.log(Level.SEVERE, "Encountered exception when restoring input", ex); 283 } 284 285 SwingUtilities.invokeLater(new Runnable(){ 286 public void run(){ 287 canvas.requestFocus(); 288 } 289 }); 290 } 291 292 /** 293 * It seems it is best to use one pixel format for all shared contexts. 294 * @see <a href="http://developer.apple.com/library/mac/#qa/qa1248/_index.html">http://developer.apple.com/library/mac/#qa/qa1248/_index.html</a> 295 */ acquirePixelFormat(boolean forPbuffer)296 protected PixelFormat acquirePixelFormat(boolean forPbuffer){ 297 if (forPbuffer){ 298 // Use 0 samples for pbuffer format, prevents 299 // crashes on bad drivers 300 if (pbufferFormat == null){ 301 pbufferFormat = new PixelFormat(settings.getBitsPerPixel(), 302 0, 303 settings.getDepthBits(), 304 settings.getStencilBits(), 305 0); 306 } 307 return pbufferFormat; 308 }else{ 309 if (canvasFormat == null){ 310 int samples = 0; 311 if (settings.getSamples() > 1){ 312 samples = settings.getSamples(); 313 } 314 canvasFormat = new PixelFormat(settings.getBitsPerPixel(), 315 0, 316 settings.getDepthBits(), 317 settings.getStencilBits(), 318 samples); 319 } 320 return canvasFormat; 321 } 322 } 323 324 /** 325 * Makes sure the pbuffer is available and ready for use 326 */ makePbufferAvailable()327 protected void makePbufferAvailable() throws LWJGLException{ 328 if (pbuffer != null && pbuffer.isBufferLost()){ 329 logger.log(Level.WARNING, "PBuffer was lost!"); 330 pbuffer.destroy(); 331 pbuffer = null; 332 } 333 334 if (pbuffer == null) { 335 pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null); 336 pbuffer.makeCurrent(); 337 logger.log(Level.INFO, "OGL: Pbuffer has been created"); 338 339 // Any created objects are no longer valid 340 if (!runningFirstTime){ 341 renderer.resetGLObjects(); 342 } 343 } 344 345 pbuffer.makeCurrent(); 346 if (!pbuffer.isCurrent()){ 347 throw new LWJGLException("Pbuffer cannot be made current"); 348 } 349 } 350 destroyPbuffer()351 protected void destroyPbuffer(){ 352 if (pbuffer != null){ 353 if (!pbuffer.isBufferLost()){ 354 pbuffer.destroy(); 355 } 356 pbuffer = null; 357 } 358 } 359 360 /** 361 * This is called: 362 * 1) When the context thread ends 363 * 2) Any time the canvas becomes non-displayable 364 */ destroyContext()365 protected void destroyContext(){ 366 try { 367 // invalidate the state so renderer can resume operation 368 if (!USE_SHARED_CONTEXT){ 369 renderer.cleanup(); 370 } 371 372 if (Display.isCreated()){ 373 /* FIXES: 374 * org.lwjgl.LWJGLException: X Error 375 * BadWindow (invalid Window parameter) request_code: 2 minor_code: 0 376 * 377 * Destroying keyboard early prevents the error above, triggered 378 * by destroying keyboard in by Display.destroy() or Display.setParent(null). 379 * Therefore Keyboard.destroy() should precede any of these calls. 380 */ 381 if (Keyboard.isCreated()){ 382 // Should only happen if called in 383 // LwjglAbstractDisplay.deinitInThread(). 384 Keyboard.destroy(); 385 } 386 387 //try { 388 // NOTE: On Windows XP, not calling setParent(null) 389 // freezes the application. 390 // On Mac it freezes the application. 391 // On Linux it fixes a crash with X Window System. 392 if (JmeSystem.getPlatform() == Platform.Windows32 393 || JmeSystem.getPlatform() == Platform.Windows64){ 394 //Display.setParent(null); 395 } 396 //} catch (LWJGLException ex) { 397 // logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex); 398 //} 399 400 Display.destroy(); 401 } 402 403 // The canvas is no longer visible, 404 // but the context thread is still running. 405 if (!needClose.get()){ 406 // MUST make sure there's still a context current here .. 407 // Display is dead, make pbuffer available to the system 408 makePbufferAvailable(); 409 410 renderer.invalidateState(); 411 }else{ 412 // The context thread is no longer running. 413 // Destroy pbuffer. 414 destroyPbuffer(); 415 } 416 } catch (LWJGLException ex) { 417 listener.handleError("Failed make pbuffer available", ex); 418 } 419 } 420 421 /** 422 * This is called: 423 * 1) When the context thread starts 424 * 2) Any time the canvas becomes displayable again. 425 */ 426 @Override createContext(AppSettings settings)427 protected void createContext(AppSettings settings) { 428 // In case canvas is not visible, we still take framerate 429 // from settings to prevent "100% CPU usage" 430 frameRate = settings.getFrameRate(); 431 432 try { 433 if (renderable.get()){ 434 if (!runningFirstTime){ 435 // because the display is a different opengl context 436 // must reset the context state. 437 if (!USE_SHARED_CONTEXT){ 438 renderer.cleanup(); 439 } 440 } 441 442 // if the pbuffer is currently active, 443 // make sure to deactivate it 444 destroyPbuffer(); 445 446 if (Keyboard.isCreated()){ 447 Keyboard.destroy(); 448 } 449 450 try { 451 Thread.sleep(1000); 452 } catch (InterruptedException ex) { 453 } 454 455 Display.setVSyncEnabled(settings.isVSync()); 456 Display.setParent(canvas); 457 458 if (USE_SHARED_CONTEXT){ 459 Display.create(acquirePixelFormat(false), pbuffer); 460 }else{ 461 Display.create(acquirePixelFormat(false)); 462 } 463 464 renderer.invalidateState(); 465 }else{ 466 // First create the pbuffer, if it is needed. 467 makePbufferAvailable(); 468 } 469 470 // At this point, the OpenGL context is active. 471 if (runningFirstTime){ 472 // THIS is the part that creates the renderer. 473 // It must always be called, now that we have the pbuffer workaround. 474 initContextFirstTime(); 475 runningFirstTime = false; 476 } 477 } catch (LWJGLException ex) { 478 listener.handleError("Failed to initialize OpenGL context", ex); 479 // TODO: Fix deadlock that happens after the error (throw runtime exception?) 480 } 481 } 482 } 483