1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.replica.replicaisland;
18
19import java.io.IOException;
20import java.io.InputStream;
21
22import javax.microedition.khronos.opengles.GL10;
23import javax.microedition.khronos.opengles.GL11;
24import javax.microedition.khronos.opengles.GL11Ext;
25
26import android.content.Context;
27import android.graphics.Bitmap;
28import android.graphics.BitmapFactory;
29import android.opengl.GLU;
30import android.opengl.GLUtils;
31
32/**
33 * The Texture Library manages all textures in the game.  Textures are pooled and handed out to
34 * requesting parties via allocateTexture().  However, the texture data itself is not immediately
35 * loaded at that time; it may have already been loaded or it may be loaded in the future via
36 * a call to loadTexture() or loadAllTextures().  This allows Texture objects to be dispersed to
37 * various game systems and while the texture data itself is streamed in or loaded as necessary.
38 */
39public class TextureLibrary extends BaseObject {
40    // Textures are stored in a simple hash.  This class implements its own array-based hash rather
41    // than using HashMap for performance.
42    Texture[] mTextureHash;
43    int[] mTextureNameWorkspace;
44    int[] mCropWorkspace;
45    static final int DEFAULT_SIZE = 512;
46    static BitmapFactory.Options sBitmapOptions  = new BitmapFactory.Options();
47
48    public TextureLibrary() {
49        super();
50        mTextureHash = new Texture[DEFAULT_SIZE];
51        for (int x = 0; x < mTextureHash.length; x++) {
52            mTextureHash[x] = new Texture();
53        }
54
55        mTextureNameWorkspace = new int[1];
56        mCropWorkspace = new int[4];
57
58        sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
59    }
60
61    @Override
62    public void reset() {
63        removeAll();
64    }
65
66    /**
67     * Creates a Texture object that is mapped to the passed resource id.  If a texture has already
68     * been allocated for this id, the previously allocated Texture object is returned.
69     * @param resourceID
70     * @return
71     */
72    public Texture allocateTexture(int resourceID) {
73        Texture texture = getTextureByResource(resourceID);
74        if (texture == null) {
75            texture = addTexture(resourceID, -1, 0, 0);
76        }
77
78        return texture;
79    }
80
81    /** Loads a single texture into memory.  Does nothing if the texture is already loaded. */
82    public Texture loadTexture(Context context, GL10 gl, int resourceID) {
83        Texture texture = allocateTexture(resourceID);
84        texture = loadBitmap(context, gl, texture);
85        return texture;
86    }
87
88    /** Loads all unloaded textures into OpenGL memory.  Already-loaded textures are ignored. */
89    public void loadAll(Context context, GL10 gl) {
90        for (int x = 0; x < mTextureHash.length; x++) {
91            if (mTextureHash[x].resource != -1 && mTextureHash[x].loaded == false) {
92                loadBitmap(context, gl, mTextureHash[x]);
93            }
94        }
95    }
96
97    /** Flushes all textures from OpenGL memory */
98    public void deleteAll(GL10 gl) {
99        for (int x = 0; x < mTextureHash.length; x++) {
100            if (mTextureHash[x].resource != -1 && mTextureHash[x].loaded) {
101            	assert mTextureHash[x].name != -1;
102                mTextureNameWorkspace[0] = mTextureHash[x].name;
103                mTextureHash[x].name = -1;
104                mTextureHash[x].loaded = false;
105                gl.glDeleteTextures(1, mTextureNameWorkspace, 0);
106                int error = gl.glGetError();
107                if (error != GL10.GL_NO_ERROR) {
108                    DebugLog.d("Texture Delete", "GLError: " + error + " (" + GLU.gluErrorString(error) + "): " + mTextureHash[x].resource);
109                }
110
111                assert error == GL10.GL_NO_ERROR;
112            }
113        }
114    }
115
116    /** Marks all textures as unloaded */
117    public void invalidateAll() {
118        for (int x = 0; x < mTextureHash.length; x++) {
119            if (mTextureHash[x].resource != -1 && mTextureHash[x].loaded) {
120                mTextureHash[x].name = -1;
121                mTextureHash[x].loaded = false;
122            }
123        }
124    }
125
126    /** Loads a bitmap into OpenGL and sets up the common parameters for 2D texture maps. */
127    protected Texture loadBitmap(Context context, GL10 gl, Texture texture) {
128        assert gl != null;
129        assert context != null;
130        assert texture != null;
131        if (texture.loaded == false && texture.resource != -1) {
132            gl.glGenTextures(1, mTextureNameWorkspace, 0);
133
134            int error = gl.glGetError();
135            if (error != GL10.GL_NO_ERROR) {
136                DebugLog.d("Texture Load 1", "GLError: " + error + " (" + GLU.gluErrorString(error) + "): " + texture.resource);
137            }
138
139            assert error == GL10.GL_NO_ERROR;
140
141            int textureName = mTextureNameWorkspace[0];
142
143            gl.glBindTexture(GL10.GL_TEXTURE_2D, textureName);
144
145            error = gl.glGetError();
146            if (error != GL10.GL_NO_ERROR) {
147                DebugLog.d("Texture Load 2", "GLError: " + error + " (" + GLU.gluErrorString(error) + "): " + texture.resource);
148            }
149
150            assert error == GL10.GL_NO_ERROR;
151
152            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
153            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
154
155            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
156            gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
157
158            gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE); //GL10.GL_REPLACE);
159
160            InputStream is = context.getResources().openRawResource(texture.resource);
161            Bitmap bitmap;
162            try {
163                bitmap = BitmapFactory.decodeStream(is);
164            } finally {
165                try {
166                    is.close();
167                } catch (IOException e) {
168                	e.printStackTrace();
169                    // Ignore.
170                }
171            }
172
173            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
174
175            error = gl.glGetError();
176            if (error != GL10.GL_NO_ERROR) {
177                DebugLog.d("Texture Load 3", "GLError: " + error + " (" + GLU.gluErrorString(error) + "): " + texture.resource);
178            }
179
180            assert error == GL10.GL_NO_ERROR;
181
182            mCropWorkspace[0] = 0;
183            mCropWorkspace[1] = bitmap.getHeight();
184            mCropWorkspace[2] = bitmap.getWidth();
185            mCropWorkspace[3] = -bitmap.getHeight();
186
187            ((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES,
188                            mCropWorkspace, 0);
189
190            texture.name = textureName;
191            texture.width = bitmap.getWidth();
192            texture.height = bitmap.getHeight();
193
194            bitmap.recycle();
195
196            error = gl.glGetError();
197            if (error != GL10.GL_NO_ERROR) {
198                DebugLog.d("Texture Load 4", "GLError: " + error + " (" + GLU.gluErrorString(error) + "): " + texture.resource);
199            }
200
201            assert error == GL10.GL_NO_ERROR;
202
203            texture.loaded = true;
204
205        }
206
207        return texture;
208    }
209
210    public boolean isTextureLoaded(int resourceID) {
211        return getTextureByResource(resourceID) != null;
212    }
213
214    /**
215     * Returns the texture associated with the passed Android resource ID.
216     * @param resourceID The resource ID of a bitmap defined in R.java.
217     * @return An associated Texture object, or null if there is no associated
218     *  texture in the library.
219     */
220    public Texture getTextureByResource(int resourceID) {
221        int index = getHashIndex(resourceID);
222        int realIndex = findFirstKey(index, resourceID);
223        Texture texture = null;
224        if (realIndex != -1) {
225            texture = mTextureHash[realIndex];
226        }
227        return texture;
228    }
229
230    private int getHashIndex(int id) {
231        return id % mTextureHash.length;
232    }
233
234    /**
235     * Locates the texture in the hash.  This hash uses a simple linear probe chaining mechanism:
236     * if the hash slot is occupied by some other entry, the next empty array index is used.
237     * This is O(n) for the worst case (every slot is a cache miss) but the average case is
238     * constant time.
239     * @param startIndex
240     * @param key
241     * @return
242     */
243    private int findFirstKey(int startIndex, int key) {
244        int index = -1;
245        for (int x = 0; x < mTextureHash.length; x++) {
246            final int actualIndex = (startIndex + x) % mTextureHash.length;
247            if (mTextureHash[actualIndex].resource == key) {
248                index = actualIndex;
249                break;
250            } else if (mTextureHash[actualIndex].resource == -1) {
251                break;
252            }
253        }
254        return index;
255    }
256
257    /** Inserts a texture into the hash */
258    protected Texture addTexture(int id, int name, int width, int height) {
259        int index = findFirstKey(getHashIndex(id), -1);
260        Texture texture = null;
261        assert index != -1;
262
263        if (index != -1) {
264            mTextureHash[index].resource = id;
265            mTextureHash[index].name = name;
266            mTextureHash[index].width = width;
267            mTextureHash[index].height = height;
268            texture = mTextureHash[index];
269        }
270
271        return texture;
272    }
273
274    public void removeAll() {
275        for (int x = 0; x < mTextureHash.length; x++) {
276            mTextureHash[x].reset();
277        }
278    }
279
280}
281