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 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.util.ThreadGroupUtils; 42 import sun.awt.Win32GraphicsConfig; 43 import sun.awt.windows.WComponentPeer; 44 import sun.java2d.InvalidPipeException; 45 import sun.java2d.ScreenUpdateManager; 46 import sun.java2d.SunGraphics2D; 47 import sun.java2d.SurfaceData; 48 import sun.java2d.windows.GDIWindowSurfaceData; 49 import sun.java2d.d3d.D3DSurfaceData.D3DWindowSurfaceData; 50 import sun.java2d.windows.WindowsFlags; 51 52 /** 53 * This class handles rendering to the screen with the D3D pipeline. 54 * 55 * Since it is not possible to render directly to the front buffer 56 * with D3D9, we create a swap chain surface (with COPY effect) in place of the 57 * GDIWindowSurfaceData. A background thread handles the swap chain flips. 58 * 59 * There are some restrictions to which windows we would use this for. 60 * @see #createScreenSurface() 61 */ 62 public class D3DScreenUpdateManager extends ScreenUpdateManager 63 implements Runnable 64 { 65 /** 66 * A window must be at least MIN_WIN_SIZE in one or both dimensions 67 * to be considered for the update manager. 68 */ 69 private static final int MIN_WIN_SIZE = 150; 70 71 private volatile boolean done; 72 private volatile Thread screenUpdater; 73 private boolean needsUpdateNow; 74 75 /** 76 * Object used by the screen updater thread for waiting 77 */ 78 private Object runLock = new Object(); 79 /** 80 * List of D3DWindowSurfaceData surfaces. Surfaces are added to the 81 * list when a graphics object is created, and removed when the surface 82 * is invalidated. 83 */ 84 private ArrayList<D3DWindowSurfaceData> d3dwSurfaces; 85 /** 86 * Cache of GDIWindowSurfaceData surfaces corresponding to the 87 * D3DWindowSurfaceData surfaces. Surfaces are added to the list when 88 * a d3dw surface is lost and could not be restored (due to lack of vram, 89 * for example), and removed then the d3dw surface is invalidated. 90 */ 91 private HashMap<D3DWindowSurfaceData, GDIWindowSurfaceData> gdiSurfaces; 92 93 public D3DScreenUpdateManager() { 94 done = false; 95 AccessController.doPrivileged( 96 (PrivilegedAction<Void>) () -> { 97 ThreadGroup rootTG = ThreadGroupUtils.getRootThreadGroup(); 98 Thread shutdown = new Thread(rootTG, () -> { 99 done = true; 100 wakeUpUpdateThread(); 101 }); 102 shutdown.setContextClassLoader(null); 103 try { 104 Runtime.getRuntime().addShutdownHook(shutdown); 105 } catch (Exception e) { 106 done = true; 107 } 108 return null; 109 } 110 ); 111 } 112 113 /** 114 * If possible, creates a D3DWindowSurfaceData (which is actually 115 * a back-buffer surface). If the creation fails, returns GDI 116 * onscreen surface instead. 117 * 118 * Note that the created D3D surface does not initialize the native 119 * resources (and is marked lost) to avoid wasting video memory. It is 120 * restored when a graphics object is requested from the peer. 121 * 122 * Note that this method is called from a synchronized block in 123 * WComponentPeer, so we don't need to synchronize 124 * 125 * Note that we only create a substibute d3dw surface if certain conditions 126 * are met 127 * <ul> 128 * <li>the fake d3d rendering on screen is not disabled via flag 129 * <li>d3d on the device is enabled 130 * <li>surface is larger than MIN_WIN_SIZE (don't bother for smaller ones) 131 * <li>it doesn't have a backBuffer for a BufferStrategy already 132 * <li>the peer is either Canvas, Panel, Window, Frame, 133 * Dialog or EmbeddedFrame 134 * </ul> 135 * 136 * @param gc GraphicsConfiguration on associated with the surface 137 * @param peer peer for which the surface is to be created 138 * @param bbNum number of back-buffers requested. if this number is >0, 139 * method returns GDI surface (we don't want to have two swap chains) 140 * @param isResize whether this surface is being created in response to 141 * a component resize event. This determines whether a repaint event will 142 * be issued after a surface is created: it will be if <code>isResize</code> 143 * is <code>true</code>. 144 * @return surface data to be use for onscreen rendering 145 */ 146 @Override 147 public SurfaceData createScreenSurface(Win32GraphicsConfig gc, 148 WComponentPeer peer, 149 int bbNum, boolean isResize) 150 { 151 if (done || !(gc instanceof D3DGraphicsConfig)) { 152 return super.createScreenSurface(gc, peer, bbNum, isResize); 153 } 154 155 SurfaceData sd = null; 156 157 if (canUseD3DOnScreen(peer, gc, bbNum)) { 158 try { 159 // note that the created surface will be in the "lost" 160 // state, it will be restored prior to rendering to it 161 // for the first time. This is done so that vram is not 162 // wasted for surfaces never rendered to 163 sd = D3DSurfaceData.createData(peer); 164 } catch (InvalidPipeException ipe) { 165 sd = null; 166 } 167 } 168 if (sd == null) { 169 sd = GDIWindowSurfaceData.createData(peer); 170 // note that we do not add this surface to the list of cached gdi 171 // surfaces as there's no d3dw surface to associate it with; 172 // this peer will have a gdi surface until next time a surface 173 // will need to be replaced 174 } 175 176 if (isResize) { 177 // since we'd potentially replaced the back-buffer surface 178 // (either with another bb, or a gdi one), the 179 // component will need to be completely repainted; 180 // this only need to be done when the surface is created in 181 // response to a resize event since when a component is created it 182 // will be repainted anyway 183 repaintPeerTarget(peer); 184 } 185 186 return sd; 187 } 188 189 /** 190 * Determines if we can use a d3d surface for onscreen rendering for this 191 * peer. 192 * We only create onscreen d3d surfaces if the following conditions are met: 193 * - d3d is enabled on this device and onscreen emulation is enabled 194 * - window is big enough to bother (either dimension > MIN_WIN_SIZE) 195 * - this heavyweight doesn't have a BufferStrategy 196 * - if we are in full-screen mode then it must be the peer of the 197 * full-screen window (since there could be only one SwapChain in fs) 198 * and it must not have any heavyweight children 199 * (as Present() doesn't respect component clipping in fullscreen mode) 200 * - it's one of the classes likely to have custom rendering worth 201 * accelerating 202 * 203 * @returns true if we can use a d3d surface for this peer's onscreen 204 * rendering 205 */ 206 public static boolean canUseD3DOnScreen(final WComponentPeer peer, 207 final Win32GraphicsConfig gc, 208 final int bbNum) 209 { 210 if (!(gc instanceof D3DGraphicsConfig)) { 211 return false; 212 } 213 D3DGraphicsConfig d3dgc = (D3DGraphicsConfig)gc; 214 D3DGraphicsDevice d3dgd = d3dgc.getD3DDevice(); 215 String peerName = peer.getClass().getName(); 216 Rectangle r = peer.getBounds(); 217 Component target = (Component)peer.getTarget(); 218 Window fsw = d3dgd.getFullScreenWindow(); 219 220 return 221 WindowsFlags.isD3DOnScreenEnabled() && 222 d3dgd.isD3DEnabledOnDevice() && 223 peer.isAccelCapable() && 224 (r.width > MIN_WIN_SIZE || r.height > MIN_WIN_SIZE) && 225 bbNum == 0 && 226 (fsw == null || (fsw == target && !hasHWChildren(target))) && 227 (peerName.equals("sun.awt.windows.WCanvasPeer") || 228 peerName.equals("sun.awt.windows.WDialogPeer") || 229 peerName.equals("sun.awt.windows.WPanelPeer") || 230 peerName.equals("sun.awt.windows.WWindowPeer") || 231 peerName.equals("sun.awt.windows.WFramePeer") || 232 peerName.equals("sun.awt.windows.WEmbeddedFramePeer")); 233 } 234 235 /** 236 * Creates a graphics object for the passed in surface data. If 237 * the surface is lost, it is restored. 238 * If the surface wasn't lost or the restoration was successful 239 * the surface is added to the list of maintained surfaces 240 * (if it hasn't been already). 241 * 242 * If the updater thread hasn't been created yet , it will be created and 243 * started. 244 * 245 * @param sd surface data for which to create SunGraphics2D 246 * @param peer peer associated with the surface data 247 * @param fgColor fg color to be used in graphics 248 * @param bgColor bg color to be used in graphics 249 * @param font font to be used in graphics 250 * @return a SunGraphics2D object for the surface (or for temp GDI 251 * surface data) 252 */ 253 @Override 254 public Graphics2D createGraphics(SurfaceData sd, 255 WComponentPeer peer, Color fgColor, Color bgColor, Font font) 256 { 257 if (!done && sd instanceof D3DWindowSurfaceData) { 258 D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd; 259 if (!d3dw.isSurfaceLost() || validate(d3dw)) { 260 trackScreenSurface(d3dw); 261 return new SunGraphics2D(sd, fgColor, bgColor, font); 262 } 263 // could not restore the d3dw surface, use the cached gdi surface 264 // instead for this graphics object; note that we do not track 265 // this new gdi surface, it is only used for this graphics 266 // object 267 sd = getGdiSurface(d3dw); 268 } 269 return super.createGraphics(sd, peer, fgColor, bgColor, font); 270 } 271 272 /** 273 * Posts a repaint event for the peer's target to the EDT 274 * @param peer for which target's the repaint should be issued 275 */ 276 private void repaintPeerTarget(WComponentPeer peer) { 277 Component target = (Component)peer.getTarget(); 278 Rectangle bounds = AWTAccessor.getComponentAccessor().getBounds(target); 279 // the system-level painting operations should call the handlePaint() 280 // method of the WComponentPeer class to repaint the component; 281 // calling repaint() forces AWT to make call to update() 282 peer.handlePaint(0, 0, bounds.width, bounds.height); 283 } 284 285 /** 286 * Adds a surface to the list of tracked surfaces. 287 * 288 * @param d3dw the surface to be added 289 */ 290 private void trackScreenSurface(SurfaceData sd) { 291 if (!done && sd instanceof D3DWindowSurfaceData) { 292 synchronized (this) { 293 if (d3dwSurfaces == null) { 294 d3dwSurfaces = new ArrayList<D3DWindowSurfaceData>(); 295 } 296 D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd; 297 if (!d3dwSurfaces.contains(d3dw)) { 298 d3dwSurfaces.add(d3dw); 299 } 300 } 301 startUpdateThread(); 302 } 303 } 304 305 @Override 306 public synchronized void dropScreenSurface(SurfaceData sd) { 307 if (d3dwSurfaces != null && sd instanceof D3DWindowSurfaceData) { 308 D3DWindowSurfaceData d3dw = (D3DWindowSurfaceData)sd; 309 removeGdiSurface(d3dw); 310 d3dwSurfaces.remove(d3dw); 311 } 312 } 313 314 @Override 315 public SurfaceData getReplacementScreenSurface(WComponentPeer peer, 316 SurfaceData sd) 317 { 318 SurfaceData newSurface = super.getReplacementScreenSurface(peer, sd); 319 // if some outstanding graphics context wants to get a replacement we 320 // need to make sure that the new surface (if it is accelerated) is 321 // being tracked 322 trackScreenSurface(newSurface); 323 return newSurface; 324 } 325 326 /** 327 * Remove the gdi surface corresponding to the passed d3dw surface 328 * from list of the cached gdi surfaces. 329 * 330 * @param d3dw surface for which associated gdi surface is to be removed 331 */ 332 private void removeGdiSurface(final D3DWindowSurfaceData d3dw) { 333 if (gdiSurfaces != null) { 334 GDIWindowSurfaceData gdisd = gdiSurfaces.get(d3dw); 335 if (gdisd != null) { 336 gdisd.invalidate(); 337 gdiSurfaces.remove(d3dw); 338 } 339 } 340 } 341 342 /** 343 * If the update thread hasn't yet been created, it will be; 344 * otherwise it is awaken 345 */ 346 private synchronized void startUpdateThread() { 347 if (screenUpdater == null) { 348 screenUpdater = AccessController.doPrivileged( 349 (PrivilegedAction<Thread>) () -> { 350 ThreadGroup rootTG = ThreadGroupUtils.getRootThreadGroup(); 351 Thread t = new Thread(rootTG, 352 D3DScreenUpdateManager.this, 353 "D3D Screen Updater"); 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 if (comp instanceof Container) { 528 for (Component c : ((Container)comp).getComponents()) { 529 if (c.getPeer() instanceof WComponentPeer || hasHWChildren(c)) { 530 return true; 531 } 532 } 533 } 534 return false; 535 } 536 }