Jogamp
2f3cead4fd38b8fa6dfb29836604cc2c80601019
[jogl.git] / src / jogl / classes / javax / media / opengl / awt / GLJPanel.java
1 /*
2  * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
3  * Copyright (c) 2010 JogAmp Community. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * - Redistribution of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * - Redistribution in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of Sun Microsystems, Inc. or the names of
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * This software is provided "AS IS," without a warranty of any kind. ALL
21  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
22  * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
23  * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
24  * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
25  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
26  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
27  * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
28  * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
29  * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
30  * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
31  * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
32  *
33  * You acknowledge that this software is not designed or intended for use
34  * in the design, construction, operation or maintenance of any nuclear
35  * facility.
36  *
37  * Sun gratefully acknowledges that this software was originally authored
38  * and developed by Kenneth Bradley Russell and Christopher John Kline.
39  */
40
41 package javax.media.opengl.awt;
42
43 import java.awt.Color;
44 import java.awt.EventQueue;
45 import java.awt.FontMetrics;
46 import java.awt.Graphics;
47 import java.awt.Graphics2D;
48 import java.awt.GraphicsConfiguration;
49 import java.awt.GraphicsEnvironment;
50 import java.awt.Rectangle;
51 import java.awt.event.HierarchyEvent;
52 import java.awt.event.HierarchyListener;
53 import java.awt.geom.NoninvertibleTransformException;
54 import java.awt.geom.Rectangle2D;
55 import java.awt.image.BufferedImage;
56 import java.awt.image.DataBufferInt;
57 import java.beans.Beans;
58 import java.nio.IntBuffer;
59 import java.util.List;
60
61 import javax.media.nativewindow.AbstractGraphicsDevice;
62 import javax.media.nativewindow.NativeSurface;
63 import javax.media.nativewindow.ScalableSurface;
64 import javax.media.nativewindow.SurfaceUpdatedListener;
65 import javax.media.nativewindow.WindowClosingProtocol;
66 import javax.media.opengl.GL;
67 import javax.media.opengl.GL2;
68 import javax.media.opengl.GL2ES3;
69 import javax.media.opengl.GL2GL3;
70 import javax.media.opengl.GLAnimatorControl;
71 import javax.media.opengl.GLAutoDrawable;
72 import javax.media.opengl.GLCapabilities;
73 import javax.media.opengl.GLCapabilitiesChooser;
74 import javax.media.opengl.GLCapabilitiesImmutable;
75 import javax.media.opengl.GLContext;
76 import javax.media.opengl.GLDrawable;
77 import javax.media.opengl.GLDrawableFactory;
78 import javax.media.opengl.GLEventListener;
79 import javax.media.opengl.GLException;
80 import javax.media.opengl.GLFBODrawable;
81 import javax.media.opengl.GLProfile;
82 import javax.media.opengl.GLRunnable;
83 import javax.media.opengl.GLSharedContextSetter;
84 import javax.media.opengl.Threading;
85 import javax.swing.JPanel;
86
87 import jogamp.nativewindow.SurfaceScaleUtils;
88 import jogamp.nativewindow.WrappedSurface;
89 import jogamp.nativewindow.jawt.JAWTUtil;
90 import jogamp.opengl.Debug;
91 import jogamp.opengl.GLContextImpl;
92 import jogamp.opengl.GLDrawableFactoryImpl;
93 import jogamp.opengl.GLDrawableHelper;
94 import jogamp.opengl.GLDrawableImpl;
95 import jogamp.opengl.awt.AWTTilePainter;
96 import jogamp.opengl.awt.Java2D;
97 import jogamp.opengl.util.glsl.GLSLTextureRaster;
98
99 import com.jogamp.common.util.awt.AWTEDTExecutor;
100 import com.jogamp.nativewindow.awt.AWTPrintLifecycle;
101 import com.jogamp.nativewindow.awt.AWTWindowClosingProtocol;
102 import com.jogamp.opengl.FBObject;
103 import com.jogamp.opengl.GLRendererQuirks;
104 import com.jogamp.opengl.util.GLPixelBuffer.GLPixelAttributes;
105 import com.jogamp.opengl.util.GLPixelBuffer.SingletonGLPixelBufferProvider;
106 import com.jogamp.opengl.util.GLDrawableUtil;
107 import com.jogamp.opengl.util.GLPixelStorageModes;
108 import com.jogamp.opengl.util.TileRenderer;
109 import com.jogamp.opengl.util.awt.AWTGLPixelBuffer;
110 import com.jogamp.opengl.util.awt.AWTGLPixelBuffer.AWTGLPixelBufferProvider;
111 import com.jogamp.opengl.util.awt.AWTGLPixelBuffer.SingleAWTGLPixelBufferProvider;
112 import com.jogamp.opengl.util.texture.TextureState;
113
114 /** A lightweight Swing component which provides OpenGL rendering
115     support. Provided for compatibility with Swing user interfaces
116     when adding a heavyweight doesn't work either because of
117     Z-ordering or LayoutManager problems.
118     <p>
119     The GLJPanel can be made transparent by creating it with a
120     GLCapabilities object with alpha bits specified and calling {@link
121     #setOpaque}(false). Pixels with resulting OpenGL alpha values less
122     than 1.0 will be overlaid on any underlying Swing rendering.
123     </p>
124     <p>
125     This component attempts to use hardware-accelerated rendering via FBO or pbuffers and
126     falls back on to software rendering if none of the former are available
127     using {@link GLDrawableFactory#createOffscreenDrawable(AbstractGraphicsDevice, GLCapabilitiesImmutable, GLCapabilitiesChooser, int, int) GLDrawableFactory.createOffscreenDrawable(..)}.<br/>
128     </p>
129     <p>
130     <a name="verticalFlip">
131     In case</a> the drawable {@link #isGLOriented()} and {@link #setSkipGLOrientationVerticalFlip(boolean) vertical flip is not skipped},
132     this component performs the required vertical flip to bring the content from OpenGL's orientation into AWT's orientation.
133     See details about <a href="#fboGLSLVerticalFlip">FBO and GLSL vertical flipping</a>.
134     </p>
135     <p>
136     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(..)}
137     for later Java2D composition.
138     </p>
139     <p>
140     In case {@link #setSkipGLOrientationVerticalFlip(boolean) vertical-flip is not skipped} and <a href="#fboGLSLVerticalFlip">GLSL based vertical-flip</a> is not performed,
141     {@link System#arraycopy(Object, int, Object, int, int) System.arraycopy(..)} is used line by line.
142     This step causes more CPU load per frame and is not hardware-accelerated.
143     </p>
144     <p>
145     Finally the Java2D compositioning takes place via via {@link Graphics#drawImage(java.awt.Image, int, int, int, int, java.awt.image.ImageObserver) Graphics.drawImage(...)}
146     on the prepared {@link BufferedImage} as described above.
147     </p>
148     <P>
149  *  Please read <a href="GLCanvas.html#java2dgl">Java2D OpenGL Remarks</a>.
150  *  </P>
151  *
152     <a name="fboGLSLVerticalFlip"><h5>FBO / GLSL Vertical Flip</h5></a>
153     In case FBO is used and GLSL is available and {@link #setSkipGLOrientationVerticalFlip(boolean) vertical flip is not skipped}, a fragment shader is utilized
154     to flip the FBO texture vertically. This hardware-accelerated step can be disabled via system property <code>jogl.gljpanel.noglsl</code>.
155     <p>
156     The FBO / GLSL code path uses one texture-unit and binds the FBO texture to it's active texture-target,
157     see {@link #setTextureUnit(int)} and {@link #getTextureUnit()}.
158     </p>
159     <p>
160     The active and dedicated texture-unit's {@link GL#GL_TEXTURE_2D} state is preserved via {@link TextureState}.
161     See also {@link Texture#textureCallOrder Order of Texture Commands}.
162     </p>
163     <p>
164     The current gl-viewport is preserved.
165     </p>
166     <p>
167     <i>Warning (Bug 842)</i>: Certain GL states other than viewport and texture (see above)
168     influencing rendering, will also influence the GLSL vertical flip, e.g. {@link GL#glFrontFace(int) glFrontFace}({@link GL#GL_CCW}).
169     It is recommended to reset those states to default when leaving the {@link GLEventListener#display(GLAutoDrawable)} method!
170     We may change this behavior in the future, i.e. preserve all influencing states.
171     </p>
172 */
173
174 @SuppressWarnings("serial")
175 public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosingProtocol, AWTPrintLifecycle, GLSharedContextSetter, ScalableSurface {
176   private static final boolean DEBUG;
177   private static final boolean DEBUG_VIEWPORT;
178   private static final boolean USE_GLSL_TEXTURE_RASTERIZER;
179   private static final boolean SKIP_VERTICAL_FLIP_DEFAULT;
180
181   /** Indicates whether the Java 2D OpenGL pipeline is requested by user. */
182   private static final boolean java2dOGLEnabledByProp;
183
184   /** Indicates whether the Java 2D OpenGL pipeline is enabled, resource-compatible and requested by user. */
185   private static final boolean useJava2DGLPipeline;
186
187   /** Indicates whether the Java 2D OpenGL pipeline's usage is error free. */
188   private static boolean java2DGLPipelineOK;
189
190   static {
191       Debug.initSingleton();
192       DEBUG = Debug.debug("GLJPanel");
193       DEBUG_VIEWPORT = Debug.isPropertyDefined("jogl.debug.GLJPanel.Viewport", true);
194       USE_GLSL_TEXTURE_RASTERIZER = !Debug.isPropertyDefined("jogl.gljpanel.noglsl", true);
195       SKIP_VERTICAL_FLIP_DEFAULT = Debug.isPropertyDefined("jogl.gljpanel.noverticalflip", true);
196       boolean enabled = Debug.getBooleanProperty("sun.java2d.opengl", false);
197       java2dOGLEnabledByProp = enabled && !Debug.isPropertyDefined("jogl.gljpanel.noogl", true);
198
199       enabled = false;
200       if( java2dOGLEnabledByProp ) {
201           // Force eager initialization of part of the Java2D class since
202           // otherwise it's likely it will try to be initialized while on
203           // the Queue Flusher Thread, which is not allowed
204           if (Java2D.isOGLPipelineResourceCompatible() && Java2D.isFBOEnabled()) {
205               if( null != Java2D.getShareContext(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()) ) {
206                   enabled = true;
207               }
208           }
209       }
210       useJava2DGLPipeline = enabled;
211       java2DGLPipelineOK = enabled;
212       if( DEBUG ) {
213           System.err.println("GLJPanel: DEBUG_VIEWPORT "+DEBUG_VIEWPORT);
214           System.err.println("GLJPanel: USE_GLSL_TEXTURE_RASTERIZER "+USE_GLSL_TEXTURE_RASTERIZER);
215           System.err.println("GLJPanel: SKIP_VERTICAL_FLIP_DEFAULT "+SKIP_VERTICAL_FLIP_DEFAULT);
216           System.err.println("GLJPanel: java2dOGLEnabledByProp "+java2dOGLEnabledByProp);
217           System.err.println("GLJPanel: useJava2DGLPipeline "+useJava2DGLPipeline);
218           System.err.println("GLJPanel: java2DGLPipelineOK "+java2DGLPipelineOK);
219       }
220   }
221
222   private static SingleAWTGLPixelBufferProvider singleAWTGLPixelBufferProvider = null;
223   private static synchronized SingleAWTGLPixelBufferProvider getSingleAWTGLPixelBufferProvider() {
224       if( null == singleAWTGLPixelBufferProvider ) {
225           singleAWTGLPixelBufferProvider = new SingleAWTGLPixelBufferProvider( true /* allowRowStride */ );
226       }
227       return singleAWTGLPixelBufferProvider;
228   }
229
230   private final GLDrawableHelper helper;
231   private boolean autoSwapBufferMode;
232
233   private volatile boolean isInitialized;
234
235   //
236   // Data used for either pbuffers or pixmap-based offscreen surfaces
237   //
238   private AWTGLPixelBufferProvider customPixelBufferProvider = null;
239   /** Single buffered offscreen caps */
240   private GLCapabilitiesImmutable offscreenCaps;
241   private final GLProfile             glProfile;
242   private final GLDrawableFactoryImpl factory;
243   private final GLCapabilitiesChooser chooser;
244   private int additionalCtxCreationFlags = 0;
245
246   // Lazy reshape notification: reshapeWidth -> panelWidth -> backend.width
247   private boolean handleReshape = false;
248   private boolean sendReshape = true;
249
250   private volatile int[] hasPixelScale = new int[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
251   private volatile int[] reqPixelScale = new int[] { ScalableSurface.AUTOMAX_PIXELSCALE, ScalableSurface.AUTOMAX_PIXELSCALE };
252
253   // For handling reshape events lazily: reshapeWidth -> panelWidth -> backend.width
254   private int reshapeWidth;
255   private int reshapeHeight;
256
257   // Width of the actual GLJPanel: reshapeWidth -> panelWidth -> backend.width
258   private int panelWidth   = 0;
259   private int panelHeight  = 0;
260
261   // These are always set to (0, 0) except when the Java2D / OpenGL
262   // pipeline is active
263   private int viewportX;
264   private int viewportY;
265
266   private int requestedTextureUnit = 0; // default
267
268   // The backend in use
269   private volatile Backend backend;
270
271   private boolean skipGLOrientationVerticalFlip = SKIP_VERTICAL_FLIP_DEFAULT;
272
273   // Used by all backends either directly or indirectly to hook up callbacks
274   private final Updater updater = new Updater();
275
276   private boolean oglPipelineUsable() {
277       return null == customPixelBufferProvider &&  useJava2DGLPipeline && java2DGLPipelineOK;
278   }
279
280   private volatile boolean isShowing;
281   private final HierarchyListener hierarchyListener = new HierarchyListener() {
282       @Override
283       public void hierarchyChanged(HierarchyEvent e) {
284           isShowing = GLJPanel.this.isShowing();
285       }
286   };
287
288   private final AWTWindowClosingProtocol awtWindowClosingProtocol =
289           new AWTWindowClosingProtocol(this, new Runnable() {
290                 @Override
291                 public void run() {
292                     GLJPanel.this.destroy();
293                 }
294             }, null);
295
296   /** Creates a new GLJPanel component with a default set of OpenGL
297       capabilities and using the default OpenGL capabilities selection
298       mechanism.
299    * @throws GLException if no default profile is available for the default desktop device.
300    */
301   public GLJPanel() throws GLException {
302     this(null);
303   }
304
305   /** Creates a new GLJPanel component with the requested set of
306       OpenGL capabilities, using the default OpenGL capabilities
307       selection mechanism.
308    * @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
309    */
310   public GLJPanel(GLCapabilitiesImmutable userCapsRequest) throws GLException {
311     this(userCapsRequest, null, null);
312   }
313
314   /** Creates a new GLJPanel component. The passed GLCapabilities
315       specifies the OpenGL capabilities for the component; if null, a
316       default set of capabilities is used. The GLCapabilitiesChooser
317       specifies the algorithm for selecting one of the available
318       GLCapabilities for the component; a DefaultGLCapabilitesChooser
319       is used if null is passed for this argument.
320     * @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
321   */
322   public GLJPanel(GLCapabilitiesImmutable userCapsRequest, GLCapabilitiesChooser chooser)
323           throws GLException
324   {
325       this(userCapsRequest, chooser, null);
326   }
327
328   /** Creates a new GLJPanel component. The passed GLCapabilities
329       specifies the OpenGL capabilities for the component; if null, a
330       default set of capabilities is used. The GLCapabilitiesChooser
331       specifies the algorithm for selecting one of the available
332       GLCapabilities for the component; a DefaultGLCapabilitesChooser
333       is used if null is passed for this argument. The passed
334       GLContext specifies an OpenGL context with which to share
335       textures, display lists and other OpenGL state, and may be null
336       if sharing is not desired. See the note in the overview documentation on
337       <a href="../../../spec-overview.html#SHARING">context sharing</a>.
338       <P>
339       Note: Sharing cannot be enabled using J2D OpenGL FBO sharing,
340       since J2D GL Context must be shared and we can only share one context.
341     * @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
342     * @deprecated Use {@link #GLJPanel(GLCapabilitiesImmutable, GLCapabilitiesChooser)}
343     *             and set shared GLContext via {@link #setSharedContext(GLContext)} or {@link #setSharedAutoDrawable(GLAutoDrawable)}.
344     */
345   public GLJPanel(GLCapabilitiesImmutable userCapsRequest, GLCapabilitiesChooser chooser, GLContext shareWith)
346           throws GLException
347   {
348     super();
349
350     // Works around problems on many vendors' cards; we don't need a
351     // back buffer for the offscreen surface anyway
352     {
353         GLCapabilities caps;
354         if (userCapsRequest != null) {
355             caps = (GLCapabilities) userCapsRequest.cloneMutable();
356         } else {
357             caps = new GLCapabilities(GLProfile.getDefault(GLProfile.getDefaultDevice()));
358         }
359         caps.setDoubleBuffered(false);
360         offscreenCaps = caps;
361     }
362     this.glProfile = offscreenCaps.getGLProfile();
363     this.factory = GLDrawableFactoryImpl.getFactoryImpl(glProfile);
364     this.chooser = chooser;
365
366     helper = new GLDrawableHelper();
367     if( null != shareWith ) {
368         helper.setSharedContext(null, shareWith);
369     }
370     autoSwapBufferMode = helper.getAutoSwapBufferMode();
371
372     this.setFocusable(true); // allow keyboard input!
373     this.addHierarchyListener(hierarchyListener);
374     this.isShowing = isShowing();
375   }
376
377   /**
378    * Attempts to initialize the backend, if not initialized yet.
379    * <p>
380    * If backend is already initialized method returns <code>true</code>.
381    * </p>
382    * <p>
383    * If <code>offthread</code> is <code>true</code>, initialization will kicked off
384    * on a <i>short lived</i> arbitrary thread and method returns immediately.<br/>
385    * If platform supports such <i>arbitrary thread</i> initialization method returns
386    * <code>true</code>, otherwise <code>false</code>.
387    * </p>
388    * <p>
389    * If <code>offthread</code> is <code>false</code>, initialization be performed
390    * on the current thread and method returns after initialization.<br/>
391    * Method returns <code>true</code> if initialization was successful, otherwise <code>false</code>.
392    * <p>
393    * @param offthread
394    */
395   public final boolean initializeBackend(boolean offthread) {
396     if( offthread ) {
397         new Thread(getThreadName()+"-GLJPanel_Init") {
398             public void run() {
399               if( !isInitialized ) {
400                   initializeBackendImpl();
401               }
402             } }.start();
403         return true;
404     } else {
405         if( !isInitialized ) {
406             return initializeBackendImpl();
407         } else {
408             return true;
409         }
410     }
411   }
412
413   @Override
414   public final void setSharedContext(GLContext sharedContext) throws IllegalStateException {
415       helper.setSharedContext(this.getContext(), sharedContext);
416   }
417
418   @Override
419   public final void setSharedAutoDrawable(GLAutoDrawable sharedAutoDrawable) throws IllegalStateException {
420       helper.setSharedAutoDrawable(this, sharedAutoDrawable);
421   }
422
423   public AWTGLPixelBufferProvider getCustomPixelBufferProvider() { return customPixelBufferProvider; }
424
425   /**
426    * @param custom custom {@link AWTGLPixelBufferProvider}
427    * @throws IllegalArgumentException if <code>custom</code> is <code>null</code>
428    * @throws IllegalStateException if backend is already realized, i.e. this instanced already painted once.
429    */
430   public void setPixelBufferProvider(AWTGLPixelBufferProvider custom) throws IllegalArgumentException, IllegalStateException {
431       if( null == custom ) {
432           throw new IllegalArgumentException("Null PixelBufferProvider");
433       }
434       if( null != backend ) {
435           throw new IllegalStateException("Backend already realized.");
436       }
437       customPixelBufferProvider = custom;
438   }
439
440   @Override
441   public final Object getUpstreamWidget() {
442     return this;
443   }
444
445   @Override
446   public void display() {
447     if( isShowing || ( printActive && isVisible() ) ) {
448         if (EventQueue.isDispatchThread()) {
449           // Want display() to be synchronous, so call paintImmediately()
450           paintImmediately(0, 0, getWidth(), getHeight());
451         } else {
452           // Multithreaded redrawing of Swing components is not allowed,
453           // so do everything on the event dispatch thread
454           try {
455             EventQueue.invokeAndWait(paintImmediatelyAction);
456           } catch (Exception e) {
457             throw new GLException(e);
458           }
459         }
460     }
461   }
462
463   protected void dispose() {
464     if(DEBUG) {
465         System.err.println(getThreadName()+": GLJPanel.dispose() - start");
466         // Thread.dumpStack();
467     }
468
469     if (backend != null && backend.getContext() != null) {
470       boolean animatorPaused = false;
471       GLAnimatorControl animator =  getAnimator();
472       if(null!=animator) {
473         animatorPaused = animator.pause();
474       }
475
476       if(backend.getContext().isCreated()) {
477           Threading.invoke(true, disposeAction, getTreeLock());
478       }
479       if(null != backend) {
480           // not yet destroyed due to backend.isUsingOwnThreadManagment() == true
481           backend.destroy();
482           isInitialized = false;
483       }
484
485       if(animatorPaused) {
486         animator.resume();
487       }
488     }
489     hasPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
490     hasPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
491
492     if(DEBUG) {
493         System.err.println(getThreadName()+": GLJPanel.dispose() - stop");
494     }
495   }
496
497   /**
498    * Just an alias for removeNotify
499    */
500   @Override
501   public void destroy() {
502       removeNotify();
503   }
504
505   /** Overridden to cause OpenGL rendering to be performed during
506       repaint cycles. Subclasses which override this method must call
507       super.paintComponent() in their paintComponent() method in order
508       to function properly. <P>
509
510       <DL><DD><CODE>paintComponent</CODE> in class <CODE>javax.swing.JComponent</CODE></DD></DL> */
511   @Override
512   protected void paintComponent(final Graphics g) {
513     if (Beans.isDesignTime()) {
514       // Make GLJPanel behave better in NetBeans GUI builder
515       g.setColor(Color.BLACK);
516       g.fillRect(0, 0, getWidth(), getHeight());
517       FontMetrics fm = g.getFontMetrics();
518       String name = getName();
519       if (name == null) {
520         name = getClass().getName();
521         int idx = name.lastIndexOf('.');
522         if (idx >= 0) {
523           name = name.substring(idx + 1);
524         }
525       }
526       Rectangle2D bounds = fm.getStringBounds(name, g);
527       g.setColor(Color.WHITE);
528       g.drawString(name,
529                    (int) ((getWidth()  - bounds.getWidth())  / 2),
530                    (int) ((getHeight() + bounds.getHeight()) / 2));
531       return;
532     }
533
534     if( !isInitialized ) {
535         initializeBackendImpl();
536     }
537
538     if (!isInitialized || printActive) {
539       return;
540     }
541
542     // NOTE: must do this when the context is not current as it may
543     // involve destroying the pbuffer (current context) and
544     // re-creating it -- tricky to do properly while the context is
545     // current
546     if( !printActive ) {
547         if (handleReshape) {
548           handleReshape = false;
549           sendReshape = handleReshape();
550         }
551
552         if( isShowing ) {
553           updater.setGraphics(g);
554           backend.doPaintComponent(g);
555         }
556     }
557   }
558
559   private final void updateWrappedSurfaceScale(final GLDrawable d) {
560       final NativeSurface s = d.getNativeSurface();
561       if( s instanceof WrappedSurface ) {
562           ((WrappedSurface)s).setSurfaceScale(null, hasPixelScale);
563       }
564   }
565
566   @Override
567   public final int[] setSurfaceScale(final int[] result, final int[] pixelScale) { // HiDPI support
568       SurfaceScaleUtils.validateReqPixelScale(reqPixelScale, pixelScale, DEBUG ? getClass().getSimpleName() : null);
569       if( null != result ) {
570           System.arraycopy(reqPixelScale, 0, result, 0, 2);
571       }
572       final Backend b = backend;
573       if ( isInitialized && null != b ) {
574           final int[] pixelScaleInt;
575           {
576               final int ps = JAWTUtil.getPixelScale(getGraphicsConfiguration());
577               pixelScaleInt = new int[] { ps, ps };
578           }
579           final int hadPixelScaleX = hasPixelScale[0];
580           final int hadPixelScaleY = hasPixelScale[1];
581           SurfaceScaleUtils.computePixelScale(hasPixelScale, hasPixelScale, reqPixelScale, pixelScaleInt, DEBUG ? getClass().getSimpleName() : null);
582           if( hadPixelScaleX != hasPixelScale[0] || hadPixelScaleY != hasPixelScale[1] ) {
583               updateWrappedSurfaceScale(b.getDrawable());
584               reshapeImpl(getWidth(), getHeight());
585               display();
586           }
587       }
588       return result;
589   }
590
591   @Override
592   public final int[] getSurfaceScale(final int[] result) {
593       System.arraycopy(hasPixelScale, 0, result, 0, 2);
594       return result;
595   }
596
597   /** Overridden to track when this component is added to a container.
598       Subclasses which override this method must call
599       super.addNotify() in their addNotify() method in order to
600       function properly. <P>
601
602       <DL><DD><CODE>addNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
603   @Override
604   public void addNotify() {
605     super.addNotify();
606     awtWindowClosingProtocol.addClosingListener();
607
608     // HiDPI support
609     final int[] pixelScaleInt;
610     {
611         final int ps = JAWTUtil.getPixelScale(getGraphicsConfiguration());
612         pixelScaleInt = new int[] { ps, ps };
613     }
614     SurfaceScaleUtils.computePixelScale(hasPixelScale, hasPixelScale, reqPixelScale, pixelScaleInt, DEBUG ? getClass().getSimpleName() : null);
615
616     if (DEBUG) {
617         System.err.println(getThreadName()+": GLJPanel.addNotify()");
618     }
619   }
620
621   /** Overridden to track when this component is removed from a
622       container. Subclasses which override this method must call
623       super.removeNotify() in their removeNotify() method in order to
624       function properly. <P>
625
626       <DL><DD><CODE>removeNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
627   @Override
628   public void removeNotify() {
629     awtWindowClosingProtocol.removeClosingListener();
630
631     dispose();
632     super.removeNotify();
633   }
634
635   /** Overridden to cause {@link GLDrawableHelper#reshape} to be
636       called on all registered {@link GLEventListener}s. Subclasses
637       which override this method must call super.reshape() in
638       their reshape() method in order to function properly. <P>
639    *
640    * {@inheritDoc}
641    */
642   @SuppressWarnings("deprecation")
643   @Override
644   public void reshape(int x, int y, int width, int height) {
645     super.reshape(x, y, width, height);
646     reshapeImpl(width, height);
647   }
648
649   private void reshapeImpl(final int width, final int height) {
650     final int scaledWidth = width * hasPixelScale[0];
651     final int scaledHeight = height * hasPixelScale[1];
652     if( DEBUG ) {
653         System.err.println(getThreadName()+": GLJPanel.reshape.0 "+this.getName()+" resize"+(printActive?"WithinPrint":"")+
654                 " [ this "+getWidth()+"x"+getHeight()+", pixelScale "+getPixelScaleStr()+
655                 ", panel "+panelWidth+"x"+panelHeight +
656                 ", reshape: " +reshapeWidth+"x"+reshapeHeight +
657                 "] -> "+(printActive?"[skipped] ":"") + width+"x"+height+" * "+getPixelScaleStr()+" -> "+scaledWidth+"x"+scaledHeight);
658     }
659     if( !printActive ) {
660         reshapeWidth = scaledWidth;
661         reshapeHeight = scaledHeight;
662         handleReshape = true;
663     }
664   }
665
666   private volatile boolean printActive = false;
667   private GLAnimatorControl printAnimator = null;
668   private GLAutoDrawable printGLAD = null;
669   private AWTTilePainter printAWTTiles = null;
670
671   @Override
672   public void setupPrint(double scaleMatX, double scaleMatY, int numSamples, int tileWidth, int tileHeight) {
673       printActive = true;
674       final int componentCount = isOpaque() ? 3 : 4;
675       final TileRenderer printRenderer = new TileRenderer();
676       printAWTTiles = new AWTTilePainter(printRenderer, componentCount, scaleMatX, scaleMatY, numSamples, tileWidth, tileHeight, DEBUG);
677       AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, setupPrintOnEDT);
678   }
679   private final Runnable setupPrintOnEDT = new Runnable() {
680       @Override
681       public void run() {
682           if( !isInitialized ) {
683               initializeBackendImpl();
684           }
685           if (!isInitialized) {
686               if(DEBUG) {
687                   System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, drawable not valid yet");
688               }
689               printActive = false;
690               return; // not yet available ..
691           }
692           if( !isVisible() ) {
693               if(DEBUG) {
694                   System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, panel not visible");
695               }
696               printActive = false;
697               return; // not yet available ..
698           }
699           sendReshape = false; // clear reshape flag
700           handleReshape = false; // ditto
701           printAnimator =  helper.getAnimator();
702           if( null != printAnimator ) {
703               printAnimator.remove(GLJPanel.this);
704           }
705
706           printGLAD = GLJPanel.this; // default: re-use
707           final GLCapabilities caps = (GLCapabilities)getChosenGLCapabilities().cloneMutable();
708           final int printNumSamples = printAWTTiles.getNumSamples(caps);
709           GLDrawable printDrawable = printGLAD.getDelegatedDrawable();
710           final boolean reqNewGLADSamples = printNumSamples != caps.getNumSamples();
711           final boolean reqNewGLADSize = printAWTTiles.customTileWidth != -1 && printAWTTiles.customTileWidth != printDrawable.getSurfaceWidth() ||
712                                          printAWTTiles.customTileHeight != -1 && printAWTTiles.customTileHeight != printDrawable.getSurfaceHeight();
713           final boolean reqNewGLAD = reqNewGLADSamples || reqNewGLADSize ;
714           if( DEBUG ) {
715               System.err.println("AWT print.setup: reqNewGLAD "+reqNewGLAD+"[ samples "+reqNewGLADSamples+", size "+reqNewGLADSize+"], "+
716                                  ", drawableSize "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+
717                                  ", customTileSize "+printAWTTiles.customTileWidth+"x"+printAWTTiles.customTileHeight+
718                                  ", scaleMat "+printAWTTiles.scaleMatX+" x "+printAWTTiles.scaleMatY+
719                                  ", numSamples "+printAWTTiles.customNumSamples+" -> "+printNumSamples+", printAnimator "+printAnimator);
720           }
721           if( reqNewGLAD ) {
722               caps.setDoubleBuffered(false);
723               caps.setOnscreen(false);
724               caps.setSampleBuffers(0 < printNumSamples);
725               caps.setNumSamples(printNumSamples);
726               final GLDrawableFactory factory = GLDrawableFactory.getFactory(caps.getGLProfile());
727               printGLAD = factory.createOffscreenAutoDrawable(null, caps, null,
728                       printAWTTiles.customTileWidth != -1 ? printAWTTiles.customTileWidth : DEFAULT_PRINT_TILE_SIZE,
729                       printAWTTiles.customTileHeight != -1 ? printAWTTiles.customTileHeight : DEFAULT_PRINT_TILE_SIZE);
730               GLDrawableUtil.swapGLContextAndAllGLEventListener(GLJPanel.this, printGLAD);
731               printDrawable = printGLAD.getDelegatedDrawable();
732           }
733           printAWTTiles.setGLOrientation( !GLJPanel.this.skipGLOrientationVerticalFlip && printGLAD.isGLOriented(), printGLAD.isGLOriented() );
734           printAWTTiles.renderer.setTileSize(printDrawable.getSurfaceWidth(), printDrawable.getSurfaceHeight(), 0);
735           printAWTTiles.renderer.attachAutoDrawable(printGLAD);
736           if( DEBUG ) {
737               System.err.println("AWT print.setup "+printAWTTiles);
738               System.err.println("AWT print.setup AA "+printNumSamples+", "+caps);
739               System.err.println("AWT print.setup printGLAD: "+printGLAD.getSurfaceWidth()+"x"+printGLAD.getSurfaceHeight()+", "+printGLAD);
740               System.err.println("AWT print.setup printDraw: "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+", "+printDrawable);
741           }
742       }
743   };
744
745   @Override
746   public void releasePrint() {
747       if( !printActive ) {
748           throw new IllegalStateException("setupPrint() not called");
749       }
750       sendReshape = false; // clear reshape flag
751       handleReshape = false; // ditto
752       AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, releasePrintOnEDT);
753   }
754
755   private final Runnable releasePrintOnEDT = new Runnable() {
756       @Override
757       public void run() {
758           if( DEBUG ) {
759               System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0 "+printAWTTiles);
760           }
761           printAWTTiles.dispose();
762           printAWTTiles= null;
763           if( printGLAD != GLJPanel.this ) {
764               GLDrawableUtil.swapGLContextAndAllGLEventListener(printGLAD, GLJPanel.this);
765               printGLAD.destroy();
766           }
767           printGLAD = null;
768           if( null != printAnimator ) {
769               printAnimator.add(GLJPanel.this);
770               printAnimator = null;
771           }
772
773           // trigger reshape, i.e. gl-viewport and -listener - this component might got resized!
774           final int awtWidth = GLJPanel.this.getWidth();
775           final int awtHeight= GLJPanel.this.getHeight();
776           final int scaledAWTWidth = awtWidth * hasPixelScale[0];
777           final int scaledAWTHeight= awtHeight * hasPixelScale[1];
778           final GLDrawable drawable = GLJPanel.this.getDelegatedDrawable();
779           if( scaledAWTWidth != panelWidth || scaledAWTHeight != panelHeight ||
780               drawable.getSurfaceWidth() != panelWidth || drawable.getSurfaceHeight() != panelHeight ) {
781               // -> !( awtSize == panelSize == drawableSize )
782               if ( DEBUG ) {
783                   System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0: resizeWithinPrint panel " +panelWidth+"x"+panelHeight + " @ scale "+getPixelScaleStr()+
784                           ", draw "+drawable.getSurfaceWidth()+"x"+drawable.getSurfaceHeight()+
785                           " -> " + awtWidth+"x"+awtHeight+" * "+getPixelScaleStr()+" -> "+scaledAWTWidth+"x"+scaledAWTHeight);
786               }
787               reshapeWidth = scaledAWTWidth;
788               reshapeHeight = scaledAWTHeight;
789               sendReshape = handleReshape(); // reshapeSize -> panelSize, backend reshape w/ GL reshape
790           } else {
791               sendReshape = true; // only GL reshape
792           }
793           printActive = false;
794           display();
795       }
796   };
797
798   @Override
799   public void print(Graphics graphics) {
800       if( !printActive ) {
801           throw new IllegalStateException("setupPrint() not called");
802       }
803       if(DEBUG && !EventQueue.isDispatchThread()) {
804           System.err.println(getThreadName()+": Warning: GLCanvas print - not called from AWT-EDT");
805           // we cannot dispatch print on AWT-EDT due to printing internal locking ..
806       }
807       sendReshape = false; // clear reshape flag
808       handleReshape = false; // ditto
809
810       final Graphics2D g2d = (Graphics2D)graphics;
811       try {
812           printAWTTiles.setupGraphics2DAndClipBounds(g2d, getWidth(), getHeight());
813           final TileRenderer tileRenderer = printAWTTiles.renderer;
814           if( DEBUG ) {
815               System.err.println("AWT print.0: "+tileRenderer);
816           }
817           if( !tileRenderer.eot() ) {
818               try {
819                   do {
820                       if( printGLAD != GLJPanel.this ) {
821                           tileRenderer.display();
822                       } else {
823                           backend.doPlainPaint();
824                       }
825                   } while ( !tileRenderer.eot() );
826                   if( DEBUG ) {
827                       System.err.println("AWT print.1: "+printAWTTiles);
828                   }
829               } finally {
830                   tileRenderer.reset();
831                   printAWTTiles.resetGraphics2D();
832               }
833           }
834       } catch (NoninvertibleTransformException nte) {
835           System.err.println("Catched: Inversion failed of: "+g2d.getTransform());
836           nte.printStackTrace();
837       }
838       if( DEBUG ) {
839           System.err.println("AWT print.X: "+printAWTTiles);
840       }
841   }
842   @Override
843   protected void printComponent(Graphics g) {
844       if( DEBUG ) {
845           System.err.println("AWT printComponent.X: "+printAWTTiles);
846       }
847       print(g);
848   }
849
850   @Override
851   public void setOpaque(boolean opaque) {
852     if (backend != null) {
853       backend.setOpaque(opaque);
854     }
855     super.setOpaque(opaque);
856   }
857
858   @Override
859   public void addGLEventListener(GLEventListener listener) {
860     helper.addGLEventListener(listener);
861   }
862
863   @Override
864   public void addGLEventListener(int index, GLEventListener listener) {
865     helper.addGLEventListener(index, listener);
866   }
867
868   @Override
869   public int getGLEventListenerCount() {
870       return helper.getGLEventListenerCount();
871   }
872
873   @Override
874   public GLEventListener getGLEventListener(int index) throws IndexOutOfBoundsException {
875       return helper.getGLEventListener(index);
876   }
877
878   @Override
879   public boolean areAllGLEventListenerInitialized() {
880      return helper.areAllGLEventListenerInitialized();
881   }
882
883   @Override
884   public boolean getGLEventListenerInitState(GLEventListener listener) {
885       return helper.getGLEventListenerInitState(listener);
886   }
887
888   @Override
889   public void setGLEventListenerInitState(GLEventListener listener, boolean initialized) {
890       helper.setGLEventListenerInitState(listener, initialized);
891   }
892
893   @Override
894   public GLEventListener disposeGLEventListener(GLEventListener listener, boolean remove) {
895     final DisposeGLEventListenerAction r = new DisposeGLEventListenerAction(listener, remove);
896     if (EventQueue.isDispatchThread()) {
897       r.run();
898     } else {
899       // Multithreaded redrawing of Swing components is not allowed,
900       // so do everything on the event dispatch thread
901       try {
902         EventQueue.invokeAndWait(r);
903       } catch (Exception e) {
904         throw new GLException(e);
905       }
906     }
907     return r.listener;
908   }
909
910   @Override
911   public GLEventListener removeGLEventListener(GLEventListener listener) {
912     return helper.removeGLEventListener(listener);
913   }
914
915   @Override
916   public void setAnimator(GLAnimatorControl animatorControl) {
917     helper.setAnimator(animatorControl);
918   }
919
920   @Override
921   public GLAnimatorControl getAnimator() {
922     return helper.getAnimator();
923   }
924
925   @Override
926   public final Thread setExclusiveContextThread(Thread t) throws GLException {
927       return helper.setExclusiveContextThread(t, getContext());
928   }
929
930   @Override
931   public final Thread getExclusiveContextThread() {
932       return helper.getExclusiveContextThread();
933   }
934
935   @Override
936   public boolean invoke(boolean wait, GLRunnable glRunnable) {
937     return helper.invoke(this, wait, glRunnable);
938   }
939
940   @Override
941   public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) {
942     return helper.invoke(this, wait, glRunnables);
943   }
944
945   @Override
946   public GLContext createContext(GLContext shareWith) {
947     final Backend b = backend;
948     if ( null == b ) {
949         return null;
950     }
951     return b.createContext(shareWith);
952   }
953
954   @Override
955   public void setRealized(boolean realized) {
956   }
957
958   @Override
959   public boolean isRealized() {
960       return isInitialized;
961   }
962
963   @Override
964   public GLContext setContext(GLContext newCtx, boolean destroyPrevCtx) {
965     final Backend b = backend;
966     if ( null == b ) {
967         return null;
968     }
969     final GLContext oldCtx = b.getContext();
970     GLDrawableHelper.switchContext(b.getDrawable(), oldCtx, destroyPrevCtx, newCtx, additionalCtxCreationFlags);
971     b.setContext(newCtx);
972     return oldCtx;
973   }
974
975
976   @Override
977   public final GLDrawable getDelegatedDrawable() {
978     final Backend b = backend;
979     if ( null == b ) {
980         return null;
981     }
982     return b.getDrawable();
983   }
984
985   @Override
986   public GLContext getContext() {
987     final Backend b = backend;
988     if ( null == b ) {
989         return null;
990     }
991     return b.getContext();
992   }
993
994   @Override
995   public GL getGL() {
996     if (Beans.isDesignTime()) {
997       return null;
998     }
999     GLContext context = getContext();
1000     return (context == null) ? null : context.getGL();
1001   }
1002
1003   @Override
1004   public GL setGL(GL gl) {
1005     GLContext context = getContext();
1006     if (context != null) {
1007       context.setGL(gl);
1008       return gl;
1009     }
1010     return null;
1011   }
1012
1013   @Override
1014   public void setAutoSwapBufferMode(boolean enable) {
1015     this.autoSwapBufferMode = enable;
1016     boolean backendHandlesSwapBuffer = false;
1017     if( isInitialized ) {
1018         final Backend b = backend;
1019         if ( null != b ) {
1020             backendHandlesSwapBuffer= b.handlesSwapBuffer();
1021         }
1022     }
1023     if( !backendHandlesSwapBuffer ) {
1024         helper.setAutoSwapBufferMode(enable);
1025     }
1026   }
1027
1028   @Override
1029   public boolean getAutoSwapBufferMode() {
1030     return autoSwapBufferMode;
1031   }
1032
1033   @Override
1034   public void swapBuffers() {
1035     if( isInitialized ) {
1036         final Backend b = backend;
1037         if ( null != b ) {
1038             b.swapBuffers();
1039         }
1040     }
1041   }
1042
1043   @Override
1044   public void setContextCreationFlags(int flags) {
1045     additionalCtxCreationFlags = flags;
1046   }
1047
1048   @Override
1049   public int getContextCreationFlags() {
1050     return additionalCtxCreationFlags;
1051   }
1052
1053   /** For a translucent GLJPanel (one for which {@link #setOpaque
1054       setOpaque}(false) has been called), indicates whether the
1055       application should preserve the OpenGL color buffer
1056       (GL_COLOR_BUFFER_BIT) for correct rendering of the GLJPanel and
1057       underlying widgets which may show through portions of the
1058       GLJPanel with alpha values less than 1.  Most Swing
1059       implementations currently expect the GLJPanel to be completely
1060       cleared (e.g., by <code>glClear(GL_COLOR_BUFFER_BIT |
1061       GL_DEPTH_BUFFER_BIT)</code>), but for certain optimized Swing
1062       implementations which use OpenGL internally, it may be possible
1063       to perform OpenGL rendering using the GLJPanel into the same
1064       OpenGL drawable as the Swing implementation uses. */
1065   public boolean shouldPreserveColorBufferIfTranslucent() {
1066     return oglPipelineUsable();
1067   }
1068
1069   @Override
1070   public int getSurfaceWidth() {
1071       return panelWidth; // FIXME HiDPI: Accurate or: getWidth() * hasPixelScale[0];
1072   }
1073
1074   @Override
1075   public int getSurfaceHeight() {
1076       return panelHeight; // FIXME HiDPI: Accurate or: getHeight() * hasPixelScale[1];
1077   }
1078
1079   /**
1080    * {@inheritDoc}
1081    * <p>
1082    * Method returns a valid value only <i>after</i>
1083    * the backend has been initialized, either {@link #initializeBackend(boolean) eagerly}
1084    * or manually via the first display call.<br/>
1085    * Method always returns a valid value when called from within a {@link GLEventListener}.
1086    * </p>
1087    */
1088   @Override
1089   public boolean isGLOriented() {
1090     final Backend b = backend;
1091     if ( null == b ) {
1092         return true;
1093     }
1094     return b.getDrawable().isGLOriented();
1095   }
1096
1097   /**
1098    * Set skipping {@link #isGLOriented()} based vertical flip,
1099    * which usually is required by the offscreen backend,
1100    * see details about <a href="#verticalFlip">vertical flip</a>
1101    * and <a href="#fboGLSLVerticalFlip">FBO / GLSL vertical flip</a>.
1102    * <p>
1103    * If set to <code>true</code>, user needs to flip the OpenGL rendered scene
1104    * <i>if {@link #isGLOriented()} == true</i>, e.g. via the PMV matrix.<br/>
1105    * See constraints of {@link #isGLOriented()}.
1106    * </p>
1107    */
1108   public final void setSkipGLOrientationVerticalFlip(boolean v) {
1109       skipGLOrientationVerticalFlip = v;
1110   }
1111   /** See {@link #setSkipGLOrientationVerticalFlip(boolean)}. */
1112   public final boolean getSkipGLOrientationVerticalFlip() {
1113       return skipGLOrientationVerticalFlip;
1114   }
1115
1116   @Override
1117   public GLCapabilitiesImmutable getChosenGLCapabilities() {
1118     final Backend b = backend;
1119     if ( null == b ) {
1120         return null;
1121     }
1122     return b.getChosenGLCapabilities();
1123   }
1124
1125   @Override
1126   public final GLProfile getGLProfile() {
1127     return glProfile;
1128   }
1129
1130   @Override
1131   public NativeSurface getNativeSurface() {
1132     final Backend b = backend;
1133     if ( null == b ) {
1134         return null;
1135     }
1136     return b.getDrawable().getNativeSurface();
1137   }
1138
1139   @Override
1140   public long getHandle() {
1141     final Backend b = backend;
1142     if ( null == b ) {
1143         return 0;
1144     }
1145     return b.getDrawable().getNativeSurface().getSurfaceHandle();
1146   }
1147
1148   @Override
1149   public final GLDrawableFactory getFactory() {
1150     return factory;
1151   }
1152
1153   /**
1154    * Returns the used texture unit, i.e. a value of [0..n], or -1 if non used.
1155    * <p>
1156    * If implementation uses a texture-unit, it will be known only after the first initialization, i.e. display call.
1157    * </p>
1158    * <p>
1159    * See <a href="#fboGLSLVerticalFlip">FBO / GLSL Vertical Flip</a>.
1160    * </p>
1161    */
1162   public final int getTextureUnit() {
1163     final Backend b = backend;
1164     if ( null == b ) {
1165         return -1;
1166     }
1167     return b.getTextureUnit();
1168   }
1169
1170   /**
1171    * Allows user to request a texture unit to be used,
1172    * must be called before the first initialization, i.e. {@link #display()} call.
1173    * <p>
1174    * Defaults to <code>0</code>.
1175    * </p>
1176    * <p>
1177    * See <a href="#fboGLSLVerticalFlip">FBO / GLSL Vertical Flip</a>.
1178    * </p>
1179    *
1180    * @param v requested texture unit
1181    * @see #getTextureUnit()
1182    */
1183   public final void setTextureUnit(int v) {
1184       requestedTextureUnit = v;
1185   }
1186
1187   //----------------------------------------------------------------------
1188   // Internals only below this point
1189   //
1190
1191   private final Object initSync = new Object();
1192   private boolean initializeBackendImpl() {
1193     synchronized(initSync) {
1194         if( !isInitialized ) {
1195             if ( 0 >= panelWidth || 0 >= panelHeight ) {
1196               // See whether we have a non-zero size yet and can go ahead with
1197               // initialization
1198               if (0 >= reshapeWidth || 0 >= reshapeHeight ) {
1199                   return false;
1200               }
1201
1202               if (DEBUG) {
1203                   System.err.println(getThreadName()+": GLJPanel.createAndInitializeBackend: " +
1204                           panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr() + " -> " +
1205                           reshapeWidth+"x"+reshapeHeight+" @ scale "+getPixelScaleStr());
1206               }
1207               // Pull down reshapeWidth and reshapeHeight into panelWidth and
1208               // panelHeight eagerly in order to complete initialization, and
1209               // force a reshape later
1210               panelWidth = reshapeWidth;
1211               panelHeight = reshapeHeight;
1212             }
1213
1214             if ( null == backend ) {
1215                 if ( oglPipelineUsable() ) {
1216                     backend = new J2DOGLBackend();
1217                 } else {
1218                     backend = new OffscreenBackend(glProfile, customPixelBufferProvider);
1219                 }
1220                 isInitialized = false;
1221             }
1222
1223             if (!isInitialized) {
1224                 backend.initialize();
1225             }
1226             return isInitialized;
1227         } else {
1228             return true;
1229         }
1230     }
1231   }
1232
1233   private final String getPixelScaleStr() { return hasPixelScale[0]+"x"+hasPixelScale[1]; }
1234
1235   @Override
1236   public WindowClosingMode getDefaultCloseOperation() {
1237       return awtWindowClosingProtocol.getDefaultCloseOperation();
1238   }
1239
1240   @Override
1241   public WindowClosingMode setDefaultCloseOperation(WindowClosingMode op) {
1242       return awtWindowClosingProtocol.setDefaultCloseOperation(op);
1243   }
1244
1245   private boolean handleReshape() {
1246     if (DEBUG) {
1247       System.err.println(getThreadName()+": GLJPanel.handleReshape: "+
1248                          panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr() + " -> " +
1249                          reshapeWidth+"x"+reshapeHeight+" @ scale "+getPixelScaleStr());
1250     }
1251     panelWidth  = reshapeWidth;
1252     panelHeight = reshapeHeight;
1253
1254     return backend.handleReshape();
1255   }
1256
1257   // This is used as the GLEventListener for the pbuffer-based backend
1258   // as well as the callback mechanism for the other backends
1259   class Updater implements GLEventListener {
1260     private Graphics g;
1261
1262     public void setGraphics(Graphics g) {
1263       this.g = g;
1264     }
1265
1266     @Override
1267     public void init(GLAutoDrawable drawable) {
1268       if (!backend.preGL(g)) {
1269         return;
1270       }
1271       helper.init(GLJPanel.this, !sendReshape);
1272       backend.postGL(g, false);
1273     }
1274
1275     @Override
1276     public void dispose(GLAutoDrawable drawable) {
1277       helper.disposeAllGLEventListener(GLJPanel.this, false);
1278     }
1279
1280     @Override
1281     public void display(GLAutoDrawable drawable) {
1282       if (!backend.preGL(g)) {
1283         return;
1284       }
1285       if (sendReshape) {
1286         if (DEBUG) {
1287           System.err.println(getThreadName()+": GLJPanel.display: reshape(" + viewportX + "," + viewportY + " " + panelWidth + "x" + panelHeight + " @ scale "+getPixelScaleStr()+")");
1288         }
1289         helper.reshape(GLJPanel.this, viewportX, viewportY, panelWidth, panelHeight);
1290         sendReshape = false;
1291       }
1292
1293       helper.display(GLJPanel.this);
1294       backend.postGL(g, true);
1295     }
1296
1297     public void plainPaint(GLAutoDrawable drawable) {
1298       helper.display(GLJPanel.this);
1299     }
1300
1301     @Override
1302     public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
1303       // This is handled above and dispatched directly to the appropriate context
1304     }
1305   }
1306
1307   @Override
1308   public String toString() {
1309     final GLDrawable d = ( null != backend ) ? backend.getDrawable() : null;
1310     return "AWT-GLJPanel[ drawableType "+ ( ( null != d ) ? d.getClass().getName() : "null" ) +
1311            ", chosenCaps " + getChosenGLCapabilities() +
1312            "]";
1313   }
1314
1315   private final Runnable disposeAction = new Runnable() {
1316     @Override
1317     public void run() {
1318         if ( null != backend ) {
1319             final GLContext _context = backend.getContext();
1320             final boolean backendDestroy = !backend.isUsingOwnLifecycle();
1321             if( null != _context && _context.isCreated() ) {
1322                 // Catch dispose GLExceptions by GLEventListener, just 'print' them
1323                 // so we can continue with the destruction.
1324                 try {
1325                     helper.disposeGL(GLJPanel.this, _context, !backendDestroy);
1326                 } catch (GLException gle) {
1327                     gle.printStackTrace();
1328                 }
1329             }
1330             if ( backendDestroy ) {
1331                 backend.destroy();
1332                 backend = null;
1333                 isInitialized = false;
1334             }
1335         }
1336     }
1337   };
1338
1339   private final Runnable updaterInitAction = new Runnable() {
1340     @Override
1341     public void run() {
1342       updater.init(GLJPanel.this);
1343     }
1344   };
1345
1346   private final Runnable updaterDisplayAction = new Runnable() {
1347     @Override
1348     public void run() {
1349       updater.display(GLJPanel.this);
1350     }
1351   };
1352
1353   private final Runnable updaterPlainDisplayAction = new Runnable() {
1354     @Override
1355     public void run() {
1356       updater.plainPaint(GLJPanel.this);
1357     }
1358   };
1359
1360   private final Runnable paintImmediatelyAction = new Runnable() {
1361     @Override
1362     public void run() {
1363       paintImmediately(0, 0, getWidth(), getHeight());
1364     }
1365   };
1366
1367   private class DisposeGLEventListenerAction implements Runnable {
1368       GLEventListener listener;
1369       private final boolean remove;
1370       private DisposeGLEventListenerAction(GLEventListener listener, boolean remove) {
1371           this.listener = listener;
1372           this.remove = remove;
1373       }
1374
1375       @Override
1376       public void run() {
1377           final Backend b = backend;
1378           if ( null != b ) {
1379               listener = helper.disposeGLEventListener(GLJPanel.this, b.getDrawable(), b.getContext(), listener, remove);
1380           }
1381       }
1382   };
1383
1384   private int getGLInteger(GL gl, int which) {
1385     int[] tmp = new int[1];
1386     gl.glGetIntegerv(which, tmp, 0);
1387     return tmp[0];
1388   }
1389
1390   protected static String getThreadName() { return Thread.currentThread().getName(); }
1391
1392   //----------------------------------------------------------------------
1393   // Implementations of the various backends
1394   //
1395
1396   /**
1397    * Abstraction of the different rendering backends: i.e., pure
1398    * software / pixmap rendering, pbuffer-based acceleration, Java 2D
1399    * JOGL bridge
1400    */
1401   static interface Backend {
1402     /** Create, Destroy, .. */
1403     public boolean isUsingOwnLifecycle();
1404
1405     /** Called each time the backend needs to initialize itself */
1406     public void initialize();
1407
1408     /** Called when the backend should clean up its resources */
1409     public void destroy();
1410
1411     /** Called when the opacity of the GLJPanel is changed */
1412     public void setOpaque(boolean opaque);
1413
1414     /**
1415      * Called to manually create an additional OpenGL context against
1416      * this GLJPanel
1417      */
1418     public GLContext createContext(GLContext shareWith);
1419
1420     /** Called to set the current backend's GLContext */
1421     public void setContext(GLContext ctx);
1422
1423     /** Called to get the current backend's GLContext */
1424     public GLContext getContext();
1425
1426     /** Called to get the current backend's GLDrawable */
1427     public GLDrawable getDrawable();
1428
1429     /** Returns the used texture unit, i.e. a value of [0..n], or -1 if non used. */
1430     public int getTextureUnit();
1431
1432     /** Called to fetch the "real" GLCapabilities for the backend */
1433     public GLCapabilitiesImmutable getChosenGLCapabilities();
1434
1435     /** Called to fetch the "real" GLProfile for the backend */
1436     public GLProfile getGLProfile();
1437
1438     /**
1439      * Called to handle a reshape event. When this is called, the
1440      * OpenGL context associated with the backend is not current, to
1441      * make it easier to destroy and re-create pbuffers if necessary.
1442      */
1443     public boolean handleReshape();
1444
1445     /**
1446      * Called before the OpenGL work is done in init() and display().
1447      * If false is returned, this render is aborted.
1448      */
1449     public boolean preGL(Graphics g);
1450
1451     /**
1452      * Return true if backend handles 'swap buffer' itself
1453      * and hence the helper's setAutoSwapBuffer(enable) shall not be called.
1454      * In this case {@link GLJPanel#autoSwapBufferMode} is being used
1455      * in the backend to determine whether to swap buffers or not.
1456      */
1457     public boolean handlesSwapBuffer();
1458
1459     /**
1460      * Shall invoke underlying drawable's swapBuffer.
1461      */
1462     public void swapBuffers();
1463
1464     /**
1465      * Called after the OpenGL work is done in init() and display().
1466      * The isDisplay argument indicates whether this was called on
1467      * behalf of a call to display() rather than init().
1468      */
1469     public void postGL(Graphics g, boolean isDisplay);
1470
1471     /** Called from within paintComponent() to initiate the render */
1472     public void doPaintComponent(Graphics g);
1473
1474     /** Called from print .. no backend update desired onscreen */
1475     public void doPlainPaint();
1476   }
1477
1478   // Base class used by both the software (pixmap) and pbuffer
1479   // backends, both of which rely on reading back the OpenGL frame
1480   // buffer and drawing it with a BufferedImage
1481   class OffscreenBackend implements Backend {
1482     private final AWTGLPixelBufferProvider pixelBufferProvider;
1483     private final boolean useSingletonBuffer;
1484     private AWTGLPixelBuffer pixelBuffer;
1485     private BufferedImage alignedImage;
1486
1487     // One of these is used to store the read back pixels before storing
1488     // in the BufferedImage
1489     protected IntBuffer readBackIntsForCPUVFlip;
1490
1491     // Implementation using software rendering
1492     private volatile GLDrawableImpl offscreenDrawable; // volatile: avoid locking for read-only access
1493     private boolean offscreenIsFBO;
1494     private FBObject fboFlipped;
1495     private GLSLTextureRaster glslTextureRaster;
1496
1497     private volatile GLContextImpl offscreenContext; // volatile: avoid locking for read-only access
1498     private boolean flipVertical;
1499     private int frameCount = 0;
1500
1501     // For saving/restoring of OpenGL state during ReadPixels
1502     private final GLPixelStorageModes psm =  new GLPixelStorageModes();
1503
1504     OffscreenBackend(GLProfile glp, AWTGLPixelBufferProvider custom) {
1505         if(null == custom) {
1506             pixelBufferProvider = getSingleAWTGLPixelBufferProvider();
1507         } else {
1508             pixelBufferProvider = custom;
1509         }
1510         if( pixelBufferProvider instanceof SingletonGLPixelBufferProvider ) {
1511             useSingletonBuffer = true;
1512         } else {
1513             useSingletonBuffer = false;
1514         }
1515     }
1516
1517     @Override
1518     public final boolean isUsingOwnLifecycle() { return false; }
1519
1520     @Override
1521     public final void initialize() {
1522       if(DEBUG) {
1523           System.err.println(getThreadName()+": OffscreenBackend: initialize() - frameCount "+frameCount);
1524       }
1525       try {
1526           final GLContext[] shareWith = { null };
1527           if( helper.isSharedGLContextPending(shareWith) ) {
1528               return; // pending ..
1529           }
1530           offscreenDrawable = (GLDrawableImpl) factory.createOffscreenDrawable(
1531                                                     null /* default platform device */,
1532                                                     offscreenCaps,
1533                                                     chooser,
1534                                                     panelWidth, panelHeight);
1535           updateWrappedSurfaceScale(offscreenDrawable);
1536           offscreenDrawable.setRealized(true);
1537           if( DEBUG ) {
1538               offscreenDrawable.getNativeSurface().addSurfaceUpdatedListener(new SurfaceUpdatedListener() {
1539                   @Override
1540                   public final void surfaceUpdated(Object updater, NativeSurface ns, long when) {
1541                       System.err.println(getThreadName()+": OffscreenBackend.swapBuffers - frameCount "+frameCount);
1542                   } } );
1543           }
1544
1545           offscreenContext = (GLContextImpl) offscreenDrawable.createContext(shareWith[0]);
1546           offscreenContext.setContextCreationFlags(additionalCtxCreationFlags);
1547           if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) {
1548               isInitialized = true;
1549               helper.setAutoSwapBufferMode(false); // we handle swap-buffers, see handlesSwapBuffer()
1550
1551               final GL gl = offscreenContext.getGL();
1552               flipVertical = !GLJPanel.this.skipGLOrientationVerticalFlip && offscreenDrawable.isGLOriented();
1553               final GLCapabilitiesImmutable chosenCaps = offscreenDrawable.getChosenGLCapabilities();
1554               offscreenIsFBO = chosenCaps.isFBO();
1555               final boolean glslCompliant = !offscreenContext.hasRendererQuirk(GLRendererQuirks.GLSLNonCompliant);
1556               final boolean useGLSLFlip = flipVertical && offscreenIsFBO && gl.isGL2ES2() && USE_GLSL_TEXTURE_RASTERIZER && glslCompliant;
1557               if( DEBUG ) {
1558                   System.err.println(getThreadName()+": OffscreenBackend.initialize: useGLSLFlip "+useGLSLFlip+
1559                           " [flip "+flipVertical+", isFBO "+offscreenIsFBO+", isGL2ES2 "+gl.isGL2ES2()+
1560                           ", noglsl "+!USE_GLSL_TEXTURE_RASTERIZER+", glslNonCompliant "+!glslCompliant+
1561                           ", isGL2ES2 " + gl.isGL2ES2()+"]");
1562               }
1563               if( useGLSLFlip ) {
1564                   final GLFBODrawable fboDrawable = (GLFBODrawable) offscreenDrawable;
1565                   fboDrawable.setTextureUnit( GLJPanel.this.requestedTextureUnit );
1566                   try {
1567                       fboFlipped = new FBObject();
1568                       fboFlipped.reset(gl, fboDrawable.getSurfaceWidth(), fboDrawable.getSurfaceHeight(), 0, false);
1569                       fboFlipped.attachTexture2D(gl, 0, chosenCaps.getAlphaBits()>0);
1570                       // fboFlipped.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24);
1571                       glslTextureRaster = new GLSLTextureRaster(fboDrawable.getTextureUnit(), true);
1572                       glslTextureRaster.init(gl.getGL2ES2());
1573                       glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, fboDrawable.getSurfaceWidth(), fboDrawable.getSurfaceHeight());
1574                   } catch (Exception ex) {
1575                       ex.printStackTrace();
1576                       if(null != glslTextureRaster) {
1577                         glslTextureRaster.dispose(gl.getGL2ES2());
1578                         glslTextureRaster = null;
1579                       }
1580                       if(null != fboFlipped) {
1581                         fboFlipped.destroy(gl);
1582                         fboFlipped = null;
1583                       }
1584                   }
1585               } else {
1586                   fboFlipped = null;
1587                   glslTextureRaster = null;
1588               }
1589               offscreenContext.release();
1590           } else {
1591               isInitialized = false;
1592           }
1593       } finally {
1594           if( !isInitialized ) {
1595               if(null != offscreenContext) {
1596                   offscreenContext.destroy();
1597                   offscreenContext = null;
1598               }
1599               if(null != offscreenDrawable) {
1600                   offscreenDrawable.setRealized(false);
1601                   offscreenDrawable = null;
1602               }
1603           }
1604       }
1605     }
1606
1607     @Override
1608     public final void destroy() {
1609       if(DEBUG) {
1610           System.err.println(getThreadName()+": OffscreenBackend: destroy() - offscreenContext: "+(null!=offscreenContext)+" - offscreenDrawable: "+(null!=offscreenDrawable)+" - frameCount "+frameCount);
1611       }
1612       if ( null != offscreenContext && offscreenContext.isCreated() ) {
1613         if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) {
1614             try {
1615                 final GL gl = offscreenContext.getGL();
1616                 if(null != glslTextureRaster) {
1617                     glslTextureRaster.dispose(gl.getGL2ES2());
1618                 }
1619                 if(null != fboFlipped) {
1620                     fboFlipped.destroy(gl);
1621                 }
1622             } finally {
1623                 offscreenContext.destroy();
1624             }
1625         }
1626       }
1627       offscreenContext = null;
1628       glslTextureRaster = null;
1629       fboFlipped = null;
1630       offscreenContext = null;
1631
1632       if (offscreenDrawable != null) {
1633         final AbstractGraphicsDevice adevice = offscreenDrawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice();
1634         offscreenDrawable.setRealized(false);
1635         offscreenDrawable = null;
1636         if(null != adevice) {
1637             adevice.close();
1638         }
1639       }
1640       offscreenIsFBO = false;
1641
1642       if( null != readBackIntsForCPUVFlip ) {
1643           readBackIntsForCPUVFlip.clear();
1644           readBackIntsForCPUVFlip = null;
1645       }
1646       if( null != pixelBuffer ) {
1647           if( !useSingletonBuffer ) {
1648               pixelBuffer.dispose();
1649           }
1650           pixelBuffer = null;
1651       }
1652       alignedImage = null;
1653     }
1654
1655     @Override
1656     public final void setOpaque(boolean opaque) {
1657       if ( opaque != isOpaque() && !useSingletonBuffer ) {
1658           pixelBuffer.dispose();
1659           pixelBuffer = null;
1660           alignedImage = null;
1661       }
1662     }
1663
1664     @Override
1665     public final boolean preGL(Graphics g) {
1666       // Empty in this implementation
1667       return true;
1668     }
1669
1670     @Override
1671     public final boolean handlesSwapBuffer() {
1672         return true;
1673     }
1674
1675     @Override
1676     public final void swapBuffers() {
1677         final GLDrawable d = offscreenDrawable;
1678         if( null != d ) {
1679             d.swapBuffers();
1680         }
1681     }
1682
1683     @Override
1684     public final void postGL(Graphics g, boolean isDisplay) {
1685       if (isDisplay) {
1686         if(DEBUG) {
1687             System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: - frameCount "+frameCount);
1688         }
1689         if( autoSwapBufferMode ) {
1690             // Since we only use a single-buffer non-MSAA or double-buffered MSAA offscreenDrawable,
1691             // we can always swap!
1692             offscreenDrawable.swapBuffers();
1693         }
1694         final GL gl = offscreenContext.getGL();
1695
1696         final int componentCount;
1697         final int alignment;
1698         if( isOpaque() ) {
1699             // w/o alpha
1700             componentCount = 3;
1701             alignment = 1;
1702         } else {
1703             // with alpha
1704             componentCount = 4;
1705             alignment = 4;
1706         }
1707
1708         final GLPixelAttributes pixelAttribs = pixelBufferProvider.getAttributes(gl, componentCount);
1709
1710         if( useSingletonBuffer ) { // attempt to fetch the latest AWTGLPixelBuffer
1711             pixelBuffer = (AWTGLPixelBuffer) ((SingletonGLPixelBufferProvider)pixelBufferProvider).getSingleBuffer(pixelAttribs);
1712         }
1713         if( null != pixelBuffer && pixelBuffer.requiresNewBuffer(gl, panelWidth, panelHeight, 0) ) {
1714             pixelBuffer.dispose();
1715             pixelBuffer = null;
1716             alignedImage = null;
1717         }
1718         if ( null == pixelBuffer ) {
1719           if (0 >= panelWidth || 0 >= panelHeight ) {
1720               return;
1721           }
1722           pixelBuffer = pixelBufferProvider.allocate(gl, pixelAttribs, panelWidth, panelHeight, 1, true, 0);
1723           if(DEBUG) {
1724               System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" pixelBufferProvider isSingletonBufferProvider "+useSingletonBuffer+", 0x"+Integer.toHexString(pixelBufferProvider.hashCode())+", "+pixelBufferProvider.getClass().getSimpleName());
1725               System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" pixelBuffer 0x"+Integer.toHexString(pixelBuffer.hashCode())+", "+pixelBuffer+", alignment "+alignment);
1726               System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" flippedVertical "+flipVertical+", glslTextureRaster "+(null!=glslTextureRaster));
1727               System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" panelSize "+panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr());
1728           }
1729         }
1730         if( offscreenDrawable.getSurfaceWidth() != panelWidth || offscreenDrawable.getSurfaceHeight() != panelHeight ) {
1731             throw new InternalError("OffscreenDrawable panelSize mismatch (reshape missed): panelSize "+panelWidth+"x"+panelHeight+" != drawable "+offscreenDrawable.getSurfaceWidth()+"x"+offscreenDrawable.getSurfaceHeight()+", on thread "+getThreadName());
1732         }
1733         if( null == alignedImage ||
1734             panelWidth != alignedImage.getWidth() || panelHeight != alignedImage.getHeight() ||
1735             !pixelBuffer.isDataBufferSource(alignedImage) ) {
1736             alignedImage = pixelBuffer.getAlignedImage(panelWidth, panelHeight);
1737             if(DEBUG) {
1738                 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);
1739             }
1740         }
1741         final IntBuffer readBackInts;
1742
1743         if( !flipVertical || null != glslTextureRaster ) {
1744            readBackInts = (IntBuffer) pixelBuffer.buffer;
1745         } else {
1746            if( null == readBackIntsForCPUVFlip || pixelBuffer.width * pixelBuffer.height > readBackIntsForCPUVFlip.remaining() ) {
1747                readBackIntsForCPUVFlip = IntBuffer.allocate(pixelBuffer.width * pixelBuffer.height);
1748            }
1749            readBackInts = readBackIntsForCPUVFlip;
1750         }
1751
1752         final TextureState usrTexState, fboTexState;
1753         final int fboTexUnit = GL.GL_TEXTURE0 + ( offscreenIsFBO ? ((GLFBODrawable)offscreenDrawable).getTextureUnit() : 0 );
1754
1755         if( offscreenIsFBO ) {
1756             usrTexState = new TextureState(gl, GL.GL_TEXTURE_2D);
1757             if( fboTexUnit != usrTexState.getUnit() ) {
1758                 // glActiveTexture(..) + glBindTexture(..) are implicit performed in GLFBODrawableImpl's
1759                 // swapBuffers/contextMadeCurent -> swapFBOImpl.
1760                 // We need to cache the texture unit's bound texture-id before it's overwritten.
1761                 gl.glActiveTexture(fboTexUnit);
1762                 fboTexState = new TextureState(gl, fboTexUnit, GL.GL_TEXTURE_2D);
1763             } else {
1764                 fboTexState = usrTexState;
1765             }
1766         } else {
1767             usrTexState = null;
1768             fboTexState = null;
1769         }
1770
1771         // Must now copy pixels from offscreen context into surface
1772         if(DEBUG) {
1773             System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.readPixels: - frameCount "+frameCount);
1774         }
1775
1776         // Save PACK modes, reset them to defaults and set alignment
1777         psm.setPackAlignment(gl, alignment);
1778         if(gl.isGL2ES3()) {
1779             final GL2ES3 gl2es3 = gl.getGL2ES3();
1780             gl2es3.glPixelStorei(GL2ES3.GL_PACK_ROW_LENGTH, panelWidth);
1781             gl2es3.glReadBuffer(gl2es3.getDefaultReadBuffer());
1782         }
1783
1784         if(null != glslTextureRaster) { // implies flippedVertical
1785             final boolean viewportChange;
1786             final int[] usrViewport = new int[] { 0, 0, 0, 0 };
1787             gl.glGetIntegerv(GL.GL_VIEWPORT, usrViewport, 0);
1788             viewportChange = 0 != usrViewport[0] || 0 != usrViewport[1] ||
1789                              offscreenDrawable.getSurfaceWidth() != usrViewport[2] || offscreenDrawable.getSurfaceHeight() != usrViewport[3];
1790             if( DEBUG_VIEWPORT ) {
1791                 System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL: "+GLJPanel.this.getName()+" Viewport: change "+viewportChange+
1792                          ", "+usrViewport[0]+"/"+usrViewport[1]+" "+usrViewport[2]+"x"+usrViewport[3]+
1793                          " -> 0/0 "+offscreenDrawable.getSurfaceWidth()+"x"+offscreenDrawable.getSurfaceHeight());
1794             }
1795             if( viewportChange ) {
1796                 gl.glViewport(0, 0, offscreenDrawable.getSurfaceWidth(), offscreenDrawable.getSurfaceHeight());
1797             }
1798
1799             // perform vert-flipping via OpenGL/FBO
1800             final GLFBODrawable fboDrawable = (GLFBODrawable)offscreenDrawable;
1801             final FBObject.TextureAttachment fboTex = fboDrawable.getTextureBuffer(GL.GL_FRONT);
1802
1803             fboFlipped.bind(gl);
1804
1805             // gl.glActiveTexture(GL.GL_TEXTURE0 + fboDrawable.getTextureUnit()); // implicit by GLFBODrawableImpl: swapBuffers/contextMadeCurent -> swapFBOImpl
1806             gl.glBindTexture(GL.GL_TEXTURE_2D, fboTex.getName());
1807             // gl.glClear(GL.GL_DEPTH_BUFFER_BIT); // fboFlipped runs w/o DEPTH!
1808
1809             glslTextureRaster.display(gl.getGL2ES2());
1810             gl.glReadPixels(0, 0, panelWidth, panelHeight, pixelAttribs.format, pixelAttribs.type, readBackInts);
1811
1812             fboFlipped.unbind(gl);
1813             if( viewportChange ) {
1814                 gl.glViewport(usrViewport[0], usrViewport[1], usrViewport[2], usrViewport[3]);
1815             }
1816             fboTexState.restore(gl);
1817         } else {
1818             gl.glReadPixels(0, 0, panelWidth, panelHeight, pixelAttribs.format, pixelAttribs.type, readBackInts);
1819
1820             if ( flipVertical ) {
1821                 // Copy temporary data into raster of BufferedImage for faster
1822                 // blitting Note that we could avoid this copy in the cases
1823                 // where !offscreenDrawable.isGLOriented(),
1824                 // but that's the software rendering path which is very slow anyway.
1825                 final BufferedImage image = alignedImage;
1826                 final int[] src = readBackInts.array();
1827                 final int[] dest = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
1828                 final int incr = panelWidth;
1829                 int srcPos = 0;
1830                 int destPos = (panelHeight - 1) * panelWidth;
1831                 for (; destPos >= 0; srcPos += incr, destPos -= incr) {
1832                   System.arraycopy(src, srcPos, dest, destPos, incr);
1833                 }
1834             }
1835         }
1836         if( offscreenIsFBO && fboTexUnit != usrTexState.getUnit() ) {
1837             usrTexState.restore(gl);
1838         }
1839
1840         // Restore saved modes.
1841         psm.restore(gl);
1842
1843         // Note: image will be drawn back in paintComponent() for
1844         // correctness on all platforms
1845       }
1846     }
1847
1848     @Override
1849     public final int getTextureUnit() {
1850         if(null != glslTextureRaster && null != offscreenDrawable) { // implies flippedVertical
1851             return ((GLFBODrawable)offscreenDrawable).getTextureUnit();
1852         }
1853         return -1;
1854     }
1855
1856     @Override
1857     public final void doPaintComponent(Graphics g) {
1858       helper.invokeGL(offscreenDrawable, offscreenContext, updaterDisplayAction, updaterInitAction);
1859
1860       if ( null != alignedImage ) {
1861         if( DEBUG ) {
1862             System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.doPaintComponent.drawImage: - frameCount "+frameCount);
1863         }
1864         // Draw resulting image in one shot
1865         g.drawImage(alignedImage, 0, 0, alignedImage.getWidth()/hasPixelScale[0], alignedImage.getHeight()/hasPixelScale[1], null); // Null ImageObserver since image data is ready.
1866       }
1867       frameCount++;
1868     }
1869
1870     @Override
1871     public final void doPlainPaint() {
1872       helper.invokeGL(offscreenDrawable, offscreenContext, updaterPlainDisplayAction, updaterInitAction);
1873     }
1874
1875     @Override
1876     public final boolean handleReshape() {
1877         GLDrawableImpl _drawable = offscreenDrawable;
1878         {
1879             final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, offscreenContext, panelWidth, panelHeight);
1880             if(_drawable != _drawableNew) {
1881                 // write back
1882                 _drawable = _drawableNew;
1883                 offscreenDrawable = _drawableNew;
1884                 updateWrappedSurfaceScale(offscreenDrawable);
1885             }
1886         }
1887         if (DEBUG) {
1888             System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.handleReshape: " +panelWidth+"x"+panelHeight + " @ scale "+getPixelScaleStr() + " -> " + _drawable.getSurfaceWidth()+"x"+_drawable.getSurfaceHeight());
1889         }
1890         panelWidth = _drawable.getSurfaceWidth();
1891         panelHeight = _drawable.getSurfaceHeight();
1892
1893         if( null != glslTextureRaster ) {
1894             if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) {
1895                 try {
1896                     final GL gl = offscreenContext.getGL();
1897                     fboFlipped.reset(gl, _drawable.getSurfaceWidth(), _drawable.getSurfaceHeight(), 0, false);
1898                     glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, _drawable.getSurfaceWidth(), _drawable.getSurfaceHeight());
1899                 } finally {
1900                     offscreenContext.release();
1901                 }
1902             }
1903         }
1904         return _drawable.isRealized();
1905     }
1906
1907     @Override
1908     public final GLContext createContext(GLContext shareWith) {
1909       return (null != offscreenDrawable) ? offscreenDrawable.createContext(shareWith) : null;
1910     }
1911
1912     @Override
1913     public final void setContext(GLContext ctx) {
1914       offscreenContext=(GLContextImpl)ctx;
1915     }
1916
1917     @Override
1918     public final GLContext getContext() {
1919       return offscreenContext;
1920     }
1921
1922     @Override
1923     public final GLDrawable getDrawable() {
1924         return offscreenDrawable;
1925     }
1926
1927     @Override
1928     public final GLCapabilitiesImmutable getChosenGLCapabilities() {
1929       if (offscreenDrawable == null) {
1930         return null;
1931       }
1932       return offscreenDrawable.getChosenGLCapabilities();
1933     }
1934
1935     @Override
1936     public final GLProfile getGLProfile() {
1937       if (offscreenDrawable == null) {
1938         return null;
1939       }
1940       return offscreenDrawable.getGLProfile();
1941     }
1942   }
1943
1944   class J2DOGLBackend implements Backend {
1945     // Opaque Object identifier representing the Java2D surface we are
1946     // drawing to; used to determine when to destroy and recreate JOGL
1947     // context
1948     private Object j2dSurface;
1949     // Graphics object being used during Java2D update action
1950     // (absolutely essential to cache this)
1951     // No-op context representing the Java2D OpenGL context
1952     private GLContext j2dContext;
1953     // Context associated with no-op drawable representing the JOGL
1954     // OpenGL context
1955     private GLDrawable joglDrawable;
1956     // The real OpenGL context JOGL uses to render
1957     private GLContext  joglContext;
1958     // State captured from Java2D OpenGL context necessary in order to
1959     // properly render into Java2D back buffer
1960     private final int[] drawBuffer   = new int[1];
1961     private final int[] readBuffer   = new int[1];
1962     // This is required when the FBO option of the Java2D / OpenGL
1963     // pipeline is active
1964     private final int[] frameBuffer  = new int[1];
1965     // Current (as of this writing) NVidia drivers have a couple of bugs
1966     // relating to the sharing of framebuffer and renderbuffer objects
1967     // between contexts. It appears we have to (a) reattach the color
1968     // attachment and (b) actually create new depth buffer storage and
1969     // attach it in order for the FBO to behave properly in our context.
1970     private boolean checkedForFBObjectWorkarounds;
1971     private boolean fbObjectWorkarounds;
1972     private int[] frameBufferDepthBuffer;
1973     private int[] frameBufferTexture;
1974     private boolean createNewDepthBuffer;
1975     // Current (as of this writing) ATI drivers have problems when the
1976     // same FBO is bound in two different contexts. Here we check for
1977     // this case and explicitly release the FBO from Java2D's context
1978     // before switching to ours. Java2D will re-bind the FBO when it
1979     // makes its context current the next time. Interestingly, if we run
1980     // this code path on NVidia hardware, it breaks the rendering
1981     // results -- no output is generated. This doesn't appear to be an
1982     // interaction with the abovementioned NVidia-specific workarounds,
1983     // as even if we disable that code the FBO is still reported as
1984     // incomplete in our context.
1985     private boolean checkedGLVendor;
1986     private boolean vendorIsATI;
1987
1988     // Holding on to this GraphicsConfiguration is a workaround for a
1989     // problem in the Java 2D / JOGL bridge when FBOs are enabled; see
1990     // comment related to Issue 274 below
1991     private GraphicsConfiguration workaroundConfig;
1992
1993     @Override
1994     public final boolean isUsingOwnLifecycle() { return true; }
1995
1996     @Override
1997     public final void initialize() {
1998       if(DEBUG) {
1999           System.err.println(getThreadName()+": J2DOGL: initialize()");
2000       }
2001       // No-op in this implementation; everything is done lazily
2002       isInitialized = true;
2003     }
2004
2005     @Override
2006     public final void destroy() {
2007       Java2D.invokeWithOGLContextCurrent(null, new Runnable() {
2008           @Override
2009           public void run() {
2010             if(DEBUG) {
2011                 System.err.println(getThreadName()+": J2DOGL: destroy() - joglContext: "+(null!=joglContext)+" - joglDrawable: "+(null!=joglDrawable));
2012             }
2013             if (joglContext != null) {
2014               joglContext.destroy();
2015               joglContext = null;
2016             }
2017             joglDrawable = null;
2018             if (j2dContext != null) {
2019               j2dContext.destroy();
2020               j2dContext = null;
2021             }
2022           }
2023         });
2024     }
2025
2026     @Override
2027     public final void setOpaque(boolean opaque) {
2028       // Empty in this implementation
2029     }
2030
2031     @Override
2032     public final GLContext createContext(GLContext shareWith) {
2033       if(null != shareWith) {
2034           throw new GLException("J2DOGLBackend cannot create context w/ additional shared context, since it already needs to share the context w/ J2D.");
2035       }
2036       return (null != joglDrawable && null != j2dContext) ? joglDrawable.createContext(j2dContext) : null;
2037     }
2038
2039     @Override
2040     public final void setContext(GLContext ctx) {
2041         joglContext=ctx;
2042     }
2043
2044     @Override
2045     public final GLContext getContext() {
2046       return joglContext;
2047     }
2048
2049     @Override
2050     public final GLDrawable getDrawable() {
2051         return joglDrawable;
2052     }
2053
2054     @Override
2055     public final int getTextureUnit() { return -1; }
2056
2057     @Override
2058     public final GLCapabilitiesImmutable getChosenGLCapabilities() {
2059       // FIXME: should do better than this; is it possible to query J2D Caps ?
2060       return new GLCapabilities(null);
2061     }
2062
2063     @Override
2064     public final GLProfile getGLProfile() {
2065       // FIXME: should do better than this; is it possible to query J2D's Profile ?
2066       return GLProfile.getDefault(GLProfile.getDefaultDevice());
2067     }
2068
2069     @Override
2070     public final boolean handleReshape() {
2071       // Empty in this implementation
2072       return true;
2073     }
2074
2075     @Override
2076     public final boolean preGL(Graphics g) {
2077       final GL2 gl = joglContext.getGL().getGL2();
2078       // Set up needed state in JOGL context from Java2D context
2079       gl.glEnable(GL2.GL_SCISSOR_TEST);
2080       Rectangle r = Java2D.getOGLScissorBox(g);
2081
2082       if (r == null) {
2083         if (DEBUG) {
2084           System.err.println(getThreadName()+": Java2D.getOGLScissorBox() returned null");
2085         }
2086         return false;
2087       }
2088       if (DEBUG) {
2089         System.err.println(getThreadName()+": GLJPanel: gl.glScissor(" + r.x + ", " + r.y + ", " + r.width + ", " + r.height + ")");
2090       }
2091
2092       gl.glScissor(r.x, r.y, r.width, r.height);
2093       Rectangle oglViewport = Java2D.getOGLViewport(g, panelWidth, panelHeight);
2094       // If the viewport X or Y changes, in addition to the panel's
2095       // width or height, we need to send a reshape operation to the
2096       // client
2097       if ((viewportX != oglViewport.x) ||
2098           (viewportY != oglViewport.y)) {
2099         sendReshape = true;
2100         if (DEBUG) {
2101           System.err.println(getThreadName()+": Sending reshape because viewport changed");
2102           System.err.println("  viewportX (" + viewportX + ") ?= oglViewport.x (" + oglViewport.x + ")");
2103           System.err.println("  viewportY (" + viewportY + ") ?= oglViewport.y (" + oglViewport.y + ")");
2104         }
2105       }
2106       viewportX = oglViewport.x;
2107       viewportY = oglViewport.y;
2108
2109       // If the FBO option is active, bind to the FBO from the Java2D
2110       // context.
2111       // Note that all of the plumbing in the context sharing stuff will
2112       // allow us to bind to this object since it's in our namespace.
2113       if (Java2D.isFBOEnabled() &&
2114           Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
2115
2116         // The texture target for Java2D's OpenGL pipeline when using FBOs
2117         // -- either GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE_ARB
2118         int fboTextureTarget = Java2D.getOGLTextureType(g);
2119
2120         if (!checkedForFBObjectWorkarounds) {
2121           checkedForFBObjectWorkarounds = true;
2122           gl.glBindTexture(fboTextureTarget, 0);
2123           gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, frameBuffer[0]);
2124           int status = gl.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER);
2125           if (status != GL.GL_FRAMEBUFFER_COMPLETE) {
2126               // Need to do workarounds
2127               fbObjectWorkarounds = true;
2128               createNewDepthBuffer = true;
2129               if (DEBUG) {
2130                   System.err.println(getThreadName()+": GLJPanel: ERR GL_FRAMEBUFFER_BINDING: Discovered Invalid J2D FBO("+frameBuffer[0]+"): "+FBObject.getStatusString(status) +
2131                                      ", frame_buffer_object workarounds to be necessary");
2132               }
2133           } else {
2134             // Don't need the frameBufferTexture temporary any more
2135             frameBufferTexture = null;
2136             if (DEBUG) {
2137               System.err.println(getThreadName()+": GLJPanel: OK GL_FRAMEBUFFER_BINDING: "+frameBuffer[0]);
2138             }
2139           }
2140         }
2141
2142         if (fbObjectWorkarounds && createNewDepthBuffer) {
2143           if (frameBufferDepthBuffer == null)
2144             frameBufferDepthBuffer = new int[1];
2145
2146           // Create our own depth renderbuffer and associated storage
2147           // If we have an old one, delete it
2148           if (frameBufferDepthBuffer[0] != 0) {
2149             gl.glDeleteRenderbuffers(1, frameBufferDepthBuffer, 0);
2150             frameBufferDepthBuffer[0] = 0;
2151           }
2152
2153           gl.glBindTexture(fboTextureTarget, frameBufferTexture[0]);
2154           int[] width = new int[1];
2155           int[] height = new int[1];
2156           gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_WIDTH, width, 0);
2157           gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_HEIGHT, height, 0);
2158
2159           gl.glGenRenderbuffers(1, frameBufferDepthBuffer, 0);
2160           if (DEBUG) {
2161             System.err.println(getThreadName()+": GLJPanel: Generated frameBufferDepthBuffer " + frameBufferDepthBuffer[0] +
2162                                " with width " + width[0] + ", height " + height[0]);
2163           }
2164
2165           gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, frameBufferDepthBuffer[0]);
2166           // FIXME: may need a loop here like in Java2D
2167           gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL2GL3.GL_DEPTH_COMPONENT24, width[0], height[0]);
2168
2169           gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER, 0);
2170           createNewDepthBuffer = false;
2171         }
2172
2173         gl.glBindTexture(fboTextureTarget, 0);
2174         gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, frameBuffer[0]);
2175
2176         if (fbObjectWorkarounds) {
2177           // Hook up the color and depth buffer attachment points for this framebuffer
2178           gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER,
2179                                     GL.GL_COLOR_ATTACHMENT0,
2180                                     fboTextureTarget,
2181                                     frameBufferTexture[0],
2182                                     0);
2183           if (DEBUG) {
2184             System.err.println(getThreadName()+": GLJPanel: frameBufferDepthBuffer: " + frameBufferDepthBuffer[0]);
2185           }
2186           gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER,
2187                                        GL.GL_DEPTH_ATTACHMENT,
2188                                        GL.GL_RENDERBUFFER,
2189                                        frameBufferDepthBuffer[0]);
2190         }
2191
2192         if (DEBUG) {
2193           int status = gl.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER);
2194           if (status != GL.GL_FRAMEBUFFER_COMPLETE) {
2195             throw new GLException("Error: framebuffer was incomplete: status = 0x" +
2196                                   Integer.toHexString(status));
2197           }
2198         }
2199       } else {
2200         if (DEBUG) {
2201           System.err.println(getThreadName()+": GLJPanel: Setting up drawBuffer " + drawBuffer[0] +
2202                              " and readBuffer " + readBuffer[0]);
2203         }
2204
2205         gl.glDrawBuffer(drawBuffer[0]);
2206         gl.glReadBuffer(readBuffer[0]);
2207       }
2208
2209       return true;
2210     }
2211
2212     @Override
2213     public final boolean handlesSwapBuffer() {
2214         return false;
2215     }
2216
2217     @Override
2218     public final void swapBuffers() {
2219         final GLDrawable d = joglDrawable;
2220         if( null != d ) {
2221             d.swapBuffers();
2222         }
2223     }
2224
2225     @Override
2226     public final void postGL(Graphics g, boolean isDisplay) {
2227       // Cause OpenGL pipeline to flush its results because
2228       // otherwise it's possible we will buffer up multiple frames'
2229       // rendering results, resulting in apparent mouse lag
2230       GL gl = joglContext.getGL();
2231       gl.glFinish();
2232
2233       if (Java2D.isFBOEnabled() &&
2234           Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
2235         // Unbind the framebuffer from our context to work around
2236         // apparent driver bugs or at least unspecified behavior causing
2237         // OpenGL to run out of memory with certain cards and drivers
2238         gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
2239       }
2240     }
2241
2242     @Override
2243     public final void doPaintComponent(final Graphics g) {
2244       // This is a workaround for an issue in the Java 2D / JOGL
2245       // bridge (reported by an end user as JOGL Issue 274) where Java
2246       // 2D can occasionally leave its internal OpenGL context current
2247       // to the on-screen window rather than its internal "scratch"
2248       // pbuffer surface to which the FBO is attached. JOGL expects to
2249       // find a stable OpenGL drawable (on Windows, an HDC) upon which
2250       // it can create another OpenGL context. It turns out that, on
2251       // Windows, when Java 2D makes its internal OpenGL context
2252       // current against the window in order to put pixels on the
2253       // screen, it gets the device context for the window, makes its
2254       // context current, and releases the device context. This means
2255       // that when JOGL's Runnable gets to run below, the HDC is
2256       // already invalid. The workaround for this is to force Java 2D
2257       // to make its context current to the scratch surface, which we
2258       // can do by executing an empty Runnable with the "shared"
2259       // context current. This will be fixed in a Java SE 6 update
2260       // release, hopefully 6u2.
2261       if (Java2D.isFBOEnabled()) {
2262         if (workaroundConfig == null) {
2263           workaroundConfig = GraphicsEnvironment.
2264             getLocalGraphicsEnvironment().
2265             getDefaultScreenDevice().
2266             getDefaultConfiguration();
2267         }
2268         Java2D.invokeWithOGLSharedContextCurrent(workaroundConfig, new Runnable() { @Override
2269         public void run() {}});
2270       }
2271
2272       Java2D.invokeWithOGLContextCurrent(g, new Runnable() {
2273           @Override
2274           public void run() {
2275             if (DEBUG) {
2276               System.err.println(getThreadName()+": GLJPanel.invokeWithOGLContextCurrent");
2277             }
2278
2279             // Create no-op context representing Java2D context
2280             if (j2dContext == null) {
2281               j2dContext = factory.createExternalGLContext();
2282               if (DEBUG) {
2283                 System.err.println(getThreadName()+": GLJPanel.Created External Context: "+j2dContext);
2284               }
2285               if (DEBUG) {
2286 //                j2dContext.setGL(new DebugGL2(j2dContext.getGL().getGL2()));
2287               }
2288
2289               // Check to see whether we can support the requested
2290               // capabilities or need to fall back to a pbuffer
2291               // FIXME: add more checks?
2292
2293               j2dContext.makeCurrent();
2294               GL gl = j2dContext.getGL();
2295               if ((getGLInteger(gl, GL.GL_RED_BITS)         < offscreenCaps.getRedBits())        ||
2296                   (getGLInteger(gl, GL.GL_GREEN_BITS)       < offscreenCaps.getGreenBits())      ||
2297                   (getGLInteger(gl, GL.GL_BLUE_BITS)        < offscreenCaps.getBlueBits())       ||
2298                   //                  (getGLInteger(gl, GL.GL_ALPHA_BITS)       < offscreenCaps.getAlphaBits())      ||
2299                   (getGLInteger(gl, GL2.GL_ACCUM_RED_BITS)   < offscreenCaps.getAccumRedBits())   ||
2300                   (getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) < offscreenCaps.getAccumGreenBits()) ||
2301                   (getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS)  < offscreenCaps.getAccumBlueBits())  ||
2302                   (getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) < offscreenCaps.getAccumAlphaBits()) ||
2303                   //          (getGLInteger(gl, GL2.GL_DEPTH_BITS)       < offscreenCaps.getDepthBits())      ||
2304                   (getGLInteger(gl, GL.GL_STENCIL_BITS)     < offscreenCaps.getStencilBits())) {
2305                 if (DEBUG) {
2306                   System.err.println(getThreadName()+": GLJPanel: Falling back to pbuffer-based support because Java2D context insufficient");
2307                   System.err.println("                    Available              Required");
2308                   System.err.println("GL_RED_BITS         " + getGLInteger(gl, GL.GL_RED_BITS)         + "              " + offscreenCaps.getRedBits());
2309                   System.err.println("GL_GREEN_BITS       " + getGLInteger(gl, GL.GL_GREEN_BITS)       + "              " + offscreenCaps.getGreenBits());
2310                   System.err.println("GL_BLUE_BITS        " + getGLInteger(gl, GL.GL_BLUE_BITS)        + "              " + offscreenCaps.getBlueBits());
2311                   System.err.println("GL_ALPHA_BITS       " + getGLInteger(gl, GL.GL_ALPHA_BITS)       + "              " + offscreenCaps.getAlphaBits());
2312                   System.err.println("GL_ACCUM_RED_BITS   " + getGLInteger(gl, GL2.GL_ACCUM_RED_BITS)   + "              " + offscreenCaps.getAccumRedBits());
2313                   System.err.println("GL_ACCUM_GREEN_BITS " + getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) + "              " + offscreenCaps.getAccumGreenBits());
2314                   System.err.println("GL_ACCUM_BLUE_BITS  " + getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS)  + "              " + offscreenCaps.getAccumBlueBits());
2315                   System.err.println("GL_ACCUM_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) + "              " + offscreenCaps.getAccumAlphaBits());
2316                   System.err.println("GL_DEPTH_BITS       " + getGLInteger(gl, GL.GL_DEPTH_BITS)       + "              " + offscreenCaps.getDepthBits());
2317                   System.err.println("GL_STENCIL_BITS     " + getGLInteger(gl, GL.GL_STENCIL_BITS)     + "              " + offscreenCaps.getStencilBits());
2318                 }
2319                 isInitialized = false;
2320                 backend = null;
2321                 java2DGLPipelineOK = false;
2322                 handleReshape = true;
2323                 j2dContext.destroy();
2324                 j2dContext = null;
2325                 return;
2326               }
2327             } else {
2328               j2dContext.makeCurrent();
2329             }
2330             try {
2331               captureJ2DState(j2dContext.getGL(), g);
2332               Object curSurface = Java2D.getOGLSurfaceIdentifier(g);
2333               if (curSurface != null) {
2334                 if (j2dSurface != curSurface) {
2335                   if (joglContext != null) {
2336                     joglContext.destroy();
2337                     joglContext = null;
2338                     joglDrawable = null;
2339                     sendReshape = true;
2340                     if (DEBUG) {
2341                       System.err.println(getThreadName()+": Sending reshape because surface changed");
2342                       System.err.println("New surface = " + curSurface);
2343                     }
2344                   }
2345                   j2dSurface = curSurface;
2346                   if (DEBUG) {
2347                       System.err.print(getThreadName()+": Surface type: ");
2348                       int surfaceType = Java2D.getOGLSurfaceType(g);
2349                       if (surfaceType == Java2D.UNDEFINED) {
2350                         System.err.println("UNDEFINED");
2351                       } else if (surfaceType == Java2D.WINDOW) {
2352                         System.err.println("WINDOW");
2353                       } else if (surfaceType == Java2D.PBUFFER) {
2354                         System.err.println("PBUFFER");
2355                       } else if (surfaceType == Java2D.TEXTURE) {
2356                         System.err.println("TEXTURE");
2357                       } else if (surfaceType == Java2D.FLIP_BACKBUFFER) {
2358                         System.err.println("FLIP_BACKBUFFER");
2359                       } else if (surfaceType == Java2D.FBOBJECT) {
2360                         System.err.println("FBOBJECT");
2361                       } else {
2362                         System.err.println("(Unknown surface type " + surfaceType + ")");
2363                       }
2364                   }
2365                 }
2366                 if (joglContext == null) {
2367                   AbstractGraphicsDevice device = j2dContext.getGLDrawable().getNativeSurface().getGraphicsConfiguration().getScreen().getDevice();
2368                   if (factory.canCreateExternalGLDrawable(device)) {
2369                     joglDrawable = factory.createExternalGLDrawable();
2370                     joglContext = joglDrawable.createContext(j2dContext);
2371                     if (DEBUG) {
2372                         System.err.println("-- Created External Drawable: "+joglDrawable);
2373                         System.err.println("-- Created Context: "+joglContext);
2374                     }
2375                   }
2376                   if (Java2D.isFBOEnabled() &&
2377                       Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT &&
2378                       fbObjectWorkarounds) {
2379                     createNewDepthBuffer = true;
2380                   }
2381                 }
2382                 helper.invokeGL(joglDrawable, joglContext, updaterDisplayAction, updaterInitAction);
2383               }
2384             } finally {
2385               j2dContext.release();
2386             }
2387           }
2388         });
2389     }
2390
2391     @Override
2392     public final void doPlainPaint() {
2393       helper.invokeGL(joglDrawable, joglContext, updaterPlainDisplayAction, updaterInitAction);
2394     }
2395
2396     private final void captureJ2DState(GL gl, Graphics g) {
2397       gl.glGetIntegerv(GL2.GL_DRAW_BUFFER, drawBuffer, 0);
2398       gl.glGetIntegerv(GL2.GL_READ_BUFFER, readBuffer, 0);
2399       if (Java2D.isFBOEnabled() &&
2400           Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
2401         gl.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, frameBuffer, 0);
2402         if(!gl.glIsFramebuffer(frameBuffer[0])) {
2403           checkedForFBObjectWorkarounds=true;
2404           fbObjectWorkarounds = true;
2405           createNewDepthBuffer = true;
2406           if (DEBUG) {
2407               System.err.println(getThreadName()+": GLJPanel: Fetched ERR GL_FRAMEBUFFER_BINDING: "+frameBuffer[0]+" - NOT A FBO"+
2408                                  ", frame_buffer_object workarounds to be necessary");
2409           }
2410         } else if (DEBUG) {
2411           System.err.println(getThreadName()+": GLJPanel: Fetched OK GL_FRAMEBUFFER_BINDING: "+frameBuffer[0]);
2412         }
2413
2414         if(fbObjectWorkarounds || !checkedForFBObjectWorkarounds) {
2415             // See above for description of what we are doing here
2416             if (frameBufferTexture == null)
2417                 frameBufferTexture = new int[1];
2418
2419             // Query the framebuffer for its color buffer so we can hook
2420             // it back up in our context (should not be necessary)
2421             gl.glGetFramebufferAttachmentParameteriv(GL.GL_FRAMEBUFFER,
2422                                                      GL.GL_COLOR_ATTACHMENT0,
2423                                                      GL.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
2424                                                      frameBufferTexture, 0);
2425             if (DEBUG) {
2426                 System.err.println(getThreadName()+": GLJPanel: FBO COLOR_ATTACHMENT0: " + frameBufferTexture[0]);
2427             }
2428         }
2429
2430         if (!checkedGLVendor) {
2431           checkedGLVendor = true;
2432           String vendor = gl.glGetString(GL.GL_VENDOR);
2433
2434           if ((vendor != null) &&
2435               vendor.startsWith("ATI")) {
2436             vendorIsATI = true;
2437           }
2438         }
2439
2440         if (vendorIsATI) {
2441           // Unbind the FBO from Java2D's context as it appears that
2442           // driver bugs on ATI's side are causing problems if the FBO is
2443           // simultaneously bound to more than one context. Java2D will
2444           // re-bind the FBO during the next validation of its context.
2445           // Note: this breaks rendering at least on NVidia hardware
2446           gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
2447         }
2448       }
2449     }
2450   }
2451 }
http://JogAmp.org git info: FAQ, tutorial and man pages.