diff options
12 files changed, 323 insertions, 32 deletions
diff --git a/src/net/java/games/jogl/Animator.java b/src/net/java/games/jogl/Animator.java index 5ab2aad75..e844c1501 100644 --- a/src/net/java/games/jogl/Animator.java +++ b/src/net/java/games/jogl/Animator.java @@ -39,6 +39,9 @@ package net.java.games.jogl; +import java.awt.EventQueue; +import net.java.games.jogl.impl.SingleThreadedWorkaround; + /** <P> An Animator can be attached to a GLDrawable to drive its display() method in a loop. For efficiency, it sets up the rendering thread for the drawable to be its own internal thread, @@ -113,7 +116,12 @@ public class Animator { // during display(), so don't disable the rendering // thread again. if (noException) { - drawable.setRenderingThread(null); + // Destruction of the underlying GLContext may have + // disabled the setRenderingThread optimization out + // from under us + if (drawable.getRenderingThread() != null) { + drawable.setRenderingThread(null); + } } } finally { synchronized (Animator.this) { @@ -133,6 +141,14 @@ public class Animator { finished. */ public synchronized void stop() { shouldStop = true; + // It's hard to tell whether the thread which calls stop() has + // dependencies on the Animator's internal thread. Currently we + // use a couple of heuristics to determine whether we should do + // the blocking wait(). + if ((Thread.currentThread() == thread) || + (SingleThreadedWorkaround.doWorkaround() && EventQueue.isDispatchThread())) { + return; + } while (shouldStop && thread != null) { try { wait(); diff --git a/src/net/java/games/jogl/GLCanvas.java b/src/net/java/games/jogl/GLCanvas.java index ee33a14c9..1deac7f32 100644 --- a/src/net/java/games/jogl/GLCanvas.java +++ b/src/net/java/games/jogl/GLCanvas.java @@ -40,6 +40,7 @@ package net.java.games.jogl; import java.awt.Canvas; +import java.awt.EventQueue; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import net.java.games.jogl.impl.*; @@ -60,7 +61,7 @@ public final class GLCanvas extends Canvas implements GLDrawable { private GLDrawableHelper drawableHelper = new GLDrawableHelper(); private GLContext context; - + GLCanvas(GraphicsConfiguration config, GLCapabilities capabilities, GLCapabilitiesChooser chooser, @@ -82,6 +83,20 @@ public final class GLCanvas extends Canvas implements GLDrawable { } } + /** Overridden from Canvas; used to indicate when it's safe to + create an OpenGL context for the component. */ + public void addNotify() { + super.addNotify(); + context.setRealized(); + } + + /** Overridden from Canvas; used to indicate that it's no longer + safe to have an OpenGL context for the component. */ + public void removeNotify() { + context.destroy(); + super.removeNotify(); + } + /** Overridden from Canvas; causes {@link GLDrawableHelper#reshape} to be called on all registered {@link GLEventListener}s. Called automatically by the AWT; should not be invoked by applications @@ -183,7 +198,15 @@ public final class GLCanvas extends Canvas implements GLDrawable { // private void displayImpl() { - context.invokeGL(displayAction, false, initAction); + if (SingleThreadedWorkaround.doWorkaround() && !EventQueue.isDispatchThread()) { + try { + EventQueue.invokeAndWait(displayOnEventDispatchThreadAction); + } catch (Exception e) { + throw new GLException(e); + } + } else { + context.invokeGL(displayAction, false, initAction); + } } class InitAction implements Runnable { @@ -206,4 +229,15 @@ public final class GLCanvas extends Canvas implements GLDrawable { } } private SwapBuffersAction swapBuffersAction = new SwapBuffersAction(); + + // Workaround for ATI driver bugs related to multithreading issues + // like simultaneous rendering via Animators to canvases that are + // being resized on the AWT event dispatch thread + class DisplayOnEventDispatchThreadAction implements Runnable { + public void run() { + context.invokeGL(displayAction, false, initAction); + } + } + private DisplayOnEventDispatchThreadAction displayOnEventDispatchThreadAction = + new DisplayOnEventDispatchThreadAction(); } diff --git a/src/net/java/games/jogl/impl/GLContext.java b/src/net/java/games/jogl/impl/GLContext.java index 9d6036aaf..5260bda6a 100644 --- a/src/net/java/games/jogl/impl/GLContext.java +++ b/src/net/java/games/jogl/impl/GLContext.java @@ -40,7 +40,6 @@ package net.java.games.jogl.impl; import java.awt.Component; -import java.awt.EventQueue; import net.java.games.jogl.*; import net.java.games.gluegen.runtime.*; @@ -62,8 +61,8 @@ public abstract class GLContext { // fetched from a locked DrawingSurface during the validation as a // result of calling show() on the main thread. To work around this // we prevent any JAWT or OpenGL operations from being done until - // the first event is received from the AWT event dispatch thread. - private boolean realized; + // addNotify() is called on the component. + protected boolean realized; protected GLCapabilities capabilities; protected GLCapabilitiesChooser chooser; @@ -77,6 +76,14 @@ public abstract class GLContext { protected GLU glu = gluRoot; // this is the context's GLU interface protected Thread renderingThread; protected Runnable deferredReshapeAction; + // Support for OpenGL context destruction and recreation in the face + // of the setRenderingThread optimization, which makes the context + // permanently current on the animation thread. FIXME: should make + // this more uniform and general, possibly by implementing in terms + // of Runnables; however, necessary sequence of operations in + // invokeGL makes this tricky. + protected boolean deferredDestroy; + protected boolean deferredSetRealized; // Error checking for setRenderingThread to ensure that one thread // doesn't attempt to call setRenderingThread on more than one @@ -163,26 +170,41 @@ public abstract class GLContext { // Defer JAWT and OpenGL operations until onscreen components are // realized - if (!realized()) { - realized = EventQueue.isDispatchThread(); - } - - if (!realized() || + if (!isRealized() || willSetRenderingThread || (renderingThread != null && renderingThread != currentThread)) { - if (isReshape) { - deferredReshapeAction = runnable; + // Support for removeNotify()/addNotify() when the + // setRenderingThread optimization is in effect and before the + // animation thread gets a chance to handle either request + if (!isRealized() && deferredSetRealized) { + setRealized(); + deferredSetRealized = false; + } else { + if (isReshape) { + deferredReshapeAction = runnable; + } + return; } - return; } - if (isReshape && noAutoRedraw) { + if (isReshape && noAutoRedraw && !SingleThreadedWorkaround.doWorkaround()) { // Don't process reshape requests on the AWT thread deferredReshapeAction = runnable; return; } + if (deferredDestroy) { + deferredDestroy = false; + if (renderingThread != null) { + // Need to disable the setRenderingThread optimization to free + // up the context + setRenderingThread(null, initAction); + } + destroy(); + return; + } + // The goal of this code is to optimize OpenGL context handling as // much as possible. In particular: // @@ -345,6 +367,11 @@ public abstract class GLContext { } public synchronized void setRenderingThread(Thread currentThreadOrNull, Runnable initAction) { + if (SingleThreadedWorkaround.doWorkaround()) { + willSetRenderingThread = false; + return; + } + Thread currentThread = Thread.currentThread(); if (currentThreadOrNull != null && currentThreadOrNull != currentThread) { throw new GLException("Argument must be either the current thread or null"); @@ -436,6 +463,7 @@ public abstract class GLContext { resetProcAddressTable(gluProcAddressTable); haveResetGLUProcAddressTable = true; // Only need to do this once globally } + recomputeSingleThreadedWorkaround(); } /** @@ -550,6 +578,53 @@ public abstract class GLContext { GLException to be thrown. */ protected abstract void free() throws GLException; + /** Inform the system that the associated heavyweight widget has + been realized and that it is safe to create an associated OpenGL + context. If the widget is later destroyed then destroy() should + be called, which will cause the underlying OpenGL context to be + destroyed as well as the realized bit to be set to false. */ + public void setRealized() { + if (getRenderingThread() != null && + Thread.currentThread() != getRenderingThread()) { + deferredSetRealized = true; + return; + } + setRealized(true); + } + + /** Sets only the "realized" bit. Should be called by subclasses + from within the destroy() implementation. */ + protected synchronized void setRealized(boolean realized) { + this.realized = realized; + } + + /** Indicates whether the component associated with this context has + been realized. */ + public synchronized boolean getRealized() { + return realized; + } + + /** Destroys the underlying OpenGL context and changes the realized + state to false. This should be called when the widget is being + destroyed. */ + public synchronized void destroy() throws GLException { + if (getRenderingThread() != null && + Thread.currentThread() != getRenderingThread()) { + deferredDestroy = true; + return; + } + setRealized(false); + GLContextShareSet.contextDestroyed(this); + destroyImpl(); + } + + /** Destroys the underlying OpenGL context. */ + protected abstract void destroyImpl() throws GLException; + + public synchronized boolean isRealized() { + return (component == null || getRealized()); + } + /** Helper routine which resets a ProcAddressTable generated by the GLEmitter by looking up anew all of its function pointers. */ protected void resetProcAddressTable(Object table) { @@ -610,10 +685,23 @@ public abstract class GLContext { perThreadSavedCurrentContext.set(new GLContextInitActionPair(context, initAction)); } - //---------------------------------------------------------------------- - // Internals only below this point - // - private boolean realized() { - return ((component == null) || realized || component.isDisplayable()); + /** Support for automatic detection of whether we need to enable the + single-threaded workaround for ATI and other vendors' cards. + Should be called by subclasses for onscreen rendering inside + their makeCurrent() implementation once the context is + current. */ + private void recomputeSingleThreadedWorkaround() { + if (!SingleThreadedWorkaround.doWorkaround()) { + GL gl = getGL(); + String str = gl.glGetString(GL.GL_VENDOR); + if (str != null && str.indexOf("ATI") >= 0) { + // Doing this instead of calling setRenderingThread(null) should + // be OK since we are doing this very early in the maintenance + // of the per-thread context stack, before we are actually + // pushing any GLContext objects on it + renderingThread = null; + SingleThreadedWorkaround.shouldDoWorkaround(); + } + } } } diff --git a/src/net/java/games/jogl/impl/SingleThreadedWorkaround.java b/src/net/java/games/jogl/impl/SingleThreadedWorkaround.java new file mode 100755 index 000000000..ca6485ea2 --- /dev/null +++ b/src/net/java/games/jogl/impl/SingleThreadedWorkaround.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN + * MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR + * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR + * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR + * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE + * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, + * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF + * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for use + * in the design, construction, operation or maintenance of any nuclear + * facility. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package net.java.games.jogl.impl; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** Encapsulates the workaround of running all display operations on + the AWT event queue thread for the purposes of working around + problems seen primarily on ATI cards when rendering into a surface + that is simultaneously being resized by the event queue thread */ + +public class SingleThreadedWorkaround { + private static boolean ATI_WORKAROUND = false; + // If the user specified the workaround's system property (either + // true or false), don't let the automatic detection have any effect + private static boolean systemPropertySpecified = false; + + static { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + String workaround = System.getProperty("ATI_WORKAROUND"); + if (workaround != null) { + systemPropertySpecified = true; + ATI_WORKAROUND = Boolean.valueOf(workaround).booleanValue(); + } + printWorkaroundNotice(); + return null; + } + }); + } + + public static void shouldDoWorkaround() { + if (!systemPropertySpecified) { + ATI_WORKAROUND = true; + printWorkaroundNotice(); + } + } + + public static boolean doWorkaround() { + return ATI_WORKAROUND; + } + + private static void printWorkaroundNotice() { + if (ATI_WORKAROUND) { + System.err.println("Using ATI workaround of dispatching display() on event thread"); + } + } +} diff --git a/src/net/java/games/jogl/impl/macosx/MacOSXGLContext.java b/src/net/java/games/jogl/impl/macosx/MacOSXGLContext.java index 2a34709f2..22d12b575 100644 --- a/src/net/java/games/jogl/impl/macosx/MacOSXGLContext.java +++ b/src/net/java/games/jogl/impl/macosx/MacOSXGLContext.java @@ -172,6 +172,18 @@ public abstract class MacOSXGLContext extends GLContext } } + protected void destroyImpl() throws GLException { + if (nsContext != 0) { + if (!CGL.deleteContext(nsContext, null)) { + throw new GLException("Unable to delete OpenGL context"); + } + if (DEBUG) { + System.err.println("!!! Destroyed OpenGL context " + nsContext); + } + nsContext = 0; + } + } + public abstract void swapBuffers() throws GLException; protected long dynamicLookupFunction(String glFuncName) { diff --git a/src/net/java/games/jogl/impl/windows/WindowsGLContext.java b/src/net/java/games/jogl/impl/windows/WindowsGLContext.java index 56a7188eb..d58a781a8 100644 --- a/src/net/java/games/jogl/impl/windows/WindowsGLContext.java +++ b/src/net/java/games/jogl/impl/windows/WindowsGLContext.java @@ -168,6 +168,18 @@ public abstract class WindowsGLContext extends GLContext { } } + protected void destroyImpl() throws GLException { + if (hglrc != 0) { + if (!WGL.wglDeleteContext(hglrc)) { + throw new GLException("Unable to delete OpenGL context"); + } + if (DEBUG) { + System.err.println("!!! Destroyed OpenGL context " + hglrc); + } + hglrc = 0; + } + } + public abstract void swapBuffers() throws GLException; protected long dynamicLookupFunction(String glFuncName) { @@ -455,6 +467,9 @@ public abstract class WindowsGLContext extends GLContext { throw new GLException("Unable to set pixel format"); } hglrc = WGL.wglCreateContext(hdc); + if (DEBUG) { + System.err.println("!!! Created OpenGL context " + hglrc); + } if (hglrc == 0) { throw new GLException("Unable to create OpenGL context"); } diff --git a/src/net/java/games/jogl/impl/windows/WindowsGLContextFactory.java b/src/net/java/games/jogl/impl/windows/WindowsGLContextFactory.java index 961a116d8..74d72645d 100644 --- a/src/net/java/games/jogl/impl/windows/WindowsGLContextFactory.java +++ b/src/net/java/games/jogl/impl/windows/WindowsGLContextFactory.java @@ -101,10 +101,10 @@ public class WindowsGLContextFactory extends GLContextFactory { GraphicsConfiguration config = device.getDefaultConfiguration(); final Dialog frame = new Dialog(new Frame(config), "", false, config); frame.setUndecorated(true); - GLCanvas canvas = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities(), - null, - null, - device); + final GLCanvas canvas = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities(), + null, + null, + device); canvas.addGLEventListener(new GLEventListener() { public void init(GLDrawable drawable) { pendingContextSet.remove(device); @@ -133,16 +133,29 @@ public class WindowsGLContextFactory extends GLContextFactory { public void reshape(GLDrawable drawable, int x, int y, int width, int height) { } + public void destroy(GLDrawable drawable) { + } + public void displayChanged(GLDrawable drawable, boolean modeChanged, boolean deviceChanged) { } }); - canvas.setSize(0, 0); - canvas.setNoAutoRedrawMode(true); - canvas.setAutoSwapBufferMode(false); - frame.add(canvas); - frame.pack(); - frame.show(); - canvas.display(); + // Attempt to work around deadlock issues with SingleThreadedWorkaround, + // which causes some of the methods below to block doing work on the AWT thread + try { + EventQueue.invokeLater(new Runnable() { + public void run() { + canvas.setSize(0, 0); + canvas.setNoAutoRedrawMode(true); + canvas.setAutoSwapBufferMode(false); + frame.add(canvas); + frame.pack(); + frame.show(); + canvas.display(); + } + }); + } catch (Exception e) { + throw new GLException(e); + } } return (GL) dummyContextMap.get(device); diff --git a/src/net/java/games/jogl/impl/windows/WindowsOnscreenGLContext.java b/src/net/java/games/jogl/impl/windows/WindowsOnscreenGLContext.java index 868846079..5bcdb4559 100644 --- a/src/net/java/games/jogl/impl/windows/WindowsOnscreenGLContext.java +++ b/src/net/java/games/jogl/impl/windows/WindowsOnscreenGLContext.java @@ -155,12 +155,19 @@ public class WindowsOnscreenGLContext extends WindowsGLContext { throw new GLException("Unable to lock surface"); } // See whether the surface changed and if so destroy the old - // OpenGL context so it will be recreated + // OpenGL context so it will be recreated (NOTE: removeNotify + // should handle this case, but it may be possible that race + // conditions can cause this code to be triggered -- should test + // more) if ((res & JAWTFactory.JAWT_LOCK_SURFACE_CHANGED) != 0) { if (hglrc != 0) { if (!WGL.wglDeleteContext(hglrc)) { throw new GLException("Unable to delete old GL context after surface changed"); } + GLContextShareSet.contextDestroyed(this); + if (DEBUG) { + System.err.println("!!! Destroyed OpenGL context " + hglrc + " due to JAWT_LOCK_SURFACE_CHANGED"); + } hglrc = 0; } } diff --git a/src/net/java/games/jogl/impl/x11/X11GLContext.java b/src/net/java/games/jogl/impl/x11/X11GLContext.java index e3bb4ce72..40e462fb3 100644 --- a/src/net/java/games/jogl/impl/x11/X11GLContext.java +++ b/src/net/java/games/jogl/impl/x11/X11GLContext.java @@ -59,6 +59,11 @@ public abstract class X11GLContext extends GLContext { // OpenGL functions. private GLProcAddressTable glProcAddressTable; private static boolean haveResetGLXProcAddressTable; + // Cache the most recent value of the "display" variable (which we + // only guarantee to be valid in between makeCurrent / free pairs) + // so that we can implement displayImpl() (which must be done when + // the context is not current) + protected long mostRecentDisplay; static { functionNameMap = new HashMap(); @@ -167,6 +172,18 @@ public abstract class X11GLContext extends GLContext { } } + protected void destroyImpl() throws GLException { + if (context != 0) { + if (!GLX.glXDestroyContext(mostRecentDisplay, context)) { + throw new GLException("Unable to delete OpenGL context"); + } + if (DEBUG) { + System.err.println("!!! Destroyed OpenGL context " + context); + } + context = 0; + } + } + public abstract void swapBuffers() throws GLException; protected long dynamicLookupFunction(String glFuncName) { diff --git a/src/net/java/games/jogl/impl/x11/X11OffscreenGLContext.java b/src/net/java/games/jogl/impl/x11/X11OffscreenGLContext.java index 8c88b0225..0290f8d44 100644 --- a/src/net/java/games/jogl/impl/x11/X11OffscreenGLContext.java +++ b/src/net/java/games/jogl/impl/x11/X11OffscreenGLContext.java @@ -129,6 +129,7 @@ public class X11OffscreenGLContext extends X11GLContext { pendingOffscreenResize = false; } } + mostRecentDisplay = display; return super.makeCurrent(initAction); } diff --git a/src/net/java/games/jogl/impl/x11/X11OnscreenGLContext.java b/src/net/java/games/jogl/impl/x11/X11OnscreenGLContext.java index 911fc3740..1b5f54979 100644 --- a/src/net/java/games/jogl/impl/x11/X11OnscreenGLContext.java +++ b/src/net/java/games/jogl/impl/x11/X11OnscreenGLContext.java @@ -189,6 +189,7 @@ public class X11OnscreenGLContext extends X11GLContext { visualID = 0; return false; } + mostRecentDisplay = display; return true; } diff --git a/src/net/java/games/jogl/impl/x11/X11PbufferGLContext.java b/src/net/java/games/jogl/impl/x11/X11PbufferGLContext.java index ccbba8b92..5ab976870 100644 --- a/src/net/java/games/jogl/impl/x11/X11PbufferGLContext.java +++ b/src/net/java/games/jogl/impl/x11/X11PbufferGLContext.java @@ -217,6 +217,7 @@ public class X11PbufferGLContext extends X11GLContext { // Set up instance variables this.display = display; + mostRecentDisplay = display; this.parentContext = parentContext; buffer = tmpBuffer; this.fbConfig = fbConfig; |