aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/com/jogamp/opengl/util
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2014-07-27 03:49:21 +0200
committerSven Gothel <[email protected]>2014-07-27 03:49:21 +0200
commitc77b8f586cb2553582a42f5b90aeee5ef85f1efe (patch)
tree2f304461ff3d87b75f347dd5cf36a580aa73c854 /src/jogl/classes/com/jogamp/opengl/util
parent37760af388303834e359703aad9562ce6165845f (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.java372
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();
- }
}