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