Home | History | Annotate | Download | only in glrenderer
      1 /*
      2  * Copyright (C) 2012 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.graphics.Canvas;
     22 import android.graphics.Color;
     23 import android.graphics.Paint;
     24 import android.graphics.PorterDuff.Mode;
     25 import android.graphics.PorterDuffXfermode;
     26 import android.graphics.RectF;
     27 import android.os.SystemClock;
     28 
     29 import com.android.gallery3d.ui.GLRoot;
     30 import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
     31 
     32 import java.util.ArrayDeque;
     33 import java.util.ArrayList;
     34 
     35 // This class is similar to BitmapTexture, except the bitmap is
     36 // split into tiles. By doing so, we may increase the time required to
     37 // upload the whole bitmap but we reduce the time of uploading each tile
     38 // so it make the animation more smooth and prevents jank.
     39 public class TiledTexture implements Texture {
     40     private static final int CONTENT_SIZE = 254;
     41     private static final int BORDER_SIZE = 1;
     42     private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
     43     private static final int INIT_CAPACITY = 8;
     44 
     45     // We are targeting at 60fps, so we have 16ms for each frame.
     46     // In this 16ms, we use about 4~8 ms to upload tiles.
     47     private static final long UPLOAD_TILE_LIMIT = 4; // ms
     48 
     49     private static Tile sFreeTileHead = null;
     50     private static final Object sFreeTileLock = new Object();
     51 
     52     private static Bitmap sUploadBitmap;
     53     private static Canvas sCanvas;
     54     private static Paint sBitmapPaint;
     55     private static Paint sPaint;
     56 
     57     private int mUploadIndex = 0;
     58 
     59     private final Tile[] mTiles;  // Can be modified in different threads.
     60                                   // Should be protected by "synchronized."
     61     private final int mWidth;
     62     private final int mHeight;
     63     private final RectF mSrcRect = new RectF();
     64     private final RectF mDestRect = new RectF();
     65 
     66     public static class Uploader implements OnGLIdleListener {
     67         private final ArrayDeque<TiledTexture> mTextures =
     68                 new ArrayDeque<TiledTexture>(INIT_CAPACITY);
     69 
     70         private final GLRoot mGlRoot;
     71         private boolean mIsQueued = false;
     72 
     73         public Uploader(GLRoot glRoot) {
     74             mGlRoot = glRoot;
     75         }
     76 
     77         public synchronized void clear() {
     78             mTextures.clear();
     79         }
     80 
     81         public synchronized void addTexture(TiledTexture t) {
     82             if (t.isReady()) return;
     83             mTextures.addLast(t);
     84 
     85             if (mIsQueued) return;
     86             mIsQueued = true;
     87             mGlRoot.addOnGLIdleListener(this);
     88         }
     89 
     90         @Override
     91         public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
     92             ArrayDeque<TiledTexture> deque = mTextures;
     93             synchronized (this) {
     94                 long now = SystemClock.uptimeMillis();
     95                 long dueTime = now + UPLOAD_TILE_LIMIT;
     96                 while (now < dueTime && !deque.isEmpty()) {
     97                     TiledTexture t = deque.peekFirst();
     98                     if (t.uploadNextTile(canvas)) {
     99                         deque.removeFirst();
    100                         mGlRoot.requestRender();
    101                     }
    102                     now = SystemClock.uptimeMillis();
    103                 }
    104                 mIsQueued = !mTextures.isEmpty();
    105 
    106                 // return true to keep this listener in the queue
    107                 return mIsQueued;
    108             }
    109         }
    110     }
    111 
    112     private static class Tile extends UploadedTexture {
    113         public int offsetX;
    114         public int offsetY;
    115         public Bitmap bitmap;
    116         public Tile nextFreeTile;
    117         public int contentWidth;
    118         public int contentHeight;
    119 
    120         @Override
    121         public void setSize(int width, int height) {
    122             contentWidth = width;
    123             contentHeight = height;
    124             mWidth = width + 2 * BORDER_SIZE;
    125             mHeight = height + 2 * BORDER_SIZE;
    126             mTextureWidth = TILE_SIZE;
    127             mTextureHeight = TILE_SIZE;
    128         }
    129 
    130         @Override
    131         protected Bitmap onGetBitmap() {
    132             // make a local copy of the reference to the bitmap,
    133             // since it might be null'd in a different thread. b/8694871
    134             Bitmap localBitmapRef = bitmap;
    135             bitmap = null;
    136 
    137             if (localBitmapRef != null) {
    138                 int x = BORDER_SIZE - offsetX;
    139                 int y = BORDER_SIZE - offsetY;
    140                 int r = localBitmapRef.getWidth() + x;
    141                 int b = localBitmapRef.getHeight() + y;
    142                 sCanvas.drawBitmap(localBitmapRef, x, y, sBitmapPaint);
    143                 localBitmapRef = null;
    144 
    145                 // draw borders if need
    146                 if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
    147                 if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
    148                 if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
    149                 if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
    150             }
    151 
    152             return sUploadBitmap;
    153         }
    154 
    155         @Override
    156         protected void onFreeBitmap(Bitmap bitmap) {
    157             // do nothing
    158         }
    159     }
    160 
    161     private static void freeTile(Tile tile) {
    162         tile.invalidateContent();
    163         tile.bitmap = null;
    164         synchronized (sFreeTileLock) {
    165             tile.nextFreeTile = sFreeTileHead;
    166             sFreeTileHead = tile;
    167         }
    168     }
    169 
    170     private static Tile obtainTile() {
    171         synchronized (sFreeTileLock) {
    172             Tile result = sFreeTileHead;
    173             if (result == null) return new Tile();
    174             sFreeTileHead = result.nextFreeTile;
    175             result.nextFreeTile = null;
    176             return result;
    177         }
    178     }
    179 
    180     private boolean uploadNextTile(GLCanvas canvas) {
    181         if (mUploadIndex == mTiles.length) return true;
    182 
    183         synchronized (mTiles) {
    184             Tile next = mTiles[mUploadIndex++];
    185 
    186             // Make sure tile has not already been recycled by the time
    187             // this is called (race condition in onGLIdle)
    188             if (next.bitmap != null) {
    189                 boolean hasBeenLoad = next.isLoaded();
    190                 next.updateContent(canvas);
    191 
    192                 // It will take some time for a texture to be drawn for the first
    193                 // time. When scrolling, we need to draw several tiles on the screen
    194                 // at the same time. It may cause a UI jank even these textures has
    195                 // been uploaded.
    196                 if (!hasBeenLoad) next.draw(canvas, 0, 0);
    197             }
    198         }
    199         return mUploadIndex == mTiles.length;
    200     }
    201 
    202     public TiledTexture(Bitmap bitmap) {
    203         mWidth = bitmap.getWidth();
    204         mHeight = bitmap.getHeight();
    205         ArrayList<Tile> list = new ArrayList<Tile>();
    206 
    207         for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
    208             for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
    209                 Tile tile = obtainTile();
    210                 tile.offsetX = x;
    211                 tile.offsetY = y;
    212                 tile.bitmap = bitmap;
    213                 tile.setSize(
    214                         Math.min(CONTENT_SIZE, mWidth - x),
    215                         Math.min(CONTENT_SIZE, mHeight - y));
    216                 list.add(tile);
    217             }
    218         }
    219         mTiles = list.toArray(new Tile[list.size()]);
    220     }
    221 
    222     public boolean isReady() {
    223         return mUploadIndex == mTiles.length;
    224     }
    225 
    226     // Can be called in UI thread.
    227     public void recycle() {
    228         synchronized (mTiles) {
    229             for (int i = 0, n = mTiles.length; i < n; ++i) {
    230                 freeTile(mTiles[i]);
    231             }
    232         }
    233     }
    234 
    235     public static void freeResources() {
    236         sUploadBitmap = null;
    237         sCanvas = null;
    238         sBitmapPaint = null;
    239         sPaint = null;
    240     }
    241 
    242     public static void prepareResources() {
    243         sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
    244         sCanvas = new Canvas(sUploadBitmap);
    245         sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    246         sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
    247         sPaint = new Paint();
    248         sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
    249         sPaint.setColor(Color.TRANSPARENT);
    250     }
    251 
    252     // We want to draw the "source" on the "target".
    253     // This method is to find the "output" rectangle which is
    254     // the corresponding area of the "src".
    255     //                                   (x,y)  target
    256     // (x0,y0)  source                     +---------------+
    257     //    +----------+                     |               |
    258     //    | src      |                     | output        |
    259     //    | +--+     |    linear map       | +----+        |
    260     //    | +--+     |    ---------->      | |    |        |
    261     //    |          | by (scaleX, scaleY) | +----+        |
    262     //    +----------+                     |               |
    263     //      Texture                        +---------------+
    264     //                                          Canvas
    265     private static void mapRect(RectF output,
    266             RectF src, float x0, float y0, float x, float y, float scaleX,
    267             float scaleY) {
    268         output.set(x + (src.left - x0) * scaleX,
    269                 y + (src.top - y0) * scaleY,
    270                 x + (src.right - x0) * scaleX,
    271                 y + (src.bottom - y0) * scaleY);
    272     }
    273 
    274     // Draws a mixed color of this texture and a specified color onto the
    275     // a rectangle. The used color is: from * (1 - ratio) + to * ratio.
    276     public void drawMixed(GLCanvas canvas, int color, float ratio,
    277             int x, int y, int width, int height) {
    278         RectF src = mSrcRect;
    279         RectF dest = mDestRect;
    280         float scaleX = (float) width / mWidth;
    281         float scaleY = (float) height / mHeight;
    282         synchronized (mTiles) {
    283             for (int i = 0, n = mTiles.length; i < n; ++i) {
    284                 Tile t = mTiles[i];
    285                 src.set(0, 0, t.contentWidth, t.contentHeight);
    286                 src.offset(t.offsetX, t.offsetY);
    287                 mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
    288                 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
    289                 canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
    290             }
    291         }
    292     }
    293 
    294     // Draws the texture on to the specified rectangle.
    295     @Override
    296     public void draw(GLCanvas canvas, int x, int y, int width, int height) {
    297         RectF src = mSrcRect;
    298         RectF dest = mDestRect;
    299         float scaleX = (float) width / mWidth;
    300         float scaleY = (float) height / mHeight;
    301         synchronized (mTiles) {
    302             for (int i = 0, n = mTiles.length; i < n; ++i) {
    303                 Tile t = mTiles[i];
    304                 src.set(0, 0, t.contentWidth, t.contentHeight);
    305                 src.offset(t.offsetX, t.offsetY);
    306                 mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
    307                 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
    308                 canvas.drawTexture(t, mSrcRect, mDestRect);
    309             }
    310         }
    311     }
    312 
    313     // Draws a sub region of this texture on to the specified rectangle.
    314     public void draw(GLCanvas canvas, RectF source, RectF target) {
    315         RectF src = mSrcRect;
    316         RectF dest = mDestRect;
    317         float x0 = source.left;
    318         float y0 = source.top;
    319         float x = target.left;
    320         float y = target.top;
    321         float scaleX = target.width() / source.width();
    322         float scaleY = target.height() / source.height();
    323 
    324         synchronized (mTiles) {
    325             for (int i = 0, n = mTiles.length; i < n; ++i) {
    326                 Tile t = mTiles[i];
    327                 src.set(0, 0, t.contentWidth, t.contentHeight);
    328                 src.offset(t.offsetX, t.offsetY);
    329                 if (!src.intersect(source)) continue;
    330                 mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
    331                 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
    332                 canvas.drawTexture(t, src, dest);
    333             }
    334         }
    335     }
    336 
    337     @Override
    338     public int getWidth() {
    339         return mWidth;
    340     }
    341 
    342     @Override
    343     public int getHeight() {
    344         return mHeight;
    345     }
    346 
    347     @Override
    348     public void draw(GLCanvas canvas, int x, int y) {
    349         draw(canvas, x, y, mWidth, mHeight);
    350     }
    351 
    352     @Override
    353     public boolean isOpaque() {
    354         return false;
    355     }
    356 }
    357