From c77b8f586cb2553582a42f5b90aeee5ef85f1efe Mon Sep 17 00:00:00 2001
From: Sven Gothel
Date: Sun, 27 Jul 2014 03:49:21 +0200
Subject: 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
---
.../com/jogamp/opengl/GLAutoDrawableDelegate.java | 2 +-
.../com/jogamp/opengl/GLEventListenerState.java | 491 +++++++++++++--------
.../classes/com/jogamp/opengl/swt/GLCanvas.java | 16 +-
.../com/jogamp/opengl/util/GLDrawableUtil.java | 372 ++++++++++------
.../classes/javax/media/opengl/GLAutoDrawable.java | 90 +++-
src/jogl/classes/javax/media/opengl/GLContext.java | 18 +-
src/jogl/classes/javax/media/opengl/Threading.java | 42 +-
.../classes/javax/media/opengl/awt/GLCanvas.java | 10 +-
.../classes/javax/media/opengl/awt/GLJPanel.java | 15 +-
.../classes/jogamp/opengl/GLAutoDrawableBase.java | 36 +-
src/jogl/classes/jogamp/opengl/GLContextImpl.java | 2 +
.../classes/jogamp/opengl/GLDrawableHelper.java | 134 +++++-
src/jogl/classes/jogamp/opengl/ThreadingImpl.java | 70 ++-
.../jogamp/opengl/awt/AWTThreadingPlugin.java | 4 +
14 files changed, 868 insertions(+), 434 deletions(-)
(limited to 'src/jogl/classes')
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 {
*
* The returned GLEventListenerState instance is the {@link #isOwner() owner of the components}.
*
+ *
+ * Locking is performed on the {@link GLAutoDrawable auto-drawable's}
+ * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}.
+ * See GLAutoDrawable Locking.
+ *
*
- * @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.
+ *
+ * Note that all components are removed from the {@link GLAutoDrawable},
+ * i.e. the {@link GLContext}, all {@link GLEventListener}.
+ *
+ *
+ * If the {@link GLAutoDrawable} was added to a {@link GLAnimatorControl}, it is removed
+ * and the {@link GLAnimatorControl} added to the GLEventListenerState.
+ *
+ *
+ * The returned GLEventListenerState instance is the {@link #isOwner() owner of the components}.
+ *
+ *
+ * Locking is performed on the {@link GLAutoDrawable auto-drawable's}
+ * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface},
+ * which is not released if keepLocked
is true
.
+ * See GLAutoDrawable Locking.
+ *
+ *
+ * keepLocked
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)}.
+ *
+ *
+ * @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
*
+ * Locking is performed on the {@link GLAutoDrawable auto-drawable's}
+ * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}.
+ * See GLAutoDrawable Locking.
+ *
+ *
* Note: After this operation, the GLEventListenerState reference should be released.
*
*
- * @param a {@link GLAutoDrawable} destination to move GLEventListenerState components to
+ * @param dest {@link GLAutoDrawable} destination to move GLEventListenerState components to
*
- *
+ * @throws GLException if a realized surface could not be locked.
* @throws GLException if this preserved {@link AbstractGraphicsDevice} is incompatible w/ the given destination one.
+ *
*
* @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}.
+ *
+ * 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.
+ *
+ *
+ * Locking is performed on the {@link GLAutoDrawable auto-drawable's}
+ * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}.
+ * See GLAutoDrawable Locking.
+ *
+ *
+ * If the {@link GLAutoDrawable} dest
has been kept locked by {@link #moveFrom(GLAutoDrawable, boolean)},
+ * it's {@link #getUnlockSurfaceOp()} shall be passed here to destUnlockOperation
to be unlocked.
+ *
+ *
+ * Note: After this operation, the GLEventListenerState reference should be released.
+ *
+ *
+ * @param dest {@link GLAutoDrawable} destination to move GLEventListenerState components to
+ * @param destUnlockOperation optional unlock operation for dest
, 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.
+ *
+ *
+ * @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 aGLCmds = new ArrayList();
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 runnables) {
+ public boolean invoke(final boolean wait, final List runnables) throws IllegalStateException {
return helper.invoke(this, wait, runnables);
}
@@ -878,6 +881,15 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS
runInGLThread(swapBuffersOnGLAction);
}
+ /**
+ * {@inheritDoc}
+ *
+ * Implementation always supports multithreading, hence method always returns true
.
+ *
+ */
+ @Override
+ public final boolean isThreadGLCapable() { return true; }
+
/**
* Runs the specified action in an SWT compatible thread, which is:
*
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} src
to dest
.
- * If preserveInitState
is true
, it's initialized state is preserved
- * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call.
- *
- * Note that it is only legal to pass preserveInitState := true
,
- * if the {@link GLContext} of both src
and dest
are shared, or has itself moved from src
to dest
.
- *
- *
- * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}.
- *
- * @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} src
to dest
.
- * If preserveInitState
is true
, it's initialized state is preserved
- * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call.
- *
- * Note that it is only legal to pass preserveInitState := true
,
- * if the {@link GLContext} of both src
and dest
are shared, or has itself moved from src
to dest
.
- *
- *
- * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}.
- *
- * @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(); 0true on {@link GLRunnable#run(GLAutoDrawable)}.
+ */
+ public static class ReshapeGLEventListener implements GLRunnable {
+ private final GLEventListener listener;
+ private final boolean displayAfterReshape;
+ /**
+ *
+ * @param listener
+ * @param displayAfterReshape true
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} src
to dest
.
+ * If preserveInitState
is true
, it's initialized state is preserved
+ * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call.
+ *
+ * Note that it is only legal to pass preserveInitState := true
,
+ * if the {@link GLContext} of both src
and dest
are shared, or has itself moved from src
to dest
.
+ *
+ *
+ * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}.
+ *
+ * @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} src
to dest
.
+ * If preserveInitState
is true
, it's initialized state is preserved
+ * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call.
+ *
+ * Note that it is only legal to pass preserveInitState := true
,
+ * if the {@link GLContext} of both src
and dest
are shared, or has itself moved from src
to dest
.
+ *
+ *
+ * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}.
+ *
+ * @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(); 0a and b
,
+ * while preserving it's initialized state, resets the GL-Viewport and issuing {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)}.
+ *
+ * The {@link GLAutoDrawable} to {@link GLAnimatorControl} association
+ * is also swapped.
+ *
+ *
+ * If an {@link GLAnimatorControl} is being attached to {@link GLAutoDrawable} a
or b
+ * and the current thread is different than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation.
+ *
+ *
+ * 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 GLAutoDrawable Locking.
+ *
+ * @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} a
and b
,
- * while preserving it's initialized state, resets the GL-Viewport and issuing {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)}.
- *
- * The {@link GLAutoDrawable} to {@link GLAnimatorControl} association
- * is also swapped.
- *
- *
- * If an {@link GLAnimatorControl} is being attached to {@link GLAutoDrawable} a
or b
- * and the current thread is different than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation.
- *
- * @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.
- *
- * The GL-Viewport is reset and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued implicit.
- *
- *
- * 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.
- *
- * @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.
+ *
+ * The GL-Viewport is reset and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued implicit.
+ *
+ *
+ * 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.
+ *
+ *
+ * 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 GLAutoDrawable Locking.
+ *
+ * @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.
+ *
+ * Usually one uses the {@link GLBase#getDefaultReadBuffer() default-read-buffer}
+ * in which case {@link GLDrawable#swapBuffers() swap-buffers} shall happen after calling reading pixels, the default.
+ *
+ *
+ * However, multisampling offscreen {@link javax.media.opengl.GLFBODrawable}s
+ * utilize {@link GLDrawable#swapBuffers() swap-buffers} to downsample
+ * the multisamples into the readable sampling sink.
+ * In this case, we require {@link GLDrawable#swapBuffers() swap-buffers} before reading pixels.
+ *
+ * @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.
- *
- * Usually one uses the {@link GLBase#getDefaultReadBuffer() default-read-buffer}
- * in which case {@link GLDrawable#swapBuffers() swap-buffers} shall happen after calling reading pixels, the default.
- *
- *
- * However, multisampling offscreen {@link javax.media.opengl.GLFBODrawable}s
- * utilize {@link GLDrawable#swapBuffers() swap-buffers} to downsample
- * the multisamples into the readable sampling sink.
- * In this case, we require {@link GLDrawable#swapBuffers() swap-buffers} before reading pixels.
- *
- * @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
+
+ GLAutoDrawable implementations perform locking in the following order:
+
+ - {@link #getUpstreamLock()}.{@link RecursiveLock#lock() lock()}
+ - {@link #getNativeSurface()}.{@link NativeSurface#lockSurface() lockSurface()}
+
+ and releases the locks accordingly:
+
+ - {@link #getNativeSurface()}.{@link NativeSurface#unlockSurface() unlockSurface()}
+ - {@link #getUpstreamLock()}.{@link RecursiveLock#unlock() unlock()}
+
+ Above locking order is mandatory to guarantee
+ atomicity of operation and to avoid race-conditions.
+ A custom implementation or user applications requiring exclusive access
+ shall follow the locking order.
+ See:
+
+ - {@link #getUpstreamLock()}
+ - {@link #invoke(boolean, GLRunnable)}
+ - {@link #invoke(boolean, List)}
+
+
*/
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, newtCtx
, to this auto-drawable.
*
- * The current context will be destroyed if destroyPrevCtx
is true
,
- * otherwise it will be dis-associated from this auto-drawable
- * via {@link GLContext#setGLDrawable(GLDrawable, boolean) setGLDrawable(null, true);} first.
- *
- *
- * The new context will be associated with this auto-drawable
- * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.
- *
- *
- * 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:
+ *
+ * - The currently associated context will be destroyed if
destroyPrevCtx
is true
,
+ * otherwise it will be disassociated from this auto-drawable
+ * via {@link GLContext#setGLDrawable(GLDrawable, boolean) setGLDrawable(null, true);} including {@link GL#glFinish() glFinish()}.
+ * - The new context will be associated with this auto-drawable
+ * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.
+ * - If the old context was current on this thread, it is being released after disassociating this auto-drawable.
+ * - If the new context was current on this thread, it is being released before associating this auto-drawable
+ * and made current afterwards.
+ * - Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.
+ * - 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 GLAutoDrawable Locking.
+ *
*
*
* @param newCtx the new context, maybe null
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.
*
+ *
+ * To avoid a deadlock situation which causes an {@link IllegalStateException} one should
+ * avoid issuing {@link #invoke(boolean, GLRunnable) invoke} while this GLAutoDrawable is being locked.
+ * Detected deadlock situations throwing an {@link IllegalStateException} are:
+ *
+ * - {@link #getAnimator() Animator} is running on another thread and waiting and is locked on current thread, but is not {@link #isThreadGLCapable() GL-Thread}
+ * - No {@link #getAnimator() Animator} is running on another thread and is locked on current thread, but is not {@link #isThreadGLCapable() GL-Thread}
+ *
+ *
*
* @param wait if true
block until execution of glRunnable
is finished, otherwise return immediately w/o waiting
* @param glRunnable the {@link GLRunnable} to execute within {@link #display()}
* @return true
if the {@link GLRunnable} has been processed or queued, otherwise false
.
+ * @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 true
block until execution of the last glRunnable
is finished, otherwise return immediately w/o waiting
* @param glRunnables the {@link GLRunnable}s to execute within {@link #display()}
* @return true
if the {@link GLRunnable}s has been processed or queued, otherwise false
.
+ * @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 glRunnables);
+ public boolean invoke(boolean wait, List 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()}.
+ *
+ * See GLAutoDrawable Locking.
+ *
+ */
+ public RecursiveLock getUpstreamLock();
+
+ /**
+ * Indicates whether the current thread is capable of
+ * performing OpenGL-related work.
+ *
+ * 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.
+ *
+ */
+ 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.
*
* If the arguments reflect the current state of this context
* this method is a no-operation and returns the old and current {@link GLDrawable}.
*
*
- * 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:
+ *
+ * - {@link GL#glFinish() glFinish()} is issued if context {@link #isCreated()} and a {@link #getGLDrawable() previous drawable} was bound before disassociation.
+ * - If the context was current on this thread, it is being released before drawable reassociation
+ * and made current afterwards.
+ * - Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.
+ * - 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 GLAutoDrawable Locking.
+ *
*
* @param readWrite The read/write drawable for framebuffer operations, maybe null
to remove association.
* @param setWriteOnly Only change the write-drawable, if setWriteOnly
is true
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.
+ *
+ * Method always returns true
+ * if {@link #getMode()} == {@link Mode#MT} or {@link #isSingleThreaded()} == false
.
+ *
+ */
public static final boolean isOpenGLThread() throws GLException {
return ThreadingImpl.isOpenGLThread();
}
@@ -173,7 +203,7 @@ public class Threading {
}
/**
- * If {@link #isSingleThreaded()} and not {@link #isOpenGLThread()}
+ * If not {@link #isOpenGLThread()}
* and the lock
is not being hold by this thread,
* invoke Runnable r
on the OpenGL thread via {@link #invokeOnOpenGLThread(boolean, Runnable)}.
*
@@ -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
@@ -299,6 +299,12 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing
return this;
}
+ @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 glRunnables) {
+ public boolean invoke(final boolean wait, final List 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;
@@ -431,6 +436,12 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing
return this;
}
+ @Override
+ public final RecursiveLock getUpstreamLock() { return lock; }
+
+ @Override
+ public final boolean isThreadGLCapable() { return EventQueue.isDispatchThread(); }
+
@Override
public void display() {
if( isShowing || ( printActive && isVisible() ) ) {
@@ -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 glRunnables) {
+ public boolean invoke(final boolean wait, final List 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 true
.
*
* @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 glRunnables) {
+ public boolean invoke(final boolean wait, final List glRunnables) throws IllegalStateException {
return helper.invoke(this, wait, glRunnables);
}
@@ -604,6 +603,15 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
return additionalCtxCreationFlags;
}
+ /**
+ * {@inheritDoc}
+ *
+ * Implementation always supports multithreading, hence method always returns true
.
+ *
+ */
+ @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.
*
- * The oldCtx
will be destroyed if destroyPrevCtx
is true
,
- * otherwise dis-associate oldCtx
from drawable
- * via {@link GLContext#setGLDrawable(GLDrawable, boolean) oldCtx.setGLDrawable(null, true);}.
- *
- *
- * Re-associate newCtx
with drawable
- * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.
- *
- *
- * If the old or new context was current on this thread, it is being released before switching the drawable.
- *
- *
- * No locking is being performed on the drawable, caller is required to take care of it.
+ * Remarks:
+ *
+ * - The
oldCtx
will be destroyed if destroyPrevCtx
is true
,
+ * otherwise disassociate oldCtx
from drawable
+ * via {@link GLContext#setGLDrawable(GLDrawable, boolean) oldCtx.setGLDrawable(null, true);} including {@link GL#glFinish() glFinish()}.
+ * - Reassociate
newCtx
with drawable
+ * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.
+ * - If the old context was current on this thread, it is being released after disassociating the drawable.
+ * - If the new context was current on this thread, it is being released before associating the drawable
+ * and made current afterwards.
+ * - Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.
+ * - 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 GLAutoDrawable Locking.
+ *
*
*
* @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;
+ }
+ }
+
/**
*
* If wait
is true
the call blocks until the glRunnable
@@ -805,13 +833,32 @@ public class GLDrawableHelper {
* the call is ignored and returns false
.
* This helps avoiding deadlocking the caller.
*
+ *
+ *
+ * 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
+ *
+ *
*
* @param drawable the {@link GLAutoDrawable} to be used
* @param wait if true
block until execution of glRunnable
is finished, otherwise return immediatly w/o waiting
* @param glRunnable the {@link GLRunnable} to execute within {@link #display()}
* @return true
if the {@link GLRunnable} has been processed or queued, otherwise false
.
+ * @throws IllegalStateException in case the drawable is locked by this thread, no animator is running on another thread and wait
is true
.
*/
- 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 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 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
+ * Method always returns true
+ * if {@link #getMode()} == {@link Mode#MT} or {@link #isSingleThreaded()} == false
.
+ *
+ */
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());
}
--
cgit v1.2.3