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