diff options
Diffstat (limited to 'src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java')
-rw-r--r-- | src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java new file mode 100644 index 000000000..7cd685d5a --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java @@ -0,0 +1,739 @@ +/** + * Copyright 2012 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package jogamp.opengl; + +import java.io.PrintStream; +import java.util.List; + +import javax.media.nativewindow.AbstractGraphicsDevice; +import javax.media.nativewindow.NativeSurface; +import javax.media.nativewindow.NativeWindowException; +import javax.media.nativewindow.WindowClosingProtocol; +import javax.media.nativewindow.WindowClosingProtocol.WindowClosingMode; +import javax.media.opengl.FPSCounter; +import javax.media.opengl.GL; +import javax.media.opengl.GLAnimatorControl; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilitiesImmutable; +import javax.media.opengl.GLContext; +import javax.media.opengl.GLDrawable; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.GLException; +import javax.media.opengl.GLOffscreenAutoDrawable; +import javax.media.opengl.GLProfile; +import javax.media.opengl.GLRunnable; +import javax.media.opengl.GLSharedContextSetter; + +import com.jogamp.common.util.locks.RecursiveLock; +import com.jogamp.opengl.GLAutoDrawableDelegate; +import com.jogamp.opengl.GLEventListenerState; +import com.jogamp.opengl.GLStateKeeper; + + +/** + * Abstract common code for GLAutoDrawable implementations. + * + * @see GLAutoDrawable + * @see GLAutoDrawableDelegate + * @see GLOffscreenAutoDrawable + * @see GLOffscreenAutoDrawableImpl + * @see GLPBufferImpl + * @see com.jogamp.newt.opengl.GLWindow + */ +public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeeper, FPSCounter, GLSharedContextSetter { + public static final boolean DEBUG = GLDrawableImpl.DEBUG; + protected final GLDrawableHelper helper = new GLDrawableHelper(); + protected final FPSCounterImpl fpsCounter = new FPSCounterImpl(); + + protected volatile GLDrawableImpl drawable; // volatile: avoid locking for read-only access + protected volatile GLContextImpl context; + protected boolean preserveGLELSAtDestroy; + protected GLEventListenerState glels; + protected GLStateKeeper.Listener glStateKeeperListener; + protected final boolean ownsDevice; + protected int additionalCtxCreationFlags = 0; + protected volatile boolean sendReshape = false; // volatile: maybe written by WindowManager thread w/o locking + protected volatile boolean sendDestroy = false; // volatile: maybe written by WindowManager thread w/o locking + + /** + * <p> + * The {@link GLContext} can be assigned later manually via {@link GLAutoDrawable#setContext(GLContext, boolean) setContext(ctx)} + * <i>or</i> it will be created <i>lazily</i> at the 1st {@link GLAutoDrawable#display() display()} method call.<br> + * <i>Lazy</i> {@link GLContext} creation will take a shared {@link GLContext} into account + * which has been set {@link #setSharedContext(GLContext) directly} + * or {@link #setSharedAutoDrawable(GLAutoDrawable) via another GLAutoDrawable}. + * </p> + * @param drawable upstream {@link GLDrawableImpl} instance, + * may be <code>null</code> for lazy initialization + * @param context upstream {@link GLContextImpl} instance, + * may not have been made current (created) yet, + * may not be associated w/ <code>drawable<code> yet, + * may be <code>null</code> for lazy initialization at 1st {@link #display()}. + * @param ownsDevice pass <code>true</code> if {@link AbstractGraphicsDevice#close()} shall be issued, + * otherwise pass <code>false</code>. Closing the device is required in case + * the drawable is created w/ it's own new instance, e.g. offscreen drawables, + * and no further lifecycle handling is applied. + */ + public GLAutoDrawableBase(GLDrawableImpl drawable, GLContextImpl context, boolean ownsDevice) { + this.drawable = drawable; + this.context = context; + this.preserveGLELSAtDestroy = false; + this.glels = null; + this.glStateKeeperListener = null; + this.ownsDevice = ownsDevice; + if(null != context && null != drawable) { + context.setGLDrawable(drawable, false); + } + resetFPSCounter(); + } + + @Override + public final void setSharedContext(GLContext sharedContext) throws IllegalStateException { + helper.setSharedContext(this.context, sharedContext); + } + + @Override + public final void setSharedAutoDrawable(GLAutoDrawable sharedAutoDrawable) throws IllegalStateException { + helper.setSharedAutoDrawable(this, sharedAutoDrawable); + } + + /** Returns the recursive lock object of the upstream implementation, which synchronizes multithreaded access on top of {@link NativeSurface#lockSurface()}. */ + protected abstract RecursiveLock getLock(); + + @Override + public final GLStateKeeper.Listener setGLStateKeeperListener(Listener l) { + final GLStateKeeper.Listener pre = glStateKeeperListener; + glStateKeeperListener = l; + return pre; + } + + @Override + public final boolean preserveGLStateAtDestroy(boolean value) { + final boolean res = isGLStatePreservationSupported() ? true : false; + if( res ) { + if( DEBUG ) { + final long surfaceHandle = null != getNativeSurface() ? getNativeSurface().getSurfaceHandle() : 0; + System.err.println("GLAutoDrawableBase.setPreserveGLStateAtDestroy: ("+getThreadName()+"): "+preserveGLELSAtDestroy+" -> "+value+" - surfaceHandle 0x"+Long.toHexString(surfaceHandle)); + } + preserveGLELSAtDestroy = value; + } + return res; + } + + @Override + public boolean isGLStatePreservationSupported() { return false; } + + @Override + public final GLEventListenerState getPreservedGLState() { + return glels; + } + + @Override + public final GLEventListenerState clearPreservedGLState() { + final GLEventListenerState r = glels; + glels = null; + return r; + } + + /** + * Preserves the {@link GLEventListenerState} from this {@link GLAutoDrawable}. + * + * @return <code>true</code> if the {@link GLEventListenerState} is preserved successfully from this {@link GLAutoDrawable}, + * otherwise <code>false</code>. + * + * @throws IllegalStateException if the {@link GLEventListenerState} is already preserved + * + * @see #restoreGLEventListenerState() + */ + protected final boolean preserveGLEventListenerState() throws IllegalStateException { + if( null != glels ) { + throw new IllegalStateException("GLEventListenerState already pulled"); + } + if( null != context && context.isCreated() ) { + if( null!= glStateKeeperListener) { + glStateKeeperListener.glStatePreserveNotify(this); + } + glels = GLEventListenerState.moveFrom(this); + return null != glels; + } + return false; + } + + /** + * Restores a previously {@link #preserveGLEventListenerState() preserved} {@link GLEventListenerState} to this {@link GLAutoDrawable}. + * + * @return <code>true</code> if the {@link GLEventListenerState} was previously {@link #preserveGLEventListenerState() preserved} + * and is moved successfully to this {@link GLAutoDrawable}, + * otherwise <code>false</code>. + * + * @see #preserveGLEventListenerState() + */ + protected final boolean restoreGLEventListenerState() { + if( null != glels ) { + glels.moveTo(this); + glels = null; + if( null!= glStateKeeperListener) { + glStateKeeperListener.glStateRestored(this); + } + return true; + } + return false; + } + + /** Default implementation to handle repaint events from the windowing system */ + protected final void defaultWindowRepaintOp() { + final GLDrawable _drawable = drawable; + if( null != _drawable && _drawable.isRealized() ) { + if( !_drawable.getNativeSurface().isSurfaceLockedByOtherThread() && !helper.isAnimatorAnimatingOnOtherThread() ) { + display(); + } + } + } + + /** Default implementation to handle resize events from the windowing system. All required locks are being claimed. */ + protected final void defaultWindowResizedOp(int newWidth, int newHeight) throws NativeWindowException, GLException { + GLDrawableImpl _drawable = drawable; + if( null!=_drawable ) { + if(DEBUG) { + final long surfaceHandle = null != getNativeSurface() ? getNativeSurface().getSurfaceHandle() : 0; + System.err.println("GLAutoDrawableBase.sizeChanged: ("+getThreadName()+"): "+newWidth+"x"+newHeight+" - surfaceHandle 0x"+Long.toHexString(surfaceHandle)); + } + if( ! _drawable.getChosenGLCapabilities().isOnscreen() ) { + final RecursiveLock _lock = getLock(); + _lock.lock(); + try { + final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, context, newWidth, newHeight); + if(_drawable != _drawableNew) { + // write back + _drawable = _drawableNew; + drawable = _drawableNew; + } + } finally { + _lock.unlock(); + } + } + sendReshape = true; // async if display() doesn't get called below, but avoiding deadlock + if( _drawable.isRealized() ) { + if( !_drawable.getNativeSurface().isSurfaceLockedByOtherThread() && !helper.isAnimatorAnimatingOnOtherThread() ) { + display(); + } + } + } + } + + /** + * Default implementation to handle destroy notifications from the windowing system. + * + * <p> + * If the {@link NativeSurface} does not implement {@link WindowClosingProtocol} + * or {@link WindowClosingMode#DISPOSE_ON_CLOSE} is enabled (default), + * a thread safe destruction is being induced. + * </p> + */ + protected final void defaultWindowDestroyNotifyOp() { + final NativeSurface ns = getNativeSurface(); + final boolean shallClose; + if(ns instanceof WindowClosingProtocol) { + shallClose = WindowClosingMode.DISPOSE_ON_CLOSE == ((WindowClosingProtocol)ns).getDefaultCloseOperation(); + } else { + shallClose = true; + } + if( shallClose ) { + destroyAvoidAwareOfLocking(); + } + } + + /** + * Calls {@link #destroy()} + * directly if the following requirements are met: + * <ul> + * <li>An {@link GLAnimatorControl} is bound (see {@link #getAnimator()}) and running on another thread. + * Here we pause the animation while issuing the destruction.</li> + * <li>Surface is not locked by another thread (considered anonymous).</li> + * </ul> + * <p> + * Otherwise destroy is being flagged to be called within the next + * call of display(). + * </p> + * <p> + * This method is being used to avoid deadlock if + * destruction is desired by <i>other</i> threads, e.g. the window manager. + * </p> + * @see #defaultWindowDestroyNotifyOp() + * @see #defaultDisplay() + */ + protected final void destroyAvoidAwareOfLocking() { + final NativeSurface ns = getNativeSurface(); + + final GLAnimatorControl ctrl = helper.getAnimator(); + + // Is an animator thread perform rendering? + if ( helper.isAnimatorStartedOnOtherThread() ) { + // Pause animations before initiating safe destroy. + final boolean isPaused = ctrl.pause(); + destroy(); + if(isPaused) { + ctrl.resume(); + } + } else if (null != ns && ns.isSurfaceLockedByOtherThread()) { + // Surface is locked by another thread. + // Flag that destroy should be performed on the next + // attempt to display. + sendDestroy = true; // async, but avoiding deadlock + } else { + // Without an external thread animating or locking the + // surface, we are safe. + destroy(); + } + } + + /** + * Calls {@link #destroyImplInLock()} while claiming the lock. + */ + protected final void defaultDestroy() { + final RecursiveLock lock = getLock(); + lock.lock(); + try { + destroyImplInLock(); + } finally { + lock.unlock(); + } + } + + /** + * Default implementation to destroys the drawable and context of this GLAutoDrawable: + * <ul> + * <li>issues the GLEventListener dispose call, if drawable and context are valid</li> + * <li>destroys the GLContext, if valid</li> + * <li>destroys the GLDrawable, if valid</li> + * </ul> + * <p>Method assumes the lock is being hold.</p> + * <p>Override it to extend it to destroy your resources, i.e. the actual window. + * In such case call <code>super.destroyImplInLock</code> first.</p> + */ + protected void destroyImplInLock() { + if( preserveGLELSAtDestroy ) { + preserveGLStateAtDestroy(false); + preserveGLEventListenerState(); + } + if( null != context ) { + if( context.isCreated() ) { + // Catch dispose GLExceptions by GLEventListener, just 'print' them + // so we can continue with the destruction. + try { + helper.disposeGL(this, context, true); + } catch (GLException gle) { + gle.printStackTrace(); + } + } + context = null; + } + if( null != drawable ) { + final AbstractGraphicsDevice device = drawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice(); + drawable.setRealized(false); + drawable = null; + if( ownsDevice ) { + device.close(); + } + } + } + + public final void defaultSwapBuffers() throws GLException { + final RecursiveLock _lock = getLock(); + _lock.lock(); + try { + if(null != drawable) { + drawable.swapBuffers(); + } + } finally { + _lock.unlock(); + } + } + + // + // GLAutoDrawable + // + + protected final Runnable defaultInitAction = new Runnable() { + @Override + public final void run() { + // Lock: Locked Surface/Window by MakeCurrent/Release + helper.init(GLAutoDrawableBase.this, !sendReshape); + resetFPSCounter(); + } }; + + protected final Runnable defaultDisplayAction = new Runnable() { + @Override + public final void run() { + // Lock: Locked Surface/Window by display _and_ MakeCurrent/Release + if (sendReshape) { + helper.reshape(GLAutoDrawableBase.this, 0, 0, getWidth(), getHeight()); + sendReshape = false; + } + helper.display(GLAutoDrawableBase.this); + fpsCounter.tickFPS(); + } }; + + protected final void defaultDisplay() { + if( sendDestroy ) { + sendDestroy=false; + destroy(); + return; + } + final RecursiveLock _lock = getLock(); + _lock.lock(); + try { + if( null == context ) { + boolean contextCreated = false; + final GLDrawableImpl _drawable = drawable; + if ( null != _drawable && _drawable.isRealized() && 0<_drawable.getWidth()*_drawable.getHeight() ) { + final GLContext[] shareWith = { null }; + if( !helper.isSharedGLContextPending(shareWith) ) { + if( !restoreGLEventListenerState() ) { + context = (GLContextImpl) _drawable.createContext(shareWith[0]); + context.setContextCreationFlags(additionalCtxCreationFlags); + contextCreated = true; + // surface is locked/unlocked implicit by context's makeCurrent/release + helper.invokeGL(_drawable, context, defaultDisplayAction, defaultInitAction); + } + } + } + if(DEBUG) { + System.err.println("GLAutoDrawableBase.defaultDisplay: contextCreated "+contextCreated); + } + } else { + // surface is locked/unlocked implicit by context's makeCurrent/release + helper.invokeGL(drawable, context, defaultDisplayAction, defaultInitAction); + } + } finally { + _lock.unlock(); + } + } + + protected final GLEventListener defaultDisposeGLEventListener(GLEventListener listener, boolean remove) { + final RecursiveLock _lock = getLock(); + _lock.lock(); + try { + return helper.disposeGLEventListener(GLAutoDrawableBase.this, drawable, context, listener, remove); + } finally { + _lock.unlock(); + } + } + + @Override + public final GLDrawable getDelegatedDrawable() { + return drawable; + } + + @Override + public final GLContext getContext() { + return context; + } + + @Override + public final GLContext setContext(GLContext newCtx, boolean destroyPrevCtx) { + final RecursiveLock lock = getLock(); + lock.lock(); + try { + final GLContext oldCtx = context; + GLDrawableHelper.switchContext(drawable, oldCtx, destroyPrevCtx, newCtx, additionalCtxCreationFlags); + context=(GLContextImpl)newCtx; + return oldCtx; + } finally { + lock.unlock(); + } + } + + @Override + public final GL getGL() { + final GLContext _context = context; + if (_context == null) { + return null; + } + return _context.getGL(); + } + + @Override + public final GL setGL(GL gl) { + final GLContext _context = context; + if (_context != null) { + _context.setGL(gl); + return gl; + } + return null; + } + + @Override + public final void addGLEventListener(GLEventListener listener) { + helper.addGLEventListener(listener); + } + + @Override + public final void addGLEventListener(int index, GLEventListener listener) throws IndexOutOfBoundsException { + helper.addGLEventListener(index, listener); + } + + @Override + public int getGLEventListenerCount() { + return helper.getGLEventListenerCount(); + } + + @Override + public GLEventListener getGLEventListener(int index) throws IndexOutOfBoundsException { + return helper.getGLEventListener(index); + } + + @Override + public boolean areAllGLEventListenerInitialized() { + return helper.areAllGLEventListenerInitialized(); + } + + @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) { + return defaultDisposeGLEventListener(listener, remove); + } + + @Override + public final GLEventListener removeGLEventListener(GLEventListener listener) { + return helper.removeGLEventListener(listener); + } + + @Override + public final void setAnimator(GLAnimatorControl animatorControl) + throws GLException { + helper.setAnimator(animatorControl); + } + + @Override + public final GLAnimatorControl getAnimator() { + return helper.getAnimator(); + } + + @Override + public final Thread setExclusiveContextThread(Thread t) throws GLException { + return helper.setExclusiveContextThread(t, context); + } + + @Override + public final Thread getExclusiveContextThread() { + return helper.getExclusiveContextThread(); + } + + @Override + public final boolean invoke(boolean wait, GLRunnable glRunnable) { + return helper.invoke(this, wait, glRunnable); + } + + @Override + public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) { + return helper.invoke(this, wait, glRunnables); + } + + @Override + public final void setAutoSwapBufferMode(boolean enable) { + helper.setAutoSwapBufferMode(enable); + } + + @Override + public final boolean getAutoSwapBufferMode() { + return helper.getAutoSwapBufferMode(); + } + + @Override + public final void setContextCreationFlags(int flags) { + additionalCtxCreationFlags = flags; + final GLContext _context = context; + if(null != _context) { + _context.setContextCreationFlags(additionalCtxCreationFlags); + } + } + + @Override + public final int getContextCreationFlags() { + return additionalCtxCreationFlags; + } + + // + // FPSCounter + // + + @Override + public final void setUpdateFPSFrames(int frames, PrintStream out) { + fpsCounter.setUpdateFPSFrames(frames, out); + } + + @Override + public final void resetFPSCounter() { + fpsCounter.resetFPSCounter(); + } + + @Override + public final int getUpdateFPSFrames() { + return fpsCounter.getUpdateFPSFrames(); + } + + @Override + public final long getFPSStartTime() { + return fpsCounter.getFPSStartTime(); + } + + @Override + public final long getLastFPSUpdateTime() { + return fpsCounter.getLastFPSUpdateTime(); + } + + @Override + public final long getLastFPSPeriod() { + return fpsCounter.getLastFPSPeriod(); + } + + @Override + public final float getLastFPS() { + return fpsCounter.getLastFPS(); + } + + @Override + public final int getTotalFPSFrames() { + return fpsCounter.getTotalFPSFrames(); + } + + @Override + public final long getTotalFPSDuration() { + return fpsCounter.getTotalFPSDuration(); + } + + @Override + public final float getTotalFPS() { + return fpsCounter.getTotalFPS(); + } + + // + // GLDrawable delegation + // + + @Override + public final GLContext createContext(final GLContext shareWith) { + final RecursiveLock lock = getLock(); + lock.lock(); + try { + if(drawable != null) { + final GLContext _ctx = drawable.createContext(shareWith); + _ctx.setContextCreationFlags(additionalCtxCreationFlags); + return _ctx; + } + return null; + } finally { + lock.unlock(); + } + } + + @Override + public final void setRealized(boolean realized) { + final RecursiveLock _lock = getLock(); + _lock.lock(); + try { + final GLDrawable _drawable = drawable; + if( null == _drawable || realized && ( 0 >= _drawable.getWidth() || 0 >= _drawable.getHeight() ) ) { + return; + } + _drawable.setRealized(realized); + if( realized && _drawable.isRealized() ) { + sendReshape=true; // ensure a reshape is being send .. + } + } finally { + _lock.unlock(); + } + } + + @Override + public final boolean isRealized() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.isRealized() : false; + } + + @Override + public int getWidth() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getWidth() : 0; + } + + @Override + public int getHeight() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getHeight() : 0; + } + + @Override + public boolean isGLOriented() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.isGLOriented() : true; + } + + @Override + public final GLCapabilitiesImmutable getChosenGLCapabilities() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getChosenGLCapabilities() : null; + } + + @Override + public final GLProfile getGLProfile() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getGLProfile() : null; + } + + @Override + public final NativeSurface getNativeSurface() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getNativeSurface() : null; + } + + @Override + public final long getHandle() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getHandle() : 0; + } + + protected static String getThreadName() { return Thread.currentThread().getName(); } + + @Override + public String toString() { + return getClass().getSimpleName()+"[ \n\tHelper: " + helper + ", \n\tDrawable: " + drawable + + ", \n\tContext: " + context + /** ", \n\tWindow: "+window+ ", \n\tFactory: "+factory+ */ "]"; + } +} |