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