diff options
Diffstat (limited to 'src/jogl/classes/jogamp/opengl/GLDrawableHelper.java')
-rw-r--r-- | src/jogl/classes/jogamp/opengl/GLDrawableHelper.java | 1342 |
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(); } } |