summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ui/GLRootView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/ui/GLRootView.java')
-rw-r--r--src/com/android/gallery3d/ui/GLRootView.java630
1 files changed, 630 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java
new file mode 100644
index 000000000..dc898d83d
--- /dev/null
+++ b/src/com/android/gallery3d/ui/GLRootView.java
@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2010 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 android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView;
+import android.os.Build;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.GLES11Canvas;
+import com.android.gallery3d.glrenderer.GLES20Canvas;
+import com.android.gallery3d.glrenderer.UploadedTexture;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.MotionEventHelper;
+import com.android.gallery3d.util.Profile;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+
+// The root component of all <code>GLView</code>s. The rendering is done in GL
+// thread while the event handling is done in the main thread. To synchronize
+// the two threads, the entry points of this package need to synchronize on the
+// <code>GLRootView</code> instance unless it can be proved that the rendering
+// thread won't access the same thing as the method. The entry points include:
+// (1) The public methods of HeadUpDisplay
+// (2) The public methods of CameraHeadUpDisplay
+// (3) The overridden methods in GLRootView.
+public class GLRootView extends GLSurfaceView
+ implements GLSurfaceView.Renderer, GLRoot {
+ private static final String TAG = "GLRootView";
+
+ private static final boolean DEBUG_FPS = false;
+ private int mFrameCount = 0;
+ private long mFrameCountingStart = 0;
+
+ private static final boolean DEBUG_INVALIDATE = false;
+ private int mInvalidateColor = 0;
+
+ private static final boolean DEBUG_DRAWING_STAT = false;
+
+ private static final boolean DEBUG_PROFILE = false;
+ private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
+
+ private static final int FLAG_INITIALIZED = 1;
+ private static final int FLAG_NEED_LAYOUT = 2;
+
+ private GL11 mGL;
+ private GLCanvas mCanvas;
+ private GLView mContentView;
+
+ private OrientationSource mOrientationSource;
+ // mCompensation is the difference between the UI orientation on GLCanvas
+ // and the framework orientation. See OrientationManager for details.
+ private int mCompensation;
+ // mCompensationMatrix maps the coordinates of touch events. It is kept sync
+ // with mCompensation.
+ private Matrix mCompensationMatrix = new Matrix();
+ private int mDisplayRotation;
+
+ private int mFlags = FLAG_NEED_LAYOUT;
+ private volatile boolean mRenderRequested = false;
+
+ private final ArrayList<CanvasAnimation> mAnimations =
+ new ArrayList<CanvasAnimation>();
+
+ private final ArrayDeque<OnGLIdleListener> mIdleListeners =
+ new ArrayDeque<OnGLIdleListener>();
+
+ private final IdleRunner mIdleRunner = new IdleRunner();
+
+ private final ReentrantLock mRenderLock = new ReentrantLock();
+ private final Condition mFreezeCondition =
+ mRenderLock.newCondition();
+ private boolean mFreeze;
+
+ private long mLastDrawFinishTime;
+ private boolean mInDownState = false;
+ private boolean mFirstDraw = true;
+
+ public GLRootView(Context context) {
+ this(context, null);
+ }
+
+ public GLRootView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mFlags |= FLAG_INITIALIZED;
+ setBackgroundDrawable(null);
+ setEGLContextClientVersion(ApiHelper.HAS_GLES20_REQUIRED ? 2 : 1);
+ if (ApiHelper.USE_888_PIXEL_FORMAT) {
+ setEGLConfigChooser(8, 8, 8, 0, 0, 0);
+ } else {
+ setEGLConfigChooser(5, 6, 5, 0, 0, 0);
+ }
+ setRenderer(this);
+ if (ApiHelper.USE_888_PIXEL_FORMAT) {
+ getHolder().setFormat(PixelFormat.RGB_888);
+ } else {
+ getHolder().setFormat(PixelFormat.RGB_565);
+ }
+
+ // Uncomment this to enable gl error check.
+ // setDebugFlags(DEBUG_CHECK_GL_ERROR);
+ }
+
+ @Override
+ public void registerLaunchedAnimation(CanvasAnimation animation) {
+ // Register the newly launched animation so that we can set the start
+ // time more precisely. (Usually, it takes much longer for first
+ // rendering, so we set the animation start time as the time we
+ // complete rendering)
+ mAnimations.add(animation);
+ }
+
+ @Override
+ public void addOnGLIdleListener(OnGLIdleListener listener) {
+ synchronized (mIdleListeners) {
+ mIdleListeners.addLast(listener);
+ mIdleRunner.enable();
+ }
+ }
+
+ @Override
+ public void setContentPane(GLView content) {
+ if (mContentView == content) return;
+ if (mContentView != null) {
+ if (mInDownState) {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent cancelEvent = MotionEvent.obtain(
+ now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ mContentView.dispatchTouchEvent(cancelEvent);
+ cancelEvent.recycle();
+ mInDownState = false;
+ }
+ mContentView.detachFromRoot();
+ BasicTexture.yieldAllTextures();
+ }
+ mContentView = content;
+ if (content != null) {
+ content.attachToRoot(this);
+ requestLayoutContentPane();
+ }
+ }
+
+ @Override
+ public void requestRenderForced() {
+ superRequestRender();
+ }
+
+ @Override
+ public void requestRender() {
+ if (DEBUG_INVALIDATE) {
+ StackTraceElement e = Thread.currentThread().getStackTrace()[4];
+ String caller = e.getFileName() + ":" + e.getLineNumber() + " ";
+ Log.d(TAG, "invalidate: " + caller);
+ }
+ if (mRenderRequested) return;
+ mRenderRequested = true;
+ if (ApiHelper.HAS_POST_ON_ANIMATION) {
+ postOnAnimation(mRequestRenderOnAnimationFrame);
+ } else {
+ super.requestRender();
+ }
+ }
+
+ private Runnable mRequestRenderOnAnimationFrame = new Runnable() {
+ @Override
+ public void run() {
+ superRequestRender();
+ }
+ };
+
+ private void superRequestRender() {
+ super.requestRender();
+ }
+
+ @Override
+ public void requestLayoutContentPane() {
+ mRenderLock.lock();
+ try {
+ if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
+
+ // "View" system will invoke onLayout() for initialization(bug ?), we
+ // have to ignore it since the GLThread is not ready yet.
+ if ((mFlags & FLAG_INITIALIZED) == 0) return;
+
+ mFlags |= FLAG_NEED_LAYOUT;
+ requestRender();
+ } finally {
+ mRenderLock.unlock();
+ }
+ }
+
+ private void layoutContentPane() {
+ mFlags &= ~FLAG_NEED_LAYOUT;
+
+ int w = getWidth();
+ int h = getHeight();
+ int displayRotation = 0;
+ int compensation = 0;
+
+ // Get the new orientation values
+ if (mOrientationSource != null) {
+ displayRotation = mOrientationSource.getDisplayRotation();
+ compensation = mOrientationSource.getCompensation();
+ } else {
+ displayRotation = 0;
+ compensation = 0;
+ }
+
+ if (mCompensation != compensation) {
+ mCompensation = compensation;
+ if (mCompensation % 180 != 0) {
+ mCompensationMatrix.setRotate(mCompensation);
+ // move center to origin before rotation
+ mCompensationMatrix.preTranslate(-w / 2, -h / 2);
+ // align with the new origin after rotation
+ mCompensationMatrix.postTranslate(h / 2, w / 2);
+ } else {
+ mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
+ }
+ }
+ mDisplayRotation = displayRotation;
+
+ // Do the actual layout.
+ if (mCompensation % 180 != 0) {
+ int tmp = w;
+ w = h;
+ h = tmp;
+ }
+ Log.i(TAG, "layout content pane " + w + "x" + h
+ + " (compensation " + mCompensation + ")");
+ if (mContentView != null && w != 0 && h != 0) {
+ mContentView.layout(0, 0, w, h);
+ }
+ // Uncomment this to dump the view hierarchy.
+ //mContentView.dumpTree("");
+ }
+
+ @Override
+ protected void onLayout(
+ boolean changed, int left, int top, int right, int bottom) {
+ if (changed) requestLayoutContentPane();
+ }
+
+ /**
+ * Called when the context is created, possibly after automatic destruction.
+ */
+ // This is a GLSurfaceView.Renderer callback
+ @Override
+ public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
+ GL11 gl = (GL11) gl1;
+ if (mGL != null) {
+ // The GL Object has changed
+ Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
+ }
+ mRenderLock.lock();
+ try {
+ mGL = gl;
+ mCanvas = ApiHelper.HAS_GLES20_REQUIRED ? new GLES20Canvas() : new GLES11Canvas(gl);
+ BasicTexture.invalidateAllTextures();
+ } finally {
+ mRenderLock.unlock();
+ }
+
+ if (DEBUG_FPS || DEBUG_PROFILE) {
+ setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ } else {
+ setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ }
+ }
+
+ /**
+ * Called when the OpenGL surface is recreated without destroying the
+ * context.
+ */
+ // This is a GLSurfaceView.Renderer callback
+ @Override
+ public void onSurfaceChanged(GL10 gl1, int width, int height) {
+ Log.i(TAG, "onSurfaceChanged: " + width + "x" + height
+ + ", gl10: " + gl1.toString());
+ Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
+ GalleryUtils.setRenderThread();
+ if (DEBUG_PROFILE) {
+ Log.d(TAG, "Start profiling");
+ Profile.enable(20); // take a sample every 20ms
+ }
+ GL11 gl = (GL11) gl1;
+ Utils.assertTrue(mGL == gl);
+
+ mCanvas.setSize(width, height);
+ }
+
+ private void outputFps() {
+ long now = System.nanoTime();
+ if (mFrameCountingStart == 0) {
+ mFrameCountingStart = now;
+ } else if ((now - mFrameCountingStart) > 1000000000) {
+ Log.d(TAG, "fps: " + (double) mFrameCount
+ * 1000000000 / (now - mFrameCountingStart));
+ mFrameCountingStart = now;
+ mFrameCount = 0;
+ }
+ ++mFrameCount;
+ }
+
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ AnimationTime.update();
+ long t0;
+ if (DEBUG_PROFILE_SLOW_ONLY) {
+ Profile.hold();
+ t0 = System.nanoTime();
+ }
+ mRenderLock.lock();
+
+ while (mFreeze) {
+ mFreezeCondition.awaitUninterruptibly();
+ }
+
+ try {
+ onDrawFrameLocked(gl);
+ } finally {
+ mRenderLock.unlock();
+ }
+
+ // We put a black cover View in front of the SurfaceView and hide it
+ // after the first draw. This prevents the SurfaceView being transparent
+ // before the first draw.
+ if (mFirstDraw) {
+ mFirstDraw = false;
+ post(new Runnable() {
+ @Override
+ public void run() {
+ View root = getRootView();
+ View cover = root.findViewById(R.id.gl_root_cover);
+ cover.setVisibility(GONE);
+ }
+ });
+ }
+
+ if (DEBUG_PROFILE_SLOW_ONLY) {
+ long t = System.nanoTime();
+ long durationInMs = (t - mLastDrawFinishTime) / 1000000;
+ long durationDrawInMs = (t - t0) / 1000000;
+ mLastDrawFinishTime = t;
+
+ if (durationInMs > 34) { // 34ms -> we skipped at least 2 frames
+ Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
+ durationInMs + ") -----");
+ Profile.commit();
+ } else {
+ Profile.drop();
+ }
+ }
+ }
+
+ private void onDrawFrameLocked(GL10 gl) {
+ if (DEBUG_FPS) outputFps();
+
+ // release the unbound textures and deleted buffers.
+ mCanvas.deleteRecycledResources();
+
+ // reset texture upload limit
+ UploadedTexture.resetUploadLimit();
+
+ mRenderRequested = false;
+
+ if ((mOrientationSource != null
+ && mDisplayRotation != mOrientationSource.getDisplayRotation())
+ || (mFlags & FLAG_NEED_LAYOUT) != 0) {
+ layoutContentPane();
+ }
+
+ mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
+ rotateCanvas(-mCompensation);
+ if (mContentView != null) {
+ mContentView.render(mCanvas);
+ } else {
+ // Make sure we always draw something to prevent displaying garbage
+ mCanvas.clearBuffer();
+ }
+ mCanvas.restore();
+
+ if (!mAnimations.isEmpty()) {
+ long now = AnimationTime.get();
+ for (int i = 0, n = mAnimations.size(); i < n; i++) {
+ mAnimations.get(i).setStartTime(now);
+ }
+ mAnimations.clear();
+ }
+
+ if (UploadedTexture.uploadLimitReached()) {
+ requestRender();
+ }
+
+ synchronized (mIdleListeners) {
+ if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
+ }
+
+ if (DEBUG_INVALIDATE) {
+ mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
+ mInvalidateColor = ~mInvalidateColor;
+ }
+
+ if (DEBUG_DRAWING_STAT) {
+ mCanvas.dumpStatisticsAndClear();
+ }
+ }
+
+ private void rotateCanvas(int degrees) {
+ if (degrees == 0) return;
+ int w = getWidth();
+ int h = getHeight();
+ int cx = w / 2;
+ int cy = h / 2;
+ mCanvas.translate(cx, cy);
+ mCanvas.rotate(degrees, 0, 0, 1);
+ if (degrees % 180 != 0) {
+ mCanvas.translate(-cy, -cx);
+ } else {
+ mCanvas.translate(-cx, -cy);
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (!isEnabled()) return false;
+
+ int action = event.getAction();
+ if (action == MotionEvent.ACTION_CANCEL
+ || action == MotionEvent.ACTION_UP) {
+ mInDownState = false;
+ } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+
+ if (mCompensation != 0) {
+ event = MotionEventHelper.transformEvent(event, mCompensationMatrix);
+ }
+
+ mRenderLock.lock();
+ try {
+ // If this has been detached from root, we don't need to handle event
+ boolean handled = mContentView != null
+ && mContentView.dispatchTouchEvent(event);
+ if (action == MotionEvent.ACTION_DOWN && handled) {
+ mInDownState = true;
+ }
+ return handled;
+ } finally {
+ mRenderLock.unlock();
+ }
+ }
+
+ private class IdleRunner implements Runnable {
+ // true if the idle runner is in the queue
+ private boolean mActive = false;
+
+ @Override
+ public void run() {
+ OnGLIdleListener listener;
+ synchronized (mIdleListeners) {
+ mActive = false;
+ if (mIdleListeners.isEmpty()) return;
+ listener = mIdleListeners.removeFirst();
+ }
+ mRenderLock.lock();
+ boolean keepInQueue;
+ try {
+ keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested);
+ } finally {
+ mRenderLock.unlock();
+ }
+ synchronized (mIdleListeners) {
+ if (keepInQueue) mIdleListeners.addLast(listener);
+ if (!mRenderRequested && !mIdleListeners.isEmpty()) enable();
+ }
+ }
+
+ public void enable() {
+ // Who gets the flag can add it to the queue
+ if (mActive) return;
+ mActive = true;
+ queueEvent(this);
+ }
+ }
+
+ @Override
+ public void lockRenderThread() {
+ mRenderLock.lock();
+ }
+
+ @Override
+ public void unlockRenderThread() {
+ mRenderLock.unlock();
+ }
+
+ @Override
+ public void onPause() {
+ unfreeze();
+ super.onPause();
+ if (DEBUG_PROFILE) {
+ Log.d(TAG, "Stop profiling");
+ Profile.disableAll();
+ Profile.dumpToFile("/sdcard/gallery.prof");
+ Profile.reset();
+ }
+ }
+
+ @Override
+ public void setOrientationSource(OrientationSource source) {
+ mOrientationSource = source;
+ }
+
+ @Override
+ public int getDisplayRotation() {
+ return mDisplayRotation;
+ }
+
+ @Override
+ public int getCompensation() {
+ return mCompensation;
+ }
+
+ @Override
+ public Matrix getCompensationMatrix() {
+ return mCompensationMatrix;
+ }
+
+ @Override
+ public void freeze() {
+ mRenderLock.lock();
+ mFreeze = true;
+ mRenderLock.unlock();
+ }
+
+ @Override
+ public void unfreeze() {
+ mRenderLock.lock();
+ mFreeze = false;
+ mFreezeCondition.signalAll();
+ mRenderLock.unlock();
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ public void setLightsOutMode(boolean enabled) {
+ if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
+
+ int flags = 0;
+ if (enabled) {
+ flags = STATUS_BAR_HIDDEN;
+ if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
+ flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+ }
+ setSystemUiVisibility(flags);
+ }
+
+ // We need to unfreeze in the following methods and in onPause().
+ // These methods will wait on GLThread. If we have freezed the GLRootView,
+ // the GLThread will wait on main thread to call unfreeze and cause dead
+ // lock.
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ unfreeze();
+ super.surfaceChanged(holder, format, w, h);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ unfreeze();
+ super.surfaceCreated(holder);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ unfreeze();
+ super.surfaceDestroyed(holder);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ unfreeze();
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ unfreeze();
+ } finally {
+ super.finalize();
+ }
+ }
+}