/*
* Copyright (c) 2007, 2008, 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 = (String)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 = (String)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 occured 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();
}
}