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