529 lines
20 KiB
Java
529 lines
20 KiB
Java
/*
|
|
* 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();
|
|
}
|
|
}
|