/** * 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.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 { 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 GLContext shareWith; 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; /* 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 /* * 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 (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(GLEventListener listener, 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, GLContext)} * 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. * @param shareWith * Optional GLContext to share state (textures, vbos, shaders, etc.) with. * @return a new instance */ public static GLCanvas create(final Composite parent, final int style, final GLCapabilitiesImmutable caps, final GLCapabilitiesChooser chooser, final GLContext shareWith) { final GLCanvas[] res = new GLCanvas[] { null }; parent.getDisplay().syncExec(new Runnable() { @Override public void run() { res[0] = new GLCanvas( parent, style, caps, chooser, shareWith ); } }); 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. * @param shareWith * Optional GLContext to share state (textures, vbos, shaders, etc.) with. */ public GLCanvas(final Composite parent, final int style, GLCapabilitiesImmutable capsReqUser, final GLCapabilitiesChooser capsChooser, final GLContext shareWith) { /* 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 clone provided caps to ensure safety */ if(null == capsReqUser) { capsReqUser = new GLCapabilities(GLProfile.getDefault(screen.getDevice())); } this.capsRequested = capsReqUser; this.capsChooser = capsChooser; this.shareWith = shareWith; // post create .. when ready gdkWindow = 0; x11Window = 0; drawable = null; context = null; final Listener listener = new Listener () { @Override public void handleEvent (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); } private final UpstreamSurfaceHook swtCanvasUpStreamHook = new UpstreamSurfaceHook() { @Override public final void create(ProxySurface s) { /* nop */ } @Override public final void destroy(ProxySurface s) { /* nop */ } @Override public final int getWidth(ProxySurface s) { return clientArea.width; } @Override public final int getHeight(ProxySurface s) { return clientArea.height; } @Override public String toString() { return "SWTCanvasUpstreamSurfaceHook[upstream: "+GLCanvas.this.toString()+", "+clientArea.width+"x"+clientArea.height+"]"; } }; protected final void updateSizeCheck() { final Rectangle oClientArea = clientArea; final Rectangle nClientArea = GLCanvas.this.getClientArea(); if ( nClientArea != null && ( nClientArea.width != oClientArea.width || nClientArea.height != oClientArea.height ) ) { clientArea = nClientArea; // write back new value final GLDrawableImpl _drawable = drawable; final boolean drawableOK = null != _drawable && _drawable.isRealized(); if(DEBUG) { final long dh = drawableOK ? _drawable.getHandle() : 0; System.err.println("GLCanvas.sizeChanged: ("+Thread.currentThread().getName()+"): "+nClientArea.x+"/"+nClientArea.y+" "+nClientArea.width+"x"+nClientArea.height+" - drawableHandle 0x"+Long.toHexString(dh)); } if( drawableOK ) { if( ! _drawable.getChosenGLCapabilities().isOnscreen() ) { final RecursiveLock _lock = lock; _lock.lock(); try { final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, context, nClientArea.width, nClientArea.height); if(_drawable != _drawableNew) { // write back drawable = _drawableNew; } } finally { _lock.unlock(); } } } if(0 != x11Window) { SWTAccessor.resizeX11Window(screen.getDevice(), clientArea, x11Window); } else if(0 != gdkWindow) { SWTAccessor.resizeGDKWindow(clientArea, gdkWindow); } sendReshape = true; // async if display() doesn't get called below, but avoiding deadlock } } private boolean isValidAndVisibleOnEDTActionResult; private final Runnable isValidAndVisibleOnEDTAction = new Runnable() { @Override public void run() { isValidAndVisibleOnEDTActionResult = !GLCanvas.this.isDisposed() && GLCanvas.this.isVisible(); } }; private final boolean isValidAndVisibleOnEDT() { synchronized(isValidAndVisibleOnEDTAction) { runOnEDTIfAvail(true, isValidAndVisibleOnEDTAction); return isValidAndVisibleOnEDTActionResult; } } /** assumes drawable == null || !drawable.isRealized() ! Checks of !isDispose() and isVisible() */ protected final boolean validateDrawableAndContextWithCheck() { if( !isValidAndVisibleOnEDT() ) { return false; } return validateDrawableAndContextPostCheck(); } /** assumes drawable == null || !drawable.isRealized() ! No check of !isDispose() and isVisible() */ protected final boolean validateDrawableAndContextPostCheck() { final Rectangle nClientArea = clientArea; if(0 >= nClientArea.width || 0 >= nClientArea.height) { return false; } final boolean res; final RecursiveLock _lock = lock; _lock.lock(); try { if(null == drawable) { createDrawableAndContext(); } if(null != drawable) { drawable.setRealized(true); res = drawable.isRealized(); } else { res = false; } } finally { _lock.unlock(); } if(res) { sendReshape = true; if(DEBUG) { System.err.println("SWT GLCanvas realized! "+this+", "+drawable); // Thread.dumpStack(); } } return res; } private final void createDrawableAndContext() { final AbstractGraphicsDevice device = screen.getDevice(); device.open(); final long nativeWindowHandle; if( useX11GTK ) { final GraphicsConfigurationFactory factory = GraphicsConfigurationFactory.getFactory(device, capsRequested); final AbstractGraphicsConfiguration cfg = factory.chooseGraphicsConfiguration( capsRequested, capsRequested, capsChooser, screen, VisualIDHolder.VID_UNDEFINED); if(DEBUG) { System.err.println("SWT.GLCanvas.X11 factory: "+factory+", chosen config: "+cfg); } if (null == cfg) { throw new NativeWindowException("Error choosing GraphicsConfiguration creating window: "+this); } final int visualID = cfg.getVisualID(VIDType.NATIVE); if( VisualIDHolder.VID_UNDEFINED != visualID ) { // gdkWindow = SWTAccessor.createCompatibleGDKChildWindow(this, visualID, clientArea.width, clientArea.height); // nativeWindowHandle = SWTAccessor.gdk_window_get_xwindow(gdkWindow); x11Window = SWTAccessor.createCompatibleX11ChildWindow(screen, this, visualID, clientArea.width, clientArea.height); nativeWindowHandle = x11Window; } else { throw new GLException("Could not choose valid visualID: 0x"+Integer.toHexString(visualID)+", "+this); } } else { nativeWindowHandle = SWTAccessor.getWindowHandle(this); } final GLDrawableFactory glFactory = GLDrawableFactory.getFactory(capsRequested.getGLProfile()); // Create a NativeWindow proxy for the SWT canvas ProxySurface proxySurface = glFactory.createProxySurface(device, screen.getIndex(), nativeWindowHandle, capsRequested, capsChooser, swtCanvasUpStreamHook); // Associate a GL surface with the proxy drawable = (GLDrawableImpl) glFactory.createGLDrawable(proxySurface); context = (GLContextImpl) drawable.createContext(shareWith); context.setContextCreationFlags(additionalCtxCreationFlags); } @Override public void update() { // don't paint background etc .. nop avoids flickering // super.update(); } /** @Override public boolean forceFocus() { final boolean r = super.forceFocus(); if(r && 0 != gdkWindow) { SWTGTKUtil.focusGDKWindow(gdkWindow); } return r; } */ @Override public void dispose() { runInGLThread(disposeOnEDTGLAction); super.dispose(); } private final void displayIfNoAnimatorNoCheck() { if ( !helper.isAnimatorAnimatingOnOtherThread() ) { final boolean drawableOK = null != drawable && drawable.isRealized(); if( drawableOK || validateDrawableAndContextPostCheck() ) { runInGLThread(makeCurrentAndDisplayOnGLAction); } } } // // GL[Auto]Drawable // @Override public void display() { final boolean drawableOK = null != drawable && drawable.isRealized(); if( drawableOK || validateDrawableAndContextWithCheck() ) { runInGLThread(makeCurrentAndDisplayOnGLAction); } } @Override public final Object getUpstreamWidget() { return this; } @Override public int getWidth() { return clientArea.width; } @Override public int getHeight() { return clientArea.height; } @Override public boolean isGLOriented() { final GLDrawable _drawable = drawable; return null != _drawable ? _drawable.isGLOriented() : true; } @Override public void addGLEventListener(final GLEventListener listener) { helper.addGLEventListener(listener); } @Override public void addGLEventListener(final int idx, final GLEventListener listener) throws IndexOutOfBoundsException { helper.addGLEventListener(idx, listener); } @Override public int getGLEventListenerCount() { return helper.getGLEventListenerCount(); } @Override 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); runInGLThread(r); return r.listener; } @Override public GLEventListener removeGLEventListener(final GLEventListener listener) { return helper.removeGLEventListener(listener); } /** * {@inheritDoc} * ** 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(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