/** * Copyright 2011 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 com.jogamp.opengl.swt; import java.util.List; import javax.media.nativewindow.AbstractGraphicsConfiguration; import javax.media.nativewindow.AbstractGraphicsDevice; import javax.media.nativewindow.AbstractGraphicsScreen; import javax.media.nativewindow.GraphicsConfigurationFactory; import javax.media.nativewindow.NativeSurface; import javax.media.nativewindow.NativeWindowException; import javax.media.nativewindow.ProxySurface; import javax.media.nativewindow.UpstreamSurfaceHook; import javax.media.nativewindow.VisualIDHolder; import javax.media.nativewindow.VisualIDHolder.VIDType; import javax.media.opengl.GL; import javax.media.opengl.GLAnimatorControl; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLCapabilities; import javax.media.opengl.GLCapabilitiesChooser; import javax.media.opengl.GLCapabilitiesImmutable; import javax.media.opengl.GLContext; import javax.media.opengl.GLDrawable; import javax.media.opengl.GLDrawableFactory; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLException; import javax.media.opengl.GLProfile; import javax.media.opengl.GLRunnable; import javax.media.opengl.GLSharedContextSetter; import javax.media.opengl.Threading; import jogamp.nativewindow.x11.X11Util; import jogamp.opengl.Debug; import jogamp.opengl.GLContextImpl; import jogamp.opengl.GLDrawableHelper; import jogamp.opengl.GLDrawableImpl; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import com.jogamp.common.GlueGenVersion; import com.jogamp.common.os.Platform; import com.jogamp.common.util.VersionUtil; import com.jogamp.common.util.locks.LockFactory; import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.nativewindow.swt.SWTAccessor; import com.jogamp.nativewindow.x11.X11GraphicsDevice; import com.jogamp.opengl.JoglVersion; /** * Native SWT Canvas implementing GLAutoDrawable *
* Implementation allows use of custom {@link GLCapabilities}. *
*/ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextSetter { private static final boolean DEBUG = Debug.debug("GLCanvas"); /* * Flag for whether the SWT thread should be used for OpenGL calls when in single-threaded mode. This is controlled * by the setting of the threading mode to worker (do not use SWT thread), awt (use SWT thread), or false (always use * calling thread). * * @see Threading * * Now done dynamically to avoid early loading of gluegen library. */ //private static final boolean useSWTThread = ThreadingImpl.getMode() != ThreadingImpl.WORKER; /* GL Stuff */ private final RecursiveLock lock = LockFactory.createRecursiveLock(); private final GLDrawableHelper helper = new GLDrawableHelper(); private final GLCapabilitiesImmutable capsRequested; private final GLCapabilitiesChooser capsChooser; private volatile Rectangle clientArea; private volatile GLDrawableImpl drawable; // volatile: avoid locking for read-only access private volatile GLContextImpl context; // volatile: avoid locking for read-only access /* Native window surface */ private final boolean useX11GTK; private volatile long gdkWindow; // either GDK child window .. private volatile long x11Window; // .. or X11 child window (for GL rendering) private final AbstractGraphicsScreen screen; /* Construction parameters stored for GLAutoDrawable accessor methods */ private int additionalCtxCreationFlags = 0; /* Flag indicating whether an unprocessed reshape is pending. */ private volatile boolean sendReshape; // volatile: maybe written by WindowManager thread w/o locking private static String getThreadName() { return Thread.currentThread().getName(); } private static String toHexString(final int v) { return "0x"+Integer.toHexString(v); } private static String toHexString(final long v) { return "0x"+Long.toHexString(v); } /* * Invokes init(...) on all GLEventListeners. Assumes context is current when run. */ private final Runnable initAction = new Runnable() { @Override public void run() { helper.init(GLCanvas.this, !sendReshape); } }; /* * Action to handle display in OpenGL, also processes reshape since they should be done at the same time. * * Assumes GLContext is current when run. */ private final Runnable displayAction = new Runnable() { @Override public void run() { if (sendReshape) { helper.reshape(GLCanvas.this, 0, 0, clientArea.width, clientArea.height); sendReshape = false; } helper.display(GLCanvas.this); } }; /* Action to make specified context current prior to running displayAction */ private final Runnable makeCurrentAndDisplayOnGLAction = new Runnable() { @Override public void run() { final RecursiveLock _lock = lock; _lock.lock(); try { if( !GLCanvas.this.isDisposed() ) { helper.invokeGL(drawable, context, displayAction, initAction); } } finally { _lock.unlock(); } } }; /* Swaps buffers, assuming the GLContext is current */ private final Runnable swapBuffersOnGLAction = new Runnable() { @Override public void run() { final RecursiveLock _lock = lock; _lock.lock(); try { final boolean drawableOK = null != drawable && drawable.isRealized(); if( drawableOK && !GLCanvas.this.isDisposed() ) { drawable.swapBuffers(); } } finally { _lock.unlock(); } } }; /* * Disposes of OpenGL resources */ private final Runnable disposeOnEDTGLAction = new Runnable() { @Override public void run() { final RecursiveLock _lock = lock; _lock.lock(); try { final GLAnimatorControl animator = getAnimator(); final boolean animatorPaused; if(null!=animator) { // can't remove us from animator for recreational addNotify() animatorPaused = animator.pause(); } else { animatorPaused = false; } if ( null != context ) { if( context.isCreated() ) { // Catch dispose GLExceptions by GLEventListener, just 'print' them // so we can continue with the destruction. try { if( !GLCanvas.this.isDisposed() ) { helper.disposeGL(GLCanvas.this, context, true); } else { context.destroy(); } } catch (final GLException gle) { gle.printStackTrace(); } } context = null; } if ( null != drawable ) { drawable.setRealized(false); drawable = null; } if( 0 != x11Window) { SWTAccessor.destroyX11Window(screen.getDevice(), x11Window); x11Window = 0; } else if( 0 != gdkWindow) { SWTAccessor.destroyGDKWindow(gdkWindow); gdkWindow = 0; } screen.getDevice().close(); if (animatorPaused) { animator.resume(); } } finally { _lock.unlock(); } } }; private class DisposeGLEventListenerAction implements Runnable { private GLEventListener listener; private final boolean remove; private DisposeGLEventListenerAction(final GLEventListener listener, final boolean remove) { this.listener = listener; this.remove = remove; } @Override public void run() { final RecursiveLock _lock = lock; _lock.lock(); try { if( !GLCanvas.this.isDisposed() ) { listener = helper.disposeGLEventListener(GLCanvas.this, drawable, context, listener, remove); } } finally { _lock.unlock(); } } }; /** * Creates an instance using {@link #GLCanvas(Composite, int, GLCapabilitiesImmutable, GLCapabilitiesChooser)} * on the SWT thread. * * @param parent * Required (non-null) parent Composite. * @param style * Optional SWT style bit-field. The {@link SWT#NO_BACKGROUND} bit is set before passing this up to the * Canvas constructor, so OpenGL handles the background. * @param caps * Optional GLCapabilities. If not provided, the default capabilities for the default GLProfile for the * graphics device determined by the parent Composite are used. Note that the GLCapabilities that are * actually used may differ based on the capabilities of the graphics device. * @param chooser * Optional GLCapabilitiesChooser to customize the selection of the used GLCapabilities based on the * requested GLCapabilities, and the available capabilities of the graphics device. * @return a new instance */ public static GLCanvas create(final Composite parent, final int style, final GLCapabilitiesImmutable caps, final GLCapabilitiesChooser chooser) { final GLCanvas[] res = new GLCanvas[] { null }; parent.getDisplay().syncExec(new Runnable() { @Override public void run() { res[0] = new GLCanvas( parent, style, caps, chooser ); } }); return res[0]; } /** * Creates a new SWT GLCanvas. * * @param parent * Required (non-null) parent Composite. * @param style * Optional SWT style bit-field. The {@link SWT#NO_BACKGROUND} bit is set before passing this up to the * Canvas constructor, so OpenGL handles the background. * @param capsReqUser * Optional GLCapabilities. If not provided, the default capabilities for the default GLProfile for the * graphics device determined by the parent Composite are used. Note that the GLCapabilities that are * actually used may differ based on the capabilities of the graphics device. * @param capsChooser * Optional GLCapabilitiesChooser to customize the selection of the used GLCapabilities based on the * requested GLCapabilities, and the available capabilities of the graphics device. */ public GLCanvas(final Composite parent, final int style, final GLCapabilitiesImmutable capsReqUser, final GLCapabilitiesChooser capsChooser) { /* NO_BACKGROUND required to avoid clearing bg in native SWT widget (we do this in the GL display) */ super(parent, style | SWT.NO_BACKGROUND); GLProfile.initSingleton(); // ensure JOGL is completly initialized SWTAccessor.setRealized(this, true); clientArea = GLCanvas.this.getClientArea(); /* Get the nativewindow-Graphics Device associated with this control (which is determined by the parent Composite). * Note: SWT is owner of the native handle, hence closing operation will be a NOP. */ final AbstractGraphicsDevice swtDevice = SWTAccessor.getDevice(this); useX11GTK = SWTAccessor.useX11GTK(); if(useX11GTK) { // Decoupled X11 Device/Screen allowing X11 display lock-free off-thread rendering final long x11DeviceHandle = X11Util.openDisplay(swtDevice.getConnection()); if( 0 == x11DeviceHandle ) { throw new RuntimeException("Error creating display(EDT): "+swtDevice.getConnection()); } final AbstractGraphicsDevice x11Device = new X11GraphicsDevice(x11DeviceHandle, AbstractGraphicsDevice.DEFAULT_UNIT, true /* owner */); screen = SWTAccessor.getScreen(x11Device, -1 /* default */); } else { screen = SWTAccessor.getScreen(swtDevice, -1 /* default */); } /* Select default GLCapabilities if none was provided, otherwise use cloned provided caps */ if(null == capsReqUser) { this.capsRequested = new GLCapabilities(GLProfile.getDefault(screen.getDevice())); } else { this.capsRequested = (GLCapabilitiesImmutable) capsReqUser.cloneMutable(); } this.capsChooser = capsChooser; // post create .. when ready gdkWindow = 0; x11Window = 0; drawable = null; context = null; final Listener listener = new Listener () { @Override public void handleEvent (final Event event) { switch (event.type) { case SWT.Paint: displayIfNoAnimatorNoCheck(); break; case SWT.Resize: updateSizeCheck(); break; case SWT.Dispose: GLCanvas.this.dispose(); break; } } }; addListener (SWT.Resize, listener); addListener (SWT.Paint, listener); addListener (SWT.Dispose, listener); } @Override public final void setSharedContext(final GLContext sharedContext) throws IllegalStateException { helper.setSharedContext(this.context, sharedContext); } @Override public final void setSharedAutoDrawable(final GLAutoDrawable sharedAutoDrawable) throws IllegalStateException { helper.setSharedAutoDrawable(this, sharedAutoDrawable); } private final UpstreamSurfaceHook swtCanvasUpStreamHook = new UpstreamSurfaceHook() { @Override public final void create(final ProxySurface s) { /* nop */ } @Override public final void destroy(final ProxySurface s) { /* nop */ } @Override public final int getSurfaceWidth(final ProxySurface s) { return clientArea.width; } @Override public final int getSurfaceHeight(final ProxySurface s) { return clientArea.height; } @Override public String toString() { return "SWTCanvasUpstreamSurfaceHook[upstream: "+GLCanvas.this.toString()+", "+clientArea.width+"x"+clientArea.height+"]"; } /** * {@inheritDoc} *
* Returns null
.
*
* This impl. calls this class's {@link #dispose()} SWT override, * where the actual implementation resides. *
*/ @Override public void destroy() { dispose(); } @Override public GLAnimatorControl getAnimator() { return helper.getAnimator(); } @Override public final Thread setExclusiveContextThread(final Thread t) throws GLException { return helper.setExclusiveContextThread(t, context); } @Override public final Thread getExclusiveContextThread() { return helper.getExclusiveContextThread(); } @Override public boolean getAutoSwapBufferMode() { return helper.getAutoSwapBufferMode(); } @Override public final GLDrawable getDelegatedDrawable() { return drawable; } @Override public GLContext getContext() { return context; } @Override public int getContextCreationFlags() { return additionalCtxCreationFlags; } @Override public GL getGL() { final GLContext _context = context; return (null == _context) ? null : _context.getGL(); } @Override public boolean invoke(final boolean wait, final GLRunnable runnable) { return helper.invoke(this, wait, runnable); } @Override public boolean invoke(final boolean wait, final List