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.annotation.TargetApi;
20 import android.content.Context;
21 import android.graphics.Matrix;
22 import android.graphics.PixelFormat;
23 import android.opengl.GLSurfaceView;
24 import android.os.Process;
25 import android.os.SystemClock;
26 import android.util.AttributeSet;
27 import android.view.MotionEvent;
28 import android.view.SurfaceHolder;
29 import android.view.View;
31 import com.android.gallery3d.R;
32 import com.android.gallery3d.anim.CanvasAnimation;
33 import com.android.gallery3d.common.ApiHelper;
34 import com.android.gallery3d.common.Utils;
35 import com.android.gallery3d.util.GalleryUtils;
36 import com.android.gallery3d.util.Profile;
38 import java.util.ArrayDeque;
39 import java.util.ArrayList;
40 import java.util.concurrent.locks.Condition;
41 import java.util.concurrent.locks.ReentrantLock;
43 import javax.microedition.khronos.egl.EGLConfig;
44 import javax.microedition.khronos.opengles.GL10;
45 import javax.microedition.khronos.opengles.GL11;
47 // The root component of all <code>GLView</code>s. The rendering is done in GL
48 // thread while the event handling is done in the main thread. To synchronize
49 // the two threads, the entry points of this package need to synchronize on the
50 // <code>GLRootView</code> instance unless it can be proved that the rendering
51 // thread won't access the same thing as the method. The entry points include:
52 // (1) The public methods of HeadUpDisplay
53 // (2) The public methods of CameraHeadUpDisplay
54 // (3) The overridden methods in GLRootView.
55 public class GLRootView extends GLSurfaceView
56 implements GLSurfaceView.Renderer, GLRoot {
57 private static final String TAG = "GLRootView";
59 private static final boolean DEBUG_FPS = false;
60 private int mFrameCount = 0;
61 private long mFrameCountingStart = 0;
63 private static final boolean DEBUG_INVALIDATE = false;
64 private int mInvalidateColor = 0;
66 private static final boolean DEBUG_DRAWING_STAT = false;
68 private static final boolean DEBUG_PROFILE = false;
69 private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
71 private static final int FLAG_INITIALIZED = 1;
72 private static final int FLAG_NEED_LAYOUT = 2;
75 private GLCanvas mCanvas;
76 private GLView mContentView;
78 private OrientationSource mOrientationSource;
79 // mCompensation is the difference between the UI orientation on GLCanvas
80 // and the framework orientation. See OrientationManager for details.
81 private int mCompensation;
82 // mCompensationMatrix maps the coordinates of touch events. It is kept sync
83 // with mCompensation.
84 private Matrix mCompensationMatrix = new Matrix();
85 private int mDisplayRotation;
87 // The value which will become mCompensation in next layout.
88 private int mPendingCompensation;
90 private int mFlags = FLAG_NEED_LAYOUT;
91 private volatile boolean mRenderRequested = false;
93 private final GalleryEGLConfigChooser mEglConfigChooser =
94 new GalleryEGLConfigChooser();
96 private final ArrayList<CanvasAnimation> mAnimations =
97 new ArrayList<CanvasAnimation>();
99 private final ArrayDeque<OnGLIdleListener> mIdleListeners =
100 new ArrayDeque<OnGLIdleListener>();
102 private final IdleRunner mIdleRunner = new IdleRunner();
104 private final ReentrantLock mRenderLock = new ReentrantLock();
105 private final Condition mFreezeCondition =
106 mRenderLock.newCondition();
107 private boolean mFreeze;
109 private long mLastDrawFinishTime;
110 private boolean mInDownState = false;
111 private boolean mFirstDraw = true;
113 public GLRootView(Context context) {
117 public GLRootView(Context context, AttributeSet attrs) {
118 super(context, attrs);
119 mFlags |= FLAG_INITIALIZED;
120 setBackgroundDrawable(null);
121 setEGLConfigChooser(mEglConfigChooser);
123 getHolder().setFormat(PixelFormat.RGB_565);
125 // Uncomment this to enable gl error check.
126 // setDebugFlags(DEBUG_CHECK_GL_ERROR);
130 public void registerLaunchedAnimation(CanvasAnimation animation) {
131 // Register the newly launched animation so that we can set the start
132 // time more precisely. (Usually, it takes much longer for first
133 // rendering, so we set the animation start time as the time we
134 // complete rendering)
135 mAnimations.add(animation);
139 public void addOnGLIdleListener(OnGLIdleListener listener) {
140 synchronized (mIdleListeners) {
141 mIdleListeners.addLast(listener);
142 mIdleRunner.enable();
147 public void setContentPane(GLView content) {
148 if (mContentView == content) return;
149 if (mContentView != null) {
151 long now = SystemClock.uptimeMillis();
152 MotionEvent cancelEvent = MotionEvent.obtain(
153 now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
154 mContentView.dispatchTouchEvent(cancelEvent);
155 cancelEvent.recycle();
156 mInDownState = false;
158 mContentView.detachFromRoot();
159 BasicTexture.yieldAllTextures();
161 mContentView = content;
162 if (content != null) {
163 content.attachToRoot(this);
164 requestLayoutContentPane();
169 public void requestRender() {
170 if (DEBUG_INVALIDATE) {
171 StackTraceElement e = Thread.currentThread().getStackTrace()[4];
172 String caller = e.getFileName() + ":" + e.getLineNumber() + " ";
173 Log.d(TAG, "invalidate: " + caller);
175 if (mRenderRequested) return;
176 mRenderRequested = true;
177 super.requestRender();
181 public void requestLayoutContentPane() {
184 if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
186 // "View" system will invoke onLayout() for initialization(bug ?), we
187 // have to ignore it since the GLThread is not ready yet.
188 if ((mFlags & FLAG_INITIALIZED) == 0) return;
190 mFlags |= FLAG_NEED_LAYOUT;
193 mRenderLock.unlock();
197 private void layoutContentPane() {
198 mFlags &= ~FLAG_NEED_LAYOUT;
202 int displayRotation = 0;
203 int compensation = 0;
205 // Get the new orientation values
206 if (mOrientationSource != null) {
207 displayRotation = mOrientationSource.getDisplayRotation();
208 compensation = mOrientationSource.getCompensation();
214 if (mCompensation != compensation) {
215 mCompensation = compensation;
216 if (mCompensation % 180 != 0) {
217 mCompensationMatrix.setRotate(mCompensation);
218 // move center to origin before rotation
219 mCompensationMatrix.preTranslate(-w / 2, -h / 2);
220 // align with the new origin after rotation
221 mCompensationMatrix.postTranslate(h / 2, w / 2);
223 mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
226 mDisplayRotation = displayRotation;
228 // Do the actual layout.
229 if (mCompensation % 180 != 0) {
234 Log.i(TAG, "layout content pane " + w + "x" + h
235 + " (compensation " + mCompensation + ")");
236 if (mContentView != null && w != 0 && h != 0) {
237 mContentView.layout(0, 0, w, h);
239 // Uncomment this to dump the view hierarchy.
240 //mContentView.dumpTree("");
244 protected void onLayout(
245 boolean changed, int left, int top, int right, int bottom) {
246 if (changed) requestLayoutContentPane();
250 * Called when the context is created, possibly after automatic destruction.
252 // This is a GLSurfaceView.Renderer callback
254 public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
255 GL11 gl = (GL11) gl1;
257 // The GL Object has changed
258 Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
263 mCanvas = new GLCanvasImpl(gl);
264 BasicTexture.invalidateAllTextures();
266 mRenderLock.unlock();
269 if (DEBUG_FPS || DEBUG_PROFILE) {
270 setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
272 setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
277 * Called when the OpenGL surface is recreated without destroying the
280 // This is a GLSurfaceView.Renderer callback
282 public void onSurfaceChanged(GL10 gl1, int width, int height) {
283 Log.i(TAG, "onSurfaceChanged: " + width + "x" + height
284 + ", gl10: " + gl1.toString());
285 Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
286 GalleryUtils.setRenderThread();
288 Log.d(TAG, "Start profiling");
289 Profile.enable(20); // take a sample every 20ms
291 GL11 gl = (GL11) gl1;
292 Utils.assertTrue(mGL == gl);
294 mCanvas.setSize(width, height);
297 private void outputFps() {
298 long now = System.nanoTime();
299 if (mFrameCountingStart == 0) {
300 mFrameCountingStart = now;
301 } else if ((now - mFrameCountingStart) > 1000000000) {
302 Log.d(TAG, "fps: " + (double) mFrameCount
303 * 1000000000 / (now - mFrameCountingStart));
304 mFrameCountingStart = now;
311 public void onDrawFrame(GL10 gl) {
312 AnimationTime.update();
314 if (DEBUG_PROFILE_SLOW_ONLY) {
316 t0 = System.nanoTime();
321 mFreezeCondition.awaitUninterruptibly();
325 onDrawFrameLocked(gl);
327 mRenderLock.unlock();
330 // We put a black cover View in front of the SurfaceView and hide it
331 // after the first draw. This prevents the SurfaceView being transparent
332 // before the first draw.
335 post(new Runnable() {
337 View root = getRootView();
338 View cover = root.findViewById(R.id.gl_root_cover);
339 cover.setVisibility(GONE);
344 if (DEBUG_PROFILE_SLOW_ONLY) {
345 long t = System.nanoTime();
346 long durationInMs = (t - mLastDrawFinishTime) / 1000000;
347 long durationDrawInMs = (t - t0) / 1000000;
348 mLastDrawFinishTime = t;
350 if (durationInMs > 34) { // 34ms -> we skipped at least 2 frames
351 Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
352 durationInMs + ") -----");
360 private void onDrawFrameLocked(GL10 gl) {
361 if (DEBUG_FPS) outputFps();
363 // release the unbound textures and deleted buffers.
364 mCanvas.deleteRecycledResources();
366 // reset texture upload limit
367 UploadedTexture.resetUploadLimit();
369 mRenderRequested = false;
371 if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
373 mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
374 rotateCanvas(-mCompensation);
375 if (mContentView != null) {
376 mContentView.render(mCanvas);
380 if (!mAnimations.isEmpty()) {
381 long now = AnimationTime.get();
382 for (int i = 0, n = mAnimations.size(); i < n; i++) {
383 mAnimations.get(i).setStartTime(now);
388 if (UploadedTexture.uploadLimitReached()) {
392 synchronized (mIdleListeners) {
393 if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
396 if (DEBUG_INVALIDATE) {
397 mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
398 mInvalidateColor = ~mInvalidateColor;
401 if (DEBUG_DRAWING_STAT) {
402 mCanvas.dumpStatisticsAndClear();
406 private void rotateCanvas(int degrees) {
407 if (degrees == 0) return;
412 mCanvas.translate(cx, cy);
413 mCanvas.rotate(degrees, 0, 0, 1);
414 if (degrees % 180 != 0) {
415 mCanvas.translate(-cy, -cx);
417 mCanvas.translate(-cx, -cy);
422 public boolean dispatchTouchEvent(MotionEvent event) {
423 if (!isEnabled()) return false;
425 int action = event.getAction();
426 if (action == MotionEvent.ACTION_CANCEL
427 || action == MotionEvent.ACTION_UP) {
428 mInDownState = false;
429 } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
433 if (mCompensation != 0) {
434 event.transform(mCompensationMatrix);
439 // If this has been detached from root, we don't need to handle event
440 boolean handled = mContentView != null
441 && mContentView.dispatchTouchEvent(event);
442 if (action == MotionEvent.ACTION_DOWN && handled) {
447 mRenderLock.unlock();
451 private class IdleRunner implements Runnable {
452 // true if the idle runner is in the queue
453 private boolean mActive = false;
457 OnGLIdleListener listener;
458 synchronized (mIdleListeners) {
460 if (mIdleListeners.isEmpty()) return;
461 listener = mIdleListeners.removeFirst();
465 if (!listener.onGLIdle(mCanvas, mRenderRequested)) return;
467 mRenderLock.unlock();
469 synchronized (mIdleListeners) {
470 mIdleListeners.addLast(listener);
471 if (!mRenderRequested) enable();
475 public void enable() {
476 // Who gets the flag can add it to the queue
484 public void lockRenderThread() {
489 public void unlockRenderThread() {
490 mRenderLock.unlock();
494 public void onPause() {
498 Log.d(TAG, "Stop profiling");
499 Profile.disableAll();
500 Profile.dumpToFile("/sdcard/gallery.prof");
506 public void setOrientationSource(OrientationSource source) {
507 mOrientationSource = source;
511 public int getDisplayRotation() {
512 return mDisplayRotation;
516 public int getCompensation() {
517 return mCompensation;
521 public Matrix getCompensationMatrix() {
522 return mCompensationMatrix;
526 public void freeze() {
529 mRenderLock.unlock();
533 public void unfreeze() {
536 mFreezeCondition.signalAll();
537 mRenderLock.unlock();
541 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
542 public void setLightsOutMode(boolean enabled) {
545 flags = SYSTEM_UI_FLAG_LOW_PROFILE;
546 if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
547 flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
550 setSystemUiVisibility(flags);
553 // We need to unfreeze in the following methods and in onPause().
554 // These methods will wait on GLThread. If we have freezed the GLRootView,
555 // the GLThread will wait on main thread to call unfreeze and cause dead
558 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
560 super.surfaceChanged(holder, format, w, h);
564 public void surfaceCreated(SurfaceHolder holder) {
566 super.surfaceCreated(holder);
570 public void surfaceDestroyed(SurfaceHolder holder) {
572 super.surfaceDestroyed(holder);
576 protected void onDetachedFromWindow() {
578 super.onDetachedFromWindow();
582 protected void finalize() throws Throwable {