JOGL v2.6.0-rc-20250712
JOGL, High-Performance Graphics Binding for Java™ (public API).
FPSAnimator.java
Go to the documentation of this file.
1/*
2 * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
3 * Copyright (c) 2010 JogAmp Community. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * - Redistribution of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * - Redistribution in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * Neither the name of Sun Microsystems, Inc. or the names of
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * This software is provided "AS IS," without a warranty of any kind. ALL
21 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
22 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
23 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
24 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
25 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
26 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
27 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
28 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
29 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
30 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
31 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
32 *
33 * You acknowledge that this software is not designed or intended for use
34 * in the design, construction, operation or maintenance of any nuclear
35 * facility.
36 *
37 * Sun gratefully acknowledges that this software was originally authored
38 * and developed by Kenneth Bradley Russell and Christopher John Kline.
39 */
40package com.jogamp.opengl.util;
41
42import java.util.Timer;
43import java.util.TimerTask;
44
45import com.jogamp.opengl.GLAutoDrawable;
46import com.jogamp.opengl.GLException;
47import com.jogamp.opengl.GLProfile;
48import com.jogamp.common.ExceptionUtils;
49
50/**
51 * An Animator subclass which attempts to achieve a target
52 * frames-per-second rate to avoid using all CPU time. The target FPS
53 * is only an estimate and is not guaranteed.
54 * <p>
55 * The Animator execution thread does not run as a daemon thread,
56 * so it is able to keep an application from terminating.<br>
57 * Call {@link #stop() } to terminate the animation and it's execution thread.
58 * </p>
59 */
60public class FPSAnimator extends AnimatorBase {
61 private Timer timer = null;
62 private MainTask task = null;
63 private int fps;
64 private final boolean scheduleAtFixedRate;
65 private boolean isAnimating; // MainTask feedback
66 private volatile boolean pauseIssued; // MainTask trigger
67 private volatile boolean stopIssued; // MainTask trigger
68
69 @Override
70 protected String getBaseName(final String prefix) {
71 return "FPS" + prefix + "Animator" ;
72 }
73
74 /**
75 * Creates an FPSAnimator with a given target frames-per-second
76 * value. Equivalent to <code>FPSAnimator(null, fps)</code>.
77 * <p>
78 * This ctor variant expects an AWT rendering thread if AWT is available.
79 * </p>
80 * @see AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD
81 * @see #FPSAnimator(int, GLAutoDrawable, int, boolean)
82 * @see GLProfile#isAWTAvailable()
83 * @see AnimatorBase#setModeBits(boolean, int)
84 */
85 public FPSAnimator(final int fps) {
86 this(MODE_EXPECT_AWT_RENDERING_THREAD, null, fps, false);
87 }
88
89 /**
90 * Creates an FPSAnimator with modeBits, see {@link AnimatorBase#AnimatorBase(int)}
91 * and a given target frames-per-second value.
92 *
93 * @see AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD
94 * @see #FPSAnimator(int, GLAutoDrawable, int, boolean)
95 * @see GLProfile#isAWTAvailable()
96 * @see AnimatorBase#setModeBits(boolean, int)
97 */
98 public FPSAnimator(final int modeBits, final int fps) {
99 this(modeBits, null, fps, false);
100 }
101
102 /**
103 * Creates an FPSAnimator with a given target frames-per-second
104 * value and a flag indicating whether to use fixed-rate
105 * scheduling. Equivalent to <code>FPSAnimator(null, fps,
106 * scheduleAtFixedRate)</code>.
107 * <p>
108 * This ctor variant expects an AWT rendering thread if AWT is available.
109 * </p>
110 * @see AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD
111 * @see #FPSAnimator(int, GLAutoDrawable, int, boolean)
112 * @see GLProfile#isAWTAvailable()
113 * @see AnimatorBase#setModeBits(boolean, int)
114 */
115 public FPSAnimator(final int fps, final boolean scheduleAtFixedRate) {
116 this(MODE_EXPECT_AWT_RENDERING_THREAD, null, fps, scheduleAtFixedRate);
117 }
118
119 /**
120 * Creates an FPSAnimator with a given target frames-per-second
121 * value and an initial drawable to animate. Equivalent to
122 * <code>FPSAnimator(null, fps, false)</code>.
123 * <p>
124 * This ctor variant expects an AWT rendering thread if AWT is available.
125 * </p>
126 * @see AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD
127 * @see #FPSAnimator(int, GLAutoDrawable, int, boolean)
128 * @see GLProfile#isAWTAvailable()
129 * @see AnimatorBase#setModeBits(boolean, int)
130 */
131 public FPSAnimator(final GLAutoDrawable drawable, final int fps) {
132 this(MODE_EXPECT_AWT_RENDERING_THREAD, drawable, fps, false);
133 }
134
135 /**
136 * Creates an FPSAnimator with a given target frames-per-second
137 * value, an initial drawable to animate, and a flag indicating
138 * whether to use fixed-rate scheduling.
139 * <p>
140 * This ctor variant expects an AWT rendering thread if AWT is available.
141 * </p>
142 * @see AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD
143 * @see #FPSAnimator(int, GLAutoDrawable, int, boolean)
144 * @see GLProfile#isAWTAvailable()
145 * @see AnimatorBase#setModeBits(boolean, int)
146 */
147 public FPSAnimator(final GLAutoDrawable drawable, final int fps, final boolean scheduleAtFixedRate) {
148 this(MODE_EXPECT_AWT_RENDERING_THREAD, drawable, fps, scheduleAtFixedRate);
149 }
150
151 /**
152 * Creates an FPSAnimator with modeBits, see {@link AnimatorBase#AnimatorBase(int)}, a given target frames-per-second
153 * value, an initial drawable to animate, and a flag indicating
154 * whether to use fixed-rate scheduling.
155 *
156 * @param modeBits pass {@link AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD} if an AWT rendering thread is expected, otherwise {@code 0}.
157 * @param drawable {@link #add(GLAutoDrawable) added} {@link GLAutoDrawable} or {@code null}
158 * @param fps target frames per seconds
159 * @param scheduleAtFixedRate flag indicating fixed rate scheduling
160 * @see AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD
161 * @see #FPSAnimator(int, GLAutoDrawable, int, boolean)
162 * @see GLProfile#isAWTAvailable()
163 * @see AnimatorBase#setModeBits(boolean, int)
164 */
165 public FPSAnimator(final int modeBits, final GLAutoDrawable drawable, final int fps, final boolean scheduleAtFixedRate) {
166 super(modeBits);
167 this.fps = fps;
168 if (drawable != null) {
169 add(drawable);
170 }
171 this.scheduleAtFixedRate = scheduleAtFixedRate;
172 }
173
174 /**
175 * @param fps
176 * @throws GLException if the animator has already been started
177 */
178 public final void setFPS(final int fps) throws GLException {
179 if ( isStarted() ) {
180 throw new GLException("Animator already started.");
181 }
182 this.fps = fps;
183 }
184 public final int getFPS() { return fps; }
185
186 class MainTask extends TimerTask {
187 private boolean justStarted;
188 private boolean alreadyStopped;
189 private boolean alreadyPaused;
190
191 public MainTask() {
192 }
193
194 public void start(final Timer timer) {
195 fpsCounter.resetFPSCounter();
196 pauseIssued = false;
197 stopIssued = false;
198 isAnimating = false;
199
200 justStarted = true;
201 alreadyStopped = false;
202 alreadyPaused = false;
203
204 final long period = 0 < fps ? (long) (1000.0f / fps) : 1; // 0 -> 1: IllegalArgumentException: Non-positive period
205 if (scheduleAtFixedRate) {
206 timer.scheduleAtFixedRate(this, 0, period);
207 } else {
208 timer.schedule(this, 0, period);
209 }
210 }
211
212 public boolean isActive() { return !alreadyStopped && !alreadyPaused; }
213
214 @Override
215 public final String toString() {
216 return "Task[thread "+animThread+", stopped "+alreadyStopped+", paused "+alreadyPaused+" pauseIssued "+pauseIssued+", stopIssued "+stopIssued+" -- started "+isStarted()+", animating "+isAnimatingImpl()+", paused "+isPaused()+", drawable "+drawables.size()+", drawablesEmpty "+drawablesEmpty+"]";
217 }
218
219 @Override
220 public void run() {
221 UncaughtAnimatorException caughtException = null;
222
223 if( justStarted ) {
224 justStarted = false;
225 synchronized (FPSAnimator.this) {
226 animThread = Thread.currentThread();
227 if(DEBUG) {
228 System.err.println("FPSAnimator start/resume:" + Thread.currentThread() + ": " + toString());
229 }
230 isAnimating = true;
231 if( drawablesEmpty ) {
232 pauseIssued = true; // isAnimating:=false @ pause below
233 } else {
234 pauseIssued = false;
235 setDrawablesExclCtxState(exclusiveContext); // may re-enable exclusive context
236 }
237 FPSAnimator.this.notifyAll(); // Wakes up 'waitForStartedCondition' sync -and resume from pause or drawablesEmpty
238 if(DEBUG) {
239 System.err.println("FPSAnimator P1:" + Thread.currentThread() + ": " + toString());
240 }
241 }
242 }
243 if( !pauseIssued && !stopIssued ) { // RUN
244 try {
245 display();
246 } catch (final UncaughtAnimatorException dre) {
247 caughtException = dre;
248 stopIssued = true;
249 }
250 } else if( pauseIssued && !stopIssued ) { // PAUSE
251 if(DEBUG) {
252 System.err.println("FPSAnimator pausing: "+alreadyPaused+", "+ Thread.currentThread() + ": " + toString());
253 }
254 this.cancel();
255
256 if( !alreadyPaused ) { // PAUSE
257 alreadyPaused = true;
260 try {
261 display(); // propagate exclusive context -> off!
262 } catch (final UncaughtAnimatorException dre) {
263 caughtException = dre;
264 stopIssued = true;
265 }
266 }
267 if( null == caughtException ) {
268 synchronized (FPSAnimator.this) {
269 if(DEBUG) {
270 System.err.println("FPSAnimator pause " + Thread.currentThread() + ": " + toString());
271 }
272 isAnimating = false;
273 FPSAnimator.this.notifyAll();
274 }
275 }
276 }
277 }
278 if( stopIssued ) { // STOP incl. immediate exception handling of 'displayCaught'
279 if(DEBUG) {
280 System.err.println("FPSAnimator stopping: "+alreadyStopped+", "+ Thread.currentThread() + ": " + toString());
281 }
282 this.cancel();
283
284 if( !alreadyStopped ) {
285 alreadyStopped = true;
288 try {
289 display(); // propagate exclusive context -> off!
290 } catch (final UncaughtAnimatorException dre) {
291 if( null == caughtException ) {
292 caughtException = dre;
293 } else {
294 System.err.println("FPSAnimator.setExclusiveContextThread: caught: "+dre.getMessage());
295 dre.printStackTrace();
296 }
297 }
298 }
299 boolean flushGLRunnables = false;
300 boolean throwCaughtException = false;
301 synchronized (FPSAnimator.this) {
302 if(DEBUG) {
303 System.err.println("FPSAnimator stop " + Thread.currentThread() + ": " + toString());
304 if( null != caughtException ) {
305 System.err.println("Animator caught: "+caughtException.getMessage());
306 caughtException.printStackTrace();
307 }
308 }
309 isAnimating = false;
310 if( null != caughtException ) {
311 flushGLRunnables = true;
312 throwCaughtException = !handleUncaughtException(caughtException);
313 }
314 animThread = null;
315 FPSAnimator.this.notifyAll();
316 }
317 if( flushGLRunnables ) {
319 }
320 if( throwCaughtException ) {
321 throw caughtException;
322 }
323 }
324 }
325 }
326 }
327 private final boolean isAnimatingImpl() {
328 return animThread != null && isAnimating ;
329 }
330 @Override
331 public final synchronized boolean isAnimating() {
332 return animThread != null && isAnimating ;
333 }
334
335 @Override
336 public final synchronized boolean isPaused() {
337 return animThread != null && pauseIssued;
338 }
339
340 static int timerNo = 0;
341
342 @Override
343 public final synchronized boolean start() {
344 if ( null != timer || null != task || isStarted() ) {
345 return false;
346 }
347 timer = new Timer( getThreadName()+"-"+baseName+"-Timer"+(timerNo++) );
348 task = new MainTask();
349 if(DEBUG) {
350 System.err.println("FPSAnimator.start() START: "+task+", "+ Thread.currentThread() + ": " + toString());
351 }
352 task.start(timer);
353
354 final boolean res = finishLifecycleAction( drawablesEmpty ? waitForStartedEmptyCondition : waitForStartedAddedCondition,
356 if(DEBUG) {
357 System.err.println("FPSAnimator.start() END: "+task+", "+ Thread.currentThread() + ": " + toString());
358 }
359 if( drawablesEmpty ) {
360 task.cancel();
361 task = null;
362 }
363 return res;
364 }
365 private final Condition waitForStartedAddedCondition = new Condition() {
366 @Override
367 public boolean eval() {
368 return !isStarted() || !isAnimating ;
369 } };
370 private final Condition waitForStartedEmptyCondition = new Condition() {
371 @Override
372 public boolean eval() {
373 return !isStarted() || isAnimating ;
374 } };
375
376 /** Stops this FPSAnimator. Due to the implementation of the
377 FPSAnimator it is not guaranteed that the FPSAnimator will be
378 completely stopped by the time this method returns. */
379 @Override
380 public final synchronized boolean stop() {
381 if ( null == timer || !isStarted() ) {
382 return false;
383 }
384 if(DEBUG) {
385 System.err.println("FPSAnimator.stop() START: "+task+", "+ Thread.currentThread() + ": " + toString());
386 }
387 final boolean res;
388 if( null == task ) {
389 // start/resume case w/ drawablesEmpty
390 res = true;
391 } else {
392 stopIssued = true;
394 }
395
396 if(DEBUG) {
397 System.err.println("FPSAnimator.stop() END: "+task+", "+ Thread.currentThread() + ": " + toString());
398 }
399 if(null != task) {
400 task.cancel();
401 task = null;
402 }
403 if(null != timer) {
404 timer.cancel();
405 timer = null;
406 }
407 animThread = null;
408 return res;
409 }
410 private final Condition waitForStoppedCondition = new Condition() {
411 @Override
412 public boolean eval() {
413 return isStarted();
414 } };
415
416 @Override
417 public final synchronized boolean pause() {
418 if ( !isStarted() || pauseIssued ) {
419 return false;
420 }
421 if(DEBUG) {
422 System.err.println("FPSAnimator.pause() START: "+task+", "+ Thread.currentThread() + ": " + toString());
423 }
424 final boolean res;
425 if( null == task ) {
426 // start/resume case w/ drawablesEmpty
427 res = true;
428 } else {
429 pauseIssued = true;
431 }
432
433 if(DEBUG) {
434 System.err.println("FPSAnimator.pause() END: "+task+", "+ Thread.currentThread() + ": " + toString());
435 }
436 if(null != task) {
437 task.cancel();
438 task = null;
439 }
440 return res;
441 }
442 private final Condition waitForPausedCondition = new Condition() {
443 @Override
444 public boolean eval() {
445 // end waiting if stopped as well
446 return isStarted() && isAnimating;
447 } };
448
449 @Override
450 public final synchronized boolean resume() {
451 if ( !isStarted() || !pauseIssued ) {
452 return false;
453 }
454 if(DEBUG) {
455 System.err.println("FPSAnimator.resume() START: "+ Thread.currentThread() + ": " + toString());
456 }
457 final boolean res;
458 if( drawablesEmpty ) {
459 res = true;
460 } else {
461 if( null != task ) {
462 if( DEBUG ) {
463 System.err.println("FPSAnimator.resume() Ops: !pauseIssued, but task != null: "+toString());
464 ExceptionUtils.dumpStack(System.err);
465 }
466 task.cancel();
467 task = null;
468 }
469 task = new MainTask();
470 task.start(timer);
472 }
473 if(DEBUG) {
474 System.err.println("FPSAnimator.resume() END: "+task+", "+ Thread.currentThread() + ": " + toString());
475 }
476 return res;
477 }
478 private final Condition waitForResumeCondition = new Condition() {
479 @Override
480 public boolean eval() {
481 // end waiting if stopped as well
482 return !drawablesEmpty && !isAnimating && isStarted();
483 } };
484}
A generic exception for OpenGL errors used throughout the binding as a substitute for RuntimeExceptio...
Base implementation of GLAnimatorControl
final synchronized void setDrawablesExclCtxState(final boolean enable)
Should be called at start() and stop() from within the animator thread.
static final int MODE_EXPECT_AWT_RENDERING_THREAD
If present in modeBits field and AWT is available, implementation is aware of the AWT EDT,...
final void display()
Called every frame to cause redrawing of all of the GLAutoDrawables this Animator manages.
final void flushGLRunnables()
Should be called in case of an uncaught exception from within the animator thread to flush all animat...
final synchronized boolean handleUncaughtException(final UncaughtAnimatorException ue)
Should be called in case of an uncaught exception from within the animator thread,...
ArrayList< GLAutoDrawable > drawables
final synchronized boolean finishLifecycleAction(final Condition waitCondition, long pollPeriod)
final synchronized void add(final GLAutoDrawable drawable)
Adds a drawable to this animator's list of rendering drawables.
static final long POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION
synchronized boolean isStarted()
Indicates whether this animator has been started.
An Animator subclass which attempts to achieve a target frames-per-second rate to avoid using all CPU...
FPSAnimator(final int modeBits, final int fps)
Creates an FPSAnimator with modeBits, see AnimatorBase#AnimatorBase(int) and a given target frames-pe...
final synchronized boolean isPaused()
Indicates whether this animator is started and either manually paused or paused automatically due to ...
FPSAnimator(final GLAutoDrawable drawable, final int fps, final boolean scheduleAtFixedRate)
Creates an FPSAnimator with a given target frames-per-second value, an initial drawable to animate,...
FPSAnimator(final int fps, final boolean scheduleAtFixedRate)
Creates an FPSAnimator with a given target frames-per-second value and a flag indicating whether to u...
String getBaseName(final String prefix)
final synchronized boolean start()
Starts this animator, if not running.
FPSAnimator(final int fps)
Creates an FPSAnimator with a given target frames-per-second value.
FPSAnimator(final GLAutoDrawable drawable, final int fps)
Creates an FPSAnimator with a given target frames-per-second value and an initial drawable to animate...
final synchronized boolean pause()
Pauses this animator.
FPSAnimator(final int modeBits, final GLAutoDrawable drawable, final int fps, final boolean scheduleAtFixedRate)
Creates an FPSAnimator with modeBits, see AnimatorBase#AnimatorBase(int), a given target frames-per-s...
final synchronized boolean isAnimating()
Indicates whether this animator is started and is not paused.
final void setFPS(final int fps)
final synchronized boolean resume()
Resumes animation if paused.
final synchronized boolean stop()
Stops this FPSAnimator.
A higher-level abstraction than GLDrawable which supplies an event based mechanism (GLEventListener) ...