/** * 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 /** *
* The {@link GLContext} can be assigned later manually via {@link GLAutoDrawable#setContext(GLContext, boolean) setContext(ctx)}
* or it will be created lazily at the 1st {@link GLAutoDrawable#display() display()} method call.
* Lazy {@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}.
*
null
for lazy initialization
* @param context upstream {@link GLContextImpl} instance,
* may not have been made current (created) yet,
* may not be associated w/ drawable yet,
* may be null
for lazy initialization at 1st {@link #display()}.
* @param ownsDevice 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 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 true
if the {@link GLEventListenerState} is preserved successfully from this {@link GLAutoDrawable},
* otherwise false
.
*
* @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 true
if the {@link GLEventListenerState} was previously {@link #preserveGLEventListenerState() preserved}
* and is moved successfully to this {@link GLAutoDrawable},
* otherwise false
.
*
* @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.
*
*
* 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.
*
*/
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:
*
* - An {@link GLAnimatorControl} is bound (see {@link #getAnimator()}) and running on another thread.
* Here we pause the animation while issuing the destruction.
* - Surface is not locked by another thread (considered anonymous).
*
*
* 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.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:
*
* - issues the GLEventListener dispose call, if drawable and context are valid
* - destroys the GLContext, if valid
* - destroys the GLDrawable, if valid
*
* 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() {
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, getSurfaceWidth(), getSurfaceHeight());
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.getSurfaceWidth()*_drawable.getSurfaceHeight() ) {
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 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.getSurfaceWidth() || 0 >= _drawable.getSurfaceHeight() ) ) {
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 getSurfaceWidth() {
final GLDrawable _drawable = drawable;
return null != _drawable ? _drawable.getSurfaceWidth() : 0;
}
@Override
public int getSurfaceHeight() {
final GLDrawable _drawable = drawable;
return null != _drawable ? _drawable.getSurfaceHeight() : 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+ */ "]";
}
}