Jogamp
Bug 741 HiDPI: Add ScalableSurface interface to get/set pixelScale w/ full OSX impl.
[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( hasPixelScale );
563       }
564   }
565
566   @Override
567   public final void setSurfaceScale(final int[] pixelScale) { // HiDPI support
568       SurfaceScaleUtils.validateReqPixelScale(reqPixelScale, pixelScale, DEBUG ? getClass().getSimpleName() : null);
569       final Backend b = backend;
570       if ( isInitialized && null != b ) {
571           final int[] pixelScaleInt;
572           {
573               final int ps = JAWTUtil.getPixelScale(getGraphicsConfiguration());
574               pixelScaleInt = new int[] { ps, ps };
575           }
576           final int hadPixelScaleX = hasPixelScale[0];
577           final int hadPixelScaleY = hasPixelScale[1];
578           SurfaceScaleUtils.computePixelScale(hasPixelScale, hasPixelScale, reqPixelScale, pixelScaleInt, DEBUG ? getClass().getSimpleName() : null);
579           if( hadPixelScaleX != hasPixelScale[0] || hadPixelScaleY != hasPixelScale[1] ) {
580               updateWrappedSurfaceScale(b.getDrawable());
581               reshapeImpl(getWidth(), getHeight());
582               display();
583           }
584       }
585   }
586
587   @Override
588   public final int[] getSurfaceScale(final int[] result) {
589       System.arraycopy(isInitialized ? hasPixelScale : reqPixelScale, 0, result, 0, 2);
590       return result;
591   }
592
593   /** Overridden to track when this component is added to a container.
594       Subclasses which override this method must call
595       super.addNotify() in their addNotify() method in order to
596       function properly. <P>
597
598       <DL><DD><CODE>addNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
599   @Override
600   public void addNotify() {
601     super.addNotify();
602     awtWindowClosingProtocol.addClosingListener();
603
604     // HiDPI support
605     final int[] pixelScaleInt;
606     {
607         final int ps = JAWTUtil.getPixelScale(getGraphicsConfiguration());
608         pixelScaleInt = new int[] { ps, ps };
609     }
610     SurfaceScaleUtils.computePixelScale(hasPixelScale, hasPixelScale, reqPixelScale, pixelScaleInt, DEBUG ? getClass().getSimpleName() : null);
611
612     if (DEBUG) {
613         System.err.println(getThreadName()+": GLJPanel.addNotify()");
614     }
615   }
616
617   /** Overridden to track when this component is removed from a
618       container. Subclasses which override this method must call
619       super.removeNotify() in their removeNotify() method in order to
620       function properly. <P>
621
622       <DL><DD><CODE>removeNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
623   @Override
624   public void removeNotify() {
625     awtWindowClosingProtocol.removeClosingListener();
626
627     dispose();
628     super.removeNotify();
629   }
630
631   /** Overridden to cause {@link GLDrawableHelper#reshape} to be
632       called on all registered {@link GLEventListener}s. Subclasses
633       which override this method must call super.reshape() in
634       their reshape() method in order to function properly. <P>
635    *
636    * {@inheritDoc}
637    */
638   @SuppressWarnings("deprecation")
639   @Override
640   public void reshape(int x, int y, int width, int height) {
641     super.reshape(x, y, width, height);
642     reshapeImpl(width, height);
643   }
644
645   private void reshapeImpl(final int width, final int height) {
646     final int scaledWidth = width * hasPixelScale[0];
647     final int scaledHeight = height * hasPixelScale[1];
648     if( DEBUG ) {
649         System.err.println(getThreadName()+": GLJPanel.reshape.0 "+this.getName()+" resize"+(printActive?"WithinPrint":"")+
650                 " [ this "+getWidth()+"x"+getHeight()+", pixelScale "+getPixelScaleStr()+
651                 ", panel "+panelWidth+"x"+panelHeight +
652                 ", reshape: " +reshapeWidth+"x"+reshapeHeight +
653                 "] -> "+(printActive?"[skipped] ":"") + width+"x"+height+" * "+getPixelScaleStr()+" -> "+scaledWidth+"x"+scaledHeight);
654     }
655     if( !printActive ) {
656         reshapeWidth = scaledWidth;
657         reshapeHeight = scaledHeight;
658         handleReshape = true;
659     }
660   }
661
662   private volatile boolean printActive = false;
663   private GLAnimatorControl printAnimator = null;
664   private GLAutoDrawable printGLAD = null;
665   private AWTTilePainter printAWTTiles = null;
666
667   @Override
668   public void setupPrint(double scaleMatX, double scaleMatY, int numSamples, int tileWidth, int tileHeight) {
669       printActive = true;
670       final int componentCount = isOpaque() ? 3 : 4;
671       final TileRenderer printRenderer = new TileRenderer();
672       printAWTTiles = new AWTTilePainter(printRenderer, componentCount, scaleMatX, scaleMatY, numSamples, tileWidth, tileHeight, DEBUG);
673       AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, setupPrintOnEDT);
674   }
675   private final Runnable setupPrintOnEDT = new Runnable() {
676       @Override
677       public void run() {
678           if( !isInitialized ) {
679               initializeBackendImpl();
680           }
681           if (!isInitialized) {
682               if(DEBUG) {
683                   System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, drawable not valid yet");
684               }
685               printActive = false;
686               return; // not yet available ..
687           }
688           if( !isVisible() ) {
689               if(DEBUG) {
690                   System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, panel not visible");
691               }
692               printActive = false;
693               return; // not yet available ..
694           }
695           sendReshape = false; // clear reshape flag
696           handleReshape = false; // ditto
697           printAnimator =  helper.getAnimator();
698           if( null != printAnimator ) {
699               printAnimator.remove(GLJPanel.this);
700           }
701
702           printGLAD = GLJPanel.this; // default: re-use
703           final GLCapabilities caps = (GLCapabilities)getChosenGLCapabilities().cloneMutable();
704           final int printNumSamples = printAWTTiles.getNumSamples(caps);
705           GLDrawable printDrawable = printGLAD.getDelegatedDrawable();
706           final boolean reqNewGLADSamples = printNumSamples != caps.getNumSamples();
707           final boolean reqNewGLADSize = printAWTTiles.customTileWidth != -1 && printAWTTiles.customTileWidth != printDrawable.getSurfaceWidth() ||
708                                          printAWTTiles.customTileHeight != -1 && printAWTTiles.customTileHeight != printDrawable.getSurfaceHeight();
709           final boolean reqNewGLAD = reqNewGLADSamples || reqNewGLADSize ;
710           if( DEBUG ) {
711               System.err.println("AWT print.setup: reqNewGLAD "+reqNewGLAD+"[ samples "+reqNewGLADSamples+", size "+reqNewGLADSize+"], "+
712                                  ", drawableSize "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+
713                                  ", customTileSize "+printAWTTiles.customTileWidth+"x"+printAWTTiles.customTileHeight+
714                                  ", scaleMat "+printAWTTiles.scaleMatX+" x "+printAWTTiles.scaleMatY+
715                                  ", numSamples "+printAWTTiles.customNumSamples+" -> "+printNumSamples+", printAnimator "+printAnimator);
716           }
717           if( reqNewGLAD ) {
718               caps.setDoubleBuffered(false);
719               caps.setOnscreen(false);
720               caps.setSampleBuffers(0 < printNumSamples);
721               caps.setNumSamples(printNumSamples);
722               final GLDrawableFactory factory = GLDrawableFactory.getFactory(caps.getGLProfile());
723               printGLAD = factory.createOffscreenAutoDrawable(null, caps, null,
724                       printAWTTiles.customTileWidth != -1 ? printAWTTiles.customTileWidth : DEFAULT_PRINT_TILE_SIZE,
725                       printAWTTiles.customTileHeight != -1 ? printAWTTiles.customTileHeight : DEFAULT_PRINT_TILE_SIZE);
726               GLDrawableUtil.swapGLContextAndAllGLEventListener(GLJPanel.this, printGLAD);
727               printDrawable = printGLAD.getDelegatedDrawable();
728           }
729           printAWTTiles.setGLOrientation( !GLJPanel.this.skipGLOrientationVerticalFlip && printGLAD.isGLOriented(), printGLAD.isGLOriented() );
730           printAWTTiles.renderer.setTileSize(printDrawable.getSurfaceWidth(), printDrawable.getSurfaceHeight(), 0);
731           printAWTTiles.renderer.attachAutoDrawable(printGLAD);
732           if( DEBUG ) {
733               System.err.println("AWT print.setup "+printAWTTiles);
734               System.err.println("AWT print.setup AA "+printNumSamples+", "+caps);
735               System.err.println("AWT print.setup printGLAD: "+printGLAD.getSurfaceWidth()+"x"+printGLAD.getSurfaceHeight()+", "+printGLAD);
736               System.err.println("AWT print.setup printDraw: "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+", "+printDrawable);
737           }
738       }
739   };
740
741   @Override
742   public void releasePrint() {
743       if( !printActive ) {
744           throw new IllegalStateException("setupPrint() not called");
745       }
746       sendReshape = false; // clear reshape flag
747       handleReshape = false; // ditto
748       AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, releasePrintOnEDT);
749   }
750
751   private final Runnable releasePrintOnEDT = new Runnable() {
752       @Override
753       public void run() {
754           if( DEBUG ) {
755               System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0 "+printAWTTiles);
756           }
757           printAWTTiles.dispose();
758           printAWTTiles= null;
759           if( printGLAD != GLJPanel.this ) {
760               GLDrawableUtil.swapGLContextAndAllGLEventListener(printGLAD, GLJPanel.this);
761               printGLAD.destroy();
762           }
763           printGLAD = null;
764           if( null != printAnimator ) {
765               printAnimator.add(GLJPanel.this);
766               printAnimator = null;
767           }
768
769           // trigger reshape, i.e. gl-viewport and -listener - this component might got resized!
770           final int awtWidth = GLJPanel.this.getWidth();
771           final int awtHeight= GLJPanel.this.getHeight();
772           final int scaledAWTWidth = awtWidth * hasPixelScale[0];
773           final int scaledAWTHeight= awtHeight * hasPixelScale[1];
774           final GLDrawable drawable = GLJPanel.this.getDelegatedDrawable();
775           if( scaledAWTWidth != panelWidth || scaledAWTHeight != panelHeight ||
776               drawable.getSurfaceWidth() != panelWidth || drawable.getSurfaceHeight() != panelHeight ) {
777               // -> !( awtSize == panelSize == drawableSize )
778               if ( DEBUG ) {
779                   System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0: resizeWithinPrint panel " +panelWidth+"x"+panelHeight + " @ scale "+getPixelScaleStr()+
780                           ", draw "+drawable.getSurfaceWidth()+"x"+drawable.getSurfaceHeight()+
781                           " -> " + awtWidth+"x"+awtHeight+" * "+getPixelScaleStr()+" -> "+scaledAWTWidth+"x"+scaledAWTHeight);
782               }
783               reshapeWidth = scaledAWTWidth;
784               reshapeHeight = scaledAWTHeight;
785               sendReshape = handleReshape(); // reshapeSize -> panelSize, backend reshape w/ GL reshape
786           } else {
787               sendReshape = true; // only GL reshape
788           }
789           printActive = false;
790           display();
791       }
792   };
793
794   @Override
795   public void print(Graphics graphics) {
796       if( !printActive ) {
797           throw new IllegalStateException("setupPrint() not called");
798       }
799       if(DEBUG && !EventQueue.isDispatchThread()) {
800           System.err.println(getThreadName()+": Warning: GLCanvas print - not called from AWT-EDT");
801           // we cannot dispatch print on AWT-EDT due to printing internal locking ..
802       }
803       sendReshape = false; // clear reshape flag
804       handleReshape = false; // ditto
805
806       final Graphics2D g2d = (Graphics2D)graphics;
807       try {
808           printAWTTiles.setupGraphics2DAndClipBounds(g2d, getWidth(), getHeight());
809           final TileRenderer tileRenderer = printAWTTiles.renderer;
810           if( DEBUG ) {
811               System.err.println("AWT print.0: "+tileRenderer);
812           }
813           if( !tileRenderer.eot() ) {
814               try {
815                   do {
816                       if( printGLAD != GLJPanel.this ) {
817                           tileRenderer.display();
818                       } else {
819                           backend.doPlainPaint();
820                       }
821                   } while ( !tileRenderer.eot() );
822                   if( DEBUG ) {
823                       System.err.println("AWT print.1: "+printAWTTiles);
824                   }
825               } finally {
826                   tileRenderer.reset();
827                   printAWTTiles.resetGraphics2D();
828               }
829           }
830       } catch (NoninvertibleTransformException nte) {
831           System.err.println("Catched: Inversion failed of: "+g2d.getTransform());
832           nte.printStackTrace();
833       }
834       if( DEBUG ) {
835           System.err.println("AWT print.X: "+printAWTTiles);
836       }
837   }
838   @Override
839   protected void printComponent(Graphics g) {
840       if( DEBUG ) {
841           System.err.println("AWT printComponent.X: "+printAWTTiles);
842       }
843       print(g);
844   }
845
846   @Override
847   public void setOpaque(boolean opaque) {
848     if (backend != null) {
849       backend.setOpaque(opaque);
850     }
851     super.setOpaque(opaque);
852   }
853
854   @Override
855   public void addGLEventListener(GLEventListener listener) {
856     helper.addGLEventListener(listener);
857   }
858
859   @Override
860   public void addGLEventListener(int index, GLEventListener listener) {
861     helper.addGLEventListener(index, listener);
862   }
863
864   @Override
865   public int getGLEventListenerCount() {
866       return helper.getGLEventListenerCount();
867   }
868
869   @Override
870   public GLEventListener getGLEventListener(int index) throws IndexOutOfBoundsException {
871       return helper.getGLEventListener(index);
872   }
873
874   @Override
875   public boolean areAllGLEventListenerInitialized() {
876      return helper.areAllGLEventListenerInitialized();
877   }
878
879   @Override
880   public boolean getGLEventListenerInitState(GLEventListener listener) {
881       return helper.getGLEventListenerInitState(listener);
882   }
883
884   @Override
885   public void setGLEventListenerInitState(GLEventListener listener, boolean initialized) {
886       helper.setGLEventListenerInitState(listener, initialized);
887   }
888
889   @Override
890   public GLEventListener disposeGLEventListener(GLEventListener listener, boolean remove) {
891     final DisposeGLEventListenerAction r = new DisposeGLEventListenerAction(listener, remove);
892     if (EventQueue.isDispatchThread()) {
893       r.run();
894     } else {
895       // Multithreaded redrawing of Swing components is not allowed,
896       // so do everything on the event dispatch thread
897       try {
898         EventQueue.invokeAndWait(r);
899       } catch (Exception e) {
900         throw new GLException(e);
901       }
902     }
903     return r.listener;
904   }
905
906   @Override
907   public GLEventListener removeGLEventListener(GLEventListener listener) {
908     return helper.removeGLEventListener(listener);
909   }
910
911   @Override
912   public void setAnimator(GLAnimatorControl animatorControl) {
913     helper.setAnimator(animatorControl);
914   }
915
916   @Override
917   public GLAnimatorControl getAnimator() {
918     return helper.getAnimator();
919   }
920
921   @Override
922   public final Thread setExclusiveContextThread(Thread t) throws GLException {
923       return helper.setExclusiveContextThread(t, getContext());
924   }
925
926   @Override
927   public final Thread getExclusiveContextThread() {
928       return helper.getExclusiveContextThread();
929   }
930
931   @Override
932   public boolean invoke(boolean wait, GLRunnable glRunnable) {
933     return helper.invoke(this, wait, glRunnable);
934   }
935
936   @Override
937   public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) {
938     return helper.invoke(this, wait, glRunnables);
939   }
940
941   @Override
942   public GLContext createContext(GLContext shareWith) {
943     final Backend b = backend;
944     if ( null == b ) {
945         return null;
946     }
947     return b.createContext(shareWith);
948   }
949
950   @Override
951   public void setRealized(boolean realized) {
952   }
953
954   @Override
955   public boolean isRealized() {
956       return isInitialized;
957   }
958
959   @Override
960   public GLContext setContext(GLContext newCtx, boolean destroyPrevCtx) {
961     final Backend b = backend;
962     if ( null == b ) {
963         return null;
964     }
965     final GLContext oldCtx = b.getContext();
966     GLDrawableHelper.switchContext(b.getDrawable(), oldCtx, destroyPrevCtx, newCtx, additionalCtxCreationFlags);
967     b.setContext(newCtx);
968     return oldCtx;
969   }
970
971
972   @Override
973   public final GLDrawable getDelegatedDrawable() {
974     final Backend b = backend;
975     if ( null == b ) {
976         return null;
977     }
978     return b.getDrawable();
979   }
980
981   @Override
982   public GLContext getContext() {
983     final Backend b = backend;
984     if ( null == b ) {
985         return null;
986     }
987     return b.getContext();
988   }
989
990   @Override
991   public GL getGL() {
992     if (Beans.isDesignTime()) {
993       return null;
994     }
995     GLContext context = getContext();
996     return (context == null) ? null : context.getGL();
997   }
998
999   @Override
1000   public GL setGL(GL gl) {
1001     GLContext context = getContext();
1002     if (context != null) {
1003       context.setGL(gl);
1004       return gl;
1005     }
1006     return null;
1007   }
1008
1009   @Override
1010   public void setAutoSwapBufferMode(boolean enable) {
1011     this.autoSwapBufferMode = enable;
1012     boolean backendHandlesSwapBuffer = false;
1013     if( isInitialized ) {
1014         final Backend b = backend;
1015         if ( null != b ) {
1016             backendHandlesSwapBuffer= b.handlesSwapBuffer();
1017         }
1018     }
1019     if( !backendHandlesSwapBuffer ) {
1020         helper.setAutoSwapBufferMode(enable);
1021     }
1022   }
1023
1024   @Override
1025   public boolean getAutoSwapBufferMode() {
1026     return autoSwapBufferMode;
1027   }
1028
1029   @Override
1030   public void swapBuffers() {
1031     if( isInitialized ) {
1032         final Backend b = backend;
1033         if ( null != b ) {
1034             b.swapBuffers();
1035         }
1036     }
1037   }
1038
1039   @Override
1040   public void setContextCreationFlags(int flags) {
1041     additionalCtxCreationFlags = flags;
1042   }
1043
1044   @Override
1045   public int getContextCreationFlags() {
1046     return additionalCtxCreationFlags;
1047   }
1048
1049   /** For a translucent GLJPanel (one for which {@link #setOpaque
1050       setOpaque}(false) has been called), indicates whether the
1051       application should preserve the OpenGL color buffer
1052       (GL_COLOR_BUFFER_BIT) for correct rendering of the GLJPanel and
1053       underlying widgets which may show through portions of the
1054       GLJPanel with alpha values less than 1.  Most Swing
1055       implementations currently expect the GLJPanel to be completely
1056       cleared (e.g., by <code>glClear(GL_COLOR_BUFFER_BIT |
1057       GL_DEPTH_BUFFER_BIT)</code>), but for certain optimized Swing
1058       implementations which use OpenGL internally, it may be possible
1059       to perform OpenGL rendering using the GLJPanel into the same
1060       OpenGL drawable as the Swing implementation uses. */
1061   public boolean shouldPreserveColorBufferIfTranslucent() {
1062     return oglPipelineUsable();
1063   }
1064
1065   @Override
1066   public int getSurfaceWidth() {
1067       return panelWidth; // FIXME HiDPI: Accurate or: getWidth() * hasPixelScale[0];
1068   }
1069
1070   @Override
1071   public int getSurfaceHeight() {
1072       return panelHeight; // FIXME HiDPI: Accurate or: getHeight() * hasPixelScale[1];
1073   }
1074
1075   /**
1076    * {@inheritDoc}
1077    * <p>
1078    * Method returns a valid value only <i>after</i>
1079    * the backend has been initialized, either {@link #initializeBackend(boolean) eagerly}
1080    * or manually via the first display call.<br/>
1081    * Method always returns a valid value when called from within a {@link GLEventListener}.
1082    * </p>
1083    */
1084   @Override
1085   public boolean isGLOriented() {
1086     final Backend b = backend;
1087     if ( null == b ) {
1088         return true;
1089     }
1090     return b.getDrawable().isGLOriented();
1091   }
1092
1093   /**
1094    * Set skipping {@link #isGLOriented()} based vertical flip,
1095    * which usually is required by the offscreen backend,
1096    * see details about <a href="#verticalFlip">vertical flip</a>
1097    * and <a href="#fboGLSLVerticalFlip">FBO / GLSL vertical flip</a>.
1098    * <p>
1099    * If set to <code>true</code>, user needs to flip the OpenGL rendered scene
1100    * <i>if {@link #isGLOriented()} == true</i>, e.g. via the PMV matrix.<br/>
1101    * See constraints of {@link #isGLOriented()}.
1102    * </p>
1103    */
1104   public final void setSkipGLOrientationVerticalFlip(boolean v) {
1105       skipGLOrientationVerticalFlip = v;
1106   }
1107   /** See {@link #setSkipGLOrientationVerticalFlip(boolean)}. */
1108   public final boolean getSkipGLOrientationVerticalFlip() {
1109       return skipGLOrientationVerticalFlip;
1110   }
1111
1112   @Override
1113   public GLCapabilitiesImmutable getChosenGLCapabilities() {
1114     final Backend b = backend;
1115     if ( null == b ) {
1116         return null;
1117     }
1118     return b.getChosenGLCapabilities();
1119   }
1120
1121   @Override
1122   public final GLProfile getGLProfile() {
1123     return glProfile;
1124   }
1125
1126   @Override
1127   public NativeSurface getNativeSurface() {
1128     final Backend b = backend;
1129     if ( null == b ) {
1130         return null;
1131     }
1132     return b.getDrawable().getNativeSurface();
1133   }
1134
1135   @Override
1136   public long getHandle() {
1137     final Backend b = backend;
1138     if ( null == b ) {
1139         return 0;
1140     }
1141     return b.getDrawable().getNativeSurface().getSurfaceHandle();
1142   }
1143
1144   @Override
1145   public final GLDrawableFactory getFactory() {
1146     return factory;
1147   }
1148
1149   /**
1150    * Returns the used texture unit, i.e. a value of [0..n], or -1 if non used.
1151    * <p>
1152    * If implementation uses a texture-unit, it will be known only after the first initialization, i.e. display call.
1153    * </p>
1154    * <p>
1155    * See <a href="#fboGLSLVerticalFlip">FBO / GLSL Vertical Flip</a>.
1156    * </p>
1157    */
1158   public final int getTextureUnit() {
1159     final Backend b = backend;
1160     if ( null == b ) {
1161         return -1;
1162     }
1163     return b.getTextureUnit();
1164   }
1165
1166   /**
1167    * Allows user to request a texture unit to be used,
1168    * must be called before the first initialization, i.e. {@link #display()} call.
1169    * <p>
1170    * Defaults to <code>0</code>.
1171    * </p>
1172    * <p>
1173    * See <a href="#fboGLSLVerticalFlip">FBO / GLSL Vertical Flip</a>.
1174    * </p>
1175    *
1176    * @param v requested texture unit
1177    * @see #getTextureUnit()
1178    */
1179   public final void setTextureUnit(int v) {
1180       requestedTextureUnit = v;
1181   }
1182
1183   //----------------------------------------------------------------------
1184   // Internals only below this point
1185   //
1186
1187   private final Object initSync = new Object();
1188   private boolean initializeBackendImpl() {
1189     synchronized(initSync) {
1190         if( !isInitialized ) {
1191             if ( 0 >= panelWidth || 0 >= panelHeight ) {
1192               // See whether we have a non-zero size yet and can go ahead with
1193               // initialization
1194               if (0 >= reshapeWidth || 0 >= reshapeHeight ) {
1195                   return false;
1196               }
1197
1198               if (DEBUG) {
1199                   System.err.println(getThreadName()+": GLJPanel.createAndInitializeBackend: " +
1200                           panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr() + " -> " +
1201                           reshapeWidth+"x"+reshapeHeight+" @ scale "+getPixelScaleStr());
1202               }
1203               // Pull down reshapeWidth and reshapeHeight into panelWidth and
1204               // panelHeight eagerly in order to complete initialization, and
1205               // force a reshape later
1206               panelWidth = reshapeWidth;
1207               panelHeight = reshapeHeight;
1208             }
1209
1210             if ( null == backend ) {
1211                 if ( oglPipelineUsable() ) {
1212                     backend = new J2DOGLBackend();
1213                 } else {
1214                     backend = new OffscreenBackend(glProfile, customPixelBufferProvider);
1215                 }
1216                 isInitialized = false;
1217             }
1218
1219             if (!isInitialized) {
1220                 backend.initialize();
1221             }
1222             return isInitialized;
1223         } else {
1224             return true;
1225         }
1226     }
1227   }
1228
1229   private final String getPixelScaleStr() { return hasPixelScale[0]+"x"+hasPixelScale[1]; }
1230
1231   @Override
1232   public WindowClosingMode getDefaultCloseOperation() {
1233       return awtWindowClosingProtocol.getDefaultCloseOperation();
1234   }
1235
1236   @Override
1237   public WindowClosingMode setDefaultCloseOperation(WindowClosingMode op) {
1238       return awtWindowClosingProtocol.setDefaultCloseOperation(op);
1239   }
1240
1241   private boolean handleReshape() {
1242     if (DEBUG) {
1243       System.err.println(getThreadName()+": GLJPanel.handleReshape: "+
1244                          panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr() + " -> " +
1245                          reshapeWidth+"x"+reshapeHeight+" @ scale "+getPixelScaleStr());
1246     }
1247     panelWidth  = reshapeWidth;
1248     panelHeight = reshapeHeight;
1249
1250     return backend.handleReshape();
1251   }
1252
1253   // This is used as the GLEventListener for the pbuffer-based backend
1254   // as well as the callback mechanism for the other backends
1255   class Updater implements GLEventListener {
1256     private Graphics g;
1257
1258     public void setGraphics(Graphics g) {
1259       this.g = g;
1260     }
1261
1262     @Override
1263     public void init(GLAutoDrawable drawable) {
1264       if (!backend.preGL(g)) {
1265         return;
1266       }
1267       helper.init(GLJPanel.this, !sendReshape);
1268       backend.postGL(g, false);
1269     }
1270
1271     @Override
1272     public void dispose(GLAutoDrawable drawable) {
1273       helper.disposeAllGLEventListener(GLJPanel.this, false);
1274     }
1275
1276     @Override
1277     public void display(GLAutoDrawable drawable) {
1278       if (!backend.preGL(g)) {
1279         return;
1280       }
1281       if (sendReshape) {
1282         if (DEBUG) {
1283           System.err.println(getThreadName()+": GLJPanel.display: reshape(" + viewportX + "," + viewportY + " " + panelWidth + "x" + panelHeight + " @ scale "+getPixelScaleStr()+")");
1284         }
1285         helper.reshape(GLJPanel.this, viewportX, viewportY, panelWidth, panelHeight);
1286         sendReshape = false;
1287       }
1288
1289       helper.display(GLJPanel.this);
1290       backend.postGL(g, true);
1291     }
1292
1293     public void plainPaint(GLAutoDrawable drawable) {
1294       helper.display(GLJPanel.this);
1295     }
1296
1297     @Override
1298     public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
1299       // This is handled above and dispatched directly to the appropriate context
1300     }
1301   }
1302
1303   @Override
1304   public String toString() {
1305     final GLDrawable d = ( null != backend ) ? backend.getDrawable() : null;
1306     return "AWT-GLJPanel[ drawableType "+ ( ( null != d ) ? d.getClass().getName() : "null" ) +
1307            ", chosenCaps " + getChosenGLCapabilities() +
1308            "]";
1309   }
1310
1311   private final Runnable disposeAction = new Runnable() {
1312     @Override
1313     public void run() {
1314         if ( null != backend ) {
1315             final GLContext _context = backend.getContext();
1316             final boolean backendDestroy = !backend.isUsingOwnLifecycle();
1317             if( null != _context && _context.isCreated() ) {
1318                 // Catch dispose GLExceptions by GLEventListener, just 'print' them
1319                 // so we can continue with the destruction.
1320                 try {
1321                     helper.disposeGL(GLJPanel.this, _context, !backendDestroy);
1322                 } catch (GLException gle) {
1323                     gle.printStackTrace();
1324                 }
1325             }
1326             if ( backendDestroy ) {
1327                 backend.destroy();
1328                 backend = null;
1329                 isInitialized = false;
1330             }
1331         }
1332     }
1333   };
1334
1335   private final Runnable updaterInitAction = new Runnable() {
1336     @Override
1337     public void run() {
1338       updater.init(GLJPanel.this);
1339     }
1340   };
1341
1342   private final Runnable updaterDisplayAction = new Runnable() {
1343     @Override
1344     public void run() {
1345       updater.display(GLJPanel.this);
1346     }
1347   };
1348
1349   private final Runnable updaterPlainDisplayAction = new Runnable() {
1350     @Override
1351     public void run() {
1352       updater.plainPaint(GLJPanel.this);
1353     }
1354   };
1355
1356   private final Runnable paintImmediatelyAction = new Runnable() {
1357     @Override
1358     public void run() {
1359       paintImmediately(0, 0, getWidth(), getHeight());
1360     }
1361   };
1362
1363   private class DisposeGLEventListenerAction implements Runnable {
1364       GLEventListener listener;
1365       private final boolean remove;
1366       private DisposeGLEventListenerAction(GLEventListener listener, boolean remove) {
1367           this.listener = listener;
1368           this.remove = remove;
1369       }
1370
1371       @Override
1372       public void run() {
1373           final Backend b = backend;
1374           if ( null != b ) {
1375               listener = helper.disposeGLEventListener(GLJPanel.this, b.getDrawable(), b.getContext(), listener, remove);
1376           }
1377       }
1378   };
1379
1380   private int getGLInteger(GL gl, int which) {
1381     int[] tmp = new int[1];
1382     gl.glGetIntegerv(which, tmp, 0);
1383     return tmp[0];
1384   }
1385
1386   protected static String getThreadName() { return Thread.currentThread().getName(); }
1387
1388   //----------------------------------------------------------------------
1389   // Implementations of the various backends
1390   //
1391
1392   /**
1393    * Abstraction of the different rendering backends: i.e., pure
1394    * software / pixmap rendering, pbuffer-based acceleration, Java 2D
1395    * JOGL bridge
1396    */
1397   static interface Backend {
1398     /** Create, Destroy, .. */
1399     public boolean isUsingOwnLifecycle();
1400
1401     /** Called each time the backend needs to initialize itself */
1402     public void initialize();
1403
1404     /** Called when the backend should clean up its resources */
1405     public void destroy();
1406
1407     /** Called when the opacity of the GLJPanel is changed */
1408     public void setOpaque(boolean opaque);
1409
1410     /**
1411      * Called to manually create an additional OpenGL context against
1412      * this GLJPanel
1413      */
1414     public GLContext createContext(GLContext shareWith);
1415
1416     /** Called to set the current backend's GLContext */
1417     public void setContext(GLContext ctx);
1418
1419     /** Called to get the current backend's GLContext */
1420     public GLContext getContext();
1421
1422     /** Called to get the current backend's GLDrawable */
1423     public GLDrawable getDrawable();
1424
1425     /** Returns the used texture unit, i.e. a value of [0..n], or -1 if non used. */
1426     public int getTextureUnit();
1427
1428     /** Called to fetch the "real" GLCapabilities for the backend */
1429     public GLCapabilitiesImmutable getChosenGLCapabilities();
1430
1431     /** Called to fetch the "real" GLProfile for the backend */
1432     public GLProfile getGLProfile();
1433
1434     /**
1435      * Called to handle a reshape event. When this is called, the
1436      * OpenGL context associated with the backend is not current, to
1437      * make it easier to destroy and re-create pbuffers if necessary.
1438      */
1439     public boolean handleReshape();
1440
1441     /**
1442      * Called before the OpenGL work is done in init() and display().
1443      * If false is returned, this render is aborted.
1444      */
1445     public boolean preGL(Graphics g);
1446
1447     /**
1448      * Return true if backend handles 'swap buffer' itself
1449      * and hence the helper's setAutoSwapBuffer(enable) shall not be called.
1450      * In this case {@link GLJPanel#autoSwapBufferMode} is being used
1451      * in the backend to determine whether to swap buffers or not.
1452      */
1453     public boolean handlesSwapBuffer();
1454
1455     /**
1456      * Shall invoke underlying drawable's swapBuffer.
1457      */
1458     public void swapBuffers();
1459
1460     /**
1461      * Called after the OpenGL work is done in init() and display().
1462      * The isDisplay argument indicates whether this was called on
1463      * behalf of a call to display() rather than init().
1464      */
1465     public void postGL(Graphics g, boolean isDisplay);
1466
1467     /** Called from within paintComponent() to initiate the render */
1468     public void doPaintComponent(Graphics g);
1469
1470     /** Called from print .. no backend update desired onscreen */
1471     public void doPlainPaint();
1472   }
1473
1474   // Base class used by both the software (pixmap) and pbuffer
1475   // backends, both of which rely on reading back the OpenGL frame
1476   // buffer and drawing it with a BufferedImage
1477   class OffscreenBackend implements Backend {
1478     private final AWTGLPixelBufferProvider pixelBufferProvider;
1479     private final boolean useSingletonBuffer;
1480     private AWTGLPixelBuffer pixelBuffer;
1481     private BufferedImage alignedImage;
1482
1483     // One of these is used to store the read back pixels before storing
1484     // in the BufferedImage
1485     protected IntBuffer readBackIntsForCPUVFlip;
1486
1487     // Implementation using software rendering
1488     private volatile GLDrawableImpl offscreenDrawable; // volatile: avoid locking for read-only access
1489     private boolean offscreenIsFBO;
1490     private FBObject fboFlipped;
1491     private GLSLTextureRaster glslTextureRaster;
1492
1493     private volatile GLContextImpl offscreenContext; // volatile: avoid locking for read-only access
1494     private boolean flipVertical;
1495     private int frameCount = 0;
1496
1497     // For saving/restoring of OpenGL state during ReadPixels
1498     private final GLPixelStorageModes psm =  new GLPixelStorageModes();
1499
1500     OffscreenBackend(GLProfile glp, AWTGLPixelBufferProvider custom) {
1501         if(null == custom) {
1502             pixelBufferProvider = getSingleAWTGLPixelBufferProvider();
1503         } else {
1504             pixelBufferProvider = custom;
1505         }
1506         if( pixelBufferProvider instanceof SingletonGLPixelBufferProvider ) {
1507             useSingletonBuffer = true;
1508         } else {
1509             useSingletonBuffer = false;
1510         }
1511     }
1512
1513     @Override
1514     public final boolean isUsingOwnLifecycle() { return false; }
1515
1516     @Override
1517     public final void initialize() {
1518       if(DEBUG) {
1519           System.err.println(getThreadName()+": OffscreenBackend: initialize() - frameCount "+frameCount);
1520       }
1521       try {
1522           final GLContext[] shareWith = { null };
1523           if( helper.isSharedGLContextPending(shareWith) ) {
1524               return; // pending ..
1525           }
1526           offscreenDrawable = (GLDrawableImpl) factory.createOffscreenDrawable(
1527                                                     null /* default platform device */,
1528                                                     offscreenCaps,
1529                                                     chooser,
1530                                                     panelWidth, panelHeight);
1531           updateWrappedSurfaceScale(offscreenDrawable);
1532           offscreenDrawable.setRealized(true);
1533           if( DEBUG ) {
1534               offscreenDrawable.getNativeSurface().addSurfaceUpdatedListener(new SurfaceUpdatedListener() {
1535                   @Override
1536                   public final void surfaceUpdated(Object updater, NativeSurface ns, long when) {
1537                       System.err.println(getThreadName()+": OffscreenBackend.swapBuffers - frameCount "+frameCount);
1538                   } } );
1539           }
1540
1541           offscreenContext = (GLContextImpl) offscreenDrawable.createContext(shareWith[0]);
1542           offscreenContext.setContextCreationFlags(additionalCtxCreationFlags);
1543           if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) {
1544               isInitialized = true;
1545               helper.setAutoSwapBufferMode(false); // we handle swap-buffers, see handlesSwapBuffer()
1546
1547               final GL gl = offscreenContext.getGL();
1548               flipVertical = !GLJPanel.this.skipGLOrientationVerticalFlip && offscreenDrawable.isGLOriented();
1549               final GLCapabilitiesImmutable chosenCaps = offscreenDrawable.getChosenGLCapabilities();
1550               offscreenIsFBO = chosenCaps.isFBO();
1551               final boolean glslCompliant = !offscreenContext.hasRendererQuirk(GLRendererQuirks.GLSLNonCompliant);
1552               final boolean useGLSLFlip = flipVertical && offscreenIsFBO && gl.isGL2ES2() && USE_GLSL_TEXTURE_RASTERIZER && glslCompliant;
1553               if( DEBUG ) {
1554                   System.err.println(getThreadName()+": OffscreenBackend.initialize: useGLSLFlip "+useGLSLFlip+
1555                           " [flip "+flipVertical+", isFBO "+offscreenIsFBO+", isGL2ES2 "+gl.isGL2ES2()+
1556                           ", noglsl "+!USE_GLSL_TEXTURE_RASTERIZER+", glslNonCompliant "+!glslCompliant+
1557                           ", isGL2ES2 " + gl.isGL2ES2()+"]");
1558               }
1559               if( useGLSLFlip ) {
1560                   final GLFBODrawable fboDrawable = (GLFBODrawable) offscreenDrawable;
1561                   fboDrawable.setTextureUnit( GLJPanel.this.requestedTextureUnit );
1562                   try {
1563                       fboFlipped = new FBObject();
1564                       fboFlipped.reset(gl, fboDrawable.getSurfaceWidth(), fboDrawable.getSurfaceHeight(), 0, false);
1565                       fboFlipped.attachTexture2D(gl, 0, chosenCaps.getAlphaBits()>0);
1566                       // fboFlipped.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24);
1567                       glslTextureRaster = new GLSLTextureRaster(fboDrawable.getTextureUnit(), true);
1568                       glslTextureRaster.init(gl.getGL2ES2());
1569                       glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, fboDrawable.getSurfaceWidth(), fboDrawable.getSurfaceHeight());
1570                   } catch (Exception ex) {
1571                       ex.printStackTrace();
1572                       if(null != glslTextureRaster) {
1573                         glslTextureRaster.dispose(gl.getGL2ES2());
1574                         glslTextureRaster = null;
1575                       }
1576                       if(null != fboFlipped) {
1577                         fboFlipped.destroy(gl);
1578                         fboFlipped = null;
1579                       }
1580                   }
1581               } else {
1582                   fboFlipped = null;
1583                   glslTextureRaster = null;
1584               }
1585               offscreenContext.release();
1586           } else {
1587               isInitialized = false;
1588           }
1589       } finally {
1590           if( !isInitialized ) {
1591               if(null != offscreenContext) {
1592                   offscreenContext.destroy();
1593                   offscreenContext = null;
1594               }
1595               if(null != offscreenDrawable) {
1596                   offscreenDrawable.setRealized(false);
1597                   offscreenDrawable = null;
1598               }
1599           }
1600       }
1601     }
1602
1603     @Override
1604     public final void destroy() {
1605       if(DEBUG) {
1606           System.err.println(getThreadName()+": OffscreenBackend: destroy() - offscreenContext: "+(null!=offscreenContext)+" - offscreenDrawable: "+(null!=offscreenDrawable)+" - frameCount "+frameCount);
1607       }
1608       if ( null != offscreenContext && offscreenContext.isCreated() ) {
1609         if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) {
1610             try {
1611                 final GL gl = offscreenContext.getGL();
1612                 if(null != glslTextureRaster) {
1613                     glslTextureRaster.dispose(gl.getGL2ES2());
1614                 }
1615                 if(null != fboFlipped) {
1616                     fboFlipped.destroy(gl);
1617                 }
1618             } finally {
1619                 offscreenContext.destroy();
1620             }
1621         }
1622       }
1623       offscreenContext = null;
1624       glslTextureRaster = null;
1625       fboFlipped = null;
1626       offscreenContext = null;
1627
1628       if (offscreenDrawable != null) {
1629         final AbstractGraphicsDevice adevice = offscreenDrawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice();
1630         offscreenDrawable.setRealized(false);
1631         offscreenDrawable = null;
1632         if(null != adevice) {
1633             adevice.close();
1634         }
1635       }
1636       offscreenIsFBO = false;
1637
1638       if( null != readBackIntsForCPUVFlip ) {
1639           readBackIntsForCPUVFlip.clear();
1640           readBackIntsForCPUVFlip = null;
1641       }
1642       if( null != pixelBuffer ) {
1643           if( !useSingletonBuffer ) {
1644               pixelBuffer.dispose();
1645           }
1646           pixelBuffer = null;
1647       }
1648       alignedImage = null;
1649     }
1650
1651     @Override
1652     public final void setOpaque(boolean opaque) {
1653       if ( opaque != isOpaque() && !useSingletonBuffer ) {
1654           pixelBuffer.dispose();
1655           pixelBuffer = null;
1656           alignedImage = null;
1657       }
1658     }
1659
1660     @Override
1661     public final boolean preGL(Graphics g) {
1662       // Empty in this implementation
1663       return true;
1664     }
1665
1666     @Override
1667     public final boolean handlesSwapBuffer() {
1668         return true;
1669     }
1670
1671     @Override
1672     public final void swapBuffers() {
1673         final GLDrawable d = offscreenDrawable;
1674         if( null != d ) {
1675             d.swapBuffers();
1676         }
1677     }
1678
1679     @Override
1680     public final void postGL(Graphics g, boolean isDisplay) {
1681       if (isDisplay) {
1682         if(DEBUG) {
1683             System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: - frameCount "+frameCount);
1684         }
1685         if( autoSwapBufferMode ) {
1686             // Since we only use a single-buffer non-MSAA or double-buffered MSAA offscreenDrawable,
1687             // we can always swap!
1688             offscreenDrawable.swapBuffers();
1689         }
1690         final GL gl = offscreenContext.getGL();
1691
1692         final int componentCount;
1693         final int alignment;
1694         if( isOpaque() ) {
1695             // w/o alpha
1696             componentCount = 3;
1697             alignment = 1;
1698         } else {
1699             // with alpha
1700             componentCount = 4;
1701             alignment = 4;
1702         }
1703
1704         final GLPixelAttributes pixelAttribs = pixelBufferProvider.getAttributes(gl, componentCount);
1705
1706         if( useSingletonBuffer ) { // attempt to fetch the latest AWTGLPixelBuffer
1707             pixelBuffer = (AWTGLPixelBuffer) ((SingletonGLPixelBufferProvider)pixelBufferProvider).getSingleBuffer(pixelAttribs);
1708         }
1709         if( null != pixelBuffer && pixelBuffer.requiresNewBuffer(gl, panelWidth, panelHeight, 0) ) {
1710             pixelBuffer.dispose();
1711             pixelBuffer = null;
1712             alignedImage = null;
1713         }
1714         if ( null == pixelBuffer ) {
1715           if (0 >= panelWidth || 0 >= panelHeight ) {
1716               return;
1717           }
1718           pixelBuffer = pixelBufferProvider.allocate(gl, pixelAttribs, panelWidth, panelHeight, 1, true, 0);
1719           if(DEBUG) {
1720               System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" pixelBufferProvider isSingletonBufferProvider "+useSingletonBuffer+", 0x"+Integer.toHexString(pixelBufferProvider.hashCode())+", "+pixelBufferProvider.getClass().getSimpleName());
1721               System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" pixelBuffer 0x"+Integer.toHexString(pixelBuffer.hashCode())+", "+pixelBuffer+", alignment "+alignment);
1722               System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" flippedVertical "+flipVertical+", glslTextureRaster "+(null!=glslTextureRaster));
1723               System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: "+GLJPanel.this.getName()+" panelSize "+panelWidth+"x"+panelHeight+" @ scale "+getPixelScaleStr());
1724           }
1725         }
1726         if( offscreenDrawable.getSurfaceWidth() != panelWidth || offscreenDrawable.getSurfaceHeight() != panelHeight ) {
1727             throw new InternalError("OffscreenDrawable panelSize mismatch (reshape missed): panelSize "+panelWidth+"x"+panelHeight+" != drawable "+offscreenDrawable.getSurfaceWidth()+"x"+offscreenDrawable.getSurfaceHeight()+", on thread "+getThreadName());
1728         }
1729         if( null == alignedImage ||
1730             panelWidth != alignedImage.getWidth() || panelHeight != alignedImage.getHeight() ||
1731             !pixelBuffer.isDataBufferSource(alignedImage) ) {
1732             alignedImage = pixelBuffer.getAlignedImage(panelWidth, panelHeight);
1733             if(DEBUG) {
1734                 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);
1735             }
1736         }
1737         final IntBuffer readBackInts;
1738
1739         if( !flipVertical || null != glslTextureRaster ) {
1740            readBackInts = (IntBuffer) pixelBuffer.buffer;
1741         } else {
1742            if( null == readBackIntsForCPUVFlip || pixelBuffer.width * pixelBuffer.height > readBackIntsForCPUVFlip.remaining() ) {
1743                readBackIntsForCPUVFlip = IntBuffer.allocate(pixelBuffer.width * pixelBuffer.height);
1744            }
1745            readBackInts = readBackIntsForCPUVFlip;
1746         }
1747
1748         final TextureState usrTexState, fboTexState;
1749         final int fboTexUnit = GL.GL_TEXTURE0 + ( offscreenIsFBO ? ((GLFBODrawable)offscreenDrawable).getTextureUnit() : 0 );
1750
1751         if( offscreenIsFBO ) {
1752             usrTexState = new TextureState(gl, GL.GL_TEXTURE_2D);
1753             if( fboTexUnit != usrTexState.getUnit() ) {
1754                 // glActiveTexture(..) + glBindTexture(..) are implicit performed in GLFBODrawableImpl's
1755                 // swapBuffers/contextMadeCurent -> swapFBOImpl.
1756                 // We need to cache the texture unit's bound texture-id before it's overwritten.
1757                 gl.glActiveTexture(fboTexUnit);
1758                 fboTexState = new TextureState(gl, fboTexUnit, GL.GL_TEXTURE_2D);
1759             } else {
1760                 fboTexState = usrTexState;
1761             }
1762         } else {
1763             usrTexState = null;
1764             fboTexState = null;
1765         }
1766
1767         // Must now copy pixels from offscreen context into surface
1768         if(DEBUG) {
1769             System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.readPixels: - frameCount "+frameCount);
1770         }
1771
1772         // Save PACK modes, reset them to defaults and set alignment
1773         psm.setPackAlignment(gl, alignment);
1774         if(gl.isGL2ES3()) {
1775             final GL2ES3 gl2es3 = gl.getGL2ES3();
1776             gl2es3.glPixelStorei(GL2ES3.GL_PACK_ROW_LENGTH, panelWidth);
1777             gl2es3.glReadBuffer(gl2es3.getDefaultReadBuffer());
1778         }
1779
1780         if(null != glslTextureRaster) { // implies flippedVertical
1781             final boolean viewportChange;
1782             final int[] usrViewport = new int[] { 0, 0, 0, 0 };
1783             gl.glGetIntegerv(GL.GL_VIEWPORT, usrViewport, 0);
1784             viewportChange = 0 != usrViewport[0] || 0 != usrViewport[1] ||
1785                              offscreenDrawable.getSurfaceWidth() != usrViewport[2] || offscreenDrawable.getSurfaceHeight() != usrViewport[3];
1786             if( DEBUG_VIEWPORT ) {
1787                 System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL: "+GLJPanel.this.getName()+" Viewport: change "+viewportChange+
1788                          ", "+usrViewport[0]+"/"+usrViewport[1]+" "+usrViewport[2]+"x"+usrViewport[3]+
1789                          " -> 0/0 "+offscreenDrawable.getSurfaceWidth()+"x"+offscreenDrawable.getSurfaceHeight());
1790             }
1791             if( viewportChange ) {
1792                 gl.glViewport(0, 0, offscreenDrawable.getSurfaceWidth(), offscreenDrawable.getSurfaceHeight());
1793             }
1794
1795             // perform vert-flipping via OpenGL/FBO
1796             final GLFBODrawable fboDrawable = (GLFBODrawable)offscreenDrawable;
1797             final FBObject.TextureAttachment fboTex = fboDrawable.getTextureBuffer(GL.GL_FRONT);
1798
1799             fboFlipped.bind(gl);
1800
1801             // gl.glActiveTexture(GL.GL_TEXTURE0 + fboDrawable.getTextureUnit()); // implicit by GLFBODrawableImpl: swapBuffers/contextMadeCurent -> swapFBOImpl
1802             gl.glBindTexture(GL.GL_TEXTURE_2D, fboTex.getName());
1803             // gl.glClear(GL.GL_DEPTH_BUFFER_BIT); // fboFlipped runs w/o DEPTH!
1804
1805             glslTextureRaster.display(gl.getGL2ES2());
1806             gl.glReadPixels(0, 0, panelWidth, panelHeight, pixelAttribs.format, pixelAttribs.type, readBackInts);
1807
1808             fboFlipped.unbind(gl);
1809             if( viewportChange ) {
1810                 gl.glViewport(usrViewport[0], usrViewport[1], usrViewport[2], usrViewport[3]);
1811             }
1812             fboTexState.restore(gl);
1813         } else {
1814             gl.glReadPixels(0, 0, panelWidth, panelHeight, pixelAttribs.format, pixelAttribs.type, readBackInts);
1815
1816             if ( flipVertical ) {
1817                 // Copy temporary data into raster of BufferedImage for faster
1818                 // blitting Note that we could avoid this copy in the cases
1819                 // where !offscreenDrawable.isGLOriented(),
1820                 // but that's the software rendering path which is very slow anyway.
1821                 final BufferedImage image = alignedImage;
1822                 final int[] src = readBackInts.array();
1823                 final int[] dest = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
1824                 final int incr = panelWidth;
1825                 int srcPos = 0;
1826                 int destPos = (panelHeight - 1) * panelWidth;
1827                 for (; destPos >= 0; srcPos += incr, destPos -= incr) {
1828                   System.arraycopy(src, srcPos, dest, destPos, incr);
1829                 }
1830             }
1831         }
1832         if( offscreenIsFBO && fboTexUnit != usrTexState.getUnit() ) {
1833             usrTexState.restore(gl);
1834         }
1835
1836         // Restore saved modes.
1837         psm.restore(gl);
1838
1839         // Note: image will be drawn back in paintComponent() for
1840         // correctness on all platforms
1841       }
1842     }
1843
1844     @Override
1845     public final int getTextureUnit() {
1846         if(null != glslTextureRaster && null != offscreenDrawable) { // implies flippedVertical
1847             return ((GLFBODrawable)offscreenDrawable).getTextureUnit();
1848         }
1849         return -1;
1850     }
1851
1852     @Override
1853     public final void doPaintComponent(Graphics g) {
1854       helper.invokeGL(offscreenDrawable, offscreenContext, updaterDisplayAction, updaterInitAction);
1855
1856       if ( null != alignedImage ) {
1857         if( DEBUG ) {
1858             System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.doPaintComponent.drawImage: - frameCount "+frameCount);
1859         }
1860         // Draw resulting image in one shot
1861         g.drawImage(alignedImage, 0, 0, alignedImage.getWidth()/hasPixelScale[0], alignedImage.getHeight()/hasPixelScale[1], null); // Null ImageObserver since image data is ready.
1862       }
1863       frameCount++;
1864     }
1865
1866     @Override
1867     public final void doPlainPaint() {
1868       helper.invokeGL(offscreenDrawable, offscreenContext, updaterPlainDisplayAction, updaterInitAction);
1869     }
1870
1871     @Override
1872     public final boolean handleReshape() {
1873         GLDrawableImpl _drawable = offscreenDrawable;
1874         {
1875             final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, offscreenContext, panelWidth, panelHeight);
1876             if(_drawable != _drawableNew) {
1877                 // write back
1878                 _drawable = _drawableNew;
1879                 offscreenDrawable = _drawableNew;
1880                 updateWrappedSurfaceScale(offscreenDrawable);
1881             }
1882         }
1883         if (DEBUG) {
1884             System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.handleReshape: " +panelWidth+"x"+panelHeight + " @ scale "+getPixelScaleStr() + " -> " + _drawable.getSurfaceWidth()+"x"+_drawable.getSurfaceHeight());
1885         }
1886         panelWidth = _drawable.getSurfaceWidth();
1887         panelHeight = _drawable.getSurfaceHeight();
1888
1889         if( null != glslTextureRaster ) {
1890             if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) {
1891                 try {
1892                     final GL gl = offscreenContext.getGL();
1893                     fboFlipped.reset(gl, _drawable.getSurfaceWidth(), _drawable.getSurfaceHeight(), 0, false);
1894                     glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, _drawable.getSurfaceWidth(), _drawable.getSurfaceHeight());
1895                 } finally {
1896                     offscreenContext.release();
1897                 }
1898             }
1899         }
1900         return _drawable.isRealized();
1901     }
1902
1903     @Override
1904     public final GLContext createContext(GLContext shareWith) {
1905       return (null != offscreenDrawable) ? offscreenDrawable.createContext(shareWith) : null;
1906     }
1907
1908     @Override
1909     public final void setContext(GLContext ctx) {
1910       offscreenContext=(GLContextImpl)ctx;
1911     }
1912
1913     @Override
1914     public final GLContext getContext() {
1915       return offscreenContext;
1916     }
1917
1918     @Override
1919     public final GLDrawable getDrawable() {
1920         return offscreenDrawable;
1921     }
1922
1923     @Override
1924     public final GLCapabilitiesImmutable getChosenGLCapabilities() {
1925       if (offscreenDrawable == null) {
1926         return null;
1927       }
1928       return offscreenDrawable.getChosenGLCapabilities();
1929     }
1930
1931     @Override
1932     public final GLProfile getGLProfile() {
1933       if (offscreenDrawable == null) {
1934         return null;
1935       }
1936       return offscreenDrawable.getGLProfile();
1937     }
1938   }
1939
1940   class J2DOGLBackend implements Backend {
1941     // Opaque Object identifier representing the Java2D surface we are
1942     // drawing to; used to determine when to destroy and recreate JOGL
1943     // context
1944     private Object j2dSurface;
1945     // Graphics object being used during Java2D update action
1946     // (absolutely essential to cache this)
1947     // No-op context representing the Java2D OpenGL context
1948     private GLContext j2dContext;
1949     // Context associated with no-op drawable representing the JOGL
1950     // OpenGL context
1951     private GLDrawable joglDrawable;
1952     // The real OpenGL context JOGL uses to render
1953     private GLContext  joglContext;
1954     // State captured from Java2D OpenGL context necessary in order to
1955     // properly render into Java2D back buffer
1956     private final int[] drawBuffer   = new int[1];
1957     private final int[] readBuffer   = new int[1];
1958     // This is required when the FBO option of the Java2D / OpenGL
1959     // pipeline is active
1960     private final int[] frameBuffer  = new int[1];
1961     // Current (as of this writing) NVidia drivers have a couple of bugs
1962     // relating to the sharing of framebuffer and renderbuffer objects
1963     // between contexts. It appears we have to (a) reattach the color
1964     // attachment and (b) actually create new depth buffer storage and
1965     // attach it in order for the FBO to behave properly in our context.
1966     private boolean checkedForFBObjectWorkarounds;
1967     private boolean fbObjectWorkarounds;
1968     private int[] frameBufferDepthBuffer;
1969     private int[] frameBufferTexture;
1970     private boolean createNewDepthBuffer;
1971     // Current (as of this writing) ATI drivers have problems when the
1972     // same FBO is bound in two different contexts. Here we check for
1973     // this case and explicitly release the FBO from Java2D's context
1974     // before switching to ours. Java2D will re-bind the FBO when it
1975     // makes its context current the next time. Interestingly, if we run
1976     // this code path on NVidia hardware, it breaks the rendering
1977     // results -- no output is generated. This doesn't appear to be an
1978     // interaction with the abovementioned NVidia-specific workarounds,
1979     // as even if we disable that code the FBO is still reported as
1980     // incomplete in our context.
1981     private boolean checkedGLVendor;
1982     private boolean vendorIsATI;
1983
1984     // Holding on to this GraphicsConfiguration is a workaround for a
1985     // problem in the Java 2D / JOGL bridge when FBOs are enabled; see
1986     // comment related to Issue 274 below
1987     private GraphicsConfiguration workaroundConfig;
1988
1989     @Override
1990     public final boolean isUsingOwnLifecycle() { return true; }
1991
1992     @Override
1993     public final void initialize() {
1994       if(DEBUG) {
1995           System.err.println(getThreadName()+": J2DOGL: initialize()");
1996       }
1997       // No-op in this implementation; everything is done lazily
1998       isInitialized = true;
1999     }
2000
2001     @Override
2002     public final void destroy() {
2003       Java2D.invokeWithOGLContextCurrent(null, new Runnable() {
2004           @Override
2005           public void run() {
2006             if(DEBUG) {
2007                 System.err.println(getThreadName()+": J2DOGL: destroy() - joglContext: "+(null!=joglContext)+" - joglDrawable: "+(null!=joglDrawable));
2008             }
2009             if (joglContext != null) {
2010               joglContext.destroy();
2011               joglContext = null;
2012             }
2013             joglDrawable = null;
2014             if (j2dContext != null) {
2015               j2dContext.destroy();
2016               j2dContext = null;
2017             }
2018           }
2019         });
2020     }
2021
2022     @Override
2023     public final void setOpaque(boolean opaque) {
2024       // Empty in this implementation
2025     }
2026
2027     @Override
2028     public final GLContext createContext(GLContext shareWith) {
2029       if(null != shareWith) {
2030           throw new GLException("J2DOGLBackend cannot create context w/ additional shared context, since it already needs to share the context w/ J2D.");
2031       }
2032       return (null != joglDrawable && null != j2dContext) ? joglDrawable.createContext(j2dContext) : null;
2033     }
2034
2035     @Override
2036     public final void setContext(GLContext ctx) {
2037         joglContext=ctx;
2038     }
2039
2040     @Override
2041     public final GLContext getContext() {
2042       return joglContext;
2043     }
2044
2045     @Override
2046     public final GLDrawable getDrawable() {
2047         return joglDrawable;
2048     }
2049
2050     @Override
2051     public final int getTextureUnit() { return -1; }
2052
2053     @Override
2054     public final GLCapabilitiesImmutable getChosenGLCapabilities() {
2055       // FIXME: should do better than this; is it possible to query J2D Caps ?
2056       return new GLCapabilities(null);
2057     }
2058
2059     @Override
2060     public final GLProfile getGLProfile() {
2061       // FIXME: should do better than this; is it possible to query J2D's Profile ?
2062       return GLProfile.getDefault(GLProfile.getDefaultDevice());
2063     }
2064
2065     @Override
2066     public final boolean handleReshape() {
2067       // Empty in this implementation
2068       return true;
2069     }
2070
2071     @Override
2072     public final boolean preGL(Graphics g) {
2073       final GL2 gl = joglContext.getGL().getGL2();
2074       // Set up needed state in JOGL context from Java2D context
2075       gl.glEnable(GL2.GL_SCISSOR_TEST);
2076       Rectangle r = Java2D.getOGLScissorBox(g);
2077
2078       if (r == null) {
2079         if (DEBUG) {
2080           System.err.println(getThreadName()+": Java2D.getOGLScissorBox() returned null");
2081         }
2082         return false;
2083       }
2084       if (DEBUG) {
2085         System.err.println(getThreadName()+": GLJPanel: gl.glScissor(" + r.x + ", " + r.y + ", " + r.width + ", " + r.height + ")");
2086       }
2087
2088       gl.glScissor(r.x, r.y, r.width, r.height);
2089       Rectangle oglViewport = Java2D.getOGLViewport(g, panelWidth, panelHeight);
2090       // If the viewport X or Y changes, in addition to the panel's
2091       // width or height, we need to send a reshape operation to the
2092       // client
2093       if ((viewportX != oglViewport.x) ||
2094           (viewportY != oglViewport.y)) {
2095         sendReshape = true;
2096         if (DEBUG) {
2097           System.err.println(getThreadName()+": Sending reshape because viewport changed");
2098           System.err.println("  viewportX (" + viewportX + ") ?= oglViewport.x (" + oglViewport.x + ")");
2099           System.err.println("  viewportY (" + viewportY + ") ?= oglViewport.y (" + oglViewport.y + ")");
2100         }
2101       }
2102       viewportX = oglViewport.x;
2103       viewportY = oglViewport.y;
2104
2105       // If the FBO option is active, bind to the FBO from the Java2D
2106       // context.
2107       // Note that all of the plumbing in the context sharing stuff will
2108       // allow us to bind to this object since it's in our namespace.
2109       if (Java2D.isFBOEnabled() &&
2110           Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
2111
2112         // The texture target for Java2D's OpenGL pipeline when using FBOs
2113         // -- either GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE_ARB
2114         int fboTextureTarget = Java2D.getOGLTextureType(g);
2115
2116         if (!checkedForFBObjectWorkarounds) {
2117           checkedForFBObjectWorkarounds = true;
2118           gl.glBindTexture(fboTextureTarget, 0);
2119           gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, frameBuffer[0]);
2120           int status = gl.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER);
2121           if (status != GL.GL_FRAMEBUFFER_COMPLETE) {
2122               // Need to do workarounds
2123               fbObjectWorkarounds = true;
2124               createNewDepthBuffer = true;
2125               if (DEBUG) {
2126                   System.err.println(getThreadName()+": GLJPanel: ERR GL_FRAMEBUFFER_BINDING: Discovered Invalid J2D FBO("+frameBuffer[0]+"): "+FBObject.getStatusString(status) +
2127                                      ", frame_buffer_object workarounds to be necessary");
2128               }
2129           } else {
2130             // Don't need the frameBufferTexture temporary any more
2131             frameBufferTexture = null;
2132             if (DEBUG) {
2133               System.err.println(getThreadName()+": GLJPanel: OK GL_FRAMEBUFFER_BINDING: "+frameBuffer[0]);
2134             }
2135           }
2136         }
2137
2138         if (fbObjectWorkarounds && createNewDepthBuffer) {
2139           if (frameBufferDepthBuffer == null)
2140             frameBufferDepthBuffer = new int[1];
2141
2142           // Create our own depth renderbuffer and associated storage
2143           // If we have an old one, delete it
2144           if (frameBufferDepthBuffer[0] != 0) {
2145             gl.glDeleteRenderbuffers(1, frameBufferDepthBuffer, 0);
2146             frameBufferDepthBuffer[0] = 0;
2147           }
2148
2149           gl.glBindTexture(fboTextureTarget, frameBufferTexture[0]);
2150           int[] width = new int[1];
2151           int[] height = new int[1];
2152           gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_WIDTH, width, 0);
2153           gl.glGetTexLevelParameteriv(fboTextureTarget, 0, GL2.GL_TEXTURE_HEIGHT, height, 0);
2154
2155           gl.glGenRenderbuffers(1, frameBufferDepthBuffer, 0);
2156           if (DEBUG) {
2157             System.err.println(getThreadName()+": GLJPanel: Generated frameBufferDepthBuffer " + frameBufferDepthBuffer[0] +
2158                                " with width " + width[0] + ", height " + height[0]);
2159           }
2160
2161           gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, frameBufferDepthBuffer[0]);
2162           // FIXME: may need a loop here like in Java2D
2163           gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL2GL3.GL_DEPTH_COMPONENT24, width[0], height[0]);
2164
2165           gl.glBindRenderbuffer(GL2.GL_RENDERBUFFER, 0);
2166           createNewDepthBuffer = false;
2167         }
2168
2169         gl.glBindTexture(fboTextureTarget, 0);
2170         gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, frameBuffer[0]);
2171
2172         if (fbObjectWorkarounds) {
2173           // Hook up the color and depth buffer attachment points for this framebuffer
2174           gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER,
2175                                     GL.GL_COLOR_ATTACHMENT0,
2176                                     fboTextureTarget,
2177                                     frameBufferTexture[0],
2178                                     0);
2179           if (DEBUG) {
2180             System.err.println(getThreadName()+": GLJPanel: frameBufferDepthBuffer: " + frameBufferDepthBuffer[0]);
2181           }
2182           gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER,
2183                                        GL.GL_DEPTH_ATTACHMENT,
2184                                        GL.GL_RENDERBUFFER,
2185                                        frameBufferDepthBuffer[0]);
2186         }
2187
2188         if (DEBUG) {
2189           int status = gl.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER);
2190           if (status != GL.GL_FRAMEBUFFER_COMPLETE) {
2191             throw new GLException("Error: framebuffer was incomplete: status = 0x" +
2192                                   Integer.toHexString(status));
2193           }
2194         }
2195       } else {
2196         if (DEBUG) {
2197           System.err.println(getThreadName()+": GLJPanel: Setting up drawBuffer " + drawBuffer[0] +
2198                              " and readBuffer " + readBuffer[0]);
2199         }
2200
2201         gl.glDrawBuffer(drawBuffer[0]);
2202         gl.glReadBuffer(readBuffer[0]);
2203       }
2204
2205       return true;
2206     }
2207
2208     @Override
2209     public final boolean handlesSwapBuffer() {
2210         return false;
2211     }
2212
2213     @Override
2214     public final void swapBuffers() {
2215         final GLDrawable d = joglDrawable;
2216         if( null != d ) {
2217             d.swapBuffers();
2218         }
2219     }
2220
2221     @Override
2222     public final void postGL(Graphics g, boolean isDisplay) {
2223       // Cause OpenGL pipeline to flush its results because
2224       // otherwise it's possible we will buffer up multiple frames'
2225       // rendering results, resulting in apparent mouse lag
2226       GL gl = joglContext.getGL();
2227       gl.glFinish();
2228
2229       if (Java2D.isFBOEnabled() &&
2230           Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
2231         // Unbind the framebuffer from our context to work around
2232         // apparent driver bugs or at least unspecified behavior causing
2233         // OpenGL to run out of memory with certain cards and drivers
2234         gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
2235       }
2236     }
2237
2238     @Override
2239     public final void doPaintComponent(final Graphics g) {
2240       // This is a workaround for an issue in the Java 2D / JOGL
2241       // bridge (reported by an end user as JOGL Issue 274) where Java
2242       // 2D can occasionally leave its internal OpenGL context current
2243       // to the on-screen window rather than its internal "scratch"
2244       // pbuffer surface to which the FBO is attached. JOGL expects to
2245       // find a stable OpenGL drawable (on Windows, an HDC) upon which
2246       // it can create another OpenGL context. It turns out that, on
2247       // Windows, when Java 2D makes its internal OpenGL context
2248       // current against the window in order to put pixels on the
2249       // screen, it gets the device context for the window, makes its
2250       // context current, and releases the device context. This means
2251       // that when JOGL's Runnable gets to run below, the HDC is
2252       // already invalid. The workaround for this is to force Java 2D
2253       // to make its context current to the scratch surface, which we
2254       // can do by executing an empty Runnable with the "shared"
2255       // context current. This will be fixed in a Java SE 6 update
2256       // release, hopefully 6u2.
2257       if (Java2D.isFBOEnabled()) {
2258         if (workaroundConfig == null) {
2259           workaroundConfig = GraphicsEnvironment.
2260             getLocalGraphicsEnvironment().
2261             getDefaultScreenDevice().
2262             getDefaultConfiguration();
2263         }
2264         Java2D.invokeWithOGLSharedContextCurrent(workaroundConfig, new Runnable() { @Override
2265         public void run() {}});
2266       }
2267
2268       Java2D.invokeWithOGLContextCurrent(g, new Runnable() {
2269           @Override
2270           public void run() {
2271             if (DEBUG) {
2272               System.err.println(getThreadName()+": GLJPanel.invokeWithOGLContextCurrent");
2273             }
2274
2275             // Create no-op context representing Java2D context
2276             if (j2dContext == null) {
2277               j2dContext = factory.createExternalGLContext();
2278               if (DEBUG) {
2279                 System.err.println(getThreadName()+": GLJPanel.Created External Context: "+j2dContext);
2280               }
2281               if (DEBUG) {
2282 //                j2dContext.setGL(new DebugGL2(j2dContext.getGL().getGL2()));
2283               }
2284
2285               // Check to see whether we can support the requested
2286               // capabilities or need to fall back to a pbuffer
2287               // FIXME: add more checks?
2288
2289               j2dContext.makeCurrent();
2290               GL gl = j2dContext.getGL();
2291               if ((getGLInteger(gl, GL.GL_RED_BITS)         < offscreenCaps.getRedBits())        ||
2292                   (getGLInteger(gl, GL.GL_GREEN_BITS)       < offscreenCaps.getGreenBits())      ||
2293                   (getGLInteger(gl, GL.GL_BLUE_BITS)        < offscreenCaps.getBlueBits())       ||
2294                   //                  (getGLInteger(gl, GL.GL_ALPHA_BITS)       < offscreenCaps.getAlphaBits())      ||
2295                   (getGLInteger(gl, GL2.GL_ACCUM_RED_BITS)   < offscreenCaps.getAccumRedBits())   ||
2296                   (getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) < offscreenCaps.getAccumGreenBits()) ||
2297                   (getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS)  < offscreenCaps.getAccumBlueBits())  ||
2298                   (getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) < offscreenCaps.getAccumAlphaBits()) ||
2299                   //          (getGLInteger(gl, GL2.GL_DEPTH_BITS)       < offscreenCaps.getDepthBits())      ||
2300                   (getGLInteger(gl, GL.GL_STENCIL_BITS)     < offscreenCaps.getStencilBits())) {
2301                 if (DEBUG) {
2302                   System.err.println(getThreadName()+": GLJPanel: Falling back to pbuffer-based support because Java2D context insufficient");
2303                   System.err.println("                    Available              Required");
2304                   System.err.println("GL_RED_BITS         " + getGLInteger(gl, GL.GL_RED_BITS)         + "              " + offscreenCaps.getRedBits());
2305                   System.err.println("GL_GREEN_BITS       " + getGLInteger(gl, GL.GL_GREEN_BITS)       + "              " + offscreenCaps.getGreenBits());
2306                   System.err.println("GL_BLUE_BITS        " + getGLInteger(gl, GL.GL_BLUE_BITS)        + "              " + offscreenCaps.getBlueBits());
2307                   System.err.println("GL_ALPHA_BITS       " + getGLInteger(gl, GL.GL_ALPHA_BITS)       + "              " + offscreenCaps.getAlphaBits());
2308                   System.err.println("GL_ACCUM_RED_BITS   " + getGLInteger(gl, GL2.GL_ACCUM_RED_BITS)   + "              " + offscreenCaps.getAccumRedBits());
2309                   System.err.println("GL_ACCUM_GREEN_BITS " + getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) + "              " + offscreenCaps.getAccumGreenBits());
2310                   System.err.println("GL_ACCUM_BLUE_BITS  " + getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS)  + "              " + offscreenCaps.getAccumBlueBits());
2311                   System.err.println("GL_ACCUM_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) + "              " + offscreenCaps.getAccumAlphaBits());
2312                   System.err.println("GL_DEPTH_BITS       " + getGLInteger(gl, GL.GL_DEPTH_BITS)       + "              " + offscreenCaps.getDepthBits());
2313                   System.err.println("GL_STENCIL_BITS     " + getGLInteger(gl, GL.GL_STENCIL_BITS)     + "              " + offscreenCaps.getStencilBits());
2314                 }
2315                 isInitialized = false;
2316                 backend = null;
2317                 java2DGLPipelineOK = false;
2318                 handleReshape = true;
2319                 j2dContext.destroy();
2320                 j2dContext = null;
2321                 return;
2322               }
2323             } else {
2324               j2dContext.makeCurrent();
2325             }
2326             try {
2327               captureJ2DState(j2dContext.getGL(), g);
2328               Object curSurface = Java2D.getOGLSurfaceIdentifier(g);
2329               if (curSurface != null) {
2330                 if (j2dSurface != curSurface) {
2331                   if (joglContext != null) {
2332                     joglContext.destroy();
2333                     joglContext = null;
2334                     joglDrawable = null;
2335                     sendReshape = true;
2336                     if (DEBUG) {
2337                       System.err.println(getThreadName()+": Sending reshape because surface changed");
2338                       System.err.println("New surface = " + curSurface);
2339                     }
2340                   }
2341                   j2dSurface = curSurface;
2342                   if (DEBUG) {
2343                       System.err.print(getThreadName()+": Surface type: ");
2344                       int surfaceType = Java2D.getOGLSurfaceType(g);
2345                       if (surfaceType == Java2D.UNDEFINED) {
2346                         System.err.println("UNDEFINED");
2347                       } else if (surfaceType == Java2D.WINDOW) {
2348                         System.err.println("WINDOW");
2349                       } else if (surfaceType == Java2D.PBUFFER) {
2350                         System.err.println("PBUFFER");
2351                       } else if (surfaceType == Java2D.TEXTURE) {
2352                         System.err.println("TEXTURE");
2353                       } else if (surfaceType == Java2D.FLIP_BACKBUFFER) {
2354                         System.err.println("FLIP_BACKBUFFER");
2355                       } else if (surfaceType == Java2D.FBOBJECT) {
2356                         System.err.println("FBOBJECT");
2357                       } else {
2358                         System.err.println("(Unknown surface type " + surfaceType + ")");
2359                       }
2360                   }
2361                 }
2362                 if (joglContext == null) {
2363                   AbstractGraphicsDevice device = j2dContext.getGLDrawable().getNativeSurface().getGraphicsConfiguration().getScreen().getDevice();
2364                   if (factory.canCreateExternalGLDrawable(device)) {
2365                     joglDrawable = factory.createExternalGLDrawable();
2366                     joglContext = joglDrawable.createContext(j2dContext);
2367                     if (DEBUG) {
2368                         System.err.println("-- Created External Drawable: "+joglDrawable);
2369                         System.err.println("-- Created Context: "+joglContext);
2370                     }
2371                   }
2372                   if (Java2D.isFBOEnabled() &&
2373                       Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT &&
2374                       fbObjectWorkarounds) {
2375                     createNewDepthBuffer = true;
2376                   }
2377                 }
2378                 helper.invokeGL(joglDrawable, joglContext, updaterDisplayAction, updaterInitAction);
2379               }
2380             } finally {
2381               j2dContext.release();
2382             }
2383           }
2384         });
2385     }
2386
2387     @Override
2388     public final void doPlainPaint() {
2389       helper.invokeGL(joglDrawable, joglContext, updaterPlainDisplayAction, updaterInitAction);
2390     }
2391
2392     private final void captureJ2DState(GL gl, Graphics g) {
2393       gl.glGetIntegerv(GL2.GL_DRAW_BUFFER, drawBuffer, 0);
2394       gl.glGetIntegerv(GL2.GL_READ_BUFFER, readBuffer, 0);
2395       if (Java2D.isFBOEnabled() &&
2396           Java2D.getOGLSurfaceType(g) == Java2D.FBOBJECT) {
2397         gl.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, frameBuffer, 0);
2398         if(!gl.glIsFramebuffer(frameBuffer[0])) {
2399           checkedForFBObjectWorkarounds=true;
2400           fbObjectWorkarounds = true;
2401           createNewDepthBuffer = true;
2402           if (DEBUG) {
2403               System.err.println(getThreadName()+": GLJPanel: Fetched ERR GL_FRAMEBUFFER_BINDING: "+frameBuffer[0]+" - NOT A FBO"+
2404                                  ", frame_buffer_object workarounds to be necessary");
2405           }
2406         } else if (DEBUG) {
2407           System.err.println(getThreadName()+": GLJPanel: Fetched OK GL_FRAMEBUFFER_BINDING: "+frameBuffer[0]);
2408         }
2409
2410         if(fbObjectWorkarounds || !checkedForFBObjectWorkarounds) {
2411             // See above for description of what we are doing here
2412             if (frameBufferTexture == null)
2413                 frameBufferTexture = new int[1];
2414
2415             // Query the framebuffer for its color buffer so we can hook
2416             // it back up in our context (should not be necessary)
2417             gl.glGetFramebufferAttachmentParameteriv(GL.GL_FRAMEBUFFER,
2418                                                      GL.GL_COLOR_ATTACHMENT0,
2419                                                      GL.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
2420                                                      frameBufferTexture, 0);
2421             if (DEBUG) {
2422                 System.err.println(getThreadName()+": GLJPanel: FBO COLOR_ATTACHMENT0: " + frameBufferTexture[0]);
2423             }
2424         }
2425
2426         if (!checkedGLVendor) {
2427           checkedGLVendor = true;
2428           String vendor = gl.glGetString(GL.GL_VENDOR);
2429
2430           if ((vendor != null) &&
2431               vendor.startsWith("ATI")) {
2432             vendorIsATI = true;
2433           }
2434         }
2435
2436         if (vendorIsATI) {
2437           // Unbind the FBO from Java2D's context as it appears that
2438           // driver bugs on ATI's side are causing problems if the FBO is
2439           // simultaneously bound to more than one context. Java2D will
2440           // re-bind the FBO during the next validation of its context.
2441           // Note: this breaks rendering at least on NVidia hardware
2442           gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
2443         }
2444       }
2445     }
2446   }
2447 }
http://JogAmp.org git info: FAQ, tutorial and man pages.