Home | History | Annotate | Download | only in lwjgl
      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
     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
    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 
    150     public LwjglCanvas(){
    151         super();
    152         canvas = new GLCanvas();
    153     }
    154 
    155     @Override
    156     public Type getType() {
    157         return Type.Canvas;
    158     }
    159 
    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
    174     public void setTitle(String title) {
    175     }
    176 
    177     @Override
    178     public void restart() {
    179         frameRate = settings.getFrameRate();
    180         // TODO: Handle other cases, like change of pixel format, etc.
    181     }
    182 
    183     public Canvas getCanvas(){
    184         return canvas;
    185     }
    186 
    187     @Override
    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 
    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      */
    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      */
    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      */
    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 
    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      */
    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
    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