aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jogl/classes/jogamp/opengl/GLDrawableHelper.java')
-rw-r--r--src/jogl/classes/jogamp/opengl/GLDrawableHelper.java1342
1 files changed, 1005 insertions, 337 deletions
diff --git a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java
index bdf0b6d74..3847b4042 100644
--- a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java
+++ b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java
@@ -1,22 +1,22 @@
/*
* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
* Copyright (c) 2010 JogAmp Community. All rights reserved.
- *
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
- *
+ *
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
- *
+ *
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
- *
+ *
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
- *
+ *
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
@@ -29,27 +29,29 @@
* DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
* SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
- *
+ *
* You acknowledge that this software is not designed or intended for use
* in the design, construction, operation or maintenance of any nuclear
* facility.
- *
+ *
* Sun gratefully acknowledges that this software was originally authored
* and developed by Kenneth Bradley Russell and Christopher John Kline.
*/
package jogamp.opengl;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
import java.util.HashSet;
import javax.media.nativewindow.NativeSurface;
import javax.media.nativewindow.NativeWindowException;
import javax.media.nativewindow.ProxySurface;
import javax.media.nativewindow.UpstreamSurfaceHook;
+import javax.media.opengl.GL;
import javax.media.opengl.GLAnimatorControl;
import javax.media.opengl.GLAutoDrawable;
-import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLDrawable;
import javax.media.opengl.GLDrawableFactory;
@@ -58,24 +60,37 @@ import javax.media.opengl.GLException;
import javax.media.opengl.GLFBODrawable;
import javax.media.opengl.GLRunnable;
-import com.jogamp.opengl.util.Animator;
+import com.jogamp.common.ExceptionUtils;
+import com.jogamp.common.util.PropertyAccess;
/** Encapsulates the implementation of most of the GLAutoDrawable's
- methods to be able to share it between GLCanvas and GLJPanel. */
-
+ methods to be able to share it between GLAutoDrawable implementations like GLAutoDrawableBase, GLCanvas and GLJPanel. */
public class GLDrawableHelper {
/** true if property <code>jogl.debug.GLDrawable.PerfStats</code> is defined. */
- private static final boolean PERF_STATS = Debug.isPropertyDefined("jogl.debug.GLDrawable.PerfStats", true);
-
+ private static final boolean PERF_STATS;
+
+ static {
+ Debug.initSingleton();
+ PERF_STATS = PropertyAccess.isPropertyDefined("jogl.debug.GLDrawable.PerfStats", true);
+ }
+
protected static final boolean DEBUG = GLDrawableImpl.DEBUG;
private final Object listenersLock = new Object();
private final ArrayList<GLEventListener> listeners = new ArrayList<GLEventListener>();
private final HashSet<GLEventListener> listenersToBeInit = new HashSet<GLEventListener>();
private final Object glRunnablesLock = new Object();
- private volatile ArrayList<GLRunnableTask> glRunnables = new ArrayList<GLRunnableTask>();
+ private ArrayList<GLRunnableTask> glRunnables = new ArrayList<GLRunnableTask>();
+ private volatile int glRunnableCount = 0;
private boolean autoSwapBufferMode;
- private Thread skipContextReleaseThread;
+ private volatile Thread exclusiveContextThread;
+ /** -1 release, 0 nop, 1 claim */
+ private volatile int exclusiveContextSwitch;
private GLAnimatorControl animatorCtrl;
+ private static Runnable nop = new Runnable() { @Override public void run() {} };
+
+ private GLContext sharedContext;
+ private GLAutoDrawable sharedAutoDrawable;
+
public GLDrawableHelper() {
reset();
@@ -87,21 +102,77 @@ public class GLDrawableHelper {
listenersToBeInit.clear();
}
autoSwapBufferMode = true;
- skipContextReleaseThread = null;
+ exclusiveContextThread = null;
+ exclusiveContextSwitch = 0;
synchronized(glRunnablesLock) {
+ glRunnableCount = 0;
glRunnables.clear();
}
animatorCtrl = null;
+ sharedContext = null;
+ sharedAutoDrawable = null;
+ }
+
+ public final void setSharedContext(final GLContext thisContext, final GLContext sharedContext) throws IllegalStateException {
+ if( null == sharedContext ) {
+ throw new IllegalStateException("Null shared GLContext");
+ }
+ if( thisContext == sharedContext ) {
+ throw new IllegalStateException("Shared GLContext same as local");
+ }
+ if( null != this.sharedContext ) {
+ throw new IllegalStateException("Shared GLContext already set");
+ }
+ if( null != this.sharedAutoDrawable ) {
+ throw new IllegalStateException("Shared GLAutoDrawable already set");
+ }
+ this.sharedContext = sharedContext;
+ }
+
+ public final void setSharedAutoDrawable(final GLAutoDrawable thisAutoDrawable, final GLAutoDrawable sharedAutoDrawable) throws IllegalStateException {
+ if( null == sharedAutoDrawable ) {
+ throw new IllegalStateException("Null shared GLAutoDrawable");
+ }
+ if( thisAutoDrawable == sharedAutoDrawable ) {
+ throw new IllegalStateException("Shared GLAutoDrawable same as this");
+ }
+ if( null != this.sharedContext ) {
+ throw new IllegalStateException("Shared GLContext already set");
+ }
+ if( null != this.sharedAutoDrawable ) {
+ throw new IllegalStateException("Shared GLAutoDrawable already set");
+ }
+ this.sharedAutoDrawable = sharedAutoDrawable;
+ }
+
+ /**
+ * @param shared returns the shared GLContext, based on set shared GLAutoDrawable
+ * or GLContext. Maybe null if none is set.
+ * @return true if initialization is pending due to a set shared GLAutoDrawable or GLContext
+ * which is not ready yet. Otherwise false.
+ */
+ public boolean isSharedGLContextPending(final GLContext[] shared) {
+ final GLContext shareWith;
+ final boolean pending;
+ if ( null != sharedAutoDrawable ) {
+ shareWith = sharedAutoDrawable.getContext();
+ pending = null == shareWith || !shareWith.isCreated() || !sharedAutoDrawable.areAllGLEventListenerInitialized();
+ } else {
+ shareWith = sharedContext;
+ pending = null != shareWith && !shareWith.isCreated();
+ }
+ shared[0] = shareWith;
+ return pending;
}
@Override
public final String toString() {
- StringBuilder sb = new StringBuilder();
+ final StringBuilder sb = new StringBuilder();
sb.append("GLAnimatorControl: "+animatorCtrl+", ");
synchronized(listenersLock) {
sb.append("GLEventListeners num "+listeners.size()+" [");
for (int i=0; i < listeners.size(); i++) {
- Object l = listeners.get(i);
+ final Object l = listeners.get(i);
sb.append(l);
sb.append("[init ");
sb.append( !listenersToBeInit.contains(l) );
@@ -112,49 +183,80 @@ public class GLDrawableHelper {
return sb.toString();
}
+ /** Limit release calls of {@link #forceNativeRelease(GLContext)} to {@value}. */
+ private static final int MAX_RELEASE_ITER = 512;
+
/**
- * Associate a new context to the drawable and also propagates the context/drawable switch by
- * calling {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.
- * <p>
- * If the old or new context was current on this thread, it is being released before switching the drawable.
- * </p>
+ * Since GLContext's {@link GLContext#makeCurrent()} and {@link GLContext#release()}
+ * is recursive, a call to {@link GLContext#release()} may not natively release the context.
* <p>
- * Be aware that the old context is still bound to the drawable,
- * and that one context can only bound to one drawable at one time!
+ * This methods continues calling {@link GLContext#release()} until the context has been natively released.
* </p>
+ * @param ctx
+ */
+ public static final void forceNativeRelease(final GLContext ctx) {
+ int releaseCount = 0;
+ do {
+ ctx.release();
+ releaseCount++;
+ if (DEBUG) {
+ System.err.println("GLDrawableHelper.forceNativeRelease() #"+releaseCount+" -- currentThread "+Thread.currentThread()+" -> "+GLContext.getCurrent());
+ }
+ } while( MAX_RELEASE_ITER > releaseCount && ctx.isCurrent() );
+
+ if( ctx.isCurrent() ) {
+ throw new GLException("Context still current after "+MAX_RELEASE_ITER+" releases: "+ctx);
+ }
+ }
+
+ /**
+ * Switch {@link GLContext} / {@link GLDrawable} association.
* <p>
- * No locking is being performed on the drawable, caller is required to take care of it.
+ * Remarks:
+ * <ul>
+ * <li>The <code>oldCtx</code> will be destroyed if <code>destroyPrevCtx</code> is <code>true</code>,
+ * otherwise disassociate <code>oldCtx</code> from <code>drawable</code>
+ * via {@link GLContext#setGLDrawable(GLDrawable, boolean) oldCtx.setGLDrawable(null, true);} including {@link GL#glFinish() glFinish()}.</li>
+ * <li>Reassociate <code>newCtx</code> with <code>drawable</code>
+ * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.</li>
+ * <li>If the old context was current on this thread, it is being released after disassociating the drawable.</li>
+ * <li>If the new context was current on this thread, it is being released before associating the drawable
+ * and made current afterwards.</li>
+ * <li>Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.</li>
+ * <li>The user shall take extra care of thread synchronization,
+ * i.e. lock the involved {@link GLDrawable#getNativeSurface() drawable's} {@link NativeSurface}s
+ * to avoid a race condition. In case {@link GLAutoDrawable auto-drawable's} are used,
+ * their {@link GLAutoDrawable#getUpstreamLock() upstream-lock} must be locked beforehand
+ * see <a href="../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li>
+ * </ul>
* </p>
- *
+ *
* @param drawable the drawable which context is changed
- * @param oldCtx the old context
- * @param newCtx the new context
+ * @param oldCtx the old context, maybe <code>null</code>.
+ * @param destroyOldCtx if <code>true</code>, destroy the <code>oldCtx</code>
+ * @param newCtx the new context, maybe <code>null</code> for dis-association.
* @param newCtxCreationFlags additional creation flags if newCtx is not null and not been created yet, see {@link GLContext#setContextCreationFlags(int)}
- * @return true if the new context was current, otherwise false
- *
- * @see GLAutoDrawable#setContext(GLContext)
+ *
+ * @see GLAutoDrawable#setContext(GLContext, boolean)
*/
- public static final boolean switchContext(GLDrawable drawable, GLContext oldCtx, GLContext newCtx, int newCtxCreationFlags) {
- if( null != oldCtx && oldCtx.isCurrent() ) {
- oldCtx.release();
+ public static final void switchContext(final GLDrawable drawable, final GLContext oldCtx, final boolean destroyOldCtx, final GLContext newCtx, final int newCtxCreationFlags) {
+ if( null != oldCtx ) {
+ if( destroyOldCtx ) {
+ oldCtx.destroy();
+ } else {
+ oldCtx.setGLDrawable(null, true); // dis-associate old pair
+ }
}
- final boolean newCtxCurrent;
+
if(null!=newCtx) {
- newCtxCurrent = newCtx.isCurrent();
- if(newCtxCurrent) {
- newCtx.release();
- }
newCtx.setContextCreationFlags(newCtxCreationFlags);
- newCtx.setGLDrawable(drawable, true); // propagate context/drawable switch
- } else {
- newCtxCurrent = false;
+ newCtx.setGLDrawable(drawable, true); // re-associate new pair
}
- return newCtxCurrent;
}
-
+
/**
* If the drawable is not realized, OP is a NOP.
- * <ul>
+ * <ul>
* <li>release context if current</li>
* <li>destroy old drawable</li>
* <li>create new drawable</li>
@@ -162,26 +264,30 @@ public class GLDrawableHelper {
* <li>make context current, if it was current</li>
* </ul>
* <p>
- * No locking is being performed, caller is required to take care of it.
+ * Locking is performed via {@link GLContext#makeCurrent()} on the passed <code>context</code>.
* </p>
- *
+ *
* @param drawable
* @param context maybe null
* @return the new drawable
*/
- public static final GLDrawableImpl recreateGLDrawable(GLDrawableImpl drawable, GLContext context) {
+ public static final GLDrawableImpl recreateGLDrawable(GLDrawableImpl drawable, final GLContext context) {
if( ! drawable.isRealized() ) {
return drawable;
}
- final boolean contextCurrent = null != context && context.isCurrent();
+ final GLContext currentContext = GLContext.getCurrent();
final GLDrawableFactory factory = drawable.getFactory();
final NativeSurface surface = drawable.getNativeSurface();
final ProxySurface proxySurface = (surface instanceof ProxySurface) ? (ProxySurface)surface : null;
-
- if(contextCurrent) {
- context.release();
+
+ if( null != context ) {
+ // Ensure to sync GL command stream
+ if( currentContext != context ) {
+ context.makeCurrent();
+ }
+ context.setGLDrawable(null, true); // dis-associate
}
-
+
if(null != proxySurface) {
proxySurface.enableUpstreamSurfaceHookLifecycle(false);
}
@@ -196,20 +302,20 @@ public class GLDrawableHelper {
}
if(null != context) {
- context.setGLDrawable(drawable, true); // re-association
+ context.setGLDrawable(drawable, true); // re-association, implicit glFinish() ctx/drawable sync
}
-
- if(contextCurrent) {
- context.makeCurrent();
+
+ if( null != currentContext ) {
+ currentContext.makeCurrent();
}
return drawable;
}
-
+
/**
* Performs resize operation on the given drawable, assuming it is offscreen.
* <p>
* The {@link GLDrawableImpl}'s {@link NativeSurface} is being locked during operation.
- * In case the holder is an auto drawable or similar, it's lock shall be claimed by the caller.
+ * In case the holder is an auto drawable or similar, it's lock shall be claimed by the caller.
* </p>
* <p>
* May recreate the drawable via {@link #recreateGLDrawable(GLDrawableImpl, GLContext)}
@@ -219,10 +325,10 @@ public class GLDrawableHelper {
* FBO drawables are resized w/o drawable destruction.
* </p>
* <p>
- * Offscreen resize operation is validated w/ drawable size in the end.
+ * Offscreen resize operation is validated w/ drawable size in the end.
* An exception is thrown if not successful.
* </p>
- *
+ *
* @param drawable
* @param context
* @param newWidth the new width, it's minimum is capped to 1
@@ -231,34 +337,42 @@ public class GLDrawableHelper {
* @throws NativeWindowException is drawable is not offscreen or it's surface lock couldn't be claimed
* @throws GLException may be thrown a resize operation
*/
- public static final GLDrawableImpl resizeOffscreenDrawable(GLDrawableImpl drawable, GLContext context, int newWidth, int newHeight)
- throws NativeWindowException, GLException
+ public static final GLDrawableImpl resizeOffscreenDrawable(GLDrawableImpl drawable, final GLContext context, int newWidth, int newHeight)
+ throws NativeWindowException, GLException
{
- if(drawable.getChosenGLCapabilities().isOnscreen()) {
- throw new NativeWindowException("Drawable is not offscreen: "+drawable);
- }
final NativeSurface ns = drawable.getNativeSurface();
final int lockRes = ns.lockSurface();
- if (NativeSurface.LOCK_SURFACE_NOT_READY >= lockRes) {
+ if ( NativeSurface.LOCK_SURFACE_NOT_READY >= lockRes ) {
throw new NativeWindowException("Could not lock surface of drawable: "+drawable);
}
+ boolean validateSize = true;
try {
- if(0>=newWidth) { newWidth = 1; }
- if(0>=newHeight) { newHeight = 1; }
- // propagate new size
- if(ns instanceof ProxySurface) {
+ if( ! drawable.isRealized() ) {
+ return drawable;
+ }
+ if( drawable.getChosenGLCapabilities().isOnscreen() ) {
+ throw new NativeWindowException("Drawable is not offscreen: "+drawable);
+ }
+ if( DEBUG && ( 0>=newWidth || 0>=newHeight) ) {
+ System.err.println("WARNING: Odd size detected: "+newWidth+"x"+newHeight+", using safe size 1x1. Drawable "+drawable);
+ Thread.dumpStack();
+ }
+ if( 0 >= newWidth ) { newWidth = 1; validateSize=false; }
+ if( 0 >= newHeight ) { newHeight = 1; validateSize=false; }
+ // propagate new size
+ if( ns instanceof ProxySurface ) {
final ProxySurface ps = (ProxySurface) ns;
final UpstreamSurfaceHook ush = ps.getUpstreamSurfaceHook();
if(ush instanceof UpstreamSurfaceHook.MutableSize) {
- ((UpstreamSurfaceHook.MutableSize)ush).setSize(newWidth, newHeight);
+ ((UpstreamSurfaceHook.MutableSize)ush).setSurfaceSize(newWidth, newHeight);
} else if(DEBUG) { // we have to assume UpstreamSurfaceHook contains the new size already, hence size check @ bottom
System.err.println("GLDrawableHelper.resizeOffscreenDrawable: Drawable's offscreen ProxySurface n.a. UpstreamSurfaceHook.MutableSize, but "+ush.getClass().getName()+": "+ush);
}
} else if(DEBUG) { // we have to assume surface contains the new size already, hence size check @ bottom
System.err.println("GLDrawableHelper.resizeOffscreenDrawable: Drawable's offscreen surface n.a. ProxySurface, but "+ns.getClass().getName()+": "+ns);
}
- if(drawable instanceof GLFBODrawable) {
- if( null != context && context.isCreated() ) {
+ if( drawable instanceof GLFBODrawable ) {
+ if( null != context && context.isCreated() ) {
((GLFBODrawable) drawable).resetSize(context.getGL());
}
} else {
@@ -267,17 +381,17 @@ public class GLDrawableHelper {
} finally {
ns.unlockSurface();
}
- if(drawable.getWidth() != newWidth || drawable.getHeight() != newHeight) {
+ if( validateSize && ( drawable.getSurfaceWidth() != newWidth || drawable.getSurfaceHeight() != newHeight ) ) {
throw new InternalError("Incomplete resize operation: expected "+newWidth+"x"+newHeight+", has: "+drawable);
}
return drawable;
}
-
- public final void addGLEventListener(GLEventListener listener) {
+
+ public final void addGLEventListener(final GLEventListener listener) {
addGLEventListener(-1, listener);
}
- public final void addGLEventListener(int index, GLEventListener listener) {
+ public final void addGLEventListener(int index, final GLEventListener listener) {
synchronized(listenersLock) {
if(0>index) {
index = listeners.size();
@@ -285,159 +399,386 @@ public class GLDrawableHelper {
// GLEventListener may be added after context is created,
// hence we earmark initialization for the next display call.
listenersToBeInit.add(listener);
+
listeners.add(index, listener);
}
}
-
- public final void removeGLEventListener(GLEventListener listener) {
+
+ /**
+ * Note that no {@link GLEventListener#dispose(GLAutoDrawable)} call is being issued
+ * due to the lack of a current context.
+ * Consider calling {@link #disposeGLEventListener(GLAutoDrawable, GLDrawable, GLContext, GLEventListener)}.
+ * @return the removed listener, or null if listener was not added
+ */
+ public final GLEventListener removeGLEventListener(final GLEventListener listener) {
synchronized(listenersLock) {
- listeners.remove(listener);
listenersToBeInit.remove(listener);
+ return listeners.remove(listener) ? listener : null;
}
}
- public final GLEventListener removeGLEventListener(int index)
- throws IndexOutOfBoundsException {
+ public final GLEventListener removeGLEventListener(int index) throws IndexOutOfBoundsException {
synchronized(listenersLock) {
if(0>index) {
index = listeners.size()-1;
}
- return listeners.remove(index);
+ final GLEventListener listener = listeners.remove(index);
+ listenersToBeInit.remove(listener);
+ return listener;
+ }
+ }
+
+ public final int getGLEventListenerCount() {
+ synchronized(listenersLock) {
+ return listeners.size();
}
}
-
+
+ public final GLEventListener getGLEventListener(int index) throws IndexOutOfBoundsException {
+ synchronized(listenersLock) {
+ if(0>index) {
+ index = listeners.size()-1;
+ }
+ return listeners.get(index);
+ }
+ }
+
+ public final boolean areAllGLEventListenerInitialized() {
+ synchronized(listenersLock) {
+ return 0 == listenersToBeInit.size();
+ }
+ }
+
+ public final boolean getGLEventListenerInitState(final GLEventListener listener) {
+ synchronized(listenersLock) {
+ return !listenersToBeInit.contains(listener);
+ }
+ }
+
+ public final void setGLEventListenerInitState(final GLEventListener listener, final boolean initialized) {
+ synchronized(listenersLock) {
+ if(initialized) {
+ listenersToBeInit.remove(listener);
+ } else {
+ listenersToBeInit.add(listener);
+ }
+ }
+ }
+
/**
- * Issues {@link javax.media.opengl.GLEventListener#dispose(javax.media.opengl.GLAutoDrawable)}
- * to all listeners.
+ * Disposes the given {@link GLEventListener} via {@link GLEventListener#dispose(GLAutoDrawable)}
+ * if it has been initialized and added to this queue.
* <p>
- * Please consider using {@link #disposeGL(GLAutoDrawable, GLDrawable, GLContext, Runnable)}
- * for correctness!
+ * If <code>remove</code> is <code>true</code>, the {@link GLEventListener} is removed from this drawable queue before disposal,
+ * otherwise marked uninitialized.
* </p>
- * @param drawable
+ * <p>
+ * Please consider using {@link #disposeGLEventListener(GLAutoDrawable, GLDrawable, GLContext, GLEventListener)}
+ * for correctness, i.e. encapsulating all calls w/ makeCurrent etc.
+ * </p>
+ * @param autoDrawable
+ * @param remove if true, the listener gets removed
+ * @return the disposed and/or removed listener, otherwise null if neither action is performed
*/
- public final void dispose(GLAutoDrawable drawable) {
+ public final GLEventListener disposeGLEventListener(final GLAutoDrawable autoDrawable, final GLEventListener listener, final boolean remove) {
+ synchronized(listenersLock) {
+ if( remove ) {
+ if( listeners.remove(listener) ) {
+ if( !listenersToBeInit.remove(listener) ) {
+ listener.dispose(autoDrawable);
+ }
+ return listener;
+ }
+ } else {
+ if( listeners.contains(listener) && !listenersToBeInit.contains(listener) ) {
+ listener.dispose(autoDrawable);
+ listenersToBeInit.add(listener);
+ return listener;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Disposes all added initialized {@link GLEventListener}s via {@link GLEventListener#dispose(GLAutoDrawable)}.
+ * <p>
+ * If <code>remove</code> is <code>true</code>, the {@link GLEventListener}s are removed from this drawable queue before disposal,
+ * otherwise maked uninitialized.
+ * </p>
+ * <p>
+ * Please consider using {@link #disposeAllGLEventListener(GLAutoDrawable, GLContext, boolean)}
+ * or {@link #disposeGL(GLAutoDrawable, GLContext, boolean)}
+ * for correctness, i.e. encapsulating all calls w/ makeCurrent etc.
+ * </p>
+ * @param autoDrawable
+ * @return the disposal count
+ * @throws GLException caused by {@link GLEventListener#dispose(GLAutoDrawable)}
+ */
+ public final int disposeAllGLEventListener(final GLAutoDrawable autoDrawable, final boolean remove) throws GLException {
+ Throwable firstCaught = null;
+ int disposeCount = 0;
synchronized(listenersLock) {
- final ArrayList<GLEventListener> _listeners = listeners;
- for (int i=0; i < _listeners.size(); i++) {
- _listeners.get(i).dispose(drawable);
+ if( remove ) {
+ for (int count = listeners.size(); 0 < count && 0 < listeners.size(); count--) {
+ final GLEventListener listener = listeners.remove(0);
+ if( !listenersToBeInit.remove(listener) ) {
+ try {
+ listener.dispose(autoDrawable);
+ } catch (final Throwable t) {
+ if( null == firstCaught ) {
+ firstCaught = t;
+ } else {
+ ExceptionUtils.dumpThrowable("subsequent", t);
+ }
+ }
+ disposeCount++;
+ }
+ }
+ } else {
+ for (int i = 0; i < listeners.size(); i++) {
+ final GLEventListener listener = listeners.get(i);
+ if( !listenersToBeInit.contains(listener) ) {
+ try {
+ listener.dispose(autoDrawable);
+ } catch (final Throwable t) {
+ if( null == firstCaught ) {
+ firstCaught = t;
+ } else {
+ ExceptionUtils.dumpThrowable("subsequent", t);
+ }
+ }
+ listenersToBeInit.add(listener);
+ disposeCount++;
+ }
+ }
}
}
+ if( null != firstCaught ) {
+ flushGLRunnables();
+ throw GLException.newGLException(firstCaught);
+ }
+ return disposeCount;
}
- private final void init(GLEventListener l, GLAutoDrawable drawable, boolean sendReshape) {
+ /**
+ * Principal helper method which runs {@link #disposeGLEventListener(GLAutoDrawable, GLEventListener, boolean)}
+ * with the context made current.
+ * <p>
+ * If an {@link GLAnimatorControl} is being attached and the current thread is different
+ * than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation.
+ * </p>
+ *
+ * @param autoDrawable
+ * @param context
+ * @param listener
+ * @param initAction
+ */
+ public final GLEventListener disposeGLEventListener(final GLAutoDrawable autoDrawable,
+ final GLDrawable drawable,
+ final GLContext context,
+ final GLEventListener listener,
+ final boolean remove) {
+ synchronized(listenersLock) {
+ // fast path for uninitialized listener
+ if( listenersToBeInit.contains(listener) ) {
+ if( remove ) {
+ listenersToBeInit.remove(listener);
+ return listeners.remove(listener) ? listener : null;
+ }
+ return null;
+ }
+ }
+ final boolean isPaused = isAnimatorAnimatingOnOtherThread() && animatorCtrl.pause();
+ final GLEventListener[] res = new GLEventListener[] { null };
+ final Runnable action = new Runnable() {
+ @Override
+ public void run() {
+ res[0] = disposeGLEventListener(autoDrawable, listener, remove);
+ }
+ };
+ invokeGL(drawable, context, action, nop);
+
+ if(isPaused) {
+ animatorCtrl.resume();
+ }
+ return res[0];
+ }
+
+ /**
+ * Principal helper method which runs {@link #disposeAllGLEventListener(GLAutoDrawable, boolean)}
+ * with the context made current.
+ * <p>
+ * If an {@link GLAnimatorControl} is being attached and the current thread is different
+ * than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation.
+ * </p>
+ *
+ * @param autoDrawable
+ * @param context
+ * @param remove
+ */
+ public final void disposeAllGLEventListener(final GLAutoDrawable autoDrawable,
+ final GLDrawable drawable,
+ final GLContext context,
+ final boolean remove) {
+
+ final boolean isPaused = isAnimatorAnimatingOnOtherThread() && animatorCtrl.pause();
+
+ final Runnable action = new Runnable() {
+ @Override
+ public void run() {
+ disposeAllGLEventListener(autoDrawable, remove);
+ }
+ };
+ invokeGL(drawable, context, action, nop);
+
+ if(isPaused) {
+ animatorCtrl.resume();
+ }
+ }
+
+ private final void init(final GLEventListener l, final GLAutoDrawable drawable, final boolean sendReshape, final boolean setViewport) {
l.init(drawable);
if(sendReshape) {
- reshape(l, drawable, 0, 0, drawable.getWidth(), drawable.getHeight(), true /* setViewport */, false /* checkInit */);
+ reshape(l, drawable, 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight(), setViewport, false /* checkInit */);
}
}
- /** The default init action to be called once after ctx is being created @ 1st makeCurrent(). */
- public final void init(GLAutoDrawable drawable) {
+ /**
+ * The default init action to be called once after ctx is being created @ 1st makeCurrent().
+ * @param sendReshape set to true if the subsequent display call won't reshape, otherwise false to avoid double reshape.
+ **/
+ public final void init(final GLAutoDrawable drawable, final boolean sendReshape) {
synchronized(listenersLock) {
final ArrayList<GLEventListener> _listeners = listeners;
- for (int i=0; i < _listeners.size(); i++) {
- final GLEventListener listener = _listeners.get(i) ;
-
- // If make ctx current, invoked by invokGL(..), results in a new ctx, init gets called.
- // This may happen not just for initial setup, but for ctx recreation due to resource change (drawable/window),
- // hence it must be called unconditional, always.
- listenersToBeInit.remove(listener); // remove if exist, avoiding dbl init
- init( listener, drawable, true /* sendReshape */);
+ final int listenerCount = _listeners.size();
+ if( listenerCount > 0 ) {
+ for (int i=0; i < listenerCount; i++) {
+ final GLEventListener listener = _listeners.get(i) ;
+
+ // If make ctx current, invoked by invokGL(..), results in a new ctx, init gets called.
+ // This may happen not just for initial setup, but for ctx recreation due to resource change (drawable/window),
+ // hence it must be called unconditional, always.
+ listenersToBeInit.remove(listener); // remove if exist, avoiding dbl init
+ init( listener, drawable, sendReshape, 0==i /* setViewport */);
+ }
+ } else {
+ // Expose same GL initialization if not using any GLEventListener
+ drawable.getGL().glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight());
}
}
}
- public final void display(GLAutoDrawable drawable) {
+ public final void display(final GLAutoDrawable drawable) {
displayImpl(drawable);
- if(!execGLRunnables(drawable)) {
- displayImpl(drawable);
+ // runForAllGLEventListener(drawable, displayAction);
+ if( glRunnableCount > 0 && !execGLRunnables(drawable) ) { // glRunnableCount volatile OK; execGL.. only executed if size > 0
+ displayImpl(drawable);
+ // runForAllGLEventListener(drawable, displayAction);
}
}
- private final void displayImpl(GLAutoDrawable drawable) {
+ private final void displayImpl(final GLAutoDrawable drawable) {
synchronized(listenersLock) {
final ArrayList<GLEventListener> _listeners = listeners;
- for (int i=0; i < _listeners.size(); i++) {
+ final int listenerCount = _listeners.size();
+ for (int i=0; i < listenerCount; i++) {
final GLEventListener listener = _listeners.get(i) ;
- // GLEventListener may need to be init,
+ // GLEventListener may need to be init,
// in case this one is added after the realization of the GLAutoDrawable
if( listenersToBeInit.remove(listener) ) {
- init( listener, drawable, true /* sendReshape */) ;
+ init( listener, drawable, true /* sendReshape */, listenersToBeInit.size() + 1 == listenerCount /* setViewport if 1st init */ );
}
listener.display(drawable);
}
}
}
- private final void reshape(GLEventListener listener, GLAutoDrawable drawable,
- int x, int y, int width, int height, boolean setViewport, boolean checkInit) {
+ public static interface GLEventListenerAction {
+ public void run(final GLAutoDrawable drawable, final GLEventListener listener);
+ }
+ /**
+ private static GLEventListenerAction displayAction = new GLEventListenerAction() {
+ public void run(final GLAutoDrawable drawable, final GLEventListener listener) {
+ listener.display(drawable);
+ } }; */
+
+ public final void runForAllGLEventListener(final GLAutoDrawable drawable, final GLEventListenerAction action) {
+ synchronized(listenersLock) {
+ final ArrayList<GLEventListener> _listeners = listeners;
+ final int listenerCount = _listeners.size();
+ for (int i=0; i < listenerCount; i++) {
+ final GLEventListener listener = _listeners.get(i) ;
+ // GLEventListener may need to be init,
+ // in case this one is added after the realization of the GLAutoDrawable
+ if( listenersToBeInit.remove(listener) ) {
+ init( listener, drawable, true /* sendReshape */, listenersToBeInit.size() + 1 == listenerCount /* setViewport if 1st init */ );
+ }
+ action.run(drawable, listener);
+ }
+ }
+ }
+
+ private final void reshape(final GLEventListener listener, final GLAutoDrawable drawable,
+ final int x, final int y, final int width, final int height, final boolean setViewport, final boolean checkInit) {
if(checkInit) {
- // GLEventListener may need to be init,
+ // GLEventListener may need to be init,
// in case this one is added after the realization of the GLAutoDrawable
synchronized(listenersLock) {
if( listenersToBeInit.remove(listener) ) {
- init( listener, drawable, false /* sendReshape */) ;
+ listener.init(drawable);
}
}
}
if(setViewport) {
+ if( GLContext.DEBUG_GL || DEBUG ) {
+ final int glerr0 = drawable.getGL().glGetError();
+ if( GL.GL_NO_ERROR != glerr0 ) {
+ System.err.println("Info: GLDrawableHelper.reshape: pre-exisiting GL error 0x"+Integer.toHexString(glerr0));
+ Thread.dumpStack();
+ }
+ }
drawable.getGL().glViewport(x, y, width, height);
}
listener.reshape(drawable, x, y, width, height);
}
- public final void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
+ public final void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {
synchronized(listenersLock) {
for (int i=0; i < listeners.size(); i++) {
- reshape((GLEventListener) listeners.get(i), drawable, x, y, width, height, 0==i, true);
+ reshape(listeners.get(i), drawable, x, y, width, height, 0==i /* setViewport */, true /* checkInit */);
}
}
}
- private final boolean execGLRunnables(GLAutoDrawable drawable) {
- boolean res = true;
- if(glRunnables.size()>0) { // volatile OK
- // swap one-shot list asap
- final ArrayList<GLRunnableTask> _glRunnables;
- synchronized(glRunnablesLock) {
- if(glRunnables.size()>0) {
- _glRunnables = glRunnables;
- glRunnables = new ArrayList<GLRunnableTask>();
- } else {
- _glRunnables = null;
- }
- }
-
- if(null!=_glRunnables) {
- for (int i=0; i < _glRunnables.size(); i++) {
- res = _glRunnables.get(i).run(drawable) && res;
- }
+ private final boolean execGLRunnables(final GLAutoDrawable drawable) { // glRunnables.size()>0
+ // swap one-shot list asap
+ final ArrayList<GLRunnableTask> _glRunnables;
+ synchronized(glRunnablesLock) {
+ if( glRunnables.size() > 0 ) {
+ glRunnableCount = 0;
+ _glRunnables = glRunnables;
+ glRunnables = new ArrayList<GLRunnableTask>();
+ } else {
+ return true;
}
}
+ boolean res = true;
+ for (int i=0; i < _glRunnables.size(); i++) {
+ res = _glRunnables.get(i).run(drawable) && res;
+ }
return res;
}
public final void flushGLRunnables() {
- if(glRunnables.size()>0) { // volatile OK
- // swap one-shot list asap
- final ArrayList<GLRunnableTask> _glRunnables;
- synchronized(glRunnablesLock) {
- if(glRunnables.size()>0) {
- _glRunnables = glRunnables;
- glRunnables = new ArrayList<GLRunnableTask>();
- } else {
- _glRunnables = null;
- }
- }
-
- if(null!=_glRunnables) {
- for (int i=0; i < _glRunnables.size(); i++) {
- _glRunnables.get(i).flush();
- }
+ synchronized(glRunnablesLock) {
+ glRunnableCount = 0;
+ while( glRunnables.size() > 0 ) {
+ glRunnables.remove(0).flush();
}
}
}
-
- public final void setAnimator(GLAnimatorControl animator) throws GLException {
+
+ public final void setAnimator(final GLAnimatorControl animator) throws GLException {
synchronized(glRunnablesLock) {
if(animatorCtrl!=animator && null!=animator && null!=animatorCtrl) {
throw new GLException("Trying to register GLAnimatorControl "+animator+", where "+animatorCtrl+" is already registered. Unregister first.");
@@ -452,15 +793,15 @@ public class GLDrawableHelper {
}
}
- public final boolean isAnimatorRunningOnOtherThread() {
+ public final boolean isAnimatorStartedOnOtherThread() {
return ( null != animatorCtrl ) ? animatorCtrl.isStarted() && animatorCtrl.getThread() != Thread.currentThread() : false ;
}
- public final boolean isAnimatorRunning() {
+ public final boolean isAnimatorStarted() {
return ( null != animatorCtrl ) ? animatorCtrl.isStarted() : false ;
}
- public final boolean isExternalAnimatorAnimating() {
+ public final boolean isAnimatorAnimatingOnOtherThread() {
return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() && animatorCtrl.getThread() != Thread.currentThread() : false ;
}
@@ -468,49 +809,180 @@ public class GLDrawableHelper {
return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() : false ;
}
+ public static final boolean isLockedByOtherThread(final GLAutoDrawable d) {
+ final Thread currentThread = Thread.currentThread();
+ final Thread upstreamLockOwner = d.getUpstreamLock().getOwner();
+ if( null != upstreamLockOwner && currentThread != upstreamLockOwner ) {
+ return true;
+ } else {
+ final NativeSurface s = d.getNativeSurface();
+ final Thread surfaceLockOwner = null != s ? s.getSurfaceLockOwner() : null;
+ return null != surfaceLockOwner && currentThread != surfaceLockOwner;
+ }
+ }
+
+ public static final boolean isLockedByThisThread(final GLAutoDrawable d) {
+ final Thread currentThread = Thread.currentThread();
+ final Thread upstreamLockOwner = d.getUpstreamLock().getOwner();
+ if( currentThread == upstreamLockOwner ) {
+ return true;
+ } else {
+ final NativeSurface s = d.getNativeSurface();
+ final Thread surfaceLockOwner = null != s ? s.getSurfaceLockOwner() : null;
+ return currentThread == surfaceLockOwner;
+ }
+ }
+
/**
* <p>
* If <code>wait</code> is <code>true</code> the call blocks until the <code>glRunnable</code>
* has been executed.<p>
* <p>
- * If <code>wait</code> is <code>true</code> <b>and</b>
+ * If <code>wait</code> is <code>true</code> <b>and</b>
* {@link GLDrawable#isRealized()} returns <code>false</code> <i>or</i> {@link GLAutoDrawable#getContext()} returns <code>null</code>,
* the call is ignored and returns <code>false</code>.<br>
* This helps avoiding deadlocking the caller.
* </p>
+ * <p>
+ * <pre>
+ * 0 == deferredHere && 0 == isGLThread -> display() will issue on GL thread, blocking!
+ *
+ * deferredHere wait isGLThread lockedByThisThread Note
+ * OK 0 x 1 x
+ * OK 0 x 0 0
+ * ERROR 0 x 0 1 Will be deferred on GL thread by display() (blocking),
+ * but locked by this thread -> ERROR
+ *
+ * 1 0 x x All good, due to no wait, non blocking
+ *
+ * 1 1 1 0
+ * 1 1 0 0
+ * SWITCH 1 1 1 1 Run immediately, don't defer since locked by this thread, but isGLThread
+ * ERROR 1 1 0 1 Locked by this thread, but _not_ isGLThread -> ERROR
+ * </pre>
+ * </p>
*
* @param drawable the {@link GLAutoDrawable} to be used
* @param wait if <code>true</code> block until execution of <code>glRunnable</code> is finished, otherwise return immediatly w/o waiting
* @param glRunnable the {@link GLRunnable} to execute within {@link #display()}
* @return <code>true</code> if the {@link GLRunnable} has been processed or queued, otherwise <code>false</code>.
+ * @throws IllegalStateException in case the drawable is locked by this thread, no animator is running on another thread and <code>wait</code> is <code>true</code>.
*/
- public final boolean invoke(GLAutoDrawable drawable, boolean wait, 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;
}
-
- Throwable throwable = null;
+
GLRunnableTask rTask = null;
- Object rTaskLock = new Object();
+ final Object rTaskLock = new Object();
+ Throwable throwable = null;
synchronized(rTaskLock) {
- boolean deferred;
+ boolean deferredHere;
synchronized(glRunnablesLock) {
- deferred = isExternalAnimatorAnimating();
- 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;
+ wait = 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 */);
+ glRunnableCount++;
+ glRunnables.add(rTask);
+ }
+ if( !deferredHere ) {
+ drawable.display();
+ } else if( wait ) {
+ try {
+ rTaskLock.wait(); // free lock, allow execution of rTask
+ } catch (final InterruptedException ie) {
+ throwable = ie;
+ }
+ if(null==throwable) {
+ throwable = rTask.getThrowable();
+ }
+ if(null!=throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @see #invoke(GLAutoDrawable, boolean, GLRunnable)
+ *
+ * @param drawable
+ * @param wait
+ * @param newGLRunnables
+ * @return
+ * @throws IllegalStateException
+ */
+ public final boolean invoke(final GLAutoDrawable drawable, boolean wait, final List<GLRunnable> newGLRunnables) throws IllegalStateException {
+ if( null == newGLRunnables || newGLRunnables.size() == 0 || null == drawable ||
+ wait && ( !drawable.isRealized() || null==drawable.getContext() ) ) {
+ return false;
+ }
+
+ final int count = newGLRunnables.size();
+ GLRunnableTask rTask = null;
+ final Object rTaskLock = new Object();
+ Throwable throwable = null;
+ synchronized(rTaskLock) {
+ boolean deferredHere;
+ synchronized(glRunnablesLock) {
+ 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;
+ wait = false;
+ } else {
+ // Locked by this thread, but _not_ isGLThread -> ERROR
+ throw new IllegalStateException("Deferred, wait, isLocked on current and not GL-Thread: thread "+Thread.currentThread());
+ }
+ }
+ } else {
+ if( !isGLThread && isLockedByThisThread(drawable) ) {
+ // Will be deferred on GL thread by display() (blocking), but locked by this thread -> ERROR
+ throw new IllegalStateException("Not deferred, isLocked on current and not GL-Thread: thread "+Thread.currentThread());
+ }
+ wait = false; // don't wait if exec immediately
+ }
+ for(int i=0; i<count-1; i++) {
+ glRunnableCount++;
+ glRunnables.add( new GLRunnableTask(newGLRunnables.get(i), null, false) );
+ }
+ rTask = new GLRunnableTask(newGLRunnables.get(count-1),
+ wait ? rTaskLock : null,
+ wait /* catch Exceptions if waiting for result */);
+ glRunnableCount++;
glRunnables.add(rTask);
}
- if( !deferred ) {
+ if( !deferredHere ) {
drawable.display();
} else if( wait ) {
try {
rTaskLock.wait(); // free lock, allow execution of rTask
- } catch (InterruptedException ie) {
+ } catch (final InterruptedException ie) {
throwable = ie;
}
if(null==throwable) {
@@ -524,7 +996,17 @@ public class GLDrawableHelper {
return true;
}
- public final void setAutoSwapBufferMode(boolean enable) {
+ public final void enqueue(final GLRunnable glRunnable) {
+ if( null == glRunnable) {
+ return;
+ }
+ synchronized(glRunnablesLock) {
+ glRunnableCount++;
+ glRunnables.add( new GLRunnableTask(glRunnable, null, false) );
+ }
+ }
+
+ public final void setAutoSwapBufferMode(final boolean enable) {
autoSwapBufferMode = enable;
}
@@ -532,26 +1014,89 @@ public class GLDrawableHelper {
return autoSwapBufferMode;
}
+ private final String getExclusiveContextSwitchString() {
+ return 0 == exclusiveContextSwitch ? "nop" : ( 0 > exclusiveContextSwitch ? "released" : "claimed" ) ;
+ }
+
/**
- * @param t the thread for which context release shall be skipped, usually the animation thread,
- * ie. {@link Animator#getThread()}.
- * @deprecated this is an experimental feature,
- * intended for measuring performance in regards to GL context switch
- * and only being used if {@link #PERF_STATS} is enabled
- * by defining property <code>jogl.debug.GLDrawable.PerfStats</code>.
+ * Dedicates this instance's {@link GLContext} to the given thread.<br/>
+ * The thread will exclusively claim the {@link GLContext} via {@link #display()} and not release it
+ * until {@link #destroy()} or <code>setExclusiveContextThread(null)</code> has been called.
+ * <p>
+ * Default non-exclusive behavior is <i>requested</i> via <code>setExclusiveContextThread(null)</code>,
+ * which will cause the next call of {@link #display()} on the exclusive thread to
+ * release the {@link GLContext}. Only after it's async release, {@link #getExclusiveContextThread()}
+ * will return <code>null</code>.
+ * </p>
+ * <p>
+ * To release a previous made exclusive thread, a user issues <code>setExclusiveContextThread(null)</code>
+ * and may poll {@link #getExclusiveContextThread()} until it returns <code>null</code>,
+ * <i>while</i> the exclusive thread is still running.
+ * </p>
+ * <p>
+ * Note: Setting a new exclusive thread without properly releasing a previous one
+ * will throw an GLException.
+ * </p>
+ * <p>
+ * One scenario could be to dedicate the context to the {@link com.jogamp.opengl.util.AnimatorBase#getThread() animator thread}
+ * and spare redundant context switches.
+ * </p>
+ * @param t the exclusive thread to claim the context, or <code>null</code> for default operation.
+ * @return previous exclusive context thread
+ * @throws GLException If an exclusive thread is still active but a new one is attempted to be set
*/
- public final void setSkipContextReleaseThread(Thread t) {
- skipContextReleaseThread = t;
+ public final Thread setExclusiveContextThread(final Thread t, final GLContext context) throws GLException {
+ if (DEBUG) {
+ System.err.println("GLDrawableHelper.setExclusiveContextThread(): START switch "+getExclusiveContextSwitchString()+", thread "+exclusiveContextThread+" -> "+t+" -- currentThread "+Thread.currentThread());
+ }
+ final Thread oldExclusiveContextThread = exclusiveContextThread;
+ if( exclusiveContextThread == t ) {
+ exclusiveContextSwitch = 0; // keep
+ } else if( null == t ) {
+ exclusiveContextSwitch = -1; // release
+ } else {
+ exclusiveContextSwitch = 1; // claim
+ if( null != exclusiveContextThread ) {
+ throw new GLException("Release current exclusive Context Thread "+exclusiveContextThread+" first");
+ }
+ if( null != context && context.isCurrent() ) {
+ try {
+ forceNativeRelease(context);
+ } catch (final Throwable ex) {
+ flushGLRunnables();
+ throw GLException.newGLException(ex);
+ }
+ }
+ exclusiveContextThread = t;
+ }
+ if (DEBUG) {
+ System.err.println("GLDrawableHelper.setExclusiveContextThread(): END switch "+getExclusiveContextSwitchString()+", thread "+exclusiveContextThread+" -- currentThread "+Thread.currentThread());
+ }
+ return oldExclusiveContextThread;
}
/**
- * @deprecated see {@link #setSkipContextReleaseThread(Thread)}
+ * @see #setExclusiveContextThread(Thread, GLContext)
*/
- public final Thread getSkipContextReleaseThread() {
- return skipContextReleaseThread;
+ public final Thread getExclusiveContextThread() {
+ return exclusiveContextThread;
+ }
+
+ private static final ThreadLocal<WeakReference<Runnable>> perThreadInitAction = new ThreadLocal<WeakReference<Runnable>>();
+ private static final Runnable getLastInitAction() {
+ final WeakReference<Runnable> lastInitActionWR = perThreadInitAction.get();
+ if( null != lastInitActionWR ) {
+ final Runnable lastInitAction = lastInitActionWR.get();
+ if( null == lastInitAction ) {
+ perThreadInitAction.set(null);
+ }
+ return lastInitAction;
+ }
+ return null;
+ }
+ private static final void setLastInitAction(final Runnable initAction) {
+ perThreadInitAction.set(new WeakReference<Runnable>(initAction));
}
-
- private static final ThreadLocal<Runnable> perThreadInitAction = new ThreadLocal<Runnable>();
/** Principal helper method which runs a Runnable with the context
made current. This could have been made part of GLContext, but a
@@ -569,209 +1114,332 @@ public class GLDrawableHelper {
* @param runnable
* @param initAction
*/
- public final void invokeGL(GLDrawable drawable,
- GLContext context,
- Runnable runnable,
- Runnable initAction) {
+ public final void invokeGL(final GLDrawable drawable,
+ final GLContext context,
+ final Runnable runnable,
+ final Runnable initAction) {
if(null==context) {
if (DEBUG) {
- Exception e = new GLException(Thread.currentThread().getName()+" Info: GLDrawableHelper " + this + ".invokeGL(): NULL GLContext");
- e.printStackTrace();
+ ExceptionUtils.dumpThrowable("informal", new GLException("Info: GLDrawableHelper " + this + ".invokeGL(): NULL GLContext"));
}
return;
}
if(PERF_STATS) {
- invokeGLImplStats(drawable, context, runnable, initAction, null);
+ invokeGLImplStats(drawable, context, runnable, initAction);
} else {
- invokeGLImpl(drawable, context, runnable, initAction, null);
+ invokeGLImpl(drawable, context, runnable, initAction);
}
}
- /**
- * Principal helper method which runs {@link #dispose(GLAutoDrawable)} with the context
- * made current and destroys the context afterwards while holding the lock.
- *
+ /**
+ * Principal helper method which runs
+ * {@link #disposeAllGLEventListener(GLAutoDrawable, boolean) disposeAllGLEventListener(autoDrawable, false)}
+ * with the context made current.
+ * <p>
+ * If <code>destroyContext</code> is <code>true</code> the context is destroyed in the end while holding the lock.
+ * </p>
+ * <p>
+ * If <code>destroyContext</code> is <code>false</code> the context is natively released, i.e. released as often as locked before.
+ * </p>
* @param autoDrawable
- * @param drawable
* @param context
- * @param postAction
+ * @param destroyContext destroy context in the end while holding the lock
+ * @throws GLException caused by {@link GLEventListener#dispose(GLAutoDrawable)} or context closing
+ *
*/
- public final void disposeGL(GLAutoDrawable autoDrawable,
- GLDrawable drawable,
- GLContext context,
- Runnable postAction) {
- if(PERF_STATS) {
- invokeGLImplStats(drawable, context, null, null, autoDrawable);
- } else {
- invokeGLImpl(drawable, context, null, null, autoDrawable);
- }
- if(null != postAction) {
- postAction.run();
- }
- }
-
- private final void invokeGLImpl(GLDrawable drawable,
- GLContext context,
- Runnable runnable,
- Runnable initAction,
- GLAutoDrawable disposeAutoDrawable) {
- final Thread currentThread = Thread.currentThread();
-
- final boolean isDisposeAction = null==initAction ;
-
+ public final void disposeGL(final GLAutoDrawable autoDrawable,
+ final GLContext context, final boolean destroyContext) throws GLException {
// Support for recursive makeCurrent() calls as well as calling
// other drawables' display() methods from within another one's
GLContext lastContext = GLContext.getCurrent();
Runnable lastInitAction = null;
if (lastContext != null) {
if (lastContext == context) {
- lastContext = null; // utilize recursive locking
+ lastContext = null;
} else {
- lastInitAction = perThreadInitAction.get();
+ // utilize recursive locking
+ lastInitAction = getLastInitAction();
lastContext.release();
}
}
- int res = GLContext.CONTEXT_NOT_CURRENT;
-
+
+ GLException disposeCaught = null;
+ Throwable contextCloseCaught = null;
+
+ int res;
try {
res = context.makeCurrent();
if (GLContext.CONTEXT_NOT_CURRENT != res) {
- if(!isDisposeAction) {
- perThreadInitAction.set(initAction);
- if (GLContext.CONTEXT_CURRENT_NEW == res) {
- if (DEBUG) {
- System.err.println("GLDrawableHelper " + this + ".invokeGL(): Running initAction");
- }
- initAction.run();
- }
- runnable.run();
- if (autoSwapBufferMode) {
- drawable.swapBuffers();
- }
- } else {
- if(GLContext.CONTEXT_CURRENT_NEW == res) {
- throw new GLException(currentThread.getName()+" GLDrawableHelper " + this + ".invokeGL(): Dispose case (no init action given): Native context was not created (new ctx): "+context);
- }
- if(listeners.size()>0) {
- dispose(disposeAutoDrawable);
+ if(GLContext.CONTEXT_CURRENT_NEW == res) {
+ throw new GLException(GLDrawableHelper.getThreadName()+" GLDrawableHelper " + this + ".invokeGL(): Dispose case (no init action given): Native context was not created (new ctx): "+context);
+ }
+ if( listeners.size() > 0 && null != autoDrawable ) {
+ try {
+ disposeAllGLEventListener(autoDrawable, false);
+ } catch(final GLException t) {
+ disposeCaught = t;
}
}
}
} finally {
try {
- if(isDisposeAction) {
+ if(destroyContext) {
context.destroy();
- flushGLRunnables();
- } else if( GLContext.CONTEXT_NOT_CURRENT != res ) {
- context.release();
+ } else {
+ forceNativeRelease(context);
}
- } catch (Exception e) {
- System.err.println("Catched: "+e.getMessage());
- e.printStackTrace();
+ } catch (final Throwable t) {
+ contextCloseCaught = t;
}
+ flushGLRunnables(); // always flush GLRunnables at dispose
+
if (lastContext != null) {
final int res2 = lastContext.makeCurrent();
if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) {
lastInitAction.run();
}
}
+ if( null != disposeCaught ) {
+ if( null != contextCloseCaught ) {
+ ExceptionUtils.dumpThrowable("subsequent", contextCloseCaught);
+ }
+ throw disposeCaught;
+ }
+ if( null != contextCloseCaught ) {
+ throw GLException.newGLException(contextCloseCaught);
+ }
}
}
-
- private final void invokeGLImplStats(GLDrawable drawable,
- GLContext context,
- Runnable runnable,
- Runnable initAction,
- GLAutoDrawable disposeAutoDrawable) {
- final Thread currentThread = Thread.currentThread();
-
- final boolean isDisposeAction = null==initAction ;
-
- // Support for recursive makeCurrent() calls as well as calling
- // other drawables' display() methods from within another one's
- int res = GLContext.CONTEXT_NOT_CURRENT;
- GLContext lastContext = GLContext.getCurrent();
- Runnable lastInitAction = null;
- if (lastContext != null) {
- if (lastContext == context) {
- if( currentThread == skipContextReleaseThread ) {
- res = GLContext.CONTEXT_CURRENT;
- } // else: utilize recursive locking
- lastContext = null;
- } else {
- lastInitAction = perThreadInitAction.get();
- lastContext.release();
- }
- }
-
- long t0 = System.currentTimeMillis();
- long tdA = 0; // makeCurrent
- long tdR = 0; // render time
- long tdS = 0; // swapBuffers
- long tdX = 0; // release
- boolean ctxClaimed = false;
- boolean ctxReleased = false;
- boolean ctxDestroyed = false;
- try {
- if (res == GLContext.CONTEXT_NOT_CURRENT) {
- res = context.makeCurrent();
- ctxClaimed = true;
- }
- if (res != GLContext.CONTEXT_NOT_CURRENT) {
- if(!isDisposeAction) {
- perThreadInitAction.set(initAction);
- if (res == GLContext.CONTEXT_CURRENT_NEW) {
- if (DEBUG) {
- System.err.println("GLDrawableHelper " + this + ".invokeGL(): Running initAction");
- }
- initAction.run();
- }
- tdR = System.currentTimeMillis();
- tdA = tdR - t0; // makeCurrent
- runnable.run();
- tdS = System.currentTimeMillis();
- tdR = tdS - tdR; // render time
- if (autoSwapBufferMode) {
- drawable.swapBuffers();
- tdX = System.currentTimeMillis();
- tdS = tdX - tdS; // swapBuffers
- }
- } else {
- if(res == GLContext.CONTEXT_CURRENT_NEW) {
- throw new GLException(currentThread.getName()+" GLDrawableHelper " + this + ".invokeGL(): Dispose case (no init action given): Native context was not created (new ctx): "+context);
- }
- if(listeners.size()>0) {
- dispose(disposeAutoDrawable);
- }
- }
+
+ private final void invokeGLImpl(final GLDrawable drawable,
+ final GLContext context,
+ final Runnable runnable,
+ final Runnable initAction) {
+ final Thread currentThread = Thread.currentThread();
+
+ Throwable glEventListenerCaught = null;
+ Throwable contextReleaseCaught = null;
+
+ // Exclusive Cases:
+ // 1: lock - unlock : default
+ // 2: lock - - : exclusive, not locked yet
+ // 3: - - - : exclusive, already locked
+ // 4: - - unlock : ex-exclusive, already locked
+ final boolean _isExclusiveThread, _releaseExclusiveThread;
+ if( null != exclusiveContextThread) {
+ if( currentThread == exclusiveContextThread ) {
+ _releaseExclusiveThread = 0 > exclusiveContextSwitch;
+ _isExclusiveThread = !_releaseExclusiveThread;
+ exclusiveContextSwitch = 0;
+ } else {
+ // Exclusive thread usage, but on other thread
+ return;
+ }
+ } else {
+ _releaseExclusiveThread = false;
+ _isExclusiveThread = false;
}
- } finally {
+
+ // Support for recursive makeCurrent() calls as well as calling
+ // other drawables' display() methods from within another one's
+ int res = GLContext.CONTEXT_NOT_CURRENT;
+ GLContext lastContext = GLContext.getCurrent();
+ Runnable lastInitAction = null;
+ if (lastContext != null) {
+ if (lastContext == context) {
+ res = GLContext.CONTEXT_CURRENT;
+ lastContext = null;
+ } else {
+ // utilize recursive locking
+ lastInitAction = getLastInitAction();
+ lastContext.release();
+ }
+ }
+
try {
- if(isDisposeAction) {
- context.destroy();
+ final boolean releaseContext;
+ if( GLContext.CONTEXT_NOT_CURRENT == res ) {
+ res = context.makeCurrent();
+ releaseContext = !_isExclusiveThread;
+ } else {
+ releaseContext = _releaseExclusiveThread;
+ }
+ if (GLContext.CONTEXT_NOT_CURRENT != res) {
+ try {
+ setLastInitAction(initAction);
+ if (GLContext.CONTEXT_CURRENT_NEW == res) {
+ if (DEBUG) {
+ System.err.println("GLDrawableHelper " + this + ".invokeGL(): Running initAction");
+ }
+ initAction.run();
+ }
+ runnable.run();
+ if ( autoSwapBufferMode ) {
+ drawable.swapBuffers();
+ }
+ } catch (final Throwable t) {
+ glEventListenerCaught = t;
+ } finally {
+ if( _releaseExclusiveThread ) {
+ exclusiveContextThread = null;
+ if (DEBUG) {
+ System.err.println("GLDrawableHelper.invokeGL() - Release ExclusiveContextThread -- currentThread "+Thread.currentThread());
+ }
+ }
+ if( releaseContext ) {
+ try {
+ context.release();
+ } catch (final Throwable t) {
+ contextReleaseCaught = t;
+ }
+ }
+ }
+ }
+ } finally {
+ if (lastContext != null) {
+ final int res2 = lastContext.makeCurrent();
+ if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) {
+ lastInitAction.run();
+ }
+ }
+ if( null != glEventListenerCaught ) {
+ flushGLRunnables();
+ if( null != contextReleaseCaught ) {
+ ExceptionUtils.dumpThrowable("subsequent", contextReleaseCaught);
+ }
+ throw GLException.newGLException(glEventListenerCaught);
+ }
+ if( null != contextReleaseCaught ) {
flushGLRunnables();
- ctxDestroyed = true;
- } else if( res != GLContext.CONTEXT_NOT_CURRENT &&
- (null == skipContextReleaseThread || currentThread != skipContextReleaseThread) ) {
- context.release();
- ctxReleased = true;
+ throw GLException.newGLException(contextReleaseCaught);
}
- } catch (Exception e) {
- System.err.println("Catched: "+e.getMessage());
- e.printStackTrace();
}
+ }
- tdX = System.currentTimeMillis() - tdX; // release / destroy
+ private final void invokeGLImplStats(final GLDrawable drawable,
+ final GLContext context,
+ final Runnable runnable,
+ final Runnable initAction) {
+ final Thread currentThread = Thread.currentThread();
+
+ Throwable glEventListenerCaught = null;
+ Throwable contextReleaseCaught = null;
+
+ // Exclusive Cases:
+ // 1: lock - unlock : default
+ // 2: lock - - : exclusive, not locked yet
+ // 3: - - - : exclusive, already locked
+ // 4: - - unlock : ex-exclusive, already locked
+ final boolean _isExclusiveThread, _releaseExclusiveThread;
+ if( null != exclusiveContextThread) {
+ if( currentThread == exclusiveContextThread ) {
+ _releaseExclusiveThread = 0 > exclusiveContextSwitch;
+ _isExclusiveThread = !_releaseExclusiveThread;
+ } else {
+ // Exclusive thread usage, but on other thread
+ return;
+ }
+ } else {
+ _releaseExclusiveThread = false;
+ _isExclusiveThread = false;
+ }
+
+ // Support for recursive makeCurrent() calls as well as calling
+ // other drawables' display() methods from within another one's
+ int res = GLContext.CONTEXT_NOT_CURRENT;
+ GLContext lastContext = GLContext.getCurrent();
+ Runnable lastInitAction = null;
if (lastContext != null) {
- final int res2 = lastContext.makeCurrent();
- if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) {
- lastInitAction.run();
- }
+ if (lastContext == context) {
+ res = GLContext.CONTEXT_CURRENT;
+ lastContext = null;
+ } else {
+ // utilize recursive locking
+ lastInitAction = getLastInitAction();
+ lastContext.release();
+ }
}
- }
- long td = System.currentTimeMillis() - t0;
- System.err.println("td0 "+td+"ms, fps "+(1.0/(td/1000.0))+", td-makeCurrent: "+tdA+"ms, td-render "+tdR+"ms, td-swap "+tdS+"ms, td-release "+tdX+"ms, ctx claimed: "+ctxClaimed+", ctx release: "+ctxReleased+", ctx destroyed "+ctxDestroyed);
+
+ final long t0 = System.currentTimeMillis();
+ long tdA = 0; // makeCurrent
+ long tdR = 0; // render time
+ long tdS = 0; // swapBuffers
+ long tdX = 0; // release
+ boolean ctxClaimed = false;
+ boolean ctxReleased = false;
+ final boolean ctxDestroyed = false;
+ try {
+ final boolean releaseContext;
+ if( GLContext.CONTEXT_NOT_CURRENT == res ) {
+ res = context.makeCurrent();
+ releaseContext = !_isExclusiveThread;
+ ctxClaimed = true;
+ } else {
+ releaseContext = _releaseExclusiveThread;
+ }
+ if (GLContext.CONTEXT_NOT_CURRENT != res) {
+ try {
+ setLastInitAction(initAction);
+ if (GLContext.CONTEXT_CURRENT_NEW == res) {
+ if (DEBUG) {
+ System.err.println("GLDrawableHelper " + this + ".invokeGL(): Running initAction");
+ }
+ initAction.run();
+ }
+ tdR = System.currentTimeMillis();
+ tdA = tdR - t0; // makeCurrent
+ runnable.run();
+ tdS = System.currentTimeMillis();
+ tdR = tdS - tdR; // render time
+ if ( autoSwapBufferMode ) {
+ drawable.swapBuffers();
+ tdX = System.currentTimeMillis();
+ tdS = tdX - tdS; // swapBuffers
+ }
+ } catch (final Throwable t) {
+ glEventListenerCaught = t;
+ } finally {
+ if( _releaseExclusiveThread ) {
+ exclusiveContextSwitch = 0;
+ exclusiveContextThread = null;
+ if (DEBUG) {
+ System.err.println("GLDrawableHelper.invokeGL() - Release ExclusiveContextThread -- currentThread "+Thread.currentThread());
+ }
+ }
+ if( releaseContext ) {
+ try {
+ context.release();
+ ctxReleased = true;
+ } catch (final Throwable t) {
+ contextReleaseCaught = t;
+ }
+ }
+ }
+ }
+ } finally {
+ tdX = System.currentTimeMillis() - tdX; // release / destroy
+ if (lastContext != null) {
+ final int res2 = lastContext.makeCurrent();
+ if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) {
+ lastInitAction.run();
+ }
+ }
+ if( null != glEventListenerCaught ) {
+ flushGLRunnables();
+ if( null != contextReleaseCaught ) {
+ ExceptionUtils.dumpThrowable("subsequent", contextReleaseCaught);
+ }
+ throw GLException.newGLException(glEventListenerCaught);
+ }
+ if( null != contextReleaseCaught ) {
+ flushGLRunnables();
+ throw GLException.newGLException(contextReleaseCaught);
+ }
+ }
+ final long td = System.currentTimeMillis() - t0;
+ System.err.println("td0 "+td+"ms, fps "+(1.0/(td/1000.0))+", td-makeCurrent: "+tdA+"ms, td-render "+tdR+"ms, td-swap "+tdS+"ms, td-release "+tdX+"ms, ctx claimed: "+ctxClaimed+", ctx release: "+ctxReleased+", ctx destroyed "+ctxDestroyed);
}
-
+
+ protected static String getThreadName() { return Thread.currentThread().getName(); }
}