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.java414
1 files changed, 414 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..e03adf1c4
--- /dev/null
+++ b/src/com/android/gallery3d/ui/GLRootView.java
@@ -0,0 +1,414 @@
+/*
+ * 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 com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.GalleryUtils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.opengl.GLSurfaceView;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+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 int FLAG_INITIALIZED = 1;
+ private static final int FLAG_NEED_LAYOUT = 2;
+
+ private GL11 mGL;
+ private GLCanvasImpl mCanvas;
+
+ private GLView mContentView;
+ private DisplayMetrics mDisplayMetrics;
+
+ private int mFlags = FLAG_NEED_LAYOUT;
+ private volatile boolean mRenderRequested = false;
+
+ private Rect mClipRect = new Rect();
+ private int mClipRetryCount = 0;
+
+ private final GalleryEGLConfigChooser mEglConfigChooser =
+ new GalleryEGLConfigChooser();
+
+ private final ArrayList<CanvasAnimation> mAnimations =
+ new ArrayList<CanvasAnimation>();
+
+ private final LinkedList<OnGLIdleListener> mIdleListeners =
+ new LinkedList<OnGLIdleListener>();
+
+ private final IdleRunner mIdleRunner = new IdleRunner();
+
+ private final ReentrantLock mRenderLock = new ReentrantLock();
+
+ private static final int TARGET_FRAME_TIME = 33;
+ private long mLastDrawFinishTime;
+ private boolean mInDownState = false;
+
+ public GLRootView(Context context) {
+ this(context, null);
+ }
+
+ public GLRootView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mFlags |= FLAG_INITIALIZED;
+ setBackgroundDrawable(null);
+ setEGLConfigChooser(mEglConfigChooser);
+ setRenderer(this);
+ getHolder().setFormat(PixelFormat.RGB_565);
+
+ // Uncomment this to enable gl error check.
+ //setDebugFlags(DEBUG_CHECK_GL_ERROR);
+ }
+
+ public GalleryEGLConfigChooser getEGLConfigChooser() {
+ return mEglConfigChooser;
+ }
+
+ @Override
+ public boolean hasStencil() {
+ return getEGLConfigChooser().getStencilBits() > 0;
+ }
+
+ @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();
+ }
+ }
+
+ public GLView getContentPane() {
+ return mContentView;
+ }
+
+ @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;
+ 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 width = getWidth();
+ int height = getHeight();
+ Log.i(TAG, "layout content pane " + width + "x" + height);
+ if (mContentView != null && width != 0 && height != 0) {
+ mContentView.layout(0, 0, width, height);
+ }
+ // 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);
+ }
+ mGL = gl;
+ mCanvas = new GLCanvasImpl(gl);
+ if (!DEBUG_FPS) {
+ setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ } else {
+ setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ }
+ }
+
+ /**
+ * 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();
+ GL11 gl = (GL11) gl1;
+ Utils.assertTrue(mGL == gl);
+
+ mCanvas.setSize(width, height);
+
+ mClipRect.set(0, 0, width, height);
+ mClipRetryCount = 2;
+ }
+
+ 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) {
+ mRenderLock.lock();
+ try {
+ onDrawFrameLocked(gl);
+ } finally {
+ mRenderLock.unlock();
+ }
+ long end = SystemClock.uptimeMillis();
+
+ if (mLastDrawFinishTime != 0) {
+ long wait = mLastDrawFinishTime + TARGET_FRAME_TIME - end;
+ if (wait > 0) {
+ SystemClock.sleep(wait);
+ }
+ }
+ mLastDrawFinishTime = SystemClock.uptimeMillis();
+ }
+
+ 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 ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
+
+ // OpenGL seems having a bug causing us not being able to reset the
+ // scissor box in "onSurfaceChanged()". We have to do it in the second
+ // onDrawFrame().
+ if (mClipRetryCount > 0) {
+ --mClipRetryCount;
+ Rect clip = mClipRect;
+ gl.glScissor(clip.left, clip.top, clip.width(), clip.height());
+ }
+
+ mCanvas.setCurrentAnimationTimeMillis(SystemClock.uptimeMillis());
+ if (mContentView != null) {
+ mContentView.render(mCanvas);
+ }
+
+ if (!mAnimations.isEmpty()) {
+ long now = SystemClock.uptimeMillis();
+ for (int i = 0, n = mAnimations.size(); i < n; i++) {
+ mAnimations.get(i).setStartTime(now);
+ }
+ mAnimations.clear();
+ }
+
+ if (UploadedTexture.uploadLimitReached()) {
+ requestRender();
+ }
+
+ synchronized (mIdleListeners) {
+ if (!mRenderRequested && !mIdleListeners.isEmpty()) {
+ mIdleRunner.enable();
+ }
+ }
+
+ if (DEBUG_INVALIDATE) {
+ mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
+ mInvalidateColor = ~mInvalidateColor;
+ }
+
+ if (DEBUG_DRAWING_STAT) {
+ mCanvas.dumpStatisticsAndClear();
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ int action = event.getAction();
+ if (action == MotionEvent.ACTION_CANCEL
+ || action == MotionEvent.ACTION_UP) {
+ mInDownState = false;
+ } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ 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();
+ }
+ }
+
+ public DisplayMetrics getDisplayMetrics() {
+ if (mDisplayMetrics == null) {
+ mDisplayMetrics = new DisplayMetrics();
+ ((Activity) getContext()).getWindowManager()
+ .getDefaultDisplay().getMetrics(mDisplayMetrics);
+ }
+ return mDisplayMetrics;
+ }
+
+ public GLCanvas getCanvas() {
+ return mCanvas;
+ }
+
+ 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 (mRenderRequested) return;
+ if (mIdleListeners.isEmpty()) return;
+ listener = mIdleListeners.removeFirst();
+ }
+ mRenderLock.lock();
+ try {
+ if (!listener.onGLIdle(GLRootView.this, mCanvas)) return;
+ } finally {
+ mRenderLock.unlock();
+ }
+ synchronized (mIdleListeners) {
+ mIdleListeners.addLast(listener);
+ 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();
+ }
+}