diff options
author | Sven Gothel <[email protected]> | 2014-07-27 03:49:21 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-07-27 03:49:21 +0200 |
commit | c77b8f586cb2553582a42f5b90aeee5ef85f1efe (patch) | |
tree | 2f304461ff3d87b75f347dd5cf36a580aa73c854 /src/jogl/classes/com/jogamp/opengl/util | |
parent | 37760af388303834e359703aad9562ce6165845f (diff) |
Bug 1033: Guarantee atomicity of high-level GLAutoDrawable operations, avoiding race conditions.
GLAutoDrawable (API CHANGE) allowing atomic operations:
- Add class API-doc chapter about 'GLAutoDrawable Locking'
- Add method invoke(..) API-doc description about throwing IllegalStateException in case of a detected deadlock situation ahead
(Note: Implemented in GLDrawableHelper.invoke(..) for all implementations)
- Add new methods for proper multithread handling:
- public RecursiveLock getUpstreamLock();
- public boolean isThreadGLCapable();
+++
GLEventListenerState/GLDrawableUtil:
- Perform operation in a atomic fashion,
i.e. lock GLAutoDrawable during whole operations:
- GLDrawableUtil.swapGLContext(..)
- GLDrawableUtil.swapGLContextAndAllGLEventListener(..)
- GLEventListenerState.moveFrom(..)
- GLEventListenerState.moveTo(..)
- ReshapeGLEventListener:
- Moved from GLEventListenerState.ReshapeGLEventListener -> GLDrawableUtil.ReshapeGLEventListener
- Takes 'displayAfterReshape' case into account.
+++
javax.media.opengl.Threading Clarifications:
- Public 'enum Mode', i.e. Threading.Mode
- Public getMode()
- Clarified 'isOpenGLThread()':
- Take 'singleThreaded' into account directly,
i.e. always return 'true' if singleThreaded == false
Diffstat (limited to 'src/jogl/classes/com/jogamp/opengl/util')
-rw-r--r-- | src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java | 372 |
1 files changed, 231 insertions, 141 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java b/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java index c74284299..a2978d6d5 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java +++ b/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java @@ -28,6 +28,7 @@ package com.jogamp.opengl.util; import javax.media.nativewindow.AbstractGraphicsDevice; +import javax.media.nativewindow.NativeSurface; import javax.media.opengl.GLAnimatorControl; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLBase; @@ -36,7 +37,9 @@ import javax.media.opengl.GLContext; import javax.media.opengl.GLDrawable; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLException; +import javax.media.opengl.GLRunnable; +import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.opengl.GLEventListenerState; import jogamp.opengl.Debug; @@ -45,148 +48,235 @@ import jogamp.opengl.Debug; * Providing utility functions dealing w/ {@link GLDrawable}s, {@link GLAutoDrawable} and their {@link GLEventListener}. */ public class GLDrawableUtil { - protected static final boolean DEBUG = Debug.debug("GLDrawable"); - - public static final boolean isAnimatorStartedOnOtherThread(final GLAnimatorControl animatorCtrl) { - return ( null != animatorCtrl ) ? animatorCtrl.isStarted() && animatorCtrl.getThread() != Thread.currentThread() : false ; - } - - public static final boolean isAnimatorStarted(final GLAnimatorControl animatorCtrl) { - return ( null != animatorCtrl ) ? animatorCtrl.isStarted() : false ; - } - - public static final boolean isAnimatorAnimatingOnOtherThread(final GLAnimatorControl animatorCtrl) { - return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() && animatorCtrl.getThread() != Thread.currentThread() : false ; - } - - public static final boolean isAnimatorAnimating(final GLAnimatorControl animatorCtrl) { - return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() : false ; - } - - /** - * Moves the designated {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>. - * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved - * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call. - * <p> - * Note that it is only legal to pass <code>preserveInitState := true</code>, - * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>. - * </p> - * <p> - * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}. - * </p> - * @param src - * @param dest - * @param listener - * @param preserveInitState - */ - public static final void moveGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final GLEventListener listener, final boolean preserveInitState) { - final boolean initialized = src.getGLEventListenerInitState(listener); - src.removeGLEventListener(listener); - dest.addGLEventListener(listener); - if(preserveInitState && initialized) { - dest.setGLEventListenerInitState(listener, true); - dest.invoke(false, new GLEventListenerState.ReshapeGLEventListener(listener)); - } // else .. !init state is default - } - - /** - * Moves all {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>. - * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved - * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call. - * <p> - * Note that it is only legal to pass <code>preserveInitState := true</code>, - * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>. - * </p> - * <p> - * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}. - * </p> - * @param src - * @param dest - * @param listener - * @param preserveInitState - */ - public static final void moveAllGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final boolean preserveInitState) { - for(int count = src.getGLEventListenerCount(); 0<count; count--) { - final GLEventListener listener = src.getGLEventListener(0); - moveGLEventListener(src, dest, listener, preserveInitState); + protected static final boolean DEBUG = Debug.debug("GLDrawable"); + + public static final boolean isAnimatorStartedOnOtherThread(final GLAnimatorControl animatorCtrl) { + return ( null != animatorCtrl ) ? animatorCtrl.isStarted() && animatorCtrl.getThread() != Thread.currentThread() : false ; + } + + public static final boolean isAnimatorStarted(final GLAnimatorControl animatorCtrl) { + return ( null != animatorCtrl ) ? animatorCtrl.isStarted() : false ; + } + + public static final boolean isAnimatorAnimatingOnOtherThread(final GLAnimatorControl animatorCtrl) { + return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() && animatorCtrl.getThread() != Thread.currentThread() : false ; + } + + public static final boolean isAnimatorAnimating(final GLAnimatorControl animatorCtrl) { + return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() : false ; + } + + /** + * {@link GLRunnable} to issue {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int)}, + * returning <code>true</code> on {@link GLRunnable#run(GLAutoDrawable)}. + */ + public static class ReshapeGLEventListener implements GLRunnable { + private final GLEventListener listener; + private final boolean displayAfterReshape; + /** + * + * @param listener + * @param displayAfterReshape <code>true</code> to issue {@link GLEventListener#display(GLAutoDrawable)} + * after {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int)}, + * otherwise false. + */ + public ReshapeGLEventListener(final GLEventListener listener, final boolean displayAfterReshape) { + this.listener = listener; + this.displayAfterReshape = displayAfterReshape; + } + @Override + public boolean run(final GLAutoDrawable drawable) { + listener.reshape(drawable, 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); + if( displayAfterReshape ) { + listener.display(drawable); + } + return true; + } + } + + /** + * Moves the designated {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>. + * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved + * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call. + * <p> + * Note that it is only legal to pass <code>preserveInitState := true</code>, + * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>. + * </p> + * <p> + * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}. + * </p> + * @param src + * @param dest + * @param listener + * @param preserveInitState + */ + public static final void moveGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final GLEventListener listener, final boolean preserveInitState) { + final boolean initialized = src.getGLEventListenerInitState(listener); + if( preserveInitState ) { + src.removeGLEventListener(listener); + dest.addGLEventListener(listener); + if( initialized ) { + dest.setGLEventListenerInitState(listener, true); + dest.invoke(false, new ReshapeGLEventListener(listener, true)); + } + } else { + src.disposeGLEventListener(listener, true); + dest.addGLEventListener(listener); + } + } + + /** + * Moves all {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>. + * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved + * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call. + * <p> + * Note that it is only legal to pass <code>preserveInitState := true</code>, + * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>. + * </p> + * <p> + * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}. + * </p> + * @param src + * @param dest + * @param listener + * @param preserveInitState + */ + public static final void moveAllGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final boolean preserveInitState) { + for(int count = src.getGLEventListenerCount(); 0<count; count--) { + final GLEventListener listener = src.getGLEventListener(0); + moveGLEventListener(src, dest, listener, preserveInitState); + } + } + + /** + * Swaps the {@link GLContext} and all {@link GLEventListener} between {@link GLAutoDrawable} <code>a</code> and <code>b</code>, + * while preserving it's initialized state, resets the GL-Viewport and issuing {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)}. + * <p> + * The {@link GLAutoDrawable} to {@link GLAnimatorControl} association + * is also swapped. + * </p> + * <p> + * If an {@link GLAnimatorControl} is being attached to {@link GLAutoDrawable} <code>a</code> or <code>b</code> + * and the current thread is different than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation. + * </p> + * <p> + * During operation, both {@link GLAutoDrawable auto-drawable's} + * {@link GLAutoDrawable#getUpstreamLock() upstream-locks} and {@link GLAutoDrawable#getNativeSurface() surfaces} are locked, + * hence atomicity of operation is guaranteed, + * see <a href="../../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>. + * </p> + * @param a + * @param b + * @throws GLException if the {@link AbstractGraphicsDevice} are incompatible w/ each other. + */ + public static final void swapGLContextAndAllGLEventListener(final GLAutoDrawable a, final GLAutoDrawable b) { + final GLEventListenerState gllsA = GLEventListenerState.moveFrom(a, true); + final GLEventListenerState gllsB = GLEventListenerState.moveFrom(b, true); + final Runnable gllsAUnlockOp = gllsA.getUnlockSurfaceOp(); + final Runnable gllsBUnlockOp = gllsB.getUnlockSurfaceOp(); + try { + gllsA.moveTo(b, gllsBUnlockOp); + gllsB.moveTo(a, gllsAUnlockOp); + } finally { + // guarantee unlock in case of an exception + gllsBUnlockOp.run(); + gllsAUnlockOp.run(); + } } - } - - /** - * Swaps the {@link GLContext} and all {@link GLEventListener} between {@link GLAutoDrawable} <code>a</code> and <code>b</code>, - * while preserving it's initialized state, resets the GL-Viewport and issuing {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)}. - * <p> - * The {@link GLAutoDrawable} to {@link GLAnimatorControl} association - * is also swapped. - * </p> - * <p> - * If an {@link GLAnimatorControl} is being attached to {@link GLAutoDrawable} <code>a</code> or <code>b</code> - * and the current thread is different than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation. - * </p> - * @param a - * @param b - * @throws GLException if the {@link AbstractGraphicsDevice} are incompatible w/ each other. - */ - public static final void swapGLContextAndAllGLEventListener(final GLAutoDrawable a, final GLAutoDrawable b) { - final GLEventListenerState gllsA = GLEventListenerState.moveFrom(a); - final GLEventListenerState gllsB = GLEventListenerState.moveFrom(b); - - gllsA.moveTo(b); - gllsB.moveTo(a); - } - - /** - * Swaps the {@link GLContext} of given {@link GLAutoDrawable} - * and {@link GLAutoDrawable#disposeGLEventListener(GLEventListener, boolean) disposes} - * each {@link GLEventListener} w/o removing it. - * <p> - * The GL-Viewport is reset and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued implicit. - * </p> - * <p> - * If an {@link GLAnimatorControl} is being attached to GLAutoDrawable src or dest and the current thread is different - * than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation. - * </p> - * @param src - * @param dest - */ - public static final void swapGLContext(final GLAutoDrawable src, final GLAutoDrawable dest) { - final GLAnimatorControl aAnim = src.getAnimator(); - final GLAnimatorControl bAnim = dest.getAnimator(); - final boolean aIsPaused = isAnimatorAnimatingOnOtherThread(aAnim) && aAnim.pause(); - final boolean bIsPaused = isAnimatorAnimatingOnOtherThread(bAnim) && bAnim.pause(); - - for(int i = src.getGLEventListenerCount() - 1; 0 <= i; i--) { - src.disposeGLEventListener(src.getGLEventListener(i), false); + + /** + * Swaps the {@link GLContext} of given {@link GLAutoDrawable} + * and {@link GLAutoDrawable#disposeGLEventListener(GLEventListener, boolean) disposes} + * each {@link GLEventListener} w/o removing it. + * <p> + * The GL-Viewport is reset and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued implicit. + * </p> + * <p> + * If an {@link GLAnimatorControl} is being attached to GLAutoDrawable src or dest and the current thread is different + * than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation. + * </p> + * <p> + * During operation, both {@link GLAutoDrawable auto-drawable's} + * {@link GLAutoDrawable#getUpstreamLock() upstream-locks} and {@link GLAutoDrawable#getNativeSurface() surfaces} are locked, + * hence atomicity of operation is guaranteed, + * see <a href="../../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>. + * </p> + * @param a + * @param b + */ + public static final void swapGLContext(final GLAutoDrawable a, final GLAutoDrawable b) { + final GLAnimatorControl aAnim = a.getAnimator(); + final GLAnimatorControl bAnim = b.getAnimator(); + final boolean aIsPaused = isAnimatorAnimatingOnOtherThread(aAnim) && aAnim.pause(); + final boolean bIsPaused = isAnimatorAnimatingOnOtherThread(bAnim) && bAnim.pause(); + + final RecursiveLock aUpstreamLock = a.getUpstreamLock(); + final RecursiveLock bUpstreamLock = b.getUpstreamLock(); + aUpstreamLock.lock(); + bUpstreamLock.lock(); + try { + final NativeSurface aSurface = a.getNativeSurface(); + final boolean aSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < aSurface.lockSurface(); + if( a.isRealized() && !aSurfaceLocked ) { + throw new GLException("Could not lock realized a surface "+a); + } + final NativeSurface bSurface = b.getNativeSurface(); + final boolean bSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < bSurface.lockSurface(); + if( b.isRealized() && !bSurfaceLocked ) { + throw new GLException("Could not lock realized b surface "+b); + } + try { + for(int i = a.getGLEventListenerCount() - 1; 0 <= i; i--) { + a.disposeGLEventListener(a.getGLEventListener(i), false); + } + for(int i = b.getGLEventListenerCount() - 1; 0 <= i; i--) { + b.disposeGLEventListener(b.getGLEventListener(i), false); + } + b.setContext( a.setContext( b.getContext(), false ), false ); + + } finally { + if( bSurfaceLocked ) { + bSurface.unlockSurface(); + } + if( aSurfaceLocked ) { + aSurface.unlockSurface(); + } + } + } finally { + bUpstreamLock.unlock(); + aUpstreamLock.unlock(); + } + a.invoke(true, setViewport); + b.invoke(true, setViewport); + if(aIsPaused) { aAnim.resume(); } + if(bIsPaused) { bAnim.resume(); } } - for(int i = dest.getGLEventListenerCount() - 1; 0 <= i; i--) { - dest.disposeGLEventListener(dest.getGLEventListener(i), false); + + private static final GLRunnable setViewport = new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + drawable.getGL().glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); + return false; // issue re-display w/ new viewport! + } + }; + + /** + * Determines whether the chosen {@link GLCapabilitiesImmutable} + * requires a {@link GLDrawable#swapBuffers() swap-buffers} + * before reading pixels. + * <p> + * Usually one uses the {@link GLBase#getDefaultReadBuffer() default-read-buffer} + * in which case {@link GLDrawable#swapBuffers() swap-buffers} shall happen <b>after</b> calling reading pixels, the default. + * </p> + * <p> + * However, <i>multisampling</i> offscreen {@link javax.media.opengl.GLFBODrawable}s + * utilize {@link GLDrawable#swapBuffers() swap-buffers} to <i>downsample</i> + * the multisamples into the readable sampling sink. + * In this case, we require {@link GLDrawable#swapBuffers() swap-buffers} <b>before</b> reading pixels. + * </p> + * @return chosenCaps.isFBO() && chosenCaps.getSampleBuffers() + */ + public static final boolean swapBuffersBeforeRead(final GLCapabilitiesImmutable chosenCaps) { + return chosenCaps.isFBO() && chosenCaps.getSampleBuffers(); } - dest.setContext( src.setContext( dest.getContext(), false ), false ); - - src.invoke(true, GLEventListenerState.setViewport); - dest.invoke(true, GLEventListenerState.setViewport); - - if(aIsPaused) { aAnim.resume(); } - if(bIsPaused) { bAnim.resume(); } - } - - /** - * Determines whether the chosen {@link GLCapabilitiesImmutable} - * requires a {@link GLDrawable#swapBuffers() swap-buffers} - * before reading pixels. - * <p> - * Usually one uses the {@link GLBase#getDefaultReadBuffer() default-read-buffer} - * in which case {@link GLDrawable#swapBuffers() swap-buffers} shall happen <b>after</b> calling reading pixels, the default. - * </p> - * <p> - * However, <i>multisampling</i> offscreen {@link javax.media.opengl.GLFBODrawable}s - * utilize {@link GLDrawable#swapBuffers() swap-buffers} to <i>downsample</i> - * the multisamples into the readable sampling sink. - * In this case, we require {@link GLDrawable#swapBuffers() swap-buffers} <b>before</b> reading pixels. - * </p> - * @return chosenCaps.isFBO() && chosenCaps.getSampleBuffers() - */ - public static final boolean swapBuffersBeforeRead(final GLCapabilitiesImmutable chosenCaps) { - return chosenCaps.isFBO() && chosenCaps.getSampleBuffers(); - } } |