UploadedTexture.java revision cb4fb7c19f20405fb5e08513e6297dffce824118
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.android.gallery3d.ui;
18
19import android.graphics.Bitmap;
20import android.graphics.Bitmap.Config;
21import android.opengl.GLUtils;
22
23import com.android.gallery3d.common.Utils;
24
25import java.util.HashMap;
26
27import javax.microedition.khronos.opengles.GL11;
28import javax.microedition.khronos.opengles.GL11Ext;
29
30// UploadedTextures use a Bitmap for the content of the texture.
31//
32// Subclasses should implement onGetBitmap() to provide the Bitmap and
33// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
34// is not needed anymore.
35//
36// isContentValid() is meaningful only when the isLoaded() returns true.
37// It means whether the content needs to be updated.
38//
39// The user of this class should call recycle() when the texture is not
40// needed anymore.
41//
42// By default an UploadedTexture is opaque (so it can be drawn faster without
43// blending). The user or subclass can override it using setOpaque().
44abstract class UploadedTexture extends BasicTexture {
45
46    // To prevent keeping allocation the borders, we store those used borders here.
47    // Since the length will be power of two, it won't use too much memory.
48    private static HashMap<BorderKey, Bitmap> sBorderLines =
49            new HashMap<BorderKey, Bitmap>();
50    private static BorderKey sBorderKey = new BorderKey();
51
52    @SuppressWarnings("unused")
53    private static final String TAG = "Texture";
54    private boolean mContentValid = true;
55    private boolean mOpaque = true;
56    private boolean mThrottled = false;
57    private static int sUploadedCount;
58    private static final int UPLOAD_LIMIT = 100;
59
60    protected Bitmap mBitmap;
61    private int mBorder;
62
63    protected UploadedTexture() {
64        this(false);
65    }
66
67    protected UploadedTexture(boolean hasBorder) {
68        super(null, 0, STATE_UNLOADED);
69        if (hasBorder) {
70            setBorder(true);
71            mBorder = 1;
72        }
73    }
74
75    private static class BorderKey implements Cloneable {
76        public boolean vertical;
77        public Config config;
78        public int length;
79
80        @Override
81        public int hashCode() {
82            int x = config.hashCode() ^ length;
83            return vertical ? x : -x;
84        }
85
86        @Override
87        public boolean equals(Object object) {
88            if (!(object instanceof BorderKey)) return false;
89            BorderKey o = (BorderKey) object;
90            return vertical == o.vertical
91                    && config == o.config && length == o.length;
92        }
93
94        @Override
95        public BorderKey clone() {
96            try {
97                return (BorderKey) super.clone();
98            } catch (CloneNotSupportedException e) {
99                throw new AssertionError(e);
100            }
101        }
102    }
103
104    protected void setThrottled(boolean throttled) {
105        mThrottled = throttled;
106    }
107
108    private static Bitmap getBorderLine(
109            boolean vertical, Config config, int length) {
110        BorderKey key = sBorderKey;
111        key.vertical = vertical;
112        key.config = config;
113        key.length = length;
114        Bitmap bitmap = sBorderLines.get(key);
115        if (bitmap == null) {
116            bitmap = vertical
117                    ? Bitmap.createBitmap(1, length, config)
118                    : Bitmap.createBitmap(length, 1, config);
119            sBorderLines.put(key.clone(), bitmap);
120        }
121        return bitmap;
122    }
123
124    private Bitmap getBitmap() {
125        if (mBitmap == null) {
126            mBitmap = onGetBitmap();
127            int w = mBitmap.getWidth() + mBorder * 2;
128            int h = mBitmap.getHeight() + mBorder * 2;
129            if (mWidth == UNSPECIFIED) {
130                setSize(w, h);
131            } else if (mWidth != w || mHeight != h) {
132                throw new IllegalStateException(String.format(
133                        "cannot change size: this = %s, orig = %sx%s, new = %sx%s",
134                        toString(), mWidth, mHeight, w, h));
135            }
136        }
137        return mBitmap;
138    }
139
140    private void freeBitmap() {
141        Utils.assertTrue(mBitmap != null);
142        onFreeBitmap(mBitmap);
143        mBitmap = null;
144    }
145
146    @Override
147    public int getWidth() {
148        if (mWidth == UNSPECIFIED) getBitmap();
149        return mWidth;
150    }
151
152    @Override
153    public int getHeight() {
154        if (mWidth == UNSPECIFIED) getBitmap();
155        return mHeight;
156    }
157
158    protected abstract Bitmap onGetBitmap();
159
160    protected abstract void onFreeBitmap(Bitmap bitmap);
161
162    protected void invalidateContent() {
163        if (mBitmap != null) freeBitmap();
164        mContentValid = false;
165        mWidth = UNSPECIFIED;
166        mHeight = UNSPECIFIED;
167    }
168
169    /**
170     * Whether the content on GPU is valid.
171     */
172    public boolean isContentValid(GLCanvas canvas) {
173        return isLoaded(canvas) && mContentValid;
174    }
175
176    /**
177     * Updates the content on GPU's memory.
178     * @param canvas
179     */
180    public void updateContent(GLCanvas canvas) {
181        if (!isLoaded(canvas)) {
182            if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
183                return;
184            }
185            uploadToCanvas(canvas);
186        } else if (!mContentValid) {
187            Bitmap bitmap = getBitmap();
188            int format = GLUtils.getInternalFormat(bitmap);
189            int type = GLUtils.getType(bitmap);
190            canvas.getGLInstance().glBindTexture(GL11.GL_TEXTURE_2D, mId);
191            GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder,
192                    bitmap, format, type);
193            freeBitmap();
194            mContentValid = true;
195        }
196    }
197
198    public static void resetUploadLimit() {
199        sUploadedCount = 0;
200    }
201
202    public static boolean uploadLimitReached() {
203        return sUploadedCount > UPLOAD_LIMIT;
204    }
205
206    static int[] sTextureId = new int[1];
207    static float[] sCropRect = new float[4];
208
209    private void uploadToCanvas(GLCanvas canvas) {
210        GL11 gl = canvas.getGLInstance();
211
212        Bitmap bitmap = getBitmap();
213        if (bitmap != null) {
214            try {
215                int bWidth = bitmap.getWidth();
216                int bHeight = bitmap.getHeight();
217                int width = bWidth + mBorder * 2;
218                int height = bHeight + mBorder * 2;
219                int texWidth = getTextureWidth();
220                int texHeight = getTextureHeight();
221                // Define a vertically flipped crop rectangle for
222                // OES_draw_texture.
223                // The four values in sCropRect are: left, bottom, width, and
224                // height. Negative value of width or height means flip.
225                sCropRect[0] = mBorder;
226                sCropRect[1] = mBorder + bHeight;
227                sCropRect[2] = bWidth;
228                sCropRect[3] = -bHeight;
229
230                // Upload the bitmap to a new texture.
231                gl.glGenTextures(1, sTextureId, 0);
232                gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]);
233                gl.glTexParameterfv(GL11.GL_TEXTURE_2D,
234                        GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
235                gl.glTexParameteri(GL11.GL_TEXTURE_2D,
236                        GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
237                gl.glTexParameteri(GL11.GL_TEXTURE_2D,
238                        GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
239                gl.glTexParameterf(GL11.GL_TEXTURE_2D,
240                        GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
241                gl.glTexParameterf(GL11.GL_TEXTURE_2D,
242                        GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
243
244                if (bWidth == texWidth && bHeight == texHeight) {
245                    GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0);
246                } else {
247                    int format = GLUtils.getInternalFormat(bitmap);
248                    int type = GLUtils.getType(bitmap);
249                    Config config = bitmap.getConfig();
250
251                    gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format,
252                            texWidth, texHeight, 0, format, type, null);
253                    GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
254                            mBorder, mBorder, bitmap, format, type);
255
256                    if (mBorder > 0) {
257                        // Left border
258                        Bitmap line = getBorderLine(true, config, texHeight);
259                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
260                                0, 0, line, format, type);
261
262                        // Top border
263                        line = getBorderLine(false, config, texWidth);
264                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
265                                0, 0, line, format, type);
266                    }
267
268                    // Right border
269                    if (mBorder + bWidth < texWidth) {
270                        Bitmap line = getBorderLine(true, config, texHeight);
271                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
272                                mBorder + bWidth, 0, line, format, type);
273                    }
274
275                    // Bottom border
276                    if (mBorder + bHeight < texHeight) {
277                        Bitmap line = getBorderLine(false, config, texWidth);
278                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
279                                0, mBorder + bHeight, line, format, type);
280                    }
281                }
282            } finally {
283                freeBitmap();
284            }
285            // Update texture state.
286            setAssociatedCanvas(canvas);
287            mId = sTextureId[0];
288            mState = UploadedTexture.STATE_LOADED;
289            mContentValid = true;
290        } else {
291            mState = STATE_ERROR;
292            throw new RuntimeException("Texture load fail, no bitmap");
293        }
294    }
295
296    @Override
297    protected boolean onBind(GLCanvas canvas) {
298        updateContent(canvas);
299        return isContentValid(canvas);
300    }
301
302    public void setOpaque(boolean isOpaque) {
303        mOpaque = isOpaque;
304    }
305
306    public boolean isOpaque() {
307        return mOpaque;
308    }
309
310    @Override
311    public void recycle() {
312        super.recycle();
313        if (mBitmap != null) freeBitmap();
314    }
315}
316