From 0d0454f248ae9d7a97b33b3364c93f46d718587d Mon Sep 17 00:00:00 2001 From: "sha..rd" Date: Sat, 30 Apr 2011 23:10:25 +0000 Subject: [PATCH] * Canvas is now using pbuffer workaround, allowing renderer to acquire renderer capabilities even if the canvas is not visible yet. * Handling of context destruction is now handled individually for displays and canvases. For canvas, this allows it to destroy the pbuffer in addition to the display. * VertexBuffer now has better detection for data size changes, might prevent GL errors in certain cases. NOTE: VertexBuffer.updateData() is generally more stable than VertexBuffer.setUpdateNeeded(). Refrain from using setUpdateNeeded() .. its an internal call anyway. Using it directly could cause GL errors. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7374 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../src/core/com/jme3/scene/VertexBuffer.java | 17 +- .../com/jme3/scene/debug/WireFrustum.java | 3 +- .../jme3/renderer/lwjgl/LwjglRenderer.java | 2 +- .../system/lwjgl/LwjglAbstractDisplay.java | 30 +-- .../com/jme3/system/lwjgl/LwjglCanvas.java | 219 ++++++++++++------ .../com/jme3/system/lwjgl/LwjglContext.java | 2 - .../com/jme3/system/lwjgl/LwjglDisplay.java | 10 + .../com/jme3/system/lwjgl/LwjglTimer.java | 3 +- .../test/com/jme3/math/TrigonometryTest.java | 4 - 9 files changed, 183 insertions(+), 107 deletions(-) diff --git a/engine/src/core/com/jme3/scene/VertexBuffer.java b/engine/src/core/com/jme3/scene/VertexBuffer.java index d3c280ca9..e55b7a0f7 100644 --- a/engine/src/core/com/jme3/scene/VertexBuffer.java +++ b/engine/src/core/com/jme3/scene/VertexBuffer.java @@ -225,6 +225,7 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable { } protected int offset = 0; + protected int lastLimit = 0; protected int stride = 0; protected int components = 0; @@ -233,7 +234,6 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable { */ protected transient int componentsLength = 0; protected Buffer data = null; - protected transient ByteBuffer mappedData; protected Usage usage; protected Type bufType; protected Format format; @@ -303,14 +303,6 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable { return data; } - public ByteBuffer getMappedData() { - return mappedData; - } - - public void setMappedData(ByteBuffer mappedData) { - this.mappedData = mappedData; - } - /** * @return The usage of this buffer. See {@link Usage} for more * information. @@ -403,6 +395,7 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable { this.usage = usage; this.format = format; this.componentsLength = components * format.getComponentSize(); + this.lastLimit = data.limit(); setUpdateNeeded(); } @@ -424,8 +417,9 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable { } // will force renderer to call glBufferData again - if (this.data.capacity() != data.capacity()){ + if (this.data.getClass() != data.getClass() || data.limit() != lastLimit){ dataSizeChanged = true; + lastLimit = data.limit(); } this.data = data; setUpdateNeeded(); @@ -696,10 +690,13 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable { } } + @Override public VertexBuffer clone(){ // NOTE: Superclass GLObject automatically creates shallow clone // e.g re-use ID. VertexBuffer vb = (VertexBuffer) super.clone(); + vb.handleRef = new Object(); + vb.id = -1; if (data != null) vb.updateData(BufferUtils.clone(data)); diff --git a/engine/src/core/com/jme3/scene/debug/WireFrustum.java b/engine/src/core/com/jme3/scene/debug/WireFrustum.java index 280475033..7a86d905e 100644 --- a/engine/src/core/com/jme3/scene/debug/WireFrustum.java +++ b/engine/src/core/com/jme3/scene/debug/WireFrustum.java @@ -73,7 +73,6 @@ public class WireFrustum extends Mesh { return; } - FloatBuffer b = BufferUtils.createFloatBuffer(points); FloatBuffer a = (FloatBuffer) vb.getData(); b.rewind(); @@ -81,7 +80,7 @@ public class WireFrustum extends Mesh { a.put(b); a.rewind(); - vb.setUpdateNeeded(); + vb.updateData(a); updateBound(); } diff --git a/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java index 7553005a3..1d3563b0b 100644 --- a/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java +++ b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java @@ -417,9 +417,9 @@ public class LwjglRenderer implements Renderer { public void resetGLObjects() { objManager.resetObjects(); statistics.clearMemory(); + context.reset(); boundShader = null; lastFb = null; - context.reset(); } public void cleanup() { diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java index b4e0d5cc6..74a1c5130 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglAbstractDisplay.java @@ -81,7 +81,10 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna */ protected abstract void createContext(AppSettings settings) throws LWJGLException; - + /** + * Destroy the context. + */ + protected abstract void destroyContext(); /** * Does LWJGL display initialization in the OpenGL thread @@ -97,10 +100,12 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna }); } - // For canvas, this wont happen until its initialized. + // For canvas, this will create a pbuffer, + // allowing us to query information. + // When the canvas context becomes available, it will + // be replaced seamlessly. createContext(settings); - if (renderable.get()) // assumes createContext will set this flag - printContextInitInfo(); + printContextInitInfo(); created.set(true); } catch (Exception ex){ @@ -137,6 +142,9 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna listener.update(); + // All this does is call swap buffers + // If the canvas is not active, there's no need to waste time + // doing that .. if (renderable.get()){ assert checkGLError(); @@ -158,22 +166,16 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna if (frameRate > 0) Display.sync(frameRate); - if (renderable.get() && autoFlush) - renderer.onFrame(); + // Subclasses just call GLObjectManager clean up objects here + // it is safe .. for now. + renderer.onFrame(); } /** * De-initialize in the OpenGL thread. */ protected void deinitInThread(){ - if (Display.isCreated()){ - renderer.cleanup(); - Display.destroy(); - }else{ - // If using canvas temporary closing, the display would - // be closed at this point - renderer.resetGLObjects(); - } + destroyContext(); listener.destroy(); logger.info("Display destroyed."); diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java index 94d00fcf0..43d128e49 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglCanvas.java @@ -43,10 +43,10 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; import org.lwjgl.LWJGLException; -import org.lwjgl.input.Controllers; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.Pbuffer; import org.lwjgl.opengl.PixelFormat; public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext { @@ -63,67 +63,72 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex private Thread renderThread; private boolean runningFirstTime = true; private boolean mouseWasGrabbed = false; - private boolean mouseActive, keyboardActive, joyActive; + private boolean mouseActive, keyboardActive; - public LwjglCanvas(){ - super(); + private Pbuffer pbuffer; - canvas = new Canvas(){ - @Override - public void addNotify(){ - super.addNotify(); - - 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; - } + private class GLCanvas extends Canvas { + @Override + public void addNotify(){ + super.addNotify(); + + if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED) + return; // already destroyed. - logger.log(Level.INFO, "EDT: Notifying OGL that canvas is visible.."); - needRestoreCanvas.set(true); + if (renderThread == null){ + logger.log(Level.INFO, "EDT: Creating OGL thread."); - // NOTE: no need to wait for OGL to initialize the canvas, - // it can happen at any time. + // Also set some settings on the canvas here. + // So we don't do it outside the AWT thread. + canvas.setFocusable(true); + canvas.setIgnoreRepaint(true); + + renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread"); + renderThread.start(); + }else if (needClose.get()){ + return; } - @Override - public void removeNotify(){ - if (needClose.get()){ - logger.log(Level.INFO, "EDT: Application is stopped. Not restoring canvas."); - super.removeNotify(); - return; - } - - // 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 destroy request!"); - // GL context is dead at this point + logger.log(Level.INFO, "EDT: Notifying OGL that canvas is visible.."); + needRestoreCanvas.set(true); - // Reset barrier for future use - actionRequiredBarrier.reset(); + // 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: Application is stopped. Not restoring canvas."); super.removeNotify(); + return; } - }; - - canvas.setFocusable(true); - canvas.setIgnoreRepaint(true); + + // 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: Acknowledged receipt of canvas death"); + // GL context is dead at this point + + // Reset barrier for future use + actionRequiredBarrier.reset(); + + super.removeNotify(); + } + } + + public LwjglCanvas(){ + super(); + canvas = new GLCanvas(); } @Override @@ -150,6 +155,8 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex @Override public void restart() { + frameRate = settings.getFrameRate(); + // TODO: Handle other cases, like change of pixel format, etc. } public Canvas getCanvas(){ @@ -194,7 +201,6 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex private void pauseCanvas(){ mouseActive = Mouse.isCreated(); keyboardActive = Keyboard.isCreated(); - joyActive = Controllers.isCreated(); if (mouseActive && Mouse.isGrabbed()){ Mouse.setGrabbed(false); @@ -205,13 +211,11 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex Mouse.destroy(); if (keyboardActive) Keyboard.destroy(); - if (joyActive) - Controllers.destroy(); - - logger.log(Level.INFO, "OGL: Destroying display (temporarily)"); - Display.destroy(); + logger.log(Level.INFO, "OGL: Canvas will become invisible! Destroying .."); + renderable.set(false); + destroyContext(); } /** @@ -234,9 +238,10 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex createContext(settings); // must call after createContext, as renderer might be null - renderer.resetGLObjects(); +// renderer.resetGLObjects(); logger.log(Level.INFO, "OGL: Waiting for display to become active.."); + // NOTE: A deadlock will happen here if createContext had an exception while (!Display.isCreated()){ try { Thread.sleep(10); @@ -269,33 +274,101 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex }); } + /** + * Makes sure the pbuffer is available and ready for use + */ + protected void makePbufferAvailable() throws LWJGLException{ + if (pbuffer == null || pbuffer.isBufferLost()){ + if (pbuffer != null && pbuffer.isBufferLost()){ + pbuffer.releaseContext(); + pbuffer.destroy(); + } + pbuffer = new Pbuffer(1, 1, new PixelFormat(0, 0, 0), null); + logger.log(Level.INFO, "OGL: Pbuffer has been created"); + } + } + + /** + * This is called: + * 1) When the context thread ends + * 2) Any time the canvas becomes non-displayable + */ + protected void destroyContext(){ + try { + renderer.resetGLObjects(); + if (Display.isCreated()){ + Display.releaseContext(); + Display.destroy(); + } + } catch (LWJGLException ex) { + listener.handleError("Failed to destroy context", ex); + } + + 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(); + }else{ + // The context thread is no longer running. + // Destroy pbuffer. + if (pbuffer != null){ + pbuffer.destroy(); + } + } + } catch (LWJGLException ex) { + listener.handleError("Failed make pbuffer available", ex); + } + } + + /** + * This is called: + * 1) When the context thread starts + * 2) Any time the canvas becomes displayable again. + */ @Override protected void createContext(AppSettings settings) { // In case canvas is not visible, we still take framerate // from settings to prevent "100% CPU usage" frameRate = settings.getFrameRate(); - if (!renderable.get()) - return; + try { + // First create the pbuffer, if it is needed. + makePbufferAvailable(); - Display.setVSyncEnabled(settings.isVSync()); + if (renderable.get()){ + if (pbuffer.isCurrent()){ + pbuffer.releaseContext(); + } - try{ - Display.setParent(canvas); - PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(), - 0, - settings.getDepthBits(), - settings.getStencilBits(), - settings.getSamples()); - Display.create(pf); - Display.makeCurrent(); + Display.setVSyncEnabled(settings.isVSync()); + Display.setParent(canvas); + PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(), + 0, + settings.getDepthBits(), + settings.getStencilBits(), + settings.getSamples()); + Display.create(pf, pbuffer); + Display.makeCurrent(); + }else{ + pbuffer.makeCurrent(); + } + // 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. initContextFirstTime(); runningFirstTime = false; } - }catch (LWJGLException ex){ - listener.handleError("Failed to parent canvas to display", ex); + } catch (LWJGLException ex) { + listener.handleError("Failed to initialize OpenGL context", ex); + // TODO: Fix deadlock that happens after the error (throw runtime exception?) } } diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java index b5004155d..77e6731d1 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglContext.java @@ -115,8 +115,6 @@ public abstract class LwjglContext implements JmeContext { } protected void initContextFirstTime(){ - assert renderable.get(); - if (GLContext.getCapabilities().OpenGL20){ renderer = new LwjglRenderer(); }else{ diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java index a46dcd7e3..1444a2526 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglDisplay.java @@ -133,6 +133,16 @@ public class LwjglDisplay extends LwjglAbstractDisplay { } } } + + protected void destroyContext(){ + try { + renderer.cleanup(); + Display.releaseContext(); + Display.destroy(); + } catch (LWJGLException ex) { + listener.handleError("Failed to destroy context", ex); + } + } public void create(boolean waitFor){ if (created.get()){ diff --git a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglTimer.java b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglTimer.java index 497f25c6e..6a860ea5d 100644 --- a/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglTimer.java +++ b/engine/src/lwjgl-ogl/com/jme3/system/lwjgl/LwjglTimer.java @@ -34,6 +34,7 @@ package com.jme3.system.lwjgl; import com.jme3.math.FastMath; import com.jme3.system.Timer; +import java.util.logging.Level; import java.util.logging.Logger; import org.lwjgl.Sys; @@ -81,7 +82,7 @@ public class LwjglTimer extends Timer { reset(); //print timer resolution info - logger.info("Timer resolution: " + LWJGL_TIMER_RES + " ticks per second"); + logger.log(Level.INFO, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES); } public void reset() { diff --git a/engine/test/com/jme3/math/TrigonometryTest.java b/engine/test/com/jme3/math/TrigonometryTest.java index df778f6f4..2a8228e69 100644 --- a/engine/test/com/jme3/math/TrigonometryTest.java +++ b/engine/test/com/jme3/math/TrigonometryTest.java @@ -1,11 +1,7 @@ package com.jme3.math; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import com.jme3.math.FastMath; -import com.jme3.math.Vector2f; import org.junit.Test; public class TrigonometryTest {