jogl.debug.GLDrawable.PerfStats
is defined. */
private static final boolean PERF_STATS = Debug.isPropertyDefined("jogl.debug.GLDrawable.PerfStats", true);
protected static final boolean DEBUG = GLDrawableImpl.DEBUG;
private final Object listenersLock = new Object();
private final ArrayList* If the old or new context was current on this thread, it is being released before switching the drawable. *
** 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! *
** No locking is being performed on the drawable, caller is required to take care of it. *
* * @param drawable the drawable which context is changed * @param oldCtx the old context * @param newCtx the new context * @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) */ public static final boolean switchContext(GLDrawable drawable, GLContext oldCtx, GLContext newCtx, int newCtxCreationFlags) { if( null != oldCtx && oldCtx.isCurrent() ) { oldCtx.release(); } 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; } return newCtxCurrent; } /** * If the drawable is not realized, OP is a NOP. ** No locking is being performed, caller is required to take care of it. *
* * @param drawable * @param context maybe null * @return the new drawable */ public static final GLDrawableImpl recreateGLDrawable(GLDrawableImpl drawable, GLContext context) { if( ! drawable.isRealized() ) { return drawable; } 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( null != context ) { // Ensure to sync GL command stream if( currentContext != context ) { context.makeCurrent(); } context.getGL().glFinish(); context.release(); } if(null != proxySurface) { proxySurface.enableUpstreamSurfaceHookLifecycle(false); } try { drawable.setRealized(false); drawable = (GLDrawableImpl) factory.createGLDrawable(surface); // [2] drawable.setRealized(true); } finally { if(null != proxySurface) { proxySurface.enableUpstreamSurfaceHookLifecycle(true); } } if(null != context) { context.setGLDrawable(drawable, true); // re-association } if( null != currentContext ) { currentContext.makeCurrent(); } return drawable; } /** * Performs resize operation on the given drawable, assuming it is offscreen. ** 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. *
** May recreate the drawable via {@link #recreateGLDrawable(GLDrawableImpl, GLContext)} * in case of a a pbuffer- or pixmap-drawable. *
** FBO drawables are resized w/o drawable destruction. *
** Offscreen resize operation is validated w/ drawable size in the end. * An exception is thrown if not successful. *
* * @param drawable * @param context * @param newWidth the new width, it's minimum is capped to 1 * @param newHeight the new height, it's minimum is capped to 1 * @return the new drawable in case of an pbuffer/pixmap drawable, otherwise the passed drawable is being returned. * @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 { 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) { throw new NativeWindowException("Could not lock surface of drawable: "+drawable); } boolean validateSize = true; try { 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); } 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() ) { ((GLFBODrawable) drawable).resetSize(context.getGL()); } } else { drawable = GLDrawableHelper.recreateGLDrawable(drawable, context); } } finally { ns.unlockSurface(); } if( validateSize && ( drawable.getWidth() != newWidth || drawable.getHeight() != newHeight ) ) { throw new InternalError("Incomplete resize operation: expected "+newWidth+"x"+newHeight+", has: "+drawable); } return drawable; } public final void addGLEventListener(GLEventListener listener) { addGLEventListener(-1, listener); } public final void addGLEventListener(int index, GLEventListener listener) { synchronized(listenersLock) { if(0>index) { index = listeners.size(); } // GLEventListener may be added after context is created, // hence we earmark initialization for the next display call. listenersToBeInit.add(listener); listeners.add(index, 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(GLEventListener listener) { synchronized(listenersLock) { listenersToBeInit.remove(listener); return listeners.remove(listener) ? listener : null; } } public final GLEventListener removeGLEventListener(int index) throws IndexOutOfBoundsException { synchronized(listenersLock) { if(0>index) { index = listeners.size()-1; } 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 getGLEventListenerInitState(GLEventListener listener) { synchronized(listenersLock) { return !listenersToBeInit.contains(listener); } } public final void setGLEventListenerInitState(GLEventListener listener, boolean initialized) { synchronized(listenersLock) { if(initialized) { listenersToBeInit.remove(listener); } else { listenersToBeInit.add(listener); } } } /** * Disposes the given {@link GLEventListener} via {@link GLEventListener#dispose(GLAutoDrawable)} * if it has been initialized and added to this queue. *
* If remove
is true
, the {@link GLEventListener} is removed from this drawable queue before disposal,
* otherwise marked uninitialized.
*
* Please consider using {@link #disposeGLEventListener(GLAutoDrawable, GLDrawable, GLContext, GLEventListener)} * for correctness, i.e. encapsulating all calls w/ makeCurrent etc. *
* @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 GLEventListener disposeGLEventListener(GLAutoDrawable autoDrawable, GLEventListener listener, 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)}. *
* If remove
is true
, the {@link GLEventListener}s are removed from this drawable queue before disposal,
* otherwise maked uninitialized.
*
* Please consider using {@link #disposeAllGLEventListener(GLAutoDrawable, GLContext, boolean)} * or {@link #disposeGL(GLAutoDrawable, GLContext)} * for correctness, i.e. encapsulating all calls w/ makeCurrent etc. *
* @param autoDrawable * @return the disposal count */ public final int disposeAllGLEventListener(GLAutoDrawable autoDrawable, boolean remove) { int disposeCount = 0; synchronized(listenersLock) { if( remove ) { for (int count = listeners.size(); 0 < count; count--) { final GLEventListener listener = listeners.remove(0); if( !listenersToBeInit.remove(listener) ) { listener.dispose(autoDrawable); disposeCount++; } } } else { final int count = listeners.size(); for (int i = 0; i < count; i++) { final GLEventListener listener = listeners.get(i); if( !listenersToBeInit.contains(listener) ) { listener.dispose(autoDrawable); listenersToBeInit.add(listener); disposeCount++; } } } } return disposeCount; } /** * Principal helper method which runs {@link #disposeGLEventListener(GLAutoDrawable, GLEventListener, boolean)} * with the context made current. ** 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. *
* * @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() { 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. ** 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. *
* * @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() { public void run() { disposeAllGLEventListener(autoDrawable, remove); } }; invokeGL(drawable, context, action, nop); if(isPaused) { animatorCtrl.resume(); } } private final void init(GLEventListener l, GLAutoDrawable drawable, boolean sendReshape) { l.init(drawable); if(sendReshape) { reshape(l, drawable, 0, 0, drawable.getWidth(), drawable.getHeight(), true /* setViewport */, false /* checkInit */); } } /** * 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(GLAutoDrawable drawable, boolean sendReshape) { synchronized(listenersLock) { final ArrayList
* If wait
is true
the call blocks until the glRunnable
* has been executed.
*
* If wait
is true
and
* {@link GLDrawable#isRealized()} returns false
or {@link GLAutoDrawable#getContext()} returns null
,
* the call is ignored and returns false
.
* This helps avoiding deadlocking the caller.
*
true
block until execution of glRunnable
is finished, otherwise return immediatly w/o waiting
* @param glRunnable the {@link GLRunnable} to execute within {@link #display()}
* @return true
if the {@link GLRunnable} has been processed or queued, otherwise false
.
*/
public final boolean invoke(GLAutoDrawable drawable, boolean wait, GLRunnable glRunnable) {
if( null == glRunnable || null == drawable ||
wait && ( !drawable.isRealized() || null==drawable.getContext() ) ) {
return false;
}
GLRunnableTask rTask = null;
Object rTaskLock = new Object();
Throwable throwable = null;
synchronized(rTaskLock) {
final boolean deferred;
synchronized(glRunnablesLock) {
deferred = isAnimatorAnimatingOnOtherThread();
if(!deferred) {
wait = false; // don't wait if exec immediatly
}
rTask = new GLRunnableTask(glRunnable,
wait ? rTaskLock : null,
wait /* catch Exceptions if waiting for result */);
glRunnables.add(rTask);
}
if( !deferred ) {
drawable.display();
} else if( wait ) {
try {
rTaskLock.wait(); // free lock, allow execution of rTask
} catch (InterruptedException ie) {
throwable = ie;
}
if(null==throwable) {
throwable = rTask.getThrowable();
}
if(null!=throwable) {
throw new RuntimeException(throwable);
}
}
}
return true;
}
public final boolean invoke(GLAutoDrawable drawable, boolean wait, ListNote: Locking of the surface is implicit done by {@link GLContext#makeCurrent()}, where unlocking is performed by the latter {@link GLContext#release()}.
* * @param drawable * @param context * @param runnable * @param 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(); } return; } if(PERF_STATS) { invokeGLImplStats(drawable, context, runnable, initAction); } else { invokeGLImpl(drawable, context, runnable, initAction); } } /** * Principal helper method which runs * {@link #disposeAllGLEventListener(GLAutoDrawable, boolean) disposeAllGLEventListener(autoDrawable, false)} * with the context made current and destroys the context afterwards while holding the lock. * @param autoDrawable * @param context */ public final void disposeGL(final GLAutoDrawable autoDrawable, final GLContext context) { // 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 } else { lastInitAction = perThreadInitAction.get(); lastContext.release(); } } int res = GLContext.CONTEXT_NOT_CURRENT; try { res = context.makeCurrent(); if (GLContext.CONTEXT_NOT_CURRENT != res) { if(GLContext.CONTEXT_CURRENT_NEW == res) { throw new GLException(Thread.currentThread().getName()+" GLDrawableHelper " + this + ".invokeGL(): Dispose case (no init action given): Native context was not created (new ctx): "+context); } if( listeners.size() > 0 && null != autoDrawable ) { disposeAllGLEventListener(autoDrawable, false); } } } finally { try { context.destroy(); flushGLRunnables(); } catch (Exception e) { System.err.println("Catched: "+e.getMessage()); e.printStackTrace(); } if (lastContext != null) { final int res2 = lastContext.makeCurrent(); if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) { lastInitAction.run(); } } } } private final void invokeGLImpl(final GLDrawable drawable, final GLContext context, final Runnable runnable, final Runnable initAction) { // 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 } else { lastInitAction = perThreadInitAction.get(); lastContext.release(); } } int res = GLContext.CONTEXT_NOT_CURRENT; try { res = context.makeCurrent(); if (GLContext.CONTEXT_NOT_CURRENT != res) { 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(); } } } finally { try { context.release(); } catch (Exception e) { System.err.println("Catched: "+e.getMessage()); e.printStackTrace(); } if (lastContext != null) { final int res2 = lastContext.makeCurrent(); if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) { lastInitAction.run(); } } } } private final void invokeGLImplStats(final GLDrawable drawable, final GLContext context, final Runnable runnable, final Runnable initAction) { final Thread currentThread = Thread.currentThread(); // 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) { 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 } } } finally { try { if( res != GLContext.CONTEXT_NOT_CURRENT && (null == skipContextReleaseThread || currentThread != skipContextReleaseThread) ) { context.release(); ctxReleased = true; } } catch (Exception e) { System.err.println("Catched: "+e.getMessage()); e.printStackTrace(); } tdX = System.currentTimeMillis() - tdX; // release / destroy if (lastContext != null) { final int res2 = lastContext.makeCurrent(); if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) { lastInitAction.run(); } } } 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); } }