diff options
author | Sven Gothel <[email protected]> | 2012-07-22 04:16:55 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2012-07-22 04:16:55 +0200 |
commit | 4b5a0f6557d7152ec770bc13ad3c494449de0529 (patch) | |
tree | 12fc93f9e7f34b61c1d5748f0e6a1cd85fc6125b /src/jogl | |
parent | adc9522ccaff74eb779d4d33905d76d52acb36bb (diff) |
Fix Bug 606 - New AWT threading implementation breaks .. ; Fix GLAutoDrawable multi-threading w/ proper pattern (hope so)
Considering code changes and remarks:
3ed491213f8f7f05d7b9866b50d764370d8ff5f6
1a91ec5c8b6fd9d9db7bc115569c369fe7b38e9b
3334a924309a9361a448d69bc707d4cce416b430
4f27bcecf7484dc041551f52a5c49e2884cb3867
It seems necessary to have
- recursive locking employed for all semantic actions which changes drawable & context (and the Window resource)
- to avoid deadlock, we have to ensure the locked code segment will not spawn
off to another thread, or a thread holds the lock, spawns of an action requiring the lock. .. sure
- other read-only methods (flags, ..) shall at least utilize a safe local copy of a volatile field
if further use to produce the result is necessary.
- flags like sendReshape require to be volatile to guarantee it's being processed
Patch impacts: AWT/SWT GLCanvas, GLAutoDrawableBase [and it's specializations]
and hopefully closes any loopholes of missing a cache hit, etc.
If you review this and find optimizations, i.e. removing a lock due to semantics etc,
don't hold back and discuss it, please.
Diffstat (limited to 'src/jogl')
6 files changed, 500 insertions, 328 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java index 0d9d3ddf5..64ee1c1ad 100644 --- a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java +++ b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java @@ -63,25 +63,13 @@ import org.eclipse.swt.widgets.Shell; import com.jogamp.common.GlueGenVersion; import com.jogamp.common.os.Platform; import com.jogamp.common.util.VersionUtil; +import com.jogamp.common.util.locks.LockFactory; +import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.nativewindow.swt.SWTAccessor; import com.jogamp.opengl.JoglVersion; /** * Native SWT Canvas implementing GLAutoDrawable - * <p> - * FIXME: If this instance runs in multithreading mode, see {@link Threading#isSingleThreaded()} (impossible), - * proper recursive locking is required for drawable/context @ destroy and display. - * Recreation etc could pull those instances while animating! - * Simply locking before using drawable/context offthread - * would allow a deadlock situation! - * </p> - * <p> - * NOTE: [MT-0] Methods utilizing [volatile] drawable/context are not synchronized. - In case any of the methods are called outside of a locked state - extra care should be added. Maybe we shall expose locking facilities to the user. - However, since the user shall stick to the GLEventListener model while utilizing - GLAutoDrawable implementations, she is safe due to the implicit locked state. - * </p> */ public class GLCanvas extends Canvas implements GLAutoDrawable { @@ -97,8 +85,9 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { //private static final boolean useSWTThread = ThreadingImpl.getMode() != ThreadingImpl.WORKER; /* GL Stuff */ + private final RecursiveLock lock = LockFactory.createRecursiveLock(); private final GLDrawableHelper helper = new GLDrawableHelper(); - private volatile GLDrawable drawable; // volatile avoids locking all accessors. FIXME still need to sync destroy/display + private volatile GLDrawable drawable; // volatile: avoid locking for read-only access private GLContext context; /* Native window surface */ @@ -112,7 +101,7 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { private final GLCapabilitiesImmutable glCapsRequested; /* Flag indicating whether an unprocessed reshape is pending. */ - private volatile boolean sendReshape; + private volatile boolean sendReshape; // volatile: maybe written by WindowManager thread w/o locking /* * Invokes init(...) on all GLEventListeners. Assumes context is current when run. @@ -141,10 +130,16 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { }; /* Action to make specified context current prior to running displayAction */ - private final Runnable makeCurrentAndDisplayAction = new Runnable() { + private final Runnable makeCurrentAndDisplayOnEDTAction = new Runnable() { @Override public void run() { - helper.invokeGL(drawable, context, displayAction, initAction); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + helper.invokeGL(drawable, context, displayAction, initAction); + } finally { + _lock.unlock(); + } } }; @@ -157,10 +152,16 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { }; /* Swaps buffers, making the GLContext current first */ - private final Runnable makeCurrentAndSwapBuffersAction = new Runnable() { + private final Runnable makeCurrentAndSwapBuffersOnEDTAction = new Runnable() { @Override public void run() { - helper.invokeGL(drawable, context, swapBuffersAction, initAction); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + helper.invokeGL(drawable, context, swapBuffersAction, initAction); + } finally { + _lock.unlock(); + } } }; @@ -181,16 +182,33 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { private final Runnable disposeOnEDTGLAction = new Runnable() { @Override public void run() { - helper.disposeGL(GLCanvas.this, drawable, context, postDisposeGLAction); - } - }; - - private final Runnable disposeGraphicsDeviceAction = new Runnable() { - @Override - public void run() { - if (null != device) { - device.close(); - device = null; + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if (null != drawable && null != context) { + boolean animatorPaused = false; + final GLAnimatorControl animator = getAnimator(); + if (null != animator) { + animatorPaused = animator.pause(); + } + + if(context.isCreated()) { + helper.disposeGL(GLCanvas.this, drawable, context, postDisposeGLAction); + } + + if (animatorPaused) { + animator.resume(); + } + } + // SWT is owner of the device handle, not us. + // Hence close() operation is a NOP. + if (null != device) { + device.close(); + device = null; + } + SWTAccessor.setRealized(GLCanvas.this, false); // unrealize .. + } finally { + _lock.unlock(); } } }; @@ -229,7 +247,8 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { clientArea = GLCanvas.this.getClientArea(); - /* Get the nativewindow-Graphics Device associated with this control (which is determined by the parent Composite) */ + /* Get the nativewindow-Graphics Device associated with this control (which is determined by the parent Composite). + * Note: SWT is owner of the native handle, hence no closing operation will be a NOP. */ device = SWTAccessor.getDevice(this); /* Since we have no means of querying the screen index yet, assume 0. Good choice due to Xinerama alike settings anyways. */ final int screenIdx = 0; @@ -345,7 +364,7 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { @Override public void display() { - runInGLThread(makeCurrentAndDisplayAction); + runInGLThread(makeCurrentAndDisplayOnEDTAction); } @Override @@ -370,7 +389,8 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { @Override public GL getGL() { - return (null == context) ? null : context.getGL(); + final GLContext _context = context; + return (null == _context) ? null : _context.getGL(); } @Override @@ -400,27 +420,35 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { @Override public GLContext setContext(GLContext newCtx) { - final GLContext oldCtx = context; - final boolean newCtxCurrent = helper.switchContext(drawable, oldCtx, newCtx, additionalCtxCreationFlags); - context=(GLContextImpl)newCtx; - if(newCtxCurrent) { - context.makeCurrent(); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + final GLContext oldCtx = context; + final boolean newCtxCurrent = helper.switchContext(drawable, oldCtx, newCtx, additionalCtxCreationFlags); + context=(GLContextImpl)newCtx; + if(newCtxCurrent) { + context.makeCurrent(); + } + return oldCtx; + } finally { + _lock.unlock(); } - return oldCtx; } @Override public void setContextCreationFlags(final int arg0) { additionalCtxCreationFlags = arg0; - if(null != context) { - context.setContextCreationFlags(additionalCtxCreationFlags); + final GLContext _context = context; + if(null != _context) { + _context.setContextCreationFlags(additionalCtxCreationFlags); } } @Override public GL setGL(final GL arg0) { - if (null != context) { - context.setGL(arg0); + final GLContext _context = context; + if (null != _context) { + _context.setGL(arg0); return arg0; } return null; @@ -428,12 +456,18 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { @Override public GLContext createContext(final GLContext shareWith) { - if(drawable != null) { - final GLContext _ctx = drawable.createContext(shareWith); - _ctx.setContextCreationFlags(additionalCtxCreationFlags); - return _ctx; + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if(drawable != null) { + final GLContext _ctx = drawable.createContext(shareWith); + _ctx.setContextCreationFlags(additionalCtxCreationFlags); + return _ctx; + } + return null; + } finally { + _lock.unlock(); } - return null; } @Override @@ -452,7 +486,8 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { @Override public GLDrawableFactory getFactory() { - return (drawable != null) ? drawable.getFactory() : null; + final GLDrawable _drawable = drawable; + return (_drawable != null) ? _drawable.getFactory() : null; } @Override @@ -462,17 +497,20 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { @Override public long getHandle() { - return (drawable != null) ? drawable.getHandle() : 0; + final GLDrawable _drawable = drawable; + return (_drawable != null) ? _drawable.getHandle() : 0; } @Override public NativeSurface getNativeSurface() { - return (drawable != null) ? drawable.getNativeSurface() : null; + final GLDrawable _drawable = drawable; + return (_drawable != null) ? _drawable.getNativeSurface() : null; } @Override public boolean isRealized() { - return (drawable != null) ? drawable.isRealized() : false; + final GLDrawable _drawable = drawable; + return (_drawable != null) ? _drawable.isRealized() : false; } @Override @@ -482,41 +520,19 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { @Override public void swapBuffers() throws GLException { - runInGLThread(makeCurrentAndSwapBuffersAction); + runInGLThread(makeCurrentAndSwapBuffersOnEDTAction); } // FIXME: API of update() method ? @Override public void update() { - // FIXME: display(); + // FIXME: display(); ? } @Override public void dispose() { - if (null != drawable && null != context) { // drawable is volatile! - boolean animatorPaused = false; - final GLAnimatorControl animator = getAnimator(); - if (null != animator) { - // can't remove us from animator for recreational addNotify() - animatorPaused = animator.pause(); - } - - if(context.isCreated()) { - runInGLThread(disposeOnEDTGLAction); - } - - if (animatorPaused) { - animator.resume(); - } - } - final Display display = getDisplay(); - - if (display.getThread() == Thread.currentThread()) { - disposeGraphicsDeviceAction.run(); - } else { - display.syncExec(disposeGraphicsDeviceAction); - } - super.dispose(); + runInGLThread(disposeOnEDTGLAction); + super.dispose(); } /** diff --git a/src/jogl/classes/javax/media/opengl/GLAutoDrawableDelegate.java b/src/jogl/classes/javax/media/opengl/GLAutoDrawableDelegate.java index 1f6166719..76959f3f4 100644 --- a/src/jogl/classes/javax/media/opengl/GLAutoDrawableDelegate.java +++ b/src/jogl/classes/javax/media/opengl/GLAutoDrawableDelegate.java @@ -67,18 +67,18 @@ public class GLAutoDrawableDelegate extends GLAutoDrawableBase { } // - // make protected methods accessible + // expose default methods // - public void defaultWindowRepaintOp() { + public final void windowRepaintOp() { super.defaultWindowRepaintOp(); } - public void defaultWindowResizedOp() { + public final void windowResizedOp() { super.defaultWindowResizedOp(); } - public void defaultWindowDestroyNotifyOp() { + public final void windowDestroyNotifyOp() { super.defaultWindowDestroyNotifyOp(); } @@ -90,6 +90,9 @@ public class GLAutoDrawableDelegate extends GLAutoDrawableBase { private final Object upstreamWidget; @Override + protected final RecursiveLock getLock() { return lock; } + + @Override public final Object getUpstreamWidget() { return upstreamWidget; } @@ -97,39 +100,21 @@ public class GLAutoDrawableDelegate extends GLAutoDrawableBase { /** * {@inheritDoc} * <p> - * This implementation calls {@link #defaultDestroyOp()}. + * This implementation calls {@link #defaultDestroy()}. * </p> * <p> * User still needs to destroy the upstream window, which details are hidden from this aspect. + * This can be performed by overriding {@link #destroyImplInLock()}. * </p> */ @Override - public void destroy() { - lock.lock(); - try { - defaultDestroyOp(); - } finally { - lock.unlock(); - } + public final void destroy() { + defaultDestroy(); } @Override public void display() { - if( sendDestroy ) { - sendDestroy=false; - destroy(); - return; - } - - lock.lock(); // sync: context/drawable could been recreated/destroyed while animating - try { - if( null != drawable && drawable.isRealized() && null != context ) { - // surface is locked/unlocked implicit by context's makeCurrent/release - helper.invokeGL(drawable, context, defaultDisplayAction, defaultInitAction); - } - } finally { - lock.unlock(); - } + defaultDisplay(); } // @@ -145,4 +130,9 @@ public class GLAutoDrawableDelegate extends GLAutoDrawableBase { public final void setRealized(boolean realized) { } + @Override + public final void swapBuffers() throws GLException { + defaultSwapBuffers(); + } + } diff --git a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java index c2e36ef9b..694a081b8 100644 --- a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java +++ b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java @@ -84,6 +84,8 @@ import javax.media.opengl.Threading; import com.jogamp.common.GlueGenVersion; import com.jogamp.common.util.VersionUtil; +import com.jogamp.common.util.locks.LockFactory; +import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration; import com.jogamp.nativewindow.awt.AWTGraphicsDevice; import com.jogamp.nativewindow.awt.AWTGraphicsScreen; @@ -132,20 +134,6 @@ import jogamp.opengl.GLDrawableHelper; * <ul> * <li><pre>sun.awt.noerasebackground=true</pre></li> * </ul> - * <p> - * FIXME: If this instance runs in multithreading mode, see {@link Threading#isSingleThreaded()} (default: single-thread), - * proper recursive locking is required for drawable/context @ destroy and display. - * Recreation etc could pull those instances while animating! - * Simply locking before using drawable/context offthread - * would allow a deadlock situation! - * </p> - * <p> - * NOTE: [MT-0] Methods utilizing [volatile] drawable/context are not synchronized. - In case any of the methods are called outside of a locked state - extra care should be added. Maybe we shall expose locking facilities to the user. - However, since the user shall stick to the GLEventListener model while utilizing - GLAutoDrawable implementations, she is safe due to the implicit locked state. - * </p> */ @SuppressWarnings("serial") @@ -153,11 +141,12 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing private static final boolean DEBUG = Debug.debug("GLCanvas"); + private final RecursiveLock lock = LockFactory.createRecursiveLock(); private final GLDrawableHelper helper = new GLDrawableHelper(); private AWTGraphicsConfiguration awtConfig; - private volatile GLDrawable drawable; // volatile avoids locking all accessors. FIXME still need to sync destroy/display + private volatile GLDrawable drawable; // volatile: avoid locking for read-only access private GLContextImpl context; - private boolean sendReshape = false; + private volatile boolean sendReshape = false; // volatile: maybe written by EDT w/o locking // copy of the cstr args, mainly for recreation private GLCapabilitiesImmutable capsReqUser; @@ -278,8 +267,9 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing @Override public final boolean isOffscreenLayerSurfaceEnabled() { - if(null != drawable) { - return ((JAWTWindow)drawable.getNativeSurface()).isOffscreenLayerSurfaceEnabled(); + final GLDrawable _drawable = drawable; + if(null != _drawable) { + return ((JAWTWindow)_drawable.getNativeSurface()).isOffscreenLayerSurfaceEnabled(); } return false; } @@ -398,12 +388,18 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing @Override public GLContext createContext(final GLContext shareWith) { - if(drawable != null) { - final GLContext _ctx = drawable.createContext(shareWith); - _ctx.setContextCreationFlags(additionalCtxCreationFlags); - return _ctx; + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if(drawable != null) { + final GLContext _ctx = drawable.createContext(shareWith); + _ctx.setContextCreationFlags(additionalCtxCreationFlags); + return _ctx; + } + return null; + } finally { + _lock.unlock(); } - return null; } @Override @@ -412,7 +408,8 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing @Override public boolean isRealized() { - return (null != drawable) ? drawable.isRealized() : false; + final GLDrawable _drawable = drawable; + return ( null != _drawable ) ? _drawable.isRealized() : false; } @Override @@ -427,50 +424,13 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing @Override public void display() { - if( !validateGLDrawable() ) { - if(DEBUG) { - System.err.println(getThreadName()+": Info: GLCanvas display - skipped GL render, drawable not valid yet"); - } - return; // not yet available .. - } Threading.invoke(true, displayOnEDTAction, getTreeLock()); - awtWindowClosingProtocol.addClosingListenerOneShot(); } private void dispose(boolean regenerate) { - final GLAnimatorControl animator = getAnimator(); - if(DEBUG) { - System.err.println(getThreadName()+": Info: dispose("+regenerate+") - START, hasContext " + - (null!=context) + ", hasDrawable " + (null!=drawable)+", "+animator); - Thread.dumpStack(); - } - - if(null!=drawable && null!=context) { // drawable is volatile! - boolean animatorPaused = false; - if(null!=animator) { - // can't remove us from animator for recreational addNotify() - animatorPaused = animator.pause(); - } - - disposeRegenerate=regenerate; - - if(context.isCreated()) { - Threading.invoke(true, disposeOnEDTAction, getTreeLock()); - } - - if(animatorPaused) { - animator.resume(); - } - } - - if(!regenerate) { - disposeAbstractGraphicsDevice(); - } - - if(DEBUG) { - System.err.println(getThreadName()+": dispose("+regenerate+") - END, "+animator); - } + disposeRegenerate=regenerate; + Threading.invoke(true, disposeOnEDTAction, getTreeLock()); } /** @@ -530,43 +490,49 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing @SuppressWarnings("deprecation") @Override public void addNotify() { - if(DEBUG) { - System.err.println(getThreadName()+": Info: addNotify - start, bounds: "+this.getBounds()); - Thread.dumpStack(); - } - - /** - * 'super.addNotify()' determines the GraphicsConfiguration, - * while calling this class's overriden 'getGraphicsConfiguration()' method - * after which it creates the native peer. - * Hence we have to set the 'awtConfig' before since it's GraphicsConfiguration - * is being used in getGraphicsConfiguration(). - * This code order also allows recreation, ie re-adding the GLCanvas. - */ - awtConfig = chooseGraphicsConfiguration(capsReqUser, capsReqUser, chooser, device); - if(null==awtConfig) { - throw new GLException("Error: NULL AWTGraphicsConfiguration"); - } - - // before native peer is valid: X11 - disableBackgroundErase(); - - // issues getGraphicsConfiguration() and creates the native peer - super.addNotify(); - - // after native peer is valid: Windows - disableBackgroundErase(); - - if (!Beans.isDesignTime()) { - createDrawableAndContext(); - } - - // init drawable by paint/display makes the init sequence more equal - // for all launch flavors (applet/javaws/..) - // validateGLDrawable(); - - if(DEBUG) { - System.err.println(getThreadName()+": Info: addNotify - end: peer: "+getPeer()); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if(DEBUG) { + System.err.println(getThreadName()+": Info: addNotify - start, bounds: "+this.getBounds()); + Thread.dumpStack(); + } + + /** + * 'super.addNotify()' determines the GraphicsConfiguration, + * while calling this class's overriden 'getGraphicsConfiguration()' method + * after which it creates the native peer. + * Hence we have to set the 'awtConfig' before since it's GraphicsConfiguration + * is being used in getGraphicsConfiguration(). + * This code order also allows recreation, ie re-adding the GLCanvas. + */ + awtConfig = chooseGraphicsConfiguration(capsReqUser, capsReqUser, chooser, device); + if(null==awtConfig) { + throw new GLException("Error: NULL AWTGraphicsConfiguration"); + } + + // before native peer is valid: X11 + disableBackgroundErase(); + + // issues getGraphicsConfiguration() and creates the native peer + super.addNotify(); + + // after native peer is valid: Windows + disableBackgroundErase(); + + if (!Beans.isDesignTime()) { + createDrawableAndContext(); + } + + // init drawable by paint/display makes the init sequence more equal + // for all launch flavors (applet/javaws/..) + // validateGLDrawable(); + + if(DEBUG) { + System.err.println(getThreadName()+": Info: addNotify - end: peer: "+getPeer()); + } + } finally { + _lock.unlock(); } } @@ -585,23 +551,24 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing } private boolean validateGLDrawable() { - boolean realized = false; - if (!Beans.isDesignTime()) { - if ( null != drawable ) { // OK: drawable is volatile - realized = drawable.isRealized(); - if ( !realized && 0 < drawable.getWidth() * drawable.getHeight() ) { - // make sure drawable realization happens on AWT EDT, due to AWTTree lock - AWTEDTExecutor.singleton.invoke(true, setRealizedOnEDTAction); - realized = true; - sendReshape=true; // ensure a reshape is being send .. - if(DEBUG) { - System.err.println(getThreadName()+": Realized Drawable: "+drawable.toString()); - Thread.dumpStack(); - } + final GLDrawable _drawable = drawable; + if ( null != _drawable ) { + if( _drawable.isRealized() ) { + return true; + } + if (!Beans.isDesignTime() && + 0 < _drawable.getWidth() * _drawable.getHeight() ) { + // make sure drawable realization happens on AWT EDT, due to AWTTree lock + AWTEDTExecutor.singleton.invoke(true, setRealizedOnEDTAction); + sendReshape=true; // ensure a reshape is being send .. + if(DEBUG) { + System.err.println(getThreadName()+": Realized Drawable: "+_drawable.toString()); + Thread.dumpStack(); } + return true; } } - return realized; + return false; } private Runnable setRealizedOnEDTAction = new Runnable() { @Override @@ -633,9 +600,6 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing try { dispose(false); } finally { - context=null; - drawable=null; - awtConfig=null; super.removeNotify(); } } @@ -655,7 +619,8 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing @Override public void reshape(int x, int y, int width, int height) { super.reshape(x, y, width, height); - if(null != drawable && drawable.isRealized() && !drawable.getChosenGLCapabilities().isOnscreen()) { + final GLDrawable _drawable = drawable; + if(null != _drawable && _drawable.isRealized() && !_drawable.getChosenGLCapabilities().isOnscreen()) { dispose(true); } else { sendReshape = true; @@ -710,13 +675,19 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing @Override public GLContext setContext(GLContext newCtx) { - final GLContext oldCtx = context; - final boolean newCtxCurrent = helper.switchContext(drawable, oldCtx, newCtx, additionalCtxCreationFlags); - context=(GLContextImpl)newCtx; - if(newCtxCurrent) { - context.makeCurrent(); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + final GLContext oldCtx = context; + final boolean newCtxCurrent = helper.switchContext(drawable, oldCtx, newCtx, additionalCtxCreationFlags); + context=(GLContextImpl)newCtx; + if(newCtxCurrent) { + context.makeCurrent(); + } + return oldCtx; + } finally { + _lock.unlock(); } - return oldCtx; } @Override @@ -729,15 +700,15 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing if (Beans.isDesignTime()) { return null; } - GLContext ctx = getContext(); - return (ctx == null) ? null : ctx.getGL(); + final GLContext _context = context; + return (_context == null) ? null : _context.getGL(); } @Override public GL setGL(GL gl) { - GLContext ctx = getContext(); - if (ctx != null) { - ctx.setGL(gl); + final GLContext _context = context; + if (_context != null) { + _context.setGL(gl); return gl; } return null; @@ -762,8 +733,9 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing @Override public void setContextCreationFlags(int flags) { additionalCtxCreationFlags = flags; - if(null != context) { - context.setContextCreationFlags(additionalCtxCreationFlags); + final GLContext _context = context; + if(null != _context) { + _context.setContextCreationFlags(additionalCtxCreationFlags); } } @@ -796,26 +768,30 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing @Override public NativeSurface getNativeSurface() { - return (null != drawable) ? drawable.getNativeSurface() : null; + final GLDrawable _drawable = drawable; + return (null != _drawable) ? _drawable.getNativeSurface() : null; } @Override public long getHandle() { - return (null != drawable) ? drawable.getHandle() : 0; + final GLDrawable _drawable = drawable; + return (null != _drawable) ? _drawable.getHandle() : 0; } @Override public GLDrawableFactory getFactory() { - return (null != drawable) ? drawable.getFactory() : null; + final GLDrawable _drawable = drawable; + return (null != _drawable) ? _drawable.getFactory() : null; } @Override public String toString() { - final int dw = (null!=drawable) ? drawable.getWidth() : -1; - final int dh = (null!=drawable) ? drawable.getHeight() : -1; + final GLDrawable _drawable = drawable; + final int dw = (null!=_drawable) ? _drawable.getWidth() : -1; + final int dh = (null!=_drawable) ? _drawable.getHeight() : -1; return "AWT-GLCanvas[Realized "+isRealized()+ - ",\n\t"+((null!=drawable)?drawable.getClass().getName():"null-drawable")+ + ",\n\t"+((null!=_drawable)?_drawable.getClass().getName():"null-drawable")+ ",\n\tFactory "+getFactory()+ ",\n\thandle 0x"+Long.toHexString(getHandle())+ ",\n\tDrawable size "+dw+"x"+dh+ @@ -829,7 +805,7 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing // private boolean disposeRegenerate; - private final Runnable postDisposeAction = new Runnable() { + private final Runnable postDisposeOnEDTAction = new Runnable() { @Override public void run() { context=null; @@ -859,7 +835,47 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing private final Runnable disposeOnEDTAction = new Runnable() { @Override public void run() { - helper.disposeGL(GLCanvas.this, drawable, context, postDisposeAction); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + final GLAnimatorControl animator = getAnimator(); + + if(DEBUG) { + System.err.println(getThreadName()+": Info: dispose("+disposeRegenerate+") - START, hasContext " + + (null!=context) + ", hasDrawable " + (null!=drawable)+", "+animator); + Thread.dumpStack(); + } + + if(null!=drawable && null!=context) { + boolean animatorPaused = false; + if(null!=animator) { + // can't remove us from animator for recreational addNotify() + animatorPaused = animator.pause(); + } + + if(context.isCreated()) { + helper.disposeGL(GLCanvas.this, drawable, context, postDisposeOnEDTAction); + } + + if(animatorPaused) { + animator.resume(); + } + } + + if(!disposeRegenerate) { + if(null != awtConfig) { + disposeAbstractGraphicsDevice(); + } + awtConfig=null; + } + + if(DEBUG) { + System.err.println(getThreadName()+": dispose("+disposeRegenerate+") - END, "+animator); + } + + } finally { + _lock.unlock(); + } } }; @@ -879,7 +895,6 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing if(DEBUG) { System.err.println(getThreadName()+": GLCanvas.dispose(false): closed GraphicsDevice: "+adeviceMsg+", result: "+closed); } - awtConfig=null; } } }; @@ -890,7 +905,7 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing * * @see #chooseGraphicsConfiguration(javax.media.opengl.GLCapabilitiesImmutable, javax.media.opengl.GLCapabilitiesImmutable, javax.media.opengl.GLCapabilitiesChooser, java.awt.GraphicsDevice) */ - void disposeAbstractGraphicsDevice() { + private void disposeAbstractGraphicsDevice() { if( EventQueue.isDispatchThread() || Thread.holdsLock(getTreeLock()) ) { disposeAbstractGraphicsDeviceAction.run(); } else { @@ -941,14 +956,30 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing private final Runnable displayOnEDTAction = new Runnable() { @Override public void run() { - helper.invokeGL(drawable, context, displayAction, initAction); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if( validateGLDrawable() ) { + helper.invokeGL(drawable, context, displayAction, initAction); + } else if(DEBUG) { + System.err.println(getThreadName()+": Info: GLCanvas display - skipped GL render, drawable not valid yet"); + } + } finally { + _lock.unlock(); + } } }; private final Runnable swapBuffersOnEDTAction = new Runnable() { @Override public void run() { - helper.invokeGL(drawable, context, swapBuffersAction, initAction); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + helper.invokeGL(drawable, context, swapBuffersAction, initAction); + } finally { + _lock.unlock(); + } } }; diff --git a/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java index 5c6d7446a..fe6d0fd76 100644 --- a/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java +++ b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java @@ -46,6 +46,7 @@ import javax.media.opengl.GLException; import javax.media.opengl.GLProfile; import javax.media.opengl.GLRunnable; +import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.opengl.util.Animator; @@ -63,11 +64,11 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { protected final GLDrawableHelper helper = new GLDrawableHelper(); protected final FPSCounterImpl fpsCounter = new FPSCounterImpl(); - protected GLDrawableImpl drawable; + protected volatile GLDrawableImpl drawable; // volatile: avoid locking for read-only access protected GLContextImpl context; protected int additionalCtxCreationFlags = 0; - protected boolean sendReshape = false; - protected boolean sendDestroy = false; + protected volatile boolean sendReshape = false; // volatile: maybe written by WindowManager thread w/o locking + protected volatile boolean sendDestroy = false; // volatile: maybe written by WindowManager thread w/o locking public GLAutoDrawableBase(GLDrawableImpl drawable, GLContextImpl context) { this.drawable = drawable; @@ -75,31 +76,47 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { resetFPSCounter(); } + protected abstract RecursiveLock getLock(); + /** Returns the delegated GLDrawable */ public final GLDrawable getDelegatedDrawable() { return drawable; } /** Default implementation to handle repaint events from the windowing system */ - protected void defaultWindowRepaintOp() { - if( null != drawable && drawable.isRealized() ) { - if( !drawable.getNativeSurface().isSurfaceLockedByOtherThread() && !helper.isAnimatorAnimating() ) { + protected final void defaultWindowRepaintOp() { + final GLDrawable _drawable = drawable; + if( null != _drawable && _drawable.isRealized() ) { + if( !_drawable.getNativeSurface().isSurfaceLockedByOtherThread() && !helper.isAnimatorAnimating() ) { display(); } - } + } } /** Default implementation to handle resize events from the windowing system */ - protected void defaultWindowResizedOp() { - if( null!=drawable ) { + protected final void defaultWindowResizedOp() { + final GLDrawable _drawable = drawable; + if( null!=_drawable ) { if(DEBUG) { System.err.println("GLAutoDrawableBase.sizeChanged: ("+Thread.currentThread().getName()+"): "+getWidth()+"x"+getHeight()+" - surfaceHandle 0x"+Long.toHexString(getNativeSurface().getSurfaceHandle())); } - sendReshape = true; - defaultWindowRepaintOp(); + sendReshape = true; // async if display() doesn't get called below, but avoiding deadlock + if( _drawable.isRealized() ) { + if( !_drawable.getNativeSurface().isSurfaceLockedByOtherThread() && !helper.isAnimatorAnimating() ) { + display(); + } + } } } - /** Default implementation to handle destroy notifications from the windowing system */ - protected void defaultWindowDestroyNotifyOp() { + /** + * Default implementation to handle destroy notifications from the windowing system. + * + * <p> + * If the {@link NativeSurface} does not implement {@link WindowClosingProtocol} + * or {@link WindowClosingMode#DISPOSE_ON_CLOSE} is enabled (default), + * {@link #defaultDestroy()} is being called. + * </p> + */ + protected final void defaultWindowDestroyNotifyOp() { final NativeSurface ns = getNativeSurface(); final boolean shallClose; if(ns instanceof WindowClosingProtocol) { @@ -108,27 +125,66 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { shallClose = true; } if( shallClose ) { - // Is an animator thread perform rendering? - if (helper.isExternalAnimatorRunning()) { - // Pause animations before initiating safe destroy. - final GLAnimatorControl ctrl = helper.getAnimator(); - final boolean isPaused = ctrl.pause(); - destroy(); - if(isPaused) { - ctrl.resume(); - } - } else if (null != ns && ns.isSurfaceLockedByOtherThread()) { - // surface is locked by another thread - // Flag that destroy should be performed on the next - // attempt to display. - sendDestroy = true; - } else { - // Without an external thread animating or locking the - // surface, we are safe. - destroy (); - } + destroyAvoidAwareOfLocking(); } } + + /** + * Calls {@link #destroy()} + * directly if the following requirements are met: + * <ul> + * <li>An {@link GLAnimatorControl} is bound (see {@link #getAnimator()}) and running on another thread. + * Here we pause the animation while issuing the destruction.</li> + * <li>Surface is not locked by another thread (considered anonymous).</li> + * </ul> + * <p> + * Otherwise destroy is being flagged to be called within the next + * call of display(). + * </p> + * <p> + * This method is being used to avoid deadlock if + * destruction is desired by <i>other</i> threads, e.g. the window manager. + * </p> + * @see #defaultWindowDestroyNotifyOp() + * @see #defaultDisplay() + */ + protected final void destroyAvoidAwareOfLocking() { + final NativeSurface ns = getNativeSurface(); + + final GLAnimatorControl ctrl = helper.getAnimator(); + + // Is an animator thread perform rendering? + if ( helper.isAnimatorRunningOnOtherThread() ) { + // Pause animations before initiating safe destroy. + final boolean isPaused = ctrl.pause(); + destroy(); + if(isPaused) { + ctrl.resume(); + } + } else if (null != ns && ns.isSurfaceLockedByOtherThread()) { + // surface is locked by another thread + // Flag that destroy should be performed on the next + // attempt to display. + sendDestroy = true; // async, but avoiding deadlock + } else { + // Without an external thread animating or locking the + // surface, we are safe. + destroy(); + } + } + + /** + * Calls {@link #destroyImplInLock()} while claiming the lock. + */ + protected final void defaultDestroy() { + final RecursiveLock lock = getLock(); + lock.lock(); + try { + destroyImplInLock(); + } finally { + lock.unlock(); + } + } /** * Default implementation to destroys the drawable and context of this GLAutoDrawable: @@ -137,24 +193,42 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { * <li>destroys the GLContext, if valid</li> * <li>destroys the GLDrawable, if valid</li> * </ul> + * <p>Method assumes the lock is being hold.</p> + * <p>Override it to extend it to destroy your resources, i.e. the actual window. + * In such case call <code>super.destroyImplInLock</code> first.</p> */ - protected void defaultDestroyOp() { - if( null != drawable && drawable.isRealized() ) { - if( null != context && context.isCreated() ) { + protected void destroyImplInLock() { + final GLContext _context = context; + final GLDrawable _drawable = drawable; + if( null != _drawable && _drawable.isRealized() ) { + if( null != _context && _context.isCreated() ) { // Catch dispose GLExceptions by GLEventListener, just 'print' them // so we can continue with the destruction. try { - helper.disposeGL(this, drawable, context, null); + helper.disposeGL(this, _drawable, _context, null); } catch (GLException gle) { gle.printStackTrace(); } } - drawable.setRealized(false); + _drawable.setRealized(false); } context = null; drawable = null; } + public final void defaultSwapBuffers() throws GLException { + final RecursiveLock _lock = getLock(); + _lock.lock(); + try { + if(drawable!=null && context != null) { + drawable.swapBuffers(); + helper.invokeGL(drawable, context, defaultSwapAction, defaultInitAction); + } + } finally { + _lock.unlock(); + } + } + // // GLAutoDrawable // @@ -179,6 +253,30 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { fpsCounter.tickFPS(); } }; + protected final void defaultDisplay() { + if( sendDestroy ) { + sendDestroy=false; + destroy(); + return; + } + final RecursiveLock _lock = getLock(); + _lock.lock(); + try { + if( null != context ) { + // surface is locked/unlocked implicit by context's makeCurrent/release + helper.invokeGL(drawable, context, defaultDisplayAction, defaultInitAction); + } + } finally { + _lock.unlock(); + } + } + + protected final Runnable defaultSwapAction = new Runnable() { + @Override + public final void run() { + drawable.swapBuffers(); + } } ; + @Override public final GLContext getContext() { return context; @@ -186,27 +284,35 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { @Override public final GLContext setContext(GLContext newCtx) { - final GLContext oldCtx = context; - final boolean newCtxCurrent = helper.switchContext(drawable, oldCtx, newCtx, additionalCtxCreationFlags); - context=(GLContextImpl)newCtx; - if(newCtxCurrent) { - context.makeCurrent(); + final RecursiveLock lock = getLock(); + lock.lock(); + try { + final GLContext oldCtx = context; + final boolean newCtxCurrent = helper.switchContext(drawable, oldCtx, newCtx, additionalCtxCreationFlags); + context=(GLContextImpl)newCtx; + if(newCtxCurrent) { + context.makeCurrent(); + } + return oldCtx; + } finally { + lock.unlock(); } - return oldCtx; } @Override public final GL getGL() { - if (context == null) { + final GLContext _context = context; + if (_context == null) { return null; } - return context.getGL(); + return _context.getGL(); } @Override public final GL setGL(GL gl) { - if (context != null) { - context.setGL(gl); + final GLContext _context = context; + if (_context != null) { + _context.setGL(gl); return gl; } return null; @@ -261,8 +367,9 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { @Override public final void setContextCreationFlags(int flags) { additionalCtxCreationFlags = flags; - if(null != context) { - context.setContextCreationFlags(additionalCtxCreationFlags); + final GLContext _context = context; + if(null != _context) { + _context.setContextCreationFlags(additionalCtxCreationFlags); } } @@ -331,27 +438,36 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { @Override public final GLContext createContext(final GLContext shareWith) { - if(drawable != null) { - final GLContext _ctx = drawable.createContext(shareWith); - _ctx.setContextCreationFlags(additionalCtxCreationFlags); - return _ctx; + final RecursiveLock lock = getLock(); + lock.lock(); + try { + if(drawable != null) { + final GLContext _ctx = drawable.createContext(shareWith); + _ctx.setContextCreationFlags(additionalCtxCreationFlags); + return _ctx; + } + return null; + } finally { + lock.unlock(); } - return null; } @Override public final boolean isRealized() { - return null != drawable ? drawable.isRealized() : false; + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.isRealized() : false; } @Override public int getWidth() { - return null != drawable ? drawable.getWidth() : 0; + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getWidth() : 0; } @Override public int getHeight() { - return null != drawable ? drawable.getHeight() : 0; + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getHeight() : 0; } /** @@ -374,29 +490,26 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { } @Override - public final void swapBuffers() throws GLException { - if(drawable!=null && context != null) { - drawable.swapBuffers(); - } - } - - @Override public final GLCapabilitiesImmutable getChosenGLCapabilities() { - return null != drawable ? drawable.getChosenGLCapabilities() : null; + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getChosenGLCapabilities() : null; } @Override public final GLProfile getGLProfile() { - return null != drawable ? drawable.getGLProfile() : null; + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getGLProfile() : null; } @Override public final NativeSurface getNativeSurface() { - return null != drawable ? drawable.getNativeSurface() : null; + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getNativeSurface() : null; } @Override public final long getHandle() { - return null != drawable ? drawable.getHandle() : 0; + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getHandle() : 0; } } diff --git a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java index 0c01aa676..090c5fe69 100644 --- a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java +++ b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java @@ -189,8 +189,9 @@ public class GLDrawableHelper { */ public final void dispose(GLAutoDrawable drawable) { synchronized(listenersLock) { - for (int i=0; i < listeners.size(); i++) { - listeners.get(i).dispose(drawable); + final ArrayList<GLEventListener> _listeners = listeners; + for (int i=0; i < _listeners.size(); i++) { + _listeners.get(i).dispose(drawable); } } } @@ -209,8 +210,9 @@ public class GLDrawableHelper { /** The default init action to be called once after ctx is being created @ 1st makeCurrent(). */ public final void init(GLAutoDrawable drawable) { synchronized(listenersLock) { - for (int i=0; i < listeners.size(); i++) { - final GLEventListener listener = listeners.get(i) ; + final ArrayList<GLEventListener> _listeners = listeners; + for (int i=0; i < _listeners.size(); i++) { + final GLEventListener listener = _listeners.get(i) ; // If make current ctx, 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), @@ -232,8 +234,9 @@ public class GLDrawableHelper { } private final void displayImpl(GLAutoDrawable drawable) { synchronized(listenersLock) { - for (int i=0; i < listeners.size(); i++) { - final GLEventListener listener = listeners.get(i) ; + final ArrayList<GLEventListener> _listeners = listeners; + for (int i=0; i < _listeners.size(); 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 init( listener, drawable, true /* sendReshape */) ; @@ -324,7 +327,7 @@ public class GLDrawableHelper { } } - public final boolean isExternalAnimatorRunning() { + public final boolean isAnimatorRunningOnOtherThread() { return ( null != animatorCtrl ) ? animatorCtrl.isStarted() && animatorCtrl.getThread() != Thread.currentThread() : false ; } diff --git a/src/jogl/classes/jogamp/opengl/GLPbufferImpl.java b/src/jogl/classes/jogamp/opengl/GLPbufferImpl.java index bbc28e283..6b64120fe 100644 --- a/src/jogl/classes/jogamp/opengl/GLPbufferImpl.java +++ b/src/jogl/classes/jogamp/opengl/GLPbufferImpl.java @@ -42,10 +42,14 @@ package jogamp.opengl; import javax.media.opengl.GLCapabilitiesImmutable; import javax.media.opengl.GLContext; +import javax.media.opengl.GLDrawable; import javax.media.opengl.GLDrawableFactory; import javax.media.opengl.GLException; import javax.media.opengl.GLPbuffer; +import com.jogamp.common.util.locks.LockFactory; +import com.jogamp.common.util.locks.RecursiveLock; + /** Platform-independent class exposing pbuffer functionality to applications. This class is not exposed in the public API as it would probably add no value; however it implements the GLDrawable @@ -101,7 +105,7 @@ public class GLPbufferImpl extends GLAutoDrawableBase implements GLPbuffer { // // GLDrawable delegation // - + @Override public final void setRealized(boolean realized) { } @@ -109,6 +113,10 @@ public class GLPbufferImpl extends GLAutoDrawableBase implements GLPbuffer { // // GLAutoDrawable completion // + private final RecursiveLock lock = LockFactory.createRecursiveLock(); // instance wide lock + + @Override + protected final RecursiveLock getLock() { return lock; } @Override public final Object getUpstreamWidget() { @@ -117,7 +125,7 @@ public class GLPbufferImpl extends GLAutoDrawableBase implements GLPbuffer { @Override public void destroy() { - defaultDestroyOp(); + defaultDestroy(); } @Override @@ -126,12 +134,23 @@ public class GLPbufferImpl extends GLAutoDrawableBase implements GLPbuffer { } @Override - public void display() { - if( null != drawable && drawable.isRealized() && null != context ) { - helper.invokeGL(drawable, context, defaultDisplayAction, initAction); + public final void display() { + final RecursiveLock _lock = lock; + _lock.lock(); // sync: context/drawable could been recreated/destroyed while animating + try { + if( null != context ) { + helper.invokeGL(drawable, context, defaultDisplayAction, initAction); + } + } finally { + _lock.unlock(); } } + @Override + public final void swapBuffers() throws GLException { + defaultSwapBuffers(); + } + //---------------------------------------------------------------------- // Internals only below this point // |