Browse Source

* Ogre3D mesh.xml loader is now more resilient to certain models exported using blender2ogre
* Ogre3D dotScene loader can now load spot lights
* Added some better debugging to FBO errors
* Fix weird explosion in TestWalkingChar
* Added additional "canvas torture methods" in TestCanvas
* Several fixes to canvas:
- Issue when size becomes 0, 0
- Freeze if no framerate limit is imposed and canvas is closed

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8210 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

sha..rd 14 years ago
parent
commit
6d728615a3

+ 4 - 5
engine/src/core/com/jme3/util/xml/SAXUtil.java

@@ -102,12 +102,11 @@ public final class SAXUtil {
     public static boolean parseBool(String bool, boolean def) throws SAXException{
         if (bool == null || bool.equals(""))
             return def;
-        else if (bool.equals("false"))
-            return false;
-        else if (bool.equals("true"))
-            return true;
         else
-            throw new SAXException("Expected a boolean, got'"+bool+"'");
+            return Boolean.valueOf(bool); 
+        //else
+        //else
+        //    throw new SAXException("Expected a boolean, got'"+bool+"'");
     }
 
     public static String parseString(String str, String def){

+ 115 - 26
engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java

@@ -90,6 +90,7 @@ import jme3tools.converters.MipMapGenerator;
 import org.lwjgl.opengl.ARBDrawBuffers;
 //import org.lwjgl.opengl.ARBDrawInstanced;
 import org.lwjgl.opengl.ARBDrawInstanced;
+import org.lwjgl.opengl.ARBFramebufferObject;
 import org.lwjgl.opengl.ARBMultisample;
 import org.lwjgl.opengl.ContextCapabilities;
 import org.lwjgl.opengl.EXTTextureArray;
@@ -427,14 +428,17 @@ public class LwjglRenderer implements Renderer {
     }
 
     public void resetGLObjects() {
+        logger.log(Level.INFO, "Reseting objects and invalidating state");
         objManager.resetObjects();
         statistics.clearMemory();
         invalidateState();
     }
 
     public void cleanup() {
+        logger.log(Level.INFO, "Deleting objects and invalidating state");
         objManager.deleteAllObjects(this);
         statistics.clearMemory();
+        invalidateState();
     }
 
     private void checkCap(Caps cap) {
@@ -537,8 +541,8 @@ public class LwjglRenderer implements Renderer {
 
         if (state.isPointSprite() && !context.pointSprite) {
             // Only enable/disable sprite
-            if (context.boundTextures[0] != null) {
-                if (context.boundTextureUnit != 0) {
+            if (context.boundTextures[0] != null){
+                if (context.boundTextureUnit != 0){
                     glActiveTexture(GL_TEXTURE0);
                     context.boundTextureUnit = 0;
                 }
@@ -547,8 +551,8 @@ public class LwjglRenderer implements Renderer {
             }
             context.pointSprite = true;
         } else if (!state.isPointSprite() && context.pointSprite) {
-            if (context.boundTextures[0] != null) {
-                if (context.boundTextureUnit != 0) {
+            if (context.boundTextures[0] != null){
+                if (context.boundTextureUnit != 0){
                     glActiveTexture(GL_TEXTURE0);
                     context.boundTextureUnit = 0;
                 }
@@ -960,7 +964,7 @@ public class LwjglRenderer implements Renderer {
             }
 
             source.setId(id);
-        } else {
+        }else{
             throw new RendererException("Cannot recompile shader source");
         }
 
@@ -1277,7 +1281,86 @@ public class LwjglRenderer implements Renderer {
             // TODO: support non-blit copies?
         }
     }
+    
+    private String getTargetBufferName(int buffer){
+        switch (buffer){
+            case GL_NONE: return "NONE";
+            case GL_FRONT: return "GL_FRONT";
+            case GL_BACK: return "GL_BACK";
+            default:
+                if ( buffer >= GL_COLOR_ATTACHMENT0_EXT
+                  && buffer <= GL_COLOR_ATTACHMENT15_EXT){
+                    return "GL_COLOR_ATTACHMENT" + 
+                                (buffer - GL_COLOR_ATTACHMENT0_EXT);
+                }else{
+                    return "UNKNOWN? " + buffer;
+                }
+        }
+    }
 
+    private void printRealRenderBufferInfo(FrameBuffer fb, RenderBuffer rb, String name){
+        System.out.println("== Renderbuffer " + name + " ==");
+        System.out.println("RB ID: " + rb.getId());
+        System.out.println("Is proper? " + glIsRenderbufferEXT(rb.getId()));
+        
+        int attachment = convertAttachmentSlot(rb.getSlot());
+        
+        int type = glGetFramebufferAttachmentParameterEXT(GL_DRAW_FRAMEBUFFER_EXT,
+                                                          attachment,
+                                                          GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT);
+        
+        int rbName = glGetFramebufferAttachmentParameterEXT(GL_DRAW_FRAMEBUFFER_EXT,
+                                                            attachment,
+                                                            GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT);
+        
+        switch (type){
+            case GL_NONE:
+                System.out.println("Type: None");
+                return; // note: return from method as other queries will be invalid
+            case GL_TEXTURE:
+                System.out.println("Type: Texture");
+                break;
+            case GL_RENDERBUFFER_EXT:
+                System.out.println("Type: Buffer");
+                System.out.println("RB ID: " + rbName);
+                break;
+        }
+        
+        
+        
+    }
+    
+    private void printRealFrameBufferInfo(FrameBuffer fb) {
+        boolean doubleBuffer = glGetBoolean(GL_DOUBLEBUFFER);
+        String drawBuf = getTargetBufferName(glGetInteger(GL_DRAW_BUFFER));
+        String readBuf = getTargetBufferName(glGetInteger(GL_READ_BUFFER));
+        
+        int fbId = fb.getId();
+        int curDrawBinding = glGetInteger(ARBFramebufferObject.GL_DRAW_FRAMEBUFFER_BINDING);
+        int curReadBinding = glGetInteger(ARBFramebufferObject.GL_READ_FRAMEBUFFER_BINDING);
+    
+        System.out.println("=== OpenGL FBO State ===");
+        System.out.println("Context doublebuffered? " + doubleBuffer);
+        System.out.println("FBO ID: " + fbId);
+        System.out.println("Is proper? " + glIsFramebufferEXT(fbId));
+        System.out.println("Is bound to draw? " + (fbId == curDrawBinding));
+        System.out.println("Is bound to read? " + (fbId == curReadBinding));
+        System.out.println("Draw buffer: " + drawBuf);
+        System.out.println("Read buffer: " + readBuf);
+        
+        if (context.boundFBO != fbId){
+            glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fbId);
+            context.boundFBO = fbId;
+        }
+        
+        if (fb.getDepthBuffer() != null){
+            printRealRenderBufferInfo(fb, fb.getDepthBuffer(), "Depth");
+        }
+        for (int i = 0; i < fb.getNumColorBuffers(); i++){
+            printRealRenderBufferInfo(fb, fb.getColorBuffer(i), "Color" + i);
+        }
+    }
+    
     private void checkFrameBufferError() {
         int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
         switch (status) {
@@ -1290,7 +1373,7 @@ public class LwjglRenderer implements Renderer {
             case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
                 throw new IllegalStateException("Framebuffer has erronous attachment.");
             case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
-                throw new IllegalStateException("Framebuffer is missing required attachment.");
+                throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached.");
             case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
                 throw new IllegalStateException("Framebuffer attachments must have same dimensions.");
             case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
@@ -1487,6 +1570,11 @@ public class LwjglRenderer implements Renderer {
 
             lastFb = null;
         } else {
+            if (fb.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null){
+                throw new IllegalArgumentException("The framebuffer: " + fb + 
+                                                   "\nDoesn't have any color/depth buffers");
+            }
+            
             if (fb.isUpdateNeeded()) {
                 updateFrameBuffer(fb);
             }
@@ -1544,13 +1632,14 @@ public class LwjglRenderer implements Renderer {
             assert fb.getId() >= 0;
             assert context.boundFBO == fb.getId();
             lastFb = fb;
-
-            try {
-                checkFrameBufferError();
-            } catch (IllegalStateException ex) {
-                logger.log(Level.SEVERE, "Problem FBO:\n{0}", fb);
-                throw ex;
-            }
+        }
+        
+        try {
+            checkFrameBufferError();
+        } catch (IllegalStateException ex) {
+            logger.log(Level.SEVERE, "=== jMonkeyEngine FBO State ===\n{0}", fb);
+            printRealFrameBufferInfo(fb);
+            throw ex;
         }
     }
 
@@ -1952,7 +2041,7 @@ public class LwjglRenderer implements Renderer {
             objManager.registerForCleanup(vb);
 
             //statistics.onNewVertexBuffer();
-
+            
             created = true;
         }
 
@@ -1964,7 +2053,7 @@ public class LwjglRenderer implements Renderer {
                 glBindBuffer(target, bufId);
                 context.boundElementArrayVBO = bufId;
                 //statistics.onVertexBufferUse(vb, true);
-            } else {
+            }else{
                 //statistics.onVertexBufferUse(vb, false);
             }
         } else {
@@ -1973,7 +2062,7 @@ public class LwjglRenderer implements Renderer {
                 glBindBuffer(target, bufId);
                 context.boundArrayVBO = bufId;
                 //statistics.onVertexBufferUse(vb, true);
-            } else {
+            }else{
                 //statistics.onVertexBufferUse(vb, false);
             }
         }
@@ -2082,7 +2171,7 @@ public class LwjglRenderer implements Renderer {
             intBuf1.position(0).limit(1);
             glDeleteBuffers(intBuf1);
             vb.resetObject();
-
+            
             //statistics.onDeleteVertexBuffer();
         }
     }
@@ -2142,7 +2231,7 @@ public class LwjglRenderer implements Renderer {
                     glBindBuffer(GL_ARRAY_BUFFER, bufId);
                     context.boundArrayVBO = bufId;
                     //statistics.onVertexBufferUse(vb, true);
-                } else {
+                }else{
                     //statistics.onVertexBufferUse(vb, false);
                 }
 
@@ -2189,7 +2278,7 @@ public class LwjglRenderer implements Renderer {
             glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufId);
             context.boundElementArrayVBO = bufId;
             //statistics.onVertexBufferUse(indexBuf, true);
-        } else {
+        }else{
             //statistics.onVertexBufferUse(indexBuf, true);
         }
 
@@ -2314,9 +2403,9 @@ public class LwjglRenderer implements Renderer {
     }
 
     private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
-        if (mesh.getId() == -1) {
+        if (mesh.getId() == -1){
             updateVertexArray(mesh);
-        } else {
+        }else{
             // TODO: Check if it was updated
         }
 
@@ -2359,7 +2448,7 @@ public class LwjglRenderer implements Renderer {
         }
         //for (Entry<VertexBuffer> entry : buffers) {
         //     VertexBuffer vb = entry.getValue();
-        for (int i = 0; i < buffersList.size(); i++) {
+        for (int i = 0; i < buffersList.size(); i++){
             VertexBuffer vb = buffersList.get(i);
 
             if (vb.getBufferType() == Type.InterleavedData
@@ -2391,10 +2480,10 @@ public class LwjglRenderer implements Renderer {
             return;
         }
 
-        if (context.pointSprite && mesh.getMode() != Mode.Points) {
+        if (context.pointSprite && mesh.getMode() != Mode.Points){
             // XXX: Hack, disable point sprite mode if mesh not in point mode
-            if (context.boundTextures[0] != null) {
-                if (context.boundTextureUnit != 0) {
+            if (context.boundTextures[0] != null){
+                if (context.boundTextureUnit != 0){
                     glActiveTexture(GL_TEXTURE0);
                     context.boundTextureUnit = 0;
                 }
@@ -2417,7 +2506,7 @@ public class LwjglRenderer implements Renderer {
 //        if (GLContext.getCapabilities().GL_ARB_vertex_array_object){
 //            renderMeshVertexArray(mesh, lod, count);
 //        }else{
-        renderMeshDefault(mesh, lod, count);
+            renderMeshDefault(mesh, lod, count);
 //        }
     }
 }

+ 141 - 85
engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java

@@ -38,13 +38,11 @@ import com.jme3.system.JmeContext.Type;
 import com.jme3.system.JmeSystem;
 import com.jme3.system.JmeSystem.Platform;
 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;
 import javax.swing.SwingUtilities;
 import org.lwjgl.LWJGLException;
+import org.lwjgl.LWJGLUtil;
 import org.lwjgl.input.Keyboard;
 import org.lwjgl.input.Mouse;
 import org.lwjgl.opengl.Display;
@@ -53,14 +51,23 @@ import org.lwjgl.opengl.PixelFormat;
 
 public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext {
 
+    protected static final int TASK_NOTHING = 0,
+                               TASK_DESTROY_DISPLAY = 1,
+                               TASK_CREATE_DISPLAY = 2,
+                               TASK_COMPLETE = 3;
+    
+//    protected static final boolean USE_SHARED_CONTEXT =
+//                Boolean.parseBoolean(System.getProperty("jme3.canvas.sharedctx", "true"));
+    
+    protected static final boolean USE_SHARED_CONTEXT = false;
+    
     private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName());
     private Canvas canvas;
     private int width;
     private int height;
 
-    private final AtomicBoolean needRestoreCanvas = new AtomicBoolean(false);
-    private final AtomicBoolean needDestroyCanvas = new AtomicBoolean(false);
-    private final CyclicBarrier actionRequiredBarrier = new CyclicBarrier(2);
+    private final Object taskLock = new Object();
+    private int desiredTask = TASK_NOTHING;
 
     private Thread renderThread;
     private boolean runningFirstTime = true;
@@ -95,11 +102,19 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
                 return;
             }
 
-            logger.log(Level.INFO, "EDT: Notifying OGL that canvas is visible..");
-            needRestoreCanvas.set(true);
-
-            // NOTE: no need to wait for OGL to initialize the canvas,
-            // it can happen at any time.
+            logger.log(Level.INFO, "EDT: Telling OGL to create display ..");
+            synchronized (taskLock){
+                desiredTask = TASK_CREATE_DISPLAY;
+//                while (desiredTask != TASK_COMPLETE){
+//                    try {
+//                        taskLock.wait();
+//                    } catch (InterruptedException ex) {
+//                        return;
+//                    }
+//                }
+//                desiredTask = TASK_NOTHING;
+            }
+//            logger.log(Level.INFO, "EDT: OGL has created the display");
         }
 
         @Override
@@ -112,18 +127,19 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
 
             // We must tell GL context to shutdown and wait for it to
             // shutdown, otherwise, issues will occur.
-            logger.log(Level.INFO, "EDT: Notifying OGL that canvas is about to become invisible..");
-            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: Telling OGL to destroy display ..");
+            synchronized (taskLock){
+                desiredTask = TASK_DESTROY_DISPLAY;
+                while (desiredTask != TASK_COMPLETE){
+                    try {
+                        taskLock.wait();
+                    } catch (InterruptedException ex){
+                        super.removeNotify();
+                        return;
+                    }
+                }
+                desiredTask = TASK_NOTHING;
             }
-
-            // Reset barrier for future use
-            actionRequiredBarrier.reset();
             
             logger.log(Level.INFO, "EDT: Acknowledged receipt of canvas death");
             // GL context is dead at this point
@@ -168,37 +184,45 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
     public Canvas getCanvas(){
         return canvas;
     }
-
+    
     @Override
     protected void runLoop(){
-        if (needDestroyCanvas.getAndSet(false)){
-            // Destroy canvas
-            logger.log(Level.INFO, "OGL: Received destroy request! Complying..");
-            try {
-                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 (desiredTask != TASK_NOTHING){
+            synchronized (taskLock){
+                switch (desiredTask){
+                    case TASK_CREATE_DISPLAY:
+                        logger.log(Level.INFO, "OGL: Creating display ..");
+                        restoreCanvas();
+                        listener.gainFocus();
+                        desiredTask = TASK_NOTHING;
+                        break;
+                    case TASK_DESTROY_DISPLAY:
+                        logger.log(Level.INFO, "OGL: Destroying display ..");
+                        listener.loseFocus();
+                        pauseCanvas();
+                        break;
                 }
+                desiredTask = TASK_COMPLETE;
+                taskLock.notifyAll();
             }
-        }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);
+        if (renderable.get()){
+            int newWidth = Math.max(canvas.getWidth(), 1);
+            int newHeight = Math.max(canvas.getHeight(), 1);
+            if (width != newWidth || height != newHeight){
+                width = newWidth;
+                height = newHeight;
+                if (listener != null){
+                    listener.reshape(width, height);
+                }
+            }
+        }else{
+            if (frameRate <= 0){
+                // NOTE: MUST be done otherwise 
+                // Windows OS will freeze
+                Display.sync(30);
+            }
         }
         
         super.runLoop();
@@ -218,8 +242,6 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
             Keyboard.destroy();
         }
 
-        logger.log(Level.INFO, "OGL: Canvas will become invisible! Destroying ..");
-        
         renderable.set(false);
         destroyContext();
     }
@@ -237,7 +259,7 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
             }
         }
         
-        logger.log(Level.INFO, "OGL: Creating display..");
+        logger.log(Level.INFO, "OGL: Creating display context ..");
 
         // Set renderable to true, since canvas is now displayable.
         renderable.set(true);
@@ -306,7 +328,27 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
         
         if (pbuffer == null) {
             pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null);
+            pbuffer.makeCurrent();
             logger.log(Level.INFO, "OGL: Pbuffer has been created");
+            
+            // Any created objects are no longer valid
+            if (!runningFirstTime){
+                renderer.resetGLObjects();
+            }
+        }
+        
+        pbuffer.makeCurrent();
+        if (!pbuffer.isCurrent()){
+            throw new LWJGLException("Pbuffer cannot be made current");
+        }
+    }
+    
+    protected void destroyPbuffer(){
+        if (pbuffer != null){
+            if (!pbuffer.isBufferLost()){
+                pbuffer.destroy();
+            }
+            pbuffer = null;
         }
     }
     
@@ -317,25 +359,9 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
      */
     protected void destroyContext(){
         try {
-            // The canvas is no longer visible,
-            // but the context thread is still running.
-            if (!needClose.get()){
-                // MUST make sure there's still a context current here ..
-                // Display is dead, make pbuffer available to the system
-                makePbufferAvailable();
-
-                // pbuffer is now available, make it current
-                pbuffer.makeCurrent();
-                
-                // invalidate the state so renderer can resume operation
-                renderer.invalidateState();
-            }else{
-                // The context thread is no longer running.
-                // Destroy pbuffer.
-                if (pbuffer != null && !pbuffer.isBufferLost()){
-                    pbuffer.destroy();
-                    pbuffer = null;
-                }
+            // invalidate the state so renderer can resume operation
+            if (!USE_SHARED_CONTEXT){
+                renderer.cleanup();
             }
             
             if (Display.isCreated()){
@@ -353,21 +379,35 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
                     Keyboard.destroy();
                 }
 
-                try {
+                //try {
                     // NOTE: On Windows XP, not calling setParent(null)
                     // freezes the application.
                     // On Mac it freezes the application.
                     // On Linux it fixes a crash with X Window System.
                     if (JmeSystem.getPlatform() == Platform.Windows32
                      || JmeSystem.getPlatform() == Platform.Windows64){
-                        Display.setParent(null);
+                        //Display.setParent(null);
                     }
-                } catch (LWJGLException ex) {
-                    logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex);
-                }
+                //} catch (LWJGLException ex) {
+                //    logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex);
+                //}
 
                 Display.destroy();
             }
+            
+            // The canvas is no longer visible,
+            // but the context thread is still running.
+            if (!needClose.get()){
+                // MUST make sure there's still a context current here ..
+                // Display is dead, make pbuffer available to the system
+                makePbufferAvailable();
+                
+                renderer.invalidateState();
+            }else{
+                // The context thread is no longer running.
+                // Destroy pbuffer.
+                destroyPbuffer();
+            }
         } catch (LWJGLException ex) {
             listener.handleError("Failed make pbuffer available", ex);
         }
@@ -385,28 +425,44 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
         frameRate = settings.getFrameRate();
         
         try {
-            // First create the pbuffer, if it is needed.
-            makePbufferAvailable();
-
             if (renderable.get()){
+                if (!runningFirstTime){
+                    // because the display is a different opengl context
+                    // must reset the context state.
+                    if (!USE_SHARED_CONTEXT){
+                        renderer.cleanup();
+                    }
+                }
+                
                 // if the pbuffer is currently active, 
                 // make sure to deactivate it
-                if (pbuffer.isCurrent()){
-                    pbuffer.releaseContext();
+                destroyPbuffer();
+                
+                if (Keyboard.isCreated()){
+                    Keyboard.destroy();
                 }
-
+                
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException ex) {
+                }
+                
                 Display.setVSyncEnabled(settings.isVSync());
                 Display.setParent(canvas);
-                Display.create(acquirePixelFormat(false), pbuffer);
                 
-                // because the display is a different opengl context
-                // must reset the context state.
+                if (USE_SHARED_CONTEXT){
+                    Display.create(acquirePixelFormat(false), pbuffer);
+                }else{
+                    Display.create(acquirePixelFormat(false));
+                }
+                
                 renderer.invalidateState();
             }else{
-                pbuffer.makeCurrent();
+                // First create the pbuffer, if it is needed.
+                makePbufferAvailable();
             }
-            // At this point, the OpenGL context is active.
 
+            // At this point, the OpenGL context is active.
             if (runningFirstTime){
                 // THIS is the part that creates the renderer.
                 // It must always be called, now that we have the pbuffer workaround.

+ 105 - 47
engine/src/ogre/com/jme3/scene/plugins/ogre/SceneLoader.java

@@ -41,6 +41,8 @@ import com.jme3.asset.AssetNotFoundException;
 import com.jme3.light.DirectionalLight;
 import com.jme3.light.Light;
 import com.jme3.light.PointLight;
+import com.jme3.light.SpotLight;
+import com.jme3.math.FastMath;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.Node;
@@ -104,16 +106,22 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
         light = null;
     }
 
+    private void checkTopNode(String topNode) throws SAXException{
+        if (!elementStack.peek().equals(topNode)){
+            throw new SAXException("dotScene parse error: Expected parent node to be " + topNode);
+        }
+    }
+    
     private Quaternion parseQuat(Attributes attribs) throws SAXException{
         if (attribs.getValue("x") != null){
             // defined as quaternion
-            // qx, qy, qz, qw defined
             float x = parseFloat(attribs.getValue("x"));
             float y = parseFloat(attribs.getValue("y"));
             float z = parseFloat(attribs.getValue("z"));
             float w = parseFloat(attribs.getValue("w"));
             return new Quaternion(x,y,z,w);
         }else if (attribs.getValue("qx") != null){
+            // defined as quaternion with prefix "q"
             float x = parseFloat(attribs.getValue("qx"));
             float y = parseFloat(attribs.getValue("qy"));
             float z = parseFloat(attribs.getValue("qz"));
@@ -129,6 +137,7 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
             q.fromAngleAxis(angle, new Vector3f(axisX, axisY, axisZ));
             return q;
         }else{
+            // defines as 3 angles along XYZ axes
             float angleX = parseFloat(attribs.getValue("angleX"));
             float angleY = parseFloat(attribs.getValue("angleY"));
             float angleZ = parseFloat(attribs.getValue("angleZ"));
@@ -139,19 +148,22 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
     }
 
     private void parseLightNormal(Attributes attribs) throws SAXException {
-        assert elementStack.peek().equals("light");
+        checkTopNode("light");
         
         // SpotLight will be supporting a direction-normal, too.
         if (light instanceof DirectionalLight)
             ((DirectionalLight) light).setDirection(parseVector3(attribs));
+        else if (light instanceof SpotLight){
+            ((SpotLight) light).setDirection(parseVector3(attribs));
+        }
     }
 
     private void parseLightAttenuation(Attributes attribs) throws SAXException {
-        // NOTE: Only radius is supported atm ( for pointlights only, since there are no spotlights, yet).
-        assert elementStack.peek().equals("light");
+        // NOTE: Derives range based on "linear" if it is used solely
+        // for the attenuation. Otherwise derives it from "range"
+        checkTopNode("light");
 
-        // SpotLight will be supporting a direction-normal, too.
-        if (light instanceof PointLight){
+        if (light instanceof PointLight || light instanceof SpotLight){
             float range = parseFloat(attribs.getValue("range"));
             float constant = parseFloat(attribs.getValue("constant"));
             float linear = parseFloat(attribs.getValue("linear"));
@@ -165,15 +177,36 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
             if (constant == 1 && quadratic == 0 && linear > 0){
                 range = 1f / linear;
             }
-            ((PointLight) light).setRadius(range);
+            
+            if (light instanceof PointLight){
+                ((PointLight) light).setRadius(range);
+            }else{
+                ((SpotLight)light).setSpotRange(range);
+            }
         }
-
     }
 
+    private void parseLightSpotLightRange(Attributes attribs) throws SAXException{
+        checkTopNode("light");
+        
+        float outer = SAXUtil.parseFloat(attribs.getValue("outer"));
+        float inner = SAXUtil.parseFloat(attribs.getValue("inner"));
+        
+        if (!(light instanceof SpotLight)){
+            throw new SAXException("dotScene parse error: spotLightRange "
+                    + "can only appear under 'spot' light elements");
+        }
+        
+        SpotLight sl = (SpotLight) light;
+        sl.setSpotInnerAngle(inner * 0.5f);
+        sl.setSpotOuterAngle(outer * 0.5f);
+    }
+    
     private void parseLight(Attributes attribs) throws SAXException {
-        assert node != null;
-        assert node.getParent() != null;
-        assert elementStack.peek().equals("node");
+        if (node == null || node.getParent() == null)
+            throw new SAXException("dotScene parse error: light can only appear under a node");
+        
+        checkTopNode("node");
         
         String lightType = parseString(attribs.getValue("type"), "point");
         if(lightType.equals("point")) {
@@ -182,10 +215,8 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
             light = new DirectionalLight();
             // Assuming "normal" property is not provided
             ((DirectionalLight)light).setDirection(Vector3f.UNIT_Z);
-        } else if(lightType.equals("spotLight")) {
-            // TODO: SpotLight class.
-            logger.warning("No SpotLight class atm, using Pointlight instead.");
-            light = new PointLight();
+        } else if(lightType.equals("spotLight") || lightType.equals("spot")) {
+            light = new SpotLight();
         } else {
             logger.log(Level.WARNING, "No matching jME3 LightType found for OGRE LightType: {0}", lightType);
         }
@@ -203,14 +234,19 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
     @Override
     public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException{
         if (qName.equals("scene")){
-            assert elementStack.size() == 0;
+            if (elementStack.size() != 0){
+                throw new SAXException("dotScene parse error: 'scene' element must be the root XML element");
+            }
+            
             String version = attribs.getValue("formatVersion");
-            if (version == null || !version.equals("1.0.0"))
+            if (version == null && !version.equals("1.0.0") && !version.equals("1.0.1"))
                 logger.log(Level.WARNING, "Unrecognized version number"
                         + " in dotScene file: {0}", version);
             
         }else if (qName.equals("nodes")){
-            assert root == null;
+            if (root != null){
+                throw new SAXException("dotScene parse error: nodes element was specified twice");
+            }
             if (sceneName == null)
                 root = new Node("OgreDotScene"+(++sceneIdx));
             else
@@ -218,22 +254,31 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
             
             node = root;
         }else if (qName.equals("externals")){
-            assert elementStack.peek().equals("scene");
-
+            checkTopNode("scene");
+            // Not loaded currently
         }else if (qName.equals("item")){
-            assert elementStack.peek().equals("externals");
+            checkTopNode("externals");
         }else if (qName.equals("file")){
-            assert elementStack.peek().equals("item");
-            String matFile = folderName+attribs.getValue("name");
-            try {
-                materialList = (MaterialList) assetManager.loadAsset(new OgreMaterialKey(matFile));
-            } catch (AssetNotFoundException ex){
-                materialList = null;
-                logger.log(Level.WARNING, "Cannot locate material file: {0}", matFile);
-            }
+            checkTopNode("item");
+            
+            // XXX: Currently material file name is based
+            // on the scene's filename. THIS IS NOT CORRECT.
+            // To solve, port SceneLoader to use DOM instead of SAX
+            
+            //String matFile = folderName+attribs.getValue("name");
+            //try {
+            //    materialList = (MaterialList) assetManager.loadAsset(new OgreMaterialKey(matFile));
+            //} catch (AssetNotFoundException ex){
+            //    materialList = null;
+            //    logger.log(Level.WARNING, "Cannot locate material file: {0}", matFile);
+            //}
         }else if (qName.equals("node")){
             String curElement = elementStack.peek();
-            assert curElement.equals("nodes") || curElement.equals("node");
+            if (!curElement.equals("node") && !curElement.equals("nodes")){
+                throw new SAXException("dotScene parse error: "
+                        + "node element can only appear under 'node' or 'nodes'");
+            }
+            
             String name = attribs.getValue("name");
             if (name == null)
                 name = "OgreNode-" + (++nodeIdx);
@@ -259,7 +304,8 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
                 }
             }
         }else if (qName.equals("entity")){
-            assert elementStack.peek().equals("node");
+            checkTopNode("node");
+            
             String name = attribs.getValue("name");
             if (name == null)
                 name = "OgreEntity-" + (++nodeIdx);
@@ -267,32 +313,31 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
                 name += "-entity";
 
             String meshFile = attribs.getValue("meshFile");
-            if (meshFile == null)
+            if (meshFile == null) {
                 throw new SAXException("Required attribute 'meshFile' missing for 'entity' node");
+            }
 
+            // TODO: Not currently used
             String materialName = attribs.getValue("materialName");
 
-            // NOTE: append "xml" since its assumed mesh filse are binary in dotScene
-            if (folderName != null)
+            if (folderName != null) {
                 meshFile = folderName + meshFile;
+            }
             
+            // NOTE: append "xml" since its assumed mesh files are binary in dotScene
             meshFile += ".xml";
             
             entityNode = new Node(name);
             OgreMeshKey key = new OgreMeshKey(meshFile, materialList);
-            Spatial ogreMesh = 
-                    (Spatial) assetManager.loadAsset(key);
-            //TODO:workaround for meshxml / mesh.xml
-            if(ogreMesh==null){
-                meshFile = folderName + attribs.getValue("meshFile") + "xml";
-                key = new OgreMeshKey(meshFile, materialList);
-                ogreMesh = (Spatial) assetManager.loadAsset(key);
-            }
+            Spatial ogreMesh = assetManager.loadModel(key);
+            
             entityNode.attachChild(ogreMesh);
             node.attachChild(entityNode);
             node = null;
         }else if (qName.equals("position")){
-            node.setLocalTranslation(SAXUtil.parseVector3(attribs));
+            if (elementStack.peek().equals("node")){
+                node.setLocalTranslation(SAXUtil.parseVector3(attribs));
+            }
         }else if (qName.equals("quaternion") || qName.equals("rotation")){
             node.setLocalRotation(parseQuat(attribs));
         }else if (qName.equals("scale")){
@@ -305,19 +350,22 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
                     light.setColor(parseColor(attribs));
                 }
             }else{
-                assert elementStack.peek().equals("environment");
+                checkTopNode("environment");
             }
-        } else if (qName.equals("normal")) {
+        } else if (qName.equals("normal") || qName.equals("direction")) {
+            checkTopNode("light");
             parseLightNormal(attribs);
         } else if (qName.equals("lightAttenuation")) {
             parseLightAttenuation(attribs);
+        } else if (qName.equals("spotLightRange") || qName.equals("lightRange")) {
+            parseLightSpotLightRange(attribs);
         }
 
         elementStack.push(qName);
     }
 
     @Override
-    public void endElement(String uri, String name, String qName) {
+    public void endElement(String uri, String name, String qName) throws SAXException {
         if (qName.equals("node")){
             node = node.getParent();
         }else if (qName.equals("nodes")){
@@ -339,11 +387,21 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
                     PointLight pl = (PointLight) light;
                     Vector3f pos = node.getWorldTranslation();
                     pl.setPosition(pos);
+                }else if (light instanceof SpotLight){
+                    SpotLight sl = (SpotLight) light;
+                    
+                    Vector3f pos = node.getWorldTranslation();
+                    sl.setPosition(pos);
+                    
+                    Quaternion q = node.getWorldRotation();
+                    Vector3f dir = sl.getDirection();
+                    q.multLocal(dir);
+                    sl.setDirection(dir);
                 }
             }
             light = null;
         }
-        assert elementStack.peek().equals(qName);
+        checkTopNode(qName);
         elementStack.pop();
     }
 

+ 1 - 1
engine/src/test/jme3test/app/TestBareBonesApp.java

@@ -78,7 +78,7 @@ public class TestBareBonesApp extends Application {
         boxGeom.updateGeometricState();
 
         // render the viewports
-        renderManager.render(tpf, true);
+        renderManager.render(tpf, context.isRenderable());
     }
 
     @Override

+ 1 - 1
engine/src/test/jme3test/app/state/TestAppStates.java

@@ -88,7 +88,7 @@ public class TestAppStates extends Application {
         stateManager.render(renderManager);
 
         // render the viewports
-        renderManager.render(tpf, true);
+        renderManager.render(tpf, context.isRenderable());
     }
 
     @Override

+ 119 - 44
engine/src/test/jme3test/awt/TestCanvas.java

@@ -37,7 +37,10 @@ import com.jme3.app.SimpleApplication;
 import com.jme3.system.AppSettings;
 import com.jme3.system.JmeCanvasContext;
 import com.jme3.util.JmeFormatter;
+import java.awt.BorderLayout;
 import java.awt.Canvas;
+import java.awt.Container;
+import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.WindowAdapter;
@@ -50,8 +53,11 @@ import javax.swing.JFrame;
 import javax.swing.JMenu;
 import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
+import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
+import javax.swing.JTabbedPane;
 import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
 
 public class TestCanvas {
 
@@ -59,87 +65,149 @@ public class TestCanvas {
     private static Canvas canvas;
     private static Application app;
     private static JFrame frame;
-    private static final String appClass = "jme3test.post.TestMultiplesFilters";
-
-    private static void createFrame(){
-        frame = new JFrame("Test");
-        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
-        frame.addWindowListener(new WindowAdapter(){
-            @Override
-            public void windowClosed(WindowEvent e) {
-                app.stop();
-            }
-        });
+    private static Container canvasPanel1, canvasPanel2;
+    private static Container currentPanel;
+    private static JTabbedPane tabbedPane;
+    private static final String appClass = "jme3test.post.TestRenderToTexture";
 
+    private static void createTabs(){
+        tabbedPane = new JTabbedPane();
+        
+        canvasPanel1 = new JPanel();
+        canvasPanel1.setLayout(new BorderLayout());
+        tabbedPane.addTab("jME3 Canvas 1", canvasPanel1);
+        
+        canvasPanel2 = new JPanel();
+        canvasPanel2.setLayout(new BorderLayout());
+        tabbedPane.addTab("jME3 Canvas 2", canvasPanel2);
+        
+        frame.getContentPane().add(tabbedPane);
+        
+        currentPanel = canvasPanel1;
+    }
+    
+    private static void createMenu(){
         JMenuBar menuBar = new JMenuBar();
         frame.setJMenuBar(menuBar);
 
-        JMenu menuFile = new JMenu("File");
-        menuBar.add(menuFile);
+        JMenu menuTortureMethods = new JMenu("Canvas Torture Methods");
+        menuBar.add(menuTortureMethods);
 
         final JMenuItem itemRemoveCanvas = new JMenuItem("Remove Canvas");
-        menuFile.add(itemRemoveCanvas);
+        menuTortureMethods.add(itemRemoveCanvas);
         itemRemoveCanvas.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent e) {
                 if (itemRemoveCanvas.getText().equals("Remove Canvas")){
-                    frame.getContentPane().remove(canvas);
-
-                    // force OS to repaint over canvas ..
-                    // this is needed since AWT does not handle
-                    // that when a heavy-weight component is removed.
-                    frame.setVisible(false);
-                    frame.setVisible(true);
-                    frame.requestFocus();
+                    currentPanel.remove(canvas);
 
                     itemRemoveCanvas.setText("Add Canvas");
                 }else if (itemRemoveCanvas.getText().equals("Add Canvas")){
-                    frame.getContentPane().add(canvas);
+                    currentPanel.add(canvas, BorderLayout.CENTER);
+                    
                     itemRemoveCanvas.setText("Remove Canvas");
                 }
             }
         });
-
+        
+        final JMenuItem itemHideCanvas = new JMenuItem("Hide Canvas");
+        menuTortureMethods.add(itemHideCanvas);
+        itemHideCanvas.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                if (itemHideCanvas.getText().equals("Hide Canvas")){
+                    canvas.setVisible(false);
+                    itemHideCanvas.setText("Show Canvas");
+                }else if (itemHideCanvas.getText().equals("Show Canvas")){
+                    canvas.setVisible(true);
+                    itemHideCanvas.setText("Hide Canvas");
+                }
+            }
+        });
+        
+        final JMenuItem itemSwitchTab = new JMenuItem("Switch to tab #2");
+        menuTortureMethods.add(itemSwitchTab);
+        itemSwitchTab.addActionListener(new ActionListener(){
+           public void actionPerformed(ActionEvent e){
+               if (itemSwitchTab.getText().equals("Switch to tab #2")){
+                   canvasPanel1.remove(canvas);
+                   canvasPanel2.add(canvas, BorderLayout.CENTER);
+                   currentPanel = canvasPanel2;
+                   itemSwitchTab.setText("Switch to tab #1");
+               }else if (itemSwitchTab.getText().equals("Switch to tab #1")){
+                   canvasPanel2.remove(canvas);
+                   canvasPanel1.add(canvas, BorderLayout.CENTER);
+                   currentPanel = canvasPanel1;
+                   itemSwitchTab.setText("Switch to tab #2");
+               }
+           } 
+        });
+        
+        JMenuItem itemSwitchLaf = new JMenuItem("Switch Look and Feel");
+        menuTortureMethods.add(itemSwitchLaf);
+        itemSwitchLaf.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e){
+                try {
+                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+                } catch (Throwable t){
+                    t.printStackTrace();
+                }
+                SwingUtilities.updateComponentTreeUI(frame);
+                frame.pack();
+            }
+        });
+        
+        JMenuItem itemSmallSize = new JMenuItem("Set size to (0, 0)");
+        menuTortureMethods.add(itemSmallSize);
+        itemSmallSize.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e){
+                Dimension preferred = frame.getPreferredSize();
+                frame.setPreferredSize(new Dimension(0, 0));
+                frame.pack();
+                frame.setPreferredSize(preferred);
+            }
+        });
+        
         JMenuItem itemKillCanvas = new JMenuItem("Stop/Start Canvas");
-        menuFile.add(itemKillCanvas);
+        menuTortureMethods.add(itemKillCanvas);
         itemKillCanvas.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent e) {
-                frame.getContentPane().remove(canvas);
+                currentPanel.remove(canvas);
                 app.stop(true);
 
                 createCanvas(appClass);
-                frame.getContentPane().add(canvas);
+                currentPanel.add(canvas, BorderLayout.CENTER);
                 frame.pack();
                 startApp();
             }
         });
 
         JMenuItem itemExit = new JMenuItem("Exit");
-        menuFile.add(itemExit);
+        menuTortureMethods.add(itemExit);
         itemExit.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent ae) {
                 frame.dispose();
                 app.stop();
             }
         });
+    }
+    
+    private static void createFrame(){
+        frame = new JFrame("Test");
+        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+        frame.addWindowListener(new WindowAdapter(){
+            @Override
+            public void windowClosed(WindowEvent e) {
+                app.stop();
+            }
+        });
 
-        JMenu menuEdit = new JMenu("Edit");
-        menuBar.add(menuEdit);
-        JMenuItem itemDelete = new JMenuItem("Delete");
-        menuEdit.add(itemDelete);
-
-        JMenu menuView = new JMenu("View");
-        menuBar.add(menuView);
-        JMenuItem itemSetting = new JMenuItem("Settings");
-        menuView.add(itemSetting);
-
-        JMenu menuHelp = new JMenu("Help");
-        menuBar.add(menuHelp);
+        createTabs();
+        createMenu();
     }
 
     public static void createCanvas(String appClass){
         AppSettings settings = new AppSettings(true);
-        settings.setWidth( Math.max(640, frame.getContentPane().getWidth()) );
-        settings.setHeight( Math.max(480, frame.getContentPane().getHeight()) );
+        settings.setWidth(640);
+        settings.setHeight(480);
 
         try{
             Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
@@ -155,6 +223,7 @@ public class TestCanvas {
         app.setPauseOnLostFocus(false);
         app.setSettings(settings);
         app.createCanvas();
+        app.startCanvas();
 
         context = (JmeCanvasContext) app.getContext();
         canvas = context.getCanvas();
@@ -184,14 +253,20 @@ public class TestCanvas {
         Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
         Logger.getLogger("").addHandler(consoleHandler);
         
+        createCanvas(appClass);
+        
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException ex) {
+        }
+        
         SwingUtilities.invokeLater(new Runnable(){
             public void run(){
                 JPopupMenu.setDefaultLightWeightPopupEnabled(false);
 
                 createFrame();
-                createCanvas(appClass);
                 
-                frame.getContentPane().add(canvas);
+                currentPanel.add(canvas, BorderLayout.CENTER);
                 frame.pack();
                 startApp();
                 frame.setLocationRelativeTo(null);

+ 1 - 1
engine/src/test/jme3test/bullet/TestWalkingChar.java

@@ -225,7 +225,7 @@ public class TestWalkingChar extends SimpleApplication implements ActionListener
         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
         mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
         effect.setMaterial(mat);
-        effect.setLocalScale(100);
+//        effect.setLocalScale(100);
         rootNode.attachChild(effect);
     }