1 /*
   2  * Copyright (c) 2005, 2011, 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 package javax.swing;
  26 
  27 import java.awt.*;
  28 import java.awt.event.*;
  29 import java.awt.image.*;
  30 import java.lang.reflect.*;
  31 import java.lang.ref.WeakReference;
  32 import java.util.*;
  33 
  34 import com.sun.java.swing.SwingUtilities3;
  35 
  36 import sun.awt.SubRegionShowable;
  37 import sun.java2d.SunGraphics2D;
  38 import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
  39 import sun.awt.SunToolkit;
  40 import sun.util.logging.PlatformLogger;
  41 
  42 /**
  43  * A PaintManager implementation that uses a BufferStrategy for
  44  * rendering.
  45  *
  46  * @author Scott Violet
  47  */
  48 class BufferStrategyPaintManager extends RepaintManager.PaintManager {
  49     //
  50     // All drawing is done to a BufferStrategy.  At the end of painting
  51     // (endPaint) the region that was painted is flushed to the screen
  52     // (using BufferStrategy.show).
  53     //
  54     // PaintManager.show is overriden to show directly from the
  55     // BufferStrategy (when using blit), if successful true is
  56     // returned and a paint event will not be generated.  To avoid
  57     // showing from the buffer while painting a locking scheme is
  58     // implemented.  When beginPaint is invoked the field painting is
  59     // set to true.  If painting is true and show is invoked we
  60     // immediately return false.  This is done to avoid blocking the
  61     // toolkit thread while painting happens.  In a similar way when
  62     // show is invoked the field showing is set to true, beginPaint
  63     // will then block until showing is true.  This scheme ensures we
  64     // only ever have one thread using the BufferStrategy and it also
  65     // ensures the toolkit thread remains as responsive as possible.
  66     //
  67     // If we're using a flip strategy the contents of the backbuffer may
  68     // have changed and so show only attempts to show from the backbuffer
  69     // if we get a blit strategy.
  70     //
  71 
  72     //
  73     // Methods used to create BufferStrategy for Applets.
  74     //
  75     private static Method COMPONENT_CREATE_BUFFER_STRATEGY_METHOD;
  76     private static Method COMPONENT_GET_BUFFER_STRATEGY_METHOD;
  77 
  78     private static final PlatformLogger LOGGER = PlatformLogger.getLogger(
  79                            "javax.swing.BufferStrategyPaintManager");
  80 
  81     /**
  82      * List of BufferInfos.  We don't use a Map primarily because
  83      * there are typically only a handful of top level components making
  84      * a Map overkill.
  85      */
  86     private ArrayList<BufferInfo> bufferInfos;
  87 
  88     /**
  89      * Indicates <code>beginPaint</code> has been invoked.  This is
  90      * set to true for the life of beginPaint/endPaint pair.
  91      */
  92     private boolean painting;
  93     /**
  94      * Indicates we're in the process of showing.  All painting, on the EDT,
  95      * is blocked while this is true.
  96      */
  97     private boolean showing;
  98 
  99     //
 100     // Region that we need to flush.  When beginPaint is called these are
 101     // reset and any subsequent calls to paint/copyArea then update these
 102     // fields accordingly.  When endPaint is called we then try and show
 103     // the accumulated region.
 104     // These fields are in the coordinate system of the root.
 105     //
 106     private int accumulatedX;
 107     private int accumulatedY;
 108     private int accumulatedMaxX;
 109     private int accumulatedMaxY;
 110 
 111     //
 112     // The following fields are set by prepare
 113     //
 114 
 115     /**
 116      * Farthest JComponent ancestor for the current paint/copyArea.
 117      */
 118     private JComponent rootJ;
 119     /**
 120      * Location of component being painted relative to root.
 121      */
 122     private int xOffset;
 123     /**
 124      * Location of component being painted relative to root.
 125      */
 126     private int yOffset;
 127     /**
 128      * Graphics from the BufferStrategy.
 129      */
 130     private Graphics bsg;
 131     /**
 132      * BufferStrategy currently being used.
 133      */
 134     private BufferStrategy bufferStrategy;
 135     /**
 136      * BufferInfo corresponding to root.
 137      */
 138     private BufferInfo bufferInfo;
 139 
 140     /**
 141      * Set to true if the bufferInfo needs to be disposed when current
 142      * paint loop is done.
 143      */
 144     private boolean disposeBufferOnEnd;
 145 
 146     private static Method getGetBufferStrategyMethod() {
 147         if (COMPONENT_GET_BUFFER_STRATEGY_METHOD == null) {
 148             getMethods();
 149         }
 150         return COMPONENT_GET_BUFFER_STRATEGY_METHOD;
 151     }
 152 
 153     private static Method getCreateBufferStrategyMethod() {
 154         if (COMPONENT_CREATE_BUFFER_STRATEGY_METHOD == null) {
 155             getMethods();
 156         }
 157         return COMPONENT_CREATE_BUFFER_STRATEGY_METHOD;
 158     }
 159 
 160     private static void getMethods() {
 161         java.security.AccessController.doPrivileged(
 162                             new java.security.PrivilegedAction<Object>() {
 163             public Object run() {
 164                 try {
 165                     COMPONENT_CREATE_BUFFER_STRATEGY_METHOD = Component.class.
 166                               getDeclaredMethod("createBufferStrategy",
 167                                                 new Class[] { int.class,
 168                                                 BufferCapabilities.class });
 169                     COMPONENT_CREATE_BUFFER_STRATEGY_METHOD.
 170                                             setAccessible(true);
 171                     COMPONENT_GET_BUFFER_STRATEGY_METHOD = Component.class.
 172                               getDeclaredMethod("getBufferStrategy");
 173                     COMPONENT_GET_BUFFER_STRATEGY_METHOD.setAccessible(true);
 174                 } catch (SecurityException e) {
 175                     assert false;
 176                 } catch (NoSuchMethodException nsme) {
 177                     assert false;
 178                 }
 179                 return null;
 180             }
 181         });
 182     }
 183 
 184     BufferStrategyPaintManager() {
 185         bufferInfos = new ArrayList<BufferInfo>(1);
 186     }
 187 
 188     //
 189     // PaintManager methods
 190     //
 191 
 192     /**
 193      * Cleans up any created BufferStrategies.
 194      */
 195     protected void dispose() {
 196         // dipose can be invoked at any random time. To avoid
 197         // threading dependancies we do the actual diposing via an
 198         // invokeLater.
 199         SwingUtilities.invokeLater(new Runnable() {
 200             public void run() {
 201                 java.util.List<BufferInfo> bufferInfos;
 202                 synchronized(BufferStrategyPaintManager.this) {
 203                     while (showing) {
 204                         try {
 205                             BufferStrategyPaintManager.this.wait();
 206                         } catch (InterruptedException ie) {
 207                         }
 208                     }
 209                     bufferInfos = BufferStrategyPaintManager.this.bufferInfos;
 210                     BufferStrategyPaintManager.this.bufferInfos = null;
 211                 }
 212                 dispose(bufferInfos);
 213             }
 214         });
 215     }
 216 
 217     private void dispose(java.util.List<BufferInfo> bufferInfos) {
 218         if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 219             LOGGER.finer("BufferStrategyPaintManager disposed",
 220                          new RuntimeException());
 221         }
 222         if (bufferInfos != null) {
 223             for (BufferInfo bufferInfo : bufferInfos) {
 224                 bufferInfo.dispose();
 225             }
 226         }
 227     }
 228 
 229     /**
 230      * Shows the specified region of the back buffer.  This will return
 231      * true if successful, false otherwise.  This is invoked on the
 232      * toolkit thread in response to an expose event.
 233      */
 234     public boolean show(Container c, int x, int y, int w, int h) {
 235         synchronized(this) {
 236             if (painting) {
 237                 // Don't show from backbuffer while in the process of
 238                 // painting.
 239                 return false;
 240             }
 241             showing = true;
 242         }
 243         try {
 244             BufferInfo info = getBufferInfo(c);
 245             BufferStrategy bufferStrategy;
 246             if (info != null && info.isInSync() &&
 247                 (bufferStrategy = info.getBufferStrategy(false)) != null) {
 248                 SubRegionShowable bsSubRegion =
 249                         (SubRegionShowable)bufferStrategy;
 250                 boolean paintAllOnExpose = info.getPaintAllOnExpose();
 251                 info.setPaintAllOnExpose(false);
 252                 if (bsSubRegion.showIfNotLost(x, y, (x + w), (y + h))) {
 253                     return !paintAllOnExpose;
 254                 }
 255                 // Mark the buffer as needing to be repainted.  We don't
 256                 // immediately do a repaint as this method will return false
 257                 // indicating a PaintEvent should be generated which will
 258                 // trigger a complete repaint.
 259                 bufferInfo.setContentsLostDuringExpose(true);
 260             }
 261         }
 262         finally {
 263             synchronized(this) {
 264                 showing = false;
 265                 notifyAll();
 266             }
 267         }
 268         return false;
 269     }
 270 
 271     public boolean paint(JComponent paintingComponent,
 272                          JComponent bufferComponent, Graphics g,
 273                          int x, int y, int w, int h) {
 274         Container root = fetchRoot(paintingComponent);
 275 
 276         if (prepare(paintingComponent, root, true, x, y, w, h)) {
 277             if ((g instanceof SunGraphics2D) &&
 278                     ((SunGraphics2D)g).getDestination() == root) {
 279                 // BufferStrategy may have already constrained the Graphics. To
 280                 // account for that we revert the constrain, then apply a
 281                 // constrain for Swing on top of that.
 282                 int cx = ((SunGraphics2D)bsg).constrainX;
 283                 int cy = ((SunGraphics2D)bsg).constrainY;
 284                 if (cx != 0 || cy != 0) {
 285                     bsg.translate(-cx, -cy);
 286                 }
 287                 ((SunGraphics2D)bsg).constrain(xOffset + cx, yOffset + cy,
 288                                                x + w, y + h);
 289                 bsg.setClip(x, y, w, h);
 290                 paintingComponent.paintToOffscreen(bsg, x, y, w, h,
 291                                                    x + w, y + h);
 292                 accumulate(xOffset + x, yOffset + y, w, h);
 293                 return true;
 294             } else {
 295                 // Assume they are going to eventually render to the screen.
 296                 // This disables showing from backbuffer until a complete
 297                 // repaint occurs.
 298                 bufferInfo.setInSync(false);
 299                 // Fall through to old rendering.
 300             }
 301         }
 302         // Invalid root, do what Swing has always done.
 303         if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 304             LOGGER.finer("prepare failed");
 305         }
 306         return super.paint(paintingComponent, bufferComponent, g, x, y, w, h);
 307     }
 308 
 309     public void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
 310                          int deltaX, int deltaY, boolean clip) {
 311         // Note: this method is only called internally and we know that
 312         // g is from a heavyweight Component, so no check is necessary as
 313         // it is in paint() above.
 314         //
 315         // If the buffer isn't in sync there is no point in doing a copyArea,
 316         // it has garbage.
 317         Container root = fetchRoot(c);
 318 
 319         if (prepare(c, root, false, 0, 0, 0, 0) && bufferInfo.isInSync()) {
 320             if (clip) {
 321                 Rectangle cBounds = c.getVisibleRect();
 322                 int relX = xOffset + x;
 323                 int relY = yOffset + y;
 324                 bsg.clipRect(xOffset + cBounds.x,
 325                              yOffset + cBounds.y,
 326                              cBounds.width, cBounds.height);
 327                 bsg.copyArea(relX, relY, w, h, deltaX, deltaY);
 328             }
 329             else {
 330                 bsg.copyArea(xOffset + x, yOffset + y, w, h, deltaX,
 331                              deltaY);
 332             }
 333             accumulate(x + xOffset + deltaX, y + yOffset + deltaY, w, h);
 334         } else {
 335             if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 336                 LOGGER.finer("copyArea: prepare failed or not in sync");
 337             }
 338             // Prepare failed, or not in sync. By calling super.copyArea
 339             // we'll copy on screen. We need to flush any pending paint to
 340             // the screen otherwise we'll do a copyArea on the wrong thing.
 341             if (!flushAccumulatedRegion()) {
 342                 // Flush failed, copyArea will be copying garbage,
 343                 // force repaint of all.
 344                 rootJ.repaint();
 345             } else {
 346                 super.copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
 347             }
 348         }
 349     }
 350 
 351     public void beginPaint() {
 352         synchronized(this) {
 353             painting = true;
 354             // Make sure another thread isn't attempting to show from
 355             // the back buffer.
 356             while(showing) {
 357                 try {
 358                     wait();
 359                 } catch (InterruptedException ie) {
 360                 }
 361             }
 362         }
 363         if (LOGGER.isLoggable(PlatformLogger.FINEST)) {
 364             LOGGER.finest("beginPaint");
 365         }
 366         // Reset the area that needs to be painted.
 367         resetAccumulated();
 368     }
 369 
 370     public void endPaint() {
 371         if (LOGGER.isLoggable(PlatformLogger.FINEST)) {
 372             LOGGER.finest("endPaint: region " + accumulatedX + " " +
 373                        accumulatedY + " " +  accumulatedMaxX + " " +
 374                        accumulatedMaxY);
 375         }
 376         if (painting) {
 377             if (!flushAccumulatedRegion()) {
 378                 if (!isRepaintingRoot()) {
 379                     repaintRoot(rootJ);
 380                 }
 381                 else {
 382                     // Contents lost twice in a row, punt.
 383                     resetDoubleBufferPerWindow();
 384                     // In case we've left junk on the screen, force a repaint.
 385                     rootJ.repaint();
 386                 }
 387             }
 388         }
 389 
 390         BufferInfo toDispose = null;
 391         synchronized(this) {
 392             painting = false;
 393             if (disposeBufferOnEnd) {
 394                 disposeBufferOnEnd = false;
 395                 toDispose = bufferInfo;
 396                 bufferInfos.remove(toDispose);
 397             }
 398         }
 399         if (toDispose != null) {
 400             toDispose.dispose();
 401         }
 402     }
 403 
 404     /**
 405      * Renders the BufferStrategy to the screen.
 406      *
 407      * @return true if successful, false otherwise.
 408      */
 409     private boolean flushAccumulatedRegion() {
 410         boolean success = true;
 411         if (accumulatedX != Integer.MAX_VALUE) {
 412             SubRegionShowable bsSubRegion = (SubRegionShowable)bufferStrategy;
 413             boolean contentsLost = bufferStrategy.contentsLost();
 414             if (!contentsLost) {
 415                 bsSubRegion.show(accumulatedX, accumulatedY,
 416                                  accumulatedMaxX, accumulatedMaxY);
 417                 contentsLost = bufferStrategy.contentsLost();
 418             }
 419             if (contentsLost) {
 420                 if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 421                     LOGGER.finer("endPaint: contents lost");
 422                 }
 423                 // Shown region was bogus, mark buffer as out of sync.
 424                 bufferInfo.setInSync(false);
 425                 success = false;
 426             }
 427         }
 428         resetAccumulated();
 429         return success;
 430     }
 431 
 432     private void resetAccumulated() {
 433         accumulatedX = Integer.MAX_VALUE;
 434         accumulatedY = Integer.MAX_VALUE;
 435         accumulatedMaxX = 0;
 436         accumulatedMaxY = 0;
 437     }
 438 
 439     /**
 440      * Invoked when the double buffering or useTrueDoubleBuffering
 441      * changes for a JRootPane.  If the rootpane is not double
 442      * buffered, or true double buffering changes we throw out any
 443      * cache we may have.
 444      */
 445     public void doubleBufferingChanged(final JRootPane rootPane) {
 446         if ((!rootPane.isDoubleBuffered() ||
 447                 !rootPane.getUseTrueDoubleBuffering()) &&
 448                 rootPane.getParent() != null) {
 449             if (!SwingUtilities.isEventDispatchThread()) {
 450                 Runnable updater = new Runnable() {
 451                     public void run() {
 452                         doubleBufferingChanged0(rootPane);
 453                     }
 454                 };
 455                 SwingUtilities.invokeLater(updater);
 456             }
 457             else {
 458                 doubleBufferingChanged0(rootPane);
 459             }
 460         }
 461     }
 462 
 463     /**
 464      * Does the work for doubleBufferingChanged.
 465      */
 466     private void doubleBufferingChanged0(JRootPane rootPane) {
 467         // This will only happen on the EDT.
 468         BufferInfo info;
 469         synchronized(this) {
 470             // Make sure another thread isn't attempting to show from
 471             // the back buffer.
 472             while(showing) {
 473                 try {
 474                     wait();
 475                 } catch (InterruptedException ie) {
 476                 }
 477             }
 478             info = getBufferInfo(rootPane.getParent());
 479             if (painting && bufferInfo == info) {
 480                 // We're in the process of painting and the user grabbed
 481                 // the Graphics. If we dispose now, endPaint will attempt
 482                 // to show a bogus BufferStrategy. Set a flag so that
 483                 // endPaint knows it needs to dispose this buffer.
 484                 disposeBufferOnEnd = true;
 485                 info = null;
 486             } else if (info != null) {
 487                 bufferInfos.remove(info);
 488             }
 489         }
 490         if (info != null) {
 491             info.dispose();
 492         }
 493     }
 494 
 495     /**
 496      * Calculates information common to paint/copyArea.
 497      *
 498      * @return true if should use buffering per window in painting.
 499      */
 500     private boolean prepare(JComponent c, Container root, boolean isPaint, int x, int y,
 501                             int w, int h) {
 502         if (bsg != null) {
 503             bsg.dispose();
 504             bsg = null;
 505         }
 506         bufferStrategy = null;
 507         if (root != null) {
 508             boolean contentsLost = false;
 509             BufferInfo bufferInfo = getBufferInfo(root);
 510             if (bufferInfo == null) {
 511                 contentsLost = true;
 512                 bufferInfo = new BufferInfo(root);
 513                 bufferInfos.add(bufferInfo);
 514                 if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 515                     LOGGER.finer("prepare: new BufferInfo: " + root);
 516                 }
 517             }
 518             this.bufferInfo = bufferInfo;
 519             if (!bufferInfo.hasBufferStrategyChanged()) {
 520                 bufferStrategy = bufferInfo.getBufferStrategy(true);
 521                 if (bufferStrategy != null) {
 522                     bsg = bufferStrategy.getDrawGraphics();
 523                     if (bufferStrategy.contentsRestored()) {
 524                         contentsLost = true;
 525                         if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 526                             LOGGER.finer("prepare: contents restored in prepare");
 527                         }
 528                     }
 529                 }
 530                 else {
 531                     // Couldn't create BufferStrategy, fallback to normal
 532                     // painting.
 533                     return false;
 534                 }
 535                 if (bufferInfo.getContentsLostDuringExpose()) {
 536                     contentsLost = true;
 537                     bufferInfo.setContentsLostDuringExpose(false);
 538                     if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 539                         LOGGER.finer("prepare: contents lost on expose");
 540                     }
 541                 }
 542                 if (isPaint && c == rootJ && x == 0 && y == 0 &&
 543                       c.getWidth() == w && c.getHeight() == h) {
 544                     bufferInfo.setInSync(true);
 545                 }
 546                 else if (contentsLost) {
 547                     // We either recreated the BufferStrategy, or the contents
 548                     // of the buffer strategy were restored.  We need to
 549                     // repaint the root pane so that the back buffer is in sync
 550                     // again.
 551                     bufferInfo.setInSync(false);
 552                     if (!isRepaintingRoot()) {
 553                         repaintRoot(rootJ);
 554                     }
 555                     else {
 556                         // Contents lost twice in a row, punt
 557                         resetDoubleBufferPerWindow();
 558                     }
 559                 }
 560                 return (bufferInfos != null);
 561             }
 562         }
 563         return false;
 564     }
 565 
 566     private Container fetchRoot(JComponent c) {
 567         boolean encounteredHW = false;
 568         rootJ = c;
 569         Container root = c;
 570         xOffset = yOffset = 0;
 571         while (root != null &&
 572                (!(root instanceof Window) &&
 573                 !SunToolkit.isInstanceOf(root, "java.applet.Applet"))) {
 574             xOffset += root.getX();
 575             yOffset += root.getY();
 576             root = root.getParent();
 577             if (root != null) {
 578                 if (root instanceof JComponent) {
 579                     rootJ = (JComponent)root;
 580                 }
 581                 else if (!root.isLightweight()) {
 582                     if (!encounteredHW) {
 583                         encounteredHW = true;
 584                     }
 585                     else {
 586                         // We've encountered two hws now and may have
 587                         // a containment hierarchy with lightweights containing
 588                         // heavyweights containing other lightweights.
 589                         // Heavyweights poke holes in lightweight
 590                         // rendering so that if we call show on the BS
 591                         // (which is associated with the Window) you will
 592                         // not see the contents over any child
 593                         // heavyweights.  If we didn't do this when we
 594                         // went to show the descendants of the nested hw
 595                         // you would see nothing, so, we bail out here.
 596                         return null;
 597                     }
 598                 }
 599             }
 600         }
 601         if ((root instanceof RootPaneContainer) &&
 602             (rootJ instanceof JRootPane)) {
 603             // We're in a Swing heavyeight (JFrame/JWindow...), use double
 604             // buffering if double buffering enabled on the JRootPane and
 605             // the JRootPane wants true double buffering.
 606             if (rootJ.isDoubleBuffered() &&
 607                     ((JRootPane)rootJ).getUseTrueDoubleBuffering()) {
 608                 // Whether or not a component is double buffered is a
 609                 // bit tricky with Swing. This gives a good approximation
 610                 // of the various ways to turn on double buffering for
 611                 // components.
 612                 return root;
 613             }
 614         }
 615         // Don't do true double buffering.
 616         return null;
 617     }
 618 
 619     /**
 620      * Turns off double buffering per window.
 621      */
 622     private void resetDoubleBufferPerWindow() {
 623         if (bufferInfos != null) {
 624             dispose(bufferInfos);
 625             bufferInfos = null;
 626             repaintManager.setPaintManager(null);
 627         }
 628     }
 629 
 630     /**
 631      * Returns the BufferInfo for the specified root or null if one
 632      * hasn't been created yet.
 633      */
 634     private BufferInfo getBufferInfo(Container root) {
 635         for (int counter = bufferInfos.size() - 1; counter >= 0; counter--) {
 636             BufferInfo bufferInfo = bufferInfos.get(counter);
 637             Container biRoot = bufferInfo.getRoot();
 638             if (biRoot == null) {
 639                 // Window gc'ed
 640                 bufferInfos.remove(counter);
 641                 if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 642                     LOGGER.finer("BufferInfo pruned, root null");
 643                 }
 644             }
 645             else if (biRoot == root) {
 646                 return bufferInfo;
 647             }
 648         }
 649         return null;
 650     }
 651 
 652     private void accumulate(int x, int y, int w, int h) {
 653         accumulatedX = Math.min(x, accumulatedX);
 654         accumulatedY = Math.min(y, accumulatedY);
 655         accumulatedMaxX = Math.max(accumulatedMaxX, x + w);
 656         accumulatedMaxY = Math.max(accumulatedMaxY, y + h);
 657     }
 658 
 659 
 660 
 661     /**
 662      * BufferInfo is used to track the BufferStrategy being used for
 663      * a particular Component.  In addition to tracking the BufferStrategy
 664      * it will install a WindowListener and ComponentListener.  When the
 665      * component is hidden/iconified the buffer is marked as needing to be
 666      * completely repainted.
 667      */
 668     private class BufferInfo extends ComponentAdapter implements
 669                                WindowListener {
 670         // NOTE: This class does NOT hold a direct reference to the root, if it
 671         // did there would be a cycle between the BufferPerWindowPaintManager
 672         // and the Window so that it could never be GC'ed
 673         //
 674         // Reference to BufferStrategy is referenced via WeakReference for
 675         // same reason.
 676         private WeakReference<BufferStrategy> weakBS;
 677         private WeakReference<Container> root;
 678         // Indicates whether or not the backbuffer and display are in sync.
 679         // This is set to true when a full repaint on the rootpane is done.
 680         private boolean inSync;
 681         // Indicates the contents were lost during and expose event.
 682         private boolean contentsLostDuringExpose;
 683         // Indicates we need to generate a paint event on expose.
 684         private boolean paintAllOnExpose;
 685 
 686 
 687         public BufferInfo(Container root) {
 688             this.root = new WeakReference<Container>(root);
 689             root.addComponentListener(this);
 690             if (root instanceof Window) {
 691                 ((Window)root).addWindowListener(this);
 692             }
 693         }
 694 
 695         public void setPaintAllOnExpose(boolean paintAllOnExpose) {
 696             this.paintAllOnExpose = paintAllOnExpose;
 697         }
 698 
 699         public boolean getPaintAllOnExpose() {
 700             return paintAllOnExpose;
 701         }
 702 
 703         public void setContentsLostDuringExpose(boolean value) {
 704             contentsLostDuringExpose = value;
 705         }
 706 
 707         public boolean getContentsLostDuringExpose() {
 708             return contentsLostDuringExpose;
 709         }
 710 
 711         public void setInSync(boolean inSync) {
 712             this.inSync = inSync;
 713         }
 714 
 715         /**
 716          * Whether or not the contents of the buffer strategy
 717          * is in sync with the window.  This is set to true when the root
 718          * pane paints all, and false when contents are lost/restored.
 719          */
 720         public boolean isInSync() {
 721             return inSync;
 722         }
 723 
 724         /**
 725          * Returns the Root (Window or Applet) that this BufferInfo references.
 726          */
 727         public Container getRoot() {
 728             return (root == null) ? null : root.get();
 729         }
 730 
 731         /**
 732          * Returns the BufferStartegy.  This will return null if
 733          * the BufferStartegy hasn't been created and <code>create</code> is
 734          * false, or if there is a problem in creating the
 735          * <code>BufferStartegy</code>.
 736          *
 737          * @param create If true, and the BufferStartegy is currently null,
 738          *               one will be created.
 739          */
 740         public BufferStrategy getBufferStrategy(boolean create) {
 741             BufferStrategy bs = (weakBS == null) ? null : weakBS.get();
 742             if (bs == null && create) {
 743                 bs = createBufferStrategy();
 744                 if (bs != null) {
 745                     weakBS = new WeakReference<BufferStrategy>(bs);
 746                 }
 747                 if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 748                     LOGGER.finer("getBufferStrategy: created bs: " + bs);
 749                 }
 750             }
 751             return bs;
 752         }
 753 
 754         /**
 755          * Returns true if the buffer strategy of the component differs
 756          * from current buffer strategy.
 757          */
 758         public boolean hasBufferStrategyChanged() {
 759             Container root = getRoot();
 760             if (root != null) {
 761                 BufferStrategy ourBS = null;
 762                 BufferStrategy componentBS = null;
 763 
 764                 ourBS = getBufferStrategy(false);
 765                 if (root instanceof Window) {
 766                     componentBS = ((Window)root).getBufferStrategy();
 767                 }
 768                 else {
 769                     try {
 770                         componentBS = (BufferStrategy)
 771                                  getGetBufferStrategyMethod().invoke(root);
 772                     } catch (InvocationTargetException ite) {
 773                         assert false;
 774                     } catch (IllegalArgumentException iae) {
 775                         assert false;
 776                     } catch (IllegalAccessException iae2) {
 777                         assert false;
 778                     }
 779                 }
 780                 if (componentBS != ourBS) {
 781                     // Component has a different BS, dispose ours.
 782                     if (ourBS != null) {
 783                         ourBS.dispose();
 784                     }
 785                     weakBS = null;
 786                     return true;
 787                 }
 788             }
 789             return false;
 790         }
 791 
 792         /**
 793          * Creates the BufferStrategy.  If the appropriate system property
 794          * has been set we'll try for flip first and then we'll try for
 795          * blit.
 796          */
 797         private BufferStrategy createBufferStrategy() {
 798             Container root = getRoot();
 799             if (root == null) {
 800                 return null;
 801             }
 802             BufferStrategy bs = null;
 803             if (SwingUtilities3.isVsyncRequested(root)) {
 804                 bs = createBufferStrategy(root, true);
 805                 if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 806                     LOGGER.finer("createBufferStrategy: using vsynced strategy");
 807                 }
 808             }
 809             if (bs == null) {
 810                 bs = createBufferStrategy(root, false);
 811             }
 812             if (!(bs instanceof SubRegionShowable)) {
 813                 // We do this for two reasons:
 814                 // 1. So that we know we can cast to SubRegionShowable and
 815                 //    invoke show with the minimal region to update
 816                 // 2. To avoid the possibility of invoking client code
 817                 //    on the toolkit thread.
 818                 bs = null;
 819             }
 820             return bs;
 821         }
 822 
 823         // Creates and returns a buffer strategy.  If
 824         // there is a problem creating the buffer strategy this will
 825         // eat the exception and return null.
 826         private BufferStrategy createBufferStrategy(Container root,
 827                 boolean isVsynced) {
 828             BufferCapabilities caps;
 829             if (isVsynced) {
 830                 caps = new ExtendedBufferCapabilities(
 831                     new ImageCapabilities(true), new ImageCapabilities(true),
 832                     BufferCapabilities.FlipContents.COPIED,
 833                     ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
 834             } else {
 835                 caps = new BufferCapabilities(
 836                     new ImageCapabilities(true), new ImageCapabilities(true),
 837                     null);
 838             }
 839             BufferStrategy bs = null;
 840             if (SunToolkit.isInstanceOf(root, "java.applet.Applet")) {
 841                 try {
 842                     getCreateBufferStrategyMethod().invoke(root, 2, caps);
 843                     bs = (BufferStrategy)getGetBufferStrategyMethod().
 844                                             invoke(root);
 845                 } catch (InvocationTargetException ite) {
 846                     // Type is not supported
 847                     if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 848                         LOGGER.finer("createBufferStratety failed",
 849                                      ite);
 850                     }
 851                 } catch (IllegalArgumentException iae) {
 852                     assert false;
 853                 } catch (IllegalAccessException iae2) {
 854                     assert false;
 855                 }
 856             }
 857             else {
 858                 try {
 859                     ((Window)root).createBufferStrategy(2, caps);
 860                     bs = ((Window)root).getBufferStrategy();
 861                 } catch (AWTException e) {
 862                     // Type not supported
 863                     if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 864                         LOGGER.finer("createBufferStratety failed",
 865                                      e);
 866                     }
 867                 }
 868             }
 869             return bs;
 870         }
 871 
 872         /**
 873          * Cleans up and removes any references.
 874          */
 875         public void dispose() {
 876             Container root = getRoot();
 877             if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 878                 LOGGER.finer("disposed BufferInfo for: " + root);
 879             }
 880             if (root != null) {
 881                 root.removeComponentListener(this);
 882                 if (root instanceof Window) {
 883                     ((Window)root).removeWindowListener(this);
 884                 }
 885                 BufferStrategy bs = getBufferStrategy(false);
 886                 if (bs != null) {
 887                     bs.dispose();
 888                 }
 889             }
 890             this.root = null;
 891             weakBS = null;
 892         }
 893 
 894         // We mark the buffer as needing to be painted on a hide/iconify
 895         // because the developer may have conditionalized painting based on
 896         // visibility.
 897         // Ideally we would also move to having the BufferStrategy being
 898         // a SoftReference in Component here, but that requires changes to
 899         // Component and the like.
 900         public void componentHidden(ComponentEvent e) {
 901             Container root = getRoot();
 902             if (root != null && root.isVisible()) {
 903                 // This case will only happen if a developer calls
 904                 // hide immediately followed by show.  In this case
 905                 // the event is delivered after show and the window
 906                 // will still be visible.  If a developer altered the
 907                 // contents of the window between the hide/show
 908                 // invocations we won't recognize we need to paint and
 909                 // the contents would be bogus.  Calling repaint here
 910                 // fixs everything up.
 911                 root.repaint();
 912             }
 913             else {
 914                 setPaintAllOnExpose(true);
 915             }
 916         }
 917 
 918         public void windowIconified(WindowEvent e) {
 919             setPaintAllOnExpose(true);
 920         }
 921 
 922         // On a dispose we chuck everything.
 923         public void windowClosed(WindowEvent e) {
 924             // Make sure we're not showing.
 925             synchronized(BufferStrategyPaintManager.this) {
 926                 while (showing) {
 927                     try {
 928                         BufferStrategyPaintManager.this.wait();
 929                     } catch (InterruptedException ie) {
 930                     }
 931                 }
 932                 bufferInfos.remove(this);
 933             }
 934             dispose();
 935         }
 936 
 937         public void windowOpened(WindowEvent e) {
 938         }
 939 
 940         public void windowClosing(WindowEvent e) {
 941         }
 942 
 943         public void windowDeiconified(WindowEvent e) {
 944         }
 945 
 946         public void windowActivated(WindowEvent e) {
 947         }
 948 
 949         public void windowDeactivated(WindowEvent e) {
 950         }
 951     }
 952 }