com.ardor3d.util.TextureManager.java Source code

Java tutorial

Introduction

Here is the source code for com.ardor3d.util.TextureManager.java

Source

/**
 * Copyright (c) 2008-2012 Ardor Labs, Inc.
 *
 * This file is part of Ardor3D.
 *
 * Ardor3D is free software: you can redistribute it and/or modify it 
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
 */

package com.ardor3d.util;

import java.lang.ref.ReferenceQueue;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import com.ardor3d.annotation.MainThread;
import com.ardor3d.image.Image;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture2D;
import com.ardor3d.image.Texture3D;
import com.ardor3d.image.TextureCubeMap;
import com.ardor3d.image.TextureStoreFormat;
import com.ardor3d.image.util.ImageLoaderUtil;
import com.ardor3d.image.util.ImageUtils;
import com.ardor3d.renderer.ContextCleanListener;
import com.ardor3d.renderer.ContextManager;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.RendererCallable;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.util.resource.ResourceLocatorTool;
import com.ardor3d.util.resource.ResourceSource;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Multimap;

/**
 * <code>TextureManager</code> provides static methods for building or retrieving a <code>Texture</code> object from
 * cache.
 */
final public class TextureManager {
    private static final Logger logger = Logger.getLogger(TextureManager.class.getName());

    private static Map<TextureKey, Texture> _tCache = new MapMaker().weakKeys().weakValues().makeMap();

    private static ReferenceQueue<TextureKey> _textureRefQueue = new ReferenceQueue<TextureKey>();

    static {
        ContextManager.addContextCleanListener(new ContextCleanListener() {
            public void cleanForContext(final RenderContext renderContext) {
                TextureManager.cleanAllTextures(null, renderContext, null);
            }
        });
    }

    private TextureManager() {
    }

    /**
     * Loads a texture by attempting to locate the given name using ResourceLocatorTool.
     * 
     * @param name
     *            the name of the texture image.
     * @param minFilter
     *            the filter for the near values. Used to determine if we should generate mipmaps.
     * @param flipVertically
     *            If true, the image is flipped vertically during image loading.
     * @return the loaded texture.
     */
    public static Texture load(final String name, final Texture.MinificationFilter minFilter,
            final boolean flipVertically) {
        return load(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, name), minFilter,
                TextureStoreFormat.GuessNoCompressedFormat, flipVertically);
    }

    /**
     * Loads a texture by attempting to locate the given name using ResourceLocatorTool.
     * 
     * @param name
     *            the name of the texture image.
     * @param minFilter
     *            the filter for the near values. Used to determine if we should generate mipmaps.
     * @param format
     *            the specific format to use when storing this texture on the card.
     * @param flipVertically
     *            If true, the image is flipped vertically during image loading.
     * @return the loaded texture.
     */
    public static Texture load(final String name, final Texture.MinificationFilter minFilter,
            final TextureStoreFormat format, final boolean flipVertically) {
        return load(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, name), minFilter, format,
                flipVertically);
    }

    /**
     * Loads a texture from the given source.
     * 
     * @param source
     *            the source of the texture image.
     * @param minFilter
     *            the filter for the near values. Used to determine if we should generate mipmaps.
     * @param flipVertically
     *            If true, the image is flipped vertically during image loading.
     * @return the loaded texture.
     */
    public static Texture load(final ResourceSource source, final Texture.MinificationFilter minFilter,
            final boolean flipVertically) {
        return load(source, minFilter, TextureStoreFormat.GuessNoCompressedFormat, flipVertically);
    }

    /**
     * Loads a texture from the given source.
     * 
     * @param source
     *            the source of the texture image.
     * @param minFilter
     *            the filter for the near values. Used to determine if we should generate mipmaps.
     * @param format
     *            the specific format to use when storing this texture on the card.
     * @param flipVertically
     *            If true, the image is flipped vertically during image loading.
     * @return the loaded texture.
     */
    public static Texture load(final ResourceSource source, final Texture.MinificationFilter minFilter,
            final TextureStoreFormat format, final boolean flipVertically) {

        if (null == source) {
            logger.warning("Could not load image...  source was null. defaultTexture used.");
            return TextureState.getDefaultTexture();
        }

        final TextureKey tkey = TextureKey.getKey(source, flipVertically, format, minFilter);

        return loadFromKey(tkey, null, null);
    }

    /**
     * Creates a texture from a given Ardor3D Image object.
     * 
     * @param image
     *            the Ardor3D image.
     * @param minFilter
     *            the filter for the near values. Used to determine if we should generate mipmaps.
     * @return the loaded texture.
     */
    public static Texture loadFromImage(final Image image, final Texture.MinificationFilter minFilter) {
        return loadFromImage(image, minFilter, TextureStoreFormat.GuessNoCompressedFormat);
    }

    /**
     * Creates a texture from a given Ardor3D Image object.
     * 
     * @param image
     *            the Ardor3D image.
     * @param minFilter
     *            the filter for the near values. Used to determine if we should generate mipmaps.
     * @param format
     *            the specific format to use when storing this texture on the card.
     * @return the loaded texture.
     */
    public static Texture loadFromImage(final Image image, final Texture.MinificationFilter minFilter,
            final TextureStoreFormat format) {
        final TextureKey key = TextureKey.getKey(null, false, format, "img_" + image.hashCode(), minFilter);
        return loadFromKey(key, image, null);
    }

    /**
     * Load a texture from the given TextureKey. If imageData is given, use that, otherwise load it using the key's
     * source information. If store is given, populate and return that Texture object.
     * 
     * @param tkey
     *            our texture key. Must not be null.
     * @param imageData
     *            optional Image data. If present, this is used instead of loading from source.
     * @param store
     *            if not null, this Texture object is populated and returned instead of a new Texture object.
     * @return the resulting texture.
     */
    public static Texture loadFromKey(final TextureKey tkey, final Image imageData, final Texture store) {
        if (tkey == null) {
            logger.warning("TextureKey is null, cannot load");
            return TextureState.getDefaultTexture();
        }

        Texture result = store;

        // First look for the texture using the supplied key
        final Texture cache = findCachedTexture(tkey);

        if (cache != null) {
            // look into cache.
            if (result == null) {
                result = cache.createSimpleClone();
                if (result.getTextureKey() == null) {
                    result.setTextureKey(tkey);
                }
                return result;
            }
            cache.createSimpleClone(result);
            return result;
        }

        Image img = imageData;
        if (img == null) {
            img = ImageLoaderUtil.loadImage(tkey.getSource(), tkey.isFlipped());
        }

        if (null == img) {
            logger.warning("(image null) Could not load: " + tkey.getSource());
            return TextureState.getDefaultTexture();
        }

        // Default to Texture2D
        if (result == null) {
            if (img.getDataSize() == 6) {
                result = new TextureCubeMap();
            } else if (img.getDataSize() > 1) {
                result = new Texture3D();
            } else {
                result = new Texture2D();
            }
        }

        result.setTextureKey(tkey);
        result.setImage(img);
        result.setMinificationFilter(tkey.getMinificationFilter());
        result.setTextureStoreFormat(ImageUtils.getTextureStoreFormat(tkey.getFormat(), result.getImage()));

        // Cache the no-context version
        addToCache(result);
        return result;
    }

    /**
     * Add a given texture to the cache
     * 
     * @param texture
     *            our texture
     */
    public static void addToCache(final Texture texture) {
        if (TextureState.getDefaultTexture() == null || (texture != TextureState.getDefaultTexture()
                && texture.getImage() != TextureState.getDefaultTextureImage())) {
            _tCache.put(texture.getTextureKey(), texture);
        }
    }

    /**
     * Locate a texture in the cache by key
     * 
     * @param textureKey
     *            our key
     * @return the texture, or null if not found.
     */
    public static Texture findCachedTexture(final TextureKey textureKey) {
        return _tCache.get(textureKey);
    }

    public static Texture removeFromCache(final TextureKey tk) {
        return _tCache.remove(tk);
    }

    /**
     * Delete all textures from card. This will gather all texture ids believed to be on the card and try to delete
     * them. If a deleter is passed in, textures that are part of the currently active context (if one is active) will
     * be deleted immediately. If a deleter is not passed in, we do not have an active context, or we encounter textures
     * that are not part of the current context, then we will queue those textures to be deleted later using the
     * GameTaskQueueManager.
     * 
     * @param deleter
     *            if not null, this renderer will be used to immediately delete any textures in the currently active
     *            context. All other textures will be queued to delete in their own contexts.
     */
    public static void cleanAllTextures(final Renderer deleter) {
        cleanAllTextures(deleter, null);
    }

    /**
     * Delete all textures from card. This will gather all texture ids believed to be on the card and try to delete
     * them. If a deleter is passed in, textures that are part of the currently active context (if one is active) will
     * be deleted immediately. If a deleter is not passed in, we do not have an active context, or we encounter textures
     * that are not part of the current context, then we will queue those textures to be deleted later using the
     * GameTaskQueueManager.
     * 
     * If a non null map is passed into futureStore, it will be populated with Future objects for each queued context.
     * These objects may be used to discover when the deletion tasks have all completed.
     * 
     * @param deleter
     *            if not null, this renderer will be used to immediately delete any textures in the currently active
     *            context. All other textures will be queued to delete in their own contexts.
     * @param futureStore
     *            if not null, this map will be populated with any Future task handles created during cleanup.
     */
    public static void cleanAllTextures(final Renderer deleter, final Map<Object, Future<Void>> futureStore) {
        // gather up expired textures... these don't exist in our cache
        Multimap<Object, Integer> idMap = gatherGCdIds();

        // Walk through the cached items and gather those too.
        for (final TextureKey key : _tCache.keySet()) {
            // possibly lazy init
            if (idMap == null) {
                idMap = ArrayListMultimap.create();
            }

            if (Constants.useMultipleContexts) {
                final Set<Object> contextObjects = key.getContextObjects();
                for (final Object o : contextObjects) {
                    // Add id to map
                    idMap.put(o, key.getTextureIdForContext(o));
                }
            } else {
                idMap.put(ContextManager.getCurrentContext().getGlContextRep(), key.getTextureIdForContext(null));
            }
            key.removeFromIdCache();
        }

        // delete the ids
        if (idMap != null && !idMap.isEmpty()) {
            handleTextureDelete(deleter, idMap, futureStore);
        }
    }

    /**
     * Deletes all textures from card for a specific gl context. This will gather all texture ids believed to be on the
     * card for the given context and try to delete them. If a deleter is passed in, textures that are part of the
     * currently active context (if one is active) will be deleted immediately. If a deleter is not passed in, we do not
     * have an active context, or we encounter textures that are not part of the current context, then we will queue
     * those textures to be deleted later using the GameTaskQueueManager.
     * 
     * If a non null map is passed into futureStore, it will be populated with Future objects for each queued context.
     * These objects may be used to discover when the deletion tasks have all completed.
     * 
     * @param deleter
     *            if not null, this renderer will be used to immediately delete any textures in the currently active
     *            context. All other textures will be queued to delete in their own contexts.
     * @param context
     *            the context to delete for.
     * @param futureStore
     *            if not null, this map will be populated with any Future task handles created during cleanup.
     */
    public static void cleanAllTextures(final Renderer deleter, final RenderContext context,
            final Map<Object, Future<Void>> futureStore) {
        // gather up expired textures... these don't exist in our cache
        Multimap<Object, Integer> idMap = gatherGCdIds();

        final Object glRep = context.getGlContextRep();
        // Walk through the cached items and gather those too.
        for (final TextureKey key : _tCache.keySet()) {
            // possibly lazy init
            if (idMap == null) {
                idMap = ArrayListMultimap.create();
            }

            final Integer id = key.getTextureIdForContext(glRep);
            if (id != 0) {
                idMap.put(context.getGlContextRep(), id);
                key.removeFromIdCache(glRep);
            }
        }

        // delete the ids
        if (!idMap.isEmpty()) {
            handleTextureDelete(deleter, idMap, futureStore);
        }
    }

    /**
     * Delete any textures from the card that have been recently garbage collected in Java. If a deleter is passed in,
     * gc'd textures that are part of the currently active context (if one is active) will be deleted immediately. If a
     * deleter is not passed in, we do not have an active context, or we encounter gc'd textures that are not part of
     * the current context, then we will queue those textures to be deleted later using the GameTaskQueueManager.
     * 
     * @param deleter
     *            if not null, this renderer will be used to immediately delete any gc'd textures in the currently
     *            active context. All other gc'd textures will be queued to delete in their own contexts.
     */
    public static void cleanExpiredTextures(final Renderer deleter) {
        cleanExpiredTextures(deleter, null);
    }

    /**
     * Delete any textures from the card that have been recently garbage collected in Java. If a deleter is passed in,
     * gc'd textures that are part of the currently active context (if one is active) will be deleted immediately. If a
     * deleter is not passed in, we do not have an active context, or we encounter gc'd textures that are not part of
     * the current context, then we will queue those textures to be deleted later using the GameTaskQueueManager.
     * 
     * If a non null map is passed into futureStore, it will be populated with Future objects for each queued context.
     * These objects may be used to discover when the deletion tasks have all completed.
     * 
     * @param deleter
     *            if not null, this renderer will be used to immediately delete any gc'd textures in the currently
     *            active context. All other gc'd textures will be queued to delete in their own contexts.
     * @param futureStore
     *            if not null, this map will be populated with any Future task handles created during cleanup.
     */
    public static void cleanExpiredTextures(final Renderer deleter, final Map<Object, Future<Void>> futureStore) {
        // gather up expired textures...
        final Multimap<Object, Integer> idMap = gatherGCdIds();

        // send to be deleted on next render.
        if (idMap != null) {
            handleTextureDelete(deleter, idMap, futureStore);
        }
    }

    @SuppressWarnings("unchecked")
    private static Multimap<Object, Integer> gatherGCdIds() {
        Multimap<Object, Integer> idMap = null;
        // Pull all expired textures from ref queue and add to an id multimap.
        ContextIdReference<TextureKey> ref;
        Integer id;
        while ((ref = (ContextIdReference<TextureKey>) _textureRefQueue.poll()) != null) {
            // lazy init
            if (idMap == null) {
                idMap = ArrayListMultimap.create();
            }
            if (Constants.useMultipleContexts) {
                final Set<Object> contextObjects = ref.getContextObjects();
                for (final Object o : contextObjects) {
                    id = ref.getValue(o);
                    if (id != null && id.intValue() != 0) {
                        // Add id to map
                        idMap.put(o, id);
                    }
                }
            } else {
                id = ref.getValue(null);
                if (id != null && id.intValue() != 0) {
                    idMap.put(ContextManager.getCurrentContext().getGlContextRep(), id);
                }
            }
            ref.clear();
        }
        return idMap;
    }

    private static void handleTextureDelete(final Renderer deleter, final Multimap<Object, Integer> idMap,
            final Map<Object, Future<Void>> futureStore) {
        Object currentGLRef = null;
        // Grab the current context, if any.
        if (deleter != null && ContextManager.getCurrentContext() != null) {
            currentGLRef = ContextManager.getCurrentContext().getGlContextRep();
        }
        // For each affected context...
        for (final Object glref : idMap.keySet()) {
            // If we have a deleter and the context is current, immediately delete
            if (currentGLRef != null && (!Constants.useMultipleContexts || glref.equals(currentGLRef))) {
                deleter.deleteTextureIds(idMap.get(glref));
            }
            // Otherwise, add a delete request to that context's render task queue.
            else {
                final Future<Void> future = GameTaskQueueManager.getManager(ContextManager.getContextForRef(glref))
                        .render(new RendererCallable<Void>() {
                            public Void call() throws Exception {
                                getRenderer().deleteTextureIds(idMap.get(glref));
                                return null;
                            }
                        });
                if (futureStore != null) {
                    futureStore.put(glref, future);
                }
            }
        }
    }

    @MainThread
    public static void preloadCache(final Renderer r) {
        for (final Texture t : _tCache.values()) {
            if (t == null) {
                continue;
            }
            if (t.getTextureKey().getSource() != null) {
                r.loadTexture(t, 0);
            }
        }
    }

    static ReferenceQueue<TextureKey> getRefQueue() {
        return _textureRefQueue;
    }
}