--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.scenegraph.utils;\r
+\r
+import java.awt.GraphicsConfiguration;\r
+import java.awt.Image;\r
+import java.awt.Transparency;\r
+import java.awt.image.VolatileImage;\r
+import java.lang.ref.Reference;\r
+import java.lang.ref.SoftReference;\r
+import java.util.LinkedHashMap;\r
+import java.util.Map;\r
+import java.util.concurrent.atomic.AtomicInteger;\r
+\r
+/**\r
+ * @author Antti Villberg\r
+ */\r
+public class VolatileImageCache {\r
+\r
+ private static final boolean DEBUG = false;\r
+\r
+ private static VolatileImageCache instance = null;\r
+\r
+ private VolatileImageCache() {\r
+ }\r
+\r
+ public static VolatileImageCache getInstance() {\r
+ if (instance == null) {\r
+ instance = new VolatileImageCache();\r
+ }\r
+ return instance;\r
+ }\r
+\r
+ /**\r
+ * The max allowed size of the cache in megapixels. The amount of actual\r
+ * consumed (video) memory is approximately 4 times this.\r
+ */\r
+ private static final int MAX_SIZE = 8 * 1024 * 1024;\r
+\r
+\r
+ /**\r
+ * Current size of the cache in megapixels.\r
+ */\r
+ private int size = 0;\r
+ private int counter = 0;\r
+\r
+ class VolatileImageProviderImpl implements VolatileImageProvider {\r
+ private final int id;\r
+ private final int w;\r
+ private final int h;\r
+\r
+ private Reference<VolatileImage> imageRef = null;\r
+\r
+ public VolatileImageProviderImpl(int w, int h) {\r
+ this.id = counter++;\r
+ this.w = w;\r
+ this.h = h;\r
+ }\r
+\r
+ private VolatileImage dereferenceImage() {\r
+ return imageRef != null ? imageRef.get() : null;\r
+ }\r
+\r
+ public VolatileImage get(GraphicsConfiguration gc, AtomicInteger _validateResult) {\r
+ int validateResult = VolatileImage.IMAGE_INCOMPATIBLE;\r
+ VolatileImage vimg = dereferenceImage();\r
+ //System.out.println("GC: " + gc);\r
+ if (vimg != null)\r
+ validateResult = vimg.validate(gc);\r
+\r
+ if (validateResult == VolatileImage.IMAGE_RESTORED) {\r
+ if (DEBUG)\r
+ System.out.println("VOLATILE IMAGE RESTORED for PROVIDER " + this);\r
+ }\r
+ if (validateResult == VolatileImage.IMAGE_INCOMPATIBLE) {\r
+ if (DEBUG)\r
+ System.out.println("(RE)CREATING VOLATILE IMAGE FOR PROVIDER " + this);\r
+ vimg = gc.createCompatibleVolatileImage(w, h, Transparency.TRANSLUCENT);\r
+\r
+ if (vimg == null) {\r
+ throw new IllegalStateException(this + ": got null VolatileImage from GraphicsConfiguration " + gc);\r
+ } else {\r
+ this.imageRef = new SoftReference<VolatileImage>(vimg);\r
+\r
+ // Implement move to front required for LRU\r
+ synchronized (cache) {\r
+ Object oldObject = cache.remove(this);\r
+ boolean wasCached = oldObject == this;\r
+ cache.put(this, this);\r
+ if (!wasCached) {\r
+ size += w * h;\r
+\r
+ if (DEBUG)\r
+ debug(this, cache.size(), size - w*h, size, "created new image");\r
+ } else {\r
+ //if (DEBUG)\r
+ // debug(this, cache.size(), size, size, "LRU move-to-front");\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ if (_validateResult != null)\r
+ _validateResult.set(validateResult);\r
+\r
+ return vimg;\r
+ }\r
+\r
+ public void dispose() {\r
+ int newSize;\r
+ synchronized (cache) {\r
+ newSize = size;\r
+ Object removed = cache.remove(this);\r
+ if (removed == this) {\r
+ newSize -= w * h;\r
+ size = newSize;\r
+\r
+ if (DEBUG)\r
+ debug(this, cache.size(), newSize + w*h, newSize, "explicitly disposed");\r
+ }\r
+ if (imageRef != null) {\r
+ flushImageRef(imageRef);\r
+ imageRef = null;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ 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
+\r
+ private static void debug(VolatileImageProviderImpl i, int entries, int oldSize, int newSize, String msg) {\r
+ if (DEBUG) {\r
+ int sizeChange = newSize - oldSize;\r
+ String s = String.format(MSG, i.id, i.hashCode(), i.w, i.h, entries, oldSize, newSize, sizeChange, msg);\r
+ System.out.println(s);\r
+ }\r
+ }\r
+\r
+ private final Map<VolatileImageProviderImpl,VolatileImageProviderImpl> cache = new LinkedHashMap<VolatileImageProviderImpl, VolatileImageProviderImpl>(50, .75F, true) {\r
+ private static final long serialVersionUID = 5946026822169837291L;\r
+ @Override\r
+ protected boolean removeEldestEntry(Map.Entry<VolatileImageProviderImpl,VolatileImageProviderImpl> eldest) {\r
+ boolean result = size > MAX_SIZE;\r
+ if (result) {\r
+// new Exception().printStackTrace();\r
+ VolatileImageProviderImpl impl = eldest.getKey();\r
+ size -= impl.w*impl.h;\r
+\r
+ //debug(impl, impl.imageId, cache.size(), oldSize, size, "cache full, dumping oldest");\r
+\r
+ flushImageRef(impl.imageRef);\r
+ impl.imageRef = null;\r
+ }\r
+ return result;\r
+ }\r
+ };\r
+\r
+ private static final void flushImageRef(Reference<? extends Image> imageRef) {\r
+ if (imageRef != null) {\r
+ Image img = imageRef.get();\r
+ imageRef.clear();\r
+ if (img != null)\r
+ img.flush();\r
+ }\r
+ }\r
+\r
+ VolatileImageProvider create(int w, int h) {\r
+// System.out.println("VolatileImageProvider.create(" + w + ", " + h + ")");\r
+ VolatileImageProviderImpl result = new VolatileImageProviderImpl(w, h);\r
+ return result;\r
+ }\r
+\r
+}\r