/* |
|
* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
package sun.java2d; |
|
import java.awt.Color; |
|
import java.awt.Rectangle; |
|
import java.awt.AlphaComposite; |
|
import java.awt.GraphicsEnvironment; |
|
import sun.awt.DisplayChangedListener; |
|
import sun.java2d.StateTrackable.State; |
|
import sun.java2d.loops.CompositeType; |
|
import sun.java2d.loops.SurfaceType; |
|
import sun.java2d.loops.Blit; |
|
import sun.java2d.loops.BlitBg; |
|
import sun.awt.image.SurfaceManager; |
|
import sun.awt.image.SurfaceManager.FlushableCacheData; |
|
import java.security.AccessController; |
|
import sun.security.action.GetPropertyAction; |
|
/** |
|
* The proxy class encapsulates the logic for managing alternate |
|
* SurfaceData representations of a primary SurfaceData. |
|
* The main class will handle tracking the state changes of the |
|
* primary SurfaceData and updating the associated SurfaceData |
|
* proxy variants. |
|
* <p> |
|
* Subclasses have 2 main responsibilities: |
|
* <ul> |
|
* <li> Override the isSupportedOperation() method to determine if |
|
* a given operation can be accelerated with a given source |
|
* SurfaceData |
|
* <li> Override the validateSurfaceData() method to create or update |
|
* a given accelerated surface to hold the pixels for the indicated |
|
* source SurfaceData |
|
* </ul> |
|
* If necessary, a subclass may also override the updateSurfaceData |
|
* method to transfer the pixels to the accelerated surface. |
|
* By default the parent class will transfer the pixels using a |
|
* standard Blit operation between the two SurfaceData objects. |
|
*/ |
|
public abstract class SurfaceDataProxy |
|
implements DisplayChangedListener, SurfaceManager.FlushableCacheData |
|
{ |
|
private static boolean cachingAllowed; |
|
private static int defaultThreshold; |
|
static { |
|
cachingAllowed = true; |
|
String manimg = AccessController.doPrivileged( |
|
new GetPropertyAction("sun.java2d.managedimages")); |
|
if (manimg != null && manimg.equals("false")) { |
|
cachingAllowed = false; |
|
System.out.println("Disabling managed images"); |
|
} |
|
defaultThreshold = 1; |
|
String num = AccessController.doPrivileged( |
|
new GetPropertyAction("sun.java2d.accthreshold")); |
|
if (num != null) { |
|
try { |
|
int parsed = Integer.parseInt(num); |
|
if (parsed >= 0) { |
|
defaultThreshold = parsed; |
|
System.out.println("New Default Acceleration Threshold: " + |
|
defaultThreshold); |
|
} |
|
} catch (NumberFormatException e) { |
|
System.err.println("Error setting new threshold:" + e); |
|
} |
|
} |
|
} |
|
public static boolean isCachingAllowed() { |
|
return cachingAllowed; |
|
} |
|
/** |
|
* Determine if an alternate form for the srcData is needed |
|
* and appropriate from the given operational parameters. |
|
*/ |
|
public abstract boolean isSupportedOperation(SurfaceData srcData, |
|
int txtype, |
|
CompositeType comp, |
|
Color bgColor); |
|
/** |
|
* Construct an alternate form of the given SurfaceData. |
|
* The contents of the returned SurfaceData may be undefined |
|
* since the calling code will take care of updating the |
|
* contents with a subsequent call to updateSurfaceData. |
|
* <p> |
|
* If the method returns null then there was a problem with |
|
* allocating the accelerated surface. The getRetryTracker() |
|
* method will be called to track when to attempt another |
|
* revalidation. |
|
*/ |
|
public abstract SurfaceData validateSurfaceData(SurfaceData srcData, |
|
SurfaceData cachedData, |
|
int w, int h); |
|
/** |
|
* If the subclass is unable to validate or create a cached |
|
* SurfaceData then this method will be used to get a |
|
* StateTracker object that will indicate when to attempt |
|
* to validate the surface again. Subclasses may return |
|
* trackers which count down an ever increasing threshold |
|
* to provide hysteresis on creating surfaces during low |
|
* memory conditions. The default implementation just waits |
|
* another "threshold" number of accesses before trying again. |
|
*/ |
|
public StateTracker getRetryTracker(SurfaceData srcData) { |
|
return new CountdownTracker(threshold); |
|
} |
|
public static class CountdownTracker implements StateTracker { |
|
private int countdown; |
|
public CountdownTracker(int threshold) { |
|
this.countdown = threshold; |
|
} |
|
public synchronized boolean isCurrent() { |
|
return (--countdown >= 0); |
|
} |
|
} |
|
/** |
|
* This instance is for cases where a caching implementation |
|
* determines that a particular source image will never need |
|
* to be cached - either the source SurfaceData was of an |
|
* incompatible type, or it was in an UNTRACKABLE state or |
|
* some other factor is discovered that permanently prevents |
|
* acceleration or caching. |
|
* This class optimally implements NOP variants of all necessary |
|
* methods to avoid caching with a minimum of fuss. |
|
*/ |
|
public static SurfaceDataProxy UNCACHED = new SurfaceDataProxy(0) { |
|
@Override |
|
public boolean isAccelerated() { |
|
return false; |
|
} |
|
@Override |
|
public boolean isSupportedOperation(SurfaceData srcData, |
|
int txtype, |
|
CompositeType comp, |
|
Color bgColor) |
|
{ |
|
return false; |
|
} |
|
@Override |
|
public SurfaceData validateSurfaceData(SurfaceData srcData, |
|
SurfaceData cachedData, |
|
int w, int h) |
|
{ |
|
throw new InternalError("UNCACHED should never validate SDs"); |
|
} |
|
@Override |
|
public SurfaceData replaceData(SurfaceData srcData, |
|
int txtype, |
|
CompositeType comp, |
|
Color bgColor) |
|
{ |
|
// Not necessary to override this, but doing so is faster |
|
return srcData; |
|
} |
|
}; |
|
// The number of attempts to copy from a STABLE source before |
|
// a cached copy is created or updated. |
|
private int threshold; |
|
/* |
|
* Source tracking data |
|
* |
|
* Every time that srcTracker is out of date we will reset numtries |
|
* to threshold and set the cacheTracker to one that is non-current. |
|
* numtries will then count down to 0 at which point the cacheTracker |
|
* will remind us that we need to update the cachedSD before we can |
|
* use it. |
|
* |
|
* Note that since these fields interrelate we should synchronize |
|
* whenever we update them, but it should be OK to read them |
|
* without synchronization. |
|
*/ |
|
private StateTracker srcTracker; |
|
private int numtries; |
|
/* |
|
* Cached data |
|
* |
|
* We cache a SurfaceData created by the subclass in cachedSD and |
|
* track its state (isValid and !surfaceLost) in cacheTracker. |
|
* |
|
* Also, when we want to note that cachedSD needs to be updated |
|
* we replace the cacheTracker with a NEVER_CURRENT tracker which |
|
* will cause us to try to revalidate and update the surface on |
|
* next use. |
|
*/ |
|
private SurfaceData cachedSD; |
|
private StateTracker cacheTracker; |
|
/* |
|
* Are we still the best object to control caching of data |
|
* for the source image? |
|
*/ |
|
private boolean valid; |
|
/** |
|
* Create a SurfaceData proxy manager that attempts to create |
|
* and cache a variant copy of the source SurfaceData after |
|
* the default threshold number of attempts to copy from the |
|
* STABLE source. |
|
*/ |
|
public SurfaceDataProxy() { |
|
this(defaultThreshold); |
|
} |
|
/** |
|
* Create a SurfaceData proxy manager that attempts to create |
|
* and cache a variant copy of the source SurfaceData after |
|
* the specified threshold number of attempts to copy from |
|
* the STABLE source. |
|
*/ |
|
public SurfaceDataProxy(int threshold) { |
|
this.threshold = threshold; |
|
this.srcTracker = StateTracker.NEVER_CURRENT; |
|
// numtries will be reset on first use |
|
this.cacheTracker = StateTracker.NEVER_CURRENT; |
|
this.valid = true; |
|
} |
|
/** |
|
* Returns true iff this SurfaceData proxy is still the best |
|
* way to control caching of the given source on the given |
|
* destination. |
|
*/ |
|
public boolean isValid() { |
|
return valid; |
|
} |
|
/** |
|
* Sets the valid state to false so that the next time this |
|
* proxy is fetched to generate a replacement SurfaceData, |
|
* the code in SurfaceData knows to replace the proxy first. |
|
*/ |
|
public void invalidate() { |
|
this.valid = false; |
|
} |
|
/** |
|
* Flush all cached resources as per the FlushableCacheData interface. |
|
* The deaccelerated parameter indicates if the flush is |
|
* happening because the associated surface is no longer |
|
* being accelerated (for instance the acceleration priority |
|
* is set below the threshold needed for acceleration). |
|
* Returns a boolean that indicates if the cached object is |
|
* no longer needed and should be removed from the cache. |
|
*/ |
|
public boolean flush(boolean deaccelerated) { |
|
if (deaccelerated) { |
|
invalidate(); |
|
} |
|
flush(); |
|
return !isValid(); |
|
} |
|
/** |
|
* Actively flushes (drops and invalidates) the cached surface |
|
* so that it can be reclaimed quickly. |
|
*/ |
|
public synchronized void flush() { |
|
SurfaceData csd = this.cachedSD; |
|
this.cachedSD = null; |
|
this.cacheTracker = StateTracker.NEVER_CURRENT; |
|
if (csd != null) { |
|
csd.flush(); |
|
} |
|
} |
|
/** |
|
* Returns true iff this SurfaceData proxy is still valid |
|
* and if it has a currently cached replacement that is also |
|
* valid and current. |
|
*/ |
|
public boolean isAccelerated() { |
|
return (isValid() && |
|
srcTracker.isCurrent() && |
|
cacheTracker.isCurrent()); |
|
} |
|
/** |
|
* This method should be called from subclasses which create |
|
* cached SurfaceData objects that depend on the current |
|
* properties of the display. |
|
*/ |
|
protected void activateDisplayListener() { |
|
GraphicsEnvironment ge = |
|
GraphicsEnvironment.getLocalGraphicsEnvironment(); |
|
// We could have a HeadlessGE at this point, so double-check before |
|
// assuming anything. |
|
// Also, no point in listening to display change events if |
|
// the image is never going to be accelerated. |
|
if (ge instanceof SunGraphicsEnvironment) { |
|
((SunGraphicsEnvironment)ge).addDisplayChangedListener(this); |
|
} |
|
} |
|
/** |
|
* Invoked when the display mode has changed. |
|
* This method will invalidate and drop the internal cachedSD object. |
|
*/ |
|
public void displayChanged() { |
|
flush(); |
|
} |
|
/** |
|
* Invoked when the palette has changed. |
|
*/ |
|
public void paletteChanged() { |
|
// We could potentially get away with just resetting cacheTracker |
|
// here but there is a small window of vulnerability in the |
|
// replaceData method where we could be just finished with |
|
// updating the cachedSD when this method is called and even |
|
// though we set a non-current cacheTracker here it will then |
|
// immediately get set to a current one by the thread that is |
|
// updating the cachedSD. It is safer to just replace the |
|
// srcTracker with a non-current version that will trigger a |
|
// full update cycle the next time this proxy is used. |
|
// The downside is having to go through a full threshold count |
|
// before we can update and use our cache again, but palette |
|
// changes should be relatively rare... |
|
this.srcTracker = StateTracker.NEVER_CURRENT; |
|
} |
|
/** |
|
* This method attempts to replace the srcData with a cached version. |
|
* It relies on the subclass to determine if the cached version will |
|
* be useful given the operational parameters. |
|
* This method checks any preexisting cached copy for being "up to date" |
|
* and tries to update it if it is stale or non-existant and the |
|
* appropriate number of accesses have occurred since it last was stale. |
|
* <p> |
|
* An outline of the process is as follows: |
|
* <ol> |
|
* <li> Check the operational parameters (txtype, comp, bgColor) |
|
* to make sure that the operation is supported. Return the |
|
* original SurfaceData if the operation cannot be accelerated. |
|
* <li> Check the tracker for the source surface to see if it has |
|
* remained stable since it was last cached. Update the state |
|
* variables to cause both a threshold countdown and an update |
|
* of the cached copy if it is not. (Setting cacheTracker to |
|
* NEVER_CURRENT effectively marks it as "needing to be updated".) |
|
* <li> Check the tracker for the cached copy to see if is still |
|
* valid and up to date. Note that the cacheTracker may be |
|
* non-current if either something happened to the cached copy |
|
* (eg. surfaceLost) or if the source was out of date and the |
|
* cacheTracker was set to NEVER_CURRENT to force an update. |
|
* Decrement the countdown and copy the source to the cache |
|
* as necessary and then update the variables to show that |
|
* the cached copy is stable. |
|
* </ol> |
|
*/ |
|
public SurfaceData replaceData(SurfaceData srcData, |
|
int txtype, |
|
CompositeType comp, |
|
Color bgColor) |
|
{ |
|
if (isSupportedOperation(srcData, txtype, comp, bgColor)) { |
|
// First deal with tracking the source. |
|
if (!srcTracker.isCurrent()) { |
|
synchronized (this) { |
|
this.numtries = threshold; |
|
this.srcTracker = srcData.getStateTracker(); |
|
this.cacheTracker = StateTracker.NEVER_CURRENT; |
|
} |
|
if (!srcTracker.isCurrent()) { |
|
// Dynamic or Untrackable (or a very recent modification) |
|
if (srcData.getState() == State.UNTRACKABLE) { |
|
// UNTRACKABLE means we can never cache again. |
|
// Invalidate so we get replaced next time we are used |
|
// (presumably with an UNCACHED proxy). |
|
invalidate(); |
|
// Aggressively drop our reference to the cachedSD |
|
// in case this proxy is not consulted again (and |
|
// thus replaced) for a long time. |
|
flush(); |
|
} |
|
return srcData; |
|
} |
|
} |
|
// Then deal with checking the validity of the cached SurfaceData |
|
SurfaceData csd = this.cachedSD; |
|
if (!cacheTracker.isCurrent()) { |
|
// Next make sure the dust has settled |
|
synchronized (this) { |
|
if (numtries > 0) { |
|
--numtries; |
|
return srcData; |
|
} |
|
} |
|
Rectangle r = srcData.getBounds(); |
|
int w = r.width; |
|
int h = r.height; |
|
// Snapshot the tracker in case it changes while |
|
// we are updating the cached SD... |
|
StateTracker curTracker = srcTracker; |
|
csd = validateSurfaceData(srcData, csd, w, h); |
|
if (csd == null) { |
|
synchronized (this) { |
|
if (curTracker == srcTracker) { |
|
this.cacheTracker = getRetryTracker(srcData); |
|
this.cachedSD = null; |
|
} |
|
} |
|
return srcData; |
|
} |
|
updateSurfaceData(srcData, csd, w, h); |
|
if (!csd.isValid()) { |
|
return srcData; |
|
} |
|
synchronized (this) { |
|
// We only reset these variables if the tracker from |
|
// before the surface update is still in use and current |
|
// Note that we must use a srcTracker that was fetched |
|
// from before the update process to make sure that we |
|
// do not lose some pixel changes in the shuffle. |
|
if (curTracker == srcTracker && curTracker.isCurrent()) { |
|
this.cacheTracker = csd.getStateTracker(); |
|
this.cachedSD = csd; |
|
} |
|
} |
|
} |
|
if (csd != null) { |
|
return csd; |
|
} |
|
} |
|
return srcData; |
|
} |
|
/** |
|
* This is the default implementation for updating the cached |
|
* SurfaceData from the source (primary) SurfaceData. |
|
* A simple Blit is used to copy the pixels from the source to |
|
* the destination SurfaceData. |
|
* A subclass can override this implementation if a more complex |
|
* operation is required to update its cached copies. |
|
*/ |
|
public void updateSurfaceData(SurfaceData srcData, |
|
SurfaceData dstData, |
|
int w, int h) |
|
{ |
|
SurfaceType srcType = srcData.getSurfaceType(); |
|
SurfaceType dstType = dstData.getSurfaceType(); |
|
Blit blit = Blit.getFromCache(srcType, |
|
CompositeType.SrcNoEa, |
|
dstType); |
|
blit.Blit(srcData, dstData, |
|
AlphaComposite.Src, null, |
|
0, 0, 0, 0, w, h); |
|
dstData.markDirty(); |
|
} |
|
/** |
|
* This is an alternate implementation for updating the cached |
|
* SurfaceData from the source (primary) SurfaceData using a |
|
* background color for transparent pixels. |
|
* A simple BlitBg is used to copy the pixels from the source to |
|
* the destination SurfaceData with the specified bgColor. |
|
* A subclass can override the normal updateSurfaceData method |
|
* and call this implementation instead if it wants to use color |
|
* keying for bitmask images. |
|
*/ |
|
public void updateSurfaceDataBg(SurfaceData srcData, |
|
SurfaceData dstData, |
|
int w, int h, Color bgColor) |
|
{ |
|
SurfaceType srcType = srcData.getSurfaceType(); |
|
SurfaceType dstType = dstData.getSurfaceType(); |
|
BlitBg blitbg = BlitBg.getFromCache(srcType, |
|
CompositeType.SrcNoEa, |
|
dstType); |
|
blitbg.BlitBg(srcData, dstData, |
|
AlphaComposite.Src, null, bgColor.getRGB(), |
|
0, 0, 0, 0, w, h); |
|
dstData.markDirty(); |
|
} |
|
} |