| /* | |
| * Copyright (c) 2007, 2024, 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; | |
| /** | |
| * 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 = System.getProperty("sun.java2d.managedimages"); | |
| if ("false".equals(manimg)) { | |
| cachingAllowed = false; | |
| System.out.println("Disabling managed images"); | |
| } | |
| defaultThreshold = 1; | |
| String num = System.getProperty("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-existent 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(); | |
| } | |
| } |