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             int x = BORDER_SIZE - offsetX;
    133             int y = BORDER_SIZE - offsetY;
    134             int r = bitmap.getWidth() + x;
    135             int b = bitmap.getHeight() + y;
    136             sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint);
    137             bitmap = null;
    138 
    139             // draw borders if need
    140             if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
    141             if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
    142             if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
    143             if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
    144 
    145             return sUploadBitmap;
    146         }
    147 
    148         @Override
    149         protected void onFreeBitmap(Bitmap bitmap) {
    150             // do nothing
    151         }
    152     }
    153 
    154     private static void freeTile(Tile tile) {
    155         tile.invalidateContent();
    156         tile.bitmap = null;
    157         synchronized (sFreeTileLock) {
    158             tile.nextFreeTile = sFreeTileHead;
    159             sFreeTileHead = tile;
    160         }
    161     }
    162 
    163     private static Tile obtainTile() {
    164         synchronized (sFreeTileLock) {
    165             Tile result = sFreeTileHead;
    166             if (result == null) return new Tile();
    167             sFreeTileHead = result.nextFreeTile;
    168             result.nextFreeTile = null;
    169             return result;
    170         }
    171     }
    172 
    173     private boolean uploadNextTile(GLCanvas canvas) {
    174         if (mUploadIndex == mTiles.length) return true;
    175 
    176         synchronized (mTiles) {
    177             Tile next = mTiles[mUploadIndex++];
    178 
    179             // Make sure tile has not already been recycled by the time
    180             // this is called (race condition in onGLIdle)
    181             if (next.bitmap != null) {
    182                 boolean hasBeenLoad = next.isLoaded();
    183                 next.updateContent(canvas);
    184 
    185                 // It will take some time for a texture to be drawn for the first
    186                 // time. When scrolling, we need to draw several tiles on the screen
    187                 // at the same time. It may cause a UI jank even these textures has
    188                 // been uploaded.
    189                 if (!hasBeenLoad) next.draw(canvas, 0, 0);
    190             }
    191         }
    192         return mUploadIndex == mTiles.length;
    193     }
    194 
    195     public TiledTexture(Bitmap bitmap) {
    196         mWidth = bitmap.getWidth();
    197         mHeight = bitmap.getHeight();
    198         ArrayList<Tile> list = new ArrayList<Tile>();
    199 
    200         for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
    201             for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
    202                 Tile tile = obtainTile();
    203                 tile.offsetX = x;
    204                 tile.offsetY = y;
    205                 tile.bitmap = bitmap;
    206                 tile.setSize(
    207                         Math.min(CONTENT_SIZE, mWidth - x),
    208                         Math.min(CONTENT_SIZE, mHeight - y));
    209                 list.add(tile);
    210             }
    211         }
    212         mTiles = list.toArray(new Tile[list.size()]);
    213     }
    214 
    215     public boolean isReady() {
    216         return mUploadIndex == mTiles.length;
    217     }
    218 
    219     // Can be called in UI thread.
    220     public void recycle() {
    221         synchronized (mTiles) {
    222             for (int i = 0, n = mTiles.length; i < n; ++i) {
    223                 freeTile(mTiles[i]);
    224             }
    225         }
    226     }
    227 
    228     public static void freeResources() {
    229         sUploadBitmap = null;
    230         sCanvas = null;
    231         sBitmapPaint = null;
    232         sPaint = null;
    233     }
    234 
    235     public static void prepareResources() {
    236         sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
    237         sCanvas = new Canvas(sUploadBitmap);
    238         sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    239         sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
    240         sPaint = new Paint();
    241         sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
    242         sPaint.setColor(Color.TRANSPARENT);
    243     }
    244 
    245     // We want to draw the "source" on the "target".
    246     // This method is to find the "output" rectangle which is
    247     // the corresponding area of the "src".
    248     //                                   (x,y)  target
    249     // (x0,y0)  source                     +---------------+
    250     //    +----------+                     |               |
    251     //    | src      |                     | output        |
    252     //    | +--+     |    linear map       | +----+        |
    253     //    | +--+     |    ---------->      | |    |        |
    254     //    |          | by (scaleX, scaleY) | +----+        |
    255     //    +----------+                     |               |
    256     //      Texture                        +---------------+
    257     //                                          Canvas
    258     private static void mapRect(RectF output,
    259             RectF src, float x0, float y0, float x, float y, float scaleX,
    260             float scaleY) {
    261         output.set(x + (src.left - x0) * scaleX,
    262                 y + (src.top - y0) * scaleY,
    263                 x + (src.right - x0) * scaleX,
    264                 y + (src.bottom - y0) * scaleY);
    265     }
    266 
    267     // Draws a mixed color of this texture and a specified color onto the
    268     // a rectangle. The used color is: from * (1 - ratio) + to * ratio.
    269     public void drawMixed(GLCanvas canvas, int color, float ratio,
    270             int x, int y, int width, int height) {
    271         RectF src = mSrcRect;
    272         RectF dest = mDestRect;
    273         float scaleX = (float) width / mWidth;
    274         float scaleY = (float) height / mHeight;
    275         synchronized (mTiles) {
    276             for (int i = 0, n = mTiles.length; i < n; ++i) {
    277                 Tile t = mTiles[i];
    278                 src.set(0, 0, t.contentWidth, t.contentHeight);
    279                 src.offset(t.offsetX, t.offsetY);
    280                 mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
    281                 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
    282                 canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
    283             }
    284         }
    285     }
    286 
    287     // Draws the texture on to the specified rectangle.
    288     @Override
    289     public void draw(GLCanvas canvas, int x, int y, int width, int height) {
    290         RectF src = mSrcRect;
    291         RectF dest = mDestRect;
    292         float scaleX = (float) width / mWidth;
    293         float scaleY = (float) height / mHeight;
    294         synchronized (mTiles) {
    295             for (int i = 0, n = mTiles.length; i < n; ++i) {
    296                 Tile t = mTiles[i];
    297                 src.set(0, 0, t.contentWidth, t.contentHeight);
    298                 src.offset(t.offsetX, t.offsetY);
    299                 mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
    300                 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
    301                 canvas.drawTexture(t, mSrcRect, mDestRect);
    302             }
    303         }
    304     }
    305 
    306     // Draws a sub region of this texture on to the specified rectangle.
    307     public void draw(GLCanvas canvas, RectF source, RectF target) {
    308         RectF src = mSrcRect;
    309         RectF dest = mDestRect;
    310         float x0 = source.left;
    311         float y0 = source.top;
    312         float x = target.left;
    313         float y = target.top;
    314         float scaleX = target.width() / source.width();
    315         float scaleY = target.height() / source.height();
    316 
    317         synchronized (mTiles) {
    318             for (int i = 0, n = mTiles.length; i < n; ++i) {
    319                 Tile t = mTiles[i];
    320                 src.set(0, 0, t.contentWidth, t.contentHeight);
    321                 src.offset(t.offsetX, t.offsetY);
    322                 if (!src.intersect(source)) continue;
    323                 mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
    324                 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
    325                 canvas.drawTexture(t, src, dest);
    326             }
    327         }
    328     }
    329 
    330     @Override
    331     public int getWidth() {
    332         return mWidth;
    333     }
    334 
    335     @Override
    336     public int getHeight() {
    337         return mHeight;
    338     }
    339 
    340     @Override
    341     public void draw(GLCanvas canvas, int x, int y) {
    342         draw(canvas, x, y, mWidth, mHeight);
    343     }
    344 
    345     @Override
    346     public boolean isOpaque() {
    347         return false;
    348     }
    349 }
    350