2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.ui;
19 import android.content.Context;
20 import android.graphics.Matrix;
21 import android.graphics.PixelFormat;
22 import android.opengl.GLSurfaceView;
23 import android.os.Process;
24 import android.os.SystemClock;
25 import android.util.AttributeSet;
26 import android.view.MotionEvent;
27 import android.view.SurfaceHolder;
29 import com.android.gallery3d.anim.CanvasAnimation;
30 import com.android.gallery3d.common.Utils;
31 import com.android.gallery3d.util.GalleryUtils;
32 import com.android.gallery3d.util.Profile;
34 import java.util.ArrayDeque;
35 import java.util.ArrayList;
36 import java.util.concurrent.locks.Condition;
37 import java.util.concurrent.locks.ReentrantLock;
39 import javax.microedition.khronos.egl.EGLConfig;
40 import javax.microedition.khronos.opengles.GL10;
41 import javax.microedition.khronos.opengles.GL11;
43 // The root component of all <code>GLView</code>s. The rendering is done in GL
44 // thread while the event handling is done in the main thread. To synchronize
45 // the two threads, the entry points of this package need to synchronize on the
46 // <code>GLRootView</code> instance unless it can be proved that the rendering
47 // thread won't access the same thing as the method. The entry points include:
48 // (1) The public methods of HeadUpDisplay
49 // (2) The public methods of CameraHeadUpDisplay
50 // (3) The overridden methods in GLRootView.
51 public class GLRootView extends GLSurfaceView
52 implements GLSurfaceView.Renderer, GLRoot {
53 private static final String TAG = "GLRootView";
55 private static final boolean DEBUG_FPS = false;
56 private int mFrameCount = 0;
57 private long mFrameCountingStart = 0;
59 private static final boolean DEBUG_INVALIDATE = false;
60 private int mInvalidateColor = 0;
62 private static final boolean DEBUG_DRAWING_STAT = false;
64 private static final boolean DEBUG_PROFILE = false;
65 private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
67 private static final int FLAG_INITIALIZED = 1;
68 private static final int FLAG_NEED_LAYOUT = 2;
71 private GLCanvas mCanvas;
72 private GLView mContentView;
74 private OrientationSource mOrientationSource;
75 // mCompensation is the difference between the UI orientation on GLCanvas
76 // and the framework orientation. See OrientationManager for details.
77 private int mCompensation;
78 // mCompensationMatrix maps the coordinates of touch events. It is kept sync
79 // with mCompensation.
80 private Matrix mCompensationMatrix = new Matrix();
81 private int mDisplayRotation;
83 // The value which will become mCompensation in next layout.
84 private int mPendingCompensation;
86 private int mFlags = FLAG_NEED_LAYOUT;
87 private volatile boolean mRenderRequested = false;
89 private final GalleryEGLConfigChooser mEglConfigChooser =
90 new GalleryEGLConfigChooser();
92 private final ArrayList<CanvasAnimation> mAnimations =
93 new ArrayList<CanvasAnimation>();
95 private final ArrayDeque<OnGLIdleListener> mIdleListeners =
96 new ArrayDeque<OnGLIdleListener>();
98 private final IdleRunner mIdleRunner = new IdleRunner();
100 private final ReentrantLock mRenderLock = new ReentrantLock();
101 private final Condition mFreezeCondition =
102 mRenderLock.newCondition();
103 private boolean mFreeze;
105 private long mLastDrawFinishTime;
106 private boolean mInDownState = false;
108 public GLRootView(Context context) {
112 public GLRootView(Context context, AttributeSet attrs) {
113 super(context, attrs);
114 mFlags |= FLAG_INITIALIZED;
115 setBackgroundDrawable(null);
116 setEGLConfigChooser(mEglConfigChooser);
118 getHolder().setFormat(PixelFormat.RGB_565);
120 // Uncomment this to enable gl error check.
121 // setDebugFlags(DEBUG_CHECK_GL_ERROR);
125 public void registerLaunchedAnimation(CanvasAnimation animation) {
126 // Register the newly launched animation so that we can set the start
127 // time more precisely. (Usually, it takes much longer for first
128 // rendering, so we set the animation start time as the time we
129 // complete rendering)
130 mAnimations.add(animation);
134 public void addOnGLIdleListener(OnGLIdleListener listener) {
135 synchronized (mIdleListeners) {
136 mIdleListeners.addLast(listener);
137 mIdleRunner.enable();
142 public void setContentPane(GLView content) {
143 if (mContentView == content) return;
144 if (mContentView != null) {
146 long now = SystemClock.uptimeMillis();
147 MotionEvent cancelEvent = MotionEvent.obtain(
148 now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
149 mContentView.dispatchTouchEvent(cancelEvent);
150 cancelEvent.recycle();
151 mInDownState = false;
153 mContentView.detachFromRoot();
154 BasicTexture.yieldAllTextures();
156 mContentView = content;
157 if (content != null) {
158 content.attachToRoot(this);
159 requestLayoutContentPane();
164 public void requestRender() {
165 if (DEBUG_INVALIDATE) {
166 StackTraceElement e = Thread.currentThread().getStackTrace()[4];
167 String caller = e.getFileName() + ":" + e.getLineNumber() + " ";
168 Log.d(TAG, "invalidate: " + caller);
170 if (mRenderRequested) return;
171 mRenderRequested = true;
172 super.requestRender();
176 public void requestLayoutContentPane() {
179 if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
181 // "View" system will invoke onLayout() for initialization(bug ?), we
182 // have to ignore it since the GLThread is not ready yet.
183 if ((mFlags & FLAG_INITIALIZED) == 0) return;
185 mFlags |= FLAG_NEED_LAYOUT;
188 mRenderLock.unlock();
192 private void layoutContentPane() {
193 mFlags &= ~FLAG_NEED_LAYOUT;
197 int displayRotation = 0;
198 int compensation = 0;
200 // Get the new orientation values
201 if (mOrientationSource != null) {
202 displayRotation = mOrientationSource.getDisplayRotation();
203 compensation = mOrientationSource.getCompensation();
209 if (mCompensation != compensation) {
210 mCompensation = compensation;
211 if (mCompensation % 180 != 0) {
212 mCompensationMatrix.setRotate(mCompensation);
213 // move center to origin before rotation
214 mCompensationMatrix.preTranslate(-w / 2, -h / 2);
215 // align with the new origin after rotation
216 mCompensationMatrix.postTranslate(h / 2, w / 2);
218 mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
221 mDisplayRotation = displayRotation;
223 // Do the actual layout.
224 if (mCompensation % 180 != 0) {
229 Log.i(TAG, "layout content pane " + w + "x" + h
230 + " (compensation " + mCompensation + ")");
231 if (mContentView != null && w != 0 && h != 0) {
232 mContentView.layout(0, 0, w, h);
234 // Uncomment this to dump the view hierarchy.
235 //mContentView.dumpTree("");
239 protected void onLayout(
240 boolean changed, int left, int top, int right, int bottom) {
241 if (changed) requestLayoutContentPane();
245 * Called when the context is created, possibly after automatic destruction.
247 // This is a GLSurfaceView.Renderer callback
249 public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
250 GL11 gl = (GL11) gl1;
252 // The GL Object has changed
253 Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
258 mCanvas = new GLCanvasImpl(gl);
259 BasicTexture.invalidateAllTextures();
261 mRenderLock.unlock();
264 if (DEBUG_FPS || DEBUG_PROFILE) {
265 setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
267 setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
272 * Called when the OpenGL surface is recreated without destroying the
275 // This is a GLSurfaceView.Renderer callback
277 public void onSurfaceChanged(GL10 gl1, int width, int height) {
278 Log.i(TAG, "onSurfaceChanged: " + width + "x" + height
279 + ", gl10: " + gl1.toString());
280 Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
281 GalleryUtils.setRenderThread();
283 Log.d(TAG, "Start profiling");
284 Profile.enable(20); // take a sample every 20ms
286 GL11 gl = (GL11) gl1;
287 Utils.assertTrue(mGL == gl);
289 mCanvas.setSize(width, height);
292 private void outputFps() {
293 long now = System.nanoTime();
294 if (mFrameCountingStart == 0) {
295 mFrameCountingStart = now;
296 } else if ((now - mFrameCountingStart) > 1000000000) {
297 Log.d(TAG, "fps: " + (double) mFrameCount
298 * 1000000000 / (now - mFrameCountingStart));
299 mFrameCountingStart = now;
306 public void onDrawFrame(GL10 gl) {
307 AnimationTime.update();
309 if (DEBUG_PROFILE_SLOW_ONLY) {
311 t0 = System.nanoTime();
316 mFreezeCondition.awaitUninterruptibly();
320 onDrawFrameLocked(gl);
322 mRenderLock.unlock();
325 if (DEBUG_PROFILE_SLOW_ONLY) {
326 long t = System.nanoTime();
327 long durationInMs = (t - mLastDrawFinishTime) / 1000000;
328 long durationDrawInMs = (t - t0) / 1000000;
329 mLastDrawFinishTime = t;
331 if (durationInMs > 34) { // 34ms -> we skipped at least 2 frames
332 Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
333 durationInMs + ") -----");
341 private void onDrawFrameLocked(GL10 gl) {
342 if (DEBUG_FPS) outputFps();
344 // release the unbound textures and deleted buffers.
345 mCanvas.deleteRecycledResources();
347 // reset texture upload limit
348 UploadedTexture.resetUploadLimit();
350 mRenderRequested = false;
352 if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
354 mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
355 rotateCanvas(-mCompensation);
356 if (mContentView != null) {
357 mContentView.render(mCanvas);
361 if (!mAnimations.isEmpty()) {
362 long now = AnimationTime.get();
363 for (int i = 0, n = mAnimations.size(); i < n; i++) {
364 mAnimations.get(i).setStartTime(now);
369 if (UploadedTexture.uploadLimitReached()) {
373 synchronized (mIdleListeners) {
374 if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
377 if (DEBUG_INVALIDATE) {
378 mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
379 mInvalidateColor = ~mInvalidateColor;
382 if (DEBUG_DRAWING_STAT) {
383 mCanvas.dumpStatisticsAndClear();
387 private void rotateCanvas(int degrees) {
388 if (degrees == 0) return;
393 mCanvas.translate(cx, cy);
394 mCanvas.rotate(degrees, 0, 0, 1);
395 if (degrees % 180 != 0) {
396 mCanvas.translate(-cy, -cx);
398 mCanvas.translate(-cx, -cy);
403 public boolean dispatchTouchEvent(MotionEvent event) {
404 if (!isEnabled()) return false;
406 int action = event.getAction();
407 if (action == MotionEvent.ACTION_CANCEL
408 || action == MotionEvent.ACTION_UP) {
409 mInDownState = false;
410 } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
414 if (mCompensation != 0) {
415 event.transform(mCompensationMatrix);
420 // If this has been detached from root, we don't need to handle event
421 boolean handled = mContentView != null
422 && mContentView.dispatchTouchEvent(event);
423 if (action == MotionEvent.ACTION_DOWN && handled) {
428 mRenderLock.unlock();
432 private class IdleRunner implements Runnable {
433 // true if the idle runner is in the queue
434 private boolean mActive = false;
438 OnGLIdleListener listener;
439 synchronized (mIdleListeners) {
441 if (mIdleListeners.isEmpty()) return;
442 listener = mIdleListeners.removeFirst();
446 if (!listener.onGLIdle(mCanvas, mRenderRequested)) return;
448 mRenderLock.unlock();
450 synchronized (mIdleListeners) {
451 mIdleListeners.addLast(listener);
452 if (!mRenderRequested) enable();
456 public void enable() {
457 // Who gets the flag can add it to the queue
465 public void lockRenderThread() {
470 public void unlockRenderThread() {
471 mRenderLock.unlock();
475 public void onPause() {
479 Log.d(TAG, "Stop profiling");
480 Profile.disableAll();
481 Profile.dumpToFile("/sdcard/gallery.prof");
487 public void setOrientationSource(OrientationSource source) {
488 mOrientationSource = source;
492 public int getDisplayRotation() {
493 return mDisplayRotation;
497 public int getCompensation() {
498 return mCompensation;
502 public Matrix getCompensationMatrix() {
503 return mCompensationMatrix;
507 public void freeze() {
510 mRenderLock.unlock();
514 public void unfreeze() {
517 mFreezeCondition.signalAll();
518 mRenderLock.unlock();
522 public void setLightsOutMode(boolean enabled) {
524 ? SYSTEM_UI_FLAG_LOW_PROFILE
525 | SYSTEM_UI_FLAG_FULLSCREEN
526 | SYSTEM_UI_FLAG_LAYOUT_STABLE
528 setSystemUiVisibility(flags);
531 // We need to unfreeze in the following methods and in onPause().
532 // These methods will wait on GLThread. If we have freezed the GLRootView,
533 // the GLThread will wait on main thread to call unfreeze and cause dead
536 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
538 super.surfaceChanged(holder, format, w, h);
542 public void surfaceCreated(SurfaceHolder holder) {
544 super.surfaceCreated(holder);
548 public void surfaceDestroyed(SurfaceHolder holder) {
550 super.surfaceDestroyed(holder);
554 protected void onDetachedFromWindow() {
556 super.onDetachedFromWindow();
560 protected void finalize() throws Throwable {