Home | History | Annotate | Download | only in glrenderer
      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 
     17 package com.android.gallery3d.glrenderer;
     18 
     19 import android.graphics.Bitmap;
     20 import android.graphics.Bitmap.Config;
     21 import android.opengl.GLUtils;
     22 
     23 import junit.framework.Assert;
     24 
     25 import java.util.HashMap;
     26 
     27 import 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().
     43 public 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