1 /*
   2  * Copyright (c) 2007, 2008, 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;
  27 
  28 import java.awt.Color;
  29 import java.awt.Rectangle;
  30 import java.awt.AlphaComposite;
  31 import java.awt.GraphicsEnvironment;
  32 
  33 import sun.awt.DisplayChangedListener;
  34 import sun.java2d.StateTrackable.State;
  35 import sun.java2d.loops.CompositeType;
  36 import sun.java2d.loops.SurfaceType;
  37 import sun.java2d.loops.Blit;
  38 import sun.java2d.loops.BlitBg;
  39 import sun.awt.image.SurfaceManager;
  40 
  41 import java.security.AccessController;
  42 import sun.security.action.GetPropertyAction;
  43 
  44 /**
  45  * The proxy class encapsulates the logic for managing alternate
  46  * SurfaceData representations of a primary SurfaceData.
  47  * The main class will handle tracking the state changes of the
  48  * primary SurfaceData and updating the associated SurfaceData
  49  * proxy variants.
  50  * <p>
  51  * Subclasses have 2 main responsibilities:
  52  * <ul>
  53  * <li> Override the isSupportedOperation() method to determine if
  54  *      a given operation can be accelerated with a given source
  55  *      SurfaceData
  56  * <li> Override the validateSurfaceData() method to create or update
  57  *      a given accelerated surface to hold the pixels for the indicated
  58  *      source SurfaceData
  59  * </ul>
  60  * If necessary, a subclass may also override the updateSurfaceData
  61  * method to transfer the pixels to the accelerated surface.
  62  * By default the parent class will transfer the pixels using a
  63  * standard Blit operation between the two SurfaceData objects.
  64  */
  65 public abstract class SurfaceDataProxy
  66     implements DisplayChangedListener, SurfaceManager.FlushableCacheData
  67 {
  68     private static boolean cachingAllowed;
  69     private static int defaultThreshold;
  70 
  71     static {
  72         cachingAllowed = true;
  73         String manimg = AccessController.doPrivileged(
  74             new GetPropertyAction("sun.java2d.managedimages"));
  75         if (manimg != null && manimg.equals("false")) {
  76             cachingAllowed = false;
  77             System.out.println("Disabling managed images");
  78         }
  79 
  80         defaultThreshold = 1;
  81         String num = AccessController.doPrivileged(
  82             new GetPropertyAction("sun.java2d.accthreshold"));
  83         if (num != null) {
  84             try {
  85                 int parsed = Integer.parseInt(num);
  86                 if (parsed >= 0) {
  87                     defaultThreshold = parsed;
  88                     System.out.println("New Default Acceleration Threshold: " +
  89                                        defaultThreshold);
  90                 }
  91             } catch (NumberFormatException e) {
  92                 System.err.println("Error setting new threshold:" + e);
  93             }
  94         }
  95     }
  96 
  97     public static boolean isCachingAllowed() {
  98         return cachingAllowed;
  99     }
 100 
 101     /**
 102      * Determine if an alternate form for the srcData is needed
 103      * and appropriate from the given operational parameters.
 104      */
 105     public abstract boolean isSupportedOperation(SurfaceData srcData,
 106                                                  int txtype,
 107                                                  CompositeType comp,
 108                                                  Color bgColor);
 109 
 110     /**
 111      * Construct an alternate form of the given SurfaceData.
 112      * The contents of the returned SurfaceData may be undefined
 113      * since the calling code will take care of updating the
 114      * contents with a subsequent call to updateSurfaceData.
 115      * <p>
 116      * If the method returns null then there was a problem with
 117      * allocating the accelerated surface.  The getRetryTracker()
 118      * method will be called to track when to attempt another
 119      * revalidation.
 120      */
 121     public abstract SurfaceData validateSurfaceData(SurfaceData srcData,
 122                                                     SurfaceData cachedData,
 123                                                     int w, int h);
 124 
 125     /**
 126      * If the subclass is unable to validate or create a cached
 127      * SurfaceData then this method will be used to get a
 128      * StateTracker object that will indicate when to attempt
 129      * to validate the surface again.  Subclasses may return
 130      * trackers which count down an ever increasing threshold
 131      * to provide hysteresis on creating surfaces during low
 132      * memory conditions.  The default implementation just waits
 133      * another "threshold" number of accesses before trying again.
 134      */
 135     public StateTracker getRetryTracker(SurfaceData srcData) {
 136         return new CountdownTracker(threshold);
 137     }
 138 
 139     public static class CountdownTracker implements StateTracker {
 140         private int countdown;
 141 
 142         public CountdownTracker(int threshold) {
 143             this.countdown = threshold;
 144         }
 145 
 146         public synchronized boolean isCurrent() {
 147             return (--countdown >= 0);
 148         }
 149     }
 150 
 151     /**
 152      * This instance is for cases where a caching implementation
 153      * determines that a particular source image will never need
 154      * to be cached - either the source SurfaceData was of an
 155      * incompatible type, or it was in an UNTRACKABLE state or
 156      * some other factor is discovered that permanently prevents
 157      * acceleration or caching.
 158      * This class optimally implements NOP variants of all necessary
 159      * methods to avoid caching with a minimum of fuss.
 160      */
 161     public static SurfaceDataProxy UNCACHED = new SurfaceDataProxy(0) {
 162         @Override
 163         public boolean isAccelerated() {
 164             return false;
 165         }
 166 
 167         @Override
 168         public boolean isSupportedOperation(SurfaceData srcData,
 169                                             int txtype,
 170                                             CompositeType comp,
 171                                             Color bgColor)
 172         {
 173             return false;
 174         }
 175 
 176         @Override
 177         public SurfaceData validateSurfaceData(SurfaceData srcData,
 178                                                SurfaceData cachedData,
 179                                                int w, int h)
 180         {
 181             throw new InternalError("UNCACHED should never validate SDs");
 182         }
 183 
 184         @Override
 185         public SurfaceData replaceData(SurfaceData srcData,
 186                                        int txtype,
 187                                        CompositeType comp,
 188                                        Color bgColor)
 189         {
 190             // Not necessary to override this, but doing so is faster
 191             return srcData;
 192         }
 193     };
 194 
 195     // The number of attempts to copy from a STABLE source before
 196     // a cached copy is created or updated.
 197     private int threshold;
 198 
 199     /*
 200      * Source tracking data
 201      *
 202      * Every time that srcTracker is out of date we will reset numtries
 203      * to threshold and set the cacheTracker to one that is non-current.
 204      * numtries will then count down to 0 at which point the cacheTracker
 205      * will remind us that we need to update the cachedSD before we can
 206      * use it.
 207      *
 208      * Note that since these fields interrelate we should synchronize
 209      * whenever we update them, but it should be OK to read them
 210      * without synchronization.
 211      */
 212     private StateTracker srcTracker;
 213     private int numtries;
 214 
 215     /*
 216      * Cached data
 217      *
 218      * We cache a SurfaceData created by the subclass in cachedSD and
 219      * track its state (isValid and !surfaceLost) in cacheTracker.
 220      *
 221      * Also, when we want to note that cachedSD needs to be updated
 222      * we replace the cacheTracker with a NEVER_CURRENT tracker which
 223      * will cause us to try to revalidate and update the surface on
 224      * next use.
 225      */
 226     private SurfaceData cachedSD;
 227     private StateTracker cacheTracker;
 228 
 229     /*
 230      * Are we still the best object to control caching of data
 231      * for the source image?
 232      */
 233     private boolean valid;
 234 
 235     /**
 236      * Create a SurfaceData proxy manager that attempts to create
 237      * and cache a variant copy of the source SurfaceData after
 238      * the default threshold number of attempts to copy from the
 239      * STABLE source.
 240      */
 241     public SurfaceDataProxy() {
 242         this(defaultThreshold);
 243     }
 244 
 245     /**
 246      * Create a SurfaceData proxy manager that attempts to create
 247      * and cache a variant copy of the source SurfaceData after
 248      * the specified threshold number of attempts to copy from
 249      * the STABLE source.
 250      */
 251     public SurfaceDataProxy(int threshold) {
 252         this.threshold = threshold;
 253 
 254         this.srcTracker = StateTracker.NEVER_CURRENT;
 255         // numtries will be reset on first use
 256         this.cacheTracker = StateTracker.NEVER_CURRENT;
 257 
 258         this.valid = true;
 259     }
 260 
 261     /**
 262      * Returns true iff this SurfaceData proxy is still the best
 263      * way to control caching of the given source on the given
 264      * destination.
 265      */
 266     public boolean isValid() {
 267         return valid;
 268     }
 269 
 270     /**
 271      * Sets the valid state to false so that the next time this
 272      * proxy is fetched to generate a replacement SurfaceData,
 273      * the code in SurfaceData knows to replace the proxy first.
 274      */
 275     public void invalidate() {
 276         this.valid = false;
 277     }
 278 
 279     /**
 280      * Flush all cached resources as per the FlushableCacheData interface.
 281      * The deaccelerated parameter indicates if the flush is
 282      * happening because the associated surface is no longer
 283      * being accelerated (for instance the acceleration priority
 284      * is set below the threshold needed for acceleration).
 285      * Returns a boolean that indicates if the cached object is
 286      * no longer needed and should be removed from the cache.
 287      */
 288     public boolean flush(boolean deaccelerated) {
 289         if (deaccelerated) {
 290             invalidate();
 291         }
 292         flush();
 293         return !isValid();
 294     }
 295 
 296     /**
 297      * Actively flushes (drops and invalidates) the cached surface
 298      * so that it can be reclaimed quickly.
 299      */
 300     public synchronized void flush() {
 301         SurfaceData csd = this.cachedSD;
 302         this.cachedSD = null;
 303         this.cacheTracker = StateTracker.NEVER_CURRENT;
 304         if (csd != null) {
 305             csd.flush();
 306         }
 307     }
 308 
 309     /**
 310      * Returns true iff this SurfaceData proxy is still valid
 311      * and if it has a currently cached replacement that is also
 312      * valid and current.
 313      */
 314     public boolean isAccelerated() {
 315         return (isValid() &&
 316                 srcTracker.isCurrent() &&
 317                 cacheTracker.isCurrent());
 318     }
 319 
 320     /**
 321      * This method should be called from subclasses which create
 322      * cached SurfaceData objects that depend on the current
 323      * properties of the display.
 324      */
 325     protected void activateDisplayListener() {
 326         GraphicsEnvironment ge =
 327             GraphicsEnvironment.getLocalGraphicsEnvironment();
 328         // We could have a HeadlessGE at this point, so double-check before
 329         // assuming anything.
 330         // Also, no point in listening to display change events if
 331         // the image is never going to be accelerated.
 332         if (ge instanceof SunGraphicsEnvironment) {
 333             ((SunGraphicsEnvironment)ge).addDisplayChangedListener(this);
 334         }
 335     }
 336 
 337     /**
 338      * Invoked when the display mode has changed.
 339      * This method will invalidate and drop the internal cachedSD object.
 340      */
 341     public void displayChanged() {
 342         flush();
 343     }
 344 
 345     /**
 346      * Invoked when the palette has changed.
 347      */
 348     public void paletteChanged() {
 349         // We could potentially get away with just resetting cacheTracker
 350         // here but there is a small window of vulnerability in the
 351         // replaceData method where we could be just finished with
 352         // updating the cachedSD when this method is called and even
 353         // though we set a non-current cacheTracker here it will then
 354         // immediately get set to a current one by the thread that is
 355         // updating the cachedSD.  It is safer to just replace the
 356         // srcTracker with a non-current version that will trigger a
 357         // full update cycle the next time this proxy is used.
 358         // The downside is having to go through a full threshold count
 359         // before we can update and use our cache again, but palette
 360         // changes should be relatively rare...
 361         this.srcTracker = StateTracker.NEVER_CURRENT;
 362     }
 363 
 364     /**
 365      * This method attempts to replace the srcData with a cached version.
 366      * It relies on the subclass to determine if the cached version will
 367      * be useful given the operational parameters.
 368      * This method checks any preexisting cached copy for being "up to date"
 369      * and tries to update it if it is stale or non-existant and the
 370      * appropriate number of accesses have occured since it last was stale.
 371      * <p>
 372      * An outline of the process is as follows:
 373      * <ol>
 374      * <li> Check the operational parameters (txtype, comp, bgColor)
 375      *      to make sure that the operation is supported.  Return the
 376      *      original SurfaceData if the operation cannot be accelerated.
 377      * <li> Check the tracker for the source surface to see if it has
 378      *      remained stable since it was last cached.  Update the state
 379      *      variables to cause both a threshold countdown and an update
 380      *      of the cached copy if it is not.  (Setting cacheTracker to
 381      *      NEVER_CURRENT effectively marks it as "needing to be updated".)
 382      * <li> Check the tracker for the cached copy to see if is still
 383      *      valid and up to date.  Note that the cacheTracker may be
 384      *      non-current if either something happened to the cached copy
 385      *      (eg. surfaceLost) or if the source was out of date and the
 386      *      cacheTracker was set to NEVER_CURRENT to force an update.
 387      *      Decrement the countdown and copy the source to the cache
 388      *      as necessary and then update the variables to show that
 389      *      the cached copy is stable.
 390      * </ol>
 391      */
 392     public SurfaceData replaceData(SurfaceData srcData,
 393                                    int txtype,
 394                                    CompositeType comp,
 395                                    Color bgColor)
 396     {
 397         if (isSupportedOperation(srcData, txtype, comp, bgColor)) {
 398             // First deal with tracking the source.
 399             if (!srcTracker.isCurrent()) {
 400                 synchronized (this) {
 401                     this.numtries = threshold;
 402                     this.srcTracker = srcData.getStateTracker();
 403                     this.cacheTracker = StateTracker.NEVER_CURRENT;
 404                 }
 405 
 406                 if (!srcTracker.isCurrent()) {
 407                     // Dynamic or Untrackable (or a very recent modification)
 408                     if (srcData.getState() == State.UNTRACKABLE) {
 409                         // UNTRACKABLE means we can never cache again.
 410 
 411                         // Invalidate so we get replaced next time we are used
 412                         // (presumably with an UNCACHED proxy).
 413                         invalidate();
 414 
 415                         // Aggressively drop our reference to the cachedSD
 416                         // in case this proxy is not consulted again (and
 417                         // thus replaced) for a long time.
 418                         flush();
 419                     }
 420                     return srcData;
 421                 }
 422             }
 423 
 424             // Then deal with checking the validity of the cached SurfaceData
 425             SurfaceData csd = this.cachedSD;
 426             if (!cacheTracker.isCurrent()) {
 427                 // Next make sure the dust has settled
 428                 synchronized (this) {
 429                     if (numtries > 0) {
 430                         --numtries;
 431                         return srcData;
 432                     }
 433                 }
 434 
 435                 Rectangle r = srcData.getBounds();
 436                 int w = r.width;
 437                 int h = r.height;
 438 
 439                 // Snapshot the tracker in case it changes while
 440                 // we are updating the cached SD...
 441                 StateTracker curTracker = srcTracker;
 442 
 443                 csd = validateSurfaceData(srcData, csd, w, h);
 444                 if (csd == null) {
 445                     synchronized (this) {
 446                         if (curTracker == srcTracker) {
 447                             this.cacheTracker = getRetryTracker(srcData);
 448                             this.cachedSD = null;
 449                         }
 450                     }
 451                     return srcData;
 452                 }
 453 
 454                 updateSurfaceData(srcData, csd, w, h);
 455                 if (!csd.isValid()) {
 456                     return srcData;
 457                 }
 458 
 459                 synchronized (this) {
 460                     // We only reset these variables if the tracker from
 461                     // before the surface update is still in use and current
 462                     // Note that we must use a srcTracker that was fetched
 463                     // from before the update process to make sure that we
 464                     // do not lose some pixel changes in the shuffle.
 465                     if (curTracker == srcTracker && curTracker.isCurrent()) {
 466                         this.cacheTracker = csd.getStateTracker();
 467                         this.cachedSD = csd;
 468                     }
 469                 }
 470             }
 471 
 472             if (csd != null) {
 473                 return csd;
 474             }
 475         }
 476 
 477         return srcData;
 478     }
 479 
 480     /**
 481      * This is the default implementation for updating the cached
 482      * SurfaceData from the source (primary) SurfaceData.
 483      * A simple Blit is used to copy the pixels from the source to
 484      * the destination SurfaceData.
 485      * A subclass can override this implementation if a more complex
 486      * operation is required to update its cached copies.
 487      */
 488     public void updateSurfaceData(SurfaceData srcData,
 489                                   SurfaceData dstData,
 490                                   int w, int h)
 491     {
 492         SurfaceType srcType = srcData.getSurfaceType();
 493         SurfaceType dstType = dstData.getSurfaceType();
 494         Blit blit = Blit.getFromCache(srcType,
 495                                       CompositeType.SrcNoEa,
 496                                       dstType);
 497         blit.Blit(srcData, dstData,
 498                   AlphaComposite.Src, null,
 499                   0, 0, 0, 0, w, h);
 500         dstData.markDirty();
 501     }
 502 
 503     /**
 504      * This is an alternate implementation for updating the cached
 505      * SurfaceData from the source (primary) SurfaceData using a
 506      * background color for transparent pixels.
 507      * A simple BlitBg is used to copy the pixels from the source to
 508      * the destination SurfaceData with the specified bgColor.
 509      * A subclass can override the normal updateSurfaceData method
 510      * and call this implementation instead if it wants to use color
 511      * keying for bitmask images.
 512      */
 513     public void updateSurfaceDataBg(SurfaceData srcData,
 514                                     SurfaceData dstData,
 515                                     int w, int h, Color bgColor)
 516     {
 517         SurfaceType srcType = srcData.getSurfaceType();
 518         SurfaceType dstType = dstData.getSurfaceType();
 519         BlitBg blitbg = BlitBg.getFromCache(srcType,
 520                                             CompositeType.SrcNoEa,
 521                                             dstType);
 522         blitbg.BlitBg(srcData, dstData,
 523                       AlphaComposite.Src, null, bgColor.getRGB(),
 524                       0, 0, 0, 0, w, h);
 525         dstData.markDirty();
 526     }
 527 }