From c002e04f848116922a1ed7bd96ead54961649bbd Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Sun, 4 Nov 2012 07:09:39 +0100 Subject: GLAutoDrawable: Fix GLEventListener lifecycle and expose more user control (API Change) ; Added GLDrawableUtil A GLEventListener resides in two states, initialized and uninitialized. When added to a GLAutoDrawable, it is uninitialized. A first 'display()' will issue GLEventListener's 'init(..)' which renders it initialized. This is usually accompanied by 'reshape(..)' propagating the drawable's dimension. Destruction of the GLAutoDrawable will issue GLEventListener's 'dispose(..)' which renders it uninitialized. It turns our these means of GLEventListener controls are not sufficient in case the user requires to remove and add them during the lifecycle and rendering of their GLAutoDrawable host. GLAutoDrawable 'removeGLEventListener(..)' merely removes the GLEventListener from the list, but does not complete it's lifecycle, i.e. issues 'dispose(..)' if initialized to realease GL related resources. Hence the following essential API changes are made to complete the lifecycle: + public GLEventListener disposeGLEventListener(GLEventListener listener, boolean remove); disposing a single GLEventListener, allowing it's removal from the list being optional This is demonstrated via GLDrawableUtil.swapGLContextAndAllGLEventListener(GLAutoDrawable a, GLAutoDrawable b), see below. ++++++++ Further more the following API changes were made to expose complete control of GLEventListener to the user: - public void removeGLEventListener(GLEventListener listener); + public GLEventListener removeGLEventListener(GLEventListener listener); The return value allows simple pipelining, and also delivers information whether the passed listener was actually removed. - public GLEventListener removeGLEventListener(int index) throws IndexOutOfBoundsException; + public int getGLEventListenerCount(); + public GLEventListener getGLEventListener(int index) throws IndexOutOfBoundsException; Dropping the redundant removal by index, while adding count and get methods. + public boolean getGLEventListenerInitState(GLEventListener listener); + public void setGLEventListenerInitState(GLEventListener listener, boolean initialized); Allows retrieving and setting of listener states. All in all these API changes allows a user to experience all freedoms in dealing w/ GLEventListeners hosted by GLAutoDrawable impl. and shall be future proof. Note that we have avoided the Iterator pattern due to it's overhead of temporal objects creation. The simple indexed access allows us to implement each method as an atomic operation. +++++++++++ Further more a simple enqueue(..) method has been added, allowing to just enqueue a GLRunnable w/o provoking it's execution - as invoke(..) does. This method pleases a use case where GLRunnables are batched and shall be executed later on.. public boolean invoke(boolean wait, GLRunnable glRunnable); + public void enqueue(GLRunnable glRunnable); +++++++++++ Added GLDrawableUtil, exposes utility function to rearrange GLEventListener, modifiy GLAutoDrawable, etc. GLDrawableUtil.swapGLContextAndAllGLEventListener(GLAutoDrawable a, GLAutoDrawable b) is tested and demonstrated w/ TestGLContextDrawableSwitchNEWT. Manually tested on X11, OSX and Windows. --- .../classes/javax/media/opengl/GLAutoDrawable.java | 183 +++++++++++++++++---- .../classes/javax/media/opengl/awt/GLCanvas.java | 62 ++++++- .../classes/javax/media/opengl/awt/GLJPanel.java | 63 ++++++- 3 files changed, 265 insertions(+), 43 deletions(-) (limited to 'src/jogl/classes/javax') diff --git a/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java b/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java index 38f1746f9..fc569b13d 100644 --- a/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java +++ b/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java @@ -166,13 +166,26 @@ public interface GLAutoDrawable extends GLDrawable { */ public GLContext setContext(GLContext newCtx); - /** Adds a {@link GLEventListener} to the end of this drawable queue. - The listeners are notified of events in the order of the queue. */ + /** + * Adds the given {@link GLEventListener listener} to the end of this drawable queue. + * The {@link GLEventListener listeners} are notified of events in the order of the queue. + *

+ * The newly added listener's {@link GLEventListener#init(GLAutoDrawable) init(..)} + * method will be called once before any other of it's callback methods. + * See {@link #getGLEventListenerInitState(GLEventListener)} for details. + *

+ * @param listener The GLEventListener object to be inserted + */ public void addGLEventListener(GLEventListener listener); /** - * Adds a {@link GLEventListener} at the given index of this drawable queue. - * The listeners are notified of events in the order of the queue. + * Adds the given {@link GLEventListener listener} at the given index of this drawable queue. + * The {@link GLEventListener listeners} are notified of events in the order of the queue. + *

+ * The newly added listener's {@link GLEventListener#init(GLAutoDrawable) init(..)} + * method will be called once before any other of it's callback methods. + * See {@link #getGLEventListenerInitState(GLEventListener)} for details. + *

* @param index Position where the listener will be inserted. * Should be within (0 <= index && index <= size()). * An index value of -1 is interpreted as the end of the list, size(). @@ -181,30 +194,120 @@ public interface GLAutoDrawable extends GLDrawable { */ public void addGLEventListener(int index, GLEventListener listener) throws IndexOutOfBoundsException; - /** - * Removes a {@link GLEventListener} from this drawable. - * Note that if this is done from within a particular drawable's - * {@link GLEventListener} handler (reshape, display, etc.) that it is not + /** + * Returns the number of {@link GLEventListener} of this drawable queue. + * @return The number of GLEventListener objects of this drawable queue. + */ + public int getGLEventListenerCount(); + + /** + * Returns the {@link GLEventListener} at the given index of this drawable queue. + * @param index Position of the listener to be returned. + * Should be within (0 <= index && index < size()). + * An index value of -1 is interpreted as last listener, size()-1. + * @return The GLEventListener object at the given index. + * @throws IndexOutOfBoundsException If the index is not within (0 <= index && index < size()), or -1 + */ + public GLEventListener getGLEventListener(int index) throws IndexOutOfBoundsException; + + /** + * Retrieves whether the given {@link GLEventListener listener} is initialized or not. + *

+ * After {@link #addGLEventListener(GLEventListener) adding} a {@link GLEventListener} it is + * marked uninitialized and added to a list of to be initialized {@link GLEventListener}. + * If such uninitialized {@link GLEventListener}'s handler methods (reshape, display) + * are about to be invoked, it's {@link GLEventListener#init(GLAutoDrawable) init(..)} method is invoked first. + * Afterwards the {@link GLEventListener} is marked initialized + * and removed from the list of to be initialized {@link GLEventListener}. + *

+ *

+ * This methods returns the {@link GLEventListener} initialized state, + * i.e. returns false if it is included in the list of to be initialized {@link GLEventListener}, + * otherwise true. + *

+ * @param listener the GLEventListener object to query it's initialized state. + */ + public boolean getGLEventListenerInitState(GLEventListener listener); + + /** + * Sets the given {@link GLEventListener listener's} initialized state. + *

+ * This methods allows manually setting the {@link GLEventListener} initialized state, + * i.e. adding it to, or removing it from the list of to be initialized {@link GLEventListener}. + * See {@link #getGLEventListenerInitState(GLEventListener)} for details. + *

+ *

+ * Warning: This method does not validate whether the given {@link GLEventListener listener's} + * is member of this drawable queue, i.e. {@link #addGLEventListener(GLEventListener) added}. + *

+ *

+ * This method is only exposed to allow users full control over the {@link GLEventListener}'s state + * and is usually not recommended to change. + *

+ *

+ * One use case is moving a {@link GLContext} and their initialized {@link GLEventListener} + * from one {@link GLAutoDrawable} to another, + * where a subsequent {@link GLEventListener#init(GLAutoDrawable) init(..)} call after adding it + * to the new owner is neither required nor desired. + * See {@link com.jogamp.opengl.util.GLDrawableUtil#swapGLContextAndAllGLEventListener(GLAutoDrawable, GLAutoDrawable) swapGLContextAndAllGLEventListener(..)}. + *

+ * @param listener the GLEventListener object to perform a state change. + * @param initialized if true, mark the listener initialized, otherwise uninitialized. + */ + public void setGLEventListenerInitState(GLEventListener listener, boolean initialized); + + /** + * Disposes the given {@link GLEventListener listener} via {@link GLEventListener#dispose(GLAutoDrawable) dispose(..)} + * if it has been initialized and added to this queue. + *

+ * If remove is true, the {@link GLEventListener} is removed from this drawable queue before disposal, + * otherwise marked uninitialized. + *

+ *

+ * If an {@link GLAnimatorControl} is being attached and the current thread is different + * than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation. + *

+ *

+ * Note that this is an expensive operation, since {@link GLEventListener#dispose(GLAutoDrawable) dispose(..)} + * is decorated by {@link GLContext#makeCurrent()} and {@link GLContext#release()}. + *

+ *

+ * Use {@link #removeGLEventListener(GLEventListener) removeGLEventListener(listener)} instead + * if you just want to remove the {@link GLEventListener listener} and don't care about the disposal of the it's (OpenGL) resources. + *

+ *

+ * Also note that this is done from within a particular drawable's + * {@link GLEventListener} handler (reshape, display, etc.), that it is not * guaranteed that all other listeners will be evaluated properly * during this update cycle. - * @param listener The GLEventListener object to be removed + *

+ * @param listener The GLEventListener object to be disposed and removed if remove is true + * @param remove pass true to have the listener removed from this drawable queue, otherwise pass false + * @return the disposed and/or removed GLEventListener, or null if no action was performed, i.e. listener was not added */ - public void removeGLEventListener(GLEventListener listener); - + public GLEventListener disposeGLEventListener(GLEventListener listener, boolean remove); + /** - * Removes a {@link GLEventListener} at the given index from this drawable. + * Removes the given {@link GLEventListener listener} from this drawable queue. + *

+ * This is an inexpensive operation, since the removed listener's + * {@link GLEventListener#dispose(GLAutoDrawable) dispose(..)} method will not be called. + *

+ *

+ * Use {@link #disposeGLEventListener(GLEventListener, boolean) disposeGLEventListener(listener, true)} + * instead to ensure disposal of the {@link GLEventListener listener}'s (OpenGL) resources. + *

+ *

* Note that if this is done from within a particular drawable's - * {@link GLEventListener} handler (reshape, display, etc.) that it is not + * {@link GLEventListener} handler (reshape, display, etc.), that it is not * guaranteed that all other listeners will be evaluated properly * during this update cycle. - * @param index Position of the listener to be removed. - * Should be within (0 <= index && index < size()). - * An index value of -1 is interpreted as last listener, size()-1. - * @return The removed GLEventListener object - * @throws IndexOutOfBoundsException If the index is not within (0 <= index && index < size()), or -1 + *

+ * @param listener The GLEventListener object to be removed + * @return the removed GLEventListener, or null if listener was not added */ - public GLEventListener removeGLEventListener(int index) throws IndexOutOfBoundsException; - + public GLEventListener removeGLEventListener(GLEventListener listener); + /** *

* Registers the usage of an animator, an {@link javax.media.opengl.GLAnimatorControl} implementation. @@ -236,25 +339,25 @@ public interface GLAutoDrawable extends GLDrawable { public GLAnimatorControl getAnimator(); /** - *

* Enqueues a one-shot {@link GLRunnable}, * which will be executed within the next {@link #display()} call * after all registered {@link GLEventListener}s * {@link GLEventListener#display(GLAutoDrawable) display(GLAutoDrawable)} * methods has been called. - *

*

* If no {@link GLAnimatorControl} is animating (default),
* or if the current thread is the animator thread,
- * a {@link #display()} call is issued after enqueue the GLRunnable.
- * No extra synchronization is performed in case wait is true, since it is executed in the current thread.

+ * a {@link #display()} call is issued after enqueue the GLRunnable, + * hence the {@link GLRunnable} will be executed right away.
+ *

*

- * If an {@link GLAnimatorControl} is animating,
- * no {@link #display()} call is issued, since the animator thread performs it.
+ * If an {@link GLAnimatorControl animator} is running,
+ * no explicit {@link #display()} call is issued, allowing the {@link GLAnimatorControl animator} to perform it when it's due.
*

*

* If wait is true the call blocks until the glRunnable - * has been executed.

+ * has been executed by the {@link GLAnimatorControl animator}, otherwise the method returns immediately. + *

*

* If wait is true and * {@link #isRealized()} returns false or {@link #getContext()} returns null, @@ -266,16 +369,40 @@ public interface GLAutoDrawable extends GLDrawable { * where all blocked callers are being notified. *

* - * @param wait if true block until execution of glRunnable is finished, otherwise return immediatly w/o waiting + * @param wait if true block until execution of glRunnable is finished, otherwise return immediately w/o waiting * @param glRunnable the {@link GLRunnable} to execute within {@link #display()} * @return true if the {@link GLRunnable} has been processed or queued, otherwise false. * * @see #setAnimator(GLAnimatorControl) * @see #display() * @see GLRunnable + * @see #enqueue(GLRunnable) */ public boolean invoke(boolean wait, GLRunnable glRunnable); + /** + * Enqueues a one-shot {@link GLRunnable}, + * which will be executed within the next {@link #display()} call + * after all registered {@link GLEventListener}s + * {@link GLEventListener#display(GLAutoDrawable) display(GLAutoDrawable)} + * methods has been called. + *

+ * Unlike the {@link #invoke(boolean, GLRunnable)}, this method only enqueues the {@link GLRunnable} + * w/o taking care of it's execution. Hence either a performing {@link GLAnimatorControl animator} + * or explicit user call shall trigger {@link #display()} to ensure it's execution. + *

+ *

+ * Method return immediately w/o waiting or blocking + *

+ * + * @param glRunnable the {@link GLRunnable} to execute within the next {@link #display()} call + * + * @see #invoke(boolean, GLRunnable) + * @see #display() + * @see GLRunnable + */ + public void enqueue(GLRunnable glRunnable); + /** Destroys all resources associated with this GLAutoDrawable, inclusive the GLContext. If a window is attached to it's implementation, it shall be closed. diff --git a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java index 1e9fcc95e..5b5f800a4 100644 --- a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java +++ b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java @@ -495,7 +495,7 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing (int) ((getHeight() + bounds.getHeight()) / 2)); return; } - if( ! this.helper.isExternalAnimatorAnimating() ) { + if( ! this.helper.isAnimatorAnimatingOnOtherThread() ) { display(); } } @@ -699,15 +699,37 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing } @Override - public void removeGLEventListener(GLEventListener listener) { - helper.removeGLEventListener(listener); + public int getGLEventListenerCount() { + return helper.getGLEventListenerCount(); } @Override - public GLEventListener removeGLEventListener(int index) throws IndexOutOfBoundsException { - return helper.removeGLEventListener(index); + public GLEventListener getGLEventListener(int index) throws IndexOutOfBoundsException { + return helper.getGLEventListener(index); + } + + @Override + public boolean getGLEventListenerInitState(GLEventListener listener) { + return helper.getGLEventListenerInitState(listener); + } + + @Override + public void setGLEventListenerInitState(GLEventListener listener, boolean initialized) { + helper.setGLEventListenerInitState(listener, initialized); } + @Override + public GLEventListener disposeGLEventListener(GLEventListener listener, boolean remove) { + final DisposeGLEventListenerAction r = new DisposeGLEventListenerAction(listener, remove); + Threading.invoke(true, r, getTreeLock()); + return r.listener; + } + + @Override + public GLEventListener removeGLEventListener(GLEventListener listener) { + return helper.removeGLEventListener(listener); + } + @Override public void setAnimator(GLAnimatorControl animatorControl) { helper.setAnimator(animatorControl); @@ -723,6 +745,11 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing return helper.invoke(this, wait, glRunnable); } + @Override + public void enqueue(GLRunnable glRunnable) { + helper.enqueue(glRunnable); + } + @Override public GLContext setContext(GLContext newCtx) { final RecursiveLock _lock = lock; @@ -984,15 +1011,12 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing } }; - // Workaround for ATI driver bugs related to multithreading issues - // like simultaneous rendering via Animators to canvases that are - // being resized on the AWT event dispatch thread private final Runnable displayOnEDTAction = new Runnable() { @Override public void run() { final RecursiveLock _lock = lock; _lock.lock(); - try { + try { helper.invokeGL(drawable, context, displayAction, initAction); } finally { _lock.unlock(); @@ -1015,6 +1039,26 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing } }; + private class DisposeGLEventListenerAction implements Runnable { + GLEventListener listener; + private boolean remove; + private DisposeGLEventListenerAction(GLEventListener listener, boolean remove) { + this.listener = listener; + this.remove = remove; + } + + @Override + public void run() { + final RecursiveLock _lock = lock; + _lock.lock(); + try { + listener = helper.disposeGLEventListener(GLCanvas.this, drawable, context, listener, remove); + } finally { + _lock.unlock(); + } + } + }; + // Disables the AWT's erasing of this Canvas's background on Windows // in Java SE 6. This internal API is not available in previous // releases, but the system property diff --git a/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java b/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java index d0b9fb913..08d70211b 100644 --- a/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java +++ b/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java @@ -426,15 +426,47 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } @Override - public void removeGLEventListener(GLEventListener listener) { - helper.removeGLEventListener(listener); + public int getGLEventListenerCount() { + return helper.getGLEventListenerCount(); } @Override - public GLEventListener removeGLEventListener(int index) throws IndexOutOfBoundsException { - return helper.removeGLEventListener(index); - } + public GLEventListener getGLEventListener(int index) throws IndexOutOfBoundsException { + return helper.getGLEventListener(index); + } + + @Override + public boolean getGLEventListenerInitState(GLEventListener listener) { + return helper.getGLEventListenerInitState(listener); + } + + @Override + public void setGLEventListenerInitState(GLEventListener listener, boolean initialized) { + helper.setGLEventListenerInitState(listener, initialized); + } + + @Override + public GLEventListener disposeGLEventListener(GLEventListener listener, boolean remove) { + final DisposeGLEventListenerAction r = new DisposeGLEventListenerAction(listener, remove); + if (EventQueue.isDispatchThread()) { + r.run(); + } else { + // Multithreaded redrawing of Swing components is not allowed, + // so do everything on the event dispatch thread + try { + EventQueue.invokeAndWait(r); + } catch (Exception e) { + throw new GLException(e); + } + } + return r.listener; + } + @Override + public GLEventListener removeGLEventListener(GLEventListener listener) { + return helper.removeGLEventListener(listener); + } + @Override public void setAnimator(GLAnimatorControl animatorControl) { helper.setAnimator(animatorControl); @@ -450,6 +482,11 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing return helper.invoke(this, wait, glRunnable); } + @Override + public void enqueue(GLRunnable glRunnable) { + helper.enqueue(glRunnable); + } + @Override public GLContext createContext(GLContext shareWith) { return (null != backend) ? backend.createContext(shareWith) : null; @@ -685,7 +722,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing @Override public void dispose(GLAutoDrawable drawable) { - helper.dispose(GLJPanel.this); + helper.disposeAllGLEventListener(GLJPanel.this, false); } @Override @@ -758,6 +795,20 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } }; + private class DisposeGLEventListenerAction implements Runnable { + GLEventListener listener; + private boolean remove; + private DisposeGLEventListenerAction(GLEventListener listener, boolean remove) { + this.listener = listener; + this.remove = remove; + } + + @Override + public void run() { + listener = helper.disposeGLEventListener(GLJPanel.this, backend.getDrawable(), backend.getContext(), listener, remove); + } + }; + private int getNextPowerOf2(int number) { // Workaround for problems where 0 width or height are transiently // seen during layout -- cgit v1.2.3