Jogamp
Bug 1039 - Specify behavior of GLEventListener Exceptions occurring while GLAutoDrawa...
authorSven Gothel <sgothel@jausoft.com>
Wed, 6 Aug 2014 03:59:08 +0000 (05:59 +0200)
committerSven Gothel <sgothel@jausoft.com>
Wed, 6 Aug 2014 03:59:08 +0000 (05:59 +0200)
Add GLAnimatorControl.UncaughtGLAnimatorExceptionHandler interface to optionally handle
uncaught exception within an animator thread by the user.

Implementation also requires to flush all enqueued GLRunnable instances
via GLAutoDrawable.invoked(..) in case such exception occurs.
Hence 'GLAutoDrawable.flushGLRunnables()' has been added.

Only subsequent exceptions, which cannot be thrown are dumped to System.stderr.

+++

Handling of exceptions during dispose()

Exception in NEWT's disposeGL*() are also caught and re-thrown after
the NEWT window has been destroyed in WindowImpl.destroyAction:

  - GLEventListener.dispose(..)
    - GLDrawableHelper.disposeAllGLEventListener(..)
      - GLDrawableHelper.disposeGL(..)
        - GLAutoDrawableBase.destroyImplInLock(..)
          - GLWindow.GLLifecycleHook.destroyActionInLock(..)
            - WindowImpl.destroyAction on NEWT-EDT
              - WindowImpl.destroy

Further more, exceptions occuring in native windowing toolkit triggered destroy()
are ignored:
  - GLAutoDrawableBase.defaultWindowDestroyNotifyOp(..)

It has to be seen whether such exception handling for
dispose() shall be added to AWT/SWT.

+++

TestGLException01NEWT covers all GLEventListener exception cases
on-thread and off-thread (via animator).

+++

18 files changed:
make/scripts/tests.sh
src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java
src/jogl/classes/com/jogamp/opengl/util/AWTAnimatorImpl.java
src/jogl/classes/com/jogamp/opengl/util/Animator.java
src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java
src/jogl/classes/com/jogamp/opengl/util/DefaultAnimatorImpl.java
src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java
src/jogl/classes/javax/media/opengl/GLAnimatorControl.java
src/jogl/classes/javax/media/opengl/GLAutoDrawable.java
src/jogl/classes/javax/media/opengl/GLDrawable.java
src/jogl/classes/javax/media/opengl/GLException.java
src/jogl/classes/javax/media/opengl/awt/GLCanvas.java
src/jogl/classes/javax/media/opengl/awt/GLJPanel.java
src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java
src/jogl/classes/jogamp/opengl/GLDrawableHelper.java
src/jogl/classes/jogamp/opengl/GLRunnableTask.java
src/newt/classes/jogamp/newt/WindowImpl.java
src/test/com/jogamp/opengl/test/junit/jogl/acore/TestGLException01NEWT.java

index bce9a69..7749ce3 100644 (file)
@@ -149,6 +149,7 @@ function jrun() {
     #D_ARGS="-Djogl.debug.GLContext -Djogl.debug.GLDrawable"
     #D_ARGS="-Djogl.debug.GLContext.NoProfileAliasing"
     #D_ARGS="-Djogl.debug.GLDrawable -Dnativewindow.debug.X11Util -Dnativewindow.debug.NativeWindow -Dnewt.debug.Display -Dnewt.debug.Screen -Dnewt.debug.Window"
+    #D_ARGS="-Djogl.debug.Animator"
     #D_ARGS="-Djogl.debug.Animator -Djogl.debug.GLDrawable -Dnativewindow.debug.NativeWindow"
     #D_ARGS="-Djogl.debug=all -Dnewt.debug=all"
     #D_ARGS="-Djogl.debug.GLDrawable -Djogl.debug.GLContext -Djogl.debug.GLCanvas"
index c409bb2..e33ceee 100644 (file)
@@ -789,6 +789,11 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS
    }
 
    @Override
+   public void flushGLRunnables() {
+       helper.flushGLRunnables();
+   }
+
+   @Override
    public void setAnimator(final GLAnimatorControl arg0) throws GLException {
       helper.setAnimator(arg0);
    }
index 496fb88..62df3fa 100644 (file)
@@ -41,12 +41,14 @@ import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+
 import javax.swing.JComponent;
 import javax.swing.RepaintManager;
 import javax.swing.SwingUtilities;
-
 import javax.media.opengl.GLAutoDrawable;
 
+import com.jogamp.opengl.util.AnimatorBase.UncaughtAnimatorException;
+
 /** Abstraction to factor out AWT dependencies from the Animator's
     implementation in a way that still allows the FPSAnimator to pick
     up this behavior if desired. */
@@ -61,7 +63,7 @@ class AWTAnimatorImpl implements AnimatorBase.AnimatorImpl {
     @Override
     public void display(final ArrayList<GLAutoDrawable> drawables,
                         final boolean ignoreExceptions,
-                        final boolean printExceptions) {
+                        final boolean printExceptions) throws UncaughtAnimatorException {
         for (int i=0; i<drawables.size(); i++) {
             final GLAutoDrawable drawable = drawables.get(i);
             if (drawable instanceof JComponent) {
@@ -73,13 +75,13 @@ class AWTAnimatorImpl implements AnimatorBase.AnimatorImpl {
             } else {
                 try {
                     drawable.display();
-                } catch (final RuntimeException e) {
+                } catch (final Throwable t) {
                     if (ignoreExceptions) {
                         if (printExceptions) {
-                            e.printStackTrace();
+                            t.printStackTrace();
                         }
                     } else {
-                        throw(e);
+                        throw new UncaughtAnimatorException(drawable, t);
                     }
                 }
             }
index 634e833..d9a9571 100644 (file)
@@ -132,7 +132,7 @@ public class Animator extends AnimatorBase {
 
         @Override
         public void run() {
-            GLException displayCaught = null;
+            UncaughtAnimatorException displayCaught = null;
 
             try {
                 synchronized (Animator.this) {
@@ -161,9 +161,9 @@ public class Animator extends AnimatorBase {
                                 ectCleared = true;
                                 setDrawablesExclCtxState(false);
                                 try {
-                                    display(); // propagate exclusive change!
-                                } catch (final Throwable t) {
-                                    displayCaught = GLException.newGLException(t);
+                                    display(); // propagate exclusive context -> off!
+                                } catch (final UncaughtAnimatorException dre) {
+                                    displayCaught = dre;
                                     stopIssued = true;
                                     isAnimating = false;
                                     break; // end pause loop
@@ -189,15 +189,15 @@ public class Animator extends AnimatorBase {
                             // Resume from pause or drawablesEmpty,
                             // implies !pauseIssued and !drawablesEmpty
                             isAnimating = true;
-                            setDrawablesExclCtxState(exclusiveContext);
+                            setDrawablesExclCtxState(exclusiveContext); // may re-enable exclusive context
                             Animator.this.notifyAll();
                         }
                     } // sync Animator.this
                     if (!stopIssued) {
                         try {
                             display();
-                        } catch (final Throwable t) {
-                            displayCaught = GLException.newGLException(t);
+                        } catch (final UncaughtAnimatorException dre) {
+                            displayCaught = dre;
                             stopIssued = true;
                             isAnimating = false;
                             break; // end animation loop
@@ -216,14 +216,13 @@ public class Animator extends AnimatorBase {
             } finally {
                 if( exclusiveContext && !drawablesEmpty ) {
                     setDrawablesExclCtxState(false);
-                    display(); // propagate exclusive change!
                     try {
-                        display(); // propagate exclusive change!
-                    } catch (final Throwable t) {
+                        display(); // propagate exclusive context -> off!
+                    } catch (final UncaughtAnimatorException dre) {
                         if( null == displayCaught ) {
-                            displayCaught = GLException.newGLException(t);
+                            displayCaught = dre;
                         } else {
-                            GLException.newGLException(t).printStackTrace();
+                            dre.printStackTrace();
                         }
                     }
                 }
@@ -237,12 +236,12 @@ public class Animator extends AnimatorBase {
                     }
                     stopIssued = false;
                     pauseIssued = false;
-                    animThread = null;
                     isAnimating = false;
-                    Animator.this.notifyAll();
                     if( null != displayCaught ) {
-                        throw displayCaught;
+                        handleUncaughtException(displayCaught);
                     }
+                    animThread = null;
+                    Animator.this.notifyAll();
                 }
             }
         }
index a5bdb93..ed23a78 100644 (file)
@@ -67,8 +67,25 @@ public abstract class AnimatorBase implements GLAnimatorControl {
      */
     public static final int MODE_EXPECT_AWT_RENDERING_THREAD = 1 << 0;
 
-    public interface AnimatorImpl {
-        void display(ArrayList<GLAutoDrawable> drawables, boolean ignoreExceptions, boolean printExceptions);
+
+    @SuppressWarnings("serial")
+    public static class UncaughtAnimatorException extends RuntimeException {
+        final GLAutoDrawable drawable;
+        public UncaughtAnimatorException(final GLAutoDrawable drawable, final Throwable cause) {
+            super(cause);
+            this.drawable = drawable;
+        }
+        public final GLAutoDrawable getGLAutoDrawable() { return drawable; }
+    }
+
+    public static interface AnimatorImpl {
+        /**
+         * @param drawables
+         * @param ignoreExceptions
+         * @param printExceptions
+         * @throws UncaughtAnimatorException as caused by {@link GLAutoDrawable#display()}
+         */
+        void display(ArrayList<GLAutoDrawable> drawables, boolean ignoreExceptions, boolean printExceptions) throws UncaughtAnimatorException;
         boolean blockUntilDone(Thread thread);
     }
 
@@ -83,6 +100,7 @@ public abstract class AnimatorBase implements GLAnimatorControl {
     protected boolean printExceptions;
     protected boolean exclusiveContext;
     protected Thread userExclusiveContextThread;
+    protected UncaughtGLAnimatorExceptionHandler uncaughtExceptionHandler;
     protected FPSCounterImpl fpsCounter = new FPSCounterImpl();
 
     private final static Class<?> awtAnimatorImplClazz;
@@ -313,11 +331,16 @@ public abstract class AnimatorBase implements GLAnimatorControl {
             }
         }
         final Thread dECT = enable ? ( null != _exclusiveContextThread ? _exclusiveContextThread : animThread ) : null ;
+        UncaughtAnimatorException displayCaught = null;
         if( propagateState ) {
             setDrawablesExclCtxState(enable);
             if( !enable ) {
                 if( Thread.currentThread() == getThread() || Thread.currentThread() == _exclusiveContextThread ) {
-                    display();
+                    try {
+                        display(); // propagate exclusive context -> off!
+                    } catch (final UncaughtAnimatorException dre) {
+                        displayCaught = dre;
+                    }
                 } else {
                     final boolean resumed = isAnimating() ? false : resume();
                     int counter = 10;
@@ -338,6 +361,13 @@ public abstract class AnimatorBase implements GLAnimatorControl {
         }
         if(DEBUG) {
             System.err.println("AnimatorBase.setExclusiveContextThread: all-GLAD Ok: "+validateDrawablesExclCtxState(dECT)+", "+this);
+            if( null != displayCaught ) {
+                System.err.println("AnimatorBase.setExclusiveContextThread: caught: "+displayCaught.getMessage());
+                displayCaught.printStackTrace();
+            }
+        }
+        if( null != displayCaught ) {
+            throw displayCaught;
         }
         return oldExclusiveContext;
     }
@@ -412,7 +442,7 @@ public abstract class AnimatorBase implements GLAnimatorControl {
         this to get the most optimized painting behavior for the set of
         components this Animator manages, in particular when multiple
         lightweight widgets are continually being redrawn. */
-    protected final void display() {
+    protected final void display() throws UncaughtAnimatorException {
         impl.display(drawables, ignoreExceptions, printExceptions);
         fpsCounter.tickFPS();
     }
@@ -482,7 +512,43 @@ public abstract class AnimatorBase implements GLAnimatorControl {
         this.printExceptions = printExceptions;
     }
 
-    protected interface Condition {
+    @Override
+    public final UncaughtGLAnimatorExceptionHandler getUncaughtExceptionHandler() {
+        return uncaughtExceptionHandler;
+    }
+
+    @Override
+    public final void setUncaughtExceptionHandler(final UncaughtGLAnimatorExceptionHandler handler) {
+        uncaughtExceptionHandler = handler;
+    }
+
+    /**
+     * Should be called in case of an uncaught exception
+     * from within the animator thread to flush all animator
+     */
+    protected final synchronized void handleUncaughtException(final UncaughtAnimatorException ue) {
+        if( null != uncaughtExceptionHandler ) {
+            try {
+                uncaughtExceptionHandler.uncaughtException(this, ue.getGLAutoDrawable(), ue.getCause());
+            } catch (final Throwable t) { /* ignore intentionally */ }
+            flushGLRunnables();
+        } else {
+            flushGLRunnables();
+            throw ue;
+        }
+    }
+
+    /**
+     * Should be called in case of an uncaught exception
+     * from within the animator thread to flush all animator
+     */
+    protected final synchronized void flushGLRunnables() {
+        for (int i=0; i<drawables.size(); i++) {
+            drawables.get(i).flushGLRunnables();
+        }
+    }
+
+    protected static interface Condition {
         /**
          * @return true if branching (continue waiting, action), otherwise false
          */
index 7fa4011..6b1485a 100644 (file)
 package com.jogamp.opengl.util;
 
 import java.util.ArrayList;
+
 import javax.media.opengl.GLAutoDrawable;
 
+import com.jogamp.opengl.util.AnimatorBase.UncaughtAnimatorException;
+
 /** Abstraction to factor out AWT dependencies from the Animator's
     implementation in a way that still allows the FPSAnimator to pick
     up this behavior if desired. */
@@ -44,18 +47,18 @@ class DefaultAnimatorImpl implements AnimatorBase.AnimatorImpl {
     @Override
     public void display(final ArrayList<GLAutoDrawable> drawables,
                         final boolean ignoreExceptions,
-                        final boolean printExceptions) {
+                        final boolean printExceptions) throws UncaughtAnimatorException {
         for (int i=0; i<drawables.size(); i++) {
             final GLAutoDrawable drawable = drawables.get(i);
             try {
                 drawable.display();
-            } catch (final RuntimeException e) {
+            } catch (final Throwable t) {
                 if (ignoreExceptions) {
                     if (printExceptions) {
-                        e.printStackTrace();
+                        t.printStackTrace();
                     }
                 } else {
-                    throw(e);
+                    throw new UncaughtAnimatorException(drawable, t);
                 }
             }
         }
index 746b642..9ae8804 100644 (file)
@@ -148,6 +148,8 @@ public class FPSAnimator extends AnimatorBase {
 
         @Override
         public void run() {
+            UncaughtAnimatorException displayCaught = null;
+
             if( justStarted ) {
                 justStarted = false;
                 synchronized (FPSAnimator.this) {
@@ -160,7 +162,7 @@ public class FPSAnimator extends AnimatorBase {
                         shouldRun = false; // isAnimating:=false @ pause below
                     } else {
                         shouldRun = true;
-                        setDrawablesExclCtxState(exclusiveContext);
+                        setDrawablesExclCtxState(exclusiveContext); // may re-enable exclusive context
                         FPSAnimator.this.notifyAll();
                     }
                     if(DEBUG) {
@@ -168,46 +170,76 @@ public class FPSAnimator extends AnimatorBase {
                     }
                 }
             }
-            if( shouldRun ) {
-                display();
-            } else if( shouldStop ) { // STOP
+            if( shouldRun && !shouldStop ) { // RUN
+                try {
+                    display();
+                } catch (final UncaughtAnimatorException dre) {
+                    displayCaught = dre;
+                    shouldRun = false;
+                    shouldStop = true;
+                }
+            } else if( !shouldRun && !shouldStop ) { // PAUSE
                 if(DEBUG) {
-                    System.err.println("FPSAnimator P4: "+alreadyStopped+", "+ Thread.currentThread() + ": " + toString());
+                    System.err.println("FPSAnimator pausing: "+alreadyPaused+", "+ Thread.currentThread() + ": " + toString());
                 }
                 this.cancel();
 
-                if( !alreadyStopped ) {
-                    alreadyStopped = true;
+                if( !alreadyPaused ) { // PAUSE
+                    alreadyPaused = true;
                     if( exclusiveContext && !drawablesEmpty ) {
                         setDrawablesExclCtxState(false);
-                        display(); // propagate exclusive change!
+                        try {
+                            display(); // propagate exclusive context -> off!
+                        } catch (final UncaughtAnimatorException dre) {
+                            displayCaught = dre;
+                            shouldRun = false;
+                            shouldStop = true;
+                        }
                     }
-                    synchronized (FPSAnimator.this) {
-                        if(DEBUG) {
-                            System.err.println("FPSAnimator stop " + Thread.currentThread() + ": " + toString());
+                    if( null == displayCaught ) {
+                        synchronized (FPSAnimator.this) {
+                            if(DEBUG) {
+                                System.err.println("FPSAnimator pause " + Thread.currentThread() + ": " + toString());
+                            }
+                            isAnimating = false;
+                            FPSAnimator.this.notifyAll();
                         }
-                        animThread = null;
-                        isAnimating = false;
-                        FPSAnimator.this.notifyAll();
                     }
                 }
-            } else {
+            }
+            if( shouldStop ) { // STOP
                 if(DEBUG) {
-                    System.err.println("FPSAnimator P5: "+alreadyPaused+", "+ Thread.currentThread() + ": " + toString());
+                    System.err.println("FPSAnimator stopping: "+alreadyStopped+", "+ Thread.currentThread() + ": " + toString());
                 }
                 this.cancel();
 
-                if( !alreadyPaused ) { // PAUSE
-                    alreadyPaused = true;
+                if( !alreadyStopped ) {
+                    alreadyStopped = true;
                     if( exclusiveContext && !drawablesEmpty ) {
                         setDrawablesExclCtxState(false);
-                        display(); // propagate exclusive change!
+                        try {
+                            display(); // propagate exclusive context -> off!
+                        } catch (final UncaughtAnimatorException dre) {
+                            if( null == displayCaught ) {
+                                displayCaught = dre;
+                            } else {
+                                dre.printStackTrace();
+                            }
+                        }
                     }
                     synchronized (FPSAnimator.this) {
                         if(DEBUG) {
-                            System.err.println("FPSAnimator pause " + Thread.currentThread() + ": " + toString());
+                            System.err.println("FPSAnimator stop " + Thread.currentThread() + ": " + toString());
+                            if( null != displayCaught ) {
+                                System.err.println("AnimatorBase.setExclusiveContextThread: caught: "+displayCaught.getMessage());
+                                displayCaught.printStackTrace();
+                            }
                         }
                         isAnimating = false;
+                        if( null != displayCaught ) {
+                            handleUncaughtException(displayCaught);
+                        }
+                        animThread = null;
                         FPSAnimator.this.notifyAll();
                     }
                 }
index 8271456..50f7e9b 100644 (file)
@@ -33,6 +33,38 @@ package javax.media.opengl;
  * which implementation may drive a {@link javax.media.opengl.GLAutoDrawable} animation.
  */
 public interface GLAnimatorControl extends FPSCounter {
+    /**
+     * A {@link GLAnimatorControl#setUncaughtExceptionHandler(UncaughtGLAnimatorExceptionHandler) registered}
+     * {@link UncaughtGLAnimatorExceptionHandler} instance is invoked when an {@link GLAnimatorControl animator} abruptly {@link #stop() stops}
+     * due to an uncaught exception from one of its {@link GLAutoDrawable}s.
+     * @see #uncaughtException(GLAnimatorControl, GLAutoDrawable, Throwable)
+     * @see GLAnimatorControl#setUncaughtExceptionHandler(UncaughtGLAnimatorExceptionHandler)
+     * @since 2.2
+     */
+    public static interface UncaughtGLAnimatorExceptionHandler {
+        /**
+         * Method invoked when the given {@link GLAnimatorControl} is {@link GLAnimatorControl#stop() stopped} due to the
+         * given uncaught exception happened on the given {@link GLAutoDrawable}.
+         * <p>
+         * The animator thread can still be retrieved via {@link GLAnimatorControl#getThread()}.
+         * </p>
+         * <p>
+         * All {@link GLAnimatorControl} states already reflect its stopped state.
+         * </p>
+         * <p>
+         * After this handler method is called, the {@link GLAnimatorControl} is stopped.
+         * </p>
+         * <p>
+         * Any exception thrown by this method will be ignored.
+         * </p>
+         * @param animator the {@link GLAnimatorControl}
+         * @param drawable the causing {@link GLAutoDrawable}
+         * @param cause the uncaught exception
+         * @see GLAnimatorControl#setUncaughtExceptionHandler(UncaughtGLAnimatorExceptionHandler)
+         * @since 2.2
+         */
+        void uncaughtException(final GLAnimatorControl animator, final GLAutoDrawable drawable, final Throwable cause);
+    }
 
     /**
      * Indicates whether this animator has been {@link #start() started}.
@@ -181,4 +213,24 @@ public interface GLAnimatorControl extends FPSCounter {
      * @throws IllegalArgumentException if drawable was not added to this animator
      */
     void remove(GLAutoDrawable drawable);
+
+    /**
+     * Returns the {@link UncaughtGLAnimatorExceptionHandler} invoked when this {@link GLAnimatorControl animator} abruptly {@link #stop() stops}
+     * due to an uncaught exception from one of its {@link GLAutoDrawable}s.
+     * <p>
+     * Default is <code>null</code>.
+     * </p>
+     * @since 2.2
+     */
+    UncaughtGLAnimatorExceptionHandler getUncaughtExceptionHandler();
+
+    /**
+     * Set the handler invoked when this {@link GLAnimatorControl animator} abruptly {@link #stop() stops}
+     * due to an uncaught exception from one of its {@link GLAutoDrawable}s.
+     * @param handler the {@link UncaughtGLAnimatorExceptionHandler} to use as this {@link GLAnimatorControl animator}'s uncaught exception
+     * handler. Pass <code>null</code> to unset the handler.
+     * @see UncaughtGLAnimatorExceptionHandler#uncaughtException(GLAnimatorControl, GLAutoDrawable, Throwable)
+     * @since 2.2
+     */
+    void setUncaughtExceptionHandler(final UncaughtGLAnimatorExceptionHandler handler);
 }
index 5745e19..bded88d 100644 (file)
@@ -229,6 +229,7 @@ public interface GLAutoDrawable extends GLDrawable {
 
   /**
    * Returns true if all added {@link GLEventListener} are initialized, otherwise false.
+   * @since 2.2
    */
   boolean areAllGLEventListenerInitialized();
 
@@ -458,6 +459,7 @@ public interface GLAutoDrawable extends GLDrawable {
    * @see #display()
    * @see GLRunnable
    * @see #invoke(boolean, List)
+   * @see #flushGLRunnables()
    */
   public boolean invoke(boolean wait, GLRunnable glRunnable) throws IllegalStateException ;
 
@@ -469,9 +471,22 @@ public interface GLAutoDrawable extends GLDrawable {
    * @return <code>true</code> if the {@link GLRunnable}s has been processed or queued, otherwise <code>false</code>.
    * @throws IllegalStateException in case of a detected deadlock situation ahead, see {@link #invoke(boolean, GLRunnable)}.
    * @see #invoke(boolean, GLRunnable)
+   * @see #flushGLRunnables()
    */
   public boolean invoke(boolean wait, List<GLRunnable> glRunnables) throws IllegalStateException;
 
+  /**
+   * Flushes all {@link #invoke(boolean, GLRunnable) enqueued} {@link GLRunnable} of this {@link GLAutoDrawable}
+   * including notifying waiting executor.
+   * <p>
+   * The executor which might have been blocked until notified
+   * will be unblocked and all tasks removed from the queue.
+   * </p>
+   * @see #invoke(boolean, GLRunnable)
+   * @since 2.2
+   */
+  public void flushGLRunnables();
+
   /** Destroys all resources associated with this GLAutoDrawable,
       inclusive the GLContext.
       If a window is attached to it's implementation, it shall be closed.
@@ -602,6 +617,7 @@ public interface GLAutoDrawable extends GLDrawable {
    * <p>
    * See <a href="#locking">GLAutoDrawable Locking</a>.
    * </p>
+   * @since 2.2
    */
   public RecursiveLock getUpstreamLock();
 
@@ -613,6 +629,7 @@ public interface GLAutoDrawable extends GLDrawable {
    * whether {@link #display()} performs the OpenGL commands on the current thread directly
    * or spawns them on the dedicated OpenGL thread.
    * </p>
+   * @since 2.2
    */
   public boolean isThreadGLCapable();
 
index ac86446..7ed057e 100644 (file)
@@ -201,6 +201,7 @@ public interface GLDrawable extends NativeSurfaceHolder {
       </p>
       @return The immutable queried instance.
       @see #getChosenGLCapabilities()
+      @since 2.2
     */
   public GLCapabilitiesImmutable getRequestedGLCapabilities();
 
index dff9b9d..3f76a62 100644 (file)
@@ -69,14 +69,18 @@ public class GLException extends RuntimeException {
   /**
    * Constructs a GLException object with the specified root
    * cause with a decorating message including the current thread name.
+   * @since 2.2
    */
   public static GLException newGLException(final Throwable t) {
       return new GLException("Caught "+t.getClass().getSimpleName()+": "+t.getMessage()+" on thread "+Thread.currentThread().getName(), t);
   }
 
-  /** Dumps a Throwable in a decorating message including the current thread name, and stack trace. */
-  public static void dumpThrowable(final Throwable t) {
-      System.err.println("Caught "+t.getClass().getSimpleName()+": "+t.getMessage()+" on thread "+Thread.currentThread().getName());
+  /**
+   * Dumps a Throwable in a decorating message including the current thread name, and stack trace.
+   * @since 2.2
+   */
+  public static void dumpThrowable(final String additionalDescr, final Throwable t) {
+      System.err.println("Caught "+additionalDescr+" "+t.getClass().getSimpleName()+": "+t.getMessage()+" on thread "+Thread.currentThread().getName());
       t.printStackTrace();
   }
 }
index ca5cf0e..dba1dbc 100644 (file)
@@ -1059,6 +1059,11 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing
   }
 
   @Override
+  public void flushGLRunnables() {
+      helper.flushGLRunnables();
+  }
+
+  @Override
   public GLContext setContext(final GLContext newCtx, final boolean destroyPrevCtx) {
       final RecursiveLock _lock = lock;
       _lock.lock();
index d08839b..1682c6d 100644 (file)
@@ -958,6 +958,11 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing
   }
 
   @Override
+  public void flushGLRunnables() {
+      helper.flushGLRunnables();
+  }
+
+  @Override
   public GLContext createContext(final GLContext shareWith) {
     final Backend b = backend;
     if ( null == b ) {
index e1cd59a..6e6aaf5 100644 (file)
@@ -279,7 +279,13 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
             shallClose = true;
         }
         if( shallClose ) {
-            destroyAvoidAwareOfLocking();
+            try {
+                destroyAvoidAwareOfLocking();
+            } catch( final Throwable t ) {
+                // Intentionally catch and ignore exception,
+                // so the destroy mechanism of the native windowing system is not corrupted!
+                GLException.dumpThrowable("ignored", t);
+            }
         }
     }
 
@@ -602,6 +608,11 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe
     }
 
     @Override
+    public void flushGLRunnables() {
+        helper.flushGLRunnables();
+    }
+
+    @Override
     public final void setAutoSwapBufferMode(final boolean enable) {
         helper.setAutoSwapBufferMode(enable);
     }
index 945ca54..f91e1bd 100644 (file)
@@ -526,8 +526,9 @@ public class GLDrawableHelper {
                   } catch (final Throwable t) {
                       if( null == firstCaught ) {
                           firstCaught = t;
+                      } else {
+                          GLException.dumpThrowable("subsequent", t);
                       }
-                      GLException.dumpThrowable(t);
                   }
                   disposeCount++;
               }
@@ -541,8 +542,9 @@ public class GLDrawableHelper {
                   } catch (final Throwable t) {
                       if( null == firstCaught ) {
                           firstCaught = t;
+                      } else {
+                          GLException.dumpThrowable("subsequent", t);
                       }
-                      GLException.dumpThrowable(t);
                   }
                   listenersToBeInit.add(listener);
                   disposeCount++;
@@ -1125,8 +1127,7 @@ public class GLDrawableHelper {
                              final Runnable  initAction) {
     if(null==context) {
         if (DEBUG) {
-            final Exception e = new GLException("Info: GLDrawableHelper " + this + ".invokeGL(): NULL GLContext");
-            GLException.dumpThrowable(e);
+            GLException.dumpThrowable("informal", new GLException("Info: GLDrawableHelper " + this + ".invokeGL(): NULL GLContext"));
         }
         return;
     }
@@ -1196,7 +1197,6 @@ public class GLDrawableHelper {
               forceNativeRelease(context);
           }
       } catch (final Throwable t) {
-          GLException.dumpThrowable(t);
           contextCloseCaught = t;
       }
       flushGLRunnables(); // always flush GLRunnables at dispose
@@ -1208,6 +1208,9 @@ public class GLDrawableHelper {
         }
       }
       if( null != disposeCaught ) {
+          if( null != contextCloseCaught ) {
+              GLException.dumpThrowable("subsequent", contextCloseCaught);
+          }
           throw disposeCaught;
       }
       if( null != contextCloseCaught ) {
@@ -1283,7 +1286,6 @@ public class GLDrawableHelper {
                       drawable.swapBuffers();
                   }
               } catch (final Throwable t) {
-                  GLException.dumpThrowable(t);
                   glEventListenerCaught = t;
               } finally {
                   if( _releaseExclusiveThread ) {
@@ -1296,7 +1298,6 @@ public class GLDrawableHelper {
                       try {
                           context.release();
                       } catch (final Throwable t) {
-                          GLException.dumpThrowable(t);
                           contextReleaseCaught = t;
                       }
                   }
@@ -1311,6 +1312,9 @@ public class GLDrawableHelper {
           }
           if( null != glEventListenerCaught ) {
               flushGLRunnables();
+              if( null != contextReleaseCaught ) {
+                  GLException.dumpThrowable("subsequent", contextReleaseCaught);
+              }
               throw GLException.newGLException(glEventListenerCaught);
           }
           if( null != contextReleaseCaught ) {
@@ -1401,7 +1405,6 @@ public class GLDrawableHelper {
                       tdS = tdX - tdS; // swapBuffers
                   }
               } catch (final Throwable t) {
-                  GLException.dumpThrowable(t);
                   glEventListenerCaught = t;
               } finally {
                   if( _releaseExclusiveThread ) {
@@ -1416,7 +1419,6 @@ public class GLDrawableHelper {
                           context.release();
                           ctxReleased = true;
                       } catch (final Throwable t) {
-                          GLException.dumpThrowable(t);
                           contextReleaseCaught = t;
                       }
                   }
@@ -1432,6 +1434,9 @@ public class GLDrawableHelper {
           }
           if( null != glEventListenerCaught ) {
               flushGLRunnables();
+              if( null != contextReleaseCaught ) {
+                  GLException.dumpThrowable("subsequent", contextReleaseCaught);
+              }
               throw GLException.newGLException(glEventListenerCaught);
           }
           if( null != contextReleaseCaught ) {
index 0ceef6b..ca1c186 100644 (file)
@@ -90,8 +90,10 @@ public class GLRunnableTask implements GLRunnable {
 
     /**
      * Simply flush this task and notify a waiting executor.
+     * <p>
      * The executor which might have been blocked until notified
      * will be unblocked and the task removed from the queue.
+     * </p>
      *
      * @see #isFlushed()
      * @see #isInQueue()
index ca6ffa1..b626289 100644 (file)
@@ -1104,6 +1104,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer
             if(null!=lifecycleHook) {
                 lifecycleHook.destroyActionPreLock();
             }
+            RuntimeException lifecycleCaughtInLock = null;
             final RecursiveLock _lock = windowLock;
             _lock.lock();
             try {
@@ -1133,7 +1134,11 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer
 
                 if(null!=lifecycleHook) {
                     // send synced destroy notification for proper cleanup, eg GLWindow/OpenGL
-                    lifecycleHook.destroyActionInLock();
+                    try {
+                        lifecycleHook.destroyActionInLock();
+                    } catch (final RuntimeException re) {
+                        lifecycleCaughtInLock = re;
+                    }
                 }
 
                 if( isNativeValid() ) {
@@ -1156,6 +1161,13 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer
 
                 if(DEBUG_IMPLEMENTATION) {
                     System.err.println("Window.destroy() END "+getThreadName()/*+", "+WindowImpl.this*/);
+                    if( null != lifecycleCaughtInLock ) {
+                        System.err.println("Window.destroy() caught: "+lifecycleCaughtInLock.getMessage());
+                        lifecycleCaughtInLock.printStackTrace();
+                    }
+                }
+                if( null != lifecycleCaughtInLock ) {
+                    throw lifecycleCaughtInLock;
                 }
             } finally {
                 // update states before release window lock
index a2dca2d..83d5e9c 100644 (file)
@@ -28,6 +28,8 @@
 
 package com.jogamp.opengl.test.junit.jogl.acore;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import com.jogamp.newt.opengl.GLWindow;
@@ -36,10 +38,12 @@ import com.jogamp.opengl.test.junit.util.UITestCase;
 import com.jogamp.opengl.util.Animator;
 import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2;
 
+import javax.media.opengl.GLAnimatorControl;
 import javax.media.opengl.GLAutoDrawable;
 import javax.media.opengl.GLCapabilities;
 import javax.media.opengl.GLEventListener;
 import javax.media.opengl.GLProfile;
+import javax.media.opengl.GLRunnable;
 
 import org.junit.Assert;
 import org.junit.BeforeClass;
@@ -52,6 +56,19 @@ public class TestGLException01NEWT extends UITestCase {
     static GLProfile glp;
     static int width, height;
 
+    @SuppressWarnings("serial")
+    static class AnimException extends RuntimeException {
+        final Thread thread;
+        final GLAnimatorControl animator;
+        final GLAutoDrawable drawable;
+        public AnimException(final Thread thread, final GLAnimatorControl animator, final GLAutoDrawable drawable, final Throwable cause) {
+            super(cause);
+            this.thread = thread;
+            this.animator = animator;
+            this.drawable = drawable;
+        }
+    }
+
     @BeforeClass
     public static void initClass() {
         glp = GLProfile.getGL2ES2();
@@ -67,72 +84,129 @@ public class TestGLException01NEWT extends UITestCase {
 
     protected void runTestGL(final GLCapabilities caps, final boolean onThread,
                              final boolean throwInInit, final boolean throwInDisplay,
-                             final boolean throwInReshape, final boolean throwInDispose) throws InterruptedException {
+                             final boolean throwInReshape, final boolean throwInInvoke,
+                             final boolean throwInDispose) throws InterruptedException {
         final GLWindow glWindow = GLWindow.create(caps);
         Assert.assertNotNull(glWindow);
-        glWindow.setTitle("NEWT Exception Test");
+        glWindow.setTitle(getTestMethodName());
         final GearsES2 demo1 = new GearsES2();
         demo1.setVerbose(false);
         glWindow.addGLEventListener(demo1);
-        final AtomicInteger initCount = new AtomicInteger();
-        final AtomicInteger disposeCount = new AtomicInteger();
-        final AtomicInteger displayCount = new AtomicInteger();
-        final AtomicInteger reshapeCount = new AtomicInteger();
+        final AtomicInteger cleanInitCount = new AtomicInteger();
+        final AtomicInteger cleanDisposeCount = new AtomicInteger();
+        final AtomicInteger cleanDisplayCount = new AtomicInteger();
+        final AtomicInteger cleanReshapeCount = new AtomicInteger();
+        final AtomicInteger cleanInvokeCount = new AtomicInteger();
+        final AtomicInteger allInitCount = new AtomicInteger();
+        final AtomicInteger allDisposeCount = new AtomicInteger();
+        final AtomicInteger allDisplayCount = new AtomicInteger();
+        final AtomicInteger allReshapeCount = new AtomicInteger();
+        final AtomicInteger allInvokeCount = new AtomicInteger();
         final AtomicInteger exceptionSent = new AtomicInteger();
 
         glWindow.addGLEventListener(new GLEventListener() {
             @Override
             public void init(final GLAutoDrawable drawable) {
-                if( throwInInit && 0 == exceptionSent.get() ) {
+                if( throwInInit ) {
                     exceptionSent.incrementAndGet();
-                    throw new RuntimeException("Injected GLEventListener exception in init");
+                    throw new RuntimeException("<Injected GLEventListener exception in init: #"+exceptionSent.get()+" on thread "+Thread.currentThread().getName()+">");
                 }
             }
             @Override
             public void dispose(final GLAutoDrawable drawable) {
-                if( throwInDispose && 0 == exceptionSent.get() ) {
+                if( throwInDispose ) {
                     exceptionSent.incrementAndGet();
-                    throw new RuntimeException("Injected GLEventListener exception in dispose");
+                    throw new RuntimeException("<Injected GLEventListener exception in dispose: #"+exceptionSent.get()+" on thread "+Thread.currentThread().getName()+">");
                 }
             }
             @Override
             public void display(final GLAutoDrawable drawable) {
-                if( throwInDisplay && 0 == exceptionSent.get() ) {
+                if( throwInDisplay ) {
                     exceptionSent.incrementAndGet();
-                    throw new RuntimeException("Injected GLEventListener exception in display");
+                    throw new RuntimeException("<Injected GLEventListener exception in display: #"+exceptionSent.get()+" on thread "+Thread.currentThread().getName()+">");
                 }
             }
             @Override
             public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {
-                if( throwInReshape && 0 == exceptionSent.get() ) {
+                if( throwInReshape ) {
                     exceptionSent.incrementAndGet();
-                    throw new RuntimeException("Injected GLEventListener exception in reshape");
+                    throw new RuntimeException("<Injected GLEventListener exception in reshape: #"+exceptionSent.get()+" on thread "+Thread.currentThread().getName()+">");
                 }
             }
         });
+        final GLRunnable glRunnableInject = new GLRunnable() {
+            @Override
+            public boolean run(final GLAutoDrawable drawable) {
+                if( throwInInvoke ) {
+                    exceptionSent.incrementAndGet();
+                    throw new RuntimeException("<Injected GLEventListener exception in invoke: #"+exceptionSent.get()+" on thread "+Thread.currentThread().getName()+">");
+                }
+                return true;
+            }
+        };
+        final GLRunnable glRunnableCount = new GLRunnable() {
+            @Override
+            public boolean run(final GLAutoDrawable drawable) {
+                if( 0 == exceptionSent.get() ) {
+                    cleanInvokeCount.incrementAndGet();
+                }
+                allInvokeCount.incrementAndGet();
+                return true;
+            }
+        };
+
         glWindow.addGLEventListener(new GLEventListener() {
             @Override
             public void init(final GLAutoDrawable drawable) {
-                initCount.incrementAndGet();
+                if( 0 == exceptionSent.get() ) {
+                    cleanInitCount.incrementAndGet();
+                }
+                allInitCount.incrementAndGet();
             }
             @Override
             public void dispose(final GLAutoDrawable drawable) {
-                disposeCount.incrementAndGet();
+                if( 0 == exceptionSent.get() ) {
+                    cleanDisposeCount.incrementAndGet();
+                }
+                allDisposeCount.incrementAndGet();
             }
             @Override
             public void display(final GLAutoDrawable drawable) {
-                displayCount.incrementAndGet();
+                if( 0 == exceptionSent.get() ) {
+                    cleanDisplayCount.incrementAndGet();
+                }
+                allDisplayCount.incrementAndGet();
             }
             @Override
             public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {
-                reshapeCount.incrementAndGet();
+                if( 0 == exceptionSent.get() ) {
+                    cleanReshapeCount.incrementAndGet();
+                }
+                allReshapeCount.incrementAndGet();
             }
         });
 
-        RuntimeException execptionAtInitReshapeDisplay = null;
-        RuntimeException execptionAtDispose = null;
+        RuntimeException exceptionAtInitReshapeDisplay = null;
+        RuntimeException exceptionAtInvoke = null;
+        RuntimeException exceptionAtDispose = null;
+        final List<AnimException> exceptionsAtGLAnimatorControl = new ArrayList<AnimException>();
+        final GLAnimatorControl.UncaughtGLAnimatorExceptionHandler uncaughtGLAnimatorExceptionHandler;
 
-        final Animator animator = !onThread ? new Animator(glWindow) : null;
+        final Animator animator;
+        if( onThread ) {
+            animator = null;
+            uncaughtGLAnimatorExceptionHandler = null;
+        } else {
+            animator = new Animator(glWindow);
+            uncaughtGLAnimatorExceptionHandler = new GLAnimatorControl.UncaughtGLAnimatorExceptionHandler() {
+                @Override
+                public void uncaughtException(final GLAnimatorControl animator, final GLAutoDrawable drawable, final Throwable cause) {
+                    final AnimException ae = new AnimException(animator.getThread(), animator, drawable, cause);
+                    exceptionsAtGLAnimatorControl.add(ae);
+                    dumpThrowable(ae);
+                } };
+            animator.setUncaughtExceptionHandler(uncaughtGLAnimatorExceptionHandler);
+        }
 
         glWindow.setSize(width, height);
 
@@ -143,7 +217,15 @@ public class TestGLException01NEWT extends UITestCase {
         try {
             glWindow.setVisible(true);
         } catch (final RuntimeException re) {
-            execptionAtInitReshapeDisplay = re;
+            exceptionAtInitReshapeDisplay = re;
+            dumpThrowable(re);
+        }
+
+        try {
+            glWindow.invoke(true, glRunnableInject);
+            glWindow.invoke(true, glRunnableCount);
+        } catch (final RuntimeException re) {
+            exceptionAtInvoke = re;
             dumpThrowable(re);
         }
 
@@ -154,7 +236,7 @@ public class TestGLException01NEWT extends UITestCase {
                 try {
                     glWindow.display();
                 } catch (final RuntimeException re) {
-                    execptionAtInitReshapeDisplay = re;
+                    exceptionAtInitReshapeDisplay = re;
                     dumpThrowable(re);
                 }
             }
@@ -168,23 +250,81 @@ public class TestGLException01NEWT extends UITestCase {
         try {
             glWindow.destroy();
         } catch (final RuntimeException re) {
-            execptionAtDispose = re;
+            exceptionAtDispose = re;
             dumpThrowable(re);
         }
 
-        if( throwInInit || throwInReshape || throwInDisplay || throwInDispose ) {
-            Assert.assertEquals("Not one exception sent", 1, exceptionSent.get());
-            if( throwInInit ) {
-                Assert.assertNotNull("No exception forwarded from init", execptionAtInitReshapeDisplay);
-            }
-            if( throwInReshape ) {
-                Assert.assertNotNull("No exception forwarded from reshape", execptionAtInitReshapeDisplay);
-            }
-            if( throwInDisplay ) {
-                Assert.assertNotNull("No exception forwarded from display", execptionAtInitReshapeDisplay);
+        final boolean onAnimThread = !onThread && !throwInDispose; /** dispose happens on [AWT|NEWT] EDT, not on animator thread! */
+
+        System.err.println("This-Thread                     : "+onThread);
+        System.err.println("Anim-Thread                     : "+onAnimThread);
+        System.err.println("ExceptionSent                   : "+exceptionSent.get());
+        System.err.println("Exception @ Init/Reshape/Display: "+(null != exceptionAtInitReshapeDisplay));
+        System.err.println("Exception @ Invoke              : "+(null != exceptionAtInvoke));
+        System.err.println("Exception @ Dispose             : "+(null != exceptionAtDispose));
+        System.err.println("Exception @ GLAnimatorControl   : "+exceptionsAtGLAnimatorControl.size());
+        System.err.println("Init Count                      : "+cleanInitCount.get()+" / "+allInitCount.get());
+        System.err.println("Reshape Count                   : "+cleanReshapeCount.get()+" / "+allReshapeCount.get());
+        System.err.println("Display Count                   : "+cleanDisplayCount.get()+" / "+allDisplayCount.get());
+        System.err.println("Invoke Count                    : "+cleanInvokeCount.get()+" / "+allInvokeCount.get());
+        System.err.println("Dispose Count                   : "+cleanDisposeCount.get()+" / "+allDisposeCount.get());
+
+        if( throwInInit || throwInReshape || throwInDisplay || throwInDispose || throwInInvoke ) {
+            Assert.assertTrue("Not one exception sent, but "+exceptionSent.get(), 0 < exceptionSent.get());
+            if( onAnimThread ) {
+                Assert.assertEquals("No exception forwarded from init to animator-handler", 1, exceptionsAtGLAnimatorControl.size());
+                Assert.assertNull("Exception forwarded from init, on-thread", exceptionAtInitReshapeDisplay);
             }
-            if( throwInDispose ) {
-                Assert.assertNotNull("No exception forwarded from dispose", execptionAtDispose);
+            if( throwInInit ) {
+                if( !onAnimThread ) {
+                    Assert.assertNotNull("No exception forwarded from init, on-thread", exceptionAtInitReshapeDisplay);
+                    Assert.assertEquals("Exception forwarded from init to animator-handler", 0, exceptionsAtGLAnimatorControl.size());
+                }
+                Assert.assertEquals("Init Count", 0, cleanInitCount.get());
+                Assert.assertEquals("Reshape Count", 0, cleanReshapeCount.get());
+                Assert.assertEquals("Display Count", 0, cleanDisplayCount.get());
+                Assert.assertEquals("Invoke Count", 0, cleanInvokeCount.get());
+                Assert.assertEquals("Dispose Count", 0, cleanDisposeCount.get());
+            } else if( throwInReshape ) {
+                if( !onAnimThread ) {
+                    Assert.assertNotNull("No exception forwarded from reshape, on-thread", exceptionAtInitReshapeDisplay);
+                    Assert.assertEquals("Exception forwarded from init to animator-handler", 0, exceptionsAtGLAnimatorControl.size());
+                }
+                Assert.assertEquals("Init Count", 1, cleanInitCount.get());
+                Assert.assertEquals("Reshape Count", 0, cleanReshapeCount.get());
+                Assert.assertEquals("Display Count", 0, cleanDisplayCount.get());
+                Assert.assertEquals("Invoke Count", 0, cleanInvokeCount.get());
+                Assert.assertEquals("Dispose Count", 0, cleanDisposeCount.get());
+            } else if( throwInDisplay ) {
+                if( !onAnimThread ) {
+                    Assert.assertNotNull("No exception forwarded from display, on-thread", exceptionAtInitReshapeDisplay);
+                    Assert.assertEquals("Exception forwarded from init to animator-handler", 0, exceptionsAtGLAnimatorControl.size());
+                }
+                Assert.assertEquals("Init Count", 1, cleanInitCount.get());
+                Assert.assertEquals("Reshape Count", 1, cleanReshapeCount.get());
+                Assert.assertEquals("Display Count", 0, cleanDisplayCount.get());
+                Assert.assertEquals("Invoke Count", 0, cleanInvokeCount.get());
+                Assert.assertEquals("Dispose Count", 0, cleanDisposeCount.get());
+            } else if( throwInInvoke ) {
+                if( !onAnimThread ) {
+                    Assert.assertNotNull("No exception forwarded from invoke, on-thread", exceptionAtInvoke);
+                    Assert.assertEquals("Exception forwarded from init to animator-handler", 0, exceptionsAtGLAnimatorControl.size());
+                }
+                Assert.assertEquals("Init Count", 1, cleanInitCount.get());
+                Assert.assertEquals("Reshape Count", 1, cleanReshapeCount.get());
+                Assert.assertTrue  ("Display count not greater-equal 1, but "+cleanDisplayCount.get(), 1 <= cleanDisplayCount.get());
+                Assert.assertEquals("Invoke Count", 0, cleanInvokeCount.get());
+                Assert.assertEquals("Dispose Count", 0, cleanDisposeCount.get());
+            } else if( throwInDispose ) {
+                if( !onAnimThread ) {
+                    Assert.assertNotNull("No exception forwarded from dispose, on-thread", exceptionAtDispose);
+                    Assert.assertEquals("Exception forwarded from init to animator-handler", 0, exceptionsAtGLAnimatorControl.size());
+                }
+                Assert.assertEquals("Init Count", 1, cleanInitCount.get());
+                Assert.assertEquals("Reshape Count", 1, cleanReshapeCount.get());
+                Assert.assertTrue  ("Display count not greater-equal 1, but "+cleanDisplayCount.get(), 1 <= cleanDisplayCount.get());
+                Assert.assertEquals("Invoke Count", 1, cleanInvokeCount.get());
+                Assert.assertEquals("Dispose Count", 0, cleanDisposeCount.get());
             }
         }
     }
@@ -193,25 +333,62 @@ public class TestGLException01NEWT extends UITestCase {
     public void test01OnThreadAtInit() throws InterruptedException {
         final GLCapabilities caps = new GLCapabilities(glp);
         caps.setBackgroundOpaque(true); // default
-        runTestGL(caps, true /* onThread */, true /* init */, false /* display */, false /* reshape */, false /* dispose */);
+        runTestGL(caps, true /* onThread */, true /* init */, false /* display */, false /* reshape */, false /* invoke */, false /* dispose */);
     }
     @Test
     public void test02OnThreadAtReshape() throws InterruptedException {
         final GLCapabilities caps = new GLCapabilities(glp);
         caps.setBackgroundOpaque(true); // default
-        runTestGL(caps, true /* onThread */, false /* init */, false /* display */, true /* reshape */, false /* dispose */);
+        runTestGL(caps, true /* onThread */, false /* init */, false /* display */, true /* reshape */, false /* invoke */, false /* dispose */);
     }
     @Test
     public void test03OnThreadAtDisplay() throws InterruptedException {
         final GLCapabilities caps = new GLCapabilities(glp);
         caps.setBackgroundOpaque(true); // default
-        runTestGL(caps, true /* onThread */, false /* init */, true /* display */, false /* reshape */, false /* dispose */);
+        runTestGL(caps, true /* onThread */, false /* init */, true /* display */, false /* reshape */, false /* invoke */, false /* dispose */);
+    }
+    @Test
+    public void test04OnThreadAtInvoke() throws InterruptedException {
+        final GLCapabilities caps = new GLCapabilities(glp);
+        caps.setBackgroundOpaque(true); // default
+        runTestGL(caps, true /* onThread */, false /* init */, true /* display */, false /* reshape */, true /* invoke */, false /* dispose */);
+    }
+    @Test
+    public void test05OnThreadAtDispose() throws InterruptedException {
+        final GLCapabilities caps = new GLCapabilities(glp);
+        caps.setBackgroundOpaque(true); // default
+        runTestGL(caps, true /* onThread */, false /* init */, false /* display */, false /* reshape */, false /* invoke */, true /* dispose */);
+    }
+
+    @Test
+    public void test11OffThreadAtInit() throws InterruptedException {
+        final GLCapabilities caps = new GLCapabilities(glp);
+        caps.setBackgroundOpaque(true); // default
+        runTestGL(caps, false /* onThread */, true /* init */, false /* display */, false /* reshape */, false /* invoke */, false /* dispose */);
+    }
+    @Test
+    public void test12OffThreadAtReshape() throws InterruptedException {
+        final GLCapabilities caps = new GLCapabilities(glp);
+        caps.setBackgroundOpaque(true); // default
+        runTestGL(caps, false /* onThread */, false /* init */, false /* display */, true /* reshape */, false /* invoke */, false /* dispose */);
+    }
+    @Test
+    public void test13OffThreadAtDisplay() throws InterruptedException {
+        final GLCapabilities caps = new GLCapabilities(glp);
+        caps.setBackgroundOpaque(true); // default
+        runTestGL(caps, false /* onThread */, false /* init */, true /* display */, false /* reshape */, false /* invoke */, false /* dispose */);
+    }
+    @Test
+    public void test14OffThreadAtInvoke() throws InterruptedException {
+        final GLCapabilities caps = new GLCapabilities(glp);
+        caps.setBackgroundOpaque(true); // default
+        runTestGL(caps, false /* onThread */, false /* init */, true /* display */, false /* reshape */, true /* invoke */, false /* dispose */);
     }
     @Test
-    public void test04OnThreadAtDispose() throws InterruptedException {
+    public void test15OffThreadAtDispose() throws InterruptedException {
         final GLCapabilities caps = new GLCapabilities(glp);
         caps.setBackgroundOpaque(true); // default
-        runTestGL(caps, true /* onThread */, false /* init */, false /* display */, false /* reshape */, true /* dispose */);
+        runTestGL(caps, false /* onThread */, false /* init */, false /* display */, false /* reshape */, false /* invoke */, true /* dispose */);
     }
 
     static long duration = 500; // ms
http://JogAmp.org git info: FAQ, tutorial and man pages.