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(
 527                                 "prepare: contents restored in prepare");
 528                         }
 529                     }
 530                 }
 531                 else {
 532                     // Couldn't create BufferStrategy, fallback to normal
 533                     // painting.
 534                     return false;
 535                 }
 536                 if (bufferInfo.getContentsLostDuringExpose()) {
 537                     contentsLost = true;
 538                     bufferInfo.setContentsLostDuringExpose(false);
 539                     if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 540                         LOGGER.finer("prepare: contents lost on expose");
 541                     }
 542                 }
 543                 if (isPaint && c == rootJ && x == 0 && y == 0 &&
 544                       c.getWidth() == w && c.getHeight() == h) {
 545                     bufferInfo.setInSync(true);
 546                 }
 547                 else if (contentsLost) {
 548                     // We either recreated the BufferStrategy, or the contents
 549                     // of the buffer strategy were restored.  We need to
 550                     // repaint the root pane so that the back buffer is in sync
 551                     // again.
 552                     bufferInfo.setInSync(false);
 553                     if (!isRepaintingRoot()) {
 554                         repaintRoot(rootJ);
 555                     }
 556                     else {
 557                         // Contents lost twice in a row, punt
 558                         resetDoubleBufferPerWindow();
 559                     }
 560                 }
 561                 return (bufferInfos != null);
 562             }
 563         }
 564         return false;
 565     }
 566 
 567     private Container fetchRoot(JComponent c) {
 568         boolean encounteredHW = false;
 569         rootJ = c;
 570         Container root = c;
 571         xOffset = yOffset = 0;
 572         while (root != null &&
 573                (!(root instanceof Window) &&
 574                 !SunToolkit.isInstanceOf(root, "java.applet.Applet"))) {
 575             xOffset += root.getX();
 576             yOffset += root.getY();
 577             root = root.getParent();
 578             if (root != null) {
 579                 if (root instanceof JComponent) {
 580                     rootJ = (JComponent)root;
 581                 }
 582                 else if (!root.isLightweight()) {
 583                     if (!encounteredHW) {
 584                         encounteredHW = true;
 585                     }
 586                     else {
 587                         // We've encountered two hws now and may have
 588                         // a containment hierarchy with lightweights containing
 589                         // heavyweights containing other lightweights.
 590                         // Heavyweights poke holes in lightweight
 591                         // rendering so that if we call show on the BS
 592                         // (which is associated with the Window) you will
 593                         // not see the contents over any child
 594                         // heavyweights.  If we didn't do this when we
 595                         // went to show the descendants of the nested hw
 596                         // you would see nothing, so, we bail out here.
 597                         return null;
 598                     }
 599                 }
 600             }
 601         }
 602         if ((root instanceof RootPaneContainer) &&
 603             (rootJ instanceof JRootPane)) {
 604             // We're in a Swing heavyeight (JFrame/JWindow...), use double
 605             // buffering if double buffering enabled on the JRootPane and
 606             // the JRootPane wants true double buffering.
 607             if (rootJ.isDoubleBuffered() &&
 608                     ((JRootPane)rootJ).getUseTrueDoubleBuffering()) {
 609                 // Whether or not a component is double buffered is a
 610                 // bit tricky with Swing. This gives a good approximation
 611                 // of the various ways to turn on double buffering for
 612                 // components.
 613                 return root;
 614             }
 615         }
 616         // Don't do true double buffering.
 617         return null;
 618     }
 619 
 620     /**
 621      * Turns off double buffering per window.
 622      */
 623     private void resetDoubleBufferPerWindow() {
 624         if (bufferInfos != null) {
 625             dispose(bufferInfos);
 626             bufferInfos = null;
 627             repaintManager.setPaintManager(null);
 628         }
 629     }
 630 
 631     /**
 632      * Returns the BufferInfo for the specified root or null if one
 633      * hasn't been created yet.
 634      */
 635     private BufferInfo getBufferInfo(Container root) {
 636         for (int counter = bufferInfos.size() - 1; counter >= 0; counter--) {
 637             BufferInfo bufferInfo = bufferInfos.get(counter);
 638             Container biRoot = bufferInfo.getRoot();
 639             if (biRoot == null) {
 640                 // Window gc'ed
 641                 bufferInfos.remove(counter);
 642                 if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 643                     LOGGER.finer("BufferInfo pruned, root null");
 644                 }
 645             }
 646             else if (biRoot == root) {
 647                 return bufferInfo;
 648             }
 649         }
 650         return null;
 651     }
 652 
 653     private void accumulate(int x, int y, int w, int h) {
 654         accumulatedX = Math.min(x, accumulatedX);
 655         accumulatedY = Math.min(y, accumulatedY);
 656         accumulatedMaxX = Math.max(accumulatedMaxX, x + w);
 657         accumulatedMaxY = Math.max(accumulatedMaxY, y + h);
 658     }
 659 
 660 
 661 
 662     /**
 663      * BufferInfo is used to track the BufferStrategy being used for
 664      * a particular Component.  In addition to tracking the BufferStrategy
 665      * it will install a WindowListener and ComponentListener.  When the
 666      * component is hidden/iconified the buffer is marked as needing to be
 667      * completely repainted.
 668      */
 669     private class BufferInfo extends ComponentAdapter implements
 670                                WindowListener {
 671         // NOTE: This class does NOT hold a direct reference to the root, if it
 672         // did there would be a cycle between the BufferPerWindowPaintManager
 673         // and the Window so that it could never be GC'ed
 674         //
 675         // Reference to BufferStrategy is referenced via WeakReference for
 676         // same reason.
 677         private WeakReference<BufferStrategy> weakBS;
 678         private WeakReference<Container> root;
 679         // Indicates whether or not the backbuffer and display are in sync.
 680         // This is set to true when a full repaint on the rootpane is done.
 681         private boolean inSync;
 682         // Indicates the contents were lost during and expose event.
 683         private boolean contentsLostDuringExpose;
 684         // Indicates we need to generate a paint event on expose.
 685         private boolean paintAllOnExpose;
 686 
 687 
 688         public BufferInfo(Container root) {
 689             this.root = new WeakReference<Container>(root);
 690             root.addComponentListener(this);
 691             if (root instanceof Window) {
 692                 ((Window)root).addWindowListener(this);
 693             }
 694         }
 695 
 696         public void setPaintAllOnExpose(boolean paintAllOnExpose) {
 697             this.paintAllOnExpose = paintAllOnExpose;
 698         }
 699 
 700         public boolean getPaintAllOnExpose() {
 701             return paintAllOnExpose;
 702         }
 703 
 704         public void setContentsLostDuringExpose(boolean value) {
 705             contentsLostDuringExpose = value;
 706         }
 707 
 708         public boolean getContentsLostDuringExpose() {
 709             return contentsLostDuringExpose;
 710         }
 711 
 712         public void setInSync(boolean inSync) {
 713             this.inSync = inSync;
 714         }
 715 
 716         /**
 717          * Whether or not the contents of the buffer strategy
 718          * is in sync with the window.  This is set to true when the root
 719          * pane paints all, and false when contents are lost/restored.
 720          */
 721         public boolean isInSync() {
 722             return inSync;
 723         }
 724 
 725         /**
 726          * Returns the Root (Window or Applet) that this BufferInfo references.
 727          */
 728         public Container getRoot() {
 729             return (root == null) ? null : root.get();
 730         }
 731 
 732         /**
 733          * Returns the BufferStartegy.  This will return null if
 734          * the BufferStartegy hasn't been created and <code>create</code> is
 735          * false, or if there is a problem in creating the
 736          * <code>BufferStartegy</code>.
 737          *
 738          * @param create If true, and the BufferStartegy is currently null,
 739          *               one will be created.
 740          */
 741         public BufferStrategy getBufferStrategy(boolean create) {
 742             BufferStrategy bs = (weakBS == null) ? null : weakBS.get();
 743             if (bs == null && create) {
 744                 bs = createBufferStrategy();
 745                 if (bs != null) {
 746                     weakBS = new WeakReference<BufferStrategy>(bs);
 747                 }
 748                 if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 749                     LOGGER.finer("getBufferStrategy: created bs: " + bs);
 750                 }
 751             }
 752             return bs;
 753         }
 754 
 755         /**
 756          * Returns true if the buffer strategy of the component differs
 757          * from current buffer strategy.
 758          */
 759         public boolean hasBufferStrategyChanged() {
 760             Container root = getRoot();
 761             if (root != null) {
 762                 BufferStrategy ourBS = null;
 763                 BufferStrategy componentBS = null;
 764 
 765                 ourBS = getBufferStrategy(false);
 766                 if (root instanceof Window) {
 767                     componentBS = ((Window)root).getBufferStrategy();
 768                 }
 769                 else {
 770                     try {
 771                         componentBS = (BufferStrategy)
 772                                  getGetBufferStrategyMethod().invoke(root);
 773                     } catch (InvocationTargetException ite) {
 774                         assert false;
 775                     } catch (IllegalArgumentException iae) {
 776                         assert false;
 777                     } catch (IllegalAccessException iae2) {
 778                         assert false;
 779                     }
 780                 }
 781                 if (componentBS != ourBS) {
 782                     // Component has a different BS, dispose ours.
 783                     if (ourBS != null) {
 784                         ourBS.dispose();
 785                     }
 786                     weakBS = null;
 787                     return true;
 788                 }
 789             }
 790             return false;
 791         }
 792 
 793         /**
 794          * Creates the BufferStrategy.  If the appropriate system property
 795          * has been set we'll try for flip first and then we'll try for
 796          * blit.
 797          */
 798         private BufferStrategy createBufferStrategy() {
 799             Container root = getRoot();
 800             if (root == null) {
 801                 return null;
 802             }
 803             BufferStrategy bs = null;
 804             if (SwingUtilities3.isVsyncRequested(root)) {
 805                 bs = createBufferStrategy(root, true);
 806                 if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 807                     LOGGER.finer("createBufferStrategy: using vsynced strategy");
 808                 }
 809             }
 810             if (bs == null) {
 811                 bs = createBufferStrategy(root, false);
 812             }
 813             if (!(bs instanceof SubRegionShowable)) {
 814                 // We do this for two reasons:
 815                 // 1. So that we know we can cast to SubRegionShowable and
 816                 //    invoke show with the minimal region to update
 817                 // 2. To avoid the possibility of invoking client code
 818                 //    on the toolkit thread.
 819                 bs = null;
 820             }
 821             return bs;
 822         }
 823 
 824         // Creates and returns a buffer strategy.  If
 825         // there is a problem creating the buffer strategy this will
 826         // eat the exception and return null.
 827         private BufferStrategy createBufferStrategy(Container root,
 828                 boolean isVsynced) {
 829             BufferCapabilities caps;
 830             if (isVsynced) {
 831                 caps = new ExtendedBufferCapabilities(
 832                     new ImageCapabilities(true), new ImageCapabilities(true),
 833                     BufferCapabilities.FlipContents.COPIED,
 834                     ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
 835             } else {
 836                 caps = new BufferCapabilities(
 837                     new ImageCapabilities(true), new ImageCapabilities(true),
 838                     null);
 839             }
 840             BufferStrategy bs = null;
 841             if (SunToolkit.isInstanceOf(root, "java.applet.Applet")) {
 842                 try {
 843                     getCreateBufferStrategyMethod().invoke(root, 2, caps);
 844                     bs = (BufferStrategy)getGetBufferStrategyMethod().
 845                                             invoke(root);
 846                 } catch (InvocationTargetException ite) {
 847                     // Type is not supported
 848                     if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 849                         LOGGER.finer("createBufferStratety failed",
 850                                      ite);
 851                     }
 852                 } catch (IllegalArgumentException iae) {
 853                     assert false;
 854                 } catch (IllegalAccessException iae2) {
 855                     assert false;
 856                 }
 857             }
 858             else {
 859                 try {
 860                     ((Window)root).createBufferStrategy(2, caps);
 861                     bs = ((Window)root).getBufferStrategy();
 862                 } catch (AWTException e) {
 863                     // Type not supported
 864                     if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 865                         LOGGER.finer("createBufferStratety failed",
 866                                      e);
 867                     }
 868                 }
 869             }
 870             return bs;
 871         }
 872 
 873         /**
 874          * Cleans up and removes any references.
 875          */
 876         public void dispose() {
 877             Container root = getRoot();
 878             if (LOGGER.isLoggable(PlatformLogger.FINER)) {
 879                 LOGGER.finer("disposed BufferInfo for: " + root);
 880             }
 881             if (root != null) {
 882                 root.removeComponentListener(this);
 883                 if (root instanceof Window) {
 884                     ((Window)root).removeWindowListener(this);
 885                 }
 886                 BufferStrategy bs = getBufferStrategy(false);
 887                 if (bs != null) {
 888                     bs.dispose();
 889                 }
 890             }
 891             this.root = null;
 892             weakBS = null;
 893         }
 894 
 895         // We mark the buffer as needing to be painted on a hide/iconify
 896         // because the developer may have conditionalized painting based on
 897         // visibility.
 898         // Ideally we would also move to having the BufferStrategy being
 899         // a SoftReference in Component here, but that requires changes to
 900         // Component and the like.
 901         public void componentHidden(ComponentEvent e) {
 902             Container root = getRoot();
 903             if (root != null && root.isVisible()) {
 904                 // This case will only happen if a developer calls
 905                 // hide immediately followed by show.  In this case
 906                 // the event is delivered after show and the window
 907                 // will still be visible.  If a developer altered the
 908                 // contents of the window between the hide/show
 909                 // invocations we won't recognize we need to paint and
 910                 // the contents would be bogus.  Calling repaint here
 911                 // fixs everything up.
 912                 root.repaint();
 913             }
 914             else {
 915                 setPaintAllOnExpose(true);
 916             }
 917         }
 918 
 919         public void windowIconified(WindowEvent e) {
 920             setPaintAllOnExpose(true);
 921         }
 922 
 923         // On a dispose we chuck everything.
 924         public void windowClosed(WindowEvent e) {
 925             // Make sure we're not showing.
 926             synchronized(BufferStrategyPaintManager.this) {
 927                 while (showing) {
 928                     try {
 929                         BufferStrategyPaintManager.this.wait();
 930                     } catch (InterruptedException ie) {
 931                     }
 932                 }
 933                 bufferInfos.remove(this);
 934             }
 935             dispose();
 936         }
 937 
 938         public void windowOpened(WindowEvent e) {
 939         }
 940 
 941         public void windowClosing(WindowEvent e) {
 942         }
 943 
 944         public void windowDeiconified(WindowEvent e) {
 945         }
 946 
 947         public void windowActivated(WindowEvent e) {
 948         }
 949 
 950         public void windowDeactivated(WindowEvent e) {
 951         }
 952     }
 953 }