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         backingStore = factory.createRTTexture(WIDTH + WIDTH, HEIGHT, WrapMode.CLAMP_NOT_NEEDED);
  62         backingStore.contentsUseful();
  63         backingStore.makePermanent();
  64         factory.setRegionTexture(backingStore);
  65         // Subdivide the texture in two halves where on half is used to store
  66         // horizontal regions and the other vertical regions. Otherwise, mixing
  67         // horizontal and vertical regions on the same area, would result in
  68         // a lot of waste texture space.
  69         hPacker = new RectanglePacker(backingStore, 0, 0, WIDTH, HEIGHT, false);
  70         vPacker = new RectanglePacker(backingStore, WIDTH, 0, WIDTH, HEIGHT, true);
  71     }
  72 
  73     /**
  74      * Check if the image size is to big to be stored in the cache
  75      *
  76      * @param w The image width
  77      * @param h The image height
  78      * @return True if the image size is less than max
  79      */
  80     boolean isImageCachable(int w, int h) {
  81         return 0 < w && w < WIDTH &&
  82                0 < h && h < HEIGHT &&
  83                (w * h) < MAX_SIZE;
  84     }
  85 
  86     RTTexture getBackingStore() {
  87         return backingStore;
  88     }
  89 
  90     /**
  91      * Search the cache for a background image representing the arguments.
  92      * When this method succeeds the x and y coordinates in rect are adjust
  93      * to the location in the backing store when the image is stored.
  94      * If a failure occurred the rect is set to empty to indicate the caller
  95      * to disable caching.
  96      *
  97      * @param key the hash key for the image
  98      * @param rect the rect image. On input, width and height determine the requested
  99      *        texture space. On ouput, the x and y the location in the texture
 100      * @param background the background used to validated if the correct image was found
 101      * @param shape the shape used to validated if the correct image was found
 102      * @param g the graphics to flush if the texture needs to be restarted
 103      * @return true means to caller needs to render to rect to initialize the content.
 104      */
 105     boolean getImageLocation(Integer key, Rectangle rect, Background background,
 106                              Shape shape, Graphics g) {
 107         CachedImage cache = imageMap.get(key);
 108         if (cache != null) {
 109             if (cache.equals(rect.width, rect.height, background, shape)) {
 110                 rect.x = cache.x;
 111                 rect.y = cache.y;
 112                 return false;
 113             }
 114             // hash collision, mark rectangle empty indicates the caller to
 115             // disable caching
 116             rect.width = rect.height = -1;
 117             return false;
 118         }
 119         boolean vertical = rect.height > 64;
 120         RectanglePacker packer = vertical ? vPacker : hPacker;
 121 
 122         if (!packer.add(rect)) {
 123             g.sync();
 124 
 125             vPacker.clear();
 126             hPacker.clear();
 127             imageMap.clear();
 128             packer.add(rect);
 129             backingStore.createGraphics().clear();
 130             if (PULSE_LOGGING_ENABLED) {
 131                 PulseLogger.incrementCounter("Region image cache flushed");
 132             }
 133         }
 134         imageMap.put(key, new CachedImage(rect, background, shape));
 135         return true;
 136     }
 137 
 138     static class CachedImage {
 139         Background background;
 140         Shape shape;
 141         int x, y, width, height;
 142 
 143         CachedImage(Rectangle rect, Background background, Shape shape) {
 144             this.x = rect.x;
 145             this.y = rect.y;
 146             this.width = rect.width;
 147             this.height = rect.height;
 148             this.background = background;
 149             this.shape = shape;
 150         }
 151 
 152         public boolean equals(int width, int height, Background background, Shape shape) {
 153             return this.width == width &&
 154                    this.height == height &&
 155                    (this.background == null ? background == null : this.background.equals(background)) &&
 156                    (this.shape == null ? shape == null : this.shape.equals(shape));
 157         }
 158     }
 159 
 160 }