/* * 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 = 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 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(); } }