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