Jogamp
Bug 1033: Guarantee atomicity of high-level GLAutoDrawable operations, avoiding race...
authorSven Gothel <sgothel@jausoft.com>
Sun, 27 Jul 2014 01:49:21 +0000 (03:49 +0200)
committerSven Gothel <sgothel@jausoft.com>
Sun, 27 Jul 2014 01:49:21 +0000 (03:49 +0200)
GLAutoDrawable (API CHANGE) allowing atomic operations:
  - Add class API-doc chapter about 'GLAutoDrawable Locking'

  - Add method invoke(..) API-doc description about throwing IllegalStateException in case of a detected deadlock situation ahead
    (Note: Implemented in GLDrawableHelper.invoke(..) for all implementations)

  - Add new methods for proper multithread handling:
    - public RecursiveLock getUpstreamLock();
    - public boolean isThreadGLCapable();

+++

GLEventListenerState/GLDrawableUtil:

  - Perform operation in a atomic fashion,
    i.e. lock GLAutoDrawable during whole operations:
      - GLDrawableUtil.swapGLContext(..)
      - GLDrawableUtil.swapGLContextAndAllGLEventListener(..)
      - GLEventListenerState.moveFrom(..)
      - GLEventListenerState.moveTo(..)

  - ReshapeGLEventListener:
    - Moved from GLEventListenerState.ReshapeGLEventListener -> GLDrawableUtil.ReshapeGLEventListener
    - Takes 'displayAfterReshape' case into account.

+++

javax.media.opengl.Threading Clarifications:
  - Public 'enum Mode', i.e. Threading.Mode

  - Public getMode()

  - Clarified 'isOpenGLThread()':
    - Take 'singleThreaded' into account directly,
      i.e. always return 'true' if singleThreaded == false

17 files changed:
make/scripts/tests.sh
src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java
src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java
src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java
src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java
src/jogl/classes/javax/media/opengl/GLAutoDrawable.java
src/jogl/classes/javax/media/opengl/GLContext.java
src/jogl/classes/javax/media/opengl/Threading.java
src/jogl/classes/javax/media/opengl/awt/GLCanvas.java
src/jogl/classes/javax/media/opengl/awt/GLJPanel.java
src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java
src/jogl/classes/jogamp/opengl/GLContextImpl.java
src/jogl/classes/jogamp/opengl/GLDrawableHelper.java
src/jogl/classes/jogamp/opengl/ThreadingImpl.java
src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java
src/nativewindow/classes/javax/media/nativewindow/NativeSurface.java
src/newt/classes/com/jogamp/newt/opengl/GLWindow.java

index 730c509..886d7c2 100644 (file)
@@ -194,6 +194,7 @@ function jrun() {
     #D_ARGS="-Djogamp.common.utils.locks.Lock.timeout=600000 -Djogamp.debug.Lock -Djogamp.debug.Lock.TraceLock -Dnativewindow.debug.ToolkitLock.TraceLock"
     #D_ARGS="-Djogamp.common.utils.locks.Lock.timeout=600000 -Djogamp.debug.Lock -Dnativewindow.debug.X11Util"
     #D_ARGS="-Djogamp.common.utils.locks.Lock.timeout=600000 -Dnativewindow.debug.X11Util"
+    #D_ARGS="-Djogamp.common.utils.locks.Lock.timeout=600000"
     #D_ARGS="-Dnewt.debug.EDT -Djogamp.common.utils.locks.Lock.timeout=600000 -Djogl.debug.Animator -Dnewt.debug.Display -Dnewt.debug.Screen"
     #D_ARGS="-Dnewt.debug.Window -Djogamp.common.utils.locks.Lock.timeout=600000"
     #D_ARGS="-Dnewt.debug=all -Djogamp.common.utils.locks.Lock.timeout=600000"
@@ -382,7 +383,7 @@ function testawtswt() {
 #testawt com.jogamp.opengl.test.junit.jogl.demos.es2.awt.TestGearsES2GLJPanelAWT $*
 #testawt com.jogamp.opengl.test.junit.jogl.demos.es2.newt.TestGearsES2NewtCanvasAWT $*
 #testnoawt com.jogamp.opengl.test.junit.jogl.glsl.TestRulerNEWT01 $*
-testnoawt com.jogamp.opengl.test.junit.graph.demos.GPUUISceneNewtDemo $*
+#testnoawt com.jogamp.opengl.test.junit.graph.demos.GPUUISceneNewtDemo $*
 #testawt com.jogamp.opengl.test.junit.graph.demos.GPUUISceneNewtCanvasAWTDemo $*
 #testawt com.jogamp.opengl.test.junit.jogl.awt.ManualHiDPIBufferedImage01AWT $*
 
@@ -532,7 +533,7 @@ testnoawt com.jogamp.opengl.test.junit.graph.demos.GPUUISceneNewtDemo $*
 #testnoawt com.jogamp.opengl.test.junit.jogl.acore.glels.TestGLContextDrawableSwitch01NEWT $*
 #testnoawt com.jogamp.opengl.test.junit.jogl.acore.glels.TestGLContextDrawableSwitch10NEWT $*
 #testawt com.jogamp.opengl.test.junit.jogl.acore.glels.TestGLContextDrawableSwitch11NewtAWT $*
-#testawt com.jogamp.opengl.test.junit.jogl.acore.glels.TestGLContextDrawableSwitch12AWT $*
+testawt com.jogamp.opengl.test.junit.jogl.acore.glels.TestGLContextDrawableSwitch12AWT $*
 #testawt com.jogamp.opengl.test.junit.jogl.acore.glels.TestGLContextDrawableSwitch21Newt2AWT $*
 #testawt com.jogamp.opengl.test.junit.jogl.acore.glels.TestBug722GLContextDrawableSwitchNewt2AWT $*
 
index f9a7ab0..4ef717a 100644 (file)
@@ -143,7 +143,7 @@ public class GLAutoDrawableDelegate extends GLAutoDrawableBase implements GLAuto
     private final RecursiveLock lock;
 
     @Override
-    protected final RecursiveLock getLock() { return lock; }
+    public final RecursiveLock getUpstreamLock() { return lock; }
 
     @Override
     public final Object getUpstreamWidget() {
index a7ba141..bfd5fe1 100644 (file)
@@ -45,7 +45,9 @@ import javax.media.opengl.GLRunnable;
 
 import jogamp.opengl.Debug;
 
+import com.jogamp.common.util.locks.RecursiveLock;
 import com.jogamp.nativewindow.MutableGraphicsConfiguration;
+import com.jogamp.opengl.util.GLDrawableUtil;
 
 /**
  * GLEventListenerState is holding {@link GLAutoDrawable} components crucial
@@ -72,11 +74,14 @@ public class GLEventListenerState {
 
     private GLEventListenerState(final AbstractGraphicsDevice upstreamDevice, final boolean proxyOwnsUpstreamDevice, final AbstractGraphicsDevice device,
                                  final GLCapabilitiesImmutable caps,
+                                 final RecursiveLock upstreamLock, final NativeSurface lockedSurface,
                                  final GLContext context, final int count, final GLAnimatorControl anim, final boolean animStarted) {
         this.upstreamDevice = upstreamDevice;
         this.proxyOwnsUpstreamDevice = proxyOwnsUpstreamDevice;
         this.device = device;
         this.caps = caps;
+        this.upstreamLock = upstreamLock;
+        this.lockedSurface = lockedSurface;
         this.context = context;
         this.listeners = new GLEventListener[count];
         this.listenersInit = new boolean[count];
@@ -107,7 +112,29 @@ public class GLEventListenerState {
     public final GLAnimatorControl anim;
     public final boolean animStarted;
 
-    private boolean owner;
+    private volatile RecursiveLock upstreamLock;
+    private volatile NativeSurface lockedSurface;
+    private volatile boolean owner;
+
+    /**
+     * Returns a {@link Runnable} {@link NativeSurface#unlockSurface() unlocking} an eventually locked {@link NativeSurface},
+     * see {@link #moveFrom(GLAutoDrawable, boolean)} and {@link #moveTo(GLAutoDrawable, Runnable)}.
+     */
+    public Runnable getUnlockSurfaceOp() { return unlockOp; }
+
+    private final Runnable unlockOp = new Runnable() {
+        public void run() {
+            final RecursiveLock rl = upstreamLock;
+            final NativeSurface ls = lockedSurface;
+            upstreamLock = null;
+            lockedSurface = null;
+            if( null != rl ) {
+                rl.unlock();
+            }
+            if( null != ls ) {
+                ls.unlockSurface();
+            }
+        } };
 
     /**
      * Last resort to destroy and loose ownership
@@ -119,6 +146,7 @@ public class GLEventListenerState {
                 listeners[i] = null;
             }
             // context.destroy(); - NPE (null drawable)
+            unlockOp.run();
             device.close();
             owner = false;
         }
@@ -142,92 +170,143 @@ public class GLEventListenerState {
      * <p>
      * The returned GLEventListenerState instance is the {@link #isOwner() owner of the components}.
      * </p>
+     * <p>
+     * Locking is performed on the {@link GLAutoDrawable auto-drawable's}
+     * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}.
+     * See <a href="../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li>
+     * </p>
      *
-     * @param a {@link GLAutoDrawable} source to move components from
+     * @param src {@link GLAutoDrawable} source to move components from
      * @return new GLEventListenerState instance {@link #isOwner() owning} moved components.
      *
      * @see #moveTo(GLAutoDrawable)
      */
-    public static GLEventListenerState moveFrom(final GLAutoDrawable a) {
-        final GLAnimatorControl aAnim = a.getAnimator();
-        final boolean aAnimStarted;
-        if( null != aAnim ) {
-            aAnimStarted = aAnim.isStarted();
-            aAnim.remove(a); // also handles ECT
+    public static GLEventListenerState moveFrom(final GLAutoDrawable src) {
+        return GLEventListenerState.moveFrom(src, false);
+    }
+
+    /**
+     * Moves all GLEventListenerState components from the given {@link GLAutoDrawable}
+     * to a newly created instance.
+     * <p>
+     * Note that all components are removed from the {@link GLAutoDrawable},
+     * i.e. the {@link GLContext}, all {@link GLEventListener}.
+     * </p>
+     * <p>
+     * If the {@link GLAutoDrawable} was added to a {@link GLAnimatorControl}, it is removed
+     * and the {@link GLAnimatorControl} added to the GLEventListenerState.
+     * </p>
+     * <p>
+     * The returned GLEventListenerState instance is the {@link #isOwner() owner of the components}.
+     * </p>
+     * <p>
+     * Locking is performed on the {@link GLAutoDrawable auto-drawable's}
+     * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface},
+     * which is <i>not released</i> if <code>keepLocked</code> is <code>true</code>.
+     * See <a href="../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li>
+     * </p>
+     * <p>
+     * <code>keepLocked</code> may be utilized if swapping a context between drawables
+     * and to ensure atomicity of operation.
+     * Here, the {@link #getUnlockSurfaceOp()} shall be passed to {@link #moveTo(GLAutoDrawable, Runnable)}.
+     * See {@link GLDrawableUtil#swapGLContextAndAllGLEventListener(GLAutoDrawable, GLAutoDrawable)}.
+     * </p>
+     *
+     * @param src {@link GLAutoDrawable} source to move components from
+     * @param keepLocked keep {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface} locked, see above
+     * @return new GLEventListenerState instance {@link #isOwner() owning} moved components.
+     *
+     * @see #moveTo(GLAutoDrawable, Runnable)
+     */
+    public static GLEventListenerState moveFrom(final GLAutoDrawable src, final boolean keepLocked) {
+        final GLAnimatorControl srcAnim = src.getAnimator();
+        final boolean srcAnimStarted;
+        if( null != srcAnim ) {
+            srcAnimStarted = srcAnim.isStarted();
+            srcAnim.remove(src); // also handles ECT
         } else {
-            aAnimStarted = false;
+            srcAnimStarted = false;
         }
 
         final GLEventListenerState glls;
-        final NativeSurface aSurface = a.getNativeSurface();
-        final boolean surfaceLocked = false; // NativeSurface.LOCK_SURFACE_NOT_READY < aSurface.lockSurface();
+        final RecursiveLock srcUpstreamLock = src.getUpstreamLock();
+        srcUpstreamLock.lock();
         try {
-            final int aSz = a.getGLEventListenerCount();
-
-            // Create new AbstractGraphicsScreen w/ cloned AbstractGraphicsDevice for future GLAutoDrawable
-            // allowing this AbstractGraphicsDevice to loose ownership -> not closing display/device!
-            final AbstractGraphicsConfiguration aCfg = aSurface.getGraphicsConfiguration();
-            final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) aCfg.getChosenCapabilities();
-            final AbstractGraphicsDevice aDevice1 = aCfg.getScreen().getDevice();
-            final AbstractGraphicsDevice aDevice2 = cloneDevice(aDevice1);
-            aDevice1.clearHandleOwner();  // don't close device handle
-            if( DEBUG ) {
-                System.err.println("GLEventListenerState.moveFrom.0a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1);
-                System.err.println("GLEventListenerState.moveFrom.0b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2);
-                System.err.println("GLEventListenerState.moveFrom.1: "+aSurface.getClass().getName()/*+", "+aSurface*/);
+            final NativeSurface srcSurface = src.getNativeSurface();
+            final boolean srcSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < srcSurface.lockSurface();
+            if( src.isRealized() && !srcSurfaceLocked ) {
+                throw new GLException("Could not lock realized surface "+src);
             }
-            final AbstractGraphicsDevice aUpDevice2;
-            final boolean proxyOwnsUpstreamDevice;
-            {
-                AbstractGraphicsDevice _aUpDevice2 = null;
-                if(aSurface instanceof ProxySurface) {
-                    final ProxySurface aProxy = (ProxySurface)aSurface;
-                    proxyOwnsUpstreamDevice = aProxy.containsUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE );
-                    final NativeSurface aUpSurface = aProxy.getUpstreamSurface();
-                    if(DEBUG && null != aUpSurface) {
-                        System.err.println("GLEventListenerState.moveFrom.2: "+aUpSurface.getClass().getName()+", "+aUpSurface);
-                    }
-                    if(null != aUpSurface) {
-                        final AbstractGraphicsDevice aUpDevice1 = aUpSurface.getGraphicsConfiguration().getScreen().getDevice();
-                        _aUpDevice2 = cloneDevice(aUpDevice1);
-                        aUpDevice1.clearHandleOwner(); // don't close device handle
-                        if(DEBUG) {
-                            System.err.println("GLEventListenerState.moveFrom.3a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1);
-                            System.err.println("GLEventListenerState.moveFrom.3b: up-pres 0x"+Integer.toHexString(_aUpDevice2.hashCode())+", "+_aUpDevice2);
-                            System.err.println("GLEventListenerState.moveFrom.3c: "+aSurface.getClass().getName()+", "+aSurface);
-                            System.err.println("GLEventListenerState.moveFrom.3d: "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString());
+
+            try {
+                final int aSz = src.getGLEventListenerCount();
+
+                // Create new AbstractGraphicsScreen w/ cloned AbstractGraphicsDevice for future GLAutoDrawable
+                // allowing this AbstractGraphicsDevice to loose ownership -> not closing display/device!
+                final AbstractGraphicsConfiguration aCfg = srcSurface.getGraphicsConfiguration();
+                final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) aCfg.getChosenCapabilities();
+                final AbstractGraphicsDevice aDevice1 = aCfg.getScreen().getDevice();
+                final AbstractGraphicsDevice aDevice2 = cloneDevice(aDevice1);
+                aDevice1.clearHandleOwner();  // don't close device handle
+                if( DEBUG ) {
+                    System.err.println("GLEventListenerState.moveFrom.0a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1);
+                    System.err.println("GLEventListenerState.moveFrom.0b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2);
+                    System.err.println("GLEventListenerState.moveFrom.1: "+srcSurface.getClass().getName()/*+", "+aSurface*/);
+                }
+                final AbstractGraphicsDevice aUpDevice2;
+                final boolean proxyOwnsUpstreamDevice;
+                {
+                    AbstractGraphicsDevice _aUpDevice2 = null;
+                    if(srcSurface instanceof ProxySurface) {
+                        final ProxySurface aProxy = (ProxySurface)srcSurface;
+                        proxyOwnsUpstreamDevice = aProxy.containsUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE );
+                        final NativeSurface aUpSurface = aProxy.getUpstreamSurface();
+                        if(DEBUG && null != aUpSurface) {
+                            System.err.println("GLEventListenerState.moveFrom.2: "+aUpSurface.getClass().getName()+", "+aUpSurface);
+                        }
+                        if(null != aUpSurface) {
+                            final AbstractGraphicsDevice aUpDevice1 = aUpSurface.getGraphicsConfiguration().getScreen().getDevice();
+                            _aUpDevice2 = cloneDevice(aUpDevice1);
+                            aUpDevice1.clearHandleOwner(); // don't close device handle
+                            if(DEBUG) {
+                                System.err.println("GLEventListenerState.moveFrom.3a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1);
+                                System.err.println("GLEventListenerState.moveFrom.3b: up-pres 0x"+Integer.toHexString(_aUpDevice2.hashCode())+", "+_aUpDevice2);
+                                System.err.println("GLEventListenerState.moveFrom.3c: "+srcSurface.getClass().getName()+", "+srcSurface);
+                                System.err.println("GLEventListenerState.moveFrom.3d: "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString());
+                            }
                         }
+                    } else {
+                        proxyOwnsUpstreamDevice = false;
                     }
-                } else {
-                    proxyOwnsUpstreamDevice = false;
+                    aUpDevice2 = _aUpDevice2;
                 }
-                aUpDevice2 = _aUpDevice2;
-            }
 
-            glls = new GLEventListenerState(aUpDevice2, proxyOwnsUpstreamDevice, aDevice2, caps, a.getContext(), aSz, aAnim, aAnimStarted);
-
-            //
-            // remove and cache all GLEventListener and their init-state
-            //
-            for(int i=0; i<aSz; i++) {
-                final GLEventListener l = a.getGLEventListener(0);
-                glls.listenersInit[i] = a.getGLEventListenerInitState(l);
-                glls.listeners[i] = a.removeGLEventListener( l );
-            }
-
-            //
-            // trigger glFinish to sync GL ctx
-            //
-            a.invoke(true, glFinish);
+                glls = new GLEventListenerState(aUpDevice2, proxyOwnsUpstreamDevice, aDevice2, caps,
+                                                keepLocked ? srcUpstreamLock : null,
+                                                srcSurfaceLocked && keepLocked ? srcSurface : null,
+                                                src.getContext(), aSz, srcAnim, srcAnimStarted);
+
+                //
+                // remove and cache all GLEventListener and their init-state
+                //
+                for(int i=0; i<aSz; i++) {
+                    final GLEventListener l = src.getGLEventListener(0);
+                    glls.listenersInit[i] = src.getGLEventListenerInitState(l);
+                    glls.listeners[i] = src.removeGLEventListener( l );
+                }
 
-            a.setContext( null, false );
+                src.setContext( null, false ); // implicit glFinish() ctx/drawable sync
 
+            } finally {
+                if( srcSurfaceLocked && !keepLocked ) {
+                    srcSurface.unlockSurface();
+                }
+            }
         } finally {
-            if( surfaceLocked ) {
-                aSurface.unlockSurface();
+            if( !keepLocked ) {
+                srcUpstreamLock.unlock();
             }
         }
-
         return glls;
     }
 
@@ -240,134 +319,184 @@ public class GLEventListenerState {
      * This operation is skipped, if the given {@link GLAutoDrawable} is already added to a {@link GLAnimatorControl} instance.
      * </p>
      * <p>
+     * Locking is performed on the {@link GLAutoDrawable auto-drawable's}
+     * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}.
+     * See <a href="../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li>
+     * </p>
+     * <p>
      * Note: After this operation, the GLEventListenerState reference should be released.
      * </p>
      *
-     * @param a {@link GLAutoDrawable} destination to move GLEventListenerState components to
+     * @param dest {@link GLAutoDrawable} destination to move GLEventListenerState components to
      *
-     * <!-- @throws GLException if the {@link GLAutoDrawable}'s configuration is incompatible, i.e. different {@link GLCapabilitiesImmutable}. -->
+     * @throws GLException if a realized surface could not be locked.
      * @throws GLException if this preserved {@link AbstractGraphicsDevice} is incompatible w/ the given destination one.
+     * <!-- @throws GLException if the {@link GLAutoDrawable}'s configuration is incompatible, i.e. different {@link GLCapabilitiesImmutable}. -->
      *
      * @see #moveFrom(GLAutoDrawable)
      * @see #isOwner()
      */
-    public final void moveTo(final GLAutoDrawable a) {
-        final GLAnimatorControl aAnim = a.getAnimator();
-        final boolean hasAnimator = null != aAnim;
-        final boolean aPaused;
-        if( hasAnimator ) {
-            aPaused = aAnim.pause();
-            aAnim.remove(a); // also handles ECT
-            if( aPaused ) {
-                aAnim.resume();
-            }
+    public final void moveTo(final GLAutoDrawable dest) throws GLException  {
+        this.moveTo(dest, null);
+    }
+
+    /**
+     * Moves all GLEventListenerState components to the given {@link GLAutoDrawable}
+     * from this instance, while loosing {@link #isOwner() ownership}.
+     * <p>
+     * If the previous {@link GLAutoDrawable} was removed from a {@link GLAnimatorControl} by previous {@link #moveFrom(GLAutoDrawable, boolean)},
+     * the given {@link GLAutoDrawable} is added to the cached {@link GLAnimatorControl}.
+     * This operation is skipped, if the given {@link GLAutoDrawable} is already added to a {@link GLAnimatorControl} instance.
+     * </p>
+     * <p>
+     * Locking is performed on the {@link GLAutoDrawable auto-drawable's}
+     * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}.
+     * See <a href="../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li>
+     * </p>
+     * <p>
+     * If the {@link GLAutoDrawable} <code>dest</code> has been kept locked by {@link #moveFrom(GLAutoDrawable, boolean)},
+     * it's {@link #getUnlockSurfaceOp()} shall be passed here to <code>destUnlockOperation</code> to be unlocked.
+     * </p>
+     * <p>
+     * Note: After this operation, the GLEventListenerState reference should be released.
+     * </p>
+     *
+     * @param dest {@link GLAutoDrawable} destination to move GLEventListenerState components to
+     * @param destUnlockOperation optional unlock operation for <code>dest</code>, see {@link #moveFrom(GLAutoDrawable, boolean)}.
+     *
+     * @throws GLException if a realized surface could not be locked.
+     * @throws GLException if this preserved {@link AbstractGraphicsDevice} is incompatible w/ the given destination one.
+     * <!-- @throws GLException if the {@link GLAutoDrawable}'s configuration is incompatible, i.e. different {@link GLCapabilitiesImmutable}. -->
+     *
+     * @see #moveFrom(GLAutoDrawable, boolean)
+     * @see #isOwner()
+     */
+    public final void moveTo(final GLAutoDrawable dest, final Runnable destUnlockOperation) throws GLException {
+        final GLAnimatorControl destAnim = dest.getAnimator();
+        final boolean destAnimPaused;
+        if( null != destAnim ) {
+            destAnimPaused = destAnim.pause();
+            destAnim.remove(dest); // also handles ECT
         } else {
-            aPaused = false;
+            destAnimPaused = false;
         }
 
         final List<GLRunnable> aGLCmds = new ArrayList<GLRunnable>();
         final int aSz = listenerCount();
 
-        final NativeSurface aSurface = a.getNativeSurface();
-        final boolean surfaceLocked = false; // NativeSurface.LOCK_SURFACE_NOT_READY < aSurface.lockSurface();
-        final boolean aRealized;
+        final RecursiveLock destUpstreamLock = dest.getUpstreamLock();
+        destUpstreamLock.lock();
+        final boolean destIsRealized;
         try {
-
-            final MutableGraphicsConfiguration aCfg = (MutableGraphicsConfiguration) aSurface.getGraphicsConfiguration();
-            /**
-            final GLCapabilitiesImmutable aCaps = (GLCapabilitiesImmutable) aCfg.getChosenCapabilities();
-            if( caps.getVisualID(VisualIDHolder.VIDType.INTRINSIC) != aCaps.getVisualID(VisualIDHolder.VIDType.INTRINSIC) ||
-                caps.getVisualID(VisualIDHolder.VIDType.NATIVE) != aCaps.getVisualID(VisualIDHolder.VIDType.NATIVE) ) {
-                throw new GLException("Incompatible Capabilities - Prev-Holder: "+caps+", New-Holder "+caps);
-            } */
-            final DefaultGraphicsDevice aDevice1 = (DefaultGraphicsDevice) aCfg.getScreen().getDevice();
-            final DefaultGraphicsDevice aDevice2 = (DefaultGraphicsDevice) device;
-            if( !aDevice1.getUniqueID().equals( aDevice2.getUniqueID() ) ) {
-                throw new GLException("Incompatible devices: Preserved <"+aDevice2.getUniqueID()+">, target <"+aDevice1.getUniqueID()+">");
-            }
-
-            // collect optional upstream surface info
-            final ProxySurface aProxy;
-            final NativeSurface aUpSurface;
-            if(aSurface instanceof ProxySurface) {
-                aProxy = (ProxySurface)aSurface;
-                aUpSurface = aProxy.getUpstreamSurface();
-            } else {
-                aProxy = null;
-                aUpSurface = null;
-            }
-            if( DEBUG ) {
-                System.err.println("GLEventListenerState.moveTo.0 : has aProxy "+(null!=aProxy));
-                System.err.println("GLEventListenerState.moveTo.0 : has aUpSurface "+(null!=aUpSurface));
-            }
-            if( null==aUpSurface && null != upstreamDevice ) {
-                throw new GLException("Incompatible Surface config - Has Upstream-Surface: Prev-Holder = true, New-Holder = false");
-            }
-
-            // Destroy and remove currently associated GLContext, if any (will be replaced)
-            a.setContext( null, true );
-            aRealized = a.isRealized();
-            if( aRealized && null != aUpSurface ) {
-                // Unrealize due to device dependencies of an upstream surface, e.g. EGLUpstreamSurfaceHook
-                a.getDelegatedDrawable().setRealized(false);
+            final NativeSurface destSurface = dest.getNativeSurface();
+            final boolean destSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < destSurface.lockSurface();
+            if( dest.isRealized() && !destSurfaceLocked ) {
+                throw new GLException("Could not lock realized surface "+dest);
             }
+            try {
+
+                final MutableGraphicsConfiguration aCfg = (MutableGraphicsConfiguration) destSurface.getGraphicsConfiguration();
+                /**
+                final GLCapabilitiesImmutable aCaps = (GLCapabilitiesImmutable) aCfg.getChosenCapabilities();
+                if( caps.getVisualID(VisualIDHolder.VIDType.INTRINSIC) != aCaps.getVisualID(VisualIDHolder.VIDType.INTRINSIC) ||
+                    caps.getVisualID(VisualIDHolder.VIDType.NATIVE) != aCaps.getVisualID(VisualIDHolder.VIDType.NATIVE) ) {
+                    throw new GLException("Incompatible Capabilities - Prev-Holder: "+caps+", New-Holder "+caps);
+                } */
+                final DefaultGraphicsDevice aDevice1 = (DefaultGraphicsDevice) aCfg.getScreen().getDevice();
+                final DefaultGraphicsDevice aDevice2 = (DefaultGraphicsDevice) device;
+                if( !aDevice1.getUniqueID().equals( aDevice2.getUniqueID() ) ) {
+                    throw new GLException("Incompatible devices: Preserved <"+aDevice2.getUniqueID()+">, target <"+aDevice1.getUniqueID()+">");
+                }
 
-            // Set new Screen and close previous one
-            {
-                if( DEBUG ) {
-                    System.err.println("GLEventListenerState.moveTo.0a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1);
-                    System.err.println("GLEventListenerState.moveTo.0b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2);
+                // collect optional upstream surface info
+                final ProxySurface aProxy;
+                final NativeSurface aUpSurface;
+                if(destSurface instanceof ProxySurface) {
+                    aProxy = (ProxySurface)destSurface;
+                    aUpSurface = aProxy.getUpstreamSurface();
+                } else {
+                    aProxy = null;
+                    aUpSurface = null;
                 }
-                DefaultGraphicsDevice.swapDeviceHandleAndOwnership(aDevice1, aDevice2);
-                aDevice2.close();
                 if( DEBUG ) {
-                    System.err.println("GLEventListenerState.moveTo.1a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1);
-                    System.err.println("GLEventListenerState.moveTo.1b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2);
+                    System.err.println("GLEventListenerState.moveTo.0 : has aProxy "+(null!=aProxy));
+                    System.err.println("GLEventListenerState.moveTo.0 : has aUpSurface "+(null!=aUpSurface));
+                }
+                if( null==aUpSurface && null != upstreamDevice ) {
+                    throw new GLException("Incompatible Surface config - Has Upstream-Surface: Prev-Holder = true, New-Holder = false");
                 }
-            }
 
-            // If using a ProxySurface w/ an upstream surface, set new Screen and close previous one on it
-            if( null != aUpSurface ) {
-                final MutableGraphicsConfiguration aUpCfg = (MutableGraphicsConfiguration) aUpSurface.getGraphicsConfiguration();
-                if( null != upstreamDevice ) {
-                    final DefaultGraphicsDevice aUpDevice1 = (DefaultGraphicsDevice) aUpCfg.getScreen().getDevice();
-                    final DefaultGraphicsDevice aUpDevice2 = (DefaultGraphicsDevice)upstreamDevice;
-                    if( !aUpDevice1.getUniqueID().equals( aUpDevice2.getUniqueID() ) ) {
-                        throw new GLException("Incompatible updtream devices: Preserved <"+aUpDevice2.getUniqueID()+">, target <"+aUpDevice1.getUniqueID()+">");
-                    }
+                // Destroy and remove currently associated GLContext, if any (will be replaced)
+                dest.setContext( null, true );
+                destIsRealized = dest.isRealized();
+                if( destIsRealized && null != aUpSurface ) {
+                    // Unrealize due to device dependencies of an upstream surface, e.g. EGLUpstreamSurfaceHook
+                    dest.getDelegatedDrawable().setRealized(false);
+                }
+
+                // Set new Screen and close previous one
+                {
                     if( DEBUG ) {
-                        System.err.println("GLEventListenerState.moveTo.2a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1);
-                        System.err.println("GLEventListenerState.moveTo.2b: up-pres 0x"+Integer.toHexString(aUpDevice2.hashCode())+", "+aUpDevice2);
-                        System.err.println("GLEventListenerState.moveTo.2c:  "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString());
-                    }
-                    DefaultGraphicsDevice.swapDeviceHandleAndOwnership(aUpDevice1, aUpDevice2);
-                    aUpDevice2.close();
-                    if( proxyOwnsUpstreamDevice ) {
-                        aProxy.addUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE );
+                        System.err.println("GLEventListenerState.moveTo.0a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1);
+                        System.err.println("GLEventListenerState.moveTo.0b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2);
                     }
+                    DefaultGraphicsDevice.swapDeviceHandleAndOwnership(aDevice1, aDevice2);
+                    aDevice2.close();
                     if( DEBUG ) {
-                        System.err.println("GLEventListenerState.moveTo.3a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1);
-                        System.err.println("GLEventListenerState.moveTo.3b: up-pres 0x"+Integer.toHexString(aUpDevice2.hashCode())+", "+aUpDevice2);
-                        System.err.println("GLEventListenerState.moveTo.3c:  "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString());
+                        System.err.println("GLEventListenerState.moveTo.1a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1);
+                        System.err.println("GLEventListenerState.moveTo.1b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2);
                     }
-                } else {
-                    throw new GLException("Incompatible Surface config - Has Upstream-Surface: Prev-Holder = false, New-Holder = true");
                 }
-            }
 
-            if( aRealized && null != aUpSurface ) {
-                a.getDelegatedDrawable().setRealized(true);
-            }
-            if( DEBUG ) {
-                System.err.println("GLEventListenerState.moveTo.X : has aProxy "+(null!=aProxy));
-                System.err.println("GLEventListenerState.moveTo.X : has aUpSurface "+(null!=aUpSurface));
+                // If using a ProxySurface w/ an upstream surface, set new Screen and close previous one on it
+                if( null != aUpSurface ) {
+                    final MutableGraphicsConfiguration aUpCfg = (MutableGraphicsConfiguration) aUpSurface.getGraphicsConfiguration();
+                    if( null != upstreamDevice ) {
+                        final DefaultGraphicsDevice aUpDevice1 = (DefaultGraphicsDevice) aUpCfg.getScreen().getDevice();
+                        final DefaultGraphicsDevice aUpDevice2 = (DefaultGraphicsDevice)upstreamDevice;
+                        if( !aUpDevice1.getUniqueID().equals( aUpDevice2.getUniqueID() ) ) {
+                            throw new GLException("Incompatible updtream devices: Preserved <"+aUpDevice2.getUniqueID()+">, target <"+aUpDevice1.getUniqueID()+">");
+                        }
+                        if( DEBUG ) {
+                            System.err.println("GLEventListenerState.moveTo.2a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1);
+                            System.err.println("GLEventListenerState.moveTo.2b: up-pres 0x"+Integer.toHexString(aUpDevice2.hashCode())+", "+aUpDevice2);
+                            System.err.println("GLEventListenerState.moveTo.2c:  "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString());
+                        }
+                        DefaultGraphicsDevice.swapDeviceHandleAndOwnership(aUpDevice1, aUpDevice2);
+                        aUpDevice2.close();
+                        if( proxyOwnsUpstreamDevice ) {
+                            aProxy.addUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE );
+                        }
+                        if( DEBUG ) {
+                            System.err.println("GLEventListenerState.moveTo.3a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1);
+                            System.err.println("GLEventListenerState.moveTo.3b: up-pres 0x"+Integer.toHexString(aUpDevice2.hashCode())+", "+aUpDevice2);
+                            System.err.println("GLEventListenerState.moveTo.3c:  "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString());
+                        }
+                    } else {
+                        throw new GLException("Incompatible Surface config - Has Upstream-Surface: Prev-Holder = false, New-Holder = true");
+                    }
+                }
+
+                if( destIsRealized && null != aUpSurface ) {
+                    dest.getDelegatedDrawable().setRealized(true);
+                }
+                if( DEBUG ) {
+                    System.err.println("GLEventListenerState.moveTo.X : has aProxy "+(null!=aProxy));
+                    System.err.println("GLEventListenerState.moveTo.X : has aUpSurface "+(null!=aUpSurface));
+                }
+                dest.setContext( context, false );
+            } finally {
+                if( destSurfaceLocked ) {
+                    destSurface.unlockSurface();
+                }
             }
-            a.setContext( context, false );
         } finally {
-            if( surfaceLocked ) {
-                aSurface.unlockSurface();
-            }
+            destUpstreamLock.unlock();
+        }
+        if( null != destUnlockOperation ) {
+            destUnlockOperation.run();
         }
+
         owner = false;
 
         //
@@ -376,36 +505,36 @@ public class GLEventListenerState {
         aGLCmds.add(setViewport);
         for(int i=0; i<aSz; i++) {
             if( listenersInit[i] ) {
-                aGLCmds.add(new ReshapeGLEventListener( listeners[i] ) );
+                aGLCmds.add(new GLDrawableUtil.ReshapeGLEventListener( listeners[i], false ) );
             }
         }
         aGLCmds.add(glFinish);
-        a.invoke(aRealized, aGLCmds); // only wait if already realized
+        dest.invoke(destIsRealized, aGLCmds); // only wait if already realized
 
         // add all cached GLEventListener to their destination and fix their init-state
         for(int i=0; i<aSz; i++) {
             final GLEventListener l = listeners[i];
-            a.addGLEventListener( l );
-            a.setGLEventListenerInitState(l, listenersInit[i]);
+            dest.addGLEventListener( l );
+            dest.setGLEventListenerInitState(l, listenersInit[i]);
             listeners[i] = null;
         }
 
-        if( hasAnimator ) {
+        if( null != destAnim ) {
             // prefer already bound animator
-            aAnim.add(a);
-            if( aPaused ) {
-                aAnim.resume();
+            destAnim.add(dest);
+            if( destAnimPaused ) {
+                destAnim.resume();
             }
         } else if ( null != anim ) {
             // use previously bound animator
-            anim.add(a); // also handles ECT
+            anim.add(dest); // also handles ECT
             if(animStarted) {
                 anim.start();
             }
         }
     }
 
-    public static final GLRunnable setViewport = new GLRunnable() {
+    private static final GLRunnable setViewport = new GLRunnable() {
         @Override
         public boolean run(final GLAutoDrawable drawable) {
             drawable.getGL().glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight());
@@ -413,23 +542,11 @@ public class GLEventListenerState {
         }
     };
 
-    public static final GLRunnable glFinish = new GLRunnable() {
+    private static final GLRunnable glFinish = new GLRunnable() {
         @Override
         public boolean run(final GLAutoDrawable drawable) {
             drawable.getGL().glFinish();
             return true;
         }
     };
-
-    public static class ReshapeGLEventListener implements GLRunnable {
-        private final GLEventListener listener;
-        public ReshapeGLEventListener(final GLEventListener listener) {
-            this.listener = listener;
-        }
-        @Override
-        public boolean run(final GLAutoDrawable drawable) {
-            listener.reshape(drawable, 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight());
-            return true;
-        }
-    }
 }
index e58d8c6..eae5948 100644 (file)
@@ -638,6 +638,9 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS
    }
 
    @Override
+   public final RecursiveLock getUpstreamLock() { return lock; }
+
+   @Override
    public int getSurfaceWidth() {
       return clientArea.width;
    }
@@ -755,12 +758,12 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS
    }
 
    @Override
-   public boolean invoke(final boolean wait, final GLRunnable runnable) {
+   public boolean invoke(final boolean wait, final GLRunnable runnable) throws IllegalStateException {
       return helper.invoke(this, wait, runnable);
    }
 
    @Override
-   public boolean invoke(final boolean wait, final List<GLRunnable> runnables) {
+   public boolean invoke(final boolean wait, final List<GLRunnable> runnables) throws IllegalStateException {
       return helper.invoke(this, wait, runnables);
    }
 
@@ -879,6 +882,15 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS
    }
 
    /**
+    * {@inheritDoc}
+    * <p>
+    * Implementation always supports multithreading, hence method always returns <code>true</code>.
+    * </p>
+    */
+   @Override
+   public final boolean isThreadGLCapable() { return true; }
+
+   /**
     * Runs the specified action in an SWT compatible thread, which is:
     * <ul>
     *   <li>Mac OSX
index c742842..a2978d6 100644 (file)
@@ -28,6 +28,7 @@
 package com.jogamp.opengl.util;
 
 import javax.media.nativewindow.AbstractGraphicsDevice;
+import javax.media.nativewindow.NativeSurface;
 import javax.media.opengl.GLAnimatorControl;
 import javax.media.opengl.GLAutoDrawable;
 import javax.media.opengl.GLBase;
@@ -36,7 +37,9 @@ import javax.media.opengl.GLContext;
 import javax.media.opengl.GLDrawable;
 import javax.media.opengl.GLEventListener;
 import javax.media.opengl.GLException;
+import javax.media.opengl.GLRunnable;
 
+import com.jogamp.common.util.locks.RecursiveLock;
 import com.jogamp.opengl.GLEventListenerState;
 
 import jogamp.opengl.Debug;
@@ -45,148 +48,235 @@ import jogamp.opengl.Debug;
  * Providing utility functions dealing w/ {@link GLDrawable}s, {@link GLAutoDrawable} and their {@link GLEventListener}.
  */
 public class GLDrawableUtil {
-  protected static final boolean DEBUG = Debug.debug("GLDrawable");
-
-  public static final boolean isAnimatorStartedOnOtherThread(final GLAnimatorControl animatorCtrl) {
-    return ( null != animatorCtrl ) ? animatorCtrl.isStarted() && animatorCtrl.getThread() != Thread.currentThread() : false ;
-  }
-
-  public static final boolean isAnimatorStarted(final GLAnimatorControl animatorCtrl) {
-    return ( null != animatorCtrl ) ? animatorCtrl.isStarted() : false ;
-  }
-
-  public static final boolean isAnimatorAnimatingOnOtherThread(final GLAnimatorControl animatorCtrl) {
-    return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() && animatorCtrl.getThread() != Thread.currentThread() : false ;
-  }
-
-  public static final boolean isAnimatorAnimating(final GLAnimatorControl animatorCtrl) {
-    return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() : false ;
-  }
-
-  /**
-   * Moves the designated {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>.
-   * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved
-   * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call.
-   * <p>
-   * Note that it is only legal to pass <code>preserveInitState := true</code>,
-   * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>.
-   * </p>
-   * <p>
-   * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}.
-   * </p>
-   * @param src
-   * @param dest
-   * @param listener
-   * @param preserveInitState
-   */
-  public static final void moveGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final GLEventListener listener, final boolean preserveInitState) {
-    final boolean initialized = src.getGLEventListenerInitState(listener);
-    src.removeGLEventListener(listener);
-    dest.addGLEventListener(listener);
-    if(preserveInitState && initialized) {
-        dest.setGLEventListenerInitState(listener, true);
-        dest.invoke(false, new GLEventListenerState.ReshapeGLEventListener(listener));
-    } // else .. !init state is default
-  }
-
-  /**
-   * Moves all {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>.
-   * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved
-   * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call.
-   * <p>
-   * Note that it is only legal to pass <code>preserveInitState := true</code>,
-   * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>.
-   * </p>
-   * <p>
-   * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}.
-   * </p>
-   * @param src
-   * @param dest
-   * @param listener
-   * @param preserveInitState
-   */
-  public static final void moveAllGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final boolean preserveInitState) {
-    for(int count = src.getGLEventListenerCount(); 0<count; count--) {
-        final GLEventListener listener = src.getGLEventListener(0);
-        moveGLEventListener(src, dest, listener, preserveInitState);
+    protected static final boolean DEBUG = Debug.debug("GLDrawable");
+
+    public static final boolean isAnimatorStartedOnOtherThread(final GLAnimatorControl animatorCtrl) {
+        return ( null != animatorCtrl ) ? animatorCtrl.isStarted() && animatorCtrl.getThread() != Thread.currentThread() : false ;
+    }
+
+    public static final boolean isAnimatorStarted(final GLAnimatorControl animatorCtrl) {
+        return ( null != animatorCtrl ) ? animatorCtrl.isStarted() : false ;
+    }
+
+    public static final boolean isAnimatorAnimatingOnOtherThread(final GLAnimatorControl animatorCtrl) {
+        return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() && animatorCtrl.getThread() != Thread.currentThread() : false ;
+    }
+
+    public static final boolean isAnimatorAnimating(final GLAnimatorControl animatorCtrl) {
+        return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() : false ;
+    }
+
+    /**
+     * {@link GLRunnable} to issue {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int)},
+     * returning <code>true</code> on {@link GLRunnable#run(GLAutoDrawable)}.
+     */
+    public static class ReshapeGLEventListener implements GLRunnable {
+        private final GLEventListener listener;
+        private final boolean displayAfterReshape;
+        /**
+         *
+         * @param listener
+         * @param displayAfterReshape <code>true</code> to issue {@link GLEventListener#display(GLAutoDrawable)}
+         *                            after {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int)},
+         *                            otherwise false.
+         */
+        public ReshapeGLEventListener(final GLEventListener listener, final boolean displayAfterReshape) {
+            this.listener = listener;
+            this.displayAfterReshape = displayAfterReshape;
+        }
+        @Override
+        public boolean run(final GLAutoDrawable drawable) {
+            listener.reshape(drawable, 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight());
+            if( displayAfterReshape ) {
+                listener.display(drawable);
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Moves the designated {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>.
+     * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved
+     * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call.
+     * <p>
+     * Note that it is only legal to pass <code>preserveInitState := true</code>,
+     * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>.
+     * </p>
+     * <p>
+     * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}.
+     * </p>
+     * @param src
+     * @param dest
+     * @param listener
+     * @param preserveInitState
+     */
+    public static final void moveGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final GLEventListener listener, final boolean preserveInitState) {
+        final boolean initialized = src.getGLEventListenerInitState(listener);
+        if( preserveInitState ) {
+            src.removeGLEventListener(listener);
+            dest.addGLEventListener(listener);
+            if( initialized ) {
+                dest.setGLEventListenerInitState(listener, true);
+                dest.invoke(false, new ReshapeGLEventListener(listener, true));
+            }
+        } else {
+            src.disposeGLEventListener(listener, true);
+            dest.addGLEventListener(listener);
+        }
+    }
+
+    /**
+     * Moves all {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>.
+     * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved
+     * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call.
+     * <p>
+     * Note that it is only legal to pass <code>preserveInitState := true</code>,
+     * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>.
+     * </p>
+     * <p>
+     * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}.
+     * </p>
+     * @param src
+     * @param dest
+     * @param listener
+     * @param preserveInitState
+     */
+    public static final void moveAllGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final boolean preserveInitState) {
+        for(int count = src.getGLEventListenerCount(); 0<count; count--) {
+            final GLEventListener listener = src.getGLEventListener(0);
+            moveGLEventListener(src, dest, listener, preserveInitState);
+        }
+    }
+
+    /**
+     * Swaps the {@link GLContext} and all {@link GLEventListener} between {@link GLAutoDrawable} <code>a</code> and <code>b</code>,
+     * while preserving it's initialized state, resets the GL-Viewport and issuing {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)}.
+     * <p>
+     * The {@link GLAutoDrawable} to {@link GLAnimatorControl} association
+     * is also swapped.
+     * </p>
+     * <p>
+     * If an {@link GLAnimatorControl} is being attached to {@link GLAutoDrawable} <code>a</code> or <code>b</code>
+     * and the current thread is different than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation.
+     * </p>
+     * <p>
+     * During operation, both {@link GLAutoDrawable auto-drawable's}
+     * {@link GLAutoDrawable#getUpstreamLock() upstream-locks} and {@link GLAutoDrawable#getNativeSurface() surfaces} are locked,
+     * hence atomicity of operation is guaranteed,
+     * see <a href="../../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.
+     * </p>
+     * @param a
+     * @param b
+     * @throws GLException if the {@link AbstractGraphicsDevice} are incompatible w/ each other.
+     */
+    public static final void swapGLContextAndAllGLEventListener(final GLAutoDrawable a, final GLAutoDrawable b) {
+        final GLEventListenerState gllsA = GLEventListenerState.moveFrom(a, true);
+        final GLEventListenerState gllsB = GLEventListenerState.moveFrom(b, true);
+        final Runnable gllsAUnlockOp = gllsA.getUnlockSurfaceOp();
+        final Runnable gllsBUnlockOp = gllsB.getUnlockSurfaceOp();
+        try {
+            gllsA.moveTo(b, gllsBUnlockOp);
+            gllsB.moveTo(a, gllsAUnlockOp);
+        } finally {
+            // guarantee unlock in case of an exception
+            gllsBUnlockOp.run();
+            gllsAUnlockOp.run();
+        }
     }
-  }
-
-  /**
-   * Swaps the {@link GLContext} and all {@link GLEventListener} between {@link GLAutoDrawable} <code>a</code> and <code>b</code>,
-   * while preserving it's initialized state, resets the GL-Viewport and issuing {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)}.
-   * <p>
-   * The {@link GLAutoDrawable} to {@link GLAnimatorControl} association
-   * is also swapped.
-   * </p>
-   * <p>
-   * If an {@link GLAnimatorControl} is being attached to {@link GLAutoDrawable} <code>a</code> or <code>b</code>
-   * and the current thread is different than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation.
-   * </p>
-   * @param a
-   * @param b
-   * @throws GLException if the {@link AbstractGraphicsDevice} are incompatible w/ each other.
-   */
-  public static final void swapGLContextAndAllGLEventListener(final GLAutoDrawable a, final GLAutoDrawable b) {
-    final GLEventListenerState gllsA = GLEventListenerState.moveFrom(a);
-    final GLEventListenerState gllsB = GLEventListenerState.moveFrom(b);
-
-    gllsA.moveTo(b);
-    gllsB.moveTo(a);
-  }
-
-  /**
-   * Swaps the {@link GLContext} of given {@link GLAutoDrawable}
-   * and {@link GLAutoDrawable#disposeGLEventListener(GLEventListener, boolean) disposes}
-   * each {@link GLEventListener} w/o removing it.
-   * <p>
-   * The GL-Viewport is reset and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued implicit.
-   * </p>
-   * <p>
-   * If an {@link GLAnimatorControl} is being attached to GLAutoDrawable src or dest and the current thread is different
-   * than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation.
-   * </p>
-   * @param src
-   * @param dest
-   */
-  public static final void swapGLContext(final GLAutoDrawable src, final GLAutoDrawable dest) {
-    final GLAnimatorControl aAnim = src.getAnimator();
-    final GLAnimatorControl bAnim = dest.getAnimator();
-    final boolean aIsPaused = isAnimatorAnimatingOnOtherThread(aAnim) && aAnim.pause();
-    final boolean bIsPaused = isAnimatorAnimatingOnOtherThread(bAnim) && bAnim.pause();
-
-    for(int i = src.getGLEventListenerCount() - 1; 0 <= i; i--) {
-        src.disposeGLEventListener(src.getGLEventListener(i), false);
+
+    /**
+     * Swaps the {@link GLContext} of given {@link GLAutoDrawable}
+     * and {@link GLAutoDrawable#disposeGLEventListener(GLEventListener, boolean) disposes}
+     * each {@link GLEventListener} w/o removing it.
+     * <p>
+     * The GL-Viewport is reset and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued implicit.
+     * </p>
+     * <p>
+     * If an {@link GLAnimatorControl} is being attached to GLAutoDrawable src or dest and the current thread is different
+     * than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation.
+     * </p>
+     * <p>
+     * During operation, both {@link GLAutoDrawable auto-drawable's}
+     * {@link GLAutoDrawable#getUpstreamLock() upstream-locks} and {@link GLAutoDrawable#getNativeSurface() surfaces} are locked,
+     * hence atomicity of operation is guaranteed,
+     * see <a href="../../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.
+     * </p>
+     * @param a
+     * @param b
+     */
+    public static final void swapGLContext(final GLAutoDrawable a, final GLAutoDrawable b) {
+        final GLAnimatorControl aAnim = a.getAnimator();
+        final GLAnimatorControl bAnim = b.getAnimator();
+        final boolean aIsPaused = isAnimatorAnimatingOnOtherThread(aAnim) && aAnim.pause();
+        final boolean bIsPaused = isAnimatorAnimatingOnOtherThread(bAnim) && bAnim.pause();
+
+        final RecursiveLock aUpstreamLock = a.getUpstreamLock();
+        final RecursiveLock bUpstreamLock = b.getUpstreamLock();
+        aUpstreamLock.lock();
+        bUpstreamLock.lock();
+        try {
+            final NativeSurface aSurface = a.getNativeSurface();
+            final boolean aSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < aSurface.lockSurface();
+            if( a.isRealized() && !aSurfaceLocked ) {
+                throw new GLException("Could not lock realized a surface "+a);
+            }
+            final NativeSurface bSurface = b.getNativeSurface();
+            final boolean bSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < bSurface.lockSurface();
+            if( b.isRealized() && !bSurfaceLocked ) {
+                throw new GLException("Could not lock realized b surface "+b);
+            }
+            try {
+                for(int i = a.getGLEventListenerCount() - 1; 0 <= i; i--) {
+                    a.disposeGLEventListener(a.getGLEventListener(i), false);
+                }
+                for(int i = b.getGLEventListenerCount() - 1; 0 <= i; i--) {
+                    b.disposeGLEventListener(b.getGLEventListener(i), false);
+                }
+                b.setContext( a.setContext( b.getContext(), false ), false );
+
+            } finally {
+                if( bSurfaceLocked ) {
+                    bSurface.unlockSurface();
+                }
+                if( aSurfaceLocked ) {
+                    aSurface.unlockSurface();
+                }
+            }
+        } finally {
+            bUpstreamLock.unlock();
+            aUpstreamLock.unlock();
+        }
+        a.invoke(true, setViewport);
+        b.invoke(true, setViewport);
+        if(aIsPaused) { aAnim.resume(); }
+        if(bIsPaused) { bAnim.resume(); }
     }
-    for(int i = dest.getGLEventListenerCount() - 1; 0 <= i; i--) {
-        dest.disposeGLEventListener(dest.getGLEventListener(i), false);
+
+    private static final GLRunnable setViewport = new GLRunnable() {
+        @Override
+        public boolean run(final GLAutoDrawable drawable) {
+            drawable.getGL().glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight());
+            return false; // issue re-display w/ new viewport!
+        }
+    };
+
+    /**
+     * Determines whether the chosen {@link GLCapabilitiesImmutable}
+     * requires a {@link GLDrawable#swapBuffers() swap-buffers}
+     * before reading pixels.
+     * <p>
+     * Usually one uses the {@link GLBase#getDefaultReadBuffer() default-read-buffer}
+     * in which case {@link GLDrawable#swapBuffers() swap-buffers} shall happen <b>after</b> calling reading pixels, the default.
+     * </p>
+     * <p>
+     * However, <i>multisampling</i> offscreen {@link javax.media.opengl.GLFBODrawable}s
+     * utilize {@link GLDrawable#swapBuffers() swap-buffers} to <i>downsample</i>
+     * the multisamples into the readable sampling sink.
+     * In this case, we require {@link GLDrawable#swapBuffers() swap-buffers} <b>before</b> reading pixels.
+     * </p>
+     * @return chosenCaps.isFBO() && chosenCaps.getSampleBuffers()
+     */
+    public static final boolean swapBuffersBeforeRead(final GLCapabilitiesImmutable chosenCaps) {
+        return chosenCaps.isFBO() && chosenCaps.getSampleBuffers();
     }
-    dest.setContext( src.setContext( dest.getContext(), false ), false );
-
-    src.invoke(true, GLEventListenerState.setViewport);
-    dest.invoke(true, GLEventListenerState.setViewport);
-
-    if(aIsPaused) { aAnim.resume(); }
-    if(bIsPaused) { bAnim.resume(); }
-  }
-
-  /**
-   * Determines whether the chosen {@link GLCapabilitiesImmutable}
-   * requires a {@link GLDrawable#swapBuffers() swap-buffers}
-   * before reading pixels.
-   * <p>
-   * Usually one uses the {@link GLBase#getDefaultReadBuffer() default-read-buffer}
-   * in which case {@link GLDrawable#swapBuffers() swap-buffers} shall happen <b>after</b> calling reading pixels, the default.
-   * </p>
-   * <p>
-   * However, <i>multisampling</i> offscreen {@link javax.media.opengl.GLFBODrawable}s
-   * utilize {@link GLDrawable#swapBuffers() swap-buffers} to <i>downsample</i>
-   * the multisamples into the readable sampling sink.
-   * In this case, we require {@link GLDrawable#swapBuffers() swap-buffers} <b>before</b> reading pixels.
-   * </p>
-   * @return chosenCaps.isFBO() && chosenCaps.getSampleBuffers()
-   */
-  public static final boolean swapBuffersBeforeRead(final GLCapabilitiesImmutable chosenCaps) {
-      return chosenCaps.isFBO() && chosenCaps.getSampleBuffers();
-  }
 }
index 377dce1..5745e19 100644 (file)
@@ -42,6 +42,10 @@ package javax.media.opengl;
 
 import java.util.List;
 
+import javax.media.nativewindow.NativeSurface;
+
+import com.jogamp.common.util.locks.RecursiveLock;
+
 import jogamp.opengl.Debug;
 
 /** A higher-level abstraction than {@link GLDrawable} which supplies
@@ -116,6 +120,28 @@ import jogamp.opengl.Debug;
     -Djogl.screenchange.action=true  Enable  the {@link GLDrawable} reconfiguration
     </PRE>
     </p>
+    <h5><a name="locking">GLAutoDrawable Locking</a></h5>
+    GLAutoDrawable implementations perform locking in the following order:
+    <ol>
+      <li> {@link #getUpstreamLock()}.{@link RecursiveLock#lock() lock()}</li>
+      <li> {@link #getNativeSurface()}.{@link NativeSurface#lockSurface() lockSurface()} </li>
+    </ol>
+    and releases the locks accordingly:
+    <ol>
+      <li> {@link #getNativeSurface()}.{@link NativeSurface#unlockSurface() unlockSurface()} </li>
+      <li> {@link #getUpstreamLock()}.{@link RecursiveLock#unlock() unlock()}</li>
+    </ol>
+    Above <i>locking order</i> is mandatory to guarantee
+    atomicity of operation and to avoid race-conditions.
+    A custom implementation or user applications requiring exclusive access
+    shall follow the <i>locking order</i>.
+    See:
+    <ul>
+      <li>{@link #getUpstreamLock()}</li>
+      <li>{@link #invoke(boolean, GLRunnable)}</li>
+      <li>{@link #invoke(boolean, List)}</li>
+    </ul>
+    </p>
   */
 public interface GLAutoDrawable extends GLDrawable {
   /** Flag reflecting whether the {@link GLDrawable} reconfiguration will be issued in
@@ -139,19 +165,22 @@ public interface GLAutoDrawable extends GLDrawable {
   /**
    * Associate the new context, <code>newtCtx</code>, to this auto-drawable.
    * <p>
-   * The current context will be destroyed if <code>destroyPrevCtx</code> is <code>true</code>,
-   * otherwise it will be dis-associated from this auto-drawable
-   * via {@link GLContext#setGLDrawable(GLDrawable, boolean) setGLDrawable(null, true);} first.
-   * </p>
-   * <p>
-   * The new context will be associated with this auto-drawable
-   * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.
-   * </p>
-   * <p>
-   * If the old or new context was current on this thread, it is being released before switching the association.
-   * The new context will be made current afterwards, if it was current before.
-   * However the user shall take extra care that no other thread
-   * attempts to make this context current.
+   * Remarks:
+   * <ul>
+   *   <li>The currently associated context will be destroyed if <code>destroyPrevCtx</code> is <code>true</code>,
+   *       otherwise it will be disassociated from this auto-drawable
+   *       via {@link GLContext#setGLDrawable(GLDrawable, boolean) setGLDrawable(null, true);} including {@link GL#glFinish() glFinish()}.</li>
+   *   <li>The new context will be associated with this auto-drawable
+   *       via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.</li>
+   *   <li>If the old context was current on this thread, it is being released after disassociating this auto-drawable.</li>
+   *   <li>If the new context was current on this thread, it is being released before associating this auto-drawable
+   *       and made current afterwards.</li>
+   *   <li>Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.</li>
+   *   <li>The user shall take extra care of thread synchronization,
+   *       i.e. lock the involved {@link GLAutoDrawable auto-drawable's}
+   *       {@link GLAutoDrawable#getUpstreamLock() upstream-locks} and {@link GLAutoDrawable#getNativeSurface() surfaces}
+   *       to avoid a race condition. See <a href="#locking">GLAutoDrawable Locking</a>.</li>
+   * </ul>
    * </p>
    *
    * @param newCtx the new context, maybe <code>null</code> for dis-association.
@@ -410,17 +439,27 @@ public interface GLAutoDrawable extends GLDrawable {
    * The internal queue of {@link GLRunnable}'s is being flushed with {@link #destroy()}
    * where all blocked callers are being notified.
    * </p>
+   * <p>
+   * To avoid a deadlock situation which causes an {@link IllegalStateException} one should
+   * avoid issuing {@link #invoke(boolean, GLRunnable) invoke} while this <a href="#locking">GLAutoDrawable is being locked</a>.<br>
+   * Detected deadlock situations throwing an {@link IllegalStateException} are:
+   * <ul>
+   *   <li>{@link #getAnimator() Animator} is running on another thread and waiting and is locked on current thread, but is not {@link #isThreadGLCapable() GL-Thread}</li>
+   *   <li>No {@link #getAnimator() Animator} is running on another thread and is locked on current thread, but is not {@link #isThreadGLCapable() GL-Thread}</li>
+   * </ul>
+   * </p>
    *
    * @param wait if <code>true</code> block until execution of <code>glRunnable</code> is finished, otherwise return immediately w/o waiting
    * @param glRunnable the {@link GLRunnable} to execute within {@link #display()}
    * @return <code>true</code> if the {@link GLRunnable} has been processed or queued, otherwise <code>false</code>.
+   * @throws IllegalStateException in case of a detected deadlock situation ahead, see above.
    *
    * @see #setAnimator(GLAnimatorControl)
    * @see #display()
    * @see GLRunnable
    * @see #invoke(boolean, List)
    */
-  public boolean invoke(boolean wait, GLRunnable glRunnable);
+  public boolean invoke(boolean wait, GLRunnable glRunnable) throws IllegalStateException ;
 
   /**
    * Extends {@link #invoke(boolean, GLRunnable)} functionality
@@ -428,9 +467,10 @@ public interface GLAutoDrawable extends GLDrawable {
    * @param wait if <code>true</code> block until execution of the last <code>glRunnable</code> is finished, otherwise return immediately w/o waiting
    * @param glRunnables the {@link GLRunnable}s to execute within {@link #display()}
    * @return <code>true</code> if the {@link GLRunnable}s has been processed or queued, otherwise <code>false</code>.
+   * @throws IllegalStateException in case of a detected deadlock situation ahead, see {@link #invoke(boolean, GLRunnable)}.
    * @see #invoke(boolean, GLRunnable)
    */
-  public boolean invoke(boolean wait, List<GLRunnable> glRunnables);
+  public boolean invoke(boolean wait, List<GLRunnable> glRunnables) throws IllegalStateException;
 
   /** Destroys all resources associated with this GLAutoDrawable,
       inclusive the GLContext.
@@ -556,4 +596,24 @@ public interface GLAutoDrawable extends GLDrawable {
    */
   public Object getUpstreamWidget();
 
+  /**
+   * Returns the recursive lock object of the {@link #getUpstreamWidget() upstream widget}
+   * to synchronize multithreaded access on top of {@link NativeSurface#lockSurface()}.
+   * <p>
+   * See <a href="#locking">GLAutoDrawable Locking</a>.
+   * </p>
+   */
+  public RecursiveLock getUpstreamLock();
+
+  /**
+   * Indicates whether the current thread is capable of
+   * performing OpenGL-related work.
+   * <p>
+   * Implementation utilizes this knowledge to determine
+   * whether {@link #display()} performs the OpenGL commands on the current thread directly
+   * or spawns them on the dedicated OpenGL thread.
+   * </p>
+   */
+  public boolean isThreadGLCapable();
+
 }
index d5d8792..6fb9436 100644 (file)
@@ -48,6 +48,7 @@ import java.util.List;
 import java.util.Set;
 
 import javax.media.nativewindow.AbstractGraphicsDevice;
+import javax.media.nativewindow.NativeSurface;
 
 import jogamp.opengl.Debug;
 import jogamp.opengl.GLContextImpl;
@@ -282,15 +283,24 @@ public abstract class GLContext {
   }
 
   /**
-   * Sets the read/write drawable for framebuffer operations.
+   * Sets the read/write drawable for framebuffer operations, i.e. reassociation of the context's drawable.
    * <p>
    * If the arguments reflect the current state of this context
    * this method is a no-operation and returns the old and current {@link GLDrawable}.
    * </p>
    * <p>
-   * If the context was current on this thread, it is being released before switching the drawable
-   * and made current afterwards. However the user shall take extra care that not other thread
-   * attempts to make this context current. Otherwise a race condition may happen.
+   * Remarks:
+   * <ul>
+   *   <li>{@link GL#glFinish() glFinish()} is issued if context {@link #isCreated()} and a {@link #getGLDrawable() previous drawable} was bound before disassociation.</li>
+   *   <li>If the context was current on this thread, it is being released before drawable reassociation
+   *       and made current afterwards.</li>
+   *   <li>Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.</li>
+   *   <li>The user shall take extra care of thread synchronization,
+   *       i.e. lock the involved {@link GLDrawable#getNativeSurface() drawable's} {@link NativeSurface}s
+   *       to avoid a race condition. In case {@link GLAutoDrawable auto-drawable's} are used,
+   *       their {@link GLAutoDrawable#getUpstreamLock() upstream-lock} must be locked beforehand
+   *       see <a href="GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li>
+   * </ul>
    * </p>
    * @param readWrite The read/write drawable for framebuffer operations, maybe <code>null</code> to remove association.
    * @param setWriteOnly Only change the write-drawable, if <code>setWriteOnly</code> is <code>true</code> and
index 6c64cbe..c8d8d00 100644 (file)
@@ -117,10 +117,36 @@ import jogamp.opengl.ThreadingImpl;
 */
 
 public class Threading {
+    public static enum Mode {
+        /**
+         * Full multithreaded OpenGL,
+         * i.e. any {@link Threading#invoke(boolean, Runnable, Object) invoke}
+         * {@link Threading#invokeOnOpenGLThread(boolean, Runnable) commands}
+         * will be issued on the current thread immediately.
+         */
+        MT(0),
+
+        /** Single-Threaded OpenGL on AWT EDT */
+        ST_AWT(1),
+
+        /** Single-Threaded OpenGL on dedicated worker thread. */
+        ST_WORKER(2);
+
+        public final int id;
+
+        Mode(final int id){
+            this.id = id;
+        }
+    }
 
     /** No reason to ever instantiate this class */
     private Threading() {}
 
+    /** Returns the threading mode */
+    public static Mode getMode() {
+        return ThreadingImpl.getMode();
+    }
+
     /** If an implementation of the javax.media.opengl APIs offers a
         multithreading option but the default behavior is single-threading,
         this API provides a mechanism for end users to disable single-threading
@@ -150,10 +176,14 @@ public class Threading {
         return ThreadingImpl.isToolkitThread();
     }
 
-    /** Indicates whether the current thread is the single thread on
-        which this implementation of the javax.media.opengl APIs
-        performs all of its OpenGL-related work. This method should only
-        be called if the single-thread model is in effect. */
+    /**
+     * Indicates whether the current thread is capable of
+     * performing OpenGL-related work.
+     * <p>
+     * Method always returns <code>true</code>
+     * if {@link #getMode()} == {@link Mode#MT} or {@link #isSingleThreaded()} == <code>false</code>.
+     * </p>
+     */
     public static final boolean isOpenGLThread() throws GLException {
         return ThreadingImpl.isOpenGLThread();
     }
@@ -173,7 +203,7 @@ public class Threading {
     }
 
     /**
-     * If {@link #isSingleThreaded()} <b>and</b> not {@link #isOpenGLThread()}
+     * If not {@link #isOpenGLThread()}
      * <b>and</b> the <code>lock</code> is not being hold by this thread,
      * invoke Runnable <code>r</code> on the OpenGL thread via {@link #invokeOnOpenGLThread(boolean, Runnable)}.
      * <p>
@@ -186,7 +216,7 @@ public class Threading {
      * @throws GLException
      */
     public static final void invoke(final boolean wait, final Runnable r, final Object lock) throws GLException {
-        if ( isSingleThreaded() && !isOpenGLThread() &&
+        if ( !isOpenGLThread() &&
              ( null == lock || !Thread.holdsLock(lock) ) ) {
             invokeOnOpenGLThread(wait, r);
         } else {
index 2d5e124..a8aa738 100644 (file)
@@ -300,6 +300,12 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing
   }
 
   @Override
+  public final RecursiveLock getUpstreamLock() { return lock; }
+
+  @Override
+  public final boolean isThreadGLCapable() { return Threading.isOpenGLThread(); }
+
+  @Override
   public void setShallUseOffscreenLayer(final boolean v) {
       shallUseOffscreenLayer = v;
   }
@@ -1040,12 +1046,12 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing
   }
 
   @Override
-  public boolean invoke(final boolean wait, final GLRunnable glRunnable) {
+  public boolean invoke(final boolean wait, final GLRunnable glRunnable) throws IllegalStateException {
     return helper.invoke(this, wait, glRunnable);
   }
 
   @Override
-  public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) {
+  public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) throws IllegalStateException {
     return helper.invoke(this, wait, glRunnables);
   }
 
index 549b6e9..f0ba08c 100644 (file)
@@ -98,6 +98,8 @@ import jogamp.opengl.util.glsl.GLSLTextureRaster;
 
 import com.jogamp.common.util.PropertyAccess;
 import com.jogamp.common.util.awt.AWTEDTExecutor;
+import com.jogamp.common.util.locks.LockFactory;
+import com.jogamp.common.util.locks.RecursiveLock;
 import com.jogamp.nativewindow.awt.AWTPrintLifecycle;
 import com.jogamp.nativewindow.awt.AWTWindowClosingProtocol;
 import com.jogamp.opengl.FBObject;
@@ -232,6 +234,9 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing
       return singleAWTGLPixelBufferProvider;
   }
 
+  /** Currently not used internally, exist merely to satisfy {@link #getUpstreamLock()}. */
+  private final RecursiveLock lock = LockFactory.createRecursiveLock();
+
   private final GLDrawableHelper helper;
   private boolean autoSwapBufferMode;
 
@@ -432,6 +437,12 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing
   }
 
   @Override
+  public final RecursiveLock getUpstreamLock() { return lock; }
+
+  @Override
+  public final boolean isThreadGLCapable() { return EventQueue.isDispatchThread(); }
+
+  @Override
   public void display() {
     if( isShowing || ( printActive && isVisible() ) ) {
         if (EventQueue.isDispatchThread()) {
@@ -927,12 +938,12 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing
   }
 
   @Override
-  public boolean invoke(final boolean wait, final GLRunnable glRunnable) {
+  public boolean invoke(final boolean wait, final GLRunnable glRunnable) throws IllegalStateException {
     return helper.invoke(this, wait, glRunnable);
   }
 
   @Override
-  public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) {
+  public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) throws IllegalStateException {
     return helper.invoke(this, wait, glRunnables);
   }
 
index fce5c1f..605c3fc 100644 (file)
@@ -57,9 +57,11 @@ import com.jogamp.opengl.GLStateKeeper;
 
 
 /**
- * Abstract common code for GLAutoDrawable implementations.
+ * Abstract common code for GLAutoDrawable implementations
+ * utilizing multithreading, i.e. {@link #isThreadGLCapable()} always returns <code>true</code>.
  *
  * @see GLAutoDrawable
+ * @see GLAutoDrawable#getThreadingMode()
  * @see GLAutoDrawableDelegate
  * @see GLOffscreenAutoDrawable
  * @see GLOffscreenAutoDrawableImpl
@@ -123,9 +125,6 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
         helper.setSharedAutoDrawable(this, sharedAutoDrawable);
     }
 
-    /** Returns the recursive lock object of the upstream implementation, which synchronizes multithreaded access on top of {@link NativeSurface#lockSurface()}. */
-    protected abstract RecursiveLock getLock();
-
     @Override
     public final GLStateKeeper.Listener setGLStateKeeperListener(final Listener l) {
         final GLStateKeeper.Listener pre = glStateKeeperListener;
@@ -240,7 +239,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
                 System.err.println("GLAutoDrawableBase.sizeChanged: ("+getThreadName()+"): "+newWidth+"x"+newHeight+" - surfaceHandle 0x"+Long.toHexString(surfaceHandle));
             }
             if( ! _drawable.getChosenGLCapabilities().isOnscreen() ) {
-                final RecursiveLock _lock = getLock();
+                final RecursiveLock _lock = getUpstreamLock();
                 _lock.lock();
                 try {
                     final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, context, newWidth, newHeight);
@@ -332,7 +331,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
      * Calls {@link #destroyImplInLock()} while claiming the lock.
      */
     protected final void defaultDestroy() {
-        final RecursiveLock lock = getLock();
+        final RecursiveLock lock = getUpstreamLock();
         lock.lock();
         try {
             destroyImplInLock();
@@ -380,7 +379,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
     }
 
     public final void defaultSwapBuffers() throws GLException {
-        final RecursiveLock _lock = getLock();
+        final RecursiveLock _lock = getUpstreamLock();
         _lock.lock();
         try {
             if(null != drawable) {
@@ -421,7 +420,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
             destroy();
             return;
         }
-        final RecursiveLock _lock = getLock();
+        final RecursiveLock _lock = getUpstreamLock();
         _lock.lock();
         try {
             if( null == context ) {
@@ -452,7 +451,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
     }
 
     protected final GLEventListener defaultDisposeGLEventListener(final GLEventListener listener, final boolean remove) {
-        final RecursiveLock _lock = getLock();
+        final RecursiveLock _lock = getUpstreamLock();
         _lock.lock();
         try {
             return helper.disposeGLEventListener(GLAutoDrawableBase.this, drawable, context, listener, remove);
@@ -473,7 +472,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
 
     @Override
     public final GLContext setContext(final GLContext newCtx, final boolean destroyPrevCtx) {
-        final RecursiveLock lock = getLock();
+        final RecursiveLock lock = getUpstreamLock();
         lock.lock();
         try {
             final GLContext oldCtx = context;
@@ -571,12 +570,12 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
     }
 
     @Override
-    public final boolean invoke(final boolean wait, final GLRunnable glRunnable) {
+    public final boolean invoke(final boolean wait, final GLRunnable glRunnable) throws IllegalStateException {
         return helper.invoke(this, wait, glRunnable);
     }
 
     @Override
-    public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) {
+    public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) throws IllegalStateException {
         return helper.invoke(this, wait, glRunnables);
     }
 
@@ -604,6 +603,15 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
         return additionalCtxCreationFlags;
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Implementation always supports multithreading, hence method always returns <code>true</code>.
+     * </p>
+     */
+    @Override
+    public final boolean isThreadGLCapable() { return true; }
+
     //
     // FPSCounter
     //
@@ -664,7 +672,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
 
     @Override
     public final GLContext createContext(final GLContext shareWith) {
-        final RecursiveLock lock = getLock();
+        final RecursiveLock lock = getUpstreamLock();
         lock.lock();
         try {
             if(drawable != null) {
@@ -680,7 +688,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
 
     @Override
     public final void setRealized(final boolean realized) {
-        final RecursiveLock _lock = getLock();
+        final RecursiveLock _lock = getUpstreamLock();
         _lock.lock();
         try {
             final GLDrawable _drawable = drawable;
index c175243..d7578b9 100644 (file)
@@ -230,6 +230,8 @@ public abstract class GLContextImpl extends GLContext {
         if(!lockHeld) {
             makeCurrent();
         }
+        // sync GL ctx w/ drawable's framebuffer before de-association
+        gl.glFinish();
         associateDrawable(false);
         if(!lockHeld) {
             release();
index eac14fd..ad1b9a5 100644 (file)
@@ -208,19 +208,23 @@ public class GLDrawableHelper {
   /**
    * Switch {@link GLContext} / {@link GLDrawable} association.
    * <p>
-   * The <code>oldCtx</code> will be destroyed if <code>destroyPrevCtx</code> is <code>true</code>,
-   * otherwise dis-associate <code>oldCtx</code> from <code>drawable</code>
-   * via {@link GLContext#setGLDrawable(GLDrawable, boolean) oldCtx.setGLDrawable(null, true);}.
-   * </p>
-   * <p>
-   * Re-associate <code>newCtx</code> with <code>drawable</code>
-   * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.
-   * </p>
-   * <p>
-   * If the old or new context was current on this thread, it is being released before switching the drawable.
-   * </p>
-   * <p>
-   * No locking is being performed on the drawable, caller is required to take care of it.
+   * Remarks:
+   * <ul>
+   *   <li>The <code>oldCtx</code> will be destroyed if <code>destroyPrevCtx</code> is <code>true</code>,
+   *       otherwise disassociate <code>oldCtx</code> from <code>drawable</code>
+   *       via {@link GLContext#setGLDrawable(GLDrawable, boolean) oldCtx.setGLDrawable(null, true);} including {@link GL#glFinish() glFinish()}.</li>
+   *   <li>Reassociate <code>newCtx</code> with <code>drawable</code>
+   *       via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.</li>
+   *   <li>If the old context was current on this thread, it is being released after disassociating the drawable.</li>
+   *   <li>If the new context was current on this thread, it is being released before associating the drawable
+   *       and made current afterwards.</li>
+   *   <li>Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.</li>
+   *   <li>The user shall take extra care of thread synchronization,
+   *       i.e. lock the involved {@link GLDrawable#getNativeSurface() drawable's} {@link NativeSurface}s
+   *       to avoid a race condition. In case {@link GLAutoDrawable auto-drawable's} are used,
+   *       their {@link GLAutoDrawable#getUpstreamLock() upstream-lock} must be locked beforehand
+   *       see <a href="../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li>
+   * </ul>
    * </p>
    *
    * @param drawable the drawable which context is changed
@@ -795,6 +799,30 @@ public class GLDrawableHelper {
     return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() : false ;
   }
 
+  public static final boolean isLockedByOtherThread(final GLAutoDrawable d) {
+      final Thread currentThread = Thread.currentThread();
+      final Thread upstreamLockOwner = d.getUpstreamLock().getOwner();
+      if( null != upstreamLockOwner && currentThread != upstreamLockOwner ) {
+          return true;
+      } else {
+          final NativeSurface s = d.getNativeSurface();
+          final Thread surfaceLockOwner = null != s ? s.getSurfaceLockOwner() : null;
+          return null != surfaceLockOwner  && currentThread != surfaceLockOwner;
+      }
+  }
+
+  public static final boolean isLockedByThisThread(final GLAutoDrawable d) {
+      final Thread currentThread = Thread.currentThread();
+      final Thread upstreamLockOwner = d.getUpstreamLock().getOwner();
+      if( currentThread == upstreamLockOwner ) {
+          return true;
+      } else {
+          final NativeSurface s = d.getNativeSurface();
+          final Thread surfaceLockOwner = null != s ? s.getSurfaceLockOwner() : null;
+          return currentThread == surfaceLockOwner;
+      }
+  }
+
   /**
    * <p>
    * If <code>wait</code> is <code>true</code> the call blocks until the <code>glRunnable</code>
@@ -805,13 +833,32 @@ public class GLDrawableHelper {
    * the call is ignored and returns <code>false</code>.<br>
    * This helps avoiding deadlocking the caller.
    * </p>
+   * <p>
+   * <pre>
+   * 0 == deferredHere && 0 == isGLThread -> display() will issue on GL thread, blocking!
+   *
+   *         deferredHere wait isGLThread   lockedByThisThread  Note
+   *  OK     0            x    1            x
+   *  OK     0            x    0            0
+   *  ERROR  0            x    0            1                   Will be deferred on GL thread by display() (blocking),
+   *                                                            but locked by this thread -> ERROR
+   *
+   *         1            0    x            x                   All good, due to no wait, non blocking
+   *
+   *         1            1    1            0
+   *         1            1    0            0
+   *  SWITCH 1            1    1            1                   Run immediately, don't defer since locked by this thread, but isGLThread
+   *  ERROR  1            1    0            1                   Locked by this thread, but _not_ isGLThread -> ERROR
+   * </pre>
+   * </p>
    *
    * @param drawable the {@link GLAutoDrawable} to be used
    * @param wait if <code>true</code> block until execution of <code>glRunnable</code> is finished, otherwise return immediatly w/o waiting
    * @param glRunnable the {@link GLRunnable} to execute within {@link #display()}
    * @return <code>true</code> if the {@link GLRunnable} has been processed or queued, otherwise <code>false</code>.
+   * @throws IllegalStateException in case the drawable is locked by this thread, no animator is running on another thread and <code>wait</code> is <code>true</code>.
    */
-  public final boolean invoke(final GLAutoDrawable drawable, boolean wait, final GLRunnable glRunnable) {
+  public final boolean invoke(final GLAutoDrawable drawable, boolean wait, final GLRunnable glRunnable) throws IllegalStateException {
     if( null == glRunnable || null == drawable ||
         wait && ( !drawable.isRealized() || null==drawable.getContext() ) ) {
         return false;
@@ -821,18 +868,33 @@ public class GLDrawableHelper {
     final Object rTaskLock = new Object();
     Throwable throwable = null;
     synchronized(rTaskLock) {
-        final boolean deferred;
+        boolean deferredHere;
         synchronized(glRunnablesLock) {
-            deferred = isAnimatorAnimatingOnOtherThread();
-            if(!deferred) {
-                wait = false; // don't wait if exec immediatly
+            final boolean isGLThread = drawable.isThreadGLCapable();
+            deferredHere = isAnimatorAnimatingOnOtherThread();
+            if( deferredHere ) {
+                if( wait && isLockedByThisThread(drawable) ) {
+                    if( isGLThread ) {
+                        // Run immediately, don't defer since locked by this thread, but isGLThread
+                        deferredHere = false;
+                    } else {
+                        // Locked by this thread, but _not_ isGLThread -> ERROR
+                        throw new IllegalStateException("Deferred, wait, isLocked on current and not GL-Thread: thread "+Thread.currentThread());
+                    }
+                }
+            } else {
+                if( !isGLThread && isLockedByThisThread(drawable) ) {
+                    // Will be deferred on GL thread by display() (blocking), but locked by this thread -> ERROR
+                    throw new IllegalStateException("Not deferred, isLocked on current and not GL-Thread: thread "+Thread.currentThread());
+                }
+                wait = false; // don't wait if exec immediately
             }
             rTask = new GLRunnableTask(glRunnable,
                                        wait ? rTaskLock : null,
                                        wait  /* catch Exceptions if waiting for result */);
             glRunnables.add(rTask);
         }
-        if( !deferred ) {
+        if( !deferredHere ) {
             drawable.display();
         } else if( wait ) {
             try {
@@ -851,7 +913,16 @@ public class GLDrawableHelper {
     return true;
   }
 
-  public final boolean invoke(final GLAutoDrawable drawable, boolean wait, final List<GLRunnable> newGLRunnables) {
+  /**
+   * @see #invoke(GLAutoDrawable, boolean, GLRunnable)
+   *
+   * @param drawable
+   * @param wait
+   * @param newGLRunnables
+   * @return
+   * @throws IllegalStateException
+   */
+  public final boolean invoke(final GLAutoDrawable drawable, boolean wait, final List<GLRunnable> newGLRunnables) throws IllegalStateException {
     if( null == newGLRunnables || newGLRunnables.size() == 0 || null == drawable ||
         wait && ( !drawable.isRealized() || null==drawable.getContext() ) ) {
         return false;
@@ -862,10 +933,25 @@ public class GLDrawableHelper {
     final Object rTaskLock = new Object();
     Throwable throwable = null;
     synchronized(rTaskLock) {
-        final boolean deferred;
+        boolean deferredHere;
         synchronized(glRunnablesLock) {
-            deferred = isAnimatorAnimatingOnOtherThread() || !drawable.isRealized();
-            if(!deferred) {
+            final boolean isGLThread = drawable.isThreadGLCapable();
+            deferredHere = isAnimatorAnimatingOnOtherThread();
+            if( deferredHere ) {
+                if( wait && isLockedByThisThread(drawable) ) {
+                    if( isGLThread ) {
+                        // Run immediately, don't defer since locked by this thread, but isGLThread
+                        deferredHere = false;
+                    } else {
+                        // Locked by this thread, but _not_ isGLThread -> ERROR
+                        throw new IllegalStateException("Deferred, wait, isLocked on current and not GL-Thread: thread "+Thread.currentThread());
+                    }
+                }
+            } else {
+                if( !isGLThread && isLockedByThisThread(drawable) ) {
+                    // Will be deferred on GL thread by display() (blocking), but locked by this thread -> ERROR
+                    throw new IllegalStateException("Not deferred, isLocked on current and not GL-Thread: thread "+Thread.currentThread());
+                }
                 wait = false; // don't wait if exec immediately
             }
             for(int i=0; i<count-1; i++) {
@@ -876,7 +962,7 @@ public class GLDrawableHelper {
                                        wait  /* catch Exceptions if waiting for result */);
             glRunnables.add(rTask);
         }
-        if( !deferred ) {
+        if( !deferredHere ) {
             drawable.display();
         } else if( wait ) {
             try {
index 2b017e8..7b405e5 100644 (file)
@@ -41,6 +41,7 @@ import java.security.PrivilegedAction;
 import javax.media.nativewindow.NativeWindowFactory;
 import javax.media.opengl.GLException;
 import javax.media.opengl.GLProfile;
+import javax.media.opengl.Threading.Mode;
 
 import com.jogamp.common.JogampRuntimeException;
 import com.jogamp.common.util.PropertyAccess;
@@ -49,16 +50,6 @@ import com.jogamp.common.util.ReflectionUtil;
 /** Implementation of the {@link javax.media.opengl.Threading} class. */
 
 public class ThreadingImpl {
-    public enum Mode {
-        MT(0), ST_AWT(1), ST_WORKER(2);
-
-        public final int id;
-
-        Mode(final int id){
-            this.id = id;
-        }
-    }
-
     protected static final boolean DEBUG = Debug.debug("Threading");
 
     private static boolean singleThreaded;
@@ -93,28 +84,23 @@ public class ThreadingImpl {
 
                         _isX11 = NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(false);
 
-                        // default setting
-                        singleThreaded = true;
-                        mode  = ( hasAWT ? Mode.ST_AWT : Mode.ST_WORKER );
-
                         if (singleThreadProp != null) {
                             if (singleThreadProp.equals("true") ||
                                 singleThreadProp.equals("auto")) {
-                                singleThreaded = true;
-                                mode  = ( hasAWT ? Mode.ST_AWT : Mode.ST_WORKER );
+                                mode  = ( hasAWT ? Mode.ST_AWT : Mode.MT );
                             } else if (singleThreadProp.equals("worker")) {
-                                singleThreaded = true;
                                 mode = Mode.ST_WORKER;
                             } else if (hasAWT && singleThreadProp.equals("awt")) {
-                                singleThreaded = true;
                                 mode = Mode.ST_AWT;
                             } else if (singleThreadProp.equals("false")) {
-                                singleThreaded = false;
                                 mode = Mode.MT;
                             } else {
                                 throw new RuntimeException("Unsupported value for property jogl.1thread: "+singleThreadProp+", should be [true/auto, worker, awt or false]");
                             }
+                        } else {
+                            mode  = ( hasAWT ? Mode.ST_AWT : Mode.MT );
                         }
+                        singleThreaded = Mode.MT != mode;
 
                         ToolkitThreadingPlugin threadingPlugin=null;
                         if(hasAWT) {
@@ -155,9 +141,11 @@ public class ThreadingImpl {
         method. This method should be called as early as possible in an
         application. */
     public static final void disableSingleThreading() {
-        singleThreaded = false;
-        if (Debug.verbose()) {
-            System.err.println("Application forced disabling of single-threading of javax.media.opengl implementation");
+        if( Mode.MT != mode ) {
+            singleThreaded = false;
+            if (Debug.verbose()) {
+                System.err.println("Application forced disabling of single-threading of javax.media.opengl implementation");
+            }
         }
     }
 
@@ -167,22 +155,28 @@ public class ThreadingImpl {
         return singleThreaded;
     }
 
-    /** Indicates whether the current thread is the single thread on
-        which this implementation of the javax.media.opengl APIs
-        performs all of its OpenGL-related work. This method should only
-        be called if the single-thread model is in effect. */
+    /**
+     * Indicates whether the current thread is capable of
+     * performing OpenGL-related work.
+     * <p>
+     * Method always returns <code>true</code>
+     * if {@link #getMode()} == {@link Mode#MT} or {@link #isSingleThreaded()} == <code>false</code>.
+     * </p>
+     */
     public static final boolean isOpenGLThread() throws GLException {
-        if(null!=threadingPlugin) {
+        if( Mode.MT == mode || !singleThreaded ) {
+            return true;
+        } else if( null != threadingPlugin ) {
             return threadingPlugin.isOpenGLThread();
-        }
-
-        switch (mode) {
-            case ST_AWT:
-                throw new InternalError();
-            case ST_WORKER:
-                return GLWorkerThread.isWorkerThread();
-            default:
-                throw new InternalError("Illegal single-threading mode " + mode);
+        } else {
+            switch (mode) {
+                case ST_AWT:
+                    throw new InternalError();
+                case ST_WORKER:
+                    return GLWorkerThread.isWorkerThread();
+                default:
+                    throw new InternalError("Illegal single-threading mode " + mode);
+            }
         }
     }
 
@@ -213,6 +207,10 @@ public class ThreadingImpl {
                 invokeOnWorkerThread(wait, r);
                 break;
 
+            case MT:
+                r.run();
+                break;
+
             default:
                 throw new InternalError("Illegal single-threading mode " + mode);
         }
index 26ec627..3f8910f 100644 (file)
@@ -108,6 +108,10 @@ public class AWTThreadingPlugin implements ToolkitThreadingPlugin {
         ThreadingImpl.invokeOnWorkerThread(wait, r);
         break;
 
+      case MT:
+        r.run();
+        break;
+
       default:
         throw new InternalError("Illegal single-threading mode " + ThreadingImpl.getMode());
     }
index 733f12d..4d764dc 100644 (file)
@@ -43,16 +43,16 @@ package javax.media.nativewindow;
  * </p>
  */
 public interface NativeSurface extends SurfaceUpdatedListener {
-  /** Unlocked state */
+  /** Unlocked state, {@value}. */
   public static final int LOCK_SURFACE_UNLOCKED = 0;
 
-  /** Returned by {@link #lockSurface()} if the surface is not ready to be locked. */
+  /** Returned by {@link #lockSurface()} if the surface is not ready to be locked, {@value}. */
   public static final int LOCK_SURFACE_NOT_READY = 1;
 
-  /** Returned by {@link #lockSurface()} if the surface is locked, but has changed. */
+  /** Returned by {@link #lockSurface()} if the surface is locked, but has changed, {@value}. */
   public static final int LOCK_SURFACE_CHANGED = 2;
 
-  /** Returned by {@link #lockSurface()} if the surface is locked, and is unchanged. */
+  /** Returned by {@link #lockSurface()} if the surface is locked, and is unchanged, {@value}. */
   public static final int LOCK_SUCCESS = 3;
 
   /**
index b9c4e35..24a203b 100644 (file)
@@ -143,6 +143,11 @@ public class GLWindow extends GLAutoDrawableBase implements GLAutoDrawable, Wind
         return window;
     }
 
+    @Override
+    public final RecursiveLock getUpstreamLock() {
+        return window.getLock();
+    }
+
     /**
      * Creates a new GLWindow attaching a new Window referencing a
      * new default Screen and default Display with the given GLCapabilities.
@@ -643,11 +648,6 @@ public class GLWindow extends GLAutoDrawableBase implements GLAutoDrawable, Wind
     //
 
     @Override
-    protected final RecursiveLock getLock() {
-        return window.getLock();
-    }
-
-    @Override
     public void display() {
         if( !isNativeValid() || !isVisible() ) { return; }
 
http://JogAmp.org git info: FAQ, tutorial and man pages.