package gl.textures; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11Ext; import util.HasDebugInformation; import util.ImageTransform; import util.Log; import android.graphics.Bitmap; import android.opengl.GLUtils; public class TextureManager implements HasDebugInformation { public interface TexturReloader { Bitmap reload(String textureName); } private static final String LOG_TAG = "Texture Manager"; private static final int INIT_TEXTURE_MAP_SIZE = 40; private static TextureManager instance = new TextureManager(); public static boolean recycleBitmapsToFreeMemory = false; /** * TODO why am i using ArrayList here... */ private ArrayList<Texture> newTexturesToLoad; private int textureArrayOffset = 0; private int[] textureArray = new int[INIT_TEXTURE_MAP_SIZE]; private HashMap<String, Texture> myTextureMap; private TexturReloader myReloader; /** * @param target * The target mesh where the texture will be set to * @param bitmap * The bitmap that should be used as the texture * @param textureName * An unique name for the texture. Textures with the same name * will have the same OpenGL textures! */ public void addTexture(TexturedRenderData target, Bitmap bitmap, String textureName) { Texture t = loadTextureFromMap(textureName); if (t == null) { addTexture(new Texture(target, bitmap, textureName)); } else { Log.d(LOG_TAG, "Texture for " + textureName + " already added, so it will get the same texture id"); t.addRenderData(target); } } private void addTexture(Texture t) { Log.d(LOG_TAG, " > Texture for " + t.getName() + " not jet added, so it will get a new texture id"); addTextureToMap(t); if (newTexturesToLoad == null) { Log.i(LOG_TAG, " > Texture Manage never used before, now its initialized"); newTexturesToLoad = new ArrayList<Texture>(); } newTexturesToLoad.add(t); } /** * Dont forget to set {@link TextureManager#recycleBitmapsToFreeMemory} to * true or the reloader wont be used anyway * * @param reloader */ public void setTextureReloader(TexturReloader reloader) { this.myReloader = reloader; } private Texture loadTextureFromMap(String textureName) { if (myTextureMap == null) return null; return myTextureMap.get(textureName); } public void updateTextures(GL10 gl) { if (newTexturesToLoad != null && newTexturesToLoad.size() > 0) { try { while (textureArray.length - textureArrayOffset < newTexturesToLoad .size()) { Log.d(LOG_TAG, "Resizing textureArray!"); textureArray = doubleTheArraySize(textureArray); } // generate and store id numbers in textureArray: gl.glGenTextures(newTexturesToLoad.size(), textureArray, textureArrayOffset); int newtextureArrayOffset = newTexturesToLoad.size(); for (int i = 0; i < newTexturesToLoad.size(); i++) { Texture t = newTexturesToLoad.get(i); int newTextureId = textureArray[textureArrayOffset + i]; t.idArrived(newTextureId); gl.glBindTexture(GL10.GL_TEXTURE_2D, newTextureId); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_REPLACE); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, t.getImage(), 0); int[] mCropWorkspace = new int[4]; mCropWorkspace[0] = 0; mCropWorkspace[1] = t.getImage().getHeight(); mCropWorkspace[2] = t.getImage().getWidth(); mCropWorkspace[3] = -t.getImage().getHeight(); // TODO maybe not working on any phone because using GL11? ((GL11) gl) .glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, mCropWorkspace, 0); t.recycleImage(); int error = gl.glGetError(); if (error != GL10.GL_NO_ERROR) { Log.e("SpriteMethodTest", "Texture Load GLError: " + error); } } textureArrayOffset = newtextureArrayOffset; newTexturesToLoad.clear(); } catch (Exception e) { showDebugInformation(); e.printStackTrace(); } } } private int[] doubleTheArraySize(int[] a) { int[] b = new int[a.length * 2]; // copy old values: for (int i = 0; i < a.length; i++) { b[i] = a[i]; } return b; } private void addTextureToMap(Texture t) { if (myTextureMap == null) myTextureMap = new HashMap<String, Texture>(); myTextureMap.put(t.getName(), t); } public static TextureManager getInstance() { return instance; } /** * its important that the used textures have a size powered 2 (2,4,8,16,32.. * x 2,4,8..) so resize the bitmap if it has not the correct size * * @param b * @return */ public Bitmap resizeBitmapIfNecessary(Bitmap b) { int height = b.getHeight(); int width = b.getWidth(); int newHeight = getNextPowerOfTwoValue(height); int newWidth = getNextPowerOfTwoValue(width); if ((height != newHeight) || (width != newWidth)) { Log.v(LOG_TAG, " > Need to resize bitmap: old height=" + height + ", old width=" + width + ", new height=" + newHeight + ", new width=" + newWidth); return ImageTransform.resizeBitmap(b, newHeight, newWidth); } return b; } public static int getNextPowerOfTwoValue(double x) { /* * calc log2(x) (log2(x) can be calculated with log(x)/log(2)) and get * the next bigger integer value. then calc 2^this value */ double x2 = Math.pow(2, Math.floor(Math.log(x) / Math.log(2)) + 1); if (x2 != x) { return (int) x2; } return (int) x; } @Override public void showDebugInformation() { Log.i(LOG_TAG, "Debug infos about the Texture Manager:"); Log.i(LOG_TAG, " > newTexturesToLoad=" + newTexturesToLoad); Log.i(LOG_TAG, " > textureArray.length=" + textureArray.length); Log.i(LOG_TAG, " > textureArrayOffset=" + textureArrayOffset); Log.i(LOG_TAG, " > length-offset=" + (textureArray.length - textureArrayOffset)); Log.i(LOG_TAG, " > newTexturesToLoad.size()=" + newTexturesToLoad.size()); } public static void resetInstance() { instance = new TextureManager(); } public static void reloadTexturesIfNeeded() { try { Collection<Texture> a = getInstance().myTextureMap.values(); resetInstance(); Log.d(LOG_TAG, "Restoring " + a.size() + " textures"); for (Iterator<Texture> iterator = a.iterator(); iterator.hasNext();) { getInstance().addTexture(iterator.next()); } } catch (Exception e) { Log.e(LOG_TAG, "Error while restoring textures"); e.printStackTrace(); } } public TexturReloader getTextureReloader() { return myReloader; } }