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 com.jogamp.opengl.awt;
42 
43 import java.beans.Beans;
44 import java.lang.reflect.Method;
45 import java.security.AccessController;
46 import java.security.PrivilegedAction;
47 import java.awt.Canvas;
48 import java.awt.Color;
49 import java.awt.FontMetrics;
50 import java.awt.Frame;
51 import java.awt.Graphics;
52 import java.awt.Graphics2D;
53 import java.awt.GraphicsConfiguration;
54 import java.awt.GraphicsDevice;
55 import java.awt.event.HierarchyEvent;
56 import java.awt.event.HierarchyListener;
57 import java.awt.geom.NoninvertibleTransformException;
58 import java.awt.geom.Rectangle2D;
59 import java.awt.EventQueue;
60 import java.lang.reflect.InvocationTargetException;
61 import java.util.ArrayList;
62 import java.util.List;
63 
64 import com.jogamp.nativewindow.AbstractGraphicsConfiguration;
65 import com.jogamp.nativewindow.OffscreenLayerOption;
66 import com.jogamp.nativewindow.ScalableSurface;
67 import com.jogamp.nativewindow.VisualIDHolder;
68 import com.jogamp.nativewindow.WindowClosingProtocol;
69 import com.jogamp.nativewindow.AbstractGraphicsDevice;
70 import com.jogamp.nativewindow.AbstractGraphicsScreen;
71 import com.jogamp.nativewindow.GraphicsConfigurationFactory;
72 import com.jogamp.nativewindow.NativeSurface;
73 import com.jogamp.nativewindow.NativeWindowFactory;
74 import com.jogamp.opengl.GL;
75 import com.jogamp.opengl.GLAnimatorControl;
76 import com.jogamp.opengl.GLAutoDrawable;
77 import com.jogamp.opengl.GLCapabilities;
78 import com.jogamp.opengl.GLCapabilitiesChooser;
79 import com.jogamp.opengl.GLCapabilitiesImmutable;
80 import com.jogamp.opengl.GLContext;
81 import com.jogamp.opengl.GLDrawable;
82 import com.jogamp.opengl.GLDrawableFactory;
83 import com.jogamp.opengl.GLEventListener;
84 import com.jogamp.opengl.GLException;
85 import com.jogamp.opengl.GLOffscreenAutoDrawable;
86 import com.jogamp.opengl.GLProfile;
87 import com.jogamp.opengl.GLRunnable;
88 import com.jogamp.opengl.GLSharedContextSetter;
89 import com.jogamp.opengl.Threading;
90 
91 import com.jogamp.common.GlueGenVersion;
92 import com.jogamp.common.util.VersionUtil;
93 import com.jogamp.common.util.awt.AWTEDTExecutor;
94 import com.jogamp.common.util.locks.LockFactory;
95 import com.jogamp.common.util.locks.RecursiveLock;
96 import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration;
97 import com.jogamp.nativewindow.awt.AWTGraphicsDevice;
98 import com.jogamp.nativewindow.awt.AWTGraphicsScreen;
99 import com.jogamp.nativewindow.awt.AWTPrintLifecycle;
100 import com.jogamp.nativewindow.awt.AWTWindowClosingProtocol;
101 import com.jogamp.nativewindow.awt.JAWTWindow;
102 import com.jogamp.opengl.JoglVersion;
103 import com.jogamp.opengl.util.GLDrawableUtil;
104 import com.jogamp.opengl.util.TileRenderer;
105 
106 import jogamp.nativewindow.SurfaceScaleUtils;
107 import jogamp.opengl.Debug;
108 import jogamp.opengl.GLContextImpl;
109 import jogamp.opengl.GLDrawableHelper;
110 import jogamp.opengl.GLDrawableImpl;
111 import jogamp.opengl.awt.AWTTilePainter;
112 
113 // FIXME: Subclasses need to call resetGLFunctionAvailability() on their
114 // context whenever the displayChanged() function is called on our
115 // GLEventListeners
116 
117 /** A heavyweight AWT component which provides OpenGL rendering
118     support. This is the primary implementation of an AWT {@link GLDrawable};
119     {@link GLJPanel} is provided for compatibility with Swing user
120     interfaces when adding a heavyweight doesn't work either because
121     of Z-ordering or LayoutManager problems.
122  *
123  * <h5><a name="offscreenlayer">Offscreen Layer Remarks</a></h5>
124  *
125  * {@link OffscreenLayerOption#setShallUseOffscreenLayer(boolean) setShallUseOffscreenLayer(true)}
126  * maybe called to use an offscreen drawable (FBO or PBuffer) allowing
127  * the underlying JAWT mechanism to composite the image, if supported.
128  * <p>
129  * {@link OffscreenLayerOption#setShallUseOffscreenLayer(boolean) setShallUseOffscreenLayer(true)}
130  * is being called if {@link GLCapabilitiesImmutable#isOnscreen()} is <code>false</code>.
131  * </p>
132  *
133  * <h5><a name="java2dgl">Java2D OpenGL Remarks</a></h5>
134  *
135  * To avoid any conflicts with a potential Java2D OpenGL context,<br>
136  * you shall consider setting the following JVM properties:<br>
137  * <ul>
138  *    <li><pre>sun.java2d.opengl=false</pre></li>
139  *    <li><pre>sun.java2d.noddraw=true</pre></li>
140  * </ul>
141  * This is especially true in case you want to utilize a GLProfile other than
142  * {@link GLProfile#GL2}, eg. using {@link GLProfile#getMaxFixedFunc()}.<br>
143  * On the other hand, if you like to experiment with GLJPanel's utilization
144  * of Java2D's OpenGL pipeline, you have to set them to
145  * <ul>
146  *    <li><pre>sun.java2d.opengl=true</pre></li>
147  *    <li><pre>sun.java2d.noddraw=true</pre></li>
148  * </ul>
149  *
150  * <h5><a name="backgrounderase">Disable Background Erase</a></h5>
151  *
152  * GLCanvas tries to disable background erase for the AWT Canvas
153  * before native peer creation (X11) and after it (Windows), <br>
154  * utilizing the optional {@link java.awt.Toolkit} method <code>disableBeackgroundErase(java.awt.Canvas)</code>.<br>
155  * However if this does not give you the desired results, you may want to disable AWT background erase in general:
156  * <ul>
157  *   <li><pre>sun.awt.noerasebackground=true</pre></li>
158  * </ul>
159  *
160  * <p>
161  * <a name="contextSharing"><h5>OpenGL Context Sharing</h5></a>
162  * To share a {@link GLContext} see the following note in the documentation overview:
163  * <a href="../../../../overview-summary.html#SHARING">context sharing</a>
164  * as well as {@link GLSharedContextSetter}.
165  * </p>
166  */
167 
168 @SuppressWarnings("serial")
169 public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosingProtocol, OffscreenLayerOption,
170                                                 AWTPrintLifecycle, GLSharedContextSetter, ScalableSurface {
171 
172   private static final boolean DEBUG = Debug.debug("GLCanvas");
173 
174   private final RecursiveLock lock = LockFactory.createRecursiveLock();
175   private final GLDrawableHelper helper = new GLDrawableHelper();
176   private volatile GLDrawableImpl drawable; // volatile: avoid locking for read-only access
177   private volatile JAWTWindow jawtWindow; // the JAWTWindow presentation of this AWT Canvas, bound to the 'drawable' lifecycle
178   private volatile GLContextImpl context; // volatile: avoid locking for read-only access
179   private volatile boolean sendReshape = false; // volatile: maybe written by EDT w/o locking
180   private final float[] minPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
181   private final float[] maxPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
182   private final float[] hasPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
183   final float[] reqPixelScale = new float[] { ScalableSurface.AUTOMAX_PIXELSCALE, ScalableSurface.AUTOMAX_PIXELSCALE };
184 
185   // copy of the cstr args, mainly for recreation
186   private final GLCapabilitiesImmutable capsReqUser;
187   private final GLCapabilitiesChooser chooser;
188   private int additionalCtxCreationFlags = 0;
189   private boolean shallUseOffscreenLayer = false;
190 
191   private volatile GraphicsDevice awtDeviceReq; // one time user req.
192   private volatile AWTGraphicsConfiguration awtConfig;
193   private volatile boolean isShowing;
194   private final HierarchyListener hierarchyListener = new HierarchyListener() {
195       @Override
196       public void hierarchyChanged(final HierarchyEvent e) {
197           isShowing = GLCanvas.this.isShowing();
198       }
199   };
200 
201   private final AWTWindowClosingProtocol awtWindowClosingProtocol =
202           new AWTWindowClosingProtocol(this, new Runnable() {
203                 @Override
204                 public void run() {
205                     GLCanvas.this.destroyImpl( true );
206                 }
207             }, null);
208 
209   /** Creates a new GLCanvas component with a default set of OpenGL
210       capabilities, using the default OpenGL capabilities selection
211       mechanism, on the default screen device.
212       <p>
213       See details about <a href="#contextSharing">OpenGL context sharing</a>.
214       </p>
215    * @throws GLException if no default profile is available for the default desktop device.
216    */
GLCanvas()217   public GLCanvas() throws GLException {
218     this(null);
219   }
220 
221   /** Creates a new GLCanvas component with the requested set of
222       OpenGL capabilities, using the default OpenGL capabilities
223       selection mechanism, on the default screen device.
224       <p>
225       See details about <a href="#contextSharing">OpenGL context sharing</a>.
226       </p>
227    * @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
228    * @see GLCanvas#GLCanvas(com.jogamp.opengl.GLCapabilitiesImmutable, com.jogamp.opengl.GLCapabilitiesChooser, com.jogamp.opengl.GLContext, java.awt.GraphicsDevice)
229    */
GLCanvas(final GLCapabilitiesImmutable capsReqUser)230   public GLCanvas(final GLCapabilitiesImmutable capsReqUser) throws GLException {
231     this(capsReqUser, null, null);
232   }
233 
234   /** Creates a new GLCanvas component. The passed GLCapabilities
235       specifies the OpenGL capabilities for the component; if null, a
236       default set of capabilities is used. The GLCapabilitiesChooser
237       specifies the algorithm for selecting one of the available
238       GLCapabilities for the component; a DefaultGLCapabilitesChooser
239       is used if null is passed for this argument.
240       The passed GraphicsDevice indicates the screen on
241       which to create the GLCanvas; the GLDrawableFactory uses the
242       default screen device of the local GraphicsEnvironment if null
243       is passed for this argument.
244       <p>
245       See details about <a href="#contextSharing">OpenGL context sharing</a>.
246       </p>
247    * @throws GLException if no GLCapabilities are given and no default profile is available for the default desktop device.
248    */
GLCanvas(final GLCapabilitiesImmutable capsReqUser, final GLCapabilitiesChooser chooser, final GraphicsDevice device)249   public GLCanvas(final GLCapabilitiesImmutable capsReqUser,
250                   final GLCapabilitiesChooser chooser,
251                   final GraphicsDevice device)
252       throws GLException
253   {
254     /*
255      * Determination of the native window is made in 'super.addNotify()',
256      * which creates the native peer using AWT's GraphicsConfiguration.
257      * GraphicsConfiguration is returned by this class overwritten
258      * 'getGraphicsConfiguration()', which returns our OpenGL compatible
259      * 'chosen' GraphicsConfiguration.
260      */
261     super();
262 
263     if(null==capsReqUser) {
264         this.capsReqUser = new GLCapabilities(GLProfile.getDefault(GLProfile.getDefaultDevice()));
265     } else {
266         // don't allow the user to change data
267         this.capsReqUser = (GLCapabilitiesImmutable) capsReqUser.cloneMutable();
268     }
269     if( !this.capsReqUser.isOnscreen() ) {
270         setShallUseOffscreenLayer(true); // trigger offscreen layer - if supported
271     }
272 
273     // One time user AWT GraphicsDevice request
274     awtDeviceReq = device;
275 
276     // instantiation will be issued in addNotify()
277     this.chooser = chooser;
278 
279     this.addHierarchyListener(hierarchyListener);
280     this.isShowing = isShowing();
281   }
282 
283   @Override
setSharedContext(final GLContext sharedContext)284   public final void setSharedContext(final GLContext sharedContext) throws IllegalStateException {
285       helper.setSharedContext(this.context, sharedContext);
286   }
287 
288   @Override
setSharedAutoDrawable(final GLAutoDrawable sharedAutoDrawable)289   public final void setSharedAutoDrawable(final GLAutoDrawable sharedAutoDrawable) throws IllegalStateException {
290       helper.setSharedAutoDrawable(this, sharedAutoDrawable);
291   }
292 
293   @Override
getUpstreamWidget()294   public final Object getUpstreamWidget() {
295     return this;
296   }
297 
298   @Override
getUpstreamLock()299   public final RecursiveLock getUpstreamLock() { return lock; }
300 
301   @Override
isThreadGLCapable()302   public final boolean isThreadGLCapable() { return Threading.isOpenGLThread(); }
303 
304   @Override
setShallUseOffscreenLayer(final boolean v)305   public void setShallUseOffscreenLayer(final boolean v) {
306       shallUseOffscreenLayer = v;
307   }
308 
309   @Override
getShallUseOffscreenLayer()310   public final boolean getShallUseOffscreenLayer() {
311       return shallUseOffscreenLayer;
312   }
313 
314   @Override
isOffscreenLayerSurfaceEnabled()315   public final boolean isOffscreenLayerSurfaceEnabled() {
316       final JAWTWindow _jawtWindow = jawtWindow;
317       if(null != _jawtWindow) {
318           return _jawtWindow.isOffscreenLayerSurfaceEnabled();
319       }
320       return false;
321   }
322 
323 
324   /**
325    * {@inheritDoc}
326    * <p>
327    * Overridden to choose a {@link GraphicsConfiguration} from a parent container's
328    * {@link GraphicsDevice}.
329    * </p>
330    * <p>
331    * Method also intercepts {@link GraphicsConfiguration} changes regarding to
332    * its capabilities and its {@link GraphicsDevice}. This may happen in case
333    * the display changes its configuration or the component is moved to another screen.
334    * </p>
335    */
336   @Override
getGraphicsConfiguration()337   public GraphicsConfiguration getGraphicsConfiguration() {
338       /**
339        * parentGC will be null unless:
340        *   - A native peer has assigned it. This means we have a native
341        *     peer, and are already committed to a graphics configuration.
342        *   - This canvas has been added to a component hierarchy and has
343        *     an ancestor with a non-null GC, but the native peer has not
344        *     yet been created. This means we can still choose the GC on
345        *     all platforms since the peer hasn't been created.
346        */
347       final GraphicsConfiguration parentGC = super.getGraphicsConfiguration();
348 
349       if( Beans.isDesignTime() ) {
350           return parentGC;
351       }
352       final GraphicsConfiguration oldGC =  null != awtConfig ? awtConfig.getAWTGraphicsConfiguration() : null;
353 
354       if ( null != parentGC && null != oldGC && !oldGC.equals(parentGC) ) {
355           // Previous oldGC != parentGC of native peer
356 
357           if ( !oldGC.getDevice().getIDstring().equals(parentGC.getDevice().getIDstring()) ) {
358               // Previous oldGC's GraphicsDevice != parentGC's GraphicsDevice of native peer
359 
360               /**
361                * Here we select a GraphicsConfiguration on the alternate device.
362                * In case the new configuration differs (-> !equalCaps),
363                * we might need a reconfiguration,
364                */
365               final AWTGraphicsConfiguration newConfig = chooseGraphicsConfiguration( (GLCapabilitiesImmutable)awtConfig.getChosenCapabilities(),
366                       (GLCapabilitiesImmutable)awtConfig.getRequestedCapabilities(),
367                       chooser, parentGC.getDevice());
368               final GraphicsConfiguration newGC = newConfig.getAWTGraphicsConfiguration();
369               final boolean equalCaps = newConfig.getChosenCapabilities().equals(awtConfig.getChosenCapabilities());
370               if(DEBUG) {
371                   System.err.println(getThreadName()+": getGraphicsConfiguration() Info: Changed GC and GD");
372                   System.err.println("Created Config (n): Old     GC "+oldGC);
373                   System.err.println("Created Config (n): Old     GD "+oldGC.getDevice().getIDstring());
374                   System.err.println("Created Config (n): Parent  GC "+parentGC);
375                   System.err.println("Created Config (n): Parent  GD "+parentGC.getDevice().getIDstring());
376                   System.err.println("Created Config (n): New     GC "+newGC);
377                   System.err.println("Created Config (n): New     GD "+newGC.getDevice().getIDstring());
378                   System.err.println("Created Config (n): Old     CF "+awtConfig);
379                   System.err.println("Created Config (n): New     CF "+newConfig);
380                   System.err.println("Created Config (n): EQUALS CAPS "+equalCaps);
381                   // Thread.dumpStack();
382               }
383               if ( null != newGC ) {
384                   if( !equalCaps && GLAutoDrawable.SCREEN_CHANGE_ACTION_ENABLED ) {
385                       // complete destruction!
386                       destroyImpl( true );
387                       // recreation!
388                       setAWTGraphicsConfiguration(newConfig);
389                       createJAWTDrawableAndContext();
390                       validateGLDrawable();
391                   } else {
392                       setAWTGraphicsConfiguration(newConfig);
393                   }
394                   /**
395                    * Return the newGC, which covers the desired capabilities and is compatible
396                    * with the available GC's of its devices.
397                    */
398                   if(DEBUG) {
399                       System.err.println(getThreadName()+": Info: getGraphicsConfiguration - end.01: newGC "+newGC);
400                   }
401                   return newGC;
402               } else {
403                   if(DEBUG) {
404                       System.err.println(getThreadName()+": Info: getGraphicsConfiguration - end.00: oldGC "+oldGC);
405                   }
406               }
407           }
408           /**
409            * If a new GC was _not_ found/defined above,
410            * method returns oldGC as selected in the constructor or first addNotify().
411            * This may cause an exception in Component.checkGD when adding to a
412            * container, and is the desired behavior.
413            */
414           return oldGC;
415       } else if (null == parentGC) {
416           /**
417            * The parentGC is null, which means we have no native peer, and are not
418            * part of a (realized) component hierarchy. So we return the
419            * desired visual that was selected in the constructor (possibly
420            * null).
421            */
422           return oldGC;
423       } else {
424           /**
425            * Otherwise we have not explicitly selected a GC in the constructor, so
426            * just return what Canvas would have.
427            */
428           return parentGC;
429       }
430   }
431 
432   @Override
createContext(final GLContext shareWith)433   public GLContext createContext(final GLContext shareWith) {
434     final RecursiveLock _lock = lock;
435     _lock.lock();
436     try {
437         if(drawable != null) {
438           final GLContext _ctx = drawable.createContext(shareWith);
439           _ctx.setContextCreationFlags(additionalCtxCreationFlags);
440           return _ctx;
441         }
442         return null;
443     } finally {
444         _lock.unlock();
445     }
446   }
447 
setRealizedImpl(final boolean realized)448   private final void setRealizedImpl(final boolean realized) {
449       final RecursiveLock _lock = lock;
450       _lock.lock();
451       try {
452           final GLDrawable _drawable = drawable;
453           if( null == _drawable || realized == _drawable.isRealized() ||
454               realized && ( 0 >= _drawable.getSurfaceWidth() || 0 >= _drawable.getSurfaceHeight() ) ) {
455               return;
456           }
457          _drawable.setRealized(realized);
458           if( realized && _drawable.isRealized() ) {
459               sendReshape=true; // ensure a reshape is being send ..
460           }
461       } finally {
462           _lock.unlock();
463       }
464   }
465   private final Runnable realizeOnEDTAction = new Runnable() {
466     @Override
467     public void run() { setRealizedImpl(true); }
468   };
469   private final Runnable unrealizeOnEDTAction = new Runnable() {
470     @Override
471     public void run() { setRealizedImpl(false); }
472   };
473 
474   @Override
setRealized(final boolean realized)475   public final void setRealized(final boolean realized) {
476       // Make sure drawable realization happens on AWT-EDT and only there. Consider the AWTTree lock!
477       AWTEDTExecutor.singleton.invoke(getTreeLock(), false /* allowOnNonEDT */, true /* wait */, realized ? realizeOnEDTAction : unrealizeOnEDTAction);
478   }
479 
480   @Override
isRealized()481   public boolean isRealized() {
482       final GLDrawable _drawable = drawable;
483       return ( null != _drawable ) ? _drawable.isRealized() : false;
484   }
485 
486   @Override
getDefaultCloseOperation()487   public WindowClosingMode getDefaultCloseOperation() {
488       return awtWindowClosingProtocol.getDefaultCloseOperation();
489   }
490 
491   @Override
setDefaultCloseOperation(final WindowClosingMode op)492   public WindowClosingMode setDefaultCloseOperation(final WindowClosingMode op) {
493       return awtWindowClosingProtocol.setDefaultCloseOperation(op);
494   }
495 
496   @Override
display()497   public void display() {
498     if( !validateGLDrawable() ) {
499         if(DEBUG) {
500             System.err.println(getThreadName()+": Info: GLCanvas display - skipped GL render, drawable not valid yet");
501         }
502         return; // not yet available ..
503     }
504     if( isShowing && !printActive ) {
505         Threading.invoke(true, displayOnEDTAction, getTreeLock());
506     }
507   }
508 
509   /**
510    * {@inheritDoc}
511    *
512    * <p>
513    * This impl. only destroys all GL related resources.
514    * </p>
515    * <p>
516    * This impl. does not remove the GLCanvas from it's parent AWT container
517    * so this class's {@link #removeNotify()} AWT override won't get called.
518    * To do so, remove this component from it's parent AWT container.
519    * </p>
520    */
521   @Override
destroy()522   public void destroy() {
523     destroyImpl( false );
524   }
525 
destroyImpl(final boolean destroyJAWTWindowAndAWTDevice)526   protected void destroyImpl(final boolean destroyJAWTWindowAndAWTDevice) {
527     Threading.invoke(true, destroyOnEDTAction, getTreeLock());
528     if( destroyJAWTWindowAndAWTDevice ) {
529         AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, disposeJAWTWindowAndAWTDeviceOnEDT);
530     }
531   }
532 
533   /** Overridden to cause OpenGL rendering to be performed during
534       repaint cycles. Subclasses which override this method must call
535       super.paint() in their paint() method in order to function
536       properly.
537     */
538   @Override
paint(final Graphics g)539   public void paint(final Graphics g) {
540     if( Beans.isDesignTime() ) {
541       // Make GLCanvas behave better in NetBeans GUI builder
542       g.setColor(Color.BLACK);
543       g.fillRect(0, 0, getWidth(), getHeight());
544       final FontMetrics fm = g.getFontMetrics();
545       String name = getName();
546       if (name == null) {
547         name = getClass().getName();
548         final int idx = name.lastIndexOf('.');
549         if (idx >= 0) {
550           name = name.substring(idx + 1);
551         }
552       }
553       final Rectangle2D bounds = fm.getStringBounds(name, g);
554       g.setColor(Color.WHITE);
555       g.drawString(name,
556                    (int) ((getWidth()  - bounds.getWidth())  / 2),
557                    (int) ((getHeight() + bounds.getHeight()) / 2));
558     } else if( !this.helper.isAnimatorAnimatingOnOtherThread() ) {
559         display();
560     }
561   }
562 
563   /** Overridden to track when this component is added to a container.
564       Subclasses which override this method must call
565       super.addNotify() in their addNotify() method in order to
566       function properly. <P>
567 
568       <B>Overrides:</B>
569       <DL><DD><CODE>addNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
570     @SuppressWarnings("deprecation")
571     @Override
addNotify()572   public void addNotify() {
573     final RecursiveLock _lock = lock;
574     _lock.lock();
575     try {
576         final boolean isBeansDesignTime = Beans.isDesignTime();
577 
578         if(DEBUG) {
579             System.err.println(getThreadName()+": Info: addNotify - start, bounds: "+this.getBounds()+", isBeansDesignTime "+isBeansDesignTime);
580             // Thread.dumpStack();
581         }
582 
583         if( isBeansDesignTime ) {
584             super.addNotify();
585         } else {
586             /**
587              * 'super.addNotify()' determines the GraphicsConfiguration,
588              * while calling this class's overridden 'getGraphicsConfiguration()' method
589              * after which it creates the native peer.
590              * Hence we have to set the 'awtConfig' before since it's GraphicsConfiguration
591              * is being used in getGraphicsConfiguration().
592              * This code order also allows recreation, ie re-adding the GLCanvas.
593              */
594 
595             // before native peer is valid: X11
596             disableBackgroundErase();
597 
598             final GraphicsDevice awtDevice;
599             if(null==awtDeviceReq) {
600                 // Query AWT GraphicsDevice from parent tree, default
601                 final GraphicsConfiguration gc = super.getGraphicsConfiguration();
602                 if(null==gc) {
603                     throw new GLException("Error: NULL AWT GraphicsConfiguration");
604                 }
605                 awtDevice = gc.getDevice();
606             } else {
607                 // Use one time user AWT GraphicsDevice request
608                 awtDevice = awtDeviceReq;
609                 awtDeviceReq = null;
610             }
611             final AWTGraphicsConfiguration awtConfig = chooseGraphicsConfiguration(capsReqUser, capsReqUser, chooser, awtDevice);
612             if(null==awtConfig) {
613                 throw new GLException("Error: NULL AWTGraphicsConfiguration");
614             }
615             setAWTGraphicsConfiguration(awtConfig);
616 
617             // issues getGraphicsConfiguration() and creates the native peer
618             super.addNotify();
619 
620             // after native peer is valid: Windows
621             disableBackgroundErase();
622 
623             createJAWTDrawableAndContext();
624 
625             // init drawable by paint/display makes the init sequence more equal
626             // for all launch flavors (applet/javaws/..)
627             // validateGLDrawable();
628         }
629         awtWindowClosingProtocol.addClosingListener();
630 
631         if(DEBUG) {
632             System.err.println(getThreadName()+": Info: addNotify - end: peer: "+getPeer());
633         }
634     } finally {
635         _lock.unlock();
636     }
637   }
638 
639   @Override
setSurfaceScale(final float[] pixelScale)640   public final boolean setSurfaceScale(final float[] pixelScale) {
641       System.arraycopy(pixelScale, 0, reqPixelScale, 0, 2);
642       if( isRealized() && isShowing ) {
643           Threading.invoke(true, setSurfaceScaleOnEDTAction, getTreeLock());
644           return true;
645       } else {
646           return false;
647       }
648   }
649   private final Runnable setSurfaceScaleOnEDTAction = new Runnable() {
650     @Override
651     public void run() {
652         final RecursiveLock _lock = lock;
653         _lock.lock();
654         try {
655             if( null != drawable && drawable.isRealized() ) {
656                 if( setSurfaceScaleImpl(jawtWindow) ) {
657                     reshapeImpl(getWidth(), getHeight());
658                     if( !helper.isAnimatorAnimatingOnOtherThread() ) {
659                         helper.invokeGL(drawable, context, displayAction, initAction); // display
660                     }
661                 }
662             }
663         } finally {
664             _lock.unlock();
665         }
666     }  };
setSurfaceScaleImpl(final ScalableSurface ns)667   private final boolean setSurfaceScaleImpl(final ScalableSurface ns) {
668       if( ns.setSurfaceScale(reqPixelScale) ) {
669           ns.getCurrentSurfaceScale(hasPixelScale);
670           return true;
671       } else {
672           return false;
673       }
674   }
675 
updatePixelScale()676   private final boolean updatePixelScale() {
677       if( jawtWindow.hasPixelScaleChanged() ) {
678           jawtWindow.getMaximumSurfaceScale(maxPixelScale);
679           jawtWindow.getMinimumSurfaceScale(minPixelScale);
680           return setSurfaceScaleImpl(jawtWindow);
681       } else {
682           return false;
683       }
684   }
685 
686   @Override
getRequestedSurfaceScale(final float[] result)687   public final float[] getRequestedSurfaceScale(final float[] result) {
688       System.arraycopy(reqPixelScale, 0, result, 0, 2);
689       return result;
690   }
691 
692   @Override
getCurrentSurfaceScale(final float[] result)693   public final float[] getCurrentSurfaceScale(final float[] result) {
694       System.arraycopy(hasPixelScale, 0, result, 0, 2);
695       return result;
696   }
697 
698   @Override
getMinimumSurfaceScale(final float[] result)699   public float[] getMinimumSurfaceScale(final float[] result) {
700       System.arraycopy(minPixelScale, 0, result, 0, 2);
701       return result;
702   }
703 
704   @Override
getMaximumSurfaceScale(final float[] result)705   public float[] getMaximumSurfaceScale(final float[] result) {
706       System.arraycopy(maxPixelScale, 0, result, 0, 2);
707       return result;
708   }
709 
createJAWTDrawableAndContext()710   private void createJAWTDrawableAndContext() {
711     if ( !Beans.isDesignTime() ) {
712         jawtWindow = (JAWTWindow) NativeWindowFactory.getNativeWindow(this, awtConfig);
713         jawtWindow.setShallUseOffscreenLayer(shallUseOffscreenLayer);
714         jawtWindow.lockSurface();
715         try {
716             jawtWindow.setSurfaceScale(reqPixelScale);
717             drawable = (GLDrawableImpl) GLDrawableFactory.getFactory(capsReqUser.getGLProfile()).createGLDrawable(jawtWindow);
718             createContextImpl(drawable);
719             jawtWindow.getCurrentSurfaceScale(hasPixelScale);
720             jawtWindow.getMinimumSurfaceScale(minPixelScale);
721             jawtWindow.getMaximumSurfaceScale(maxPixelScale);
722         } finally {
723             jawtWindow.unlockSurface();
724         }
725     }
726   }
createContextImpl(final GLDrawable drawable)727   private boolean createContextImpl(final GLDrawable drawable) {
728     final GLContext[] shareWith = { null };
729     if( !helper.isSharedGLContextPending(shareWith) ) {
730         context = (GLContextImpl) drawable.createContext(shareWith[0]);
731         context.setContextCreationFlags(additionalCtxCreationFlags);
732         if(DEBUG) {
733             System.err.println(getThreadName()+": Context created: has shared "+(null != shareWith[0]));
734         }
735         return true;
736     } else {
737         if(DEBUG) {
738             System.err.println(getThreadName()+": Context !created: pending share");
739         }
740         return false;
741     }
742   }
743 
validateGLDrawable()744   private boolean validateGLDrawable() {
745       if( Beans.isDesignTime() || !isDisplayable() ) {
746           return false; // early out!
747       }
748       final GLDrawable _drawable = drawable;
749       if ( null != _drawable ) {
750           boolean res = _drawable.isRealized();
751           if( !res ) {
752               // re-try drawable creation
753               if( 0 >= _drawable.getSurfaceWidth() || 0 >= _drawable.getSurfaceHeight() ) {
754                   return false; // early out!
755               }
756               setRealized(true);
757               res = _drawable.isRealized();
758               if(DEBUG) {
759                   System.err.println(getThreadName()+": Realized Drawable: isRealized "+res+", "+_drawable.toString());
760                   // Thread.dumpStack();
761               }
762           }
763           if( res && null == context ) {
764               // re-try context creation
765               res = createContextImpl(_drawable); // pending creation.
766           }
767           return res;
768       }
769       return false;
770   }
771 
setAWTGraphicsConfiguration(final AWTGraphicsConfiguration config)772   private void setAWTGraphicsConfiguration(final AWTGraphicsConfiguration config) {
773     // Cache awtConfig
774     awtConfig = config;
775     if( null != jawtWindow ) {
776         // Notify JAWTWindow ..
777         jawtWindow.setAWTGraphicsConfiguration(config);
778     }
779   }
780 
781   /** <p>Overridden to track when this component is removed from a
782       container. Subclasses which override this method must call
783       super.removeNotify() in their removeNotify() method in order to
784       function properly. </p>
785       <p>User shall not call this method outside of EDT, read the AWT/Swing specs
786       about this.</p>
787       <B>Overrides:</B>
788       <DL><DD><CODE>removeNotify</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
789     @SuppressWarnings("deprecation")
790     @Override
removeNotify()791   public void removeNotify() {
792     if(DEBUG) {
793         System.err.println(getThreadName()+": Info: removeNotify - start");
794         // Thread.dumpStack();
795     }
796 
797     awtWindowClosingProtocol.removeClosingListener();
798 
799     if( Beans.isDesignTime() ) {
800       super.removeNotify();
801     } else {
802       try {
803         destroyImpl( true );
804       } finally {
805         super.removeNotify();
806       }
807     }
808     if(DEBUG) {
809         System.err.println(getThreadName()+": Info: removeNotify - end, peer: "+getPeer());
810     }
811   }
812 
813   /** Overridden to cause {@link GLDrawableHelper#reshape} to be
814       called on all registered {@link GLEventListener}s. Subclasses
815       which override this method must call super.reshape() in
816       their reshape() method in order to function properly. <P>
817 
818       <B>Overrides:</B>
819       <DL><DD><CODE>reshape</CODE> in class <CODE>java.awt.Component</CODE></DD></DL> */
820     @SuppressWarnings("deprecation")
821     @Override
reshape(final int x, final int y, final int width, final int height)822   public void reshape(final int x, final int y, final int width, final int height) {
823     synchronized (getTreeLock()) { // super.reshape(..) claims tree lock, so we do extend it's lock over reshape
824         super.reshape(x, y, width, height);
825         reshapeImpl(width, height);
826     }
827   }
reshapeImpl(final int width, final int height)828   private void reshapeImpl(final int width, final int height) {
829     final int scaledWidth = SurfaceScaleUtils.scale(width, hasPixelScale[0]);
830     final int scaledHeight = SurfaceScaleUtils.scale(height, hasPixelScale[1]);
831 
832     if(DEBUG) {
833         final NativeSurface ns = getNativeSurface();
834         final long nsH = null != ns ? ns.getSurfaceHandle() : 0;
835         System.err.println(getThreadName()+": GLCanvas.reshape.0 "+this.getName()+" resize"+(printActive?"WithinPrint":"")+
836                 " [ this "+getWidth()+"x"+getHeight()+", pixelScale "+getPixelScaleStr()+
837                 "] -> "+(printActive?"[skipped] ":"") + width+"x"+height+" * "+getPixelScaleStr()+" -> "+scaledWidth+"x"+scaledHeight+
838                 " - surfaceHandle 0x"+Long.toHexString(nsH));
839         // Thread.dumpStack();
840     }
841     if( validateGLDrawable() && !printActive ) {
842         final GLDrawableImpl _drawable = drawable;
843         if( ! _drawable.getChosenGLCapabilities().isOnscreen() ) {
844             final RecursiveLock _lock = lock;
845             _lock.lock();
846             try {
847                 final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, context, scaledWidth, scaledHeight);
848                 if(_drawable != _drawableNew) {
849                     // write back
850                     drawable = _drawableNew;
851                 }
852             } finally {
853                _lock.unlock();
854             }
855         }
856         sendReshape = true; // async if display() doesn't get called below, but avoiding deadlock
857     }
858   }
859 
860   /**
861    * Overridden from Canvas to prevent the AWT's clearing of the
862    * canvas from interfering with the OpenGL rendering.
863    */
864   @Override
update(final Graphics g)865   public void update(final Graphics g) {
866     paint(g);
867   }
868 
869   private volatile boolean printActive = false;
870   private GLAnimatorControl printAnimator = null;
871   private GLAutoDrawable printGLAD = null;
872   private AWTTilePainter printAWTTiles = null;
873 
874   @Override
setupPrint(final double scaleMatX, final double scaleMatY, final int numSamples, final int tileWidth, final int tileHeight)875   public void setupPrint(final double scaleMatX, final double scaleMatY, final int numSamples, final int tileWidth, final int tileHeight) {
876       printActive = true;
877       final int componentCount = isOpaque() ? 3 : 4;
878       final TileRenderer printRenderer = new TileRenderer();
879       printAWTTiles = new AWTTilePainter(printRenderer, componentCount, scaleMatX, scaleMatY, numSamples, tileWidth, tileHeight, DEBUG);
880       AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, setupPrintOnEDT);
881   }
882   private final Runnable setupPrintOnEDT = new Runnable() {
883       @Override
884       public void run() {
885           final RecursiveLock _lock = lock;
886           _lock.lock();
887           try {
888               if( !validateGLDrawable() ) {
889                   if(DEBUG) {
890                       System.err.println(getThreadName()+": Info: GLCanvas setupPrint - skipped GL render, drawable not valid yet");
891                   }
892                   printActive = false;
893                   return; // not yet available ..
894               }
895               if( !isVisible() ) {
896                   if(DEBUG) {
897                       System.err.println(getThreadName()+": Info: GLCanvas setupPrint - skipped GL render, canvas not visible");
898                   }
899                   printActive = false;
900                   return; // not yet available ..
901               }
902               sendReshape = false; // clear reshape flag
903               printAnimator =  helper.getAnimator();
904               if( null != printAnimator ) {
905                   printAnimator.remove(GLCanvas.this);
906               }
907               printGLAD = GLCanvas.this; // _not_ default, shall be replaced by offscreen GLAD
908               final GLCapabilitiesImmutable gladCaps = getChosenGLCapabilities();
909               final int printNumSamples = printAWTTiles.getNumSamples(gladCaps);
910               GLDrawable printDrawable = printGLAD.getDelegatedDrawable();
911               final boolean reqNewGLADSamples = printNumSamples != gladCaps.getNumSamples();
912               final boolean reqNewGLADSize = printAWTTiles.customTileWidth != -1 && printAWTTiles.customTileWidth != printDrawable.getSurfaceWidth() ||
913                                              printAWTTiles.customTileHeight != -1 && printAWTTiles.customTileHeight != printDrawable.getSurfaceHeight();
914               final boolean reqNewGLADOnscrn = gladCaps.isOnscreen();
915 
916               final GLCapabilities newGLADCaps = (GLCapabilities)gladCaps.cloneMutable();
917               newGLADCaps.setDoubleBuffered(false);
918               newGLADCaps.setOnscreen(false);
919               if( printNumSamples != newGLADCaps.getNumSamples() ) {
920                   newGLADCaps.setSampleBuffers(0 < printNumSamples);
921                   newGLADCaps.setNumSamples(printNumSamples);
922               }
923               final boolean reqNewGLADSafe = GLDrawableUtil.isSwapGLContextSafe(getRequestedGLCapabilities(), gladCaps, newGLADCaps);
924 
925               final boolean reqNewGLAD = ( reqNewGLADOnscrn || reqNewGLADSamples || reqNewGLADSize ) && reqNewGLADSafe;
926 
927               if( DEBUG ) {
928                   System.err.println("AWT print.setup: reqNewGLAD "+reqNewGLAD+"[ onscreen "+reqNewGLADOnscrn+", samples "+reqNewGLADSamples+", size "+reqNewGLADSize+", safe "+reqNewGLADSafe+"], "+
929                                      ", drawableSize "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+
930                                      ", customTileSize "+printAWTTiles.customTileWidth+"x"+printAWTTiles.customTileHeight+
931                                      ", scaleMat "+printAWTTiles.scaleMatX+" x "+printAWTTiles.scaleMatY+
932                                      ", numSamples "+printAWTTiles.customNumSamples+" -> "+printNumSamples+", printAnimator "+printAnimator);
933               }
934               if( reqNewGLAD ) {
935                   final GLDrawableFactory factory = GLDrawableFactory.getFactory(newGLADCaps.getGLProfile());
936                   GLOffscreenAutoDrawable offGLAD = null;
937                   try {
938                       offGLAD = factory.createOffscreenAutoDrawable(null, newGLADCaps, null,
939                                   printAWTTiles.customTileWidth != -1 ? printAWTTiles.customTileWidth : DEFAULT_PRINT_TILE_SIZE,
940                                   printAWTTiles.customTileHeight != -1 ? printAWTTiles.customTileHeight : DEFAULT_PRINT_TILE_SIZE);
941                   } catch (final GLException gle) {
942                       if( DEBUG ) {
943                           System.err.println("Caught: "+gle.getMessage());
944                           gle.printStackTrace();
945                       }
946                   }
947                   if( null != offGLAD ) {
948                       printGLAD = offGLAD;
949                       GLDrawableUtil.swapGLContextAndAllGLEventListener(GLCanvas.this, printGLAD);
950                       printDrawable = printGLAD.getDelegatedDrawable();
951                   }
952               }
953               printAWTTiles.setGLOrientation(printGLAD.isGLOriented(), printGLAD.isGLOriented());
954               printAWTTiles.renderer.setTileSize(printDrawable.getSurfaceWidth(), printDrawable.getSurfaceHeight(), 0);
955               printAWTTiles.renderer.attachAutoDrawable(printGLAD);
956               if( DEBUG ) {
957                   System.err.println("AWT print.setup "+printAWTTiles);
958                   System.err.println("AWT print.setup AA "+printNumSamples+", "+newGLADCaps);
959                   System.err.println("AWT print.setup printGLAD: "+printGLAD.getSurfaceWidth()+"x"+printGLAD.getSurfaceHeight()+", "+printGLAD);
960                   System.err.println("AWT print.setup printDraw: "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+", "+printDrawable);
961               }
962           } finally {
963               _lock.unlock();
964           }
965       }
966   };
967 
968   @Override
releasePrint()969   public void releasePrint() {
970       if( !printActive || null == printGLAD ) {
971           throw new IllegalStateException("setupPrint() not called");
972       }
973       sendReshape = false; // clear reshape flag
974       AWTEDTExecutor.singleton.invoke(getTreeLock(), true /* allowOnNonEDT */, true /* wait */, releasePrintOnEDT);
975   }
976   private final Runnable releasePrintOnEDT = new Runnable() {
977       @Override
978       public void run() {
979           final RecursiveLock _lock = lock;
980           _lock.lock();
981           try {
982               if( DEBUG ) {
983                   System.err.println("AWT print.release "+printAWTTiles);
984               }
985               printAWTTiles.dispose();
986               printAWTTiles= null;
987               if( printGLAD != GLCanvas.this ) {
988                   GLDrawableUtil.swapGLContextAndAllGLEventListener(printGLAD, GLCanvas.this);
989                   printGLAD.destroy();
990               }
991               printGLAD = null;
992               if( null != printAnimator ) {
993                   printAnimator.add(GLCanvas.this);
994                   printAnimator = null;
995               }
996               sendReshape = true; // trigger reshape, i.e. gl-viewport and -listener - this component might got resized!
997               printActive = false;
998               display();
999           } finally {
1000               _lock.unlock();
1001           }
1002       }
1003   };
1004 
1005   @Override
print(final Graphics graphics)1006   public void print(final Graphics graphics) {
1007       if( !printActive || null == printGLAD ) {
1008           throw new IllegalStateException("setupPrint() not called");
1009       }
1010       if(DEBUG && !EventQueue.isDispatchThread()) {
1011           System.err.println(getThreadName()+": Warning: GLCanvas print - not called from AWT-EDT");
1012           // we cannot dispatch print on AWT-EDT due to printing internal locking ..
1013       }
1014       sendReshape = false; // clear reshape flag
1015 
1016       final Graphics2D g2d = (Graphics2D)graphics;
1017       try {
1018           printAWTTiles.setupGraphics2DAndClipBounds(g2d, getWidth(), getHeight());
1019           final TileRenderer tileRenderer = printAWTTiles.renderer;
1020           if( DEBUG ) {
1021               System.err.println("AWT print.0: "+tileRenderer);
1022           }
1023           if( !tileRenderer.eot() ) {
1024               try {
1025                   do {
1026                       if( printGLAD != GLCanvas.this ) {
1027                           tileRenderer.display();
1028                       } else {
1029                           Threading.invoke(true, displayOnEDTAction, getTreeLock());
1030                       }
1031                   } while ( !tileRenderer.eot() );
1032                   if( DEBUG ) {
1033                       System.err.println("AWT print.1: "+printAWTTiles);
1034                   }
1035               } finally {
1036                   tileRenderer.reset();
1037                   printAWTTiles.resetGraphics2D();
1038               }
1039           }
1040       } catch (final NoninvertibleTransformException nte) {
1041           System.err.println("Caught: Inversion failed of: "+g2d.getTransform());
1042           nte.printStackTrace();
1043       }
1044       if( DEBUG ) {
1045           System.err.println("AWT print.X: "+printAWTTiles);
1046       }
1047   }
1048 
1049   @Override
addGLEventListener(final GLEventListener listener)1050   public void addGLEventListener(final GLEventListener listener) {
1051     helper.addGLEventListener(listener);
1052   }
1053 
1054   @Override
addGLEventListener(final int index, final GLEventListener listener)1055   public void addGLEventListener(final int index, final GLEventListener listener) throws IndexOutOfBoundsException {
1056     helper.addGLEventListener(index, listener);
1057   }
1058 
1059   @Override
getGLEventListenerCount()1060   public int getGLEventListenerCount() {
1061       return helper.getGLEventListenerCount();
1062   }
1063 
1064   @Override
getGLEventListener(final int index)1065   public GLEventListener getGLEventListener(final int index) throws IndexOutOfBoundsException {
1066       return helper.getGLEventListener(index);
1067   }
1068 
1069   @Override
areAllGLEventListenerInitialized()1070   public boolean areAllGLEventListenerInitialized() {
1071      return helper.areAllGLEventListenerInitialized();
1072   }
1073 
1074   @Override
getGLEventListenerInitState(final GLEventListener listener)1075   public boolean getGLEventListenerInitState(final GLEventListener listener) {
1076       return helper.getGLEventListenerInitState(listener);
1077   }
1078 
1079   @Override
setGLEventListenerInitState(final GLEventListener listener, final boolean initialized)1080   public void setGLEventListenerInitState(final GLEventListener listener, final boolean initialized) {
1081       helper.setGLEventListenerInitState(listener, initialized);
1082   }
1083 
1084   @Override
disposeGLEventListener(final GLEventListener listener, final boolean remove)1085   public GLEventListener disposeGLEventListener(final GLEventListener listener, final boolean remove) {
1086     final DisposeGLEventListenerAction r = new DisposeGLEventListenerAction(listener, remove);
1087     Threading.invoke(true, r, getTreeLock());
1088     return r.listener;
1089   }
1090 
1091   @Override
removeGLEventListener(final GLEventListener listener)1092   public GLEventListener removeGLEventListener(final GLEventListener listener) {
1093     return helper.removeGLEventListener(listener);
1094   }
1095 
1096   @Override
setAnimator(final GLAnimatorControl animatorControl)1097   public void setAnimator(final GLAnimatorControl animatorControl) {
1098     helper.setAnimator(animatorControl);
1099   }
1100 
1101   @Override
getAnimator()1102   public GLAnimatorControl getAnimator() {
1103     return helper.getAnimator();
1104   }
1105 
1106   @Override
setExclusiveContextThread(final Thread t)1107   public final Thread setExclusiveContextThread(final Thread t) throws GLException {
1108       return helper.setExclusiveContextThread(t, context);
1109   }
1110 
1111   @Override
getExclusiveContextThread()1112   public final Thread getExclusiveContextThread() {
1113       return helper.getExclusiveContextThread();
1114   }
1115 
1116   @Override
invoke(final boolean wait, final GLRunnable glRunnable)1117   public boolean invoke(final boolean wait, final GLRunnable glRunnable) throws IllegalStateException {
1118     return helper.invoke(this, wait, glRunnable);
1119   }
1120 
1121   @Override
invoke(final boolean wait, final List<GLRunnable> glRunnables)1122   public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) throws IllegalStateException {
1123     return helper.invoke(this, wait, glRunnables);
1124   }
1125 
1126   @Override
flushGLRunnables()1127   public void flushGLRunnables() {
1128       helper.flushGLRunnables();
1129   }
1130 
1131   @Override
setContext(final GLContext newCtx, final boolean destroyPrevCtx)1132   public GLContext setContext(final GLContext newCtx, final boolean destroyPrevCtx) {
1133       final RecursiveLock _lock = lock;
1134       _lock.lock();
1135       try {
1136           final GLContext oldCtx = context;
1137           GLDrawableHelper.switchContext(drawable, oldCtx, destroyPrevCtx, newCtx, additionalCtxCreationFlags);
1138           context=(GLContextImpl)newCtx;
1139           return oldCtx;
1140       } finally {
1141           _lock.unlock();
1142       }
1143   }
1144 
1145   @Override
getDelegatedDrawable()1146   public final GLDrawable getDelegatedDrawable() {
1147     return drawable;
1148   }
1149 
1150   @Override
getContext()1151   public GLContext getContext() {
1152     return context;
1153   }
1154 
1155   @Override
getGL()1156   public GL getGL() {
1157     if( Beans.isDesignTime() ) {
1158       return null;
1159     }
1160     final GLContext _context = context;
1161     return (_context == null) ? null : _context.getGL();
1162   }
1163 
1164   @Override
setGL(final GL gl)1165   public GL setGL(final GL gl) {
1166     final GLContext _context = context;
1167     if (_context != null) {
1168       _context.setGL(gl);
1169       return gl;
1170     }
1171     return null;
1172   }
1173 
1174 
1175   @Override
setAutoSwapBufferMode(final boolean onOrOff)1176   public void setAutoSwapBufferMode(final boolean onOrOff) {
1177     helper.setAutoSwapBufferMode(onOrOff);
1178   }
1179 
1180   @Override
getAutoSwapBufferMode()1181   public boolean getAutoSwapBufferMode() {
1182     return helper.getAutoSwapBufferMode();
1183   }
1184 
1185   @Override
swapBuffers()1186   public void swapBuffers() {
1187     Threading.invoke(true, swapBuffersOnEDTAction, getTreeLock());
1188   }
1189 
1190   @Override
setContextCreationFlags(final int flags)1191   public void setContextCreationFlags(final int flags) {
1192     additionalCtxCreationFlags = flags;
1193     final GLContext _context = context;
1194     if(null != _context) {
1195       _context.setContextCreationFlags(additionalCtxCreationFlags);
1196     }
1197   }
1198 
1199   @Override
getContextCreationFlags()1200   public int getContextCreationFlags() {
1201     return additionalCtxCreationFlags;
1202   }
1203 
1204   @Override
getGLProfile()1205   public GLProfile getGLProfile() {
1206     return capsReqUser.getGLProfile();
1207   }
1208 
1209   @Override
getChosenGLCapabilities()1210   public GLCapabilitiesImmutable getChosenGLCapabilities() {
1211     if( Beans.isDesignTime() ) {
1212         return capsReqUser;
1213     } else if( null == awtConfig ) {
1214         throw new GLException("No AWTGraphicsConfiguration: "+this);
1215     }
1216     return (GLCapabilitiesImmutable)awtConfig.getChosenCapabilities();
1217   }
1218 
1219   @Override
getRequestedGLCapabilities()1220   public GLCapabilitiesImmutable getRequestedGLCapabilities() {
1221     if( null == awtConfig ) {
1222         return capsReqUser;
1223     }
1224     return (GLCapabilitiesImmutable)awtConfig.getRequestedCapabilities();
1225   }
1226 
1227   @Override
getSurfaceWidth()1228   public int getSurfaceWidth() {
1229       return SurfaceScaleUtils.scale(getWidth(), hasPixelScale[0]);
1230   }
1231 
1232   @Override
getSurfaceHeight()1233   public int getSurfaceHeight() {
1234       return SurfaceScaleUtils.scale(getHeight(), hasPixelScale[1]);
1235   }
1236 
1237   @Override
isGLOriented()1238   public boolean isGLOriented() {
1239     final GLDrawable _drawable = drawable;
1240     return null != _drawable ? _drawable.isGLOriented() : true;
1241   }
1242 
1243   @Override
getNativeSurface()1244   public NativeSurface getNativeSurface() {
1245     final GLDrawable _drawable = drawable;
1246     return (null != _drawable) ? _drawable.getNativeSurface() : null;
1247   }
1248 
1249   @Override
getHandle()1250   public long getHandle() {
1251     final GLDrawable _drawable = drawable;
1252     return (null != _drawable) ? _drawable.getHandle() : 0;
1253   }
1254 
1255   @Override
getFactory()1256   public GLDrawableFactory getFactory() {
1257     final GLDrawable _drawable = drawable;
1258     return (null != _drawable) ? _drawable.getFactory() : null;
1259   }
1260 
1261   @Override
toString()1262   public String toString() {
1263     final GLDrawable _drawable = drawable;
1264     final int dw = (null!=_drawable) ? _drawable.getSurfaceWidth() : -1;
1265     final int dh = (null!=_drawable) ? _drawable.getSurfaceHeight() : -1;
1266 
1267     return "AWT-GLCanvas[Realized "+isRealized()+
1268                           ",\n\t"+((null!=_drawable)?_drawable.getClass().getName():"null-drawable")+
1269                           ",\n\tFactory   "+getFactory()+
1270                           ",\n\thandle    0x"+Long.toHexString(getHandle())+
1271                           ",\n\tDrawable size "+dw+"x"+dh+" surface["+getSurfaceWidth()+"x"+getSurfaceHeight()+"]"+
1272                           ",\n\tAWT[pos "+getX()+"/"+getY()+", size "+getWidth()+"x"+getHeight()+
1273                           ",\n\tvisible "+isVisible()+", displayable "+isDisplayable()+", showing "+isShowing+
1274                           ",\n\t"+awtConfig+"]]";
1275   }
1276 
1277   //----------------------------------------------------------------------
1278   // Internals only below this point
1279   //
1280 
getPixelScaleStr()1281   private final String getPixelScaleStr() { return "["+hasPixelScale[0]+", "+hasPixelScale[1]+"]"; }
1282 
1283   private final Runnable destroyOnEDTAction = new Runnable() {
1284     @Override
1285     public void run() {
1286         final RecursiveLock _lock = lock;
1287         _lock.lock();
1288         try {
1289             final GLAnimatorControl animator =  getAnimator();
1290 
1291             if(DEBUG) {
1292                 System.err.println(getThreadName()+": Info: destroyOnEDTAction() - START, hasContext " +
1293                         (null!=context) + ", hasDrawable " + (null!=drawable)+", "+animator);
1294                 // Thread.dumpStack();
1295             }
1296 
1297             final boolean animatorPaused;
1298             if(null!=animator) {
1299                 // can't remove us from animator for recreational addNotify()
1300                 animatorPaused = animator.pause();
1301             } else {
1302                 animatorPaused = false;
1303             }
1304 
1305             GLException exceptionOnDisposeGL = null;
1306 
1307             // OLS will be detached by disposeGL's context destruction below
1308             if( null != context ) {
1309                 if( context.isCreated() ) {
1310                     try {
1311                         helper.disposeGL(GLCanvas.this, context, true);
1312                         if(DEBUG) {
1313                             System.err.println(getThreadName()+": destroyOnEDTAction() - post ctx: "+context);
1314                         }
1315                     } catch (final GLException gle) {
1316                         exceptionOnDisposeGL = gle;
1317                     }
1318                 }
1319                 context = null;
1320             }
1321 
1322             Throwable exceptionOnUnrealize = null;
1323             if( null != drawable ) {
1324                 try {
1325                     drawable.setRealized(false);
1326                     if(DEBUG) {
1327                         System.err.println(getThreadName()+": destroyOnEDTAction() - post drawable: "+drawable);
1328                     }
1329                 } catch( final Throwable re ) {
1330                     exceptionOnUnrealize = re;
1331                 }
1332                 drawable = null;
1333             }
1334 
1335             if(animatorPaused) {
1336                 animator.resume();
1337             }
1338 
1339             // throw exception in order of occurrence ..
1340             if( null != exceptionOnDisposeGL ) {
1341                 throw exceptionOnDisposeGL;
1342             }
1343             if( null != exceptionOnUnrealize ) {
1344                 throw GLException.newGLException(exceptionOnUnrealize);
1345             }
1346 
1347             if(DEBUG) {
1348                 System.err.println(getThreadName()+": dispose() - END, animator "+animator);
1349             }
1350 
1351         } finally {
1352             _lock.unlock();
1353         }
1354     }
1355   };
1356 
1357   /**
1358    * Disposes the JAWTWindow and AbstractGraphicsDevice within EDT,
1359    * since resources created (X11: Display), must be destroyed in the same thread, where they have been created.
1360    * <p>
1361    * The drawable and context handle are null'ed as well, assuming {@link #destroy()} has been called already.
1362    * </p>
1363    *
1364    * @see #chooseGraphicsConfiguration(com.jogamp.opengl.GLCapabilitiesImmutable, com.jogamp.opengl.GLCapabilitiesImmutable, com.jogamp.opengl.GLCapabilitiesChooser, java.awt.GraphicsDevice)
1365    */
1366   private final Runnable disposeJAWTWindowAndAWTDeviceOnEDT = new Runnable() {
1367     @Override
1368     public void run() {
1369         context=null;
1370         drawable=null;
1371 
1372         if( null != jawtWindow ) {
1373             jawtWindow.destroy();
1374             if(DEBUG) {
1375                 System.err.println(getThreadName()+": GLCanvas.disposeJAWTWindowAndAWTDeviceOnEDT(): post JAWTWindow: "+jawtWindow);
1376             }
1377             jawtWindow=null;
1378         }
1379         hasPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
1380         hasPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
1381         minPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
1382         minPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
1383         maxPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
1384         maxPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
1385 
1386         if(null != awtConfig) {
1387             final AbstractGraphicsConfiguration aconfig = awtConfig.getNativeGraphicsConfiguration();
1388             final AbstractGraphicsDevice adevice = aconfig.getScreen().getDevice();
1389             final String adeviceMsg;
1390             if(DEBUG) {
1391                 adeviceMsg = adevice.toString();
1392             } else {
1393                 adeviceMsg = null;
1394             }
1395             final boolean closed = adevice.close();
1396             if(DEBUG) {
1397                 System.err.println(getThreadName()+": GLCanvas.disposeJAWTWindowAndAWTDeviceOnEDT(): post GraphicsDevice: "+adeviceMsg+", result: "+closed);
1398             }
1399         }
1400         awtConfig = null;
1401     }
1402   };
1403 
1404   private final Runnable initAction = new Runnable() {
1405     @Override
1406     public void run() {
1407       helper.init(GLCanvas.this, !sendReshape);
1408     }
1409   };
1410 
1411   private final Runnable displayAction = new Runnable() {
1412     @Override
1413     public void run() {
1414       if (sendReshape) {
1415         if(DEBUG) {
1416             System.err.println(getThreadName()+": Reshape: "+getSurfaceWidth()+"x"+getSurfaceHeight());
1417         }
1418         // Note: we ignore the given x and y within the parent component
1419         // since we are drawing directly into this heavyweight component.
1420         helper.reshape(GLCanvas.this, 0, 0, getSurfaceWidth(), getSurfaceHeight());
1421         sendReshape = false;
1422       }
1423 
1424       helper.display(GLCanvas.this);
1425     }
1426   };
1427 
1428   private final Runnable displayOnEDTAction = new Runnable() {
1429     @Override
1430     public void run() {
1431         final RecursiveLock _lock = lock;
1432         _lock.lock();
1433         try {
1434             if( null != drawable && drawable.isRealized() ) {
1435                 if( GLCanvas.this.updatePixelScale() ) {
1436                     GLCanvas.this.reshapeImpl(getWidth(), getHeight());
1437                 }
1438                 helper.invokeGL(drawable, context, displayAction, initAction);
1439             }
1440         } finally {
1441             _lock.unlock();
1442         }
1443     }
1444   };
1445 
1446   private final Runnable swapBuffersOnEDTAction = new Runnable() {
1447     @Override
1448     public void run() {
1449         final RecursiveLock _lock = lock;
1450         _lock.lock();
1451         try {
1452             if( null != drawable && drawable.isRealized() ) {
1453                 drawable.swapBuffers();
1454             }
1455         } finally {
1456             _lock.unlock();
1457         }
1458     }
1459   };
1460 
1461   private class DisposeGLEventListenerAction implements Runnable {
1462     GLEventListener listener;
1463     private final boolean remove;
DisposeGLEventListenerAction(final GLEventListener listener, final boolean remove)1464     private DisposeGLEventListenerAction(final GLEventListener listener, final boolean remove) {
1465         this.listener = listener;
1466         this.remove = remove;
1467     }
1468 
1469     @Override
run()1470     public void run() {
1471         final RecursiveLock _lock = lock;
1472         _lock.lock();
1473         try {
1474             listener = helper.disposeGLEventListener(GLCanvas.this, drawable, context, listener, remove);
1475         } finally {
1476             _lock.unlock();
1477         }
1478     }
1479   };
1480 
1481   // Disables the AWT's erasing of this Canvas's background on Windows
1482   // in Java SE 6. This internal API is not available in previous
1483   // releases, but the system property
1484   // -Dsun.awt.noerasebackground=true can be specified to get similar
1485   // results globally in previous releases.
1486   private static boolean disableBackgroundEraseInitialized;
1487   private static Method  disableBackgroundEraseMethod;
disableBackgroundErase()1488   private void disableBackgroundErase() {
1489     if (!disableBackgroundEraseInitialized) {
1490       try {
1491         AccessController.doPrivileged(new PrivilegedAction<Object>() {
1492             @Override
1493             public Object run() {
1494               try {
1495                 Class<?> clazz = getToolkit().getClass();
1496                 while (clazz != null && disableBackgroundEraseMethod == null) {
1497                   try {
1498                     disableBackgroundEraseMethod =
1499                       clazz.getDeclaredMethod("disableBackgroundErase",
1500                                               new Class[] { Canvas.class });
1501                     disableBackgroundEraseMethod.setAccessible(true);
1502                   } catch (final Exception e) {
1503                     clazz = clazz.getSuperclass();
1504                   }
1505                 }
1506               } catch (final Exception e) {
1507               }
1508               return null;
1509             }
1510           });
1511       } catch (final Exception e) {
1512       }
1513       disableBackgroundEraseInitialized = true;
1514       if(DEBUG) {
1515         System.err.println(getThreadName()+": GLCanvas: TK disableBackgroundErase method found: "+
1516                 (null!=disableBackgroundEraseMethod));
1517       }
1518     }
1519     if (disableBackgroundEraseMethod != null) {
1520       Throwable t=null;
1521       try {
1522         disableBackgroundEraseMethod.invoke(getToolkit(), new Object[] { this });
1523       } catch (final Exception e) {
1524         t = e;
1525       }
1526       if(DEBUG) {
1527         System.err.println(getThreadName()+": GLCanvas: TK disableBackgroundErase error: "+t);
1528       }
1529     }
1530   }
1531 
1532   /**
1533    * Issues the GraphicsConfigurationFactory's choosing facility within EDT,
1534    * since resources created (X11: Display), must be destroyed in the same thread, where they have been created.
1535    *
1536    * @param capsChosen
1537    * @param capsRequested
1538    * @param chooser
1539    * @param device
1540    * @return the chosen AWTGraphicsConfiguration
1541    *
1542    * @see #disposeJAWTWindowAndAWTDeviceOnEDT
1543    */
chooseGraphicsConfiguration(final GLCapabilitiesImmutable capsChosen, final GLCapabilitiesImmutable capsRequested, final GLCapabilitiesChooser chooser, final GraphicsDevice device)1544   private AWTGraphicsConfiguration chooseGraphicsConfiguration(final GLCapabilitiesImmutable capsChosen,
1545                                                                final GLCapabilitiesImmutable capsRequested,
1546                                                                final GLCapabilitiesChooser chooser,
1547                                                                final GraphicsDevice device) {
1548     // Make GLCanvas behave better in NetBeans GUI builder
1549     if( Beans.isDesignTime() ) {
1550       return null;
1551     }
1552     if( null == device ) {
1553         throw new GLException("Error: NULL AWT GraphicsDevice");
1554     }
1555     final AbstractGraphicsScreen aScreen = AWTGraphicsScreen.createScreenDevice(device, AbstractGraphicsDevice.DEFAULT_UNIT);
1556     AWTGraphicsConfiguration config = null;
1557 
1558     if( EventQueue.isDispatchThread() || Thread.holdsLock(getTreeLock()) ) {
1559         config = (AWTGraphicsConfiguration)
1560                 GraphicsConfigurationFactory.getFactory(AWTGraphicsDevice.class, GLCapabilitiesImmutable.class).chooseGraphicsConfiguration(capsChosen,
1561                                                                                                              capsRequested,
1562                                                                                                              chooser, aScreen, VisualIDHolder.VID_UNDEFINED);
1563     } else {
1564         try {
1565             final ArrayList<AWTGraphicsConfiguration> bucket = new ArrayList<AWTGraphicsConfiguration>(1);
1566             EventQueue.invokeAndWait(new Runnable() {
1567                 @Override
1568                 public void run() {
1569                     final AWTGraphicsConfiguration c = (AWTGraphicsConfiguration)
1570                             GraphicsConfigurationFactory.getFactory(AWTGraphicsDevice.class, GLCapabilitiesImmutable.class).chooseGraphicsConfiguration(capsChosen,
1571                                                                                                                          capsRequested,
1572                                                                                                                          chooser, aScreen, VisualIDHolder.VID_UNDEFINED);
1573                     bucket.add(c);
1574                 }
1575             });
1576             config = ( bucket.size() > 0 ) ? bucket.get(0) : null ;
1577         } catch (final InvocationTargetException e) {
1578             throw new GLException(e.getTargetException());
1579         } catch (final InterruptedException e) {
1580             throw new GLException(e);
1581         }
1582     }
1583 
1584     if ( null == config ) {
1585       throw new GLException("Error: Couldn't fetch AWTGraphicsConfiguration");
1586     }
1587 
1588     return config;
1589   }
1590 
getThreadName()1591   protected static String getThreadName() { return Thread.currentThread().getName(); }
1592 
1593   /**
1594    * A most simple JOGL AWT test entry
1595    */
main(final String args[])1596   public static void main(final String args[]) {
1597     System.err.println(VersionUtil.getPlatformInfo());
1598     System.err.println(GlueGenVersion.getInstance());
1599     // System.err.println(NativeWindowVersion.getInstance());
1600     System.err.println(JoglVersion.getInstance());
1601 
1602     System.err.println(JoglVersion.getDefaultOpenGLInfo(null, null, true).toString());
1603 
1604     final GLCapabilitiesImmutable caps = new GLCapabilities( GLProfile.getDefault(GLProfile.getDefaultDevice()) );
1605     final Frame frame = new Frame("JOGL AWT Test");
1606 
1607     final GLCanvas glCanvas = new GLCanvas(caps);
1608     frame.add(glCanvas);
1609     frame.setSize(128, 128);
1610 
1611     glCanvas.addGLEventListener(new GLEventListener() {
1612         @Override
1613         public void init(final GLAutoDrawable drawable) {
1614             final GL gl = drawable.getGL();
1615             System.err.println(JoglVersion.getGLInfo(gl, null));
1616         }
1617         @Override
1618         public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { }
1619         @Override
1620         public void display(final GLAutoDrawable drawable) { }
1621         @Override
1622         public void dispose(final GLAutoDrawable drawable) { }
1623     });
1624 
1625     try {
1626         javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
1627             @Override
1628             public void run() {
1629                 frame.setVisible(true);
1630             }});
1631     } catch (final Throwable t) {
1632         t.printStackTrace();
1633     }
1634     glCanvas.display();
1635     try {
1636         javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
1637             @Override
1638             public void run() {
1639                 frame.dispose();
1640             }});
1641     } catch (final Throwable t) {
1642         t.printStackTrace();
1643     }
1644   }
1645 
1646 }
1647