/*
 * Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.scenario.effect.impl;

import java.lang.ref.SoftReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.sun.scenario.effect.Filterable;

A simple object pool used to recycle temporary images used by the various EffectPeer implementations. Image allocation can be a fairly expensive operation (in terms of footprint and performance), especially for the GPU backends, so image reuse is critical.
/** * A simple object pool used to recycle temporary images used by the * various {@code EffectPeer} implementations. Image allocation can be * a fairly expensive operation (in terms of footprint and performance), * especially for the GPU backends, so image reuse is critical. */
public class ImagePool { public static long numEffects; static long numCreated; static long pixelsCreated; static long numAccessed; static long pixelsAccessed; static { AccessController.doPrivileged((PrivilegedAction) () -> { if (System.getProperty("decora.showstats") != null) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { printStats(); } }); } return null; }); } static void printStats() { System.out.println("effects executed: " + numEffects); System.out.println("images created: " + numCreated); System.out.println("pixels created: " + pixelsCreated); System.out.println("images accessed: " + numAccessed); System.out.println("pixels accessed: " + pixelsAccessed); if (numEffects != 0) { double avgImgs = ((double) numAccessed) / numEffects; double avgPxls = ((double) pixelsAccessed) / numEffects; System.out.println("images per effect: " + avgImgs); System.out.println("pixels per effect: " + avgPxls); } } static final int QUANT = 32; private final List<SoftReference<PoolFilterable>> unlocked = new ArrayList<SoftReference<PoolFilterable>>(); private final List<SoftReference<PoolFilterable>> locked = new ArrayList<SoftReference<PoolFilterable>>(); // On Canmore with the PowerVR SGX chip, there is a driver issue // that causes incorrect rendering if one tries to reuse an FBO // more than once in a particular frame (due to their tile-based // deferred rendering engine). The ugly workaround here is to // avoid using the same Filterable (FBO) more than once between // swapBuffers() operations. When the workaround is enabled, // the checkIn() method will move the Filterable into "purgatory" // instead of returning it to the pool of available images. Just // after the swapBuffers() operation, the Prism toolkit will call // the releasePurgatory() method to allow images to return to the // pool for the next rendering cycle. This of course greatly // increases the amount of VRAM used by an app, and may cause // slowdowns for certain frames due to increased allocation // (where there would normally be reuse). private final boolean usePurgatory = Boolean.getBoolean("decora.purgatory"); private final List<Filterable> hardPurgatory = new ArrayList<Filterable>(); private final List<SoftReference<PoolFilterable>> softPurgatory = new ArrayList<SoftReference<PoolFilterable>>();
Package-private constructor.
/** * Package-private constructor. */
ImagePool() { } public synchronized PoolFilterable checkOut(Renderer renderer, int w, int h) { if (w <= 0 || h <= 0) { // if image is empty in any way, return a small non-empty image. w = h = 1; } // Allocate images rounded up to the nearest quantum size threshold. w = ((w + QUANT - 1) / QUANT) * QUANT; h = ((h + QUANT - 1) / QUANT) * QUANT; // Adjust allocation sizes for platform requirements (pow2 etc.) w = renderer.getCompatibleWidth(w); h = renderer.getCompatibleHeight(h); numAccessed++; pixelsAccessed += ((long) w) * h; // first look for an already cached image of sufficient size, // choosing the one that is closest in size to the requested dimensions SoftReference<PoolFilterable> chosenEntry = null; PoolFilterable chosenImage = null; int mindiff = Integer.MAX_VALUE; Iterator<SoftReference<PoolFilterable>> entries = unlocked.iterator(); while (entries.hasNext()) { SoftReference<PoolFilterable> entry = entries.next(); PoolFilterable eimg = entry.get(); if (eimg == null) { entries.remove(); continue; } int ew = eimg.getMaxContentWidth(); int eh = eimg.getMaxContentHeight(); if (ew >= w && eh >= h && ew * eh / 2 <= w * h) { int diff = (ew-w) * (eh-h); if (chosenEntry == null || diff < mindiff) { eimg.lock(); if (eimg.isLost()) { entries.remove(); continue; } if (chosenImage != null) { chosenImage.unlock(); } chosenEntry = entry; // The following calls to setContentWidth / setContentHeight // should be uncommented only after the rest of the imagepool // is fixed to handle a change in content size, and when both the // SW pipeline and J2D pipeline are able to handle the change. // eimg.setContentWidth(w); // eimg.setContentHeight(h); chosenImage = eimg; mindiff = diff; } } } if (chosenEntry != null) { unlocked.remove(chosenEntry); locked.add(chosenEntry); renderer.clearImage(chosenImage); return chosenImage; } // get rid of expired entries from locked list entries = locked.iterator(); while (entries.hasNext()) { SoftReference<PoolFilterable> entry = entries.next(); Filterable eimg = entry.get(); if (eimg == null) { entries.remove(); } } // if all else fails, just create a new one... PoolFilterable img = null; try { img = renderer.createCompatibleImage(w, h); } catch (OutOfMemoryError e) {} if (img == null) { // we may be out of vram or heap pruneCache(); try { img = renderer.createCompatibleImage(w, h); } catch (OutOfMemoryError e) {} } if (img != null) { img.setImagePool(this); locked.add(new SoftReference<PoolFilterable>(img)); numCreated++; pixelsCreated += ((long) w) * h; } return img; } public synchronized void checkIn(PoolFilterable img) { SoftReference<PoolFilterable> chosenEntry = null; Filterable chosenImage = null; Iterator<SoftReference<PoolFilterable>> entries = locked.iterator(); while (entries.hasNext()) { SoftReference<PoolFilterable> entry = entries.next(); Filterable eimg = entry.get(); if (eimg == null) { entries.remove(); } else if (eimg == img) { chosenEntry = entry; chosenImage = eimg; img.unlock(); break; } } if (chosenEntry != null) { locked.remove(chosenEntry); if (usePurgatory) { // hold the entry in purgatory instead of releasing it back // to the unlocked pool immediately; it will be released // after the next call to releasePurgatory()... // System.err.println("==> Adding image to purgatory: " + // chosenImage.getPhysicalWidth() + "x" + // chosenImage.getPhysicalHeight()); hardPurgatory.add(chosenImage); softPurgatory.add(chosenEntry); } else { unlocked.add(chosenEntry); } } } public synchronized void releasePurgatory() { if (usePurgatory && !softPurgatory.isEmpty()) { // System.err.println("==> Releasing " + softPurgatory.size() + " entries from purgatory!"); // release images kept in purgatory back into the unlocked pool unlocked.addAll(softPurgatory); softPurgatory.clear(); hardPurgatory.clear(); } } private void pruneCache() { // flush all unlocked images for (SoftReference<PoolFilterable> r : unlocked) { Filterable image = r.get(); if (image != null) { image.flush(); } } unlocked.clear(); // this is to help to free up space held by those images that we no // longer have references to System.gc(); System.runFinalization(); System.gc(); System.runFinalization(); } public synchronized void dispose() { for (SoftReference<PoolFilterable> r : unlocked) { Filterable image = r.get(); if (image != null) { image.flush(); } } unlocked.clear(); // not flushing the locked ones, just clearing references to them locked.clear(); } }