aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/com/jogamp/opengl
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2013-01-11 08:13:24 +0100
committerSven Gothel <[email protected]>2013-01-11 08:13:24 +0100
commit224fab1b2c71464826594740022fdcbe278867dc (patch)
treefb4292f8cd8b8fa2664c04005ba2ddde630d159c /src/jogl/classes/com/jogamp/opengl
parent6fd9c3d84e1758ae27cd10a89237a558460ca1fb (diff)
GLAutoDrawable/AnimatorBase: Add ExclusiveContextThread (ECT) feature; AnimatorBase: Add setModeBits/MODE_EXPECT_AWT_RENDERING_THREAD; FPSAnimator: Make transactions deterministic.
ExclusiveContextThread (ECT) allows user to dedicate a GLContext to a given thread. Only the ECT will be allowed to claim the GLContext, hence releasing must be done on the ECT itself. The core feature is accessible via GLAutoDrawable, while it can be conveniently enabled and disabled via an AnimatorBase implementation. The latter ensures it's being released on the ECT and waits for the result. Note that ECT cannot be guaranteed to work correctly w/ native (heavyweight) AWT components due to resource locking and AWT-EDT access. This is disabled in all new tests per default and noted on the API doc. Note: 'Animator transaction' == start(), stop(), pause(), resume(). - Add ExclusiveContextThread (ECT) feature - GLAutoDrawable NEW: - Thread setExclusiveContextThread(Thread t) - Thread getExclusiveContextThread() - AnimatorBase NEW: - Thread setExclusiveContext(Thread t) - boolean setExclusiveContext(boolean enable) - boolean isExclusiveContextEnabled() - Thread getExclusiveContextThread() - AnimatorBase: Add setModeBits/MODE_EXPECT_AWT_RENDERING_THREAD Allows user to pre-determine whether AWT rendering is expected before starting the animator. If AWT is excluded, a more simple and transaction correct impl. will be used. - FPSAnimator: Make transactions deterministic. FPSAnimator previously did not ensure whether a transaction was completed. A deterministic transaction is required to utilize ECT. FPSAnimator now uses same mechanism like Animator to ensure completeness, i.e. Condition and 'finishLifecycleAction(..)'. Both are moved to AnimatorBase. Tested manually on Linux/NV, Linux/AMD, Windows/NV and OSX/NV. - All new tests validated correctness. - All new tests shows an performance increase of ~3x w/ single GLWindow, where multiple GLWindows don't show a perf. increase.
Diffstat (limited to 'src/jogl/classes/com/jogamp/opengl')
-rw-r--r--src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java10
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/Animator.java187
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java458
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java307
4 files changed, 718 insertions, 244 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 &gt; <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();
+ } };
}