1 /* 2 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.java2d.d3d; 27 28 import java.awt.Color; 29 import java.awt.Component; 30 import java.awt.Container; 31 import java.awt.Font; 32 import java.awt.Graphics2D; 33 import java.awt.Rectangle; 34 import java.awt.Window; 35 import java.security.AccessController; 36 import java.security.PrivilegedAction; 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import sun.awt.SunToolkit; 40 import sun.awt.AWTAccessor; 41 import sun.awt.Win32GraphicsConfig; 42 import sun.awt.windows.WComponentPeer; 43 import sun.java2d.InvalidPipeException; 44 import sun.java2d.ScreenUpdateManager; 45 import sun.java2d.SunGraphics2D; 46 import sun.java2d.SurfaceData; 47 import sun.java2d.windows.GDIWindowSurfaceData; 48 import sun.java2d.d3d.D3DSurfaceData.D3DWindowSurfaceData; 49 import sun.java2d.windows.WindowsFlags; 50 51 /** 52 * This class handles rendering to the screen with the D3D pipeline. 53 * 54 * Since it is not possible to render directly to the front buffer 55 * with D3D9, we create a swap chain surface (with COPY effect) in place of the 56 * GDIWindowSurfaceData. A background thread handles the swap chain flips. 57 * 58 * There are some restrictions to which windows we would use this for. 59 * @see #createScreenSurface() 60 */ 61 public class D3DScreenUpdateManager extends ScreenUpdateManager 62 implements Runnable 63 { 64 /** 65 * A window must be at least MIN_WIN_SIZE in one or both dimensions 66 * to be considered for the update manager. 67 */ 68 private static final int MIN_WIN_SIZE = 150; 69 70 private volatile boolean done; 71 private volatile Thread screenUpdater; 72 private boolean needsUpdateNow; 73 74 /** 75 * Object used by the screen updater thread for waiting 76 */ 77 private Object runLock = new Object(); 78 /** 79 * List of D3DWindowSurfaceData surfaces. Surfaces are added to the 80 * list when a graphics object is created, and removed when the surface 81 * is invalidated. 82 */ 83 private ArrayList<D3DWindowSurfaceData> d3dwSurfaces; 84 /** 85 * Cache of GDIWindowSurfaceData surfaces corresponding to the 86 * D3DWindowSurfaceData surfaces. Surfaces are added to the list when 87 * a d3dw surface is lost and could not be restored (due to lack of vram, 88 * for example), and removed then the d3dw surface is invalidated. 89 */ 90 private HashMap<D3DWindowSurfaceData, GDIWindowSurfaceData> gdiSurfaces; 91 92 public D3DScreenUpdateManager() { 93 done = false; 94 AccessController.doPrivileged( 95 new PrivilegedAction() { 96 public Object run() { 97 ThreadGroup currentTG = 98 Thread.currentThread().getThreadGroup(); 99 ThreadGroup parentTG = currentTG.getParent(); 100 while (parentTG != null) { 101 currentTG = parentTG; 102 parentTG = currentTG.getParent(); 103 } 104 Thread shutdown = new Thread(currentTG, new Runnable() { 105 public void run() { 106 done = true; 107 wakeUpUpdateThread(); 108 } 109 }); 110 shutdown.setContextClassLoader(null); 111 try { 112 Runtime.getRuntime().addShutdownHook(shutdown); 113 } catch (Exception e) { 114 done = true; 115 } 116 return null; 117 } 118 } 119 ); 120 } 121 122 /** 123 * If possible, creates a D3DWindowSurfaceData (which is actually 124 * a back-buffer surface). If the creation fails, returns GDI 125 * onscreen surface instead. 126 * 127 * Note that the created D3D surface does not initialize the native 128 * resources (and is marked lost) to avoid wasting video memory. It is 129 * restored when a graphics object is requested from the peer. 130 * 131 * Note that this method is called from a synchronized block in 132 * WComponentPeer, so we don't need to synchronize 133 * 134 * Note that we only create a substibute d3dw surface if certain conditions 135 * are met 136 * <ul> 137 * <li>the fake d3d rendering on screen is not disabled via flag 138 * <li>d3d on the device is enabled 139 * <li>surface is larger than MIN_WIN_SIZE (don't bother for smaller ones) 140 * <li>it doesn't have a backBuffer for a BufferStrategy already 141 * <li>the peer is either Canvas, Panel, Window, Frame, 142 * Dialog or EmbeddedFrame 143 * </ul> 144 * 145 * @param gc GraphicsConfiguration on associated with the surface 146 * @param peer peer for which the surface is to be created 147 * @param bbNum number of back-buffers requested. if this number is >0, 148 * method returns GDI surface (we don't want to have two swap chains) 149 * @param isResize whether this surface is being created in response to 150 * a component resize event. This determines whether a repaint event will 151 * be issued after a surface is created: it will be if <code>isResize</code> 152 * is <code>true</code>. 153 * @return surface data to be use for onscreen rendering 154 */ 155 @Override 156 public SurfaceData createScreenSurface(Win32GraphicsConfig gc, 157 WComponentPeer peer, 158 int bbNum, boolean isResize) 159 { 160 if (done || !(gc instanceof D3DGraphicsConfig)) { 161 return super.createScreenSurface(gc, peer, bbNum, isResize); 162 } 163 164 SurfaceData sd = null; 165 166 if (canUseD3DOnScreen(peer, gc, bbNum)) { 167 try { 168 // note that the created surface will be in the "lost" 169 // state, it will be restored prior to rendering to it 170 // for the first time. This is done so that vram is not 171 // wasted for surfaces never rendered to 172 sd = D3DSurfaceData.createData(peer); 173 } catch (InvalidPipeException ipe) { 174 sd = null; 175 } 176 } 177 if (sd == null) { 178 sd = GDIWindowSurfaceData.createData(peer); 179 // note that we do not add this surface to the list of cached gdi 180 // surfaces as there's no d3dw surface to associate it with; 181 // this peer will have a gdi surface until next time a surface 182 // will need to be replaced 183 } 184 185 if (isResize) { 186 // since we'd potentially replaced the back-buffer surface 187 // (either with another bb, or a gdi one), the 188 // component will need to be completely repainted; 189 // this only need to be done when the surface is created in 190 // response to a resize event since when a component is created it 191 // will be repainted anyway 192 repaintPeerTarget(peer); 193 } 194 195 return sd; 196 } 197 198 /** 199 * Determines if we can use a d3d surface for onscreen rendering for this 200 * peer. 201 * We only create onscreen d3d surfaces if the following conditions are met: 202 * - d3d is enabled on this device and onscreen emulation is enabled 203 * - window is big enough to bother (either dimension > MIN_WIN_SIZE) 204 * - this heavyweight doesn't have a BufferStrategy 205 * - if we are in full-screen mode then it must be the peer of the 206 * full-screen window (since there could be only one SwapChain in fs) 207 * and it must not have any heavyweight children 208 * (as Present() doesn't respect component clipping in fullscreen mode) 209 * - it's one of the classes likely to have custom rendering worth 210 * accelerating 211 * 212 * @returns true if we can use a d3d surface for this peer's onscreen 213 * rendering 214 */ 215 public static boolean canUseD3DOnScreen(final WComponentPeer peer, 216 final Win32GraphicsConfig gc, 217 final int bbNum) 218 { 219 if (!(gc instanceof D3DGraphicsConfig)) { 220 return false; 221 } 222 D3DGraphicsConfig d3dgc = (D3DGraphicsConfig)gc; 223 D3DGraphicsDevice d3dgd = d3dgc.getD3DDevice(); 224 String peerName = peer.getClass().getName(); 225 Rectangle r = peer.getBounds(); 226 Component target = (Component)peer.getTarget(); 227 Window fsw = d3dgd.getFullScreenWindow(); 228 229 return 230 WindowsFlags.isD3DOnScreenEnabled() && 231 d3dgd.isD3DEnabledOnDevice() && 232 peer.isAccelCapable() && 233 (r.width > MIN_WIN_SIZE || r.height > MIN_WIN_SIZE) && 234 bbNum == 0 && 235 (fsw == null || (fsw == target && !hasHWChildren(target))) && 236 (peerName.equals("sun.awt.windows.WCanvasPeer") || 237 peerName.equals("sun.awt.windows.WDialogPeer") || 238 peerName.equals("sun.awt.windows.WPanelPeer") || 239 peerName.equals("sun.awt.windows.WWindowPeer") || 240 peerName.equals("sun.awt.windows.WFramePeer") || 241 peerName.equals("sun.awt.windows.WEmbeddedFramePeer")); 242 } 243 244 /** 245 * Creates a graphics object for the passed in surface data. If 246 * the surface is lost, it is restored. 247 * If the surface wasn't lost or the restoration was successful 248 * the surface is added to the list of maintained surfaces 249 * (if it hasn't been already). 250 * 251 * If the updater thread hasn't been created yet , it will be created and 252 * started. 253 * 254 * @param sd surface data for which to create SunGraphics2D 255 * @param peer peer associated with the surface data 256 * @param fgColor fg color to be used in graphics 257 * @param bgColor bg color to be used in graphics 258 * @param font font to be used in graphics 259 * @return a SunGraphics2D object for the surface (or for temp GDI 260 * surface data) 261 */ 262 @Override 263 public Graphics2D createGraphics(SurfaceData sd, 264 WComponentPeer peer, Color fgColor, Color bgColor, Font font) 265 { 266 if (!done && sd instanceof D3DWindowSurfaceData) { 267 D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd; 268 if (!d3dw.isSurfaceLost() || validate(d3dw)) { 269 trackScreenSurface(d3dw); 270 return new SunGraphics2D(sd, fgColor, bgColor, font); 271 } 272 // could not restore the d3dw surface, use the cached gdi surface 273 // instead for this graphics object; note that we do not track 274 // this new gdi surface, it is only used for this graphics 275 // object 276 sd = getGdiSurface(d3dw); 277 } 278 return super.createGraphics(sd, peer, fgColor, bgColor, font); 279 } 280 281 /** 282 * Posts a repaint event for the peer's target to the EDT 283 * @param peer for which target's the repaint should be issued 284 */ 285 private void repaintPeerTarget(WComponentPeer peer) { 286 Component target = (Component)peer.getTarget(); 287 Rectangle bounds = AWTAccessor.getComponentAccessor().getBounds(target); 288 // the system-level painting operations should call the handlePaint() 289 // method of the WComponentPeer class to repaint the component; 290 // calling repaint() forces AWT to make call to update() 291 peer.handlePaint(0, 0, bounds.width, bounds.height); 292 } 293 294 /** 295 * Adds a surface to the list of tracked surfaces. 296 * 297 * @param d3dw the surface to be added 298 */ 299 private void trackScreenSurface(SurfaceData sd) { 300 if (!done && sd instanceof D3DWindowSurfaceData) { 301 synchronized (this) { 302 if (d3dwSurfaces == null) { 303 d3dwSurfaces = new ArrayList<D3DWindowSurfaceData>(); 304 } 305 D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd; 306 if (!d3dwSurfaces.contains(d3dw)) { 307 d3dwSurfaces.add(d3dw); 308 } 309 } 310 startUpdateThread(); 311 } 312 } 313 314 @Override 315 public synchronized void dropScreenSurface(SurfaceData sd) { 316 if (d3dwSurfaces != null && sd instanceof D3DWindowSurfaceData) { 317 D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd; 318 removeGdiSurface(d3dw); 319 d3dwSurfaces.remove(d3dw); 320 } 321 } 322 323 @Override 324 public SurfaceData getReplacementScreenSurface(WComponentPeer peer, 325 SurfaceData sd) 326 { 327 SurfaceData newSurface = super.getReplacementScreenSurface(peer, sd); 328 // if some outstanding graphics context wants to get a replacement we 329 // need to make sure that the new surface (if it is accelerated) is 330 // being tracked 331 trackScreenSurface(newSurface); 332 return newSurface; 333 } 334 335 /** 336 * Remove the gdi surface corresponding to the passed d3dw surface 337 * from list of the cached gdi surfaces. 338 * 339 * @param d3dw surface for which associated gdi surface is to be removed 340 */ 341 private void removeGdiSurface(final D3DWindowSurfaceData d3dw) { 342 if (gdiSurfaces != null) { 343 GDIWindowSurfaceData gdisd = gdiSurfaces.get(d3dw); 344 if (gdisd != null) { 345 gdisd.invalidate(); 346 gdiSurfaces.remove(d3dw); 347 } 348 } 349 } 350 351 /** 352 * If the update thread hasn't yet been created, it will be; 353 * otherwise it is awaken 354 */ 355 private synchronized void startUpdateThread() { 356 if (screenUpdater == null) { 357 screenUpdater = (Thread)java.security.AccessController.doPrivileged( 358 new java.security.PrivilegedAction() { 359 public Object run() { 360 ThreadGroup tg = 361 Thread.currentThread().getThreadGroup(); 362 for (ThreadGroup tgn = tg; 363 tgn != null; tg = tgn, tgn = tg.getParent()); 364 Thread t = new Thread(tg, D3DScreenUpdateManager.this, 365 "D3D Screen Updater"); 366 // REMIND: should it be higher? 367 t.setPriority(Thread.NORM_PRIORITY + 2); 368 t.setDaemon(true); 369 return t; 370 } 371 }); 372 screenUpdater.start(); 373 } else { 374 wakeUpUpdateThread(); 375 } 376 } 377 378 /** 379 * Wakes up the screen updater thread. 380 * 381 * This method is not synchronous, it doesn't wait 382 * for the updater thread to complete the updates. 383 * 384 * It should be used when it is not necessary to wait for the 385 * completion, for example, when a new surface had been added 386 * to the list of tracked surfaces (which means that it's about 387 * to be rendered to). 388 */ 389 public void wakeUpUpdateThread() { 390 synchronized (runLock) { 391 runLock.notifyAll(); 392 } 393 } 394 395 /** 396 * Wakes up the screen updater thread and waits for the completion 397 * of the update. 398 * 399 * This method is called from Toolkit.sync() or 400 * when there was a copy from a VI to the screen 401 * so that swing applications would not appear to be 402 * sluggish. 403 */ 404 public void runUpdateNow() { 405 synchronized (this) { 406 // nothing to do if the updater thread hadn't been started or if 407 // there are no tracked surfaces 408 if (done || screenUpdater == null || 409 d3dwSurfaces == null || d3dwSurfaces.size() == 0) 410 { 411 return; 412 } 413 } 414 synchronized (runLock) { 415 needsUpdateNow = true; 416 runLock.notifyAll(); 417 while (needsUpdateNow) { 418 try { 419 runLock.wait(); 420 } catch (InterruptedException e) {} 421 } 422 } 423 } 424 425 public void run() { 426 while (!done) { 427 synchronized (runLock) { 428 // If the list is empty, suspend the thread until a 429 // new surface is added. Note that we have to check before 430 // wait() (and inside the runLock), otherwise we could miss a 431 // notify() when a new surface is added and sleep forever. 432 long timeout = d3dwSurfaces.size() > 0 ? 100 : 0; 433 434 // don't go to sleep if there's a thread waiting for an update 435 if (!needsUpdateNow) { 436 try { runLock.wait(timeout); } 437 catch (InterruptedException e) {} 438 } 439 // if we were woken up, there are probably surfaces in the list, 440 // no need to check if the list is empty 441 } 442 443 // make a copy to avoid synchronization during the loop 444 D3DWindowSurfaceData surfaces[] = new D3DWindowSurfaceData[] {}; 445 synchronized (this) { 446 surfaces = d3dwSurfaces.toArray(surfaces); 447 } 448 for (D3DWindowSurfaceData sd : surfaces) { 449 // skip invalid surfaces (they could have become invalid 450 // after we made a copy of the list) - just a precaution 451 if (sd.isValid() && (sd.isDirty() || sd.isSurfaceLost())) { 452 if (!sd.isSurfaceLost()) { 453 // the flip and the clearing of the dirty state 454 // must be done under the lock, otherwise it's 455 // possible to miss an update to the surface 456 D3DRenderQueue rq = D3DRenderQueue.getInstance(); 457 rq.lock(); 458 try { 459 Rectangle r = sd.getBounds(); 460 D3DSurfaceData.swapBuffers(sd, 0, 0, 461 r.width, r.height); 462 sd.markClean(); 463 } finally { 464 rq.unlock(); 465 } 466 } else if (!validate(sd)) { 467 // it is possible that the validation may never 468 // succeed, we need to detect this and replace 469 // the d3dw surface with gdi; the replacement of 470 // the surface will also trigger a repaint 471 sd.getPeer().replaceSurfaceDataLater(); 472 } 473 } 474 } 475 synchronized (runLock) { 476 needsUpdateNow = false; 477 runLock.notifyAll(); 478 } 479 } 480 } 481 482 /** 483 * Restores the passed surface if it was lost, resets the lost status. 484 * @param sd surface to be validated 485 * @return true if surface wasn't lost or if restoration was successful, 486 * false otherwise 487 */ 488 private boolean validate(D3DWindowSurfaceData sd) { 489 if (sd.isSurfaceLost()) { 490 try { 491 sd.restoreSurface(); 492 // if succeeded, first fill the surface with bg color 493 // note: use the non-synch method to avoid incorrect lock order 494 Color bg = sd.getPeer().getBackgroundNoSync(); 495 SunGraphics2D sg2d = new SunGraphics2D(sd, bg, bg, null); 496 sg2d.fillRect(0, 0, sd.getBounds().width, sd.getBounds().height); 497 sg2d.dispose(); 498 // now clean the dirty status so that we don't flip it 499 // next time before it gets repainted; it is safe 500 // to do without the lock because we will issue a 501 // repaint anyway so we will not lose any rendering 502 sd.markClean(); 503 // since the surface was successfully restored we need to 504 // repaint whole window to repopulate the back-buffer 505 repaintPeerTarget(sd.getPeer()); 506 } catch (InvalidPipeException ipe) { 507 return false; 508 } 509 } 510 return true; 511 } 512 513 /** 514 * Creates (or returns a cached one) gdi surface for the same peer as 515 * the passed d3dw surface has. 516 * 517 * @param d3dw surface used as key into the cache 518 * @return gdi window surface associated with the d3d window surfaces' peer 519 */ 520 private synchronized SurfaceData getGdiSurface(D3DWindowSurfaceData d3dw) { 521 if (gdiSurfaces == null) { 522 gdiSurfaces = 523 new HashMap<D3DWindowSurfaceData, GDIWindowSurfaceData>(); 524 } 525 GDIWindowSurfaceData gdisd = gdiSurfaces.get(d3dw); 526 if (gdisd == null) { 527 gdisd = GDIWindowSurfaceData.createData(d3dw.getPeer()); 528 gdiSurfaces.put(d3dw, gdisd); 529 } 530 return gdisd; 531 } 532 533 /** 534 * Returns true if the component has heavyweight children. 535 * 536 * @param comp component to check for hw children 537 * @return true if Component has heavyweight children 538 */ 539 private static boolean hasHWChildren(Component comp) { 540 if (comp instanceof Container) { 541 for (Component c : ((Container)comp).getComponents()) { 542 if (c.getPeer() instanceof WComponentPeer || hasHWChildren(c)) { 543 return true; 544 } 545 } 546 } 547 return false; 548 } 549 }