1/*
2 * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.java2d;
27
28import java.awt.Color;
29import java.awt.Rectangle;
30import java.awt.AlphaComposite;
31import java.awt.GraphicsEnvironment;
32
33import sun.awt.DisplayChangedListener;
34import sun.java2d.StateTrackable.State;
35import sun.java2d.loops.CompositeType;
36import sun.java2d.loops.SurfaceType;
37import sun.java2d.loops.Blit;
38import sun.java2d.loops.BlitBg;
39import sun.awt.image.SurfaceManager;
40import sun.awt.image.SurfaceManager.FlushableCacheData;
41
42import java.security.AccessController;
43import sun.security.action.GetPropertyAction;
44
45/**
46 * The proxy class encapsulates the logic for managing alternate
47 * SurfaceData representations of a primary SurfaceData.
48 * The main class will handle tracking the state changes of the
49 * primary SurfaceData and updating the associated SurfaceData
50 * proxy variants.
51 * <p>
52 * Subclasses have 2 main responsibilities:
53 * <ul>
54 * <li> Override the isSupportedOperation() method to determine if
55 *      a given operation can be accelerated with a given source
56 *      SurfaceData
57 * <li> Override the validateSurfaceData() method to create or update
58 *      a given accelerated surface to hold the pixels for the indicated
59 *      source SurfaceData
60 * </ul>
61 * If necessary, a subclass may also override the updateSurfaceData
62 * method to transfer the pixels to the accelerated surface.
63 * By default the parent class will transfer the pixels using a
64 * standard Blit operation between the two SurfaceData objects.
65 */
66public abstract class SurfaceDataProxy
67    implements DisplayChangedListener, SurfaceManager.FlushableCacheData
68{
69    private static boolean cachingAllowed;
70    private static int defaultThreshold;
71
72    static {
73        cachingAllowed = true;
74        String manimg = AccessController.doPrivileged(
75            new GetPropertyAction("sun.java2d.managedimages"));
76        if (manimg != null && manimg.equals("false")) {
77            cachingAllowed = false;
78            System.out.println("Disabling managed images");
79        }
80
81        defaultThreshold = 1;
82        String num = AccessController.doPrivileged(
83            new GetPropertyAction("sun.java2d.accthreshold"));
84        if (num != null) {
85            try {
86                int parsed = Integer.parseInt(num);
87                if (parsed >= 0) {
88                    defaultThreshold = parsed;
89                    System.out.println("New Default Acceleration Threshold: " +
90                                       defaultThreshold);
91                }
92            } catch (NumberFormatException e) {
93                System.err.println("Error setting new threshold:" + e);
94            }
95        }
96    }
97
98    public static boolean isCachingAllowed() {
99        return cachingAllowed;
100    }
101
102    /**
103     * Determine if an alternate form for the srcData is needed
104     * and appropriate from the given operational parameters.
105     */
106    public abstract boolean isSupportedOperation(SurfaceData srcData,
107                                                 int txtype,
108                                                 CompositeType comp,
109                                                 Color bgColor);
110
111    /**
112     * Construct an alternate form of the given SurfaceData.
113     * The contents of the returned SurfaceData may be undefined
114     * since the calling code will take care of updating the
115     * contents with a subsequent call to updateSurfaceData.
116     * <p>
117     * If the method returns null then there was a problem with
118     * allocating the accelerated surface.  The getRetryTracker()
119     * method will be called to track when to attempt another
120     * revalidation.
121     */
122    public abstract SurfaceData validateSurfaceData(SurfaceData srcData,
123                                                    SurfaceData cachedData,
124                                                    int w, int h);
125
126    /**
127     * If the subclass is unable to validate or create a cached
128     * SurfaceData then this method will be used to get a
129     * StateTracker object that will indicate when to attempt
130     * to validate the surface again.  Subclasses may return
131     * trackers which count down an ever increasing threshold
132     * to provide hysteresis on creating surfaces during low
133     * memory conditions.  The default implementation just waits
134     * another "threshold" number of accesses before trying again.
135     */
136    public StateTracker getRetryTracker(SurfaceData srcData) {
137        return new CountdownTracker(threshold);
138    }
139
140    public static class CountdownTracker implements StateTracker {
141        private int countdown;
142
143        public CountdownTracker(int threshold) {
144            this.countdown = threshold;
145        }
146
147        public synchronized boolean isCurrent() {
148            return (--countdown >= 0);
149        }
150    }
151
152    /**
153     * This instance is for cases where a caching implementation
154     * determines that a particular source image will never need
155     * to be cached - either the source SurfaceData was of an
156     * incompatible type, or it was in an UNTRACKABLE state or
157     * some other factor is discovered that permanently prevents
158     * acceleration or caching.
159     * This class optimally implements NOP variants of all necessary
160     * methods to avoid caching with a minimum of fuss.
161     */
162    public static SurfaceDataProxy UNCACHED = new SurfaceDataProxy(0) {
163        @Override
164        public boolean isAccelerated() {
165            return false;
166        }
167
168        @Override
169        public boolean isSupportedOperation(SurfaceData srcData,
170                                            int txtype,
171                                            CompositeType comp,
172                                            Color bgColor)
173        {
174            return false;
175        }
176
177        @Override
178        public SurfaceData validateSurfaceData(SurfaceData srcData,
179                                               SurfaceData cachedData,
180                                               int w, int h)
181        {
182            throw new InternalError("UNCACHED should never validate SDs");
183        }
184
185        @Override
186        public SurfaceData replaceData(SurfaceData srcData,
187                                       int txtype,
188                                       CompositeType comp,
189                                       Color bgColor)
190        {
191            // Not necessary to override this, but doing so is faster
192            return srcData;
193        }
194    };
195
196    // The number of attempts to copy from a STABLE source before
197    // a cached copy is created or updated.
198    private int threshold;
199
200    /*
201     * Source tracking data
202     *
203     * Every time that srcTracker is out of date we will reset numtries
204     * to threshold and set the cacheTracker to one that is non-current.
205     * numtries will then count down to 0 at which point the cacheTracker
206     * will remind us that we need to update the cachedSD before we can
207     * use it.
208     *
209     * Note that since these fields interrelate we should synchronize
210     * whenever we update them, but it should be OK to read them
211     * without synchronization.
212     */
213    private StateTracker srcTracker;
214    private int numtries;
215
216    /*
217     * Cached data
218     *
219     * We cache a SurfaceData created by the subclass in cachedSD and
220     * track its state (isValid and !surfaceLost) in cacheTracker.
221     *
222     * Also, when we want to note that cachedSD needs to be updated
223     * we replace the cacheTracker with a NEVER_CURRENT tracker which
224     * will cause us to try to revalidate and update the surface on
225     * next use.
226     */
227    private SurfaceData cachedSD;
228    private StateTracker cacheTracker;
229
230    /*
231     * Are we still the best object to control caching of data
232     * for the source image?
233     */
234    private boolean valid;
235
236    /**
237     * Create a SurfaceData proxy manager that attempts to create
238     * and cache a variant copy of the source SurfaceData after
239     * the default threshold number of attempts to copy from the
240     * STABLE source.
241     */
242    public SurfaceDataProxy() {
243        this(defaultThreshold);
244    }
245
246    /**
247     * Create a SurfaceData proxy manager that attempts to create
248     * and cache a variant copy of the source SurfaceData after
249     * the specified threshold number of attempts to copy from
250     * the STABLE source.
251     */
252    public SurfaceDataProxy(int threshold) {
253        this.threshold = threshold;
254
255        this.srcTracker = StateTracker.NEVER_CURRENT;
256        // numtries will be reset on first use
257        this.cacheTracker = StateTracker.NEVER_CURRENT;
258
259        this.valid = true;
260    }
261
262    /**
263     * Returns true iff this SurfaceData proxy is still the best
264     * way to control caching of the given source on the given
265     * destination.
266     */
267    public boolean isValid() {
268        return valid;
269    }
270
271    /**
272     * Sets the valid state to false so that the next time this
273     * proxy is fetched to generate a replacement SurfaceData,
274     * the code in SurfaceData knows to replace the proxy first.
275     */
276    public void invalidate() {
277        this.valid = false;
278    }
279
280    /**
281     * Flush all cached resources as per the FlushableCacheData interface.
282     * The deaccelerated parameter indicates if the flush is
283     * happening because the associated surface is no longer
284     * being accelerated (for instance the acceleration priority
285     * is set below the threshold needed for acceleration).
286     * Returns a boolean that indicates if the cached object is
287     * no longer needed and should be removed from the cache.
288     */
289    public boolean flush(boolean deaccelerated) {
290        if (deaccelerated) {
291            invalidate();
292        }
293        flush();
294        return !isValid();
295    }
296
297    /**
298     * Actively flushes (drops and invalidates) the cached surface
299     * so that it can be reclaimed quickly.
300     */
301    public synchronized void flush() {
302        SurfaceData csd = this.cachedSD;
303        this.cachedSD = null;
304        this.cacheTracker = StateTracker.NEVER_CURRENT;
305        if (csd != null) {
306            csd.flush();
307        }
308    }
309
310    /**
311     * Returns true iff this SurfaceData proxy is still valid
312     * and if it has a currently cached replacement that is also
313     * valid and current.
314     */
315    public boolean isAccelerated() {
316        return (isValid() &&
317                srcTracker.isCurrent() &&
318                cacheTracker.isCurrent());
319    }
320
321    /**
322     * This method should be called from subclasses which create
323     * cached SurfaceData objects that depend on the current
324     * properties of the display.
325     */
326    protected void activateDisplayListener() {
327        GraphicsEnvironment ge =
328            GraphicsEnvironment.getLocalGraphicsEnvironment();
329        // We could have a HeadlessGE at this point, so double-check before
330        // assuming anything.
331        // Also, no point in listening to display change events if
332        // the image is never going to be accelerated.
333        if (ge instanceof SunGraphicsEnvironment) {
334            ((SunGraphicsEnvironment)ge).addDisplayChangedListener(this);
335        }
336    }
337
338    /**
339     * Invoked when the display mode has changed.
340     * This method will invalidate and drop the internal cachedSD object.
341     */
342    public void displayChanged() {
343        flush();
344    }
345
346    /**
347     * Invoked when the palette has changed.
348     */
349    public void paletteChanged() {
350        // We could potentially get away with just resetting cacheTracker
351        // here but there is a small window of vulnerability in the
352        // replaceData method where we could be just finished with
353        // updating the cachedSD when this method is called and even
354        // though we set a non-current cacheTracker here it will then
355        // immediately get set to a current one by the thread that is
356        // updating the cachedSD.  It is safer to just replace the
357        // srcTracker with a non-current version that will trigger a
358        // full update cycle the next time this proxy is used.
359        // The downside is having to go through a full threshold count
360        // before we can update and use our cache again, but palette
361        // changes should be relatively rare...
362        this.srcTracker = StateTracker.NEVER_CURRENT;
363    }
364
365    /**
366     * This method attempts to replace the srcData with a cached version.
367     * It relies on the subclass to determine if the cached version will
368     * be useful given the operational parameters.
369     * This method checks any preexisting cached copy for being "up to date"
370     * and tries to update it if it is stale or non-existant and the
371     * appropriate number of accesses have occurred since it last was stale.
372     * <p>
373     * An outline of the process is as follows:
374     * <ol>
375     * <li> Check the operational parameters (txtype, comp, bgColor)
376     *      to make sure that the operation is supported.  Return the
377     *      original SurfaceData if the operation cannot be accelerated.
378     * <li> Check the tracker for the source surface to see if it has
379     *      remained stable since it was last cached.  Update the state
380     *      variables to cause both a threshold countdown and an update
381     *      of the cached copy if it is not.  (Setting cacheTracker to
382     *      NEVER_CURRENT effectively marks it as "needing to be updated".)
383     * <li> Check the tracker for the cached copy to see if is still
384     *      valid and up to date.  Note that the cacheTracker may be
385     *      non-current if either something happened to the cached copy
386     *      (eg. surfaceLost) or if the source was out of date and the
387     *      cacheTracker was set to NEVER_CURRENT to force an update.
388     *      Decrement the countdown and copy the source to the cache
389     *      as necessary and then update the variables to show that
390     *      the cached copy is stable.
391     * </ol>
392     */
393    public SurfaceData replaceData(SurfaceData srcData,
394                                   int txtype,
395                                   CompositeType comp,
396                                   Color bgColor)
397    {
398        if (isSupportedOperation(srcData, txtype, comp, bgColor)) {
399            // First deal with tracking the source.
400            if (!srcTracker.isCurrent()) {
401                synchronized (this) {
402                    this.numtries = threshold;
403                    this.srcTracker = srcData.getStateTracker();
404                    this.cacheTracker = StateTracker.NEVER_CURRENT;
405                }
406
407                if (!srcTracker.isCurrent()) {
408                    // Dynamic or Untrackable (or a very recent modification)
409                    if (srcData.getState() == State.UNTRACKABLE) {
410                        // UNTRACKABLE means we can never cache again.
411
412                        // Invalidate so we get replaced next time we are used
413                        // (presumably with an UNCACHED proxy).
414                        invalidate();
415
416                        // Aggressively drop our reference to the cachedSD
417                        // in case this proxy is not consulted again (and
418                        // thus replaced) for a long time.
419                        flush();
420                    }
421                    return srcData;
422                }
423            }
424
425            // Then deal with checking the validity of the cached SurfaceData
426            SurfaceData csd = this.cachedSD;
427            if (!cacheTracker.isCurrent()) {
428                // Next make sure the dust has settled
429                synchronized (this) {
430                    if (numtries > 0) {
431                        --numtries;
432                        return srcData;
433                    }
434                }
435
436                Rectangle r = srcData.getBounds();
437                int w = r.width;
438                int h = r.height;
439
440                // Snapshot the tracker in case it changes while
441                // we are updating the cached SD...
442                StateTracker curTracker = srcTracker;
443
444                csd = validateSurfaceData(srcData, csd, w, h);
445                if (csd == null) {
446                    synchronized (this) {
447                        if (curTracker == srcTracker) {
448                            this.cacheTracker = getRetryTracker(srcData);
449                            this.cachedSD = null;
450                        }
451                    }
452                    return srcData;
453                }
454
455                updateSurfaceData(srcData, csd, w, h);
456                if (!csd.isValid()) {
457                    return srcData;
458                }
459
460                synchronized (this) {
461                    // We only reset these variables if the tracker from
462                    // before the surface update is still in use and current
463                    // Note that we must use a srcTracker that was fetched
464                    // from before the update process to make sure that we
465                    // do not lose some pixel changes in the shuffle.
466                    if (curTracker == srcTracker && curTracker.isCurrent()) {
467                        this.cacheTracker = csd.getStateTracker();
468                        this.cachedSD = csd;
469                    }
470                }
471            }
472
473            if (csd != null) {
474                return csd;
475            }
476        }
477
478        return srcData;
479    }
480
481    /**
482     * This is the default implementation for updating the cached
483     * SurfaceData from the source (primary) SurfaceData.
484     * A simple Blit is used to copy the pixels from the source to
485     * the destination SurfaceData.
486     * A subclass can override this implementation if a more complex
487     * operation is required to update its cached copies.
488     */
489    public void updateSurfaceData(SurfaceData srcData,
490                                  SurfaceData dstData,
491                                  int w, int h)
492    {
493        SurfaceType srcType = srcData.getSurfaceType();
494        SurfaceType dstType = dstData.getSurfaceType();
495        Blit blit = Blit.getFromCache(srcType,
496                                      CompositeType.SrcNoEa,
497                                      dstType);
498        blit.Blit(srcData, dstData,
499                  AlphaComposite.Src, null,
500                  0, 0, 0, 0, w, h);
501        dstData.markDirty();
502    }
503
504    /**
505     * This is an alternate implementation for updating the cached
506     * SurfaceData from the source (primary) SurfaceData using a
507     * background color for transparent pixels.
508     * A simple BlitBg is used to copy the pixels from the source to
509     * the destination SurfaceData with the specified bgColor.
510     * A subclass can override the normal updateSurfaceData method
511     * and call this implementation instead if it wants to use color
512     * keying for bitmask images.
513     */
514    public void updateSurfaceDataBg(SurfaceData srcData,
515                                    SurfaceData dstData,
516                                    int w, int h, Color bgColor)
517    {
518        SurfaceType srcType = srcData.getSurfaceType();
519        SurfaceType dstType = dstData.getSurfaceType();
520        BlitBg blitbg = BlitBg.getFromCache(srcType,
521                                            CompositeType.SrcNoEa,
522                                            dstType);
523        blitbg.BlitBg(srcData, dstData,
524                      AlphaComposite.Src, null, bgColor.getRGB(),
525                      0, 0, 0, 0, w, h);
526        dstData.markDirty();
527    }
528}
529