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