summaryrefslogtreecommitdiffstats
path: root/src/jogl/classes
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2014-07-27 03:49:21 +0200
committerSven Gothel <[email protected]>2014-07-27 03:49:21 +0200
commitc77b8f586cb2553582a42f5b90aeee5ef85f1efe (patch)
tree2f304461ff3d87b75f347dd5cf36a580aa73c854 /src/jogl/classes
parent37760af388303834e359703aad9562ce6165845f (diff)
Bug 1033: Guarantee atomicity of high-level GLAutoDrawable operations, avoiding race conditions.
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
Diffstat (limited to 'src/jogl/classes')
-rw-r--r--src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java2
-rw-r--r--src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java491
-rw-r--r--src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java16
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java372
-rw-r--r--src/jogl/classes/javax/media/opengl/GLAutoDrawable.java90
-rw-r--r--src/jogl/classes/javax/media/opengl/GLContext.java18
-rw-r--r--src/jogl/classes/javax/media/opengl/Threading.java42
-rw-r--r--src/jogl/classes/javax/media/opengl/awt/GLCanvas.java10
-rw-r--r--src/jogl/classes/javax/media/opengl/awt/GLJPanel.java15
-rw-r--r--src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java36
-rw-r--r--src/jogl/classes/jogamp/opengl/GLContextImpl.java2
-rw-r--r--src/jogl/classes/jogamp/opengl/GLDrawableHelper.java134
-rw-r--r--src/jogl/classes/jogamp/opengl/ThreadingImpl.java70
-rw-r--r--src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java4
14 files changed, 868 insertions, 434 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java b/src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java
index f9a7ab029..4ef717a38 100644
--- a/src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java
+++ b/src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java
@@ -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() {
diff --git a/src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java b/src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java
index a7ba141bb..bfd5fe115 100644
--- a/src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java
+++ b/src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java
@@ -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;
- }
- }
}
diff --git a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java
index e58d8c64a..eae5948ed 100644
--- a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java
+++ b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java
@@ -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
diff --git a/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java b/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java
index c74284299..a2978d6d5 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java
@@ -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();
- }
}
diff --git a/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java b/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java
index 377dce190..5745e197f 100644
--- a/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java
+++ b/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java
@@ -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();
+
}
diff --git a/src/jogl/classes/javax/media/opengl/GLContext.java b/src/jogl/classes/javax/media/opengl/GLContext.java
index d5d8792d8..6fb943613 100644
--- a/src/jogl/classes/javax/media/opengl/GLContext.java
+++ b/src/jogl/classes/javax/media/opengl/GLContext.java
@@ -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
diff --git a/src/jogl/classes/javax/media/opengl/Threading.java b/src/jogl/classes/javax/media/opengl/Threading.java
index 6c64cbe31..c8d8d0071 100644
--- a/src/jogl/classes/javax/media/opengl/Threading.java
+++ b/src/jogl/classes/javax/media/opengl/Threading.java
@@ -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 {
diff --git a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java
index 2d5e12429..a8aa7382d 100644
--- a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java
+++ b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java
@@ -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);
}
diff --git a/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java b/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java
index 549b6e96f..f0ba08c3e 100644
--- a/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java
+++ b/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java
@@ -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);
}
diff --git a/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java
index fce5c1fcc..605c3fce8 100644
--- a/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java
+++ b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java
@@ -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;
diff --git a/src/jogl/classes/jogamp/opengl/GLContextImpl.java b/src/jogl/classes/jogamp/opengl/GLContextImpl.java
index c175243ae..d7578b9ea 100644
--- a/src/jogl/classes/jogamp/opengl/GLContextImpl.java
+++ b/src/jogl/classes/jogamp/opengl/GLContextImpl.java
@@ -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();
diff --git a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java
index eac14fdff..ad1b9a556 100644
--- a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java
+++ b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java
@@ -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 {
diff --git a/src/jogl/classes/jogamp/opengl/ThreadingImpl.java b/src/jogl/classes/jogamp/opengl/ThreadingImpl.java
index 2b017e8e9..7b405e524 100644
--- a/src/jogl/classes/jogamp/opengl/ThreadingImpl.java
+++ b/src/jogl/classes/jogamp/opengl/ThreadingImpl.java
@@ -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);
}
diff --git a/src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java b/src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java
index 26ec62785..3f8910fb5 100644
--- a/src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java
+++ b/src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java
@@ -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());
}