import com.android.gallery3d.R;
import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.ui.BitmapPool;
+import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.ui.GLRootView;
import com.android.gallery3d.util.ThreadPool;
} finally {
mGLRootView.unlockRenderThread();
}
- BitmapPool.clear();
+ MediaItem.getMicroThumbPool().clear();
}
@Override
import android.os.Handler;
import android.os.Message;
+import android.os.Process;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.ContentListener;
@Override
public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
boolean updateComplete = false;
while (mActive) {
synchronized (this) {
import android.os.Handler;
import android.os.Message;
+import android.os.Process;
import android.os.SystemClock;
import com.android.gallery3d.common.Utils;
@Override
public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
boolean updateComplete = false;
while (mActive) {
synchronized (this) {
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.data.ImageCacheService.ImageData;
-import com.android.gallery3d.ui.BitmapPool;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap;
if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
- bitmap = BitmapPool.decode(jc, BitmapPool.TYPE_MICRO_THUMB,
+ bitmap = MediaItem.getMicroThumbPool().decode(jc,
data.mData, data.mOffset, data.mData.length - data.mOffset, options);
} else {
bitmap = DecodeUtils.decode(jc,
import android.graphics.Bitmap;
import android.graphics.BitmapRegionDecoder;
-import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.ui.BitmapPool;
import com.android.gallery3d.ui.ScreenNail;
+import com.android.gallery3d.util.ThreadPool.Job;
// MediaItem represents an image or a video item.
public abstract class MediaItem extends MediaObject {
public static final String MIME_TYPE_JPEG = "image/jpeg";
+ private static final BitmapPool sMicroThumbPool =
+ new BitmapPool(MICROTHUMBNAIL_TARGET_SIZE, MICROTHUMBNAIL_TARGET_SIZE);
+
// TODO: fix default value for latlng and change this.
public static final double INVALID_LATLNG = 0f;
"should only request thumb/microthumb from cache");
}
}
+
+ public static BitmapPool getMicroThumbPool() {
+ return sMicroThumbPool;
+ }
}
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.TextUtils;
private final TextPaint mCountPaint;
private final Context mContext;
+ private int mLabelWidth;
+ private BitmapPool mBitmapPool;
+
private final LazyLoadedBitmap mLocalSetIcon;
private final LazyLoadedBitmap mPicasaIcon;
private final LazyLoadedBitmap mCameraIcon;
}
}
+ public synchronized void setLabelWidth(int width) {
+ if (mLabelWidth == width) return;
+ mLabelWidth = width;
+ mBitmapPool = new BitmapPool(mLabelWidth, mSpec.labelBackgroundHeight);
+ }
+
public ThreadPool.Job<Bitmap> requestLabel(
- String title, String count, int sourceType, int slotWidth) {
- return new AlbumLabelJob(null, title, count, sourceType, slotWidth);
+ String title, String count, int sourceType) {
+ return new AlbumLabelJob(null, title, count, sourceType);
}
public ThreadPool.Job<Bitmap> requestLabel(
- MediaSet album, int sourceType, int slotWidth) {
- return new AlbumLabelJob(album, null, null, sourceType, slotWidth);
+ MediaSet album, int sourceType) {
+ return new AlbumLabelJob(album, null, null, sourceType);
}
private static void drawText(Canvas canvas,
private final String mTitle;
private final String mCount;
private final int mSourceType;
- private final int mSlotWidth;
public AlbumLabelJob(MediaSet album,
- String title, String count, int sourceType, int slotWidth) {
+ String title, String count, int sourceType) {
mAlbum = album;
mTitle = title;
mCount = count;
mSourceType = sourceType;
- mSlotWidth = slotWidth;
}
@Override
? Utils.ensureNotNull(mCount)
: String.valueOf(album.getTotalMediaItemCount());
Bitmap icon = getOverlayAlbumIcon(mSourceType);
- Bitmap bitmap = Bitmap.createBitmap(mSlotWidth,
- s.labelBackgroundHeight, Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
+
+ Bitmap bitmap = null;
+ Canvas canvas;
+ int labelWidth;
+
+ synchronized (this) {
+ labelWidth = mLabelWidth;
+ bitmap = mBitmapPool.getBitmap();
+ }
+ if (bitmap == null) {
+ bitmap = Bitmap.createBitmap(labelWidth,
+ s.labelBackgroundHeight, Config.ARGB_8888);
+ canvas = new Canvas(bitmap);
+ } else {
+ canvas = new Canvas(bitmap);
+ canvas.drawColor(0, PorterDuff.Mode.SRC);
+ }
// draw title
if (jc.isCancelled()) return null;
int x = s.leftMargin;
int y = s.titleOffset;
- drawText(canvas, x, y, title, mSlotWidth - s.leftMargin, mTitlePaint);
+ drawText(canvas, x, y, title, labelWidth - s.leftMargin, mTitlePaint);
// draw the count
if (jc.isCancelled()) return null;
if (icon != null) x = s.iconSize;
y += s.titleFontSize + s.countOffset;
drawText(canvas, x, y, count,
- mSlotWidth - s.leftMargin - s.iconSize, mCountPaint);
+ labelWidth - s.leftMargin - s.iconSize, mCountPaint);
// draw the icon
if (icon != null) {
return bitmap;
}
}
+
+ public void reycleLabel(Bitmap label) {
+ mBitmapPool.recycle(label);
+ }
+
+ public void clearRecycledLabels() {
+ mBitmapPool.clear();
+ }
}
private final ThreadPool mThreadPool;
private final AlbumLabelMaker mLabelMaker;
private final String mLoadingText;
+ private final TextureUploader mTextureUploader;
private int mActiveRequestCount = 0;
private boolean mIsActive = false;
mLabelMaker = new AlbumLabelMaker(activity.getAndroidContext(), labelSpec);
mLoadingText = activity.getAndroidContext().getString(R.string.loading);
+ mTextureUploader = new TextureUploader(activity.getGLRoot());
mHandler = new SynchronizedHandler(activity.getGLRoot()) {
@Override
0, Math.max(0, mSize - data.length));
int contentEnd = Math.min(contentStart + data.length, mSize);
setContentWindow(contentStart, contentEnd);
- if (mIsActive) updateAllImageRequests();
+
+ if (mIsActive) {
+ updateTextureUploadQueue();
+ updateAllImageRequests();
+ }
}
// We would like to request non active slots in the following order:
entry.label = null;
}
if (album != null) {
- entry.labelLoader = new AlbumLabelLoader(
- slotIndex, album, entry.sourceType, mSlotWidth);
+ entry.labelLoader =
+ new AlbumLabelLoader(slotIndex, album, entry.sourceType);
}
entry.coverItem = cover;
return loader.isRequestInProgress();
}
- private void notifySlotChanged(int slotIndex) {
- // If the updated content is not cached, ignore it
- if (slotIndex < mContentStart || slotIndex >= mContentEnd) {
- Log.w(TAG, String.format(
- "invalid update: %s is outside (%s, %s)",
- slotIndex, mContentStart, mContentEnd) );
- return;
+ private void queueTextureForUpload(boolean isActive, Texture texture) {
+ if ((texture == null) || !(texture instanceof BitmapTexture)) return;
+ if (isActive) {
+ mTextureUploader.addFgTexture((BitmapTexture) texture);
+ } else {
+ mTextureUploader.addBgTexture((BitmapTexture) texture);
}
+ }
- AlbumSetEntry entry = mData[slotIndex % mData.length];
- MediaSet set = mSource.getMediaSet(slotIndex);
- MediaItem coverItem = mSource.getCoverItem(slotIndex);
- updateAlbumSetEntry(entry, slotIndex, set, coverItem);
- updateAllImageRequests();
- if (mListener != null && isActiveSlot(slotIndex)) {
- mListener.onContentChanged();
+ private void updateTextureUploadQueue() {
+ if (!mIsActive) return;
+ mTextureUploader.clear();
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ AlbumSetEntry entry = mData[i % mData.length];
+ boolean isActive = isActiveSlot(i);
+ queueTextureForUpload(isActive, entry.label);
+ queueTextureForUpload(isActive, entry.content);
}
}
// paused, ignore slot changed event
return;
}
- notifySlotChanged(index);
+
+ // If the updated content is not cached, ignore it
+ if (index < mContentStart || index >= mContentEnd) {
+ Log.w(TAG, String.format(
+ "invalid update: %s is outside (%s, %s)",
+ index, mContentStart, mContentEnd) );
+ return;
+ }
+
+ AlbumSetEntry entry = mData[index % mData.length];
+ MediaSet set = mSource.getMediaSet(index);
+ MediaItem coverItem = mSource.getCoverItem(index);
+ updateAlbumSetEntry(entry, index, set, coverItem);
+ updateAllImageRequests();
+ updateTextureUploadQueue();
+ if (mListener != null && isActiveSlot(index)) {
+ mListener.onContentChanged();
+ }
}
public BitmapTexture getLoadingTexture() {
if (mLoadingLabel == null) {
Bitmap bitmap = mLabelMaker.requestLabel(mLoadingText, null,
- SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED, mSlotWidth)
+ SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED)
.run(ThreadPool.JOB_CONTEXT_STUB);
mLoadingLabel = new BitmapTexture(bitmap);
mLoadingLabel.setOpaque(false);
for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
freeSlotContent(i);
}
+ mLabelMaker.clearRecycledLabels();
}
public void resume() {
@Override
protected void recycleBitmap(Bitmap bitmap) {
- BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap);
+ MediaItem.getMicroThumbPool().recycle(bitmap);
}
@Override
@Override
public void updateEntry() {
Bitmap bitmap = getBitmap();
- if (bitmap == null) return;
+ if (bitmap == null) return; // error or recycled
AlbumSetEntry entry = mData[mSlotIndex % mData.length];
- BitmapTexture content = new BitmapTexture(bitmap);
- entry.content = content;
+ BitmapTexture texture = new BitmapTexture(bitmap);
+ entry.content = texture;
if (isActiveSlot(mSlotIndex)) {
+ mTextureUploader.addFgTexture(texture);
--mActiveRequestCount;
if (mActiveRequestCount == 0) requestNonactiveImages();
if (mListener != null) mListener.onContentChanged();
+ } else {
+ mTextureUploader.addBgTexture(texture);
}
}
}
private final MediaSet mMediaSet;
private final int mSlotIndex;
private final int mSourceType;
- private final int mLabelWidth;
- public AlbumLabelLoader(int slotIndex,
- MediaSet mediaSet, int sourceType, int labelWidth) {
+ public AlbumLabelLoader(
+ int slotIndex, MediaSet mediaSet, int sourceType) {
mSlotIndex = slotIndex;
mMediaSet = mediaSet;
mSourceType = sourceType;
- mLabelWidth = labelWidth;
}
@Override
protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
return mThreadPool.submit(mLabelMaker.requestLabel(
- mMediaSet, mSourceType, mLabelWidth), l);
+ mMediaSet, mSourceType), l);
}
@Override
protected void recycleBitmap(Bitmap bitmap) {
+ mLabelMaker.reycleLabel(bitmap);
}
@Override
@Override
public void updateEntry() {
- if (isRecycled()) return;
-
Bitmap bitmap = getBitmap();
- if (bitmap == null) return;
+ if (bitmap == null) return; // Error or recycled
AlbumSetEntry entry = mData[mSlotIndex % mData.length];
- BitmapTexture content = new BitmapTexture(bitmap);
- content.setOpaque(false);
- entry.label = content;
+ BitmapTexture texture = new BitmapTexture(bitmap);
+ texture.setOpaque(false);
+ entry.label = texture;
if (isActiveSlot(mSlotIndex)) {
+ mTextureUploader.addFgTexture(texture);
--mActiveRequestCount;
if (mActiveRequestCount == 0) requestNonactiveImages();
if (mListener != null) mListener.onContentChanged();
+ } else {
+ mTextureUploader.addBgTexture(texture);
}
}
}
public void onSlotSizeChanged(int width, int height) {
if (mSlotWidth == width) return;
+
mSlotWidth = width;
mLoadingLabel = null;
+ mLabelMaker.setLabelWidth(mSlotWidth);
+
+ if (!mIsActive) return;
for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
AlbumSetEntry entry = mData[i % mData.length];
}
entry.labelLoader = (entry.album == null)
? null
- : new AlbumLabelLoader(i, entry.album, entry.sourceType, width);
+ : new AlbumLabelLoader(i, entry.album, entry.sourceType);
}
updateAllImageRequests();
+ updateTextureUploadQueue();
}
}
}
}
+ private static Texture checkTexture(GLCanvas canvas, Texture texture) {
+ return ((texture == null) || ((texture instanceof UploadedTexture)
+ && !((UploadedTexture) texture).isContentValid(canvas)))
+ ? null
+ : texture;
+ }
+
@Override
public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) {
AlbumSetEntry entry = mDataWindow.get(index);
private int renderContent(GLCanvas canvas,
int pass, AlbumSetEntry entry, int width, int height) {
- // Fit the content into the box
- Texture content = entry.content;
+ Texture content = checkTexture(canvas, entry.content);
if (content == null) {
content = mWaitLoadingTexture;
content = entry.content;
}
+ // Fit the content into the box
int w = content.getWidth();
int h = content.getHeight();
// We show the loading message only when the album is still loading
// (Not when we are still preparing the label)
- Texture content = entry.label;
+ Texture content = checkTexture(canvas, entry.label);
if (entry.album == null) {
content = mDataWindow.getLoadingTexture();
}
}
private final AlbumView.Model mSource;
+ private final AlbumEntry mData[];
+ private final SynchronizedHandler mHandler;
+ private final JobLimiter mThreadPool;
+ private final TextureUploader mTextureUploader;
+
private int mSize;
private int mContentStart = 0;
private Listener mListener;
- private final AlbumEntry mData[];
-
- private SynchronizedHandler mHandler;
- private JobLimiter mThreadPool;
-
private int mActiveRequestCount = 0;
private boolean mIsActive = false;
};
mThreadPool = new JobLimiter(activity.getThreadPool(), JOB_LIMIT);
+ mTextureUploader = new TextureUploader(activity.getGLRoot());
}
public void setListener(Listener listener) {
0, Math.max(0, mSize - data.length));
int contentEnd = Math.min(contentStart + data.length, mSize);
setContentWindow(contentStart, contentEnd);
+ updateUploadedTextures();
if (mIsActive) updateAllImageRequests();
}
+ private void uploadTexture(boolean isActive, Texture texture) {
+ if ((texture == null) || !(texture instanceof BitmapTexture)) return;
+ if (isActive) {
+ mTextureUploader.addFgTexture((BitmapTexture) texture);
+ } else {
+ mTextureUploader.addBgTexture((BitmapTexture) texture);
+ }
+ }
+
+ private void updateUploadedTextures() {
+ if (!mIsActive) return;
+ mTextureUploader.clear();
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ AlbumEntry entry = mData[i % mData.length];
+ boolean isActive = isActiveSlot(i);
+ uploadTexture(isActive, entry.content);
+ }
+ }
+
// We would like to request non active slots in the following order:
// Order: 8 6 4 2 1 3 5 7
// |---------|---------------|---------|
}
private class ThumbnailLoader extends BitmapLoader {
- final int mSlotIndex;
- final MediaItem mItem;
+ private final int mSlotIndex;
+ private final MediaItem mItem;
public ThumbnailLoader(int slotIndex, MediaItem item) {
mSlotIndex = slotIndex;
@Override
protected void recycleBitmap(Bitmap bitmap) {
- BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap);
+ MediaItem.getMicroThumbPool().recycle(bitmap);
}
@Override
}
public void updateEntry() {
- if (isRecycled()) return;
+ Bitmap bitmap = getBitmap();
+ if (bitmap == null) return; // error or recycled
AlbumEntry entry = mData[mSlotIndex % mData.length];
- Bitmap bitmap = entry.contentLoader.getBitmap();
- if (bitmap != null) entry.content = new BitmapTexture(bitmap);
+ entry.content = new BitmapTexture(bitmap);
if (isActiveSlot(mSlotIndex)) {
+ mTextureUploader.addFgTexture((BitmapTexture) entry.content);
--mActiveRequestCount;
if (mActiveRequestCount == 0) requestNonactiveImages();
if (mListener != null) mListener.onContentChanged();
+ } else {
+ mTextureUploader.addBgTexture((BitmapTexture) entry.content);
}
}
}
mSlotView.invalidate();
}
+ private static Texture checkTexture(GLCanvas canvas, Texture texture) {
+ return ((texture == null) || ((texture instanceof UploadedTexture)
+ && !((UploadedTexture) texture).isContentValid(canvas)))
+ ? null
+ : texture;
+ }
+
@Override
public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) {
AlbumSlidingWindow.AlbumEntry entry = mDataWindow.get(index);
- Texture content = entry.content;
+ Texture content = checkTexture(canvas, entry.content);
if (content == null) {
content = mWaitLoadingTexture;
import android.graphics.BitmapFactory.Options;
import com.android.gallery3d.data.DecodeUtils;
-import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.util.ThreadPool.JobContext;
import java.io.FileDescriptor;
public class BitmapPool {
private static final String TAG = "BitmapPool";
- public static final int TYPE_MICRO_THUMB = 0;
- private static final int TYPE_COUNT = 1;
private static final int POOL_SIZE = 16;
- private static final int EXPECTED_WIDTH[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE};
- private static final int EXPECTED_HEIGHT[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE};
+ private final ArrayList<Bitmap> mPool = new ArrayList<Bitmap>(POOL_SIZE);
- @SuppressWarnings("unchecked")
- private static final ArrayList<Bitmap> sPools[] = new ArrayList[TYPE_COUNT];
- static {
- for (int i = 0; i < TYPE_COUNT; ++i) {
- sPools[i] = new ArrayList<Bitmap>();
- }
- }
+ private final int mWidth;
+ private final int mHeight;
- private BitmapPool() {
+ public BitmapPool(int width, int height) {
+ mWidth = width;
+ mHeight = height;
}
- public static Bitmap getBitmap(int type) {
- ArrayList<Bitmap> list = sPools[type];
- synchronized (list) {
- int size = list.size();
- return size > 0 ? list.remove(size - 1) : null;
- }
+ public synchronized Bitmap getBitmap() {
+ int size = mPool.size();
+ return size > 0 ? mPool.remove(size - 1) : null;
}
- public static void recycle(int type, Bitmap bitmap) {
+ public void recycle(Bitmap bitmap) {
if (bitmap == null) return;
- if ((bitmap.getWidth() != EXPECTED_WIDTH[type])
- || (bitmap.getHeight() != EXPECTED_HEIGHT[type])) {
+ if ((bitmap.getWidth() != mWidth) || (bitmap.getHeight() != mHeight)) {
bitmap.recycle();
return;
}
- ArrayList<Bitmap> list = sPools[type];
- synchronized (list) {
- if (list.size() < POOL_SIZE) list.add(bitmap);
+ synchronized (this) {
+ if (mPool.size() < POOL_SIZE) mPool.add(bitmap);
}
}
- public static void clear() {
- for (int i = 0; i < TYPE_COUNT; ++i) {
- ArrayList<Bitmap> list = sPools[i];
- synchronized (list) {
- list.clear();
- }
- }
+ public synchronized void clear() {
+ mPool.clear();
}
- public static Bitmap decode(JobContext jc, int type,
+ public Bitmap decode(JobContext jc,
byte[] data, int offset, int length, BitmapFactory.Options options) {
if (options == null) options = new BitmapFactory.Options();
if (options.inSampleSize < 1) options.inSampleSize = 1;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- options.inBitmap = (options.inSampleSize == 1) ? getBitmap(type) : null;
+ options.inBitmap = (options.inSampleSize == 1) ? getBitmap() : null;
try {
Bitmap bitmap = DecodeUtils.decode(jc, data, offset, length, options);
if (options.inBitmap != null && options.inBitmap != bitmap) {
- recycle(type, bitmap);
+ recycle(options.inBitmap);
options.inBitmap = null;
}
return bitmap;
if (options.inBitmap == null) throw e;
Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
- recycle(type, options.inBitmap);
+ recycle(options.inBitmap);
options.inBitmap = null;
return DecodeUtils.decode(jc, data, offset, length, options);
}
// This is the same as the method above except the source data comes
// from a file descriptor instead of a byte array.
- public static Bitmap decode(int type,
- JobContext jc, FileDescriptor fileDescriptor, Options options) {
+ public Bitmap decode(JobContext jc,
+ FileDescriptor fileDescriptor, Options options) {
if (options == null) options = new BitmapFactory.Options();
if (options.inSampleSize < 1) options.inSampleSize = 1;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- options.inBitmap = (options.inSampleSize == 1) ? getBitmap(type) : null;
+ options.inBitmap = (options.inSampleSize == 1) ? getBitmap() : null;
try {
Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
if (options.inBitmap != null&& options.inBitmap != bitmap) {
- recycle(type, bitmap);
+ recycle(options.inBitmap);
options.inBitmap = null;
}
return bitmap;
if (options.inBitmap == null) throw e;
Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
- recycle(type, options.inBitmap);
+ recycle(options.inBitmap);
options.inBitmap = null;
return DecodeUtils.decode(jc, fileDescriptor, options);
}
public interface GLRoot {
+ // Listener will be called when GL is idle AND before each frame.
+ // Mainly used for uploading textures.
public static interface OnGLIdleListener {
- public boolean onGLIdle(GLRoot root, GLCanvas canvas);
+ public boolean onGLIdle(
+ GLCanvas canvas, boolean renderRequested);
}
public void addOnGLIdleListener(OnGLIdleListener listener);
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.Profile;
+import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.egl.EGLConfig;
private final ArrayList<CanvasAnimation> mAnimations =
new ArrayList<CanvasAnimation>();
- private final LinkedList<OnGLIdleListener> mIdleListeners =
- new LinkedList<OnGLIdleListener>();
+ private final ArrayDeque<OnGLIdleListener> mIdleListeners =
+ new ArrayDeque<OnGLIdleListener>();
private final IdleRunner mIdleRunner = new IdleRunner();
} finally {
mRenderLock.unlock();
}
+
if (DEBUG_PROFILE_SLOW_ONLY) {
long t = System.nanoTime();
long durationInMs = (t - mLastDrawFinishTime) / 1000000;
gl.glScissor(clip.left, clip.top, clip.width(), clip.height());
}
-
if (mContentView != null) {
mContentView.render(mCanvas);
}
}
synchronized (mIdleListeners) {
- if (!mRenderRequested && !mIdleListeners.isEmpty()) {
- mIdleRunner.enable();
- }
+ if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
}
if (DEBUG_INVALIDATE) {
OnGLIdleListener listener;
synchronized (mIdleListeners) {
mActive = false;
- if (mRenderRequested) return;
if (mIdleListeners.isEmpty()) return;
listener = mIdleListeners.removeFirst();
}
mRenderLock.lock();
try {
- if (!listener.onGLIdle(GLRootView.this, mCanvas)) return;
+ if (!listener.onGLIdle(mCanvas, mRenderRequested)) return;
} finally {
mRenderLock.unlock();
}
synchronized (mIdleListeners) {
mIdleListeners.addLast(listener);
- enable();
+ if (!mRenderRequested) enable();
}
}
public static final int OVERSCROLL_SYSTEM = 1;
public static final int OVERSCROLL_NONE = 2;
+ // to prevent allocating memory
+ private final Rect mTempRect = new Rect();
+
public SlotView(Context context, Spec spec) {
mGestureDetector =
new GestureDetector(context, new MyGestureListener());
if (index < 0 || index >= slotCount) {
return;
}
- Rect rect = mLayout.getSlotRect(index);
+ Rect rect = mLayout.getSlotRect(index, mTempRect);
int position = WIDE
? (rect.left + rect.right - getWidth()) / 2
: (rect.top + rect.bottom - getHeight()) / 2;
}
public void makeSlotVisible(int index) {
- Rect rect = mLayout.getSlotRect(index);
+ Rect rect = mLayout.getSlotRect(index, mTempRect);
int visibleBegin = WIDE ? mScrollX : mScrollY;
int visibleLength = WIDE ? getWidth() : getHeight();
int visibleEnd = visibleBegin + visibleLength;
}
public Rect getSlotRect(int slotIndex) {
- return mLayout.getSlotRect(slotIndex);
+ return mLayout.getSlotRect(slotIndex, new Rect());
}
@Override
private int renderItem(
GLCanvas canvas, int index, int pass, boolean paperActive) {
canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
- Rect rect = getSlotRect(index);
+ Rect rect = mLayout.getSlotRect(index, mTempRect);
if (paperActive) {
canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0);
} else {
return vPadding != mVerticalPadding || hPadding != mHorizontalPadding;
}
- public Rect getSlotRect(int index) {
+ public Rect getSlotRect(int index, Rect rect) {
int col, row;
if (WIDE) {
col = index / mUnitCount;
int x = mHorizontalPadding + col * (mSlotWidth + mSlotGap);
int y = mVerticalPadding + row * (mSlotHeight + mSlotGap);
- return new Rect(x, y, x + mSlotWidth, y + mSlotHeight);
+ rect.set(x, y, x + mSlotWidth, y + mSlotHeight);
+ return rect;
}
public int getSlotWidth() {
--- /dev/null
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
+
+import java.util.ArrayDeque;
+
+public class TextureUploader implements OnGLIdleListener {
+ private static final int INIT_CAPACITY = 64;
+ private static final int QUOTA_PER_FRAME = 1;
+
+ private final ArrayDeque<UploadedTexture> mFgTextures =
+ new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+ private final ArrayDeque<UploadedTexture> mBgTextures =
+ new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+ private final GLRoot mGLRoot;
+ private transient boolean mIsQueued = false;
+
+ public TextureUploader(GLRoot root) {
+ mGLRoot = root;
+ }
+
+ public synchronized void clear() {
+ mFgTextures.clear();
+ mBgTextures.clear();
+ }
+
+ // caller should hold synchronized on "this"
+ private void queueSelfIfNeed() {
+ if (mIsQueued) return;
+ mIsQueued = true;
+ mGLRoot.addOnGLIdleListener(this);
+ }
+
+ public synchronized void addBgTexture(UploadedTexture t) {
+ mBgTextures.addLast(t);
+ queueSelfIfNeed();
+ }
+
+ public synchronized void addFgTexture(UploadedTexture t) {
+ mFgTextures.addLast(t);
+ queueSelfIfNeed();
+ }
+
+ private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
+ int uploadQuota, boolean isBackground) {
+ while (uploadQuota > 0) {
+ UploadedTexture t;
+ synchronized (this) {
+ if (deque.isEmpty()) break;
+ t = deque.removeFirst();
+ }
+ if (!t.isContentValid(canvas)) {
+ t.updateContent(canvas);
+
+ // It will took some more time for a texture to be drawn for
+ // the first time.
+ // Thus, when scrolling, if a new column appears on screen,
+ // it may cause a UI jank even these textures are uploaded.
+ if (isBackground) t.draw(canvas, 0, 0);
+ --uploadQuota;
+ }
+ }
+ return uploadQuota;
+ }
+
+ @Override
+ public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+ int uploadQuota = QUOTA_PER_FRAME;
+ uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
+ if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
+
+ // don't upload background texture if there is pending render request
+ if (!renderRequested) {
+ upload(canvas, mBgTextures, uploadQuota, true);
+ }
+ synchronized (this) {
+ mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
+ return mIsQueued;
+ }
+ }
+}
AtomicBoolean mActive = new AtomicBoolean(false);
@Override
- public boolean onGLIdle(GLRoot root, GLCanvas canvas) {
+ public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+ if (renderRequested) return false;
int quota = UPLOAD_LIMIT;
Tile tile;
while (true) {