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