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 }