aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/com/jogamp/opengl/awt
diff options
context:
space:
mode:
Diffstat (limited to 'src/jogl/classes/com/jogamp/opengl/awt')
-rw-r--r--src/jogl/classes/com/jogamp/opengl/awt/AWTGLAutoDrawable.java51
-rw-r--r--src/jogl/classes/com/jogamp/opengl/awt/ComponentEvents.java75
-rw-r--r--src/jogl/classes/com/jogamp/opengl/awt/GLCanvas.java1643
-rw-r--r--src/jogl/classes/com/jogamp/opengl/awt/GLJPanel.java2689
4 files changed, 4458 insertions, 0 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/awt/AWTGLAutoDrawable.java b/src/jogl/classes/com/jogamp/opengl/awt/AWTGLAutoDrawable.java
new file mode 100644
index 000000000..6e273e4e6
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/opengl/awt/AWTGLAutoDrawable.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ * Sun gratefully acknowledges that this software was originally authored
+ * and developed by Kenneth Bradley Russell and Christopher John Kline.
+ */
+
+package com.jogamp.opengl.awt;
+
+import com.jogamp.opengl.GLAutoDrawable;
+
+public interface AWTGLAutoDrawable extends GLAutoDrawable, ComponentEvents {
+ /** Requests a new width and height for this AWTGLAutoDrawable. */
+ public void setSize(int width, int height);
+
+ /** Schedules a repaint of the component at some point in the
+ future. */
+ public void repaint();
+}
diff --git a/src/jogl/classes/com/jogamp/opengl/awt/ComponentEvents.java b/src/jogl/classes/com/jogamp/opengl/awt/ComponentEvents.java
new file mode 100644
index 000000000..996776c9b
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/opengl/awt/ComponentEvents.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ * Sun gratefully acknowledges that this software was originally authored
+ * and developed by Kenneth Bradley Russell and Christopher John Kline.
+ */
+
+package com.jogamp.opengl.awt;
+
+import com.jogamp.opengl.*;
+import java.awt.event.*;
+import java.beans.PropertyChangeListener;
+
+/** Factors out the listener manipulation for the events supported by
+ all of the {@link GLDrawable} implementations. Provided to reduce
+ clutter in the documentation for GLDrawable. */
+
+public interface ComponentEvents {
+ public void addComponentListener(ComponentListener l);
+ public void removeComponentListener(ComponentListener l);
+ public void addFocusListener(FocusListener l);
+ public void removeFocusListener(FocusListener l);
+ public void addHierarchyBoundsListener(HierarchyBoundsListener l);
+ public void removeHierarchyBoundsListener(HierarchyBoundsListener l);
+ public void addHierarchyListener(HierarchyListener l);
+ public void removeHierarchyListener(HierarchyListener l);
+ public void addInputMethodListener(InputMethodListener l);
+ public void removeInputMethodListener(InputMethodListener l);
+ public void addKeyListener(KeyListener l);
+ public void removeKeyListener(KeyListener l);
+ public void addMouseListener(MouseListener l);
+ public void removeMouseListener(MouseListener l);
+ public void addMouseMotionListener(MouseMotionListener l);
+ public void removeMouseMotionListener(MouseMotionListener l);
+ public void addMouseWheelListener(MouseWheelListener l);
+ public void removeMouseWheelListener(MouseWheelListener l);
+ public void addPropertyChangeListener(PropertyChangeListener listener);
+ public void removePropertyChangeListener(PropertyChangeListener listener);
+ public void addPropertyChangeListener(String propertyName,
+ PropertyChangeListener listener);
+ public void removePropertyChangeListener(String propertyName,
+ PropertyChangeListener listener);
+}
diff --git a/src/jogl/classes/com/jogamp/opengl/awt/GLCanvas.java b/src/jogl/classes/com/jogamp/opengl/awt/GLCanvas.java
new file mode 100644
index 000000000..11d217535
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/opengl/awt/GLCanvas.java
@@ -0,0 +1,1643 @@
+/*
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2010 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:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ * Sun gratefully acknowledges that this software was originally authored
+ * and developed by Kenneth Bradley Russell and Christopher John Kline.
+ */
+
+package com.jogamp.opengl.awt;
+
+import java.beans.Beans;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.awt.Canvas;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Rectangle2D;
+import java.awt.EventQueue;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.jogamp.nativewindow.AbstractGraphicsConfiguration;
+import com.jogamp.nativewindow.OffscreenLayerOption;
+import com.jogamp.nativewindow.ScalableSurface;
+import com.jogamp.nativewindow.VisualIDHolder;
+import com.jogamp.nativewindow.WindowClosingProtocol;
+import com.jogamp.nativewindow.AbstractGraphicsDevice;
+import com.jogamp.nativewindow.AbstractGraphicsScreen;
+import com.jogamp.nativewindow.GraphicsConfigurationFactory;
+import com.jogamp.nativewindow.NativeSurface;
+import com.jogamp.nativewindow.NativeWindowFactory;
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.GLAnimatorControl;
+import com.jogamp.opengl.GLAutoDrawable;
+import com.jogamp.opengl.GLCapabilities;
+import com.jogamp.opengl.GLCapabilitiesChooser;
+import com.jogamp.opengl.GLCapabilitiesImmutable;
+import com.jogamp.opengl.GLContext;
+import com.jogamp.opengl.GLDrawable;
+import com.jogamp.opengl.GLDrawableFactory;
+import com.jogamp.opengl.GLEventListener;
+import com.jogamp.opengl.GLException;
+import com.jogamp.opengl.GLOffscreenAutoDrawable;
+import com.jogamp.opengl.GLProfile;
+import com.jogamp.opengl.GLRunnable;
+import com.jogamp.opengl.GLSharedContextSetter;
+import com.jogamp.opengl.Threading;
+
+import com.jogamp.common.GlueGenVersion;
+import com.jogamp.common.util.VersionUtil;
+import com.jogamp.common.util.awt.AWTEDTExecutor;
+import com.jogamp.common.util.locks.LockFactory;
+import com.jogamp.common.util.locks.RecursiveLock;
+import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration;
+import com.jogamp.nativewindow.awt.AWTGraphicsDevice;
+import com.jogamp.nativewindow.awt.AWTGraphicsScreen;
+import com.jogamp.nativewindow.awt.AWTPrintLifecycle;
+import com.jogamp.nativewindow.awt.AWTWindowClosingProtocol;
+import com.jogamp.nativewindow.awt.JAWTWindow;
+import com.jogamp.opengl.JoglVersion;
+import com.jogamp.opengl.util.GLDrawableUtil;
+import com.jogamp.opengl.util.TileRenderer;
+
+import jogamp.nativewindow.SurfaceScaleUtils;
+import jogamp.opengl.Debug;
+import jogamp.opengl.GLContextImpl;
+import jogamp.opengl.GLDrawableHelper;
+import jogamp.opengl.GLDrawableImpl;
+import jogamp.opengl.awt.AWTTilePainter;
+
+// FIXME: Subclasses need to call resetGLFunctionAvailability() on their
+// context whenever the displayChanged() function is called on our
+// GLEventListeners
+
+/** A heavyweight AWT component which provides OpenGL rendering
+ support. This is the primary implementation of an AWT {@link GLDrawable};
+ {@link GLJPanel} is provided for compatibility with Swing user
+ interfaces when adding a heavyweight doesn't work either because
+ of Z-ordering or LayoutManager problems.
+ *
+ * <h5><a name="offscreenlayer">Offscreen Layer Remarks</a></h5>
+ *
+ * {@link OffscreenLayerOption#setShallUseOffscreenLayer(boolean) setShallUseOffscreenLayer(true)}
+ * maybe called to use an offscreen drawable (FBO or PBuffer) allowing
+ * the underlying JAWT mechanism to composite the image, if supported.
+ * <p>
+ * {@link OffscreenLayerOption#setShallUseOffscreenLayer(boolean) setShallUseOffscreenLayer(true)}
+ * is being called if {@link GLCapabilitiesImmutable#isOnscreen()} is <code>false</code>.
+ * </p>
+ *
+ * <h5><a name="java2dgl">Java2D OpenGL Remarks</a></h5>
+ *
+ * To avoid any conflicts with a potential Java2D OpenGL context,<br>
+ * you shall consider setting the following JVM properties:<br>
+ * <ul>
+ * <li><pre>sun.java2d.opengl=false</pre></li>
+ * <li><pre>sun.java2d.noddraw=true</pre></li>
+ * </ul>
+ * This is especially true in case you want to utilize a GLProfile other than
+ * {@link GLProfile#GL2}, eg. using {@link GLProfile#getMaxFixedFunc()}.<br>
+ * On the other hand, if you like to experiment with GLJPanel's utilization
+ * of Java2D's OpenGL pipeline, you have to set them to
+ * <ul>
+ * <li><pre>sun.java2d.opengl=true</pre></li>
+ * <li><pre>sun.java2d.noddraw=true</pre></li>
+ * </ul>
+ *
+ * <h5><a name="backgrounderase">Disable Background Erase</a></h5>
+ *
+ * GLCanvas tries to disable background erase for the AWT Canvas
+ * before native peer creation (X11) and after it (Windows), <br>
+ * utilizing the optional {@link java.awt.Toolkit} method <code>disableBeackgroundErase(java.awt.Canvas)</code>.<br>
+ * However if this does not give you the desired results, you may want to disable AWT background erase in general:
+ * <ul>
+ * <li><pre>sun.awt.noerasebackground=true</pre></li>
+ * </ul>
+ *
+ * <p>
+ * <a name="contextSharing"><h5>OpenGL Context Sharing</h5></a>
+ * To share a {@link GLContext} see the following note in the documentation overview:
+ * <a href="../../../../overview-summary.html#SHARING">context sharing</a>
+ * as well as {@link GLSharedContextSetter}.
+ * </p>
+ */
+
+@SuppressWarnings("serial")
+public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosingProtocol, OffscreenLayerOption,
+ AWTPrintLifecycle, GLSharedContextSetter, ScalableSurface {
+
+ private static final boolean DEBUG = Debug.debug("GLCanvas");
+
+ private final RecursiveLock lock = LockFactory.createRecursiveLock();
+ private final GLDrawableHelper helper = new GLDrawableHelper();
+ private AWTGraphicsConfiguration awtConfig;
+ private volatile GLDrawableImpl drawable; // volatile: avoid locking for read-only access
+ private volatile JAWTWindow jawtWindow; // the JAWTWindow presentation of this AWT Canvas, bound to the 'drawable' lifecycle
+ private volatile GLContextImpl context; // volatile: avoid locking for read-only access
+ private volatile boolean sendReshape = false; // volatile: maybe written by EDT w/o locking
+ private final float[] minPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
+ private final float[] maxPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
+ private final float[] hasPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
+ final float[] reqPixelScale = new float[] { ScalableSurface.AUTOMAX_PIXELSCALE, ScalableSurface.AUTOMAX_PIXELSCALE };
+
+ // copy of the cstr args, mainly for recreation
+ private final GLCapabilitiesImmutable capsReqUser;
+ private final GLCapabilitiesChooser chooser;
+ private int additionalCtxCreationFlags = 0;
+ private final GraphicsDevice device;
+ private boolean shallUseOffscreenLayer = false;
+
+ private volatile boolean isShowing;
+ private final HierarchyListener hierarchyListener = new HierarchyListener() {
+ @Override
+ public void hierarchyChanged(final HierarchyEvent e) {
+ isShowing = GLCanvas.this.isShowing();
+ }
+ };
+
+ private final AWTWindowClosingProtocol awtWindowClosingProtocol =
+ new AWTWindowClosingProtocol(this, new Runnable() {
+ @Override
+ public void run() {
+ GLCanvas.this.destroyImpl( true );
+ }
+ }, null);
+
+ /** Creates a new GLCanvas component with a default set of OpenGL
+ capabilities, using the default OpenGL capabilities selection
+ mechanism, on the default screen device.
+ <p>
+ See details about <a href="#contextSharing">OpenGL context sharing</a>.
+ </p>
+ * @throws GLException if no default profile is available for the default desktop device.
+ */
+ public GLCanvas() throws GLException {
+ this(null);
+ }
+
+ /** Creates a new GLCanvas component with the requested set of
+ OpenGL capabilities, using the default OpenGL capabilities
+ selection mechanism, on the default screen device.
+ <p>
+ See details about <a href="#contextSharing">OpenGL context sharing</a>.
+ </p>
+ * @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
+ * @see GLCanvas#GLCanvas(com.jogamp.opengl.GLCapabilitiesImmutable, com.jogamp.opengl.GLCapabilitiesChooser, com.jogamp.opengl.GLContext, java.awt.GraphicsDevice)
+ */
+ public GLCanvas(final GLCapabilitiesImmutable capsReqUser) throws GLException {
+ this(capsReqUser, null, null);
+ }
+
+ /** Creates a new GLCanvas component. The passed GLCapabilities
+ specifies the OpenGL capabilities for the component; if null, a
+ default set of capabilities is used. The GLCapabilitiesChooser
+ specifies the algorithm for selecting one of the available
+ GLCapabilities for the component; a DefaultGLCapabilitesChooser
+ is used if null is passed for this argument.
+ The passed GraphicsDevice indicates the screen on
+ which to create the GLCanvas; the GLDrawableFactory uses the
+ default screen device of the local GraphicsEnvironment if null
+ is passed for this argument.
+ <p>
+ See details about <a href="#contextSharing">OpenGL context sharing</a>.
+ </p>
+ * @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
+ */
+ public GLCanvas(final GLCapabilitiesImmutable capsReqUser,
+ final GLCapabilitiesChooser chooser,
+ final GraphicsDevice device)
+ throws GLException
+ {
+ /*
+ * Determination of the native window is made in 'super.addNotify()',
+ * which creates the native peer using AWT's GraphicsConfiguration.
+ * GraphicsConfiguration is returned by this class overwritten
+ * 'getGraphicsConfiguration()', which returns our OpenGL compatible
+ * 'chosen' GraphicsConfiguration.
+ */
+ super();
+
+ if(null==capsReqUser) {
+ this.capsReqUser = new GLCapabilities(GLProfile.getDefault(GLProfile.getDefaultDevice()));
+ } else {
+ // don't allow the user to change data
+ this.capsReqUser = (GLCapabilitiesImmutable) capsReqUser.cloneMutable();
+ }
+ if( !this.capsReqUser.isOnscreen() ) {
+ setShallUseOffscreenLayer(true); // trigger offscreen layer - if supported
+ }
+
+ if(null==device) {
+ final GraphicsConfiguration gc = super.getGraphicsConfiguration();
+ if(null!=gc) {
+ this.device = gc.getDevice();
+ } else {
+ this.device = null;
+ }
+ } else {
+ this.device = device;
+ }
+
+ // instantiation will be issued in addNotify()
+ this.chooser = chooser;
+
+ this.addHierarchyListener(hierarchyListener);
+ this.isShowing = isShowing();
+ }
+
+ @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);
+ }
+
+ @Override
+ public final Object getUpstreamWidget() {
+ return this;
+ }
+
+ @Override
+ public final RecursiveLock getUpstreamLock() { return lock; }
+
+ @Override
+ public final boolean isThreadGLCapable() { return Threading.isOpenGLThread(); }
+
+ @Override
+ public void setShallUseOffscreenLayer(final boolean v) {
+ shallUseOffscreenLayer = v;
+ }
+
+ @Override
+ public final boolean getShallUseOffscreenLayer() {
+ return shallUseOffscreenLayer;
+ }
+
+ @Override
+ public final boolean isOffscreenLayerSurfaceEnabled() {
+ final JAWTWindow _jawtWindow = jawtWindow;
+ if(null != _jawtWindow) {
+ return _jawtWindow.isOffscreenLayerSurfaceEnabled();
+ }
+ return false;
+ }
+
+
+ /**
+ * Overridden to choose a GraphicsConfiguration on a parent container's
+ * GraphicsDevice because both devices
+ */
+ @Override
+ public GraphicsConfiguration getGraphicsConfiguration() {
+ /*
+ * Workaround for problems with Xinerama and java.awt.Component.checkGD
+ * when adding to a container on a different graphics device than the
+ * one that this Canvas is associated with.
+ *
+ * GC will be null unless:
+ * - A native peer has assigned it. This means we have a native
+ * peer, and are already comitted to a graphics configuration.
+ * - This canvas has been added to a component hierarchy and has
+ * an ancestor with a non-null GC, but the native peer has not
+ * yet been created. This means we can still choose the GC on
+ * all platforms since the peer hasn't been created.
+ */
+ final GraphicsConfiguration gc = super.getGraphicsConfiguration();
+
+ if( Beans.isDesignTime() ) {
+ return gc;
+ }
+
+ /*
+ * chosen is only non-null on platforms where the GLDrawableFactory
+ * returns a non-null GraphicsConfiguration (in the GLCanvas
+ * constructor).
+ *
+ * if gc is from this Canvas' native peer then it should equal chosen,
+ * otherwise it is from an ancestor component that this Canvas is being
+ * added to, and we go into this block.
+ */
+ GraphicsConfiguration chosen = null != awtConfig ? awtConfig.getAWTGraphicsConfiguration() : null;
+
+ if (gc != null && chosen != null && !chosen.equals(gc)) {
+ /*
+ * Check for compatibility with gc. If they differ by only the
+ * device then return a new GCconfig with the super-class' GDevice
+ * (and presumably the same visual ID in Xinerama).
+ *
+ */
+ if (!chosen.getDevice().getIDstring().equals(gc.getDevice().getIDstring())) {
+ /*
+ * Here we select a GraphicsConfiguration on the alternate
+ * device that is presumably identical to the chosen
+ * configuration, but on the other device.
+ *
+ * Should really check to ensure that we select a configuration
+ * with the same X visual ID for Xinerama screens, otherwise the
+ * GLDrawable may have the wrong visual ID (I don't think this
+ * ever gets updated). May need to add a method to
+ * X11GLDrawableFactory to do this in a platform specific
+ * manner.
+ *
+ * However, on platforms where we can actually get into this
+ * block, both devices should have the same visual list, and the
+ * same configuration should be selected here.
+ */
+ final AWTGraphicsConfiguration config = chooseGraphicsConfiguration( (GLCapabilitiesImmutable)awtConfig.getChosenCapabilities(),
+ (GLCapabilitiesImmutable)awtConfig.getRequestedCapabilities(),
+ chooser, gc.getDevice());
+ final GraphicsConfiguration compatible = config.getAWTGraphicsConfiguration();
+ final boolean equalCaps = config.getChosenCapabilities().equals(awtConfig.getChosenCapabilities());
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info:");
+ System.err.println("Created Config (n): HAVE GC "+chosen);
+ System.err.println("Created Config (n): THIS GC "+gc);
+ System.err.println("Created Config (n): Choosen GC "+compatible);
+ System.err.println("Created Config (n): HAVE CF "+awtConfig);
+ System.err.println("Created Config (n): Choosen CF "+config);
+ System.err.println("Created Config (n): EQUALS CAPS "+equalCaps);
+ // Thread.dumpStack();
+ }
+
+ if (compatible != null) {
+ /*
+ * Save the new GC for equals test above, and to return to
+ * any outside callers of this method.
+ */
+ chosen = compatible;
+
+ if( !equalCaps && GLAutoDrawable.SCREEN_CHANGE_ACTION_ENABLED ) {
+ // complete destruction!
+ destroyImpl( true );
+ // recreation!
+ awtConfig = config;
+ createJAWTDrawableAndContext();
+ validateGLDrawable();
+ } else {
+ awtConfig = config;
+ }
+ }
+ }
+
+ /*
+ * If a compatible GC was not found in the block above, this will
+ * return the GC that was selected in the constructor (and might
+ * cause an exception in Component.checkGD when adding to a
+ * container, but in this case that would be the desired behavior).
+ *
+ */
+ return chosen;
+ } else if (gc == null) {
+ /*
+ * The GC is null, which means we have no native peer, and are not
+ * part of a (realized) component hierarchy. So we return the
+ * desired visual that was selected in the constructor (possibly
+ * null).
+ */
+ return chosen;
+ }
+
+ /*
+ * Otherwise we have not explicitly selected a GC in the constructor, so
+ * just return what Canvas would have.
+ */
+ return gc;
+ }
+
+ @Override
+ public GLContext createContext(final GLContext shareWith) {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if(drawable != null) {
+ final GLContext _ctx = drawable.createContext(shareWith);
+ _ctx.setContextCreationFlags(additionalCtxCreationFlags);
+ return _ctx;
+ }
+ return null;
+ } finally {
+ _lock.unlock();
+ }
+ }
+
+ private final void setRealizedImpl(final boolean realized) {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ final GLDrawable _drawable = drawable;
+ if( null == _drawable || realized == _drawable.isRealized() ||
+ 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();
+ }
+ }
+ private final Runnable realizeOnEDTAction = new Runnable() {
+ @Override
+ public void run() { setRealizedImpl(true); }
+ };
+ private final Runnable unrealizeOnEDTAction = new Runnable() {
+ @Override
+ public void run() { setRealizedImpl(false); }
+ };
+
+ @Override
+ public final void setRealized(final boolean realized) {
+ // Make sure drawable realization happens on AWT-EDT and only there. Consider the AWTTree lock!
+ AWTEDTExecutor.singleton.invoke(getTreeLock(), false /* allowOnNonEDT */, true /* wait */, realized ? realizeOnEDTAction : unrealizeOnEDTAction);
+ }
+
+ @Override
+ public boolean isRealized() {
+ final GLDrawable _drawable = drawable;
+ return ( null != _drawable ) ? _drawable.isRealized() : false;
+ }
+
+ @Override
+ public WindowClosingMode getDefaultCloseOperation() {
+ return awtWindowClosingProtocol.getDefaultCloseOperation();
+ }
+
+ @Override
+ public WindowClosingMode setDefaultCloseOperation(final WindowClosingMode op) {
+ return awtWindowClosingProtocol.setDefaultCloseOperation(op);
+ }
+
+ @Override
+ public void display() {
+ if( !validateGLDrawable() ) {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: GLCanvas display - skipped GL render, drawable not valid yet");
+ }
+ return; // not yet available ..
+ }
+ if( isShowing && !printActive ) {
+ Threading.invoke(true, displayOnEDTAction, getTreeLock());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>
+ * This impl. only destroys all GL related resources.
+ * </p>
+ * <p>
+ * This impl. does not remove the GLCanvas from it's parent AWT container
+ * so this class's {@link #removeNotify()} AWT override won't get called.
+ * To do so, remove this component from it's parent AWT container.
+ * </p>
+ */
+ @Override
+ public void destroy() {
+ destroyImpl( false );
+ }
+
+ protected void destroyImpl(final boolean destroyJAWTWindowAndAWTDevice) {
+ Threading.invoke(true, destroyOnEDTAction, getTreeLock());
+ if( destroyJAWTWindowAndAWTDevice ) {
+ AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, disposeJAWTWindowAndAWTDeviceOnEDT);
+ }
+ }
+
+ /** Overridden to cause OpenGL rendering to be performed during
+ repaint cycles. Subclasses which override this method must call
+ super.paint() in their paint() method in order to function
+ properly.
+ */
+ @Override
+ public void paint(final Graphics g) {
+ if( Beans.isDesignTime() ) {
+ // Make GLCanvas behave better in NetBeans GUI builder
+ g.setColor(Color.BLACK);
+ g.fillRect(0, 0, getWidth(), getHeight());
+ final FontMetrics fm = g.getFontMetrics();
+ String name = getName();
+ if (name == null) {
+ name = getClass().getName();
+ final int idx = name.lastIndexOf('.');
+ if (idx >= 0) {
+ name = name.substring(idx + 1);
+ }
+ }
+ final Rectangle2D bounds = fm.getStringBounds(name, g);
+ g.setColor(Color.WHITE);
+ g.drawString(name,
+ (int) ((getWidth() - bounds.getWidth()) / 2),
+ (int) ((getHeight() + bounds.getHeight()) / 2));
+ } else if( !this.helper.isAnimatorAnimatingOnOtherThread() ) {
+ display();
+ }
+ }
+
+ /** Overridden to track when this component is added to a container.
+ Subclasses which override this method must call
+ super.addNotify() in their addNotify() method in order to
+ function properly. <P>
+
+ <B>Overrides:</B>
+ <DL><DD><CODE>addNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
+ @SuppressWarnings("deprecation")
+ @Override
+ public void addNotify() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ final boolean isBeansDesignTime = Beans.isDesignTime();
+
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: addNotify - start, bounds: "+this.getBounds()+", isBeansDesignTime "+isBeansDesignTime);
+ // Thread.dumpStack();
+ }
+
+ if( isBeansDesignTime ) {
+ super.addNotify();
+ } else {
+ /**
+ * 'super.addNotify()' determines the GraphicsConfiguration,
+ * while calling this class's overriden 'getGraphicsConfiguration()' method
+ * after which it creates the native peer.
+ * Hence we have to set the 'awtConfig' before since it's GraphicsConfiguration
+ * is being used in getGraphicsConfiguration().
+ * This code order also allows recreation, ie re-adding the GLCanvas.
+ */
+ awtConfig = chooseGraphicsConfiguration(capsReqUser, capsReqUser, chooser, device);
+ if(null==awtConfig) {
+ throw new GLException("Error: NULL AWTGraphicsConfiguration");
+ }
+
+ // before native peer is valid: X11
+ disableBackgroundErase();
+
+ // issues getGraphicsConfiguration() and creates the native peer
+ super.addNotify();
+
+ // after native peer is valid: Windows
+ disableBackgroundErase();
+
+ createJAWTDrawableAndContext();
+
+ // init drawable by paint/display makes the init sequence more equal
+ // for all launch flavors (applet/javaws/..)
+ // validateGLDrawable();
+ }
+ awtWindowClosingProtocol.addClosingListener();
+
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: addNotify - end: peer: "+getPeer());
+ }
+ } finally {
+ _lock.unlock();
+ }
+ }
+
+ @Override
+ public final boolean setSurfaceScale(final float[] pixelScale) {
+ System.arraycopy(pixelScale, 0, reqPixelScale, 0, 2);
+ if( isRealized() && isShowing ) {
+ Threading.invoke(true, setSurfaceScaleOnEDTAction, getTreeLock());
+ return true;
+ } else {
+ return false;
+ }
+ }
+ private final Runnable setSurfaceScaleOnEDTAction = new Runnable() {
+ @Override
+ public void run() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if( null != drawable && drawable.isRealized() ) {
+ if( setSurfaceScaleImpl(jawtWindow) ) {
+ reshapeImpl(getWidth(), getHeight());
+ if( !helper.isAnimatorAnimatingOnOtherThread() ) {
+ helper.invokeGL(drawable, context, displayAction, initAction); // display
+ }
+ }
+ }
+ } finally {
+ _lock.unlock();
+ }
+ } };
+ private final boolean setSurfaceScaleImpl(final ScalableSurface ns) {
+ if( ns.setSurfaceScale(reqPixelScale) ) {
+ ns.getCurrentSurfaceScale(hasPixelScale);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private final boolean updatePixelScale() {
+ if( jawtWindow.hasPixelScaleChanged() ) {
+ jawtWindow.getMaximumSurfaceScale(maxPixelScale);
+ jawtWindow.getMinimumSurfaceScale(minPixelScale);
+ return setSurfaceScaleImpl(jawtWindow);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public final float[] getRequestedSurfaceScale(final float[] result) {
+ System.arraycopy(reqPixelScale, 0, result, 0, 2);
+ return result;
+ }
+
+ @Override
+ public final float[] getCurrentSurfaceScale(final float[] result) {
+ System.arraycopy(hasPixelScale, 0, result, 0, 2);
+ return result;
+ }
+
+ @Override
+ public float[] getMinimumSurfaceScale(final float[] result) {
+ System.arraycopy(minPixelScale, 0, result, 0, 2);
+ return result;
+ }
+
+ @Override
+ public float[] getMaximumSurfaceScale(final float[] result) {
+ System.arraycopy(maxPixelScale, 0, result, 0, 2);
+ return result;
+ }
+
+ private void createJAWTDrawableAndContext() {
+ if ( !Beans.isDesignTime() ) {
+ jawtWindow = (JAWTWindow) NativeWindowFactory.getNativeWindow(this, awtConfig);
+ jawtWindow.setShallUseOffscreenLayer(shallUseOffscreenLayer);
+ jawtWindow.lockSurface();
+ try {
+ jawtWindow.setSurfaceScale(reqPixelScale);
+ drawable = (GLDrawableImpl) GLDrawableFactory.getFactory(capsReqUser.getGLProfile()).createGLDrawable(jawtWindow);
+ createContextImpl(drawable);
+ jawtWindow.getCurrentSurfaceScale(hasPixelScale);
+ jawtWindow.getMinimumSurfaceScale(minPixelScale);
+ jawtWindow.getMaximumSurfaceScale(maxPixelScale);
+ } finally {
+ jawtWindow.unlockSurface();
+ }
+ }
+ }
+ private boolean createContextImpl(final GLDrawable drawable) {
+ final GLContext[] shareWith = { null };
+ if( !helper.isSharedGLContextPending(shareWith) ) {
+ context = (GLContextImpl) drawable.createContext(shareWith[0]);
+ context.setContextCreationFlags(additionalCtxCreationFlags);
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Context created: has shared "+(null != shareWith[0]));
+ }
+ return true;
+ } else {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Context !created: pending share");
+ }
+ return false;
+ }
+ }
+
+ private boolean validateGLDrawable() {
+ if( Beans.isDesignTime() || !isDisplayable() ) {
+ return false; // early out!
+ }
+ final GLDrawable _drawable = drawable;
+ if ( null != _drawable ) {
+ boolean res = _drawable.isRealized();
+ if( !res ) {
+ // re-try drawable creation
+ if( 0 >= _drawable.getSurfaceWidth() || 0 >= _drawable.getSurfaceHeight() ) {
+ return false; // early out!
+ }
+ setRealized(true);
+ res = _drawable.isRealized();
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Realized Drawable: isRealized "+res+", "+_drawable.toString());
+ // Thread.dumpStack();
+ }
+ }
+ if( res && null == context ) {
+ // re-try context creation
+ res = createContextImpl(_drawable); // pending creation.
+ }
+ return res;
+ }
+ return false;
+ }
+
+ /** <p>Overridden to track when this component is removed from a
+ container. Subclasses which override this method must call
+ super.removeNotify() in their removeNotify() method in order to
+ function properly. </p>
+ <p>User shall not call this method outside of EDT, read the AWT/Swing specs
+ about this.</p>
+ <B>Overrides:</B>
+ <DL><DD><CODE>removeNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
+ @SuppressWarnings("deprecation")
+ @Override
+ public void removeNotify() {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: removeNotify - start");
+ // Thread.dumpStack();
+ }
+
+ awtWindowClosingProtocol.removeClosingListener();
+
+ if( Beans.isDesignTime() ) {
+ super.removeNotify();
+ } else {
+ try {
+ destroyImpl( true );
+ } finally {
+ super.removeNotify();
+ }
+ }
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: removeNotify - end, peer: "+getPeer());
+ }
+ }
+
+ /** Overridden to cause {@link GLDrawableHelper#reshape} to be
+ called on all registered {@link GLEventListener}s. Subclasses
+ which override this method must call super.reshape() in
+ their reshape() method in order to function properly. <P>
+
+ <B>Overrides:</B>
+ <DL><DD><CODE>reshape</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
+ @SuppressWarnings("deprecation")
+ @Override
+ public void reshape(final int x, final int y, final int width, final int height) {
+ synchronized (getTreeLock()) { // super.reshape(..) claims tree lock, so we do extend it's lock over reshape
+ super.reshape(x, y, width, height);
+ reshapeImpl(width, height);
+ }
+ }
+ private void reshapeImpl(final int width, final int height) {
+ final int scaledWidth = SurfaceScaleUtils.scale(width, hasPixelScale[0]);
+ final int scaledHeight = SurfaceScaleUtils.scale(height, hasPixelScale[1]);
+
+ if(DEBUG) {
+ final NativeSurface ns = getNativeSurface();
+ final long nsH = null != ns ? ns.getSurfaceHandle() : 0;
+ System.err.println(getThreadName()+": GLCanvas.reshape.0 "+this.getName()+" resize"+(printActive?"WithinPrint":"")+
+ " [ this "+getWidth()+"x"+getHeight()+", pixelScale "+getPixelScaleStr()+
+ "] -> "+(printActive?"[skipped] ":"") + width+"x"+height+" * "+getPixelScaleStr()+" -> "+scaledWidth+"x"+scaledHeight+
+ " - surfaceHandle 0x"+Long.toHexString(nsH));
+ // Thread.dumpStack();
+ }
+ if( validateGLDrawable() && !printActive ) {
+ final GLDrawableImpl _drawable = drawable;
+ if( ! _drawable.getChosenGLCapabilities().isOnscreen() ) {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, context, scaledWidth, scaledHeight);
+ if(_drawable != _drawableNew) {
+ // write back
+ drawable = _drawableNew;
+ }
+ } finally {
+ _lock.unlock();
+ }
+ }
+ sendReshape = true; // async if display() doesn't get called below, but avoiding deadlock
+ }
+ }
+
+ /**
+ * Overridden from Canvas to prevent the AWT's clearing of the
+ * canvas from interfering with the OpenGL rendering.
+ */
+ @Override
+ public void update(final Graphics g) {
+ paint(g);
+ }
+
+ private volatile boolean printActive = false;
+ private GLAnimatorControl printAnimator = null;
+ private GLAutoDrawable printGLAD = null;
+ private AWTTilePainter printAWTTiles = null;
+
+ @Override
+ public void setupPrint(final double scaleMatX, final double scaleMatY, final int numSamples, final int tileWidth, final int tileHeight) {
+ printActive = true;
+ final int componentCount = isOpaque() ? 3 : 4;
+ final TileRenderer printRenderer = new TileRenderer();
+ printAWTTiles = new AWTTilePainter(printRenderer, componentCount, scaleMatX, scaleMatY, numSamples, tileWidth, tileHeight, DEBUG);
+ AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, setupPrintOnEDT);
+ }
+ private final Runnable setupPrintOnEDT = new Runnable() {
+ @Override
+ public void run() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if( !validateGLDrawable() ) {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: GLCanvas setupPrint - skipped GL render, drawable not valid yet");
+ }
+ printActive = false;
+ return; // not yet available ..
+ }
+ if( !isVisible() ) {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: GLCanvas setupPrint - skipped GL render, canvas not visible");
+ }
+ printActive = false;
+ return; // not yet available ..
+ }
+ sendReshape = false; // clear reshape flag
+ printAnimator = helper.getAnimator();
+ if( null != printAnimator ) {
+ printAnimator.remove(GLCanvas.this);
+ }
+ printGLAD = GLCanvas.this; // _not_ default, shall be replaced by offscreen GLAD
+ final GLCapabilitiesImmutable gladCaps = getChosenGLCapabilities();
+ final int printNumSamples = printAWTTiles.getNumSamples(gladCaps);
+ GLDrawable printDrawable = printGLAD.getDelegatedDrawable();
+ final boolean reqNewGLADSamples = printNumSamples != gladCaps.getNumSamples();
+ final boolean reqNewGLADSize = printAWTTiles.customTileWidth != -1 && printAWTTiles.customTileWidth != printDrawable.getSurfaceWidth() ||
+ printAWTTiles.customTileHeight != -1 && printAWTTiles.customTileHeight != printDrawable.getSurfaceHeight();
+ final boolean reqNewGLADOnscrn = gladCaps.isOnscreen();
+
+ final GLCapabilities newGLADCaps = (GLCapabilities)gladCaps.cloneMutable();
+ newGLADCaps.setDoubleBuffered(false);
+ newGLADCaps.setOnscreen(false);
+ if( printNumSamples != newGLADCaps.getNumSamples() ) {
+ newGLADCaps.setSampleBuffers(0 < printNumSamples);
+ newGLADCaps.setNumSamples(printNumSamples);
+ }
+ final boolean reqNewGLADSafe = GLDrawableUtil.isSwapGLContextSafe(getRequestedGLCapabilities(), gladCaps, newGLADCaps);
+
+ final boolean reqNewGLAD = ( reqNewGLADOnscrn || reqNewGLADSamples || reqNewGLADSize ) && reqNewGLADSafe;
+
+ if( DEBUG ) {
+ System.err.println("AWT print.setup: reqNewGLAD "+reqNewGLAD+"[ onscreen "+reqNewGLADOnscrn+", samples "+reqNewGLADSamples+", size "+reqNewGLADSize+", safe "+reqNewGLADSafe+"], "+
+ ", drawableSize "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+
+ ", customTileSize "+printAWTTiles.customTileWidth+"x"+printAWTTiles.customTileHeight+
+ ", scaleMat "+printAWTTiles.scaleMatX+" x "+printAWTTiles.scaleMatY+
+ ", numSamples "+printAWTTiles.customNumSamples+" -> "+printNumSamples+", printAnimator "+printAnimator);
+ }
+ if( reqNewGLAD ) {
+ final GLDrawableFactory factory = GLDrawableFactory.getFactory(newGLADCaps.getGLProfile());
+ GLOffscreenAutoDrawable offGLAD = null;
+ try {
+ offGLAD = factory.createOffscreenAutoDrawable(null, newGLADCaps, null,
+ printAWTTiles.customTileWidth != -1 ? printAWTTiles.customTileWidth : DEFAULT_PRINT_TILE_SIZE,
+ printAWTTiles.customTileHeight != -1 ? printAWTTiles.customTileHeight : DEFAULT_PRINT_TILE_SIZE);
+ } catch (final GLException gle) {
+ if( DEBUG ) {
+ System.err.println("Caught: "+gle.getMessage());
+ gle.printStackTrace();
+ }
+ }
+ if( null != offGLAD ) {
+ printGLAD = offGLAD;
+ GLDrawableUtil.swapGLContextAndAllGLEventListener(GLCanvas.this, printGLAD);
+ printDrawable = printGLAD.getDelegatedDrawable();
+ }
+ }
+ printAWTTiles.setGLOrientation(printGLAD.isGLOriented(), printGLAD.isGLOriented());
+ printAWTTiles.renderer.setTileSize(printDrawable.getSurfaceWidth(), printDrawable.getSurfaceHeight(), 0);
+ printAWTTiles.renderer.attachAutoDrawable(printGLAD);
+ if( DEBUG ) {
+ System.err.println("AWT print.setup "+printAWTTiles);
+ System.err.println("AWT print.setup AA "+printNumSamples+", "+newGLADCaps);
+ System.err.println("AWT print.setup printGLAD: "+printGLAD.getSurfaceWidth()+"x"+printGLAD.getSurfaceHeight()+", "+printGLAD);
+ System.err.println("AWT print.setup printDraw: "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+", "+printDrawable);
+ }
+ } finally {
+ _lock.unlock();
+ }
+ }
+ };
+
+ @Override
+ public void releasePrint() {
+ if( !printActive || null == printGLAD ) {
+ throw new IllegalStateException("setupPrint() not called");
+ }
+ sendReshape = false; // clear reshape flag
+ AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, releasePrintOnEDT);
+ }
+ private final Runnable releasePrintOnEDT = new Runnable() {
+ @Override
+ public void run() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if( DEBUG ) {
+ System.err.println("AWT print.release "+printAWTTiles);
+ }
+ printAWTTiles.dispose();
+ printAWTTiles= null;
+ if( printGLAD != GLCanvas.this ) {
+ GLDrawableUtil.swapGLContextAndAllGLEventListener(printGLAD, GLCanvas.this);
+ printGLAD.destroy();
+ }
+ printGLAD = null;
+ if( null != printAnimator ) {
+ printAnimator.add(GLCanvas.this);
+ printAnimator = null;
+ }
+ sendReshape = true; // trigger reshape, i.e. gl-viewport and -listener - this component might got resized!
+ printActive = false;
+ display();
+ } finally {
+ _lock.unlock();
+ }
+ }
+ };
+
+ @Override
+ public void print(final Graphics graphics) {
+ if( !printActive || null == printGLAD ) {
+ throw new IllegalStateException("setupPrint() not called");
+ }
+ if(DEBUG && !EventQueue.isDispatchThread()) {
+ System.err.println(getThreadName()+": Warning: GLCanvas print - not called from AWT-EDT");
+ // we cannot dispatch print on AWT-EDT due to printing internal locking ..
+ }
+ sendReshape = false; // clear reshape flag
+
+ final Graphics2D g2d = (Graphics2D)graphics;
+ try {
+ printAWTTiles.setupGraphics2DAndClipBounds(g2d, getWidth(), getHeight());
+ final TileRenderer tileRenderer = printAWTTiles.renderer;
+ if( DEBUG ) {
+ System.err.println("AWT print.0: "+tileRenderer);
+ }
+ if( !tileRenderer.eot() ) {
+ try {
+ do {
+ if( printGLAD != GLCanvas.this ) {
+ tileRenderer.display();
+ } else {
+ Threading.invoke(true, displayOnEDTAction, getTreeLock());
+ }
+ } while ( !tileRenderer.eot() );
+ if( DEBUG ) {
+ System.err.println("AWT print.1: "+printAWTTiles);
+ }
+ } finally {
+ tileRenderer.reset();
+ printAWTTiles.resetGraphics2D();
+ }
+ }
+ } catch (final NoninvertibleTransformException nte) {
+ System.err.println("Caught: Inversion failed of: "+g2d.getTransform());
+ nte.printStackTrace();
+ }
+ if( DEBUG ) {
+ System.err.println("AWT print.X: "+printAWTTiles);
+ }
+ }
+
+ @Override
+ public void addGLEventListener(final GLEventListener listener) {
+ helper.addGLEventListener(listener);
+ }
+
+ @Override
+ public void addGLEventListener(final int index, final GLEventListener listener) throws IndexOutOfBoundsException {
+ helper.addGLEventListener(index, listener);
+ }
+
+ @Override
+ public int getGLEventListenerCount() {
+ return helper.getGLEventListenerCount();
+ }
+
+ @Override
+ public GLEventListener getGLEventListener(final int index) throws IndexOutOfBoundsException {
+ return helper.getGLEventListener(index);
+ }
+
+ @Override
+ public boolean areAllGLEventListenerInitialized() {
+ return helper.areAllGLEventListenerInitialized();
+ }
+
+ @Override
+ public boolean getGLEventListenerInitState(final GLEventListener listener) {
+ return helper.getGLEventListenerInitState(listener);
+ }
+
+ @Override
+ public void setGLEventListenerInitState(final GLEventListener listener, final boolean initialized) {
+ helper.setGLEventListenerInitState(listener, initialized);
+ }
+
+ @Override
+ public GLEventListener disposeGLEventListener(final GLEventListener listener, final boolean remove) {
+ final DisposeGLEventListenerAction r = new DisposeGLEventListenerAction(listener, remove);
+ Threading.invoke(true, r, getTreeLock());
+ return r.listener;
+ }
+
+ @Override
+ public GLEventListener removeGLEventListener(final GLEventListener listener) {
+ return helper.removeGLEventListener(listener);
+ }
+
+ @Override
+ public void setAnimator(final GLAnimatorControl animatorControl) {
+ helper.setAnimator(animatorControl);
+ }
+
+ @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 invoke(final boolean wait, final GLRunnable glRunnable) throws IllegalStateException {
+ return helper.invoke(this, wait, glRunnable);
+ }
+
+ @Override
+ public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) throws IllegalStateException {
+ return helper.invoke(this, wait, glRunnables);
+ }
+
+ @Override
+ public void flushGLRunnables() {
+ helper.flushGLRunnables();
+ }
+
+ @Override
+ public GLContext setContext(final GLContext newCtx, final boolean destroyPrevCtx) {
+ final RecursiveLock _lock = lock;
+ _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 GLDrawable getDelegatedDrawable() {
+ return drawable;
+ }
+
+ @Override
+ public GLContext getContext() {
+ return context;
+ }
+
+ @Override
+ public GL getGL() {
+ if( Beans.isDesignTime() ) {
+ return null;
+ }
+ final GLContext _context = context;
+ return (_context == null) ? null : _context.getGL();
+ }
+
+ @Override
+ public GL setGL(final GL gl) {
+ final GLContext _context = context;
+ if (_context != null) {
+ _context.setGL(gl);
+ return gl;
+ }
+ return null;
+ }
+
+
+ @Override
+ public void setAutoSwapBufferMode(final boolean onOrOff) {
+ helper.setAutoSwapBufferMode(onOrOff);
+ }
+
+ @Override
+ public boolean getAutoSwapBufferMode() {
+ return helper.getAutoSwapBufferMode();
+ }
+
+ @Override
+ public void swapBuffers() {
+ Threading.invoke(true, swapBuffersOnEDTAction, getTreeLock());
+ }
+
+ @Override
+ public void setContextCreationFlags(final int flags) {
+ additionalCtxCreationFlags = flags;
+ final GLContext _context = context;
+ if(null != _context) {
+ _context.setContextCreationFlags(additionalCtxCreationFlags);
+ }
+ }
+
+ @Override
+ public int getContextCreationFlags() {
+ return additionalCtxCreationFlags;
+ }
+
+ @Override
+ public GLProfile getGLProfile() {
+ return capsReqUser.getGLProfile();
+ }
+
+ @Override
+ public GLCapabilitiesImmutable getChosenGLCapabilities() {
+ if( Beans.isDesignTime() ) {
+ return capsReqUser;
+ } else if( null == awtConfig ) {
+ throw new GLException("No AWTGraphicsConfiguration: "+this);
+ }
+ return (GLCapabilitiesImmutable)awtConfig.getChosenCapabilities();
+ }
+
+ @Override
+ public GLCapabilitiesImmutable getRequestedGLCapabilities() {
+ if( null == awtConfig ) {
+ return capsReqUser;
+ }
+ return (GLCapabilitiesImmutable)awtConfig.getRequestedCapabilities();
+ }
+
+ @Override
+ public int getSurfaceWidth() {
+ return SurfaceScaleUtils.scale(getWidth(), hasPixelScale[0]);
+ }
+
+ @Override
+ public int getSurfaceHeight() {
+ return SurfaceScaleUtils.scale(getHeight(), hasPixelScale[1]);
+ }
+
+ @Override
+ public boolean isGLOriented() {
+ final GLDrawable _drawable = drawable;
+ return null != _drawable ? _drawable.isGLOriented() : true;
+ }
+
+ @Override
+ public NativeSurface getNativeSurface() {
+ final GLDrawable _drawable = drawable;
+ return (null != _drawable) ? _drawable.getNativeSurface() : null;
+ }
+
+ @Override
+ public long getHandle() {
+ final GLDrawable _drawable = drawable;
+ return (null != _drawable) ? _drawable.getHandle() : 0;
+ }
+
+ @Override
+ public GLDrawableFactory getFactory() {
+ final GLDrawable _drawable = drawable;
+ return (null != _drawable) ? _drawable.getFactory() : null;
+ }
+
+ @Override
+ public String toString() {
+ final GLDrawable _drawable = drawable;
+ final int dw = (null!=_drawable) ? _drawable.getSurfaceWidth() : -1;
+ final int dh = (null!=_drawable) ? _drawable.getSurfaceHeight() : -1;
+
+ return "AWT-GLCanvas[Realized "+isRealized()+
+ ",\n\t"+((null!=_drawable)?_drawable.getClass().getName():"null-drawable")+
+ ",\n\tFactory "+getFactory()+
+ ",\n\thandle 0x"+Long.toHexString(getHandle())+
+ ",\n\tDrawable size "+dw+"x"+dh+" surface["+getSurfaceWidth()+"x"+getSurfaceHeight()+"]"+
+ ",\n\tAWT[pos "+getX()+"/"+getY()+", size "+getWidth()+"x"+getHeight()+
+ ",\n\tvisible "+isVisible()+", displayable "+isDisplayable()+", showing "+isShowing+
+ ",\n\t"+awtConfig+"]]";
+ }
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ private final String getPixelScaleStr() { return "["+hasPixelScale[0]+", "+hasPixelScale[1]+"]"; }
+
+ private final Runnable destroyOnEDTAction = new Runnable() {
+ @Override
+ public void run() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ final GLAnimatorControl animator = getAnimator();
+
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: destroyOnEDTAction() - START, hasContext " +
+ (null!=context) + ", hasDrawable " + (null!=drawable)+", "+animator);
+ // Thread.dumpStack();
+ }
+
+ final boolean animatorPaused;
+ if(null!=animator) {
+ // can't remove us from animator for recreational addNotify()
+ animatorPaused = animator.pause();
+ } else {
+ animatorPaused = false;
+ }
+
+ GLException exceptionOnDisposeGL = null;
+
+ // OLS will be detached by disposeGL's context destruction below
+ if( null != context ) {
+ if( context.isCreated() ) {
+ try {
+ helper.disposeGL(GLCanvas.this, context, true);
+ if(DEBUG) {
+ System.err.println(getThreadName()+": destroyOnEDTAction() - post ctx: "+context);
+ }
+ } catch (final GLException gle) {
+ exceptionOnDisposeGL = gle;
+ }
+ }
+ context = null;
+ }
+
+ Throwable exceptionOnUnrealize = null;
+ if( null != drawable ) {
+ try {
+ drawable.setRealized(false);
+ if(DEBUG) {
+ System.err.println(getThreadName()+": destroyOnEDTAction() - post drawable: "+drawable);
+ }
+ } catch( final Throwable re ) {
+ exceptionOnUnrealize = re;
+ }
+ drawable = null;
+ }
+
+ if(animatorPaused) {
+ animator.resume();
+ }
+
+ // throw exception in order of occurrence ..
+ if( null != exceptionOnDisposeGL ) {
+ throw exceptionOnDisposeGL;
+ }
+ if( null != exceptionOnUnrealize ) {
+ throw GLException.newGLException(exceptionOnUnrealize);
+ }
+
+ if(DEBUG) {
+ System.err.println(getThreadName()+": dispose() - END, animator "+animator);
+ }
+
+ } finally {
+ _lock.unlock();
+ }
+ }
+ };
+
+ /**
+ * Disposes the JAWTWindow and AbstractGraphicsDevice within EDT,
+ * since resources created (X11: Display), must be destroyed in the same thread, where they have been created.
+ * <p>
+ * The drawable and context handle are null'ed as well, assuming {@link #destroy()} has been called already.
+ * </p>
+ *
+ * @see #chooseGraphicsConfiguration(com.jogamp.opengl.GLCapabilitiesImmutable, com.jogamp.opengl.GLCapabilitiesImmutable, com.jogamp.opengl.GLCapabilitiesChooser, java.awt.GraphicsDevice)
+ */
+ private final Runnable disposeJAWTWindowAndAWTDeviceOnEDT = new Runnable() {
+ @Override
+ public void run() {
+ context=null;
+ drawable=null;
+
+ if( null != jawtWindow ) {
+ jawtWindow.destroy();
+ if(DEBUG) {
+ System.err.println(getThreadName()+": GLCanvas.disposeJAWTWindowAndAWTDeviceOnEDT(): post JAWTWindow: "+jawtWindow);
+ }
+ jawtWindow=null;
+ }
+ hasPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
+ hasPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
+ minPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
+ minPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
+ maxPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
+ maxPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
+
+ if(null != awtConfig) {
+ final AbstractGraphicsConfiguration aconfig = awtConfig.getNativeGraphicsConfiguration();
+ final AbstractGraphicsDevice adevice = aconfig.getScreen().getDevice();
+ final String adeviceMsg;
+ if(DEBUG) {
+ adeviceMsg = adevice.toString();
+ } else {
+ adeviceMsg = null;
+ }
+ final boolean closed = adevice.close();
+ if(DEBUG) {
+ System.err.println(getThreadName()+": GLCanvas.disposeJAWTWindowAndAWTDeviceOnEDT(): post GraphicsDevice: "+adeviceMsg+", result: "+closed);
+ }
+ }
+ awtConfig=null;
+ }
+ };
+
+ private final Runnable initAction = new Runnable() {
+ @Override
+ public void run() {
+ helper.init(GLCanvas.this, !sendReshape);
+ }
+ };
+
+ private final Runnable displayAction = new Runnable() {
+ @Override
+ public void run() {
+ if (sendReshape) {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Reshape: "+getSurfaceWidth()+"x"+getSurfaceHeight());
+ }
+ // Note: we ignore the given x and y within the parent component
+ // since we are drawing directly into this heavyweight component.
+ helper.reshape(GLCanvas.this, 0, 0, getSurfaceWidth(), getSurfaceHeight());
+ sendReshape = false;
+ }
+
+ helper.display(GLCanvas.this);
+ }
+ };
+
+ private final Runnable displayOnEDTAction = new Runnable() {
+ @Override
+ public void run() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if( null != drawable && drawable.isRealized() ) {
+ if( GLCanvas.this.updatePixelScale() ) {
+ GLCanvas.this.reshapeImpl(getWidth(), getHeight());
+ }
+ helper.invokeGL(drawable, context, displayAction, initAction);
+ }
+ } finally {
+ _lock.unlock();
+ }
+ }
+ };
+
+ private final Runnable swapBuffersOnEDTAction = new Runnable() {
+ @Override
+ public void run() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if( null != drawable && drawable.isRealized() ) {
+ drawable.swapBuffers();
+ }
+ } finally {
+ _lock.unlock();
+ }
+ }
+ };
+
+ private class DisposeGLEventListenerAction implements Runnable {
+ 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 {
+ listener = helper.disposeGLEventListener(GLCanvas.this, drawable, context, listener, remove);
+ } finally {
+ _lock.unlock();
+ }
+ }
+ };
+
+ // Disables the AWT's erasing of this Canvas's background on Windows
+ // in Java SE 6. This internal API is not available in previous
+ // releases, but the system property
+ // -Dsun.awt.noerasebackground=true can be specified to get similar
+ // results globally in previous releases.
+ private static boolean disableBackgroundEraseInitialized;
+ private static Method disableBackgroundEraseMethod;
+ private void disableBackgroundErase() {
+ if (!disableBackgroundEraseInitialized) {
+ try {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ @Override
+ public Object run() {
+ try {
+ Class<?> clazz = getToolkit().getClass();
+ while (clazz != null && disableBackgroundEraseMethod == null) {
+ try {
+ disableBackgroundEraseMethod =
+ clazz.getDeclaredMethod("disableBackgroundErase",
+ new Class[] { Canvas.class });
+ disableBackgroundEraseMethod.setAccessible(true);
+ } catch (final Exception e) {
+ clazz = clazz.getSuperclass();
+ }
+ }
+ } catch (final Exception e) {
+ }
+ return null;
+ }
+ });
+ } catch (final Exception e) {
+ }
+ disableBackgroundEraseInitialized = true;
+ if(DEBUG) {
+ System.err.println(getThreadName()+": GLCanvas: TK disableBackgroundErase method found: "+
+ (null!=disableBackgroundEraseMethod));
+ }
+ }
+ if (disableBackgroundEraseMethod != null) {
+ Throwable t=null;
+ try {
+ disableBackgroundEraseMethod.invoke(getToolkit(), new Object[] { this });
+ } catch (final Exception e) {
+ t = e;
+ }
+ if(DEBUG) {
+ System.err.println(getThreadName()+": GLCanvas: TK disableBackgroundErase error: "+t);
+ }
+ }
+ }
+
+ /**
+ * Issues the GraphicsConfigurationFactory's choosing facility within EDT,
+ * since resources created (X11: Display), must be destroyed in the same thread, where they have been created.
+ *
+ * @param capsChosen
+ * @param capsRequested
+ * @param chooser
+ * @param device
+ * @return the chosen AWTGraphicsConfiguration
+ *
+ * @see #disposeJAWTWindowAndAWTDeviceOnEDT
+ */
+ private AWTGraphicsConfiguration chooseGraphicsConfiguration(final GLCapabilitiesImmutable capsChosen,
+ final GLCapabilitiesImmutable capsRequested,
+ final GLCapabilitiesChooser chooser,
+ final GraphicsDevice device) {
+ // Make GLCanvas behave better in NetBeans GUI builder
+ if( Beans.isDesignTime() ) {
+ return null;
+ }
+
+ final AbstractGraphicsScreen aScreen = null != device ?
+ AWTGraphicsScreen.createScreenDevice(device, AbstractGraphicsDevice.DEFAULT_UNIT):
+ AWTGraphicsScreen.createDefault();
+ AWTGraphicsConfiguration config = null;
+
+ if( EventQueue.isDispatchThread() || Thread.holdsLock(getTreeLock()) ) {
+ config = (AWTGraphicsConfiguration)
+ GraphicsConfigurationFactory.getFactory(AWTGraphicsDevice.class, GLCapabilitiesImmutable.class).chooseGraphicsConfiguration(capsChosen,
+ capsRequested,
+ chooser, aScreen, VisualIDHolder.VID_UNDEFINED);
+ } else {
+ try {
+ final ArrayList<AWTGraphicsConfiguration> bucket = new ArrayList<AWTGraphicsConfiguration>(1);
+ EventQueue.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ final AWTGraphicsConfiguration c = (AWTGraphicsConfiguration)
+ GraphicsConfigurationFactory.getFactory(AWTGraphicsDevice.class, GLCapabilitiesImmutable.class).chooseGraphicsConfiguration(capsChosen,
+ capsRequested,
+ chooser, aScreen, VisualIDHolder.VID_UNDEFINED);
+ bucket.add(c);
+ }
+ });
+ config = ( bucket.size() > 0 ) ? bucket.get(0) : null ;
+ } catch (final InvocationTargetException e) {
+ throw new GLException(e.getTargetException());
+ } catch (final InterruptedException e) {
+ throw new GLException(e);
+ }
+ }
+
+ if ( null == config ) {
+ throw new GLException("Error: Couldn't fetch AWTGraphicsConfiguration");
+ }
+
+ return config;
+ }
+
+ protected static String getThreadName() { return Thread.currentThread().getName(); }
+
+ /**
+ * A most simple JOGL AWT test entry
+ */
+ public static void main(final String args[]) {
+ System.err.println(VersionUtil.getPlatformInfo());
+ System.err.println(GlueGenVersion.getInstance());
+ // System.err.println(NativeWindowVersion.getInstance());
+ System.err.println(JoglVersion.getInstance());
+
+ System.err.println(JoglVersion.getDefaultOpenGLInfo(null, null, true).toString());
+
+ final GLCapabilitiesImmutable caps = new GLCapabilities( GLProfile.getDefault(GLProfile.getDefaultDevice()) );
+ final Frame frame = new Frame("JOGL AWT Test");
+
+ final GLCanvas glCanvas = new GLCanvas(caps);
+ frame.add(glCanvas);
+ frame.setSize(128, 128);
+
+ glCanvas.addGLEventListener(new GLEventListener() {
+ @Override
+ public void init(final GLAutoDrawable drawable) {
+ final GL gl = drawable.getGL();
+ System.err.println(JoglVersion.getGLInfo(gl, null));
+ }
+ @Override
+ public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { }
+ @Override
+ public void display(final GLAutoDrawable drawable) { }
+ @Override
+ public void dispose(final GLAutoDrawable drawable) { }
+ });
+
+ try {
+ javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ frame.setVisible(true);
+ }});
+ } catch (final Throwable t) {
+ t.printStackTrace();
+ }
+ glCanvas.display();
+ try {
+ javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ frame.dispose();
+ }});
+ } catch (final Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/jogl/classes/com/jogamp/opengl/awt/GLJPanel.java b/src/jogl/classes/com/jogamp/opengl/awt/GLJPanel.java
new file mode 100644
index 000000000..91b2f5e0c
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/opengl/awt/GLJPanel.java
@@ -0,0 +1,2689 @@
+/*
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (c) 2010 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:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ *
+ * Sun gratefully acknowledges that this software was originally authored
+ * and developed by Kenneth Bradley Russell and Christopher John Kline.
+ */
+
+package com.jogamp.opengl.awt;
+
+import java.awt.Color;
+import java.awt.EventQueue;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsEnvironment;
+import java.awt.Rectangle;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.beans.Beans;
+import java.nio.IntBuffer;
+import java.util.List;
+
+import com.jogamp.nativewindow.AbstractGraphicsDevice;
+import com.jogamp.nativewindow.NativeSurface;
+import com.jogamp.nativewindow.ScalableSurface;
+import com.jogamp.nativewindow.SurfaceUpdatedListener;
+import com.jogamp.nativewindow.WindowClosingProtocol;
+import com.jogamp.nativewindow.util.PixelFormat;
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.GL2;
+import com.jogamp.opengl.GL2ES3;
+import com.jogamp.opengl.GL2GL3;
+import com.jogamp.opengl.GLAnimatorControl;
+import com.jogamp.opengl.GLAutoDrawable;
+import com.jogamp.opengl.GLCapabilities;
+import com.jogamp.opengl.GLCapabilitiesChooser;
+import com.jogamp.opengl.GLCapabilitiesImmutable;
+import com.jogamp.opengl.GLContext;
+import com.jogamp.opengl.GLDrawable;
+import com.jogamp.opengl.GLDrawableFactory;
+import com.jogamp.opengl.GLEventListener;
+import com.jogamp.opengl.GLException;
+import com.jogamp.opengl.GLFBODrawable;
+import com.jogamp.opengl.GLOffscreenAutoDrawable;
+import com.jogamp.opengl.GLProfile;
+import com.jogamp.opengl.GLRunnable;
+import com.jogamp.opengl.GLSharedContextSetter;
+import com.jogamp.opengl.Threading;
+import javax.swing.JPanel;
+
+import jogamp.nativewindow.SurfaceScaleUtils;
+import jogamp.nativewindow.WrappedSurface;
+import jogamp.nativewindow.jawt.JAWTUtil;
+import jogamp.opengl.Debug;
+import jogamp.opengl.GLContextImpl;
+import jogamp.opengl.GLDrawableFactoryImpl;
+import jogamp.opengl.GLDrawableHelper;
+import jogamp.opengl.GLDrawableImpl;
+import jogamp.opengl.awt.AWTTilePainter;
+import jogamp.opengl.awt.Java2D;
+import jogamp.opengl.util.glsl.GLSLTextureRaster;
+
+import com.jogamp.common.util.PropertyAccess;
+import com.jogamp.common.util.awt.AWTEDTExecutor;
+import com.jogamp.common.util.locks.LockFactory;
+import com.jogamp.common.util.locks.RecursiveLock;
+import com.jogamp.nativewindow.awt.AWTPrintLifecycle;
+import com.jogamp.nativewindow.awt.AWTWindowClosingProtocol;
+import com.jogamp.opengl.FBObject;
+import com.jogamp.opengl.GLRendererQuirks;
+import com.jogamp.opengl.util.GLPixelBuffer.GLPixelAttributes;
+import com.jogamp.opengl.util.GLPixelBuffer.SingletonGLPixelBufferProvider;
+import com.jogamp.opengl.util.GLDrawableUtil;
+import com.jogamp.opengl.util.GLPixelStorageModes;
+import com.jogamp.opengl.util.TileRenderer;
+import com.jogamp.opengl.util.awt.AWTGLPixelBuffer;
+import com.jogamp.opengl.util.awt.AWTGLPixelBuffer.AWTGLPixelBufferProvider;
+import com.jogamp.opengl.util.awt.AWTGLPixelBuffer.SingleAWTGLPixelBufferProvider;
+import com.jogamp.opengl.util.texture.TextureState;
+
+/** A lightweight Swing component which provides OpenGL rendering
+ support. Provided for compatibility with Swing user interfaces
+ when adding a heavyweight doesn't work either because of
+ Z-ordering or LayoutManager problems.
+ <p>
+ The GLJPanel can be made transparent by creating it with a
+ GLCapabilities object with alpha bits specified and calling {@link
+ #setOpaque}(false). Pixels with resulting OpenGL alpha values less
+ than 1.0 will be overlaid on any underlying Swing rendering.
+ </p>
+ <p>
+ This component attempts to use hardware-accelerated rendering via FBO or pbuffers and
+ falls back on to software rendering if none of the former are available
+ using {@link GLDrawableFactory#createOffscreenDrawable(AbstractGraphicsDevice, GLCapabilitiesImmutable, GLCapabilitiesChooser, int, int) GLDrawableFactory.createOffscreenDrawable(..)}.<br/>
+ </p>
+ <p>
+ <a name="verticalFlip">A vertical-flip is required</a>, if the drawable {@link #isGLOriented()} and {@link #setSkipGLOrientationVerticalFlip(boolean) vertical flip is not skipped}.<br>
+ In this case this component performs the required vertical flip to bring the content from OpenGL's orientation into AWT's orientation.<br>
+ In case <a href="#fboGLSLVerticalFlip">GLSL based vertical-flip</a> is not available,
+ the CPU intensive {@link System#arraycopy(Object, int, Object, int, int) System.arraycopy(..)} is used line by line.
+ See details about <a href="#fboGLSLVerticalFlip">FBO and GLSL vertical flipping</a>.
+ </p>
+ <p>
+ For performance reasons, as well as for <a href="#bug842">GL state sideeffects</a>,
+ <b>{@link #setSkipGLOrientationVerticalFlip(boolean) skipping vertical flip} is highly recommended</b>!
+ </p>
+ <p>
+ The OpenGL path is concluded by copying the rendered pixels an {@link BufferedImage} via {@link GL#glReadPixels(int, int, int, int, int, int, java.nio.Buffer) glReadPixels(..)}
+ for later Java2D composition.
+ </p>
+ <p>
+ Finally the Java2D compositioning takes place via via {@link Graphics#drawImage(java.awt.Image, int, int, int, int, java.awt.image.ImageObserver) Graphics.drawImage(...)}
+ on the prepared {@link BufferedImage} as described above.
+ </p>
+ <P>
+ * Please read <a href="GLCanvas.html#java2dgl">Java2D OpenGL Remarks</a>.
+ * </P>
+ *
+ <a name="fboGLSLVerticalFlip"><h5>FBO / GLSL Vertical Flip</h5></a>
+ If <a href="#verticalFlip">vertical flip is required</a>,
+ FBO is used, GLSL is available and {@link #setSkipGLOrientationVerticalFlip(boolean) vertical flip is not skipped}, a fragment shader is utilized
+ to flip the FBO texture vertically. This hardware-accelerated step can be disabled via system property <code>jogl.gljpanel.noglsl</code>.
+ <p>
+ The FBO / GLSL code path uses one texture-unit and binds the FBO texture to it's active texture-target,
+ see {@link #setTextureUnit(int)} and {@link #getTextureUnit()}.
+ </p>
+ <p>
+ The active and dedicated texture-unit's {@link GL#GL_TEXTURE_2D} state is preserved via {@link TextureState}.
+ See also {@link Texture#textureCallOrder Order of Texture Commands}.
+ </p>
+ <p>
+ The current gl-viewport is preserved.
+ </p>
+ <p>
+ <a name="bug842"><i>Warning (Bug 842)</i></a>: Certain GL states other than viewport and texture (see above)
+ influencing rendering, will also influence the GLSL vertical flip, e.g. {@link GL#glFrontFace(int) glFrontFace}({@link GL#GL_CCW}).
+ It is recommended to reset those states to default when leaving the {@link GLEventListener#display(GLAutoDrawable)} method!
+ We may change this behavior in the future, i.e. preserve all influencing states.
+ </p>
+ <p>
+ <a name="contextSharing"><h5>OpenGL Context Sharing</h5></a>
+ To share a {@link GLContext} see the following note in the documentation overview:
+ <a href="../../../../overview-summary.html#SHARING">context sharing</a>
+ as well as {@link GLSharedContextSetter}.
+ </p>
+*/
+
+@SuppressWarnings("serial")
+public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosingProtocol, AWTPrintLifecycle, GLSharedContextSetter, ScalableSurface {
+ private static final boolean DEBUG;
+ private static final boolean DEBUG_FRAMES;
+ private static final boolean DEBUG_VIEWPORT;
+ private static final boolean USE_GLSL_TEXTURE_RASTERIZER;
+ private static final boolean SKIP_VERTICAL_FLIP_DEFAULT;
+
+ /** Indicates whether the Java 2D OpenGL pipeline is requested by user. */
+ private static final boolean java2dOGLEnabledByProp;
+
+ /** Indicates whether the Java 2D OpenGL pipeline is enabled, resource-compatible and requested by user. */
+ private static final boolean useJava2DGLPipeline;
+
+ /** Indicates whether the Java 2D OpenGL pipeline's usage is error free. */
+ private static boolean java2DGLPipelineOK;
+
+ static {
+ Debug.initSingleton();
+ DEBUG = Debug.debug("GLJPanel");
+ DEBUG_FRAMES = PropertyAccess.isPropertyDefined("jogl.debug.GLJPanel.Frames", true);
+ DEBUG_VIEWPORT = PropertyAccess.isPropertyDefined("jogl.debug.GLJPanel.Viewport", true);
+ USE_GLSL_TEXTURE_RASTERIZER = !PropertyAccess.isPropertyDefined("jogl.gljpanel.noglsl", true);
+ SKIP_VERTICAL_FLIP_DEFAULT = PropertyAccess.isPropertyDefined("jogl.gljpanel.noverticalflip", true);
+ boolean enabled = PropertyAccess.getBooleanProperty("sun.java2d.opengl", false);
+ java2dOGLEnabledByProp = enabled && !PropertyAccess.isPropertyDefined("jogl.gljpanel.noogl", true);
+
+ enabled = false;
+ if( java2dOGLEnabledByProp ) {
+ // Force eager initialization of part of the Java2D class since
+ // otherwise it's likely it will try to be initialized while on
+ // the Queue Flusher Thread, which is not allowed
+ if (Java2D.isOGLPipelineResourceCompatible() && Java2D.isFBOEnabled()) {
+ if( null != Java2D.getShareContext(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()) ) {
+ enabled = true;
+ }
+ }
+ }
+ useJava2DGLPipeline = enabled;
+ java2DGLPipelineOK = enabled;
+ if( DEBUG ) {
+ System.err.println("GLJPanel: DEBUG_VIEWPORT "+DEBUG_VIEWPORT);
+ System.err.println("GLJPanel: USE_GLSL_TEXTURE_RASTERIZER "+USE_GLSL_TEXTURE_RASTERIZER);
+ System.err.println("GLJPanel: SKIP_VERTICAL_FLIP_DEFAULT "+SKIP_VERTICAL_FLIP_DEFAULT);
+ System.err.println("GLJPanel: java2dOGLEnabledByProp "+java2dOGLEnabledByProp);
+ System.err.println("GLJPanel: useJava2DGLPipeline "+useJava2DGLPipeline);
+ System.err.println("GLJPanel: java2DGLPipelineOK "+java2DGLPipelineOK);
+ }
+ }
+
+ private static SingleAWTGLPixelBufferProvider singleAWTGLPixelBufferProvider = null;
+ private static synchronized SingleAWTGLPixelBufferProvider getSingleAWTGLPixelBufferProvider() {
+ if( null == singleAWTGLPixelBufferProvider ) {
+ singleAWTGLPixelBufferProvider = new SingleAWTGLPixelBufferProvider( true /* allowRowStride */ );
+ }
+ return singleAWTGLPixelBufferProvider;
+ }
+
+ private final RecursiveLock lock = LockFactory.createRecursiveLock();
+
+ private final GLDrawableHelper helper;
+ private boolean autoSwapBufferMode;
+
+ private volatile boolean isInitialized;
+
+ //
+ // Data used for either pbuffers or pixmap-based offscreen surfaces
+ //
+ private AWTGLPixelBufferProvider customPixelBufferProvider = null;
+ /** Requested single buffered offscreen caps */
+ private volatile GLCapabilitiesImmutable reqOffscreenCaps;
+ private volatile GLDrawableFactoryImpl factory;
+ private final GLCapabilitiesChooser chooser;
+ private int additionalCtxCreationFlags = 0;
+
+ // Lazy reshape notification: reshapeWidth -> panelWidth -> backend.width
+ private boolean handleReshape = false;
+ private boolean sendReshape = true;
+
+ private final float[] minPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
+ private final float[] maxPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
+ private final float[] hasPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
+ private final float[] reqPixelScale = new float[] { ScalableSurface.AUTOMAX_PIXELSCALE, ScalableSurface.AUTOMAX_PIXELSCALE };
+
+ /** For handling reshape events lazily: reshapeWidth -> panelWidth -> backend.width in pixel units (scaled) */
+ private int reshapeWidth;
+ /** For handling reshape events lazily: reshapeHeight -> panelHeight -> backend.height in pixel units (scaled) */
+ private int reshapeHeight;
+
+ /** Scaled pixel width of the actual GLJPanel: reshapeWidth -> panelWidth -> backend.width */
+ private int panelWidth = 0;
+ /** Scaled pixel height of the actual GLJPanel: reshapeHeight -> panelHeight -> backend.height */
+ private int panelHeight = 0;
+
+ // These are always set to (0, 0) except when the Java2D / OpenGL
+ // pipeline is active
+ private int viewportX;
+ private int viewportY;
+
+ private int requestedTextureUnit = 0; // default
+
+ // The backend in use
+ private volatile Backend backend;
+
+ private boolean skipGLOrientationVerticalFlip = SKIP_VERTICAL_FLIP_DEFAULT;
+
+ // Used by all backends either directly or indirectly to hook up callbacks
+ private final Updater updater = new Updater();
+
+ private boolean oglPipelineUsable() {
+ return null == customPixelBufferProvider && useJava2DGLPipeline && java2DGLPipelineOK;
+ }
+
+ private volatile boolean isShowing;
+ private final HierarchyListener hierarchyListener = new HierarchyListener() {
+ @Override
+ public void hierarchyChanged(final HierarchyEvent e) {
+ isShowing = GLJPanel.this.isShowing();
+ }
+ };
+
+ private final AWTWindowClosingProtocol awtWindowClosingProtocol =
+ new AWTWindowClosingProtocol(this, new Runnable() {
+ @Override
+ public void run() {
+ GLJPanel.this.destroy();
+ }
+ }, null);
+
+ /** Creates a new GLJPanel component with a default set of OpenGL
+ capabilities and using the default OpenGL capabilities selection
+ mechanism.
+ <p>
+ See details about <a href="#contextSharing">OpenGL context sharing</a>.
+ </p>
+ * @throws GLException if no default profile is available for the default desktop device.
+ */
+ public GLJPanel() throws GLException {
+ this(null);
+ }
+
+ /** Creates a new GLJPanel component with the requested set of
+ OpenGL capabilities, using the default OpenGL capabilities
+ selection mechanism.
+ <p>
+ See details about <a href="#contextSharing">OpenGL context sharing</a>.
+ </p>
+ * @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
+ */
+ public GLJPanel(final GLCapabilitiesImmutable userCapsRequest) throws GLException {
+ this(userCapsRequest, null);
+ }
+
+ /** Creates a new GLJPanel component. The passed GLCapabilities
+ specifies the OpenGL capabilities for the component; if null, a
+ default set of capabilities is used. The GLCapabilitiesChooser
+ specifies the algorithm for selecting one of the available
+ GLCapabilities for the component; a DefaultGLCapabilitesChooser
+ is used if null is passed for this argument.
+ <p>
+ See details about <a href="#contextSharing">OpenGL context sharing</a>.
+ </p>
+ * @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
+ */
+ public GLJPanel(final GLCapabilitiesImmutable userCapsRequest, final GLCapabilitiesChooser chooser)
+ throws GLException
+ {
+ super();
+
+ // Works around problems on many vendors' cards; we don't need a
+ // back buffer for the offscreen surface anyway
+ {
+ GLCapabilities caps;
+ if (userCapsRequest != null) {
+ caps = (GLCapabilities) userCapsRequest.cloneMutable();
+ } else {
+ caps = new GLCapabilities(GLProfile.getDefault(GLProfile.getDefaultDevice()));
+ }
+ caps.setDoubleBuffered(false);
+ reqOffscreenCaps = caps;
+ }
+ this.factory = GLDrawableFactoryImpl.getFactoryImpl( reqOffscreenCaps.getGLProfile() ); // pre-fetch, reqOffscreenCaps may changed
+ this.chooser = chooser;
+
+ helper = new GLDrawableHelper();
+ autoSwapBufferMode = helper.getAutoSwapBufferMode();
+
+ this.setFocusable(true); // allow keyboard input!
+ this.addHierarchyListener(hierarchyListener);
+ this.isShowing = isShowing();
+ }
+
+ /**
+ * Attempts to initialize the backend, if not initialized yet.
+ * <p>
+ * If backend is already initialized method returns <code>true</code>.
+ * </p>
+ * <p>
+ * If <code>offthread</code> is <code>true</code>, initialization will kicked off
+ * on a <i>short lived</i> arbitrary thread and method returns immediately.<br/>
+ * If platform supports such <i>arbitrary thread</i> initialization method returns
+ * <code>true</code>, otherwise <code>false</code>.
+ * </p>
+ * <p>
+ * If <code>offthread</code> is <code>false</code>, initialization be performed
+ * on the current thread and method returns after initialization.<br/>
+ * Method returns <code>true</code> if initialization was successful, otherwise <code>false</code>.
+ * <p>
+ * @param offthread
+ */
+ public final boolean initializeBackend(final boolean offthread) {
+ if( offthread ) {
+ new Thread(getThreadName()+"-GLJPanel_Init") {
+ public void run() {
+ if( !isInitialized ) {
+ initializeBackendImpl();
+ }
+ } }.start();
+ return true;
+ } else {
+ if( !isInitialized ) {
+ return initializeBackendImpl();
+ } else {
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public final void setSharedContext(final GLContext sharedContext) throws IllegalStateException {
+ helper.setSharedContext(this.getContext(), sharedContext);
+ }
+
+ @Override
+ public final void setSharedAutoDrawable(final GLAutoDrawable sharedAutoDrawable) throws IllegalStateException {
+ helper.setSharedAutoDrawable(this, sharedAutoDrawable);
+ }
+
+ public AWTGLPixelBufferProvider getCustomPixelBufferProvider() { return customPixelBufferProvider; }
+
+ /**
+ * @param custom custom {@link AWTGLPixelBufferProvider}
+ * @throws IllegalArgumentException if <code>custom</code> is <code>null</code>
+ * @throws IllegalStateException if backend is already realized, i.e. this instanced already painted once.
+ */
+ public void setPixelBufferProvider(final AWTGLPixelBufferProvider custom) throws IllegalArgumentException, IllegalStateException {
+ if( null == custom ) {
+ throw new IllegalArgumentException("Null PixelBufferProvider");
+ }
+ if( null != backend ) {
+ throw new IllegalStateException("Backend already realized.");
+ }
+ customPixelBufferProvider = custom;
+ }
+
+ @Override
+ public final Object getUpstreamWidget() {
+ return this;
+ }
+
+ @Override
+ public final RecursiveLock getUpstreamLock() { return lock; }
+
+ @Override
+ public final boolean isThreadGLCapable() { return EventQueue.isDispatchThread(); }
+
+ @Override
+ public void display() {
+ if( isShowing || ( printActive && isVisible() ) ) {
+ if (EventQueue.isDispatchThread()) {
+ // Want display() to be synchronous, so call paintImmediately()
+ paintImmediatelyAction.run();
+ } else {
+ // Multithreaded redrawing of Swing components is not allowed,
+ // so do everything on the event dispatch thread
+ try {
+ EventQueue.invokeAndWait(paintImmediatelyAction);
+ } catch (final Exception e) {
+ throw new GLException(e);
+ }
+ }
+ }
+ }
+
+ protected void dispose(final Runnable post) {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.dispose() - start");
+ // Thread.dumpStack();
+ }
+
+ if (backend != null && backend.getContext() != null) {
+ final boolean animatorPaused;
+ final GLAnimatorControl animator = getAnimator();
+ if(null!=animator) {
+ animatorPaused = animator.pause();
+ } else {
+ animatorPaused = false;
+ }
+
+ if(backend.getContext().isCreated()) {
+ Threading.invoke(true, disposeAction, getTreeLock());
+ }
+ if(null != backend) {
+ // not yet destroyed due to backend.isUsingOwnThreadManagment() == true
+ backend.destroy();
+ isInitialized = false;
+ }
+ if( null != post ) {
+ post.run();
+ }
+
+ if( animatorPaused ) {
+ animator.resume();
+ }
+ }
+
+ if(DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.dispose() - stop");
+ }
+ }
+
+ /**
+ * Just an alias for removeNotify
+ */
+ @Override
+ public void destroy() {
+ removeNotify();
+ }
+
+ /** Overridden to cause OpenGL rendering to be performed during
+ repaint cycles. Subclasses which override this method must call
+ super.paintComponent() in their paintComponent() method in order
+ to function properly. <P>
+
+ <DL><DD><CODE>paintComponent</CODE> in class <CODE>javax.swing.JComponent</CODE></DD></DL> */
+ @Override
+ protected void paintComponent(final Graphics g) {
+ if (Beans.isDesignTime()) {
+ // Make GLJPanel behave better in NetBeans GUI builder
+ g.setColor(Color.BLACK);
+ g.fillRect(0, 0, getWidth(), getHeight());
+ final FontMetrics fm = g.getFontMetrics();
+ String name = getName();
+ if (name == null) {
+ name = getClass().getName();
+ final int idx = name.lastIndexOf('.');
+ if (idx >= 0) {
+ name = name.substring(idx + 1);
+ }
+ }
+ final Rectangle2D bounds = fm.getStringBounds(name, g);
+ g.setColor(Color.WHITE);
+ g.drawString(name,
+ (int) ((getWidth() - bounds.getWidth()) / 2),
+ (int) ((getHeight() + bounds.getHeight()) / 2));
+ return;
+ }
+
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if( !isInitialized ) {
+ initializeBackendImpl();
+ }
+
+ if (!isInitialized || printActive) {
+ return;
+ }
+
+ // NOTE: must do this when the context is not current as it may
+ // involve destroying the pbuffer (current context) and
+ // re-creating it -- tricky to do properly while the context is
+ // current
+ if( !printActive ) {
+ updatePixelScale(backend);
+ if ( handleReshape ) {
+ handleReshape = false;
+ sendReshape = handleReshape();
+ }
+
+ if( isShowing ) {
+ updater.setGraphics(g);
+ backend.doPaintComponent(g);
+ }
+ }
+ } finally {
+ _lock.unlock();
+ }
+ }
+
+ private final void updateWrappedSurfaceScale(final GLDrawable d) {
+ final NativeSurface s = d.getNativeSurface();
+ if( s instanceof WrappedSurface ) {
+ ((WrappedSurface)s).setSurfaceScale(hasPixelScale);
+ }
+ }
+
+ @Override
+ public final boolean setSurfaceScale(final float[] pixelScale) { // HiDPI support
+ System.arraycopy(pixelScale, 0, reqPixelScale, 0, 2);
+ final Backend b = backend;
+ if ( isInitialized && null != b && isShowing ) {
+ if( isShowing || ( printActive && isVisible() ) ) {
+ if (EventQueue.isDispatchThread()) {
+ setSurfaceScaleAction.run();
+ } else {
+ try {
+ EventQueue.invokeAndWait(setSurfaceScaleAction);
+ } catch (final Exception e) {
+ throw new GLException(e);
+ }
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+ private final Runnable setSurfaceScaleAction = new Runnable() {
+ @Override
+ public void run() {
+ final Backend b = backend;
+ if( null != b && setSurfaceScaleImpl(b) ) {
+ if( !helper.isAnimatorAnimatingOnOtherThread() ) {
+ paintImmediatelyAction.run(); // display
+ }
+ }
+ }
+ };
+
+ private final boolean setSurfaceScaleImpl(final Backend b) {
+ if( SurfaceScaleUtils.setNewPixelScale(hasPixelScale, hasPixelScale, reqPixelScale, minPixelScale, maxPixelScale, DEBUG ? getClass().getSimpleName() : null) ) {
+ reshapeImpl(getWidth(), getHeight());
+ updateWrappedSurfaceScale(b.getDrawable());
+ return true;
+ }
+ return false;
+ }
+
+ private final boolean updatePixelScale(final Backend b) {
+ if( JAWTUtil.getPixelScale(getGraphicsConfiguration(), minPixelScale, maxPixelScale) ) {
+ return setSurfaceScaleImpl(b);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public final float[] getRequestedSurfaceScale(final float[] result) {
+ System.arraycopy(reqPixelScale, 0, result, 0, 2);
+ return result;
+ }
+
+ @Override
+ public final float[] getCurrentSurfaceScale(final float[] result) {
+ System.arraycopy(hasPixelScale, 0, result, 0, 2);
+ return result;
+ }
+
+ @Override
+ public float[] getMinimumSurfaceScale(final float[] result) {
+ System.arraycopy(minPixelScale, 0, result, 0, 2);
+ return result;
+ }
+
+ @Override
+ public float[] getMaximumSurfaceScale(final float[] result) {
+ System.arraycopy(maxPixelScale, 0, result, 0, 2);
+ return result;
+ }
+
+ /** Overridden to track when this component is added to a container.
+ Subclasses which override this method must call
+ super.addNotify() in their addNotify() method in order to
+ function properly. <P>
+
+ <DL><DD><CODE>addNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ awtWindowClosingProtocol.addClosingListener();
+
+ // HiDPI support
+ JAWTUtil.getPixelScale(getGraphicsConfiguration(), minPixelScale, maxPixelScale);
+ SurfaceScaleUtils.setNewPixelScale(hasPixelScale, hasPixelScale, reqPixelScale, minPixelScale, maxPixelScale, DEBUG ? getClass().getSimpleName() : null);
+
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.addNotify()");
+ }
+ }
+
+ /** Overridden to track when this component is removed from a
+ container. Subclasses which override this method must call
+ super.removeNotify() in their removeNotify() method in order to
+ function properly. <P>
+
+ <DL><DD><CODE>removeNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
+ @Override
+ public void removeNotify() {
+ awtWindowClosingProtocol.removeClosingListener();
+
+ dispose(null);
+ hasPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
+ hasPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
+ minPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
+ minPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
+ maxPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
+ maxPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
+
+ super.removeNotify();
+ }
+
+ /** Overridden to cause {@link GLDrawableHelper#reshape} to be
+ called on all registered {@link GLEventListener}s. Subclasses
+ which override this method must call super.reshape() in
+ their reshape() method in order to function properly. <P>
+ *
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("deprecation")
+ @Override
+ public void reshape(final int x, final int y, final int width, final int height) {
+ super.reshape(x, y, width, height);
+ reshapeImpl(width, height);
+ }
+
+ private void reshapeImpl(final int width, final int height) {
+ final int scaledWidth = SurfaceScaleUtils.scale(width, hasPixelScale[0]);
+ final int scaledHeight = SurfaceScaleUtils.scale(height, hasPixelScale[1]);
+ if( !printActive && ( handleReshape || scaledWidth != panelWidth || scaledHeight != panelHeight ) ) {
+ reshapeWidth = scaledWidth;
+ reshapeHeight = scaledHeight;
+ handleReshape = true;
+ }
+ if( DEBUG ) {
+ System.err.println(getThreadName()+": GLJPanel.reshape.0 "+this.getName()+" resize ["+(printActive?"printing":"paint")+
+ "] [ this "+getWidth()+"x"+getHeight()+", pixelScale "+getPixelScaleStr()+
+ ", panel "+panelWidth+"x"+panelHeight +
+ "] -> "+(handleReshape?"":"[skipped] ") + width+"x"+height+" * "+getPixelScaleStr()+
+ " -> "+scaledWidth+"x"+scaledHeight+", reshapeSize "+reshapeWidth+"x"+reshapeHeight);
+ }
+ }
+
+ private volatile boolean printActive = false;
+ private GLAnimatorControl printAnimator = null;
+ private GLAutoDrawable printGLAD = null;
+ private AWTTilePainter printAWTTiles = null;
+
+ @Override
+ public void setupPrint(final double scaleMatX, final double scaleMatY, final int numSamples, final int tileWidth, final int tileHeight) {
+ printActive = true;
+ if( DEBUG ) {
+ System.err.printf(getThreadName()+": GLJPanel.setupPrint: scale %f / %f, samples %d, tileSz %d x %d%n", scaleMatX, scaleMatY, numSamples, tileWidth, tileHeight);
+ }
+ final int componentCount = isOpaque() ? 3 : 4;
+ final TileRenderer printRenderer = new TileRenderer();
+ printAWTTiles = new AWTTilePainter(printRenderer, componentCount, scaleMatX, scaleMatY, numSamples, tileWidth, tileHeight, DEBUG);
+ AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, setupPrintOnEDT);
+ }
+ private final Runnable setupPrintOnEDT = new Runnable() {
+ @Override
+ public void run() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if( !isInitialized ) {
+ initializeBackendImpl();
+ }
+ if (!isInitialized) {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, drawable not valid yet");
+ }
+ printActive = false;
+ return; // not yet available ..
+ }
+ if( !isVisible() ) {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, panel not visible");
+ }
+ printActive = false;
+ return; // not yet available ..
+ }
+ sendReshape = false; // clear reshape flag
+ handleReshape = false; // ditto
+ printAnimator = helper.getAnimator();
+ if( null != printAnimator ) {
+ printAnimator.remove(GLJPanel.this);
+ }
+
+ printGLAD = GLJPanel.this; // default: re-use
+ final GLCapabilitiesImmutable gladCaps = getChosenGLCapabilities();
+ final int printNumSamples = printAWTTiles.getNumSamples(gladCaps);
+ GLDrawable printDrawable = printGLAD.getDelegatedDrawable();
+ final boolean reqNewGLADSamples = printNumSamples != gladCaps.getNumSamples();
+ final boolean reqNewGLADSize = printAWTTiles.customTileWidth != -1 && printAWTTiles.customTileWidth != printDrawable.getSurfaceWidth() ||
+ printAWTTiles.customTileHeight != -1 && printAWTTiles.customTileHeight != printDrawable.getSurfaceHeight();
+
+ final GLCapabilities newGLADCaps = (GLCapabilities)gladCaps.cloneMutable();
+ newGLADCaps.setDoubleBuffered(false);
+ newGLADCaps.setOnscreen(false);
+ if( printNumSamples != newGLADCaps.getNumSamples() ) {
+ newGLADCaps.setSampleBuffers(0 < printNumSamples);
+ newGLADCaps.setNumSamples(printNumSamples);
+ }
+ final boolean reqNewGLADSafe = GLDrawableUtil.isSwapGLContextSafe(getRequestedGLCapabilities(), gladCaps, newGLADCaps);
+
+ final boolean reqNewGLAD = ( reqNewGLADSamples || reqNewGLADSize ) && reqNewGLADSafe;
+
+ if( DEBUG ) {
+ System.err.println("AWT print.setup: reqNewGLAD "+reqNewGLAD+"[ samples "+reqNewGLADSamples+", size "+reqNewGLADSize+", safe "+reqNewGLADSafe+"], "+
+ ", drawableSize "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+
+ ", customTileSize "+printAWTTiles.customTileWidth+"x"+printAWTTiles.customTileHeight+
+ ", scaleMat "+printAWTTiles.scaleMatX+" x "+printAWTTiles.scaleMatY+
+ ", numSamples "+printAWTTiles.customNumSamples+" -> "+printNumSamples+", printAnimator "+printAnimator);
+ }
+ if( reqNewGLAD ) {
+ final GLDrawableFactory factory = GLDrawableFactory.getFactory(newGLADCaps.getGLProfile());
+ GLOffscreenAutoDrawable offGLAD = null;
+ try {
+ offGLAD = factory.createOffscreenAutoDrawable(null, newGLADCaps, null,
+ printAWTTiles.customTileWidth != -1 ? printAWTTiles.customTileWidth : DEFAULT_PRINT_TILE_SIZE,
+ printAWTTiles.customTileHeight != -1 ? printAWTTiles.customTileHeight : DEFAULT_PRINT_TILE_SIZE);
+ } catch (final GLException gle) {
+ if( DEBUG ) {
+ System.err.println("Caught: "+gle.getMessage());
+ gle.printStackTrace();
+ }
+ }
+ if( null != offGLAD ) {
+ printGLAD = offGLAD;
+ GLDrawableUtil.swapGLContextAndAllGLEventListener(GLJPanel.this, printGLAD);
+ printDrawable = printGLAD.getDelegatedDrawable();
+ }
+ }
+ printAWTTiles.setGLOrientation( !GLJPanel.this.skipGLOrientationVerticalFlip && printGLAD.isGLOriented(), printGLAD.isGLOriented() );
+ printAWTTiles.renderer.setTileSize(printDrawable.getSurfaceWidth(), printDrawable.getSurfaceHeight(), 0);
+ printAWTTiles.renderer.attachAutoDrawable(printGLAD);
+ if( DEBUG ) {
+ System.err.println("AWT print.setup "+printAWTTiles);
+ System.err.println("AWT print.setup AA "+printNumSamples+", "+newGLADCaps);
+ System.err.println("AWT print.setup printGLAD: "+printGLAD.getSurfaceWidth()+"x"+printGLAD.getSurfaceHeight()+", "+printGLAD);
+ System.err.println("AWT print.setup printDraw: "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+", "+printDrawable);
+ }
+ } finally {
+ _lock.unlock();
+ }
+ }
+ };
+
+ @Override
+ public void releasePrint() {
+ if( !printActive ) {
+ throw new IllegalStateException("setupPrint() not called");
+ }
+ sendReshape = false; // clear reshape flag
+ handleReshape = false; // ditto
+ AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, releasePrintOnEDT);
+ }
+
+ private final Runnable releasePrintOnEDT = new Runnable() {
+ @Override
+ public void run() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if( DEBUG ) {
+ System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0 "+printAWTTiles);
+ }
+ printAWTTiles.dispose();
+ printAWTTiles= null;
+ if( printGLAD != GLJPanel.this ) {
+ GLDrawableUtil.swapGLContextAndAllGLEventListener(printGLAD, GLJPanel.this);
+ printGLAD.destroy();
+ }
+ printGLAD = null;
+ if( null != printAnimator ) {
+ printAnimator.add(GLJPanel.this);
+ printAnimator = null;
+ }
+
+ // trigger reshape, i.e. gl-viewport and -listener - this component might got resized!
+ final int awtWidth = GLJPanel.this.getWidth();
+ final int awtHeight= GLJPanel.this.getHeight();
+ final int scaledAWTWidth = SurfaceScaleUtils.scale(awtWidth, hasPixelScale[0]);
+ final int scaledAWTHeight= SurfaceScaleUtils.scale(awtHeight, hasPixelScale[1]);
+ final GLDrawable drawable = GLJPanel.this.getDelegatedDrawable();
+ if( scaledAWTWidth != panelWidth || scaledAWTHeight != panelHeight ||
+ drawable.getSurfaceWidth() != panelWidth || drawable.getSurfaceHeight() != panelHeight ) {
+ // -> !( awtSize == panelSize == drawableSize )
+ if ( DEBUG ) {
+ System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0: resize [printing] panel " +panelWidth+"x"+panelHeight + " @ scale "+getPixelScaleStr()+
+ ", draw "+drawable.getSurfaceWidth()+"x"+drawable.getSurfaceHeight()+
+ " -> " + awtWidth+"x"+awtHeight+" * "+getPixelScaleStr()+" -> "+scaledAWTWidth+"x"+scaledAWTHeight);
+ }
+ reshapeWidth = scaledAWTWidth;
+ reshapeHeight = scaledAWTHeight;
+ sendReshape = handleReshape(); // reshapeSize -> panelSize, backend reshape w/ GL reshape
+ } else {
+ sendReshape = true; // only GL reshape
+ }
+ printActive = false;
+ display();
+ } finally {
+ _lock.unlock();
+ }
+ }
+ };
+
+ @Override
+ public void print(final Graphics graphics) {
+ if( !printActive ) {
+ throw new IllegalStateException("setupPrint() not called");
+ }
+ if(DEBUG && !EventQueue.isDispatchThread()) {
+ System.err.println(getThreadName()+": Warning: GLCanvas print - not called from AWT-EDT");
+ // we cannot dispatch print on AWT-EDT due to printing internal locking ..
+ }
+ sendReshape = false; // clear reshape flag
+ handleReshape = false; // ditto
+
+ final Graphics2D g2d = (Graphics2D)graphics;
+ try {
+ printAWTTiles.setupGraphics2DAndClipBounds(g2d, getWidth(), getHeight());
+ final TileRenderer tileRenderer = printAWTTiles.renderer;
+ if( DEBUG ) {
+ System.err.println("AWT print.0: "+tileRenderer);
+ }
+ if( !tileRenderer.eot() ) {
+ try {
+ do {
+ if( printGLAD != GLJPanel.this ) {
+ tileRenderer.display();
+ } else {
+ backend.doPlainPaint();
+ }
+ } while ( !tileRenderer.eot() );
+ if( DEBUG ) {
+ System.err.println("AWT print.1: "+printAWTTiles);
+ }
+ } finally {
+ tileRenderer.reset();
+ printAWTTiles.resetGraphics2D();
+ }
+ }
+ } catch (final NoninvertibleTransformException nte) {
+ System.err.println("Caught: Inversion failed of: "+g2d.getTransform());
+ nte.printStackTrace();
+ }
+ if( DEBUG ) {
+ System.err.println("AWT print.X: "+printAWTTiles);
+ }
+ }
+ @Override
+ protected void printComponent(final Graphics g) {
+ if( DEBUG ) {
+ System.err.println("AWT printComponent.X: "+printAWTTiles);
+ }
+ print(g);
+ }
+
+ @Override
+ public void setOpaque(final boolean opaque) {
+ if (backend != null) {
+ backend.setOpaque(opaque);
+ }
+ super.setOpaque(opaque);
+ }
+
+ @Override
+ public void addGLEventListener(final GLEventListener listener) {
+ helper.addGLEventListener(listener);
+ }
+
+ @Override
+ public void addGLEventListener(final int index, final GLEventListener listener) {
+ helper.addGLEventListener(index, listener);
+ }
+
+ @Override
+ public int getGLEventListenerCount() {
+ return helper.getGLEventListenerCount();
+ }
+
+ @Override
+ public GLEventListener getGLEventListener(final int index) throws IndexOutOfBoundsException {
+ return helper.getGLEventListener(index);
+ }
+
+ @Override
+ public boolean areAllGLEventListenerInitialized() {
+ return helper.areAllGLEventListenerInitialized();
+ }
+
+ @Override
+ public boolean getGLEventListenerInitState(final GLEventListener listener) {
+ return helper.getGLEventListenerInitState(listener);
+ }
+
+ @Override
+ public void setGLEventListenerInitState(final GLEventListener listener, final boolean initialized) {
+ helper.setGLEventListenerInitState(listener, initialized);
+ }
+
+ @Override
+ public GLEventListener disposeGLEventListener(final GLEventListener listener, final boolean remove) {
+ final DisposeGLEventListenerAction r = new DisposeGLEventListenerAction(listener, remove);
+ if (EventQueue.isDispatchThread()) {
+ r.run();
+ } else {
+ // Multithreaded redrawing of Swing components is not allowed,
+ // so do everything on the event dispatch thread
+ try {
+ EventQueue.invokeAndWait(r);
+ } catch (final Exception e) {
+ throw new GLException(e);
+ }
+ }
+ return r.listener;
+ }
+
+ @Override
+ public GLEventListener removeGLEventListener(final GLEventListener listener) {
+ return helper.removeGLEventListener(listener);
+ }
+
+ @Override
+ public void setAnimator(final GLAnimatorControl animatorControl) {
+ helper.setAnimator(animatorControl);
+ }
+
+ @Override
+ public GLAnimatorControl getAnimator() {
+ return helper.getAnimator();
+ }
+
+ @Override
+ public final Thread setExclusiveContextThread(final Thread t) throws GLException {
+ return helper.setExclusiveContextThread(t, getContext());
+ }
+
+ @Override
+ public final Thread getExclusiveContextThread() {
+ return helper.getExclusiveContextThread();
+ }
+
+ @Override
+ public boolean invoke(final boolean wait, final GLRunnable glRunnable) throws IllegalStateException {
+ return helper.invoke(this, wait, glRunnable);
+ }
+
+ @Override
+ public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) throws IllegalStateException {
+ return helper.invoke(this, wait, glRunnables);
+ }
+
+ @Override
+ public void flushGLRunnables() {
+ helper.flushGLRunnables();
+ }
+
+ @Override
+ public GLContext createContext(final GLContext shareWith) {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ final Backend b = backend;
+ if ( null == b ) {
+ return null;
+ }
+ return b.createContext(shareWith);
+ } finally {
+ _lock.unlock();
+ }
+ }
+
+ @Override
+ public void setRealized(final boolean realized) {
+ }
+
+ @Override
+ public boolean isRealized() {
+ return isInitialized;
+ }
+
+ @Override
+ public GLContext setContext(final GLContext newCtx, final boolean destroyPrevCtx) {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ final Backend b = backend;
+ if ( null == b ) {
+ return null;
+ }
+ final GLContext oldCtx = b.getContext();
+ GLDrawableHelper.switchContext(b.getDrawable(), oldCtx, destroyPrevCtx, newCtx, additionalCtxCreationFlags);
+ b.setContext(newCtx);
+ return oldCtx;
+ } finally {
+ _lock.unlock();
+ }
+ }
+
+
+ @Override
+ public final GLDrawable getDelegatedDrawable() {
+ final Backend b = backend;
+ if ( null == b ) {
+ return null;
+ }
+ return b.getDrawable();
+ }
+
+ @Override
+ public GLContext getContext() {
+ final Backend b = backend;
+ if ( null == b ) {
+ return null;
+ }
+ return b.getContext();
+ }
+
+ @Override
+ public GL getGL() {
+ if (Beans.isDesignTime()) {
+ return null;
+ }
+ final GLContext context = getContext();
+ return (context == null) ? null : context.getGL();
+ }
+
+ @Override
+ public GL setGL(final GL gl) {
+ final GLContext context = getContext();
+ if (context != null) {
+ context.setGL(gl);
+ return gl;
+ }
+ return null;
+ }
+
+ @Override
+ public void setAutoSwapBufferMode(final boolean enable) {
+ this.autoSwapBufferMode = enable;
+ boolean backendHandlesSwapBuffer = false;
+ if( isInitialized ) {
+ final Backend b = backend;
+ if ( null != b ) {
+ backendHandlesSwapBuffer= b.handlesSwapBuffer();
+ }
+ }
+ if( !backendHandlesSwapBuffer ) {
+ helper.setAutoSwapBufferMode(enable);
+ }
+ }
+
+ @Override
+ public boolean getAutoSwapBufferMode() {
+ return autoSwapBufferMode;
+ }
+
+ @Override
+ public void swapBuffers() {
+ if( isInitialized ) {
+ final Backend b = backend;
+ if ( null != b ) {
+ b.swapBuffers();
+ }
+ }
+ }
+
+ @Override
+ public void setContextCreationFlags(final int flags) {
+ additionalCtxCreationFlags = flags;
+ }
+
+ @Override
+ public int getContextCreationFlags() {
+ return additionalCtxCreationFlags;
+ }
+
+ /** For a translucent GLJPanel (one for which {@link #setOpaque
+ setOpaque}(false) has been called), indicates whether the
+ application should preserve the OpenGL color buffer
+ (GL_COLOR_BUFFER_BIT) for correct rendering of the GLJPanel and
+ underlying widgets which may show through portions of the
+ GLJPanel with alpha values less than 1. Most Swing
+ implementations currently expect the GLJPanel to be completely
+ cleared (e.g., by <code>glClear(GL_COLOR_BUFFER_BIT |
+ GL_DEPTH_BUFFER_BIT)</code>), but for certain optimized Swing
+ implementations which use OpenGL internally, it may be possible
+ to perform OpenGL rendering using the GLJPanel into the same
+ OpenGL drawable as the Swing implementation uses. */
+ public boolean shouldPreserveColorBufferIfTranslucent() {
+ return oglPipelineUsable();
+ }
+
+ @Override
+ public int getSurfaceWidth() {
+ return panelWidth; // scaled surface width in pixel units, current as-from reshape
+ }
+
+ @Override
+ public int getSurfaceHeight() {
+ return panelHeight; // scaled surface height in pixel units, current as-from reshape
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Method returns a valid value only <i>after</i>
+ * the backend has been initialized, either {@link #initializeBackend(boolean) eagerly}
+ * or manually via the first display call.<br/>
+ * Method always returns a valid value when called from within a {@link GLEventListener}.
+ * </p>
+ */
+ @Override
+ public boolean isGLOriented() {
+ final Backend b = backend;
+ if ( null == b ) {
+ return true;
+ }
+ return b.getDrawable().isGLOriented();
+ }
+
+ /**
+ * Skip {@link #isGLOriented()} based vertical flip,
+ * which usually is required by the offscreen backend,
+ * see details about <a href="#verticalFlip">vertical flip</a>
+ * and <a href="#fboGLSLVerticalFlip">FBO / GLSL vertical flip</a>.
+ * <p>
+ * If set to <code>true</code>, user needs to flip the OpenGL rendered scene
+ * <i>if {@link #isGLOriented()} == true</i>, e.g. via the projection matrix.<br/>
+ * See constraints of {@link #isGLOriented()}.
+ * </p>
+ */
+ public final void setSkipGLOrientationVerticalFlip(final boolean v) {
+ skipGLOrientationVerticalFlip = v;
+ }
+ /** See {@link #setSkipGLOrientationVerticalFlip(boolean)}. */
+ public final boolean getSkipGLOrientationVerticalFlip() {
+ return skipGLOrientationVerticalFlip;
+ }
+
+ @Override
+ public GLCapabilitiesImmutable getChosenGLCapabilities() {
+ final Backend b = backend;
+ if ( null == b ) {
+ return null;
+ }
+ return b.getChosenGLCapabilities();
+ }
+
+ @Override
+ public final GLCapabilitiesImmutable getRequestedGLCapabilities() {
+ return reqOffscreenCaps;
+ }
+
+ /**
+ * Set a new requested {@link GLCapabilitiesImmutable} for this GLJPanel
+ * allowing reconfiguration.
+ * <p>
+ * Method shall be invoked from the {@link #isThreadGLCapable() AWT-EDT thread}.
+ * In case it is not invoked on the AWT-EDT thread, an attempt is made to do so.
+ * </p>
+ * <p>
+ * Method will dispose a previous {@link #isRealized() realized} GLContext and offscreen backend!
+ * </p>
+ * @param caps new capabilities.
+ */
+ public final void setRequestedGLCapabilities(final GLCapabilitiesImmutable caps) {
+ if( null == caps ) {
+ throw new IllegalArgumentException("null caps");
+ }
+ Threading.invoke(true,
+ new Runnable() {
+ @Override
+ public void run() {
+ dispose( new Runnable() {
+ @Override
+ public void run() {
+ // switch to new caps and re-init backend
+ // after actual dispose, but before resume animator
+ reqOffscreenCaps = caps;
+ initializeBackendImpl();
+ } } );
+ }
+ }, getTreeLock());
+ }
+
+ @Override
+ public final GLProfile getGLProfile() {
+ return reqOffscreenCaps.getGLProfile();
+ }
+
+ @Override
+ public NativeSurface getNativeSurface() {
+ final Backend b = backend;
+ if ( null == b ) {
+ return null;
+ }
+ return b.getDrawable().getNativeSurface();
+ }
+
+ @Override
+ public long getHandle() {
+ final Backend b = backend;
+ if ( null == b ) {
+ return 0;
+ }
+ return b.getDrawable().getNativeSurface().getSurfaceHandle();
+ }
+
+ @Override
+ public final GLDrawableFactory getFactory() {
+ return factory;
+ }
+
+ /**
+ * Returns the used texture unit, i.e. a value of [0..n], or -1 if non used.
+ * <p>
+ * If implementation uses a texture-unit, it will be known only after the first initialization, i.e. display call.
+ * </p>
+ * <p>
+ * See <a href="#fboGLSLVerticalFlip">FBO / GLSL Vertical Flip</a>.
+ * </p>
+ */
+ public final int getTextureUnit() {
+ final Backend b = backend;
+ if ( null == b ) {
+ return -1;
+ }
+ return b.getTextureUnit();
+ }
+
+ /**
+ * Allows user to request a texture unit to be used,
+ * must be called before the first initialization, i.e. {@link #display()} call.
+ * <p>
+ * Defaults to <code>0</code>.
+ * </p>
+ * <p>
+ * See <a href="#fboGLSLVerticalFlip">FBO / GLSL Vertical Flip</a>.
+ * </p>
+ *
+ * @param v requested texture unit
+ * @see #getTextureUnit()
+ */
+ public final void setTextureUnit(final int v) {
+ requestedTextureUnit = v;
+ }
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ private final Object initSync = new Object();
+ private boolean initializeBackendImpl() {
+ synchronized(initSync) {
+ if( !isInitialized ) {
+ if( handleReshape ) {
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.createAndInitializeBackend.1: ["+(printActive?"printing":"paint")+"] "+
+ panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr() + " -> " +
+ reshapeWidth+"x"+reshapeHeight+" @ scale "+getPixelScaleStr());
+ }
+ panelWidth = reshapeWidth;
+ panelHeight = reshapeHeight;
+ handleReshape = false;
+ } else {
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.createAndInitializeBackend.0: ["+(printActive?"printing":"paint")+"] "+
+ panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr());
+ }
+ }
+
+ if ( 0 >= panelWidth || 0 >= panelHeight ) {
+ return false;
+ }
+
+ if ( null == backend ) {
+ if ( oglPipelineUsable() ) {
+ backend = new J2DOGLBackend();
+ } else {
+ backend = new OffscreenBackend(customPixelBufferProvider);
+ }
+ isInitialized = false;
+ }
+
+ if (!isInitialized) {
+ this.factory = GLDrawableFactoryImpl.getFactoryImpl( reqOffscreenCaps.getGLProfile() ); // reqOffscreenCaps may have changed
+ backend.initialize();
+ }
+ return isInitialized;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ private final String getPixelScaleStr() { return "["+hasPixelScale[0]+", "+hasPixelScale[1]+"]"; }
+
+ @Override
+ public WindowClosingMode getDefaultCloseOperation() {
+ return awtWindowClosingProtocol.getDefaultCloseOperation();
+ }
+
+ @Override
+ public WindowClosingMode setDefaultCloseOperation(final WindowClosingMode op) {
+ return awtWindowClosingProtocol.setDefaultCloseOperation(op);
+ }
+
+ private boolean handleReshape() {
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.handleReshape: "+
+ panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr() + " -> " +
+ reshapeWidth+"x"+reshapeHeight+" @ scale "+getPixelScaleStr());
+ }
+ panelWidth = reshapeWidth;
+ panelHeight = reshapeHeight;
+
+ return backend.handleReshape();
+ }
+
+ // This is used as the GLEventListener for the pbuffer-based backend
+ // as well as the callback mechanism for the other backends
+ class Updater implements GLEventListener {
+ private Graphics g;
+
+ public void setGraphics(final Graphics g) {
+ this.g = g;
+ }
+
+ @Override
+ public void init(final GLAutoDrawable drawable) {
+ if (!backend.preGL(g)) {
+ return;
+ }
+ helper.init(GLJPanel.this, !sendReshape);
+ backend.postGL(g, false);
+ }
+
+ @Override
+ public void dispose(final GLAutoDrawable drawable) {
+ helper.disposeAllGLEventListener(GLJPanel.this, false);
+ }
+
+ @Override
+ public void display(final GLAutoDrawable drawable) {
+ if (!backend.preGL(g)) {
+ return;
+ }
+ if (sendReshape) {
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.display: reshape(" + viewportX + "," + viewportY + " " + panelWidth + "x" + panelHeight + " @ scale "+getPixelScaleStr()+")");
+ }
+ helper.reshape(GLJPanel.this, viewportX, viewportY, panelWidth, panelHeight);
+ sendReshape = false;
+ }
+
+ helper.display(GLJPanel.this);
+ backend.postGL(g, true);
+ }
+
+ public void plainPaint(final GLAutoDrawable drawable) {
+ helper.display(GLJPanel.this);
+ }
+
+ @Override
+ public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {
+ // This is handled above and dispatched directly to the appropriate context
+ }
+ }
+
+ @Override
+ public String toString() {
+ final GLDrawable d = ( null != backend ) ? backend.getDrawable() : null;
+ return "AWT-GLJPanel[ drawableType "+ ( ( null != d ) ? d.getClass().getName() : "null" ) +
+ ", chosenCaps " + getChosenGLCapabilities() +
+ "]";
+ }
+
+ private final Runnable disposeAction = new Runnable() {
+ @Override
+ public void run() {
+ final RecursiveLock _lock = lock;
+ _lock.lock();
+ try {
+ if ( null != backend ) {
+ final GLContext _context = backend.getContext();
+ final boolean backendDestroy = !backend.isUsingOwnLifecycle();
+
+ GLException exceptionOnDisposeGL = null;
+ if( null != _context && _context.isCreated() ) {
+ try {
+ helper.disposeGL(GLJPanel.this, _context, !backendDestroy);
+ } catch (final GLException gle) {
+ exceptionOnDisposeGL = gle;
+ }
+ }
+ Throwable exceptionBackendDestroy = null;
+ if ( backendDestroy ) {
+ try {
+ backend.destroy();
+ } catch( final Throwable re ) {
+ exceptionBackendDestroy = re;
+ }
+ backend = null;
+ isInitialized = false;
+ }
+
+ // throw exception in order of occurrence ..
+ if( null != exceptionOnDisposeGL ) {
+ throw exceptionOnDisposeGL;
+ }
+ if( null != exceptionBackendDestroy ) {
+ throw GLException.newGLException(exceptionBackendDestroy);
+ }
+ }
+ } finally {
+ _lock.unlock();
+ }
+ }
+ };
+
+ private final Runnable updaterInitAction = new Runnable() {
+ @Override
+ public void run() {
+ updater.init(GLJPanel.this);
+ }
+ };
+
+ private final Runnable updaterDisplayAction = new Runnable() {
+ @Override
+ public void run() {
+ updater.display(GLJPanel.this);
+ }
+ };
+
+ private final Runnable updaterPlainDisplayAction = new Runnable() {
+ @Override
+ public void run() {
+ updater.plainPaint(GLJPanel.this);
+ }
+ };
+
+ private final Runnable paintImmediatelyAction = new Runnable() {
+ @Override
+ public void run() {
+ paintImmediately(0, 0, getWidth(), getHeight());
+ }
+ };
+
+ private class DisposeGLEventListenerAction implements Runnable {
+ 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 Backend b = backend;
+ if ( null != b ) {
+ listener = helper.disposeGLEventListener(GLJPanel.this, b.getDrawable(), b.getContext(), listener, remove);
+ }
+ }
+ };
+
+ private int getGLInteger(final GL gl, final int which) {
+ final int[] tmp = new int[1];
+ gl.glGetIntegerv(which, tmp, 0);
+ return tmp[0];
+ }
+
+ protected static String getThreadName() { return Thread.currentThread().getName(); }
+
+ //----------------------------------------------------------------------
+ // Implementations of the various backends
+ //
+
+ /**
+ * Abstraction of the different rendering backends: i.e., pure
+ * software / pixmap rendering, pbuffer-based acceleration, Java 2D
+ * JOGL bridge
+ */
+ static interface Backend {
+ /** Create, Destroy, .. */
+ public boolean isUsingOwnLifecycle();
+
+ /** Called each time the backend needs to initialize itself */
+ public void initialize();
+
+ /** Called when the backend should clean up its resources */
+ public void destroy();
+
+ /** Called when the opacity of the GLJPanel is changed */
+ public void setOpaque(boolean opaque);
+
+ /**
+ * Called to manually create an additional OpenGL context against
+ * this GLJPanel
+ */
+ public GLContext createContext(GLContext shareWith);
+
+ /** Called to set the current backend's GLContext */
+ public void setContext(GLContext ctx);
+
+ /** Called to get the current backend's GLContext */
+ public GLContext getContext();
+
+ /** Called to get the current backend's GLDrawable */
+ public GLDrawable getDrawable();
+
+ /** Returns the used texture unit, i.e. a value of [0..n], or -1 if non used. */
+ public int getTextureUnit();
+
+ /** Called to fetch the "real" GLCapabilities for the backend */
+ public GLCapabilitiesImmutable getChosenGLCapabilities();
+
+ /** Called to fetch the "real" GLProfile for the backend */
+ public GLProfile getGLProfile();
+
+ /**
+ * Called to handle a reshape event. When this is called, the
+ * OpenGL context associated with the backend is not current, to
+ * make it easier to destroy and re-create pbuffers if necessary.
+ */
+ public boolean handleReshape();
+
+ /**
+ * Called before the OpenGL work is done in init() and display().
+ * If false is returned, this render is aborted.
+ */
+ public boolean preGL(Graphics g);
+
+ /**
+ * Return true if backend handles 'swap buffer' itself
+ * and hence the helper's setAutoSwapBuffer(enable) shall not be called.
+ * In this case {@link GLJPanel#autoSwapBufferMode} is being used
+ * in the backend to determine whether to swap buffers or not.
+ */
+ public boolean handlesSwapBuffer();
+
+ /**
+ * Shall invoke underlying drawable's swapBuffer.
+ */
+ public void swapBuffers();
+
+ /**
+ * Called after the OpenGL work is done in init() and display().
+ * The isDisplay argument indicates whether this was called on
+ * behalf of a call to display() rather than init().
+ */
+ public void postGL(Graphics g, boolean isDisplay);
+
+ /** Called from within paintComponent() to initiate the render */
+ public void doPaintComponent(Graphics g);
+
+ /** Called from print .. no backend update desired onscreen */
+ public void doPlainPaint();
+ }
+
+ // Base class used by both the software (pixmap) and pbuffer
+ // backends, both of which rely on reading back the OpenGL frame
+ // buffer and drawing it with a BufferedImage
+ class OffscreenBackend implements Backend {
+ private final AWTGLPixelBufferProvider pixelBufferProvider;
+ private final boolean useSingletonBuffer;
+ private AWTGLPixelBuffer pixelBuffer;
+ private BufferedImage alignedImage;
+
+ // One of these is used to store the read back pixels before storing
+ // in the BufferedImage
+ protected IntBuffer readBackIntsForCPUVFlip;
+
+ // Implementation using software rendering
+ private volatile GLDrawable offscreenDrawable; // volatile: avoid locking for read-only access
+ private boolean offscreenIsFBO;
+ private FBObject fboFlipped;
+ private GLSLTextureRaster glslTextureRaster;
+
+ private volatile GLContextImpl offscreenContext; // volatile: avoid locking for read-only access
+ private boolean flipVertical;
+ private int frameCount = 0;
+
+ // For saving/restoring of OpenGL state during ReadPixels
+ private final GLPixelStorageModes psm = new GLPixelStorageModes();
+
+ OffscreenBackend(final AWTGLPixelBufferProvider custom) {
+ if(null == custom) {
+ pixelBufferProvider = getSingleAWTGLPixelBufferProvider();
+ } else {
+ pixelBufferProvider = custom;
+ }
+ if( pixelBufferProvider instanceof SingletonGLPixelBufferProvider ) {
+ useSingletonBuffer = true;
+ } else {
+ useSingletonBuffer = false;
+ }
+ }
+
+ @Override
+ public final boolean isUsingOwnLifecycle() { return false; }
+
+ @Override
+ public final void initialize() {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": OffscreenBackend: initialize() - frameCount "+frameCount);
+ }
+ GLException glException = null;
+ try {
+ final GLContext[] shareWith = { null };
+ if( helper.isSharedGLContextPending(shareWith) ) {
+ return; // pending ..
+ }
+ offscreenDrawable = factory.createOffscreenDrawable(
+ null /* default platform device */,
+ reqOffscreenCaps,
+ chooser,
+ panelWidth, panelHeight);
+ updateWrappedSurfaceScale(offscreenDrawable);
+ offscreenDrawable.setRealized(true);
+ if( DEBUG_FRAMES ) {
+ offscreenDrawable.getNativeSurface().addSurfaceUpdatedListener(new SurfaceUpdatedListener() {
+ @Override
+ public final void surfaceUpdated(final Object updater, final NativeSurface ns, final long when) {
+ System.err.println(getThreadName()+": OffscreenBackend.swapBuffers - frameCount "+frameCount);
+ } } );
+ }
+
+ //
+ // Pre context configuration
+ //
+ flipVertical = !GLJPanel.this.skipGLOrientationVerticalFlip && offscreenDrawable.isGLOriented();
+ offscreenIsFBO = offscreenDrawable.getRequestedGLCapabilities().isFBO();
+ final boolean useGLSLFlip_pre = flipVertical && offscreenIsFBO && reqOffscreenCaps.getGLProfile().isGL2ES2() && USE_GLSL_TEXTURE_RASTERIZER;
+ if( offscreenIsFBO && !useGLSLFlip_pre ) {
+ // Texture attachment only required for GLSL vertical flip, hence simply use a color-renderbuffer attachment.
+ ((GLFBODrawable)offscreenDrawable).setFBOMode(0);
+ }
+
+ offscreenContext = (GLContextImpl) offscreenDrawable.createContext(shareWith[0]);
+ offscreenContext.setContextCreationFlags(additionalCtxCreationFlags);
+ if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) {
+ isInitialized = true;
+ helper.setAutoSwapBufferMode(false); // we handle swap-buffers, see handlesSwapBuffer()
+
+ final GL gl = offscreenContext.getGL();
+ // Remedy for Bug 1020, i.e. OSX/Nvidia's FBO needs to be cleared before blitting,
+ // otherwise first MSAA frame lacks antialiasing.
+ // Clearing of FBO is performed within GLFBODrawableImpl.initialize(..):
+ // gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+
+ final GLCapabilitiesImmutable chosenCaps = offscreenDrawable.getChosenGLCapabilities();
+ final boolean glslCompliant = !offscreenContext.hasRendererQuirk(GLRendererQuirks.GLSLNonCompliant);
+ final boolean useGLSLFlip = useGLSLFlip_pre && gl.isGL2ES2() && glslCompliant;
+ if( DEBUG ) {
+ System.err.println(getThreadName()+": OffscreenBackend.initialize: useGLSLFlip "+useGLSLFlip+
+ " [flip "+flipVertical+", isFBO "+offscreenIsFBO+", isGL2ES2 "+gl.isGL2ES2()+
+ ", noglsl "+!USE_GLSL_TEXTURE_RASTERIZER+", glslNonCompliant "+!glslCompliant+
+ ", isGL2ES2 " + gl.isGL2ES2()+"\n "+offscreenDrawable+"]");
+ }
+ if( useGLSLFlip ) {
+ final GLFBODrawable fboDrawable = (GLFBODrawable) offscreenDrawable;
+ fboDrawable.setTextureUnit( GLJPanel.this.requestedTextureUnit );
+ try {
+ fboFlipped = new FBObject();
+ fboFlipped.init(gl, panelWidth, panelHeight, 0);
+ fboFlipped.attachColorbuffer(gl, 0, chosenCaps.getAlphaBits()>0);
+ // fboFlipped.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24);
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT); // Bug 1020 (see above), cannot do in FBObject due to unknown 'first bind' state.
+ glslTextureRaster = new GLSLTextureRaster(fboDrawable.getTextureUnit(), true);
+ glslTextureRaster.init(gl.getGL2ES2());
+ glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, panelWidth, panelHeight);
+ } catch (final Exception ex) {
+ ex.printStackTrace();
+ if(null != glslTextureRaster) {
+ glslTextureRaster.dispose(gl.getGL2ES2());
+ glslTextureRaster = null;
+ }
+ if(null != fboFlipped) {
+ fboFlipped.destroy(gl);
+ fboFlipped = null;
+ }
+ }
+ } else {
+ fboFlipped = null;
+ glslTextureRaster = null;
+ }
+ offscreenContext.release();
+ } else {
+ isInitialized = false;
+ }
+ } catch( final GLException gle ) {
+ glException = gle;
+ } finally {
+ if( !isInitialized ) {
+ if(null != offscreenContext) {
+ offscreenContext.destroy();
+ offscreenContext = null;
+ }
+ if(null != offscreenDrawable) {
+ offscreenDrawable.setRealized(false);
+ offscreenDrawable = null;
+ }
+ }
+ if( null != glException ) {
+ throw new GLException("Caught GLException: "+glException.getMessage(), glException);
+ }
+ }
+ }
+
+ @Override
+ public final void destroy() {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": OffscreenBackend: destroy() - offscreenContext: "+(null!=offscreenContext)+" - offscreenDrawable: "+(null!=offscreenDrawable)+" - frameCount "+frameCount);
+ }
+ if ( null != offscreenContext && offscreenContext.isCreated() ) {
+ if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) {
+ try {
+ final GL gl = offscreenContext.getGL();
+ if(null != glslTextureRaster) {
+ glslTextureRaster.dispose(gl.getGL2ES2());
+ }
+ if(null != fboFlipped) {
+ fboFlipped.destroy(gl);
+ }
+ } finally {
+ offscreenContext.destroy();
+ }
+ }
+ }
+ offscreenContext = null;
+ glslTextureRaster = null;
+ fboFlipped = null;
+ offscreenContext = null;
+
+ if (offscreenDrawable != null) {
+ final AbstractGraphicsDevice adevice = offscreenDrawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice();
+ offscreenDrawable.setRealized(false);
+ offscreenDrawable = null;
+ if(null != adevice) {
+ adevice.close();
+ }
+ }
+ offscreenIsFBO = false;
+
+ if( null != readBackIntsForCPUVFlip ) {
+ readBackIntsForCPUVFlip.clear();
+ readBackIntsForCPUVFlip = null;
+ }
+ if( null != pixelBuffer ) {
+ if( !useSingletonBuffer ) {
+ pixelBuffer.dispose();
+ }
+ pixelBuffer = null;
+ }
+ alignedImage = null;
+ }
+
+ @Override
+ public final void setOpaque(final boolean opaque) {
+ if ( opaque != isOpaque() && !useSingletonBuffer ) {
+ pixelBuffer.dispose();
+ pixelBuffer = null;
+ alignedImage = null;
+ }
+ }
+
+ @Override
+ public final boolean preGL(final Graphics g) {
+ // Empty in this implementation
+ return true;
+ }
+
+ @Override
+ public final boolean handlesSwapBuffer() {
+ return true;
+ }
+
+ @Override
+ public final void swapBuffers() {
+ final GLDrawable d = offscreenDrawable;
+ if( null != d ) {
+ d.swapBuffers();
+ }
+ }
+
+ @Override
+ public final void postGL(final Graphics g, final boolean isDisplay) {
+ if (isDisplay) {
+ if( DEBUG_FRAMES ) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: - frameCount "+frameCount);
+ }
+
+ final GL gl = offscreenContext.getGL();
+
+ //
+ // Save TextureState ASAP, i.e. the user values for the used FBO texture-unit
+ // and the current active texture-unit (if not same)
+ //
+ final TextureState usrTexState, fboTexState;
+ final int fboTexUnit;
+
+ if( offscreenIsFBO ) {
+ fboTexUnit = GL.GL_TEXTURE0 + ((GLFBODrawable)offscreenDrawable).getTextureUnit();
+ usrTexState = new TextureState(gl, GL.GL_TEXTURE_2D);
+ if( fboTexUnit != usrTexState.getUnit() ) {
+ // glActiveTexture(..) + glBindTexture(..) are implicit performed in GLFBODrawableImpl's
+ // swapBuffers/contextMadeCurent -> swapFBOImpl.
+ // We need to cache the texture unit's bound texture-id before it's overwritten.
+ gl.glActiveTexture(fboTexUnit);
+ fboTexState = new TextureState(gl, fboTexUnit, GL.GL_TEXTURE_2D);
+ } else {
+ fboTexState = usrTexState;
+ }
+ } else {
+ fboTexUnit = 0;
+ usrTexState = null;
+ fboTexState = null;
+ }
+
+
+ if( autoSwapBufferMode ) {
+ // Since we only use a single-buffer non-MSAA or double-buffered MSAA offscreenDrawable,
+ // we can always swap!
+ offscreenDrawable.swapBuffers();
+ }
+
+ final int componentCount;
+ final int alignment;
+ if( isOpaque() ) {
+ // w/o alpha
+ componentCount = 3;
+ alignment = 1;
+ } else {
+ // with alpha
+ componentCount = 4;
+ alignment = 4;
+ }
+
+ final PixelFormat awtPixelFormat = pixelBufferProvider.getAWTPixelFormat(gl.getGLProfile(), componentCount);
+ final GLPixelAttributes pixelAttribs = pixelBufferProvider.getAttributes(gl, componentCount, true);
+
+ if( useSingletonBuffer ) { // attempt to fetch the latest AWTGLPixelBuffer
+ pixelBuffer = (AWTGLPixelBuffer) ((SingletonGLPixelBufferProvider)pixelBufferProvider).getSingleBuffer(awtPixelFormat.comp, pixelAttribs, true);
+ }
+ if( null != pixelBuffer && pixelBuffer.requiresNewBuffer(gl, panelWidth, panelHeight, 0) ) {
+ pixelBuffer.dispose();
+ pixelBuffer = null;
+ alignedImage = null;
+ }
+ final boolean DEBUG_INIT;
+ if ( null == pixelBuffer ) {
+ if (0 >= panelWidth || 0 >= panelHeight ) {
+ return;
+ }
+ pixelBuffer = pixelBufferProvider.allocate(gl, awtPixelFormat.comp, pixelAttribs, true, panelWidth, panelHeight, 1, 0);
+ if(DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" pixelBufferProvider isSingletonBufferProvider "+useSingletonBuffer+", 0x"+Integer.toHexString(pixelBufferProvider.hashCode())+", "+pixelBufferProvider.getClass().getSimpleName());
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" pixelBuffer 0x"+Integer.toHexString(pixelBuffer.hashCode())+", "+pixelBuffer+", alignment "+alignment);
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" flippedVertical "+flipVertical+", glslTextureRaster "+(null!=glslTextureRaster)+", isGL2ES3 "+gl.isGL2ES3());
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" panelSize "+panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr());
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" pixelAttribs "+pixelAttribs);
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" awtPixelFormat "+awtPixelFormat);
+ DEBUG_INIT = true;
+ } else {
+ DEBUG_INIT = false;
+ }
+ } else {
+ DEBUG_INIT = false;
+ }
+ if( offscreenDrawable.getSurfaceWidth() != panelWidth || offscreenDrawable.getSurfaceHeight() != panelHeight ) {
+ throw new InternalError("OffscreenDrawable panelSize mismatch (reshape missed): panelSize "+panelWidth+"x"+panelHeight+" != drawable "+offscreenDrawable.getSurfaceWidth()+"x"+offscreenDrawable.getSurfaceHeight()+", on thread "+getThreadName());
+ }
+ if( null == alignedImage ||
+ panelWidth != alignedImage.getWidth() || panelHeight != alignedImage.getHeight() ||
+ !pixelBuffer.isDataBufferSource(alignedImage) ) {
+ alignedImage = pixelBuffer.getAlignedImage(panelWidth, panelHeight);
+ if(DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" new alignedImage "+alignedImage.getWidth()+"x"+alignedImage.getHeight()+" @ scale "+getPixelScaleStr()+", "+alignedImage+", pixelBuffer "+pixelBuffer.width+"x"+pixelBuffer.height+", "+pixelBuffer);
+ }
+ }
+ final IntBuffer readBackInts;
+
+ if( !flipVertical || null != glslTextureRaster ) {
+ readBackInts = (IntBuffer) pixelBuffer.buffer;
+ } else {
+ if( null == readBackIntsForCPUVFlip || pixelBuffer.width * pixelBuffer.height > readBackIntsForCPUVFlip.remaining() ) {
+ readBackIntsForCPUVFlip = IntBuffer.allocate(pixelBuffer.width * pixelBuffer.height);
+ }
+ readBackInts = readBackIntsForCPUVFlip;
+ }
+
+ // Must now copy pixels from offscreen context into surface
+ if( DEBUG_FRAMES ) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.readPixels: - frameCount "+frameCount);
+ }
+
+ // Save PACK modes, reset them to defaults and set alignment
+ psm.setPackAlignment(gl, alignment);
+ if( gl.isGL2ES3() ) {
+ final GL2ES3 gl2es3 = gl.getGL2ES3();
+ psm.setPackRowLength(gl2es3, panelWidth);
+ gl2es3.glReadBuffer(gl2es3.getDefaultReadBuffer());
+ if( DEBUG_INIT ) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.0: fboDrawable "+offscreenDrawable);
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.0: isGL2ES3, readBuffer 0x"+Integer.toHexString(gl2es3.getDefaultReadBuffer()));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.0: def-readBuffer 0x"+Integer.toHexString(gl2es3.getDefaultReadBuffer()));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.0: def-readFBO 0x"+Integer.toHexString(gl2es3.getDefaultReadFramebuffer()));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.0: bound-readFBO 0x"+Integer.toHexString(gl2es3.getBoundFramebuffer(GL2ES3.GL_READ_FRAMEBUFFER)));
+ }
+ }
+
+ if(null != glslTextureRaster) { // implies flippedVertical
+ final boolean viewportChange;
+ final int[] usrViewport = new int[] { 0, 0, 0, 0 };
+ gl.glGetIntegerv(GL.GL_VIEWPORT, usrViewport, 0);
+ viewportChange = 0 != usrViewport[0] || 0 != usrViewport[1] ||
+ panelWidth != usrViewport[2] || panelHeight != usrViewport[3];
+ if( DEBUG_VIEWPORT ) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL: "+GLJPanel.this.getName()+" Viewport: change "+viewportChange+
+ ", "+usrViewport[0]+"/"+usrViewport[1]+" "+usrViewport[2]+"x"+usrViewport[3]+
+ " -> 0/0 "+panelWidth+"x"+panelHeight);
+ }
+ if( viewportChange ) {
+ gl.glViewport(0, 0, panelWidth, panelHeight);
+ }
+
+ // perform vert-flipping via OpenGL/FBO
+ final GLFBODrawable fboDrawable = (GLFBODrawable)offscreenDrawable;
+ final FBObject.TextureAttachment fboTex = fboDrawable.getColorbuffer(GL.GL_FRONT).getTextureAttachment();
+
+ fboFlipped.bind(gl);
+
+ // gl.glActiveTexture(GL.GL_TEXTURE0 + fboDrawable.getTextureUnit()); // implicit by GLFBODrawableImpl: swapBuffers/contextMadeCurent -> swapFBOImpl
+ gl.glBindTexture(GL.GL_TEXTURE_2D, fboTex.getName());
+ // gl.glClear(GL.GL_DEPTH_BUFFER_BIT); // fboFlipped runs w/o DEPTH!
+
+ glslTextureRaster.display(gl.getGL2ES2());
+ if( DEBUG_INIT ) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.1: fboDrawable "+fboDrawable);
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.1: read from fbo-rb "+fboFlipped.getReadFramebuffer()+", fbo "+fboFlipped);
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.1: isGL2ES3, readBuffer 0x"+Integer.toHexString(gl.getDefaultReadBuffer()));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.1: def-readBuffer 0x"+Integer.toHexString(gl.getDefaultReadBuffer()));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.1: def-readFBO 0x"+Integer.toHexString(gl.getDefaultReadFramebuffer()));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.1: bound-readFBO 0x"+Integer.toHexString(gl.getBoundFramebuffer(GL2ES3.GL_READ_FRAMEBUFFER)));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.1: "+GLJPanel.this.getName()+" pixelAttribs "+pixelAttribs);
+ }
+ gl.glReadPixels(0, 0, panelWidth, panelHeight, pixelAttribs.format, pixelAttribs.type, readBackInts);
+
+ fboFlipped.unbind(gl);
+ if( DEBUG_INIT ) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.2: fboDrawable "+fboDrawable);
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.2: read from fbo-rb "+fboFlipped.getReadFramebuffer()+", fbo "+fboFlipped);
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.2: isGL2ES3, readBuffer 0x"+Integer.toHexString(gl.getDefaultReadBuffer()));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.2: def-readBuffer 0x"+Integer.toHexString(gl.getDefaultReadBuffer()));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.2: def-readFBO 0x"+Integer.toHexString(gl.getDefaultReadFramebuffer()));
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0.2: bound-readFBO 0x"+Integer.toHexString(gl.getBoundFramebuffer(GL2ES3.GL_READ_FRAMEBUFFER)));
+ }
+ if( viewportChange ) {
+ gl.glViewport(usrViewport[0], usrViewport[1], usrViewport[2], usrViewport[3]);
+ }
+ } else {
+ gl.glReadPixels(0, 0, panelWidth, panelHeight, pixelAttribs.format, pixelAttribs.type, readBackInts);
+
+ if ( flipVertical ) {
+ // Copy temporary data into raster of BufferedImage for faster
+ // blitting Note that we could avoid this copy in the cases
+ // where !offscreenDrawable.isGLOriented(),
+ // but that's the software rendering path which is very slow anyway.
+ final BufferedImage image = alignedImage;
+ final int[] src = readBackInts.array();
+ final int[] dest = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
+ final int incr = panelWidth;
+ int srcPos = 0;
+ int destPos = (panelHeight - 1) * panelWidth;
+ for (; destPos >= 0; srcPos += incr, destPos -= incr) {
+ System.arraycopy(src, srcPos, dest, destPos, incr);
+ }
+ }
+ }
+ if( 0 != fboTexUnit ) { // implies offscreenIsFBO
+ fboTexState.restore(gl);
+ if( fboTexUnit != usrTexState.getUnit() ) {
+ usrTexState.restore(gl);
+ }
+ }
+
+ // Restore saved modes.
+ psm.restore(gl);
+
+ // Note: image will be drawn back in paintComponent() for
+ // correctness on all platforms
+ }
+ }
+
+ @Override
+ public final int getTextureUnit() {
+ if(null != glslTextureRaster && null != offscreenDrawable) { // implies flippedVertical
+ return ((GLFBODrawable)offscreenDrawable).getTextureUnit();
+ }
+ return -1;
+ }
+
+ @Override
+ public final void doPaintComponent(final Graphics g) {
+ helper.invokeGL(offscreenDrawable, offscreenContext, updaterDisplayAction, updaterInitAction);
+
+ if ( null != alignedImage ) {
+ if( DEBUG_FRAMES ) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.doPaintComponent.drawImage: - frameCount "+frameCount);
+ }
+ // Draw resulting image in one shot
+ g.drawImage(alignedImage, 0, 0,
+ SurfaceScaleUtils.scaleInv(alignedImage.getWidth(), hasPixelScale[0]),
+ SurfaceScaleUtils.scaleInv(alignedImage.getHeight(), hasPixelScale[1]), null); // Null ImageObserver since image data is ready.
+ }
+ frameCount++;
+ }
+
+ @Override
+ public final void doPlainPaint() {
+ helper.invokeGL(offscreenDrawable, offscreenContext, updaterPlainDisplayAction, updaterInitAction);
+ }
+
+ @Override
+ public final boolean handleReshape() {
+ GLDrawableImpl _drawable = (GLDrawableImpl)offscreenDrawable;
+ {
+ final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, offscreenContext, panelWidth, panelHeight);
+ if(_drawable != _drawableNew) {
+ // write back
+ _drawable = _drawableNew;
+ offscreenDrawable = _drawableNew;
+ updateWrappedSurfaceScale(offscreenDrawable);
+ }
+ }
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.handleReshape: " +panelWidth+"x"+panelHeight + " @ scale "+getPixelScaleStr() + " -> " + _drawable.getSurfaceWidth()+"x"+_drawable.getSurfaceHeight());
+ }
+ panelWidth = _drawable.getSurfaceWidth();
+ panelHeight = _drawable.getSurfaceHeight();
+
+ if( null != glslTextureRaster ) {
+ if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) {
+ try {
+ final GL gl = offscreenContext.getGL();
+ fboFlipped.reset(gl, panelWidth, panelHeight, 0);
+ glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, panelWidth, panelHeight);
+ } finally {
+ offscreenContext.release();
+ }
+ }
+ }
+ return _drawable.isRealized();
+ }
+
+ @Override
+ public final GLContext createContext(final GLContext shareWith) {
+ return (null != offscreenDrawable) ? offscreenDrawable.createContext(shareWith) : null;
+ }
+
+ @Override
+ public final void setContext(final GLContext ctx) {
+ offscreenContext=(GLContextImpl)ctx;
+ }
+
+ @Override
+ public final GLContext getContext() {
+ return offscreenContext;
+ }
+
+ @Override
+ public final GLDrawable getDrawable() {
+ return offscreenDrawable;
+ }
+
+ @Override
+ public final GLCapabilitiesImmutable getChosenGLCapabilities() {
+ if (offscreenDrawable == null) {
+ return null;
+ }
+ return offscreenDrawable.getChosenGLCapabilities();
+ }
+
+ @Override
+ public final GLProfile getGLProfile() {
+ if (offscreenDrawable == null) {
+ return null;
+ }
+ return offscreenDrawable.getGLProfile();
+ }
+ }
+
+ class J2DOGLBackend implements Backend {
+ // Opaque Object identifier representing the Java2D surface we are
+ // drawing to; used to determine when to destroy and recreate JOGL
+ // context
+ private Object j2dSurface;
+ // Graphics object being used during Java2D update action
+ // (absolutely essential to cache this)
+ // No-op context representing the Java2D OpenGL context
+ private GLContext j2dContext;
+ // Context associated with no-op drawable representing the JOGL
+ // OpenGL context
+ private GLDrawable joglDrawable;
+ // The real OpenGL context JOGL uses to render
+ private GLContext joglContext;
+ // State captured from Java2D OpenGL context necessary in order to
+ // properly render into Java2D back buffer
+ private final int[] drawBuffer = new int[1];
+ private final int[] readBuffer = new int[1];
+ // This is required when the FBO option of the Java2D / OpenGL
+ // pipeline is active
+ private final int[] frameBuffer = new int[1];
+ // Current (as of this writing) NVidia drivers have a couple of bugs
+ // relating to the sharing of framebuffer and renderbuffer objects
+ // between contexts. It appears we have to (a) reattach the color
+ // attachment and (b) actually create new depth buffer storage and
+ // attach it in order for the FBO to behave properly in our context.
+ private boolean checkedForFBObjectWorkarounds;
+ private boolean fbObjectWorkarounds;
+ private int[] frameBufferDepthBuffer;
+ private int[] frameBufferTexture;
+ private boolean createNewDepthBuffer;
+ // Current (as of this writing) ATI drivers have problems when the
+ // same FBO is bound in two different contexts. Here we check for
+ // this case and explicitly release the FBO from Java2D's context
+ // before switching to ours. Java2D will re-bind the FBO when it
+ // makes its context current the next time. Interestingly, if we run
+ // this code path on NVidia hardware, it breaks the rendering
+ // results -- no output is generated. This doesn't appear to be an
+ // interaction with the abovementioned NVidia-specific workarounds,
+ // as even if we disable that code the FBO is still reported as
+ // incomplete in our context.
+ private boolean checkedGLVendor;
+ private boolean vendorIsATI;
+
+ // Holding on to this GraphicsConfiguration is a workaround for a
+ // problem in the Java 2D / JOGL bridge when FBOs are enabled; see
+ // comment related to Issue 274 below
+ private GraphicsConfiguration workaroundConfig;
+
+ @Override
+ public final boolean isUsingOwnLifecycle() { return true; }
+
+ @Override
+ public final void initialize() {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": J2DOGL: initialize()");
+ }
+ // No-op in this implementation; everything is done lazily
+ isInitialized = true;
+ }
+
+ @Override
+ public final void destroy() {
+ Java2D.invokeWithOGLContextCurrent(null, new Runnable() {
+ @Override
+ public void run() {
+ if(DEBUG) {
+ System.err.println(getThreadName()+": J2DOGL: destroy() - joglContext: "+(null!=joglContext)+" - joglDrawable: "+(null!=joglDrawable));
+ }
+ if (joglContext != null) {
+ joglContext.destroy();
+ joglContext = null;
+ }
+ joglDrawable = null;
+ if (j2dContext != null) {
+ j2dContext.destroy();
+ j2dContext = null;
+ }
+ }
+ });
+ }
+
+ @Override
+ public final void setOpaque(final boolean opaque) {
+ // Empty in this implementation
+ }
+
+ @Override
+ public final GLContext createContext(final GLContext shareWith) {
+ if(null != shareWith) {
+ throw new GLException("J2DOGLBackend cannot create context w/ additional shared context, since it already needs to share the context w/ J2D.");
+ }
+ return (null != joglDrawable && null != j2dContext) ? joglDrawable.createContext(j2dContext) : null;
+ }
+
+ @Override
+ public final void setContext(final GLContext ctx) {
+ joglContext=ctx;
+ }
+
+ @Override
+ public final GLContext getContext() {
+ return joglContext;
+ }
+
+ @Override
+ public final GLDrawable getDrawable() {
+ return joglDrawable;
+ }
+
+ @Override
+ public final int getTextureUnit() { return -1; }
+
+ @Override
+ public final GLCapabilitiesImmutable getChosenGLCapabilities() {
+ // FIXME: should do better than this; is it possible to query J2D Caps ?
+ return new GLCapabilities(null);
+ }
+
+ @Override
+ public final GLProfile getGLProfile() {
+ // FIXME: should do better than this; is it possible to query J2D's Profile ?
+ return GLProfile.getDefault(GLProfile.getDefaultDevice());
+ }
+
+ @Override
+ public final boolean handleReshape() {
+ // Empty in this implementation
+ return true;
+ }
+
+ @Override
+ public final boolean preGL(final Graphics g) {
+ final GL2 gl = joglContext.getGL().getGL2();
+ // Set up needed state in JOGL context from Java2D context
+ gl.glEnable(GL.GL_SCISSOR_TEST);
+ final Rectangle r = Java2D.getOGLScissorBox(g);
+
+ if (r == null) {
+ if (DEBUG) {
+ System.err.println(getThreadName()+": Java2D.getOGLScissorBox() returned null");
+ }
+ return false;
+ }
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: gl.glScissor(" + r.x + ", " + r.y + ", " + r.width + ", " + r.height + ")");
+ }
+
+ gl.glScissor(r.x, r.y, r.width, r.height);
+ final Rectangle oglViewport = Java2D.getOGLViewport(g, panelWidth, panelHeight);
+ // If the viewport X or Y changes, in addition to the panel's
+ // width or height, we need to send a reshape operation to the
+ // client
+ if ((viewportX != oglViewport.x) ||
+ (viewportY != oglViewport.y)) {
+ sendReshape = true;
+ if (DEBUG) {
+ System.err.println(getThreadName()+": Sending reshape because viewport changed");
+ System.err.println(" viewportX (" + viewportX + ") ?= oglViewport.x (" + oglViewport.x + ")");
+ System.err.println(" viewportY (" + viewportY + ") ?= oglViewport.y (" + oglViewport.y + ")");
+ }
+ }
+ viewportX = oglViewport.x;
+ viewportY = oglViewport.y;
+
+ // If the FBO option is active, bind to the FBO from the Java2D
+ // context.
+ // Note that all of the plumbing in the context sharing stuff will
+ // allow us to bind to this object since it's in our namespace.
+ if (Java2D.isFBOEnabled() &&
+ Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
+
+ // The texture target for Java2D's OpenGL pipeline when using FBOs
+ // -- either GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE_ARB
+ final int fboTextureTarget = Java2D.getOGLTextureType(g);
+
+ if (!checkedForFBObjectWorkarounds) {
+ checkedForFBObjectWorkarounds = true;
+ gl.glBindTexture(fboTextureTarget, 0);
+ gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, frameBuffer[0]);
+ final int status = gl.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER);
+ if (status != GL.GL_FRAMEBUFFER_COMPLETE) {
+ // Need to do workarounds
+ fbObjectWorkarounds = true;
+ createNewDepthBuffer = true;
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: ERR GL_FRAMEBUFFER_BINDING: Discovered Invalid J2D FBO("+frameBuffer[0]+"): "+FBObject.getStatusString(status) +
+ ", frame_buffer_object workarounds to be necessary");
+ }
+ } else {
+ // Don't need the frameBufferTexture temporary any more
+ frameBufferTexture = null;
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: OK GL_FRAMEBUFFER_BINDING: "+frameBuffer[0]);
+ }
+ }
+ }
+
+ if (fbObjectWorkarounds && createNewDepthBuffer) {
+ if (frameBufferDepthBuffer == null)
+ frameBufferDepthBuffer = new int[1];
+
+ // Create our own depth renderbuffer and associated storage
+ // If we have an old one, delete it
+ if (frameBufferDepthBuffer[0] != 0) {
+ gl.glDeleteRenderbuffers(1, frameBufferDepthBuffer, 0);
+ frameBufferDepthBuffer[0] = 0;
+ }
+
+ gl.glBindTexture(fboTextureTarget, frameBufferTexture[0]);
+ final int[] width = new int[1];
+ final int[] height = new int[1];
+ gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2GL3.GL_TEXTURE_WIDTH, width, 0);
+ gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2GL3.GL_TEXTURE_HEIGHT, height, 0);
+
+ gl.glGenRenderbuffers(1, frameBufferDepthBuffer, 0);
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: Generated frameBufferDepthBuffer " + frameBufferDepthBuffer[0] +
+ " with width " + width[0] + ", height " + height[0]);
+ }
+
+ gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, frameBufferDepthBuffer[0]);
+ // FIXME: may need a loop here like in Java2D
+ gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL.GL_DEPTH_COMPONENT24, width[0], height[0]);
+
+ gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0);
+ createNewDepthBuffer = false;
+ }
+
+ gl.glBindTexture(fboTextureTarget, 0);
+ gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, frameBuffer[0]);
+
+ if (fbObjectWorkarounds) {
+ // Hook up the color and depth buffer attachment points for this framebuffer
+ gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER,
+ GL.GL_COLOR_ATTACHMENT0,
+ fboTextureTarget,
+ frameBufferTexture[0],
+ 0);
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: frameBufferDepthBuffer: " + frameBufferDepthBuffer[0]);
+ }
+ gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER,
+ GL.GL_DEPTH_ATTACHMENT,
+ GL.GL_RENDERBUFFER,
+ frameBufferDepthBuffer[0]);
+ }
+
+ if (DEBUG) {
+ final int status = gl.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER);
+ if (status != GL.GL_FRAMEBUFFER_COMPLETE) {
+ throw new GLException("Error: framebuffer was incomplete: status = 0x" +
+ Integer.toHexString(status));
+ }
+ }
+ } else {
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: Setting up drawBuffer " + drawBuffer[0] +
+ " and readBuffer " + readBuffer[0]);
+ }
+
+ gl.glDrawBuffer(drawBuffer[0]);
+ gl.glReadBuffer(readBuffer[0]);
+ }
+
+ return true;
+ }
+
+ @Override
+ public final boolean handlesSwapBuffer() {
+ return false;
+ }
+
+ @Override
+ public final void swapBuffers() {
+ final GLDrawable d = joglDrawable;
+ if( null != d ) {
+ d.swapBuffers();
+ }
+ }
+
+ @Override
+ public final void postGL(final Graphics g, final boolean isDisplay) {
+ // Cause OpenGL pipeline to flush its results because
+ // otherwise it's possible we will buffer up multiple frames'
+ // rendering results, resulting in apparent mouse lag
+ final GL gl = joglContext.getGL();
+ gl.glFinish();
+
+ if (Java2D.isFBOEnabled() &&
+ Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
+ // Unbind the framebuffer from our context to work around
+ // apparent driver bugs or at least unspecified behavior causing
+ // OpenGL to run out of memory with certain cards and drivers
+ gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
+ }
+ }
+
+ @Override
+ public final void doPaintComponent(final Graphics g) {
+ // This is a workaround for an issue in the Java 2D / JOGL
+ // bridge (reported by an end user as JOGL Issue 274) where Java
+ // 2D can occasionally leave its internal OpenGL context current
+ // to the on-screen window rather than its internal "scratch"
+ // pbuffer surface to which the FBO is attached. JOGL expects to
+ // find a stable OpenGL drawable (on Windows, an HDC) upon which
+ // it can create another OpenGL context. It turns out that, on
+ // Windows, when Java 2D makes its internal OpenGL context
+ // current against the window in order to put pixels on the
+ // screen, it gets the device context for the window, makes its
+ // context current, and releases the device context. This means
+ // that when JOGL's Runnable gets to run below, the HDC is
+ // already invalid. The workaround for this is to force Java 2D
+ // to make its context current to the scratch surface, which we
+ // can do by executing an empty Runnable with the "shared"
+ // context current. This will be fixed in a Java SE 6 update
+ // release, hopefully 6u2.
+ if (Java2D.isFBOEnabled()) {
+ if (workaroundConfig == null) {
+ workaroundConfig = GraphicsEnvironment.
+ getLocalGraphicsEnvironment().
+ getDefaultScreenDevice().
+ getDefaultConfiguration();
+ }
+ Java2D.invokeWithOGLSharedContextCurrent(workaroundConfig, new Runnable() { @Override
+ public void run() {}});
+ }
+
+ Java2D.invokeWithOGLContextCurrent(g, new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.invokeWithOGLContextCurrent");
+ }
+
+ // Create no-op context representing Java2D context
+ if (j2dContext == null) {
+ j2dContext = factory.createExternalGLContext();
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel.Created External Context: "+j2dContext);
+ }
+ if (DEBUG) {
+// j2dContext.setGL(new DebugGL2(j2dContext.getGL().getGL2()));
+ }
+
+ // Check to see whether we can support the requested
+ // capabilities or need to fall back to a pbuffer
+ // FIXME: add more checks?
+
+ j2dContext.makeCurrent();
+ final GL gl = j2dContext.getGL();
+ if ((getGLInteger(gl, GL.GL_RED_BITS) < reqOffscreenCaps.getRedBits()) ||
+ (getGLInteger(gl, GL.GL_GREEN_BITS) < reqOffscreenCaps.getGreenBits()) ||
+ (getGLInteger(gl, GL.GL_BLUE_BITS) < reqOffscreenCaps.getBlueBits()) ||
+ // (getGLInteger(gl, GL.GL_ALPHA_BITS) < offscreenCaps.getAlphaBits()) ||
+ (getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) < reqOffscreenCaps.getAccumRedBits()) ||
+ (getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) < reqOffscreenCaps.getAccumGreenBits()) ||
+ (getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) < reqOffscreenCaps.getAccumBlueBits()) ||
+ (getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) < reqOffscreenCaps.getAccumAlphaBits()) ||
+ // (getGLInteger(gl, GL2.GL_DEPTH_BITS) < offscreenCaps.getDepthBits()) ||
+ (getGLInteger(gl, GL.GL_STENCIL_BITS) < reqOffscreenCaps.getStencilBits())) {
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: Falling back to pbuffer-based support because Java2D context insufficient");
+ System.err.println(" Available Required");
+ System.err.println("GL_RED_BITS " + getGLInteger(gl, GL.GL_RED_BITS) + " " + reqOffscreenCaps.getRedBits());
+ System.err.println("GL_GREEN_BITS " + getGLInteger(gl, GL.GL_GREEN_BITS) + " " + reqOffscreenCaps.getGreenBits());
+ System.err.println("GL_BLUE_BITS " + getGLInteger(gl, GL.GL_BLUE_BITS) + " " + reqOffscreenCaps.getBlueBits());
+ System.err.println("GL_ALPHA_BITS " + getGLInteger(gl, GL.GL_ALPHA_BITS) + " " + reqOffscreenCaps.getAlphaBits());
+ System.err.println("GL_ACCUM_RED_BITS " + getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) + " " + reqOffscreenCaps.getAccumRedBits());
+ System.err.println("GL_ACCUM_GREEN_BITS " + getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) + " " + reqOffscreenCaps.getAccumGreenBits());
+ System.err.println("GL_ACCUM_BLUE_BITS " + getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) + " " + reqOffscreenCaps.getAccumBlueBits());
+ System.err.println("GL_ACCUM_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) + " " + reqOffscreenCaps.getAccumAlphaBits());
+ System.err.println("GL_DEPTH_BITS " + getGLInteger(gl, GL.GL_DEPTH_BITS) + " " + reqOffscreenCaps.getDepthBits());
+ System.err.println("GL_STENCIL_BITS " + getGLInteger(gl, GL.GL_STENCIL_BITS) + " " + reqOffscreenCaps.getStencilBits());
+ }
+ isInitialized = false;
+ backend = null;
+ java2DGLPipelineOK = false;
+ handleReshape = true;
+ j2dContext.destroy();
+ j2dContext = null;
+ return;
+ }
+ } else {
+ j2dContext.makeCurrent();
+ }
+ try {
+ captureJ2DState(j2dContext.getGL(), g);
+ final Object curSurface = Java2D.getOGLSurfaceIdentifier(g);
+ if (curSurface != null) {
+ if (j2dSurface != curSurface) {
+ if (joglContext != null) {
+ joglContext.destroy();
+ joglContext = null;
+ joglDrawable = null;
+ sendReshape = true;
+ if (DEBUG) {
+ System.err.println(getThreadName()+": Sending reshape because surface changed");
+ System.err.println("New surface = " + curSurface);
+ }
+ }
+ j2dSurface = curSurface;
+ if (DEBUG) {
+ System.err.print(getThreadName()+": Surface type: ");
+ final int surfaceType = Java2D.getOGLSurfaceType(g);
+ if (surfaceType == Java2D.UNDEFINED) {
+ System.err.println("UNDEFINED");
+ } else if (surfaceType == Java2D.WINDOW) {
+ System.err.println("WINDOW");
+ } else if (surfaceType == Java2D.PBUFFER) {
+ System.err.println("PBUFFER");
+ } else if (surfaceType == Java2D.TEXTURE) {
+ System.err.println("TEXTURE");
+ } else if (surfaceType == Java2D.FLIP_BACKBUFFER) {
+ System.err.println("FLIP_BACKBUFFER");
+ } else if (surfaceType == Java2D.FBOBJECT) {
+ System.err.println("FBOBJECT");
+ } else {
+ System.err.println("(Unknown surface type " + surfaceType + ")");
+ }
+ }
+ }
+ if (joglContext == null) {
+ final AbstractGraphicsDevice device = j2dContext.getGLDrawable().getNativeSurface().getGraphicsConfiguration().getScreen().getDevice();
+ if (factory.canCreateExternalGLDrawable(device)) {
+ joglDrawable = factory.createExternalGLDrawable();
+ joglContext = joglDrawable.createContext(j2dContext);
+ if (DEBUG) {
+ System.err.println("-- Created External Drawable: "+joglDrawable);
+ System.err.println("-- Created Context: "+joglContext);
+ }
+ }
+ if (Java2D.isFBOEnabled() &&
+ Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT &&
+ fbObjectWorkarounds) {
+ createNewDepthBuffer = true;
+ }
+ }
+ helper.invokeGL(joglDrawable, joglContext, updaterDisplayAction, updaterInitAction);
+ }
+ } finally {
+ j2dContext.release();
+ }
+ }
+ });
+ }
+
+ @Override
+ public final void doPlainPaint() {
+ helper.invokeGL(joglDrawable, joglContext, updaterPlainDisplayAction, updaterInitAction);
+ }
+
+ private final void captureJ2DState(final GL gl, final Graphics g) {
+ gl.glGetIntegerv(GL2GL3.GL_DRAW_BUFFER, drawBuffer, 0);
+ gl.glGetIntegerv(GL2ES3.GL_READ_BUFFER, readBuffer, 0);
+ if (Java2D.isFBOEnabled() &&
+ Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
+ gl.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, frameBuffer, 0);
+ if(!gl.glIsFramebuffer(frameBuffer[0])) {
+ checkedForFBObjectWorkarounds=true;
+ fbObjectWorkarounds = true;
+ createNewDepthBuffer = true;
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: Fetched ERR GL_FRAMEBUFFER_BINDING: "+frameBuffer[0]+" - NOT A FBO"+
+ ", frame_buffer_object workarounds to be necessary");
+ }
+ } else if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: Fetched OK GL_FRAMEBUFFER_BINDING: "+frameBuffer[0]);
+ }
+
+ if(fbObjectWorkarounds || !checkedForFBObjectWorkarounds) {
+ // See above for description of what we are doing here
+ if (frameBufferTexture == null)
+ frameBufferTexture = new int[1];
+
+ // Query the framebuffer for its color buffer so we can hook
+ // it back up in our context (should not be necessary)
+ gl.glGetFramebufferAttachmentParameteriv(GL.GL_FRAMEBUFFER,
+ GL.GL_COLOR_ATTACHMENT0,
+ GL.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
+ frameBufferTexture, 0);
+ if (DEBUG) {
+ System.err.println(getThreadName()+": GLJPanel: FBO COLOR_ATTACHMENT0: " + frameBufferTexture[0]);
+ }
+ }
+
+ if (!checkedGLVendor) {
+ checkedGLVendor = true;
+ final String vendor = gl.glGetString(GL.GL_VENDOR);
+
+ if ((vendor != null) &&
+ vendor.startsWith("ATI")) {
+ vendorIsATI = true;
+ }
+ }
+
+ if (vendorIsATI) {
+ // Unbind the FBO from Java2D's context as it appears that
+ // driver bugs on ATI's side are causing problems if the FBO is
+ // simultaneously bound to more than one context. Java2D will
+ // re-bind the FBO during the next validation of its context.
+ // Note: this breaks rendering at least on NVidia hardware
+ gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
+ }
+ }
+ }
+ }
+}