1 /*
   2  * Copyright (c) 2012, 2014, 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 
  26 package com.sun.javafx.sg.prism;
  27 
  28 import javafx.scene.layout.Background;
  29 import java.util.HashMap;
  30 import com.sun.javafx.geom.Rectangle;
  31 import com.sun.javafx.geom.Shape;
  32 import com.sun.javafx.logging.PulseLogger;
  33 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
  34 import com.sun.prism.Graphics;
  35 import com.sun.prism.RTTexture;
  36 import com.sun.prism.ResourceFactory;
  37 import com.sun.prism.Texture.WrapMode;
  38 import com.sun.prism.impl.packrect.RectanglePacker;
  39 
  40 /**
  41  * RegionImageCache - A fixed pixel count sized cache of Images keyed by arbitrary set of arguments. All images are held with
  42  * SoftReferences so they will be dropped by the GC if heap memory gets tight. When our size hits max pixel count least
  43  * recently requested images are removed first.
  44  *
  45  */
  46 class RegionImageCache {
  47 
  48     // Maximum cached image size in pixels
  49     private final static int MAX_SIZE = 300 * 300;
  50     private static final int WIDTH = 1024;
  51     private static final int HEIGHT = 1024;
  52 
  53     private HashMap<Integer, CachedImage> imageMap;
  54     private RTTexture backingStore;
  55     private RectanglePacker hPacker;
  56     private RectanglePacker vPacker;
  57 
  58 
  59     RegionImageCache(final ResourceFactory factory) {
  60         imageMap = new HashMap<>();
  61         WrapMode mode;
  62         int pad;
  63         if (factory.isWrapModeSupported(WrapMode.CLAMP_TO_ZERO)) {
  64             mode = WrapMode.CLAMP_TO_ZERO;
  65             pad = 0;
  66         } else {
  67             mode = WrapMode.CLAMP_NOT_NEEDED;
  68             pad = 1;
  69         }
  70         backingStore = factory.createRTTexture(WIDTH + WIDTH, HEIGHT, mode);
  71         backingStore.contentsUseful();
  72         backingStore.makePermanent();
  73         factory.setRegionTexture(backingStore);
  74         // Subdivide the texture in two halves where on half is used to store
  75         // horizontal regions and the other vertical regions. Otherwise, mixing
  76         // horizontal and vertical regions on the same area, would result in
  77         // a lot of waste texture space.
  78         // Note that requests are already padded on the right and bottom edges
  79         // (and that includes the gap between the caches) so we only have to
  80         // pad top and left edges if CLAMP_TO_ZERO needs to be simulated.
  81         hPacker = new RectanglePacker(backingStore, pad, pad, WIDTH-pad, HEIGHT-pad, false);
  82         vPacker = new RectanglePacker(backingStore, WIDTH, pad, WIDTH, HEIGHT-pad, true);
  83     }
  84 
  85     /**
  86      * Check if the image size is to big to be stored in the cache
  87      *
  88      * @param w The image width
  89      * @param h The image height
  90      * @return True if the image size is less than max
  91      */
  92     boolean isImageCachable(int w, int h) {
  93         return 0 < w && w < WIDTH &&
  94                0 < h && h < HEIGHT &&
  95                (w * h) < MAX_SIZE;
  96     }
  97 
  98     RTTexture getBackingStore() {
  99         return backingStore;
 100     }
 101 
 102     /**
 103      * Search the cache for a background image representing the arguments.
 104      * When this method succeeds the x and y coordinates in rect are adjust
 105      * to the location in the backing store when the image is stored.
 106      * If a failure occurred the rect is set to empty to indicate the caller
 107      * to disable caching.
 108      *
 109      * @param key the hash key for the image
 110      * @param rect the rect image. On input, width and height determine the requested
 111      *        texture space. On ouput, the x and y the location in the texture
 112      * @param background the background used to validated if the correct image was found
 113      * @param shape the shape used to validated if the correct image was found
 114      * @param g the graphics to flush if the texture needs to be restarted
 115      * @return true means to caller needs to render to rect to initialize the content.
 116      */
 117     boolean getImageLocation(Integer key, Rectangle rect, Background background,
 118                              Shape shape, Graphics g) {
 119         CachedImage cache = imageMap.get(key);
 120         if (cache != null) {
 121             if (cache.equals(rect.width, rect.height, background, shape)) {
 122                 rect.x = cache.x;
 123                 rect.y = cache.y;
 124                 return false;
 125             }
 126             // hash collision, mark rectangle empty indicates the caller to
 127             // disable caching
 128             rect.width = rect.height = -1;
 129             return false;
 130         }
 131         boolean vertical = rect.height > 64;
 132         RectanglePacker packer = vertical ? vPacker : hPacker;
 133 
 134         if (!packer.add(rect)) {
 135             g.sync();
 136 
 137             vPacker.clear();
 138             hPacker.clear();
 139             imageMap.clear();
 140             packer.add(rect);
 141             backingStore.createGraphics().clear();
 142             if (PULSE_LOGGING_ENABLED) {
 143                 PulseLogger.incrementCounter("Region image cache flushed");
 144             }
 145         }
 146         imageMap.put(key, new CachedImage(rect, background, shape));
 147         return true;
 148     }
 149 
 150     static class CachedImage {
 151         Background background;
 152         Shape shape;
 153         int x, y, width, height;
 154 
 155         CachedImage(Rectangle rect, Background background, Shape shape) {
 156             this.x = rect.x;
 157             this.y = rect.y;
 158             this.width = rect.width;
 159             this.height = rect.height;
 160             this.background = background;
 161             this.shape = shape;
 162         }
 163 
 164         public boolean equals(int width, int height, Background background, Shape shape) {
 165             return this.width == width &&
 166                    this.height == height &&
 167                    (this.background == null ? background == null : this.background.equals(background)) &&
 168                    (this.shape == null ? shape == null : this.shape.equals(shape));
 169         }
 170     }
 171 
 172 }