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