]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/VolatileImageCache.java
G2DParentNode handles "undefined" child bounds separately
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / VolatileImageCache.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.scenegraph.utils;
13
14 import java.awt.GraphicsConfiguration;
15 import java.awt.Image;
16 import java.awt.Transparency;
17 import java.awt.image.VolatileImage;
18 import java.lang.ref.Reference;
19 import java.lang.ref.SoftReference;
20 import java.util.LinkedHashMap;
21 import java.util.Map;
22 import java.util.concurrent.atomic.AtomicInteger;
23
24 /**
25  * @author Antti Villberg
26  */
27 public class VolatileImageCache {
28
29     private static final boolean DEBUG = false;
30
31     private static VolatileImageCache instance = null;
32
33     private VolatileImageCache() {
34     }
35
36     public static VolatileImageCache getInstance() {
37         if (instance == null) {
38             instance = new VolatileImageCache();
39         }
40         return instance;
41     }
42
43     /**
44      * The max allowed size of the cache in megapixels. The amount of actual
45      * consumed (video) memory is approximately 4 times this.
46      */
47     private static final int MAX_SIZE = 8 * 1024 * 1024;
48
49
50     /**
51      * Current size of the cache in megapixels.
52      */
53     private int size = 0;
54     private int counter = 0;
55
56     class VolatileImageProviderImpl implements VolatileImageProvider {
57         private final int id;
58         private final int w;
59         private final int h;
60
61         private Reference<VolatileImage> imageRef = null;
62
63         public VolatileImageProviderImpl(int w, int h) {
64             this.id = counter++;
65             this.w = w;
66             this.h = h;
67         }
68
69         private VolatileImage dereferenceImage() {
70             return imageRef != null ? imageRef.get() : null;
71         }
72
73         public VolatileImage get(GraphicsConfiguration gc, AtomicInteger _validateResult) {
74             int validateResult = VolatileImage.IMAGE_INCOMPATIBLE;
75             VolatileImage vimg = dereferenceImage();
76             //System.out.println("GC: " + gc);
77             if (vimg != null)
78                 validateResult = vimg.validate(gc);
79
80             if (validateResult == VolatileImage.IMAGE_RESTORED) {
81                 if (DEBUG)
82                     System.out.println("VOLATILE IMAGE RESTORED for PROVIDER " + this);
83             }
84             if (validateResult == VolatileImage.IMAGE_INCOMPATIBLE) {
85                 if (DEBUG)
86                     System.out.println("(RE)CREATING VOLATILE IMAGE FOR PROVIDER " + this);
87                 vimg = gc.createCompatibleVolatileImage(w, h, Transparency.TRANSLUCENT);
88
89                 if (vimg == null) {
90                     throw new IllegalStateException(this + ": got null VolatileImage from GraphicsConfiguration " + gc);
91                 } else {
92                     this.imageRef = new SoftReference<VolatileImage>(vimg);
93
94                     // Implement move to front required for LRU
95                     synchronized (cache) {
96                         Object oldObject = cache.remove(this);
97                         boolean wasCached = oldObject == this;
98                         cache.put(this, this);
99                         if (!wasCached) {
100                             size += w * h;
101
102                             if (DEBUG)
103                                 debug(this, cache.size(), size - w*h, size, "created new image");
104                         } else {
105                             //if (DEBUG)
106                             //    debug(this, cache.size(), size, size, "LRU move-to-front");
107                         }
108                     }
109                 }
110             }
111
112             if (_validateResult != null)
113                 _validateResult.set(validateResult);
114
115             return vimg;
116         }
117
118         public void dispose() {
119             int newSize;
120             synchronized (cache) {
121                 newSize = size;
122                 Object removed = cache.remove(this);
123                 if (removed == this) {
124                     newSize -= w * h;
125                     size = newSize;
126
127                     if (DEBUG)
128                         debug(this, cache.size(), newSize + w*h, newSize, "explicitly disposed");
129                 }
130                 if (imageRef != null) {
131                     flushImageRef(imageRef);
132                     imageRef = null;
133                 }
134             }
135         }
136     }
137
138     private static final String MSG = "[provider #%-10d @%-8x][image (%-3d,%-3d)][cache entries=%-4d, old size=%-10d, new size=%-10d, size change=%-+6d] %s";
139
140     private static void debug(VolatileImageProviderImpl i, int entries, int oldSize, int newSize, String msg) {
141         if (DEBUG) {
142             int sizeChange = newSize - oldSize;
143             String s = String.format(MSG, i.id, i.hashCode(), i.w, i.h, entries, oldSize, newSize, sizeChange, msg);
144             System.out.println(s);
145         }
146     }
147
148     private final Map<VolatileImageProviderImpl,VolatileImageProviderImpl> cache = new LinkedHashMap<VolatileImageProviderImpl, VolatileImageProviderImpl>(50, .75F, true) {
149         private static final long serialVersionUID = 5946026822169837291L;
150         @Override
151         protected boolean removeEldestEntry(Map.Entry<VolatileImageProviderImpl,VolatileImageProviderImpl> eldest) {
152             boolean result = size > MAX_SIZE;
153             if (result) {
154 //                new Exception().printStackTrace();
155                 VolatileImageProviderImpl impl = eldest.getKey();
156                 size -= impl.w*impl.h;
157
158                 //debug(impl, impl.imageId, cache.size(), oldSize, size, "cache full, dumping oldest");
159
160                 flushImageRef(impl.imageRef);
161                 impl.imageRef = null;
162             }
163             return result;
164         }
165     };
166
167     private static final void flushImageRef(Reference<? extends Image> imageRef) {
168         if (imageRef != null) {
169             Image img = imageRef.get();
170             imageRef.clear();
171             if (img != null)
172                 img.flush();
173         }
174     }
175
176     VolatileImageProvider create(int w, int h) {
177 //        System.out.println("VolatileImageProvider.create(" + w + ", " + h + ")");
178         VolatileImageProviderImpl result = new VolatileImageProviderImpl(w, h);
179         return result;
180     }
181
182 }