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 Locking
    + GLAutoDrawable implementations perform locking in the following order: +
      +
    1. {@link #getUpstreamLock()}.{@link RecursiveLock#lock() lock()}
    2. +
    3. {@link #getNativeSurface()}.{@link NativeSurface#lockSurface() lockSurface()}
    4. +
    + and releases the locks accordingly: +
      +
    1. {@link #getNativeSurface()}.{@link NativeSurface#unlockSurface() unlockSurface()}
    2. +
    3. {@link #getUpstreamLock()}.{@link RecursiveLock#unlock() unlock()}
    4. +
    + 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