diff options
Diffstat (limited to 'src/jogl')
11 files changed, 1093 insertions, 414 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java index 4ba4def9a..80e1aa80d 100644 --- a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java +++ b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java @@ -649,6 +649,16 @@ public class GLCanvas extends Canvas implements GLAutoDrawable { } @Override + public final Thread setExclusiveContextThread(Thread t) throws GLException { + return helper.setExclusiveContextThread(t, context); + } + + @Override + public final Thread getExclusiveContextThread() { + return helper.getExclusiveContextThread(); + } + + @Override public boolean getAutoSwapBufferMode() { return helper.getAutoSwapBufferMode(); } diff --git a/src/jogl/classes/com/jogamp/opengl/util/Animator.java b/src/jogl/classes/com/jogamp/opengl/util/Animator.java index ddbc66fa3..10f43a0c1 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/Animator.java +++ b/src/jogl/classes/com/jogamp/opengl/util/Animator.java @@ -41,7 +41,7 @@ package com.jogamp.opengl.util; import javax.media.opengl.GLAutoDrawable; - +import javax.media.opengl.GLException; /** <P> An Animator can be attached to one or more {@link GLAutoDrawable}s to drive their display() methods in a loop. </P> @@ -57,10 +57,7 @@ import javax.media.opengl.GLAutoDrawable; * Call {@link #stop() } to terminate the animation and it's execution thread. * </p> */ -public class Animator extends AnimatorBase { - /** timeout in milliseconds, 15 frames @ 60Hz = 240ms, limiting {@link #finishLifecycleAction(Condition)} */ - private static final long TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION = 15*16; - +public class Animator extends AnimatorBase { protected ThreadGroup threadGroup; private Runnable runnable; private boolean runAsFastAsPossible; @@ -68,6 +65,9 @@ public class Animator extends AnimatorBase { protected boolean pauseIssued; protected volatile boolean stopIssued; + /** + * Creates a new, empty Animator. + */ public Animator() { super(); if(DEBUG) { @@ -75,25 +75,38 @@ public class Animator extends AnimatorBase { } } + /** + * Creates a new Animator w/ an associated ThreadGroup. + */ public Animator(ThreadGroup tg) { super(); - threadGroup = tg; - + setThreadGroup(tg); if(DEBUG) { System.err.println("Animator created, ThreadGroup: "+threadGroup); } } - /** Creates a new Animator for a particular drawable. */ + /** + * Creates a new Animator for a particular drawable. + */ public Animator(GLAutoDrawable drawable) { super(); add(drawable); + if(DEBUG) { + System.err.println("Animator created, w/ "+drawable); + } } - /** Creates a new Animator for a particular drawable. */ + /** + * Creates a new Animator w/ an associated ThreadGroup for a particular drawable. + */ public Animator(ThreadGroup tg, GLAutoDrawable drawable) { - this(tg); + super(); + setThreadGroup(tg); add(drawable); + if(DEBUG) { + System.err.println("Animator created, ThreadGroup: "+threadGroup+" and "+drawable); + } } protected String getBaseName(String prefix) { @@ -114,7 +127,7 @@ public class Animator extends AnimatorBase { stateSync.unlock(); } } - + private final void setIsAnimatingSynced(boolean v) { stateSync.lock(); try { @@ -126,36 +139,39 @@ public class Animator extends AnimatorBase { class MainLoop implements Runnable { public String toString() { - return "[started "+isStartedImpl()+", animating "+isAnimatingImpl()+", paused "+isPausedImpl()+", drawable "+drawables.size()+"]"; + return "[started "+isStartedImpl()+", animating "+isAnimatingImpl()+", paused "+isPausedImpl()+", drawable "+drawables.size()+", drawablesEmpty "+drawablesEmpty+"]"; } public void run() { try { - synchronized (Animator.this) { - if(DEBUG) { - System.err.println("Animator start:" + Thread.currentThread() + ": " + toString()); - } - fpsCounter.resetFPSCounter(); - animThread = Thread.currentThread(); - setIsAnimatingSynced(false); // barrier - Animator.this.notifyAll(); + if(DEBUG) { + System.err.println("Animator start:" + Thread.currentThread() + ": " + toString()); } + fpsCounter.resetFPSCounter(); + animThread = Thread.currentThread(); + setIsAnimatingSynced(false); // barrier + // 'waitForStartedCondition' wake-up is handled below! while (!stopIssued) { synchronized (Animator.this) { - // Don't consume CPU unless there is work to be done and not paused + // Pause; Also don't consume CPU unless there is work to be done and not paused + boolean ectCleared = false; while (!stopIssued && (pauseIssued || drawablesEmpty)) { boolean wasPaused = pauseIssued; if (DEBUG) { System.err.println("Animator pause:" + Thread.currentThread() + ": " + toString()); } + if ( exclusiveContext && !drawablesEmpty && !ectCleared ) { + ectCleared = true; + setDrawablesExclCtxState(false); + display(); // propagate exclusive change! + } setIsAnimatingSynced(false); // barrier Animator.this.notifyAll(); try { Animator.this.wait(); } catch (InterruptedException e) { } - if (wasPaused) { // resume from pause -> reset counter fpsCounter.resetFPSCounter(); @@ -165,9 +181,12 @@ public class Animator extends AnimatorBase { } } if (!stopIssued && !isAnimating) { - // resume from pause or drawablesEmpty, + // Wakes up 'waitForStartedCondition' sync + // - and - + // Resume from pause or drawablesEmpty, // implies !pauseIssued and !drawablesEmpty - setIsAnimatingSynced(true); + setIsAnimatingSynced(true); // barrier + setDrawablesExclCtxState(exclusiveContext); Animator.this.notifyAll(); } } // sync Animator.this @@ -180,6 +199,10 @@ public class Animator extends AnimatorBase { } } } finally { + if( exclusiveContext && !drawablesEmpty ) { + setDrawablesExclCtxState(false); + display(); // propagate exclusive change! + } synchronized (Animator.this) { if(DEBUG) { System.err.println("Animator stop " + Thread.currentThread() + ": " + toString()); @@ -194,18 +217,6 @@ public class Animator extends AnimatorBase { } } - private final boolean isStartedImpl() { - return animThread != null ; - } - public final boolean isStarted() { - stateSync.lock(); - try { - return animThread != null ; - } finally { - stateSync.unlock(); - } - } - private final boolean isAnimatingImpl() { return animThread != null && isAnimating ; } @@ -230,39 +241,19 @@ public class Animator extends AnimatorBase { } } - interface Condition { - /** - * @return true if branching (cont waiting, action), otherwise false - */ - boolean result(); - } - - private synchronized void finishLifecycleAction(Condition condition) { - // It's hard to tell whether the thread which changes the lifecycle has - // dependencies on the Animator's internal thread. Currently we - // use a couple of heuristics to determine whether we should do - // the blocking wait(). - final boolean blocking = impl.blockUntilDone(animThread); - long remaining = blocking ? TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION : 0; - while (remaining>0 && condition.result()) { - long td = System.currentTimeMillis(); - try { - wait(remaining); - } catch (InterruptedException ie) { } - remaining -= (System.currentTimeMillis() - td) ; - } - if(DEBUG) { - if(remaining<0) { - System.err.println("finishLifecycleAction(" + condition.getClass().getName() + "): ++++++ timeout reached ++++++ " + Thread.currentThread().getName()); - } - System.err.println("finishLifecycleAction(" + condition.getClass().getName() + "): finished "+ - "- blocking "+blocking+ - ", waited " + (blocking ? ( TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION - remaining ) : 0 ) + "/" + TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION + - ", started: " + isStartedImpl() +", animating: " + isAnimatingImpl() + - ", paused: " + isPausedImpl() + ", drawables " + drawables.size() + " - " + Thread.currentThread().getName()); + /** + * Set a {@link ThreadGroup} for the {@link #getThread() animation thread}. + * + * @param tg the {@link ThreadGroup} + * @throws GLException if the animator has already been started + */ + public synchronized void setThreadGroup(ThreadGroup tg) throws GLException { + if ( isStartedImpl() ) { + throw new GLException("Animator already started."); } + threadGroup = tg; } - + public synchronized boolean start() { if ( isStartedImpl() ) { return false; @@ -284,74 +275,48 @@ public class Animator extends AnimatorBase { System.err.println("Animator "+ct.getName()+"[daemon "+ct.isDaemon()+"]: starting "+thread.getName()+"[daemon "+thread.isDaemon()+"]"); } thread.start(); - finishLifecycleAction(waitForStartedCondition); - return true; + return finishLifecycleAction(waitForStartedCondition, 0); } - - private class WaitForStartedCondition implements Condition { - public boolean result() { + private final Condition waitForStartedCondition = new Condition() { + public boolean eval() { return !isStartedImpl() || (!drawablesEmpty && !isAnimating) ; - } - } - Condition waitForStartedCondition = new WaitForStartedCondition(); + } }; public synchronized boolean stop() { if ( !isStartedImpl() ) { return false; } stopIssued = true; - notifyAll(); - finishLifecycleAction(waitForStoppedCondition); - return true; + return finishLifecycleAction(waitForStoppedCondition, 0); } - private class WaitForStoppedCondition implements Condition { - public boolean result() { + private final Condition waitForStoppedCondition = new Condition() { + public boolean eval() { return isStartedImpl(); - } - } - Condition waitForStoppedCondition = new WaitForStoppedCondition(); + } }; public synchronized boolean pause() { if ( !isStartedImpl() || pauseIssued ) { return false; } - stateSync.lock(); - try { - pauseIssued = true; - } finally { - stateSync.unlock(); - } - notifyAll(); - finishLifecycleAction(waitForPausedCondition); - return true; + pauseIssued = true; + return finishLifecycleAction(waitForPausedCondition, 0); } - private class WaitForPausedCondition implements Condition { - public boolean result() { + private final Condition waitForPausedCondition = new Condition() { + public boolean eval() { // end waiting if stopped as well return isAnimating && isStartedImpl(); - } - } - Condition waitForPausedCondition = new WaitForPausedCondition(); + } }; public synchronized boolean resume() { if ( !isStartedImpl() || !pauseIssued ) { return false; } - stateSync.lock(); - try { - pauseIssued = false; - } finally { - stateSync.unlock(); - } - notifyAll(); - finishLifecycleAction(waitForResumeCondition); - return true; + pauseIssued = false; + return finishLifecycleAction(waitForResumeCondition, 0); } - private class WaitForResumeCondition implements Condition { - public boolean result() { + private final Condition waitForResumeCondition = new Condition() { + public boolean eval() { // end waiting if stopped as well return !drawablesEmpty && !isAnimating && isStartedImpl(); - } - } - Condition waitForResumeCondition = new WaitForResumeCondition(); + } }; } diff --git a/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java b/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java index 46fc1d991..bda716e97 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java +++ b/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java @@ -38,6 +38,7 @@ import java.util.ArrayList; import javax.media.opengl.GLAnimatorControl; import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLException; import javax.media.opengl.GLProfile; /** @@ -51,69 +52,174 @@ import javax.media.opengl.GLProfile; */ public abstract class AnimatorBase implements GLAnimatorControl { protected static final boolean DEBUG = Debug.debug("Animator"); - - private static int animatorCount = 0; - + + /** A 1s timeout while waiting for a native action response, limiting {@link #finishLifecycleAction(Condition, long)} */ + protected static final long TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION = 1000; + + protected static final long POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION = 32; // 2 frames @ 60Hz + + /** + * If present in <code>modeBits</code> field and + * {@link GLProfile#isAWTAvailable() AWT is available}, + * implementation is aware of the AWT EDT, otherwise not. + * <p> + * This is the <i>default</i>. + * </p> + * @see #setModeBits(boolean, int) + */ + public static final int MODE_EXPECT_AWT_RENDERING_THREAD = 1 << 0; + public interface AnimatorImpl { void display(ArrayList<GLAutoDrawable> drawables, boolean ignoreExceptions, boolean printExceptions); boolean blockUntilDone(Thread thread); } - protected ArrayList<GLAutoDrawable> drawables = new ArrayList<GLAutoDrawable>(); - protected boolean drawablesEmpty; + protected int modeBits; protected AnimatorImpl impl; protected String baseName; + + protected ArrayList<GLAutoDrawable> drawables = new ArrayList<GLAutoDrawable>(); + protected boolean drawablesEmpty; protected Thread animThread; protected boolean ignoreExceptions; protected boolean printExceptions; + protected boolean exclusiveContext; + protected Thread userExclusiveContextThread; protected FPSCounterImpl fpsCounter = new FPSCounterImpl(); protected RecursiveLock stateSync = LockFactory.createRecursiveLock(); - - /** Creates a new, empty Animator. */ - public AnimatorBase() { - if(GLProfile.isAWTAvailable()) { + + private final static Class<?> awtAnimatorImplClazz; + static { + GLProfile.initSingleton(); + if( GLProfile.isAWTAvailable() ) { + Class<?> clazz; try { - impl = (AnimatorImpl) Class.forName("com.jogamp.opengl.util.AWTAnimatorImpl").newInstance(); - baseName = "AWTAnimator"; - } catch (Exception e) { e.printStackTrace(); } - } - if(null==impl) { - impl = new DefaultAnimatorImpl(); - baseName = "Animator"; - } - synchronized (Animator.class) { - animatorCount++; - baseName = baseName.concat("-"+animatorCount); - drawablesEmpty = true; + clazz = Class.forName("com.jogamp.opengl.util.AWTAnimatorImpl"); + } catch (Exception e) { + clazz = null; + } + awtAnimatorImplClazz = clazz; + } else { + awtAnimatorImplClazz = null; } } + /** + * Creates a new, empty Animator instance + * while expecting an AWT rendering thread if AWT is available. + * + * @see GLProfile#isAWTAvailable() + */ + public AnimatorBase() { + modeBits = MODE_EXPECT_AWT_RENDERING_THREAD; // default! + drawablesEmpty = true; + } + + /** + * Initializes implementation details post setup, + * invoked at {@link #add(GLAutoDrawable)}, {@link #start()}, .. + * <p> + * Operation is a NOP if <code>force</code> is <code>false</code> + * and this instance is already initialized. + * </p> + * + * @throws GLException if Animator is {@link #isStarted()} + */ + protected void initImpl(boolean force) { + if( force || null == impl ) { + if( 0 != ( MODE_EXPECT_AWT_RENDERING_THREAD & modeBits ) && null != awtAnimatorImplClazz ) { + try { + impl = (AnimatorImpl) awtAnimatorImplClazz.newInstance(); + baseName = getBaseName("AWT"); + } catch (Exception e) { e.printStackTrace(); } + } + if( null == impl ) { + impl = new DefaultAnimatorImpl(); + baseName = getBaseName(""); + } + if(DEBUG) { + System.err.println("Animator.initImpl: baseName "+baseName+", implClazz "+impl.getClass().getName()+" - "+toString()+" - "+Thread.currentThread().getName()); + } + } + } protected abstract String getBaseName(String prefix); - public synchronized void add(GLAutoDrawable drawable) { + /** + * Enables or disables the given <code>bitValues</code> + * in this Animators <code>modeBits</code>. + * @param enable + * @param bitValues + * + * @throws GLException if Animator is {@link #isStarted()} + * @see AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD + */ + public synchronized void setModeBits(boolean enable, int bitValues) throws GLException { + if( isStarted() ) { + throw new GLException("Animator already started"); + } + final int _oldModeBits = modeBits; + if(enable) { + modeBits |= bitValues; + } else { + modeBits &= ~bitValues; + } + if( _oldModeBits != modeBits ) { + initImpl(true); + } + } + public synchronized int getModeBits() { return modeBits; } + + + @Override + public synchronized void add(final GLAutoDrawable drawable) { if(DEBUG) { - System.err.println("Animator add: "+drawable.hashCode()+" - "+Thread.currentThread().getName()); + System.err.println("Animator add: 0x"+Integer.toHexString(drawable.hashCode())+" - "+toString()+" - "+Thread.currentThread().getName()); } + if( drawables.contains(drawable) ) { + throw new IllegalArgumentException("Drawable already added to animator: "+this+", "+drawable); + } + initImpl(false); boolean paused = pause(); + if( isStarted() ) { + drawable.setExclusiveContextThread( exclusiveContext ? getExclusiveContextThread() : null ); // if already running .. + } drawables.add(drawable); drawablesEmpty = drawables.size() == 0; drawable.setAnimator(this); if(paused) { resume(); } - if(impl.blockUntilDone(animThread)) { - while(isStarted() && !isPaused() && !isAnimating()) { - try { - wait(); - } catch (InterruptedException ie) { } - } + final Condition waitForAnimatingAndECTCondition = new Condition() { + public boolean eval() { + final Thread dect = drawable.getExclusiveContextThread(); + return isStarted() && !isPaused() && !isAnimating() && ( exclusiveContext && null == dect || !exclusiveContext && null != dect ); + } }; + final boolean res = finishLifecycleAction(waitForAnimatingAndECTCondition, 0); + if(DEBUG) { + System.err.println("Animator add: Wait for Animating/ECT OK: "+res+", "+toString()+", dect "+drawable.getExclusiveContextThread()); } notifyAll(); } - public synchronized void remove(GLAutoDrawable drawable) { + @Override + public synchronized void remove(final GLAutoDrawable drawable) { if(DEBUG) { - System.err.println("Animator remove: "+drawable.hashCode()+" - "+Thread.currentThread().getName() + ": "+toString()); + System.err.println("Animator remove: 0x"+Integer.toHexString(drawable.hashCode())+" - "+toString()+" - "+Thread.currentThread().getName()); + } + if( !drawables.contains(drawable) ) { + throw new IllegalArgumentException("Drawable not added to animator: "+this+", "+drawable); + } + + if( exclusiveContext && isAnimating() ) { + drawable.setExclusiveContextThread( null ); + final Condition waitForNullECTCondition = new Condition() { + public boolean eval() { + return null != drawable.getExclusiveContextThread(); + } }; + final boolean res = finishLifecycleAction(waitForNullECTCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION); + if(DEBUG) { + System.err.println("Animator remove: Wait for Null-ECT OK: "+res+", "+toString()+", dect "+drawable.getExclusiveContextThread()); + } } boolean paused = pause(); @@ -123,14 +229,202 @@ public abstract class AnimatorBase implements GLAnimatorControl { if(paused) { resume(); } - if(impl.blockUntilDone(animThread)) { - while(isStarted() && drawablesEmpty && isAnimating()) { + final boolean res = finishLifecycleAction(waitForNotAnimatingIfEmptyCondition, 0); + if(DEBUG) { + System.err.println("Animator remove: Wait for !Animating-if-empty OK: "+res+", "+toString()); + } + notifyAll(); + } + private final Condition waitForNotAnimatingIfEmptyCondition = new Condition() { + public boolean eval() { + return isStarted() && drawablesEmpty && isAnimating(); + } }; + + + /** + * Dedicate all {@link GLAutoDrawable}'s context to the given exclusive context thread. + * <p> + * The given thread will be exclusive to all {@link GLAutoDrawable}'s context while {@link #isAnimating()}. + * </p> + * <p> + * If already started and disabling, method waits + * until change is propagated to all {@link GLAutoDrawable} if not + * called from the animator thread or {@link #getExclusiveContextThread() exclusive context thread}. + * </p> + * <p> + * Note: Utilizing this feature w/ AWT could lead to an AWT-EDT deadlock, depending on the AWT implementation. + * Hence it is advised not to use it with native AWT GLAutoDrawable like GLCanvas. + * </p> + * + * @param enable + * @return previous value + * @see #setExclusiveContext(boolean) + * @see #getExclusiveContextThread() + * @see #isExclusiveContextEnabled() + */ + // @Override + public final Thread setExclusiveContext(Thread t) { + final Thread old; + final boolean enable = null != t; + stateSync.lock(); + try { + old = userExclusiveContextThread; + if( enable && t != animThread ) { // disable: will be cleared at end after propagation && filter out own animThread usae + userExclusiveContextThread=t; + } + } finally { + stateSync.unlock(); + } + setExclusiveContext(enable); + return old; + } + + /** + * Dedicate all {@link GLAutoDrawable}'s context to this animator thread. + * <p> + * The given thread will be exclusive to all {@link GLAutoDrawable}'s context while {@link #isAnimating()}. + * </p> + * <p> + * If already started and disabling, method waits + * until change is propagated to all {@link GLAutoDrawable} if not + * called from the animator thread or {@link #getExclusiveContextThread() exclusive context thread}. + * </p> + * <p> + * Note: Utilizing this feature w/ AWT could lead to an AWT-EDT deadlock, depending on the AWT implementation. + * Hence it is advised not to use it with native AWT GLAutoDrawable like GLCanvas. + * </p> + * + * @param enable + * @return previous value + * @see #setExclusiveContext(Thread) + * @see #getExclusiveContextThread() + * @see #isExclusiveContextEnabled() + */ + // @Override + public final boolean setExclusiveContext(boolean enable) { + final boolean propagateState; + final boolean oldExclusiveContext; + final Thread _exclusiveContextThread; + synchronized (AnimatorBase.this) { + propagateState = isStarted() && !drawablesEmpty; + _exclusiveContextThread = userExclusiveContextThread; + oldExclusiveContext = exclusiveContext; + exclusiveContext = enable; + if(DEBUG) { + System.err.println("AnimatorBase.setExclusiveContextThread: "+oldExclusiveContext+" -> "+exclusiveContext+", propagateState "+propagateState+", "+this); + } + } + final Thread dECT = enable ? ( null != userExclusiveContextThread ? userExclusiveContextThread : animThread ) : null ; + if( propagateState ) { + setDrawablesExclCtxState(enable); + if( !enable ) { + if( Thread.currentThread() == getThread() || Thread.currentThread() == _exclusiveContextThread ) { + display(); + } else { + final boolean resumed = isAnimating() ? false : resume(); + int counter = 10; + while( 0<counter && isAnimating() && !validateDrawablesExclCtxState(dECT) ) { + try { + Thread.sleep(20); + } catch (InterruptedException e) { } + counter--; + } + if(resumed) { + pause(); + } + } + stateSync.lock(); try { - wait(); - } catch (InterruptedException ie) { } + userExclusiveContextThread=null; + } finally { + stateSync.unlock(); + } } } - notifyAll(); + if(DEBUG) { + System.err.println("AnimatorBase.setExclusiveContextThread: all-GLAD Ok: "+validateDrawablesExclCtxState(dECT)+", "+this); + } + return oldExclusiveContext; + } + + /** + * Returns <code>true</code>, if the exclusive context thread is enabled, otherwise <code>false</code>. + * + * @see #setExclusiveContext(boolean) + * @see #setExclusiveContext(Thread) + */ + // @Override + public final boolean isExclusiveContextEnabled() { + stateSync.lock(); + try { + return exclusiveContext; + } finally { + stateSync.unlock(); + } + } + + /** + * Returns the exclusive context thread if {@link #isExclusiveContextEnabled()} and {@link #isStarted()}, otherwise <code>null</code>. + * <p> + * If exclusive context is enabled via {@link #setExclusiveContext(boolean)} + * the {@link #getThread() animator thread} is returned if above conditions are met. + * </p> + * <p> + * If exclusive context is enabled via {@link #setExclusiveContext(Thread)} + * the user passed thread is returned if above conditions are met. + * </p> + * @see #setExclusiveContext(boolean) + * @see #setExclusiveContext(Thread) + */ + // @Override + public final Thread getExclusiveContextThread() { + stateSync.lock(); + try { + return ( isStartedImpl() && exclusiveContext ) ? ( null != userExclusiveContextThread ? userExclusiveContextThread : animThread ) : null ; + } finally { + stateSync.unlock(); + } + } + + /** + * Should be called at {@link #start()} and {@link #stop()} + * from within the animator thread. + * <p> + * At {@link #stop()} an additional {@link #display()} call shall be issued + * to allow propagation of releasing the exclusive thread. + * </p> + */ + protected final synchronized void setDrawablesExclCtxState(boolean enable) { + if(DEBUG) { + System.err.println("AnimatorBase.setExclusiveContextImpl exlusive "+exclusiveContext+": Enable "+enable+" for "+this+" - "+Thread.currentThread()); + // Thread.dumpStack(); + } + final Thread ect = getExclusiveContextThread(); + for (int i=0; i<drawables.size(); i++) { + try { + drawables.get(i).setExclusiveContextThread( enable ? ect : null ); + } catch (RuntimeException e) { + e.printStackTrace(); + } + } + } + protected final boolean validateDrawablesExclCtxState(Thread expected) { + for (int i=0; i<drawables.size(); i++) { + if( expected != drawables.get(i).getExclusiveContextThread() ) { + return false; + } + } + return true; + } + + @Override + public final Thread getThread() { + stateSync.lock(); + try { + return animThread; + } finally { + stateSync.unlock(); + } } /** Called every frame to cause redrawing of all of the @@ -143,55 +437,56 @@ public abstract class AnimatorBase implements GLAnimatorControl { fpsCounter.tickFPS(); } + @Override public final void setUpdateFPSFrames(int frames, PrintStream out) { fpsCounter.setUpdateFPSFrames(frames, out); } + @Override public final void resetFPSCounter() { fpsCounter.resetFPSCounter(); } + @Override public final int getUpdateFPSFrames() { return fpsCounter.getUpdateFPSFrames(); } + @Override public final long getFPSStartTime() { return fpsCounter.getFPSStartTime(); } + @Override public final long getLastFPSUpdateTime() { return fpsCounter.getLastFPSUpdateTime(); } + @Override public final long getLastFPSPeriod() { return fpsCounter.getLastFPSPeriod(); } + @Override public final float getLastFPS() { return fpsCounter.getLastFPS(); } + @Override public final int getTotalFPSFrames() { return fpsCounter.getTotalFPSFrames(); } + @Override public final long getTotalFPSDuration() { return fpsCounter.getTotalFPSDuration(); } + @Override public final float getTotalFPS() { return fpsCounter.getTotalFPS(); } - public final Thread getThread() { - stateSync.lock(); - try { - return animThread; - } finally { - stateSync.unlock(); - } - } - /** Sets a flag causing this Animator to ignore exceptions produced while redrawing the drawables. By default this flag is set to false, causing any exception thrown to halt the Animator. */ @@ -207,7 +502,78 @@ public abstract class AnimatorBase implements GLAnimatorControl { this.printExceptions = printExceptions; } + protected interface Condition { + /** + * @return true if branching (continue waiting, action), otherwise false + */ + boolean eval(); + } + + /** + * @param waitCondition method will wait until TO is reached or {@link Condition#eval() waitCondition.eval()} returns <code>false</code>. + * @param pollPeriod if <code>0</code>, method will wait until TO is reached or being notified. + * if > <code>0</code>, method will wait for the given <code>pollPeriod</code> in milliseconds. + * @return <code>true</code> if {@link Condition#eval() waitCondition.eval()} returned <code>false</code>, otherwise <code>false</code>. + */ + protected synchronized boolean finishLifecycleAction(Condition waitCondition, long pollPeriod) { + // It's hard to tell whether the thread which changes the lifecycle has + // dependencies on the Animator's internal thread. Currently we + // use a couple of heuristics to determine whether we should do + // the blocking wait(). + initImpl(false); + final boolean blocking = impl.blockUntilDone(animThread); + long remaining = blocking ? TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION : 0; + if( 0 >= pollPeriod ) { + pollPeriod = remaining; + } + boolean nok = waitCondition.eval(); + while ( nok && remaining>0 ) { + final long t1 = System.currentTimeMillis(); + if( pollPeriod > remaining ) { pollPeriod = remaining; } + notifyAll(); + try { + wait(pollPeriod); + } catch (InterruptedException ie) { } + remaining -= System.currentTimeMillis() - t1 ; + nok = waitCondition.eval(); + } + if(DEBUG || nok) { + if( remaining<=0 && nok ) { + System.err.println("finishLifecycleAction(" + waitCondition.getClass().getName() + "): ++++++ timeout reached ++++++ " + Thread.currentThread().getName()); + } + stateSync.lock(); // avoid too many lock/unlock ops + try { + System.err.println("finishLifecycleAction(" + waitCondition.getClass().getName() + "): OK "+(!nok)+ + "- pollPeriod "+pollPeriod+", blocking "+blocking+ + ", waited " + (blocking ? ( TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION - remaining ) : 0 ) + "/" + TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION + + " - " + Thread.currentThread().getName()); + System.err.println(" - "+toString()); + } finally { + stateSync.unlock(); + } + if(nok) { + Thread.dumpStack(); + } + } + return !nok; + } + + protected final boolean isStartedImpl() { + return animThread != null ; + } + @Override + public boolean isStarted() { + stateSync.lock(); + try { + return animThread != null ; + } finally { + stateSync.unlock(); + } + } + public String toString() { - return getClass().getName()+"[started "+isStarted()+", animating "+isAnimating()+", paused "+isPaused()+", drawable "+drawables.size()+", totals[dt "+getTotalFPSDuration()+", frames "+getTotalFPSFrames()+", fps "+getTotalFPS()+"]]"; + return getClass().getName()+"[started "+isStarted()+", animating "+isAnimating()+", paused "+isPaused()+", drawable "+drawables.size()+ + ", totals[dt "+getTotalFPSDuration()+", frames "+getTotalFPSFrames()+", fps "+getTotalFPS()+ + "], modeBits "+modeBits+", init'ed "+(null!=impl)+", animThread "+getThread()+", exclCtxThread "+exclusiveContext+"("+getExclusiveContextThread()+")]"; } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java b/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java index 251792110..bfcab23fd 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java +++ b/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java @@ -39,8 +39,11 @@ */ package com.jogamp.opengl.util; -import java.util.*; -import javax.media.opengl.*; +import java.util.Timer; +import java.util.TimerTask; + +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLException; /** * An Animator subclass which attempts to achieve a target @@ -54,10 +57,12 @@ import javax.media.opengl.*; */ public class FPSAnimator extends AnimatorBase { private Timer timer = null; - private TimerTask task = null; + private MainTask task = null; private int fps; private boolean scheduleAtFixedRate; - private volatile boolean shouldRun; + private boolean isAnimating; // MainTask feedback + private volatile boolean shouldRun; // MainTask trigger + private volatile boolean shouldStop; // MainTask trigger protected String getBaseName(String prefix) { return "FPS" + prefix + "Animator" ; @@ -88,6 +93,7 @@ public class FPSAnimator extends AnimatorBase { value, an initial drawable to animate, and a flag indicating whether to use fixed-rate scheduling. */ public FPSAnimator(GLAutoDrawable drawable, int fps, boolean scheduleAtFixedRate) { + super(); this.fps = fps; if (drawable != null) { add(drawable); @@ -95,133 +101,260 @@ public class FPSAnimator extends AnimatorBase { this.scheduleAtFixedRate = scheduleAtFixedRate; } - public final boolean isStarted() { - stateSync.lock(); - try { - return (timer != null); - } finally { - stateSync.unlock(); + /** + * @param fps + * @throws GLException if the animator has already been started + */ + public final synchronized void setFPS(int fps) throws GLException { + if ( isStartedImpl() ) { + throw new GLException("Animator already started."); } + this.fps = fps; } + public final int getFPS() { return fps; } + + class MainTask extends TimerTask { + private boolean justStarted; + private boolean alreadyStopped; + private boolean alreadyPaused; + + public MainTask() { + } + + public void start(Timer timer) { + fpsCounter.resetFPSCounter(); + shouldRun = true; + shouldStop = false; + + justStarted = true; + alreadyStopped = false; + alreadyPaused = false; + final long period = 0 < fps ? (long) (1000.0f / (float) fps) : 1; // 0 -> 1: IllegalArgumentException: Non-positive period + if (scheduleAtFixedRate) { + timer.scheduleAtFixedRate(this, 0, period); + } else { + timer.schedule(this, 0, period); + } + } + + public boolean isActive() { return !alreadyStopped && !alreadyPaused; } + + public String toString() { + return "Task[thread "+animThread+", stopped "+alreadyStopped+", paused "+alreadyPaused+" shouldRun "+shouldRun+", shouldStop "+shouldStop+" -- started "+isStartedImpl()+", animating "+isAnimatingImpl()+", paused "+isPausedImpl()+", drawable "+drawables.size()+", drawablesEmpty "+drawablesEmpty+"]"; + } + + public void run() { + if( justStarted ) { + justStarted = false; + synchronized (FPSAnimator.this) { + animThread = Thread.currentThread(); + if(DEBUG) { + System.err.println("FPSAnimator start/resume:" + Thread.currentThread() + ": " + toString()); + } + isAnimating = true; + if( drawablesEmpty ) { + shouldRun = false; // isAnimating:=false @ pause below + } else { + shouldRun = true; + setDrawablesExclCtxState(exclusiveContext); + FPSAnimator.this.notifyAll(); + } + System.err.println("FPSAnimator P1:" + Thread.currentThread() + ": " + toString()); + } + } + if( shouldRun ) { + display(); + } else if( shouldStop ) { // STOP + System.err.println("FPSAnimator P4: "+alreadyStopped+", "+ Thread.currentThread() + ": " + toString()); + this.cancel(); + + if( !alreadyStopped ) { + alreadyStopped = true; + if( exclusiveContext && !drawablesEmpty ) { + setDrawablesExclCtxState(false); + display(); // propagate exclusive change! + } + synchronized (FPSAnimator.this) { + if(DEBUG) { + System.err.println("FPSAnimator stop " + Thread.currentThread() + ": " + toString()); + } + animThread = null; + isAnimating = false; + FPSAnimator.this.notifyAll(); + } + } + } else { + System.err.println("FPSAnimator P5: "+alreadyPaused+", "+ Thread.currentThread() + ": " + toString()); + this.cancel(); + + if( !alreadyPaused ) { // PAUSE + alreadyPaused = true; + if( exclusiveContext && !drawablesEmpty ) { + setDrawablesExclCtxState(false); + display(); // propagate exclusive change! + } + synchronized (FPSAnimator.this) { + if(DEBUG) { + System.err.println("FPSAnimator pause " + Thread.currentThread() + ": " + toString()); + } + isAnimating = false; + FPSAnimator.this.notifyAll(); + } + } + } + } + } + private final boolean isAnimatingImpl() { + return animThread != null && isAnimating ; + } public final boolean isAnimating() { stateSync.lock(); try { - return (timer != null) && (task != null); + return animThread != null && isAnimating ; } finally { stateSync.unlock(); } } + private final boolean isPausedImpl() { + return animThread != null && ( !shouldRun && !shouldStop ) ; + } public final boolean isPaused() { stateSync.lock(); try { - return (timer != null) && (task == null); + return animThread != null && ( !shouldRun && !shouldStop ) ; } finally { stateSync.unlock(); } } - private void startTask() { - if(null != task) { - return; - } - final long period = (long) (1000.0f / (float) fps); - task = new TimerTask() { - public void run() { - if(FPSAnimator.this.shouldRun) { - FPSAnimator.this.animThread = Thread.currentThread(); - // display impl. uses synchronized block on the animator instance - display(); - } - } - }; - - fpsCounter.resetFPSCounter(); - shouldRun = true; - - if (scheduleAtFixedRate) { - timer.scheduleAtFixedRate(task, 0, period); - } else { - timer.schedule(task, 0, period); - } - } - - public synchronized boolean start() { - if (timer != null) { + static int timerNo = 0; + + public synchronized boolean start() { + if ( null != timer || null != task || isStartedImpl() ) { return false; } - stateSync.lock(); - try { - timer = new Timer(); - startTask(); - } finally { - stateSync.unlock(); + timer = new Timer( Thread.currentThread().getName()+"-"+baseName+"-Timer"+(timerNo++) ); + task = new MainTask(); + if(DEBUG) { + System.err.println("FPSAnimator.start() START: "+task+", "+ Thread.currentThread() + ": " + toString()); } - return true; + task.start(timer); + + final boolean res = finishLifecycleAction( drawablesEmpty ? waitForStartedEmptyCondition : waitForStartedAddedCondition, + POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION); + if(DEBUG) { + System.err.println("FPSAnimator.start() END: "+task+", "+ Thread.currentThread() + ": " + toString()); + } + if( drawablesEmpty ) { + task.cancel(); + task = null; + } + return res; } + private final Condition waitForStartedAddedCondition = new Condition() { + public boolean eval() { + return !isStartedImpl() || !isAnimating ; + } }; + private final Condition waitForStartedEmptyCondition = new Condition() { + public boolean eval() { + return !isStartedImpl() || isAnimating ; + } }; /** Stops this FPSAnimator. Due to the implementation of the FPSAnimator it is not guaranteed that the FPSAnimator will be completely stopped by the time this method returns. */ public synchronized boolean stop() { - if (timer == null) { + if ( null == timer || !isStartedImpl() ) { return false; + } + if(DEBUG) { + System.err.println("FPSAnimator.stop() START: "+task+", "+ Thread.currentThread() + ": " + toString()); } - stateSync.lock(); - try { + final boolean res; + if( null == task ) { + // start/resume case w/ drawablesEmpty + res = true; + } else { shouldRun = false; - if(null != task) { - task.cancel(); - task = null; - } - if(null != timer) { - timer.cancel(); - timer = null; - } - animThread = null; - try { - final long periodx2 = 2L * (long) (1000.0f / (float) fps); - Thread.sleep(periodx2 > 20L ? periodx2 : 20L); // max(2 x timer period, ~ 1/60), since we can't ctrl stopped threads - } catch (InterruptedException e) { } - } finally { - stateSync.unlock(); + shouldStop = true; + res = finishLifecycleAction(waitForStoppedCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION); + } + + if(DEBUG) { + System.err.println("FPSAnimator.stop() END: "+task+", "+ Thread.currentThread() + ": " + toString()); + } + if(null != task) { + task.cancel(); + task = null; } - return true; + if(null != timer) { + timer.cancel(); + timer = null; + } + animThread = null; + return res; } + private final Condition waitForStoppedCondition = new Condition() { + public boolean eval() { + return isStartedImpl(); + } }; public synchronized boolean pause() { - if (timer == null) { + if ( !isStartedImpl() || ( null != task && isPausedImpl() ) ) { return false; } - stateSync.lock(); - try { + if(DEBUG) { + System.err.println("FPSAnimator.pause() START: "+task+", "+ Thread.currentThread() + ": " + toString()); + } + final boolean res; + if( null == task ) { + // start/resume case w/ drawablesEmpty + res = true; + } else { shouldRun = false; - if(null != task) { - task.cancel(); - task = null; - } - animThread = null; - try { - final long periodx2 = 2L * (long) (1000.0f / (float) fps); - Thread.sleep(periodx2 > 20L ? periodx2 : 20L); // max(2 x timer period, ~ 1/60), since we can't ctrl stopped threads - } catch (InterruptedException e) { } - } finally { - stateSync.unlock(); + res = finishLifecycleAction(waitForPausedCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION); + } + + if(DEBUG) { + System.err.println("FPSAnimator.pause() END: "+task+", "+ Thread.currentThread() + ": " + toString()); + } + if(null != task) { + task.cancel(); + task = null; } - return true; + return res; } + private final Condition waitForPausedCondition = new Condition() { + public boolean eval() { + // end waiting if stopped as well + return isAnimating && isStartedImpl(); + } }; public synchronized boolean resume() { - if (timer == null) { + if ( null != task || !isStartedImpl() || !isPausedImpl() ) { return false; } - stateSync.lock(); - try { - startTask(); - } finally { - stateSync.unlock(); + if(DEBUG) { + System.err.println("FPSAnimator.resume() START: "+ Thread.currentThread() + ": " + toString()); + } + final boolean res; + if( drawablesEmpty ) { + res = true; + } else { + task = new MainTask(); + task.start(timer); + res = finishLifecycleAction(waitForResumeCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION); + } + if(DEBUG) { + System.err.println("FPSAnimator.resume() END: "+task+", "+ Thread.currentThread() + ": " + toString()); } - return true; + return res; } + private final Condition waitForResumeCondition = new Condition() { + public boolean eval() { + // end waiting if stopped as well + return !drawablesEmpty && !isAnimating && isStartedImpl(); + } }; } diff --git a/src/jogl/classes/javax/media/opengl/GLAnimatorControl.java b/src/jogl/classes/javax/media/opengl/GLAnimatorControl.java index 83e9e22c4..3052b924e 100644 --- a/src/jogl/classes/javax/media/opengl/GLAnimatorControl.java +++ b/src/jogl/classes/javax/media/opengl/GLAnimatorControl.java @@ -117,7 +117,7 @@ public interface GLAnimatorControl extends FPSCounter { * or in some cases from an implementation-internal thread like the * AWT event queue thread. * - * @return false if if not started or already paused, otherwise true + * @return false if not started, already paused or failed to pause, otherwise true * * @see #resume() * @see #isAnimating() @@ -134,7 +134,7 @@ public interface GLAnimatorControl extends FPSCounter { * <P> * If resumed, all counters (time, frames, ..) are reset to zero. * - * @return false if if not started or not paused, otherwise true + * @return false if not started, not paused or unable to resume, otherwise true * * @see #pause() * @see #isAnimating() @@ -142,13 +142,24 @@ public interface GLAnimatorControl extends FPSCounter { boolean resume(); /** + * Adds a drawable to this animator's list of rendering drawables.<br> + * This allows the animator thread to become active, i.e. {@link #isAnimating()}==true, + * in case the first drawable is added and {@link #isStarted()} and not {@link #isPaused()}.<br> + * + * @param drawable the drawable to be added + * @throws IllegalArgumentException if drawable was already added to this animator + */ + void add(GLAutoDrawable drawable); + + /** * Removes a drawable from the animator's list of rendering drawables.<br> * This method should get called in case a drawable becomes invalid, * and will not be recovered.<br> - * This allows the animator thread to become idle in case the last drawable - * has reached it's end of life.<br> + * This allows the animator thread to become idle, i.e. {@link #isAnimating()}==false, + * in case the last drawable has reached it's end of life.<br> * - * @param drawable the to be removed drawable + * @param drawable the drawable to be removed + * @throws IllegalArgumentException if drawable was not added to this animator */ void remove(GLAutoDrawable drawable); } diff --git a/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java b/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java index 0f487f463..a7db3f3fd 100644 --- a/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java +++ b/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java @@ -311,13 +311,13 @@ public interface GLAutoDrawable extends GLDrawable { public GLEventListener removeGLEventListener(GLEventListener listener); /** - * <p> * Registers the usage of an animator, an {@link javax.media.opengl.GLAnimatorControl} implementation. - * The animator will be queried whether it's animating, ie periodically issuing {@link #display()} calls or not.</p><br> + * The animator will be queried whether it's animating, ie periodically issuing {@link #display()} calls or not. * <p> * This method shall be called by an animator implementation only,<br> * e.g. {@link com.jogamp.opengl.util.Animator#add(javax.media.opengl.GLAutoDrawable)}, passing it's control implementation,<br> - * and {@link com.jogamp.opengl.util.Animator#remove(javax.media.opengl.GLAutoDrawable)}, passing <code>null</code>.</p><br> + * and {@link com.jogamp.opengl.util.Animator#remove(javax.media.opengl.GLAutoDrawable)}, passing <code>null</code>. + * </p> * <p> * Impacts {@link #display()} and {@link #invoke(boolean, GLRunnable)} semantics.</p><br> * @@ -341,6 +341,45 @@ public interface GLAutoDrawable extends GLDrawable { public GLAnimatorControl getAnimator(); /** + * Dedicates this instance's {@link GLContext} to the given thread.<br/> + * The thread will exclusively claim the {@link GLContext} via {@link #display()} and not release it + * until {@link #destroy()} or <code>setExclusiveContextThread(null)</code> has been called. + * <p> + * Default non-exclusive behavior is <i>requested</i> via <code>setExclusiveContextThread(null)</code>, + * which will cause the next call of {@link #display()} on the exclusive thread to + * release the {@link GLContext}. Only after it's async release, {@link #getExclusiveContextThread()} + * will return <code>null</code>. + * </p> + * <p> + * To release a previous made exclusive thread, a user issues <code>setExclusiveContextThread(null)</code> + * and may poll {@link #getExclusiveContextThread()} until it returns <code>null</code>, + * <i>while</i> the exclusive thread is still running. + * </p> + * <p> + * Note: Setting a new exclusive thread without properly releasing a previous one + * will throw an GLException. + * </p> + * <p> + * Note: Utilizing this feature w/ AWT could lead to an AWT-EDT deadlock, depending on the AWT implementation. + * Hence it is advised not to use it with native AWT GLAutoDrawable like GLCanvas. + * </p> + * <p> + * One scenario could be to dedicate the context to the {@link GLAnimatorControl#getThread() animator thread} + * and spare redundant context switches, see {@link com.jogamp.opengl.util.AnimatorBase#setExclusiveContext(boolean)}. + * </p> + * @param t the exclusive thread to claim the context, or <code>null</code> for default operation. + * @return previous exclusive context thread + * @throws GLException If an exclusive thread is still active but a new one is attempted to be set + * @see com.jogamp.opengl.util.AnimatorBase#setExclusiveContext(boolean) + */ + public Thread setExclusiveContextThread(Thread t) throws GLException; + + /** + * @see #setExclusiveContextThread(Thread) + */ + public Thread getExclusiveContextThread(); + + /** * Enqueues a one-shot {@link GLRunnable}, * which will be executed within the next {@link #display()} call * after all registered {@link GLEventListener}s diff --git a/src/jogl/classes/javax/media/opengl/GLContext.java b/src/jogl/classes/javax/media/opengl/GLContext.java index 235003c38..461d481a8 100644 --- a/src/jogl/classes/javax/media/opengl/GLContext.java +++ b/src/jogl/classes/javax/media/opengl/GLContext.java @@ -276,6 +276,9 @@ public abstract class GLContext { /** * Makes this GLContext current on the calling thread. * <p> + * Recursive call to {@link #makeCurrent()} and hence {@link #release()} are supported. + * </p> + * <p> * There are two return values that indicate success and one that * indicates failure. * </p> @@ -288,7 +291,7 @@ public abstract class GLContext { * </p> * <p> * A return value of {@link #CONTEXT_CURRENT} indicates that the context has - * been made currrent, with its previous state restored. + * been made current, with its previous state restored. * </p> * <p> * If the context could not be made current (for example, because @@ -320,6 +323,9 @@ public abstract class GLContext { /** * Releases control of this GLContext from the current thread. * <p> + * Recursive call to {@link #release()} and hence {@link #makeCurrent()} are supported. + * </p> + * <p> * The drawable's surface is being unlocked at exit, * assumed to be locked by {@link #makeCurrent()}. * </p> diff --git a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java index 2f7fef9be..2de86b545 100644 --- a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java +++ b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java @@ -742,6 +742,16 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing } @Override + public final Thread setExclusiveContextThread(Thread t) throws GLException { + return helper.setExclusiveContextThread(t, context); + } + + @Override + public final Thread getExclusiveContextThread() { + return helper.getExclusiveContextThread(); + } + + @Override public boolean invoke(boolean wait, GLRunnable glRunnable) { return helper.invoke(this, wait, glRunnable); } diff --git a/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java b/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java index 23dedaa66..664edb996 100644 --- a/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java +++ b/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java @@ -474,6 +474,16 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } @Override + public final Thread setExclusiveContextThread(Thread t) throws GLException { + return helper.setExclusiveContextThread(t, getContext()); + } + + @Override + public final Thread getExclusiveContextThread() { + return helper.getExclusiveContextThread(); + } + + @Override public boolean invoke(boolean wait, GLRunnable glRunnable) { return helper.invoke(this, wait, glRunnable); } diff --git a/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java index cbb7cd699..eadd59559 100644 --- a/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java +++ b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java @@ -50,7 +50,6 @@ import javax.media.opengl.GLRunnable; import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.opengl.GLAutoDrawableDelegate; -import com.jogamp.opengl.util.Animator; /** @@ -407,6 +406,16 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { } @Override + public final Thread setExclusiveContextThread(Thread t) throws GLException { + return helper.setExclusiveContextThread(t, context); + } + + @Override + public final Thread getExclusiveContextThread() { + return helper.getExclusiveContextThread(); + } + + @Override public final boolean invoke(boolean wait, GLRunnable glRunnable) { return helper.invoke(this, wait, glRunnable); } @@ -532,25 +541,6 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { return null != _drawable ? _drawable.getHeight() : 0; } - /** - * @param t the thread for which context release shall be skipped, usually the animation thread, - * ie. {@link Animator#getThread()}. - * @deprecated This is an experimental feature, - * intended for measuring performance in regards to GL context switch. - */ - @Deprecated - public void setSkipContextReleaseThread(Thread t) { - helper.setSkipContextReleaseThread(t); - } - - /** - * @deprecated see {@link #setSkipContextReleaseThread(Thread)} - */ - @Deprecated - public Thread getSkipContextReleaseThread() { - return helper.getSkipContextReleaseThread(); - } - @Override public final GLCapabilitiesImmutable getChosenGLCapabilities() { final GLDrawable _drawable = drawable; diff --git a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java index dc5d50cf2..f8c58ee34 100644 --- a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java +++ b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java @@ -58,8 +58,6 @@ import javax.media.opengl.GLException; import javax.media.opengl.GLFBODrawable; import javax.media.opengl.GLRunnable; -import com.jogamp.opengl.util.Animator; - /** Encapsulates the implementation of most of the GLAutoDrawable's methods to be able to share it between GLCanvas and GLJPanel. */ public class GLDrawableHelper { @@ -73,7 +71,9 @@ public class GLDrawableHelper { private final Object glRunnablesLock = new Object(); private volatile ArrayList<GLRunnableTask> glRunnables = new ArrayList<GLRunnableTask>(); private boolean autoSwapBufferMode; - private Thread skipContextReleaseThread; + private volatile Thread exclusiveContextThread; + /** -1 release, 0 nop, 1 claim */ + private volatile int exclusiveContextSwitch; private GLAnimatorControl animatorCtrl; private static Runnable nop = new Runnable() { public void run() {} }; @@ -87,7 +87,8 @@ public class GLDrawableHelper { listenersToBeInit.clear(); } autoSwapBufferMode = true; - skipContextReleaseThread = null; + exclusiveContextThread = null; + exclusiveContextSwitch = 0; synchronized(glRunnablesLock) { glRunnables.clear(); } @@ -113,6 +114,23 @@ public class GLDrawableHelper { } /** + * Since GLContext's {@link GLContext#makeCurrent()} and {@link GLContext#release()} + * is recursive, a call to {@link GLContext#release()} may not natively release the context. + * <p> + * This methods continues calling {@link GLContext#release()} until the context has been natively released. + * </p> + * @param ctx + */ + public static final void forceNativeRelease(GLContext ctx) { + do { + ctx.release(); + if (DEBUG) { + System.err.println("GLDrawableHelper.forceNativeRelease() -- currentThread "+Thread.currentThread()+" -> "+GLContext.getCurrent()); + } + } while( ctx == GLContext.getCurrent() ); + } + + /** * Associate a new context to the drawable and also propagates the context/drawable switch by * calling {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}. * <p> @@ -767,23 +785,72 @@ public class GLDrawableHelper { return autoSwapBufferMode; } + private final String getExclusiveContextSwitchString() { + return 0 == exclusiveContextSwitch ? "nop" : ( 0 > exclusiveContextSwitch ? "released" : "claimed" ) ; + } + /** - * @param t the thread for which context release shall be skipped, usually the animation thread, - * ie. {@link Animator#getThread()}. - * @deprecated this is an experimental feature, - * intended for measuring performance in regards to GL context switch - * and only being used if {@link #PERF_STATS} is enabled - * by defining property <code>jogl.debug.GLDrawable.PerfStats</code>. + * Dedicates this instance's {@link GLContext} to the given thread.<br/> + * The thread will exclusively claim the {@link GLContext} via {@link #display()} and not release it + * until {@link #destroy()} or <code>setExclusiveContextThread(null)</code> has been called. + * <p> + * Default non-exclusive behavior is <i>requested</i> via <code>setExclusiveContextThread(null)</code>, + * which will cause the next call of {@link #display()} on the exclusive thread to + * release the {@link GLContext}. Only after it's async release, {@link #getExclusiveContextThread()} + * will return <code>null</code>. + * </p> + * <p> + * To release a previous made exclusive thread, a user issues <code>setExclusiveContextThread(null)</code> + * and may poll {@link #getExclusiveContextThread()} until it returns <code>null</code>, + * <i>while</i> the exclusive thread is still running. + * </p> + * <p> + * Note: Setting a new exclusive thread without properly releasing a previous one + * will throw an GLException. + * </p> + * <p> + * One scenario could be to dedicate the context to the {@link com.jogamp.opengl.util.AnimatorBase#getThread() animator thread} + * and spare redundant context switches. + * </p> + * @param t the exclusive thread to claim the context, or <code>null</code> for default operation. + * @return previous exclusive context thread + * @throws GLException If an exclusive thread is still active but a new one is attempted to be set */ - public final void setSkipContextReleaseThread(Thread t) { - skipContextReleaseThread = t; + public final Thread setExclusiveContextThread(Thread t, GLContext context) throws GLException { + if (DEBUG) { + System.err.println("GLDrawableHelper.setExclusiveContextThread(): START switch "+getExclusiveContextSwitchString()+", thread "+exclusiveContextThread+" -> "+t+" -- currentThread "+Thread.currentThread()); + } + final Thread oldExclusiveContextThread = exclusiveContextThread; + if( exclusiveContextThread == t ) { + exclusiveContextSwitch = 0; // keep + } else if( null == t ) { + exclusiveContextSwitch = -1; // release + } else { + exclusiveContextSwitch = 1; // claim + if( null != exclusiveContextThread ) { + throw new GLException("Release current exclusive Context Thread "+exclusiveContextThread+" first"); + } + if( null != context && GLContext.getCurrent() == context ) { + try { + forceNativeRelease(context); + } catch (Throwable ex) { + ex.printStackTrace(); + throw new GLException(ex); + } + } + exclusiveContextThread = t; + } + if (DEBUG) { + System.err.println("GLDrawableHelper.setExclusiveContextThread(): END switch "+getExclusiveContextSwitchString()+", thread "+exclusiveContextThread+" -- currentThread "+Thread.currentThread()); + } + return oldExclusiveContextThread; } - + /** - * @deprecated see {@link #setSkipContextReleaseThread(Thread)} + * @see #setExclusiveContextThread(Thread, GLContext) */ - public final Thread getSkipContextReleaseThread() { - return skipContextReleaseThread; + public final Thread getExclusiveContextThread() { + return exclusiveContextThread; } private static final ThreadLocal<Runnable> perThreadInitAction = new ThreadLocal<Runnable>(); @@ -828,7 +895,10 @@ public class GLDrawableHelper { * {@link #disposeAllGLEventListener(GLAutoDrawable, boolean) disposeAllGLEventListener(autoDrawable, false)} * with the context made current. * <p> - * If <code>destroyContext</code> is <code>true</code> the context is destroyed in the end while holding the lock.<br/> + * If <code>destroyContext</code> is <code>true</code> the context is destroyed in the end while holding the lock. + * </p> + * <p> + * If <code>destroyContext</code> is <code>false</code> the context is natively released, i.e. released as often as locked before. * </p> * @param autoDrawable * @param context @@ -842,14 +912,15 @@ public class GLDrawableHelper { Runnable lastInitAction = null; if (lastContext != null) { if (lastContext == context) { - lastContext = null; // utilize recursive locking + lastContext = null; } else { + // utilize recursive locking lastInitAction = perThreadInitAction.get(); lastContext.release(); } } - int res = GLContext.CONTEXT_NOT_CURRENT; - + + int res; try { res = context.makeCurrent(); if (GLContext.CONTEXT_NOT_CURRENT != res) { @@ -865,7 +936,7 @@ public class GLDrawableHelper { if(destroyContext) { context.destroy(); } else { - context.release(); + forceNativeRelease(context); } flushGLRunnables(); } catch (Exception e) { @@ -880,136 +951,204 @@ public class GLDrawableHelper { } } } - + 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) { - try { - 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(); - } - } - } - } finally { - 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; + final GLContext context, + final Runnable runnable, + final Runnable initAction) { + final Thread currentThread = Thread.currentThread(); + + // Exclusive Cases: + // 1: lock - unlock : default + // 2: lock - - : exclusive, not locked yet + // 3: - - - : exclusive, already locked + // 4: - - unlock : ex-exclusive, already locked + final boolean _isExclusiveThread, _releaseExclusiveThread; + if( null != exclusiveContextThread) { + if( currentThread == exclusiveContextThread ) { + _releaseExclusiveThread = 0 > exclusiveContextSwitch; + _isExclusiveThread = !_releaseExclusiveThread; + exclusiveContextSwitch = 0; + } else { + // Exclusive thread usage, but on other thread + return; + } + } else { + _releaseExclusiveThread = false; + _isExclusiveThread = false; } - 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"); + + // Support for recursive makeCurrent() calls as well as calling + // other drawables' display() methods from within another one's + int res = GLContext.CONTEXT_NOT_CURRENT; + GLContext lastContext = GLContext.getCurrent(); + Runnable lastInitAction = null; + if (lastContext != null) { + if (lastContext == context) { + res = GLContext.CONTEXT_CURRENT; + lastContext = null; + } else { + // utilize recursive locking + lastInitAction = perThreadInitAction.get(); + lastContext.release(); } - 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; + final boolean releaseContext; + if( GLContext.CONTEXT_NOT_CURRENT == res ) { + res = context.makeCurrent(); + releaseContext = !_isExclusiveThread; + } else { + releaseContext = _releaseExclusiveThread; } - } catch (Exception e) { - System.err.println("Catched: "+e.getMessage()); - e.printStackTrace(); + if (GLContext.CONTEXT_NOT_CURRENT != res) { + try { + 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 { + if( _releaseExclusiveThread ) { + exclusiveContextThread = null; + if (DEBUG) { + System.err.println("GLDrawableHelper.invokeGL() - Release ExclusiveContextThread -- currentThread "+Thread.currentThread()); + } + } + if( releaseContext ) { + try { + context.release(); + } catch (Exception e) { + System.err.println("Catched: "+e.getMessage()); + e.printStackTrace(); + } + } + } + } + } finally { + 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(); + + // Exclusive Cases: + // 1: lock - unlock : default + // 2: lock - - : exclusive, not locked yet + // 3: - - - : exclusive, already locked + // 4: - - unlock : ex-exclusive, already locked + final boolean _isExclusiveThread, _releaseExclusiveThread; + if( null != exclusiveContextThread) { + if( currentThread == exclusiveContextThread ) { + _releaseExclusiveThread = 0 > exclusiveContextSwitch; + _isExclusiveThread = !_releaseExclusiveThread; + } else { + // Exclusive thread usage, but on other thread + return; + } + } else { + _releaseExclusiveThread = false; + _isExclusiveThread = false; } - tdX = System.currentTimeMillis() - tdX; // release / destroy + // Support for recursive makeCurrent() calls as well as calling + // other drawables' display() methods from within another one's + int res = GLContext.CONTEXT_NOT_CURRENT; + GLContext lastContext = GLContext.getCurrent(); + Runnable lastInitAction = null; if (lastContext != null) { - final int res2 = lastContext.makeCurrent(); - if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) { - lastInitAction.run(); - } + if (lastContext == context) { + res = GLContext.CONTEXT_CURRENT; + lastContext = null; + } else { + // utilize recursive locking + lastInitAction = perThreadInitAction.get(); + lastContext.release(); + } } - } - long td = System.currentTimeMillis() - t0; - System.err.println("td0 "+td+"ms, fps "+(1.0/(td/1000.0))+", td-makeCurrent: "+tdA+"ms, td-render "+tdR+"ms, td-swap "+tdS+"ms, td-release "+tdX+"ms, ctx claimed: "+ctxClaimed+", ctx release: "+ctxReleased+", ctx destroyed "+ctxDestroyed); + + 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 { + final boolean releaseContext; + if( GLContext.CONTEXT_NOT_CURRENT == res ) { + res = context.makeCurrent(); + releaseContext = !_isExclusiveThread; + ctxClaimed = true; + } else { + releaseContext = _releaseExclusiveThread; + } + if (GLContext.CONTEXT_NOT_CURRENT != res) { + try { + perThreadInitAction.set(initAction); + if (GLContext.CONTEXT_CURRENT_NEW == res) { + if (DEBUG) { + System.err.println("GLDrawableHelper " + this + ".invokeGL(): Running initAction"); + } + initAction.run(); + } + tdR = System.currentTimeMillis(); + tdA = tdR - t0; // makeCurrent + runnable.run(); + tdS = System.currentTimeMillis(); + tdR = tdS - tdR; // render time + if ( autoSwapBufferMode ) { + drawable.swapBuffers(); + tdX = System.currentTimeMillis(); + tdS = tdX - tdS; // swapBuffers + } + } finally { + if( _releaseExclusiveThread ) { + exclusiveContextSwitch = 0; + exclusiveContextThread = null; + if (DEBUG) { + System.err.println("GLDrawableHelper.invokeGL() - Release ExclusiveContextThread -- currentThread "+Thread.currentThread()); + } + } + if( releaseContext ) { + try { + context.release(); + ctxReleased = true; + } catch (Exception e) { + System.err.println("Catched: "+e.getMessage()); + e.printStackTrace(); + } + } + } + } + } finally { + tdX = System.currentTimeMillis() - tdX; // release / destroy + if (lastContext != null) { + final int res2 = lastContext.makeCurrent(); + if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) { + lastInitAction.run(); + } + } + } + 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); } } |