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