1 /*
2  * Copyright (c) 2008 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  */
34 
35 package jogamp.newt.driver.awt;
36 
37 import java.awt.Canvas;
38 import java.awt.Graphics;
39 import java.awt.GraphicsDevice;
40 import java.awt.GraphicsConfiguration;
41 import java.lang.reflect.Method;
42 import java.security.AccessController;
43 import java.security.PrivilegedAction;
44 
45 import com.jogamp.nativewindow.AbstractGraphicsDevice;
46 import com.jogamp.nativewindow.AbstractGraphicsScreen;
47 import com.jogamp.nativewindow.CapabilitiesChooser;
48 import com.jogamp.nativewindow.CapabilitiesImmutable;
49 import com.jogamp.nativewindow.GraphicsConfigurationFactory;
50 import com.jogamp.nativewindow.NativeWindow;
51 import com.jogamp.nativewindow.NativeWindowException;
52 import com.jogamp.nativewindow.NativeWindowFactory;
53 import com.jogamp.nativewindow.VisualIDHolder;
54 
55 import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration;
56 import com.jogamp.nativewindow.awt.AWTGraphicsDevice;
57 import com.jogamp.nativewindow.awt.AWTGraphicsScreen;
58 import com.jogamp.nativewindow.awt.JAWTWindow;
59 import com.jogamp.newt.Window;
60 
61 @SuppressWarnings("serial")
62 public class AWTCanvas extends Canvas {
63   private final WindowDriver driver;
64   private final CapabilitiesImmutable capabilities;
65   private final CapabilitiesChooser chooser;
66   private final UpstreamScalable upstreamScale;
67   private GraphicsConfiguration chosen;
68   private volatile GraphicsDevice device;
69   private volatile AWTGraphicsConfiguration awtConfig;
70   private volatile JAWTWindow jawtWindow=null; // the JAWTWindow presentation of this AWT Canvas, bound to the 'drawable' lifecycle
71 
72   public static interface UpstreamScalable {
getReqPixelScale()73       float[] getReqPixelScale();
setHasPixelScale(final float[] pixelScale)74       void setHasPixelScale(final float[] pixelScale);
75   }
76 
77   private boolean displayConfigChanged=false;
78 
AWTCanvas(final WindowDriver driver, final CapabilitiesImmutable capabilities, final CapabilitiesChooser chooser, final UpstreamScalable upstreamScale)79   public AWTCanvas(final WindowDriver driver, final CapabilitiesImmutable capabilities, final CapabilitiesChooser chooser, final UpstreamScalable upstreamScale) {
80     super();
81     if(null==capabilities) {
82         throw new NativeWindowException("Capabilities null");
83     }
84     if(null==driver) {
85         throw new NativeWindowException("driver null");
86     }
87     this.driver = driver;
88     this.capabilities=capabilities;
89     this.chooser=chooser;
90     this.upstreamScale = upstreamScale;
91   }
92 
getAWTGraphicsConfiguration()93   public AWTGraphicsConfiguration getAWTGraphicsConfiguration() {
94     return awtConfig;
95   }
96 
97   /**
98    * Overridden from Canvas to prevent the AWT's clearing of the
99    * canvas from interfering with the OpenGL rendering.
100    */
101   @Override
update(final Graphics g)102   public void update(final Graphics g) {
103     // paint(g);
104   }
105 
106   /** Overridden to cause OpenGL rendering to be performed during
107       repaint cycles. Subclasses which override this method must call
108       super.paint() in their paint() method in order to function
109       properly.
110    */
111   @Override
paint(final Graphics g)112   public void paint(final Graphics g) {
113   }
114 
hasDeviceChanged()115   public boolean hasDeviceChanged() {
116     final boolean res = displayConfigChanged;
117     displayConfigChanged=false;
118     return res;
119   }
120 
121   @Override
addNotify()122   public void addNotify() {
123 
124     // before native peer is valid: X11
125     disableBackgroundErase();
126 
127     /**
128      * 'super.addNotify()' determines the GraphicsConfiguration,
129      * while calling this class's overriden 'getGraphicsConfiguration()' method
130      * after which it creates the native peer.
131      * Hence we have to set the 'awtConfig' before since it's GraphicsConfiguration
132      * is being used in getGraphicsConfiguration().
133      * This code order also allows recreation, ie re-adding the GLCanvas.
134      */
135     awtConfig = chooseGraphicsConfiguration(capabilities, capabilities, chooser, device);
136     if(Window.DEBUG_IMPLEMENTATION) {
137         System.err.println(getThreadName()+": AWTCanvas.addNotify.0: Created Config: "+awtConfig);
138     }
139     if(null==awtConfig) {
140         throw new NativeWindowException("Error: NULL AWTGraphicsConfiguration");
141     }
142     chosen = awtConfig.getAWTGraphicsConfiguration();
143 
144     setAWTGraphicsConfiguration(awtConfig);
145 
146     // issues getGraphicsConfiguration() and creates the native peer
147     super.addNotify();
148 
149     // after native peer is valid: Windows
150     disableBackgroundErase();
151 
152     {
153         jawtWindow = (JAWTWindow) NativeWindowFactory.getNativeWindow(this, awtConfig);
154         // trigger initialization cycle
155         jawtWindow.lockSurface();
156         try {
157             jawtWindow.setSurfaceScale(upstreamScale.getReqPixelScale() );
158             upstreamScale.setHasPixelScale(jawtWindow.getCurrentSurfaceScale(new float[2]));
159         } finally {
160             jawtWindow.unlockSurface();
161         }
162     }
163 
164     final GraphicsConfiguration gc = super.getGraphicsConfiguration();
165     if(null!=gc) {
166         device = gc.getDevice();
167     }
168     driver.localCreate();
169     if(Window.DEBUG_IMPLEMENTATION) {
170         System.err.println(getThreadName()+": AWTCanvas.addNotify.X");
171     }
172   }
173 
getNativeWindow()174   public NativeWindow getNativeWindow() {
175     final JAWTWindow _jawtWindow = jawtWindow;
176     return (null != _jawtWindow) ? _jawtWindow : null;
177   }
178 
isOffscreenLayerSurfaceEnabled()179   public boolean isOffscreenLayerSurfaceEnabled() {
180       return null != jawtWindow ? jawtWindow.isOffscreenLayerSurfaceEnabled() : false;
181   }
182 
setAWTGraphicsConfiguration(final AWTGraphicsConfiguration config)183   private void setAWTGraphicsConfiguration(final AWTGraphicsConfiguration config) {
184     // Cache awtConfig
185     awtConfig = config;
186     if( null != jawtWindow ) {
187         // Notify JAWTWindow ..
188         jawtWindow.setAWTGraphicsConfiguration(config);
189     }
190   }
191 
192   @Override
removeNotify()193   public void removeNotify() {
194       if(Window.DEBUG_IMPLEMENTATION) {
195           System.err.println(getThreadName()+": AWTCanvas.removeNotify.0: Created Config: "+awtConfig);
196       }
197       try {
198         driver.localDestroy();
199       } finally {
200         super.removeNotify();
201       }
202   }
203 
dispose()204   void dispose() {
205     if( null != jawtWindow ) {
206         jawtWindow.destroy();
207         if(Window.DEBUG_IMPLEMENTATION) {
208             System.err.println(getThreadName()+": AWTCanvas.disposeJAWTWindowAndAWTDeviceOnEDT(): post JAWTWindow: "+jawtWindow);
209         }
210         jawtWindow=null;
211     }
212     if(null != awtConfig) {
213         final AbstractGraphicsDevice adevice = awtConfig.getNativeGraphicsConfiguration().getScreen().getDevice();
214         String adeviceMsg=null;
215         if(Window.DEBUG_IMPLEMENTATION) {
216             adeviceMsg = adevice.toString();
217         }
218         final boolean closed = adevice.close();
219         if(Window.DEBUG_IMPLEMENTATION) {
220             System.err.println(getThreadName()+": AWTCanvas.dispose(): closed GraphicsDevice: "+adeviceMsg+", result: "+closed);
221         }
222     }
223     awtConfig = null;
224   }
225 
getThreadName()226   private String getThreadName() { return Thread.currentThread().getName(); }
227 
228   /**
229    * Overridden to choose a GraphicsConfiguration on a parent container's
230    * GraphicsDevice because both devices
231    */
232   @Override
getGraphicsConfiguration()233   public GraphicsConfiguration getGraphicsConfiguration() {
234     /*
235      * Workaround for problems with Xinerama and java.awt.Component.checkGD
236      * when adding to a container on a different graphics device than the
237      * one that this Canvas is associated with.
238      *
239      * GC will be null unless:
240      *   - A native peer has assigned it. This means we have a native
241      *     peer, and are already comitted to a graphics configuration.
242      *   - This canvas has been added to a component hierarchy and has
243      *     an ancestor with a non-null GC, but the native peer has not
244      *     yet been created. This means we can still choose the GC on
245      *     all platforms since the peer hasn't been created.
246      */
247     final GraphicsConfiguration gc = super.getGraphicsConfiguration();
248     /*
249      * chosen is only non-null on platforms where the GLDrawableFactory
250      * returns a non-null GraphicsConfiguration (in the GLCanvas
251      * constructor).
252      *
253      * if gc is from this Canvas' native peer then it should equal chosen,
254      * otherwise it is from an ancestor component that this Canvas is being
255      * added to, and we go into this block.
256      */
257     if (gc != null && chosen != null && !chosen.equals(gc)) {
258       /*
259        * Check for compatibility with gc. If they differ by only the
260        * device then return a new GCconfig with the super-class' GDevice
261        * (and presumably the same visual ID in Xinerama).
262        *
263        */
264       if (!chosen.getDevice().getIDstring().equals(gc.getDevice().getIDstring())) {
265         /*
266          * Here we select a GraphicsConfiguration on the alternate
267          * device that is presumably identical to the chosen
268          * configuration, but on the other device.
269          *
270          * Should really check to ensure that we select a configuration
271          * with the same X visual ID for Xinerama screens, otherwise the
272          * GLDrawable may have the wrong visual ID (I don't think this
273          * ever gets updated). May need to add a method to
274          * X11GLDrawableFactory to do this in a platform specific
275          * manner.
276          *
277          * However, on platforms where we can actually get into this
278          * block, both devices should have the same visual list, and the
279          * same configuration should be selected here.
280          */
281         final AWTGraphicsConfiguration newConfig = chooseGraphicsConfiguration(
282                 awtConfig.getChosenCapabilities(), awtConfig.getRequestedCapabilities(), chooser, gc.getDevice());
283         final GraphicsConfiguration compatible = (null!=newConfig)?newConfig.getAWTGraphicsConfiguration():null;
284         if(Window.DEBUG_IMPLEMENTATION) {
285             final Exception e = new Exception("Info: Call Stack: "+Thread.currentThread().getName());
286             e.printStackTrace();
287             System.err.println("Created Config (n): HAVE    GC "+chosen);
288             System.err.println("Created Config (n): THIS    GC "+gc);
289             System.err.println("Created Config (n): Choosen GC "+compatible);
290             System.err.println("Created Config (n): HAVE    CF "+awtConfig);
291             System.err.println("Created Config (n): Choosen CF "+newConfig);
292             System.err.println("Created Config (n): EQUALS CAPS "+newConfig.getChosenCapabilities().equals(awtConfig.getChosenCapabilities()));
293         }
294 
295         if (compatible != null) {
296           /*
297            * Save the new GC for equals test above, and to return to
298            * any outside callers of this method.
299            */
300           chosen = compatible;
301           if( !newConfig.getChosenCapabilities().equals(awtConfig.getChosenCapabilities())) {
302               displayConfigChanged=true;
303           }
304           setAWTGraphicsConfiguration(newConfig);
305         }
306       }
307 
308       /*
309        * If a compatible GC was not found in the block above, this will
310        * return the GC that was selected in the constructor (and might
311        * cause an exception in Component.checkGD when adding to a
312        * container, but in this case that would be the desired behavior).
313        *
314        */
315       return chosen;
316     } else if (gc == null) {
317       /*
318        * The GC is null, which means we have no native peer, and are not
319        * part of a (realized) component hierarchy. So we return the
320        * desired visual that was selected in the constructor (possibly
321        * null).
322        */
323       return chosen;
324     }
325 
326     /*
327      * Otherwise we have not explicitly selected a GC in the constructor, so
328      * just return what Canvas would have.
329      */
330     return gc;
331   }
332 
chooseGraphicsConfiguration(final CapabilitiesImmutable capsChosen, final CapabilitiesImmutable capsRequested, final CapabilitiesChooser chooser, final GraphicsDevice device)333   private static AWTGraphicsConfiguration chooseGraphicsConfiguration(final CapabilitiesImmutable capsChosen,
334                                                                       final CapabilitiesImmutable capsRequested,
335                                                                       final CapabilitiesChooser chooser,
336                                                                       final GraphicsDevice device) {
337     final AbstractGraphicsScreen aScreen = null != device ?
338             AWTGraphicsScreen.createScreenDevice(device, AbstractGraphicsDevice.DEFAULT_UNIT):
339             AWTGraphicsScreen.createDefault();
340     final AWTGraphicsConfiguration config = (AWTGraphicsConfiguration)
341       GraphicsConfigurationFactory.getFactory(AWTGraphicsDevice.class, capsChosen.getClass()).chooseGraphicsConfiguration(capsChosen,
342                                                                                                    capsRequested,
343                                                                                                    chooser, aScreen, VisualIDHolder.VID_UNDEFINED);
344     if (config == null) {
345       throw new NativeWindowException("Error: Couldn't fetch AWTGraphicsConfiguration");
346     }
347 
348     return config;
349   }
350 
351   // Disables the AWT's erasing of this Canvas's background on Windows
352   // in Java SE 6. This internal API is not available in previous
353   // releases, but the system property
354   // -Dsun.awt.noerasebackground=true can be specified to get similar
355   // results globally in previous releases.
356   private static boolean disableBackgroundEraseInitialized;
357   private static Method  disableBackgroundEraseMethod;
disableBackgroundErase()358   private void disableBackgroundErase() {
359     if (!disableBackgroundEraseInitialized) {
360       try {
361         AccessController.doPrivileged(new PrivilegedAction<Object>() {
362             @Override
363             public Object run() {
364               try {
365                 Class<?> clazz = getToolkit().getClass();
366                 while (clazz != null && disableBackgroundEraseMethod == null) {
367                   try {
368                     disableBackgroundEraseMethod =
369                       clazz.getDeclaredMethod("disableBackgroundErase",
370                                               new Class[] { Canvas.class });
371                     disableBackgroundEraseMethod.setAccessible(true);
372                   } catch (final Exception e) {
373                     clazz = clazz.getSuperclass();
374                   }
375                 }
376               } catch (final Exception e) {
377               }
378               return null;
379             }
380           });
381       } catch (final Exception e) {
382       }
383       disableBackgroundEraseInitialized = true;
384       if(Window.DEBUG_IMPLEMENTATION) {
385         System.err.println("AWTCanvas: TK disableBackgroundErase method found: "+
386                 (null!=disableBackgroundEraseMethod));
387       }
388     }
389     if (disableBackgroundEraseMethod != null) {
390       Throwable t=null;
391       try {
392         disableBackgroundEraseMethod.invoke(getToolkit(), new Object[] { this });
393       } catch (final Exception e) {
394         // FIXME: workaround for 6504460 (incorrect backport of 6333613 in 5.0u10)
395         // throw new GLException(e);
396         t = e;
397       }
398       if(Window.DEBUG_IMPLEMENTATION) {
399         System.err.println("AWTCanvas: TK disableBackgroundErase error: "+t);
400       }
401     }
402   }
403 }
404