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 sd 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 }