package org.andengine.opengl.texture; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import org.andengine.opengl.texture.bitmap.BitmapTexture; import org.andengine.opengl.texture.bitmap.BitmapTextureFormat; import org.andengine.opengl.util.GLState; import org.andengine.util.adt.io.in.AssetInputStreamOpener; import org.andengine.util.adt.io.in.IInputStreamOpener; import org.andengine.util.debug.Debug; import android.content.res.AssetManager; /** * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 17:48:46 - 08.03.2010 */ public class TextureManager { // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== private final HashSet<ITexture> mTexturesManaged = new HashSet<ITexture>(); private final HashMap<String, ITexture> mTexturesMapped = new HashMap<String, ITexture>(); private final ArrayList<ITexture> mTexturesLoaded = new ArrayList<ITexture>(); private final ArrayList<ITexture> mTexturesToBeLoaded = new ArrayList<ITexture>(); private final ArrayList<ITexture> mTexturesToBeUnloaded = new ArrayList<ITexture>(); private TextureWarmUpVertexBufferObject mTextureWarmUpVertexBufferObject; // =========================================================== // Constructors // =========================================================== // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== public synchronized void onCreate() { this.mTextureWarmUpVertexBufferObject = new TextureWarmUpVertexBufferObject(); } public synchronized void onReload() { final HashSet<ITexture> managedTextures = this.mTexturesManaged; if(!managedTextures.isEmpty()) { for(final ITexture texture : managedTextures) { // TODO Can the use of the iterator be avoided somehow? texture.setNotLoadedToHardware(); } } if(!this.mTexturesLoaded.isEmpty()) { this.mTexturesToBeLoaded.addAll(this.mTexturesLoaded); // TODO Check if addAll uses iterator internally! this.mTexturesLoaded.clear(); } if(!this.mTexturesToBeUnloaded.isEmpty()) { this.mTexturesManaged.removeAll(this.mTexturesToBeUnloaded); // TODO Check if removeAll uses iterator internally! this.mTexturesToBeUnloaded.clear(); } this.mTextureWarmUpVertexBufferObject.setNotLoadedToHardware(); } public synchronized void onDestroy() { final HashSet<ITexture> managedTextures = this.mTexturesManaged; for(final ITexture texture : managedTextures) { // TODO Can the use of the iterator be avoided somehow? texture.setNotLoadedToHardware(); } this.mTexturesToBeLoaded.clear(); this.mTexturesLoaded.clear(); this.mTexturesManaged.clear(); this.mTexturesMapped.clear(); this.mTextureWarmUpVertexBufferObject.dispose(); this.mTextureWarmUpVertexBufferObject = null; } public synchronized boolean hasMappedTexture(final String pID) { if(pID == null) { throw new IllegalArgumentException("pID must not be null!"); } return this.mTexturesMapped.containsKey(pID); } public synchronized ITexture getMappedTexture(final String pID) { if(pID == null) { throw new IllegalArgumentException("pID must not be null!"); } return this.mTexturesMapped.get(pID); } public synchronized void addMappedTexture(final String pID, final ITexture pTexture) throws IllegalArgumentException { if(pID == null) { throw new IllegalArgumentException("pID must not be null!"); } else if(pTexture == null) { throw new IllegalArgumentException("pTexture must not be null!"); } else if(this.mTexturesMapped.containsKey(pID)) { throw new IllegalArgumentException("Collision for pID: '" + pID + "'."); } this.mTexturesMapped.put(pID, pTexture); } public synchronized ITexture removedMappedTexture(final String pID) { if(pID == null) { throw new IllegalArgumentException("pID must not be null!"); } return this.mTexturesMapped.remove(pID); } /** * @param pTexture the {@link ITexture} to be loaded before the very next frame is drawn (Or prevent it from being unloaded then). * @return <code>true</code> when the {@link ITexture} was previously not managed by this {@link TextureManager}, <code>false</code> if it was already managed. */ public synchronized boolean loadTexture(final ITexture pTexture) { if(pTexture == null) { throw new IllegalArgumentException("pTexture must not be null!"); } if(this.mTexturesManaged.contains(pTexture)) { /* Just make sure it doesn't get deleted. */ this.mTexturesToBeUnloaded.remove(pTexture); return false; } else { this.mTexturesManaged.add(pTexture); this.mTexturesToBeLoaded.add(pTexture); return true; } } /** * Must be called from the GL-{@link Thread}. * * @param pGLState * @param pTexture the {@link ITexture} to be loaded right now, if it is not loaded. * @return <code>true</code> when the {@link ITexture} was previously not managed by this {@link TextureManager}, <code>false</code> if it was already managed. */ public synchronized boolean loadTexture(final GLState pGLState, final ITexture pTexture) throws IOException { if(pTexture == null) { throw new IllegalArgumentException("pTexture must not be null!"); } if(!pTexture.isLoadedToHardware()) { pTexture.loadToHardware(pGLState); } else if(pTexture.isUpdateOnHardwareNeeded()) { pTexture.reloadToHardware(pGLState); } if(this.mTexturesManaged.contains(pTexture)) { /* Just make sure it doesn't get deleted. */ this.mTexturesToBeUnloaded.remove(pTexture); return false; } else { this.mTexturesManaged.add(pTexture); this.mTexturesLoaded.add(pTexture); return true; } } /** * @param pTexture the {@link ITexture} to be unloaded before the very next frame is drawn (Or prevent it from being loaded then). * @return <code>true</code> when the {@link ITexture} was already managed by this {@link TextureManager}, <code>false</code> if it was not managed. */ public synchronized boolean unloadTexture(final ITexture pTexture) { if(pTexture == null) { throw new IllegalArgumentException("pTexture must not be null!"); } if(this.mTexturesManaged.contains(pTexture)) { /* If the Texture is loaded, unload it. * If the Texture is about to be loaded, stop it from being loaded. */ if(this.mTexturesLoaded.contains(pTexture)) { this.mTexturesToBeUnloaded.add(pTexture); } else if(this.mTexturesToBeLoaded.remove(pTexture)) { this.mTexturesManaged.remove(pTexture); } return true; } else { return false; } } /** * Must be called from the GL-{@link Thread}. * * @param pGLState * @param pTexture the {@link ITexture} to be unloaded right now, if it is loaded. * @return <code>true</code> when the {@link ITexture} was already managed by this {@link TextureManager}, <code>false</code> if it was not managed. */ public synchronized boolean unloadTexture(final GLState pGLState, final ITexture pTexture) { if(pTexture == null) { throw new IllegalArgumentException("pTexture must not be null!"); } else if(pTexture.isLoadedToHardware()) { pTexture.unloadFromHardware(pGLState); } if(this.mTexturesManaged.contains(pTexture)) { /* Just make sure it doesn't get loaded. */ this.mTexturesLoaded.remove(pTexture); this.mTexturesToBeLoaded.remove(pTexture); return true; } else { return false; } } public synchronized void updateTextures(final GLState pGLState) { final HashSet<ITexture> texturesManaged = this.mTexturesManaged; final ArrayList<ITexture> texturesLoaded = this.mTexturesLoaded; final ArrayList<ITexture> texturesToBeLoaded = this.mTexturesToBeLoaded; final ArrayList<ITexture> texturesToBeUnloaded = this.mTexturesToBeUnloaded; /* First reload Textures that need to be updated. */ for(int i = texturesLoaded.size() - 1; i >= 0; i--) { final ITexture textureToBeReloaded = texturesLoaded.get(i); if(textureToBeReloaded.isUpdateOnHardwareNeeded()) { try { textureToBeReloaded.reloadToHardware(pGLState); } catch (final IOException e) { Debug.e(e); } } } /* Then load pending Textures. */ final int texturesToBeLoadedCount = texturesToBeLoaded.size(); if(texturesToBeLoadedCount > 0) { for(int i = texturesToBeLoadedCount - 1; i >= 0; i--) { final ITexture textureToBeLoaded = texturesToBeLoaded.remove(i); if(!textureToBeLoaded.isLoadedToHardware()) { try { textureToBeLoaded.loadToHardware(pGLState); /* Execute the warm-up to ensure the texture data is actually moved to the GPU. */ this.mTextureWarmUpVertexBufferObject.warmup(pGLState, textureToBeLoaded); } catch (final IOException e) { Debug.e(e); } } texturesLoaded.add(textureToBeLoaded); } } /* Then unload pending Textures. */ final int texturesToBeUnloadedCount = texturesToBeUnloaded.size(); if(texturesToBeUnloadedCount > 0) { for(int i = texturesToBeUnloadedCount - 1; i >= 0; i--) { final ITexture textureToBeUnloaded = texturesToBeUnloaded.remove(i); if(textureToBeUnloaded.isLoadedToHardware()) { textureToBeUnloaded.unloadFromHardware(pGLState); } texturesLoaded.remove(textureToBeUnloaded); texturesManaged.remove(textureToBeUnloaded); } } /* Finally invoke the GC if anything has changed. */ if((texturesToBeLoadedCount > 0) || (texturesToBeUnloadedCount > 0)) { System.gc(); } } public synchronized ITexture getTexture(final String pID, final AssetManager pAssetManager, final String pAssetPath) throws IOException { return this.getTexture(pID, pAssetManager, pAssetPath, TextureOptions.DEFAULT); } public synchronized ITexture getTexture(final String pID, final AssetManager pAssetManager, final String pAssetPath, final TextureOptions pTextureOptions) throws IOException { if(this.hasMappedTexture(pID)) { return this.getMappedTexture(pID); } else { final ITexture texture = new BitmapTexture(this, new AssetInputStreamOpener(pAssetManager, pAssetPath), pTextureOptions); this.loadTexture(texture); this.addMappedTexture(pID, texture); return texture; } } public synchronized ITexture getTexture(final String pID, final IInputStreamOpener pInputStreamOpener) throws IOException { return this.getTexture(pID, pInputStreamOpener, TextureOptions.DEFAULT); } public synchronized ITexture getTexture(final String pID, final IInputStreamOpener pInputStreamOpener, final TextureOptions pTextureOptions) throws IOException { return this.getTexture(pID, pInputStreamOpener, BitmapTextureFormat.RGBA_8888, pTextureOptions); } public synchronized ITexture getTexture(final String pID, final IInputStreamOpener pInputStreamOpener, final BitmapTextureFormat pBitmapTextureFormat, final TextureOptions pTextureOptions) throws IOException { return this.getTexture(pID, pInputStreamOpener, pBitmapTextureFormat, pTextureOptions, true); } public synchronized ITexture getTexture(final String pID, final IInputStreamOpener pInputStreamOpener, final BitmapTextureFormat pBitmapTextureFormat, final TextureOptions pTextureOptions, final boolean pLoadToHardware) throws IOException { if(this.hasMappedTexture(pID)) { return this.getMappedTexture(pID); } else { final ITexture texture = new BitmapTexture(this, pInputStreamOpener, pBitmapTextureFormat, pTextureOptions); if(pLoadToHardware) { this.loadTexture(texture); } this.addMappedTexture(pID, texture); return texture; } } // =========================================================== // Inner and Anonymous Classes // =========================================================== }