/** * 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 javax.media.nativewindow.AbstractGraphicsConfiguration; import javax.media.nativewindow.AbstractGraphicsDevice; import javax.media.nativewindow.NativeSurface; 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.GLAutoDrawableDelegate; 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.GLProfile; import javax.media.opengl.GLRunnable; import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.opengl.util.Animator; /** * Abstract common code for GLAutoDrawable implementations. * * @see GLAutoDrawable * @see GLAutoDrawableDelegate * @see GLPBufferImpl * @see GLWindow */ public abstract class GLAutoDrawableBase implements GLAutoDrawable, FPSCounter { public static final boolean DEBUG = Debug.debug("GLAutoDrawable"); protected final GLDrawableHelper helper = new GLDrawableHelper(); protected final FPSCounterImpl fpsCounter = new FPSCounterImpl(); protected volatile GLDrawableImpl drawable; // volatile: avoid locking for read-only access protected GLContextImpl context; protected final boolean ownDevice; 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 /** * @param drawable a valid {@link GLDrawableImpl}, may not be realized yet. * @param context a valid {@link GLContextImpl}, may not be made current (created) yet. * @param ownDevice pass true if {@link AbstractGraphicsDevice#close()} shall be issued, * otherwise pass false. 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 ownDevice) { this.drawable = drawable; this.context = context; this.ownDevice = ownDevice; resetFPSCounter(); } protected abstract RecursiveLock getLock(); /** Returns the delegated GLDrawable */ public final GLDrawable getDelegatedDrawable() { return drawable; } /** 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.isAnimatorAnimating() ) { display(); } } } /** Default implementation to handle resize events from the windowing system */ protected final void defaultWindowResizedOp() { final GLDrawable _drawable = drawable; if( null!=_drawable ) { if(DEBUG) { System.err.println("GLAutoDrawableBase.sizeChanged: ("+Thread.currentThread().getName()+"): "+getWidth()+"x"+getHeight()+" - surfaceHandle 0x"+Long.toHexString(getNativeSurface().getSurfaceHandle())); } sendReshape = true; // async if display() doesn't get called below, but avoiding deadlock if( _drawable.isRealized() ) { if( !_drawable.getNativeSurface().isSurfaceLockedByOtherThread() && !helper.isAnimatorAnimating() ) { display(); } } } } /** * Default implementation to handle destroy notifications from the windowing system. * *

* If the {@link NativeSurface} does not implement {@link WindowClosingProtocol} * or {@link WindowClosingMode#DISPOSE_ON_CLOSE} is enabled (default), * {@link #defaultDestroy()} is being called. *

*/ 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: * *

* Otherwise destroy is being flagged to be called within the next * call of display(). *

*

* This method is being used to avoid deadlock if * destruction is desired by other threads, e.g. the window manager. *

* @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.isAnimatorRunningOnOtherThread() ) { // 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: * *

Method assumes the lock is being hold.

*

Override it to extend it to destroy your resources, i.e. the actual window. * In such case call super.destroyImplInLock first.

*/ protected void destroyImplInLock() { final GLContext _context = context; final GLDrawable _drawable = drawable; if( null != _drawable ) { if( _drawable.isRealized() ) { if( null != _context && _context.isCreated() ) { // Catch dispose GLExceptions by GLEventListener, just 'print' them // so we can continue with the destruction. try { helper.disposeGL(this, _drawable, _context, null); } catch (GLException gle) { gle.printStackTrace(); } } _drawable.setRealized(false); } if( ownDevice ) { _drawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice().close(); } } context = null; drawable = null; } public final void defaultSwapBuffers() throws GLException { final RecursiveLock _lock = getLock(); _lock.lock(); try { if(drawable!=null && context != null) { drawable.swapBuffers(); helper.invokeGL(drawable, context, defaultSwapAction, defaultInitAction); } } 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); 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 ) { // surface is locked/unlocked implicit by context's makeCurrent/release helper.invokeGL(drawable, context, defaultDisplayAction, defaultInitAction); } } finally { _lock.unlock(); } } protected final Runnable defaultSwapAction = new Runnable() { @Override public final void run() { drawable.swapBuffers(); } } ; @Override public final GLContext getContext() { return context; } @Override public final GLContext setContext(GLContext newCtx) { final RecursiveLock lock = getLock(); lock.lock(); try { final GLContext oldCtx = context; final boolean newCtxCurrent = helper.switchContext(drawable, oldCtx, newCtx, additionalCtxCreationFlags); context=(GLContextImpl)newCtx; if(newCtxCurrent) { context.makeCurrent(); } 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 final void removeGLEventListener(GLEventListener listener) { helper.removeGLEventListener(listener); } @Override public GLEventListener removeGLEventListener(int index) throws IndexOutOfBoundsException { return helper.removeGLEventListener(index); } @Override public final void setAnimator(GLAnimatorControl animatorControl) throws GLException { helper.setAnimator(animatorControl); } @Override public final GLAnimatorControl getAnimator() { return helper.getAnimator(); } @Override public final boolean invoke(boolean wait, GLRunnable glRunnable) { return helper.invoke(this, wait, glRunnable); } @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 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; } /** * @param t the thread for which context release shall be skipped, usually the animation thread, * ie. {@link Animator#getThread()}. * @deprecated this is an experimental feature, * intended for measuring performance in regards to GL context switch */ @Deprecated public void setSkipContextReleaseThread(Thread t) { helper.setSkipContextReleaseThread(t); } /** * @deprecated see {@link #setSkipContextReleaseThread(Thread)} */ @Deprecated public Thread getSkipContextReleaseThread() { return helper.getSkipContextReleaseThread(); } @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; } }