From 224fab1b2c71464826594740022fdcbe278867dc Mon Sep 17 00:00:00 2001
From: Sven Gothel An Animator can be attached to one or more {@link
GLAutoDrawable}s to drive their display() methods in a loop.
modeBits
field and
+ * {@link GLProfile#isAWTAvailable() AWT is available},
+ * implementation is aware of the AWT EDT, otherwise not.
+ * + * This is the default. + *
+ * @see #setModeBits(boolean, int) + */ + public static final int MODE_EXPECT_AWT_RENDERING_THREAD = 1 << 0; + public interface AnimatorImpl { void display(ArrayList
+ * Operation is a NOP if force
is false
+ * and this instance is already initialized.
+ *
bitValues
+ * in this Animators modeBits
.
+ * @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.
+ * + * The given thread will be exclusive to all {@link GLAutoDrawable}'s context while {@link #isAnimating()}. + *
+ *+ * 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}. + *
+ *+ * 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. + *
+ * + * @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. + *+ * The given thread will be exclusive to all {@link GLAutoDrawable}'s context while {@link #isAnimating()}. + *
+ *+ * 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}. + *
+ *+ * 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. + *
+ * + * @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( 0false
.
+ *
+ * @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 null
.
+ * + * If exclusive context is enabled via {@link #setExclusiveContext(boolean)} + * the {@link #getThread() animator thread} is returned if above conditions are met. + *
+ *+ * If exclusive context is enabled via {@link #setExclusiveContext(Thread)} + * the user passed thread is returned if above conditions are met. + *
+ * @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. + *+ * At {@link #stop()} an additional {@link #display()} call shall be issued + * to allow propagation of releasing the exclusive thread. + *
+ */ + 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; i0
, method will wait until TO is reached or being notified.
+ * if > 0
, method will wait for the given pollPeriod
in milliseconds.
+ * @return true
if {@link Condition#eval() waitCondition.eval()} returned false
, otherwise false
.
+ */
+ 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,21 +134,32 @@ public interface GLAnimatorControl extends FPSCounter {
*
* 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()
*/
boolean resume();
+ /**
+ * Adds a drawable to this animator's list of rendering drawables.
+ * 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()}.
+ *
+ * @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.
* This method should get called in case a drawable becomes invalid,
* and will not be recovered.
- * This allows the animator thread to become idle in case the last drawable
- * has reached it's end of life.
+ * 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.
*
- * @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);
/**
- *
* 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.
* This method shall be called by an animator implementation only,
* e.g. {@link com.jogamp.opengl.util.Animator#add(javax.media.opengl.GLAutoDrawable)}, passing it's control implementation,
- * and {@link com.jogamp.opengl.util.Animator#remove(javax.media.opengl.GLAutoDrawable)}, passing null
.
null
.
+ *
* * Impacts {@link #display()} and {@link #invoke(boolean, GLRunnable)} semantics.
setExclusiveContextThread(null)
has been called.
+ *
+ * Default non-exclusive behavior is requested via setExclusiveContextThread(null)
,
+ * 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 null
.
+ *
+ * To release a previous made exclusive thread, a user issues setExclusiveContextThread(null)
+ * and may poll {@link #getExclusiveContextThread()} until it returns null
,
+ * while the exclusive thread is still running.
+ *
+ * Note: Setting a new exclusive thread without properly releasing a previous one + * will throw an GLException. + *
+ *+ * 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. + *
+ *+ * 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)}. + *
+ * @param t the exclusive thread to claim the context, ornull
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
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.
* + * Recursive call to {@link #makeCurrent()} and hence {@link #release()} are supported. + *
+ ** There are two return values that indicate success and one that * indicates failure. *
@@ -288,7 +291,7 @@ public abstract class GLContext { * ** 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. *
** 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. *
+ * Recursive call to {@link #release()} and hence {@link #makeCurrent()} are supported. + *
+ ** The drawable's surface is being unlocked at exit, * assumed to be locked by {@link #makeCurrent()}. *
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 @@ -741,6 +741,16 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing return helper.getAnimator(); } + @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 @@ -473,6 +473,16 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing return helper.getAnimator(); } + @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; /** @@ -406,6 +405,16 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { return helper.getAnimator(); } + @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+ * This methods continues calling {@link GLContext#release()} until the context has been natively released. + *
+ * @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);}. @@ -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 propertyjogl.debug.GLDrawable.PerfStats
.
+ * Dedicates this instance's {@link GLContext} to the given thread.setExclusiveContextThread(null)
has been called.
+ *
+ * Default non-exclusive behavior is requested via setExclusiveContextThread(null)
,
+ * 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 null
.
+ *
+ * To release a previous made exclusive thread, a user issues setExclusiveContextThread(null)
+ * and may poll {@link #getExclusiveContextThread()} until it returns null
,
+ * while the exclusive thread is still running.
+ *
+ * Note: Setting a new exclusive thread without properly releasing a previous one + * will throw an GLException. + *
+ *+ * One scenario could be to dedicate the context to the {@link com.jogamp.opengl.util.AnimatorBase#getThread() animator thread} + * and spare redundant context switches. + *
+ * @param t the exclusive thread to claim the context, ornull
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
- * If destroyContext
is true
the context is destroyed in the end while holding the lock.
+ * If destroyContext
is true
the context is destroyed in the end while holding the lock.
+ *
+ * If destroyContext
is false
the context is natively released, i.e. released as often as locked before.
*