OSDN Git Service

Import translations. DO NOT MERGE
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / ui / GLRootView.java
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.gallery3d.ui;
18
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;
30
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;
37
38 import java.util.ArrayDeque;
39 import java.util.ArrayList;
40 import java.util.concurrent.locks.Condition;
41 import java.util.concurrent.locks.ReentrantLock;
42
43 import javax.microedition.khronos.egl.EGLConfig;
44 import javax.microedition.khronos.opengles.GL10;
45 import javax.microedition.khronos.opengles.GL11;
46
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";
58
59     private static final boolean DEBUG_FPS = false;
60     private int mFrameCount = 0;
61     private long mFrameCountingStart = 0;
62
63     private static final boolean DEBUG_INVALIDATE = false;
64     private int mInvalidateColor = 0;
65
66     private static final boolean DEBUG_DRAWING_STAT = false;
67
68     private static final boolean DEBUG_PROFILE = false;
69     private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
70
71     private static final int FLAG_INITIALIZED = 1;
72     private static final int FLAG_NEED_LAYOUT = 2;
73
74     private GL11 mGL;
75     private GLCanvas mCanvas;
76     private GLView mContentView;
77
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;
86
87     // The value which will become mCompensation in next layout.
88     private int mPendingCompensation;
89
90     private int mFlags = FLAG_NEED_LAYOUT;
91     private volatile boolean mRenderRequested = false;
92
93     private final GalleryEGLConfigChooser mEglConfigChooser =
94             new GalleryEGLConfigChooser();
95
96     private final ArrayList<CanvasAnimation> mAnimations =
97             new ArrayList<CanvasAnimation>();
98
99     private final ArrayDeque<OnGLIdleListener> mIdleListeners =
100             new ArrayDeque<OnGLIdleListener>();
101
102     private final IdleRunner mIdleRunner = new IdleRunner();
103
104     private final ReentrantLock mRenderLock = new ReentrantLock();
105     private final Condition mFreezeCondition =
106             mRenderLock.newCondition();
107     private boolean mFreeze;
108
109     private long mLastDrawFinishTime;
110     private boolean mInDownState = false;
111     private boolean mFirstDraw = true;
112
113     public GLRootView(Context context) {
114         this(context, null);
115     }
116
117     public GLRootView(Context context, AttributeSet attrs) {
118         super(context, attrs);
119         mFlags |= FLAG_INITIALIZED;
120         setBackgroundDrawable(null);
121         setEGLConfigChooser(mEglConfigChooser);
122         setRenderer(this);
123         getHolder().setFormat(PixelFormat.RGB_565);
124
125         // Uncomment this to enable gl error check.
126         // setDebugFlags(DEBUG_CHECK_GL_ERROR);
127     }
128
129     @Override
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);
136     }
137
138     @Override
139     public void addOnGLIdleListener(OnGLIdleListener listener) {
140         synchronized (mIdleListeners) {
141             mIdleListeners.addLast(listener);
142             mIdleRunner.enable();
143         }
144     }
145
146     @Override
147     public void setContentPane(GLView content) {
148         if (mContentView == content) return;
149         if (mContentView != null) {
150             if (mInDownState) {
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;
157             }
158             mContentView.detachFromRoot();
159             BasicTexture.yieldAllTextures();
160         }
161         mContentView = content;
162         if (content != null) {
163             content.attachToRoot(this);
164             requestLayoutContentPane();
165         }
166     }
167
168     @Override
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);
174         }
175         if (mRenderRequested) return;
176         mRenderRequested = true;
177         super.requestRender();
178     }
179
180     @Override
181     public void requestLayoutContentPane() {
182         mRenderLock.lock();
183         try {
184             if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
185
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;
189
190             mFlags |= FLAG_NEED_LAYOUT;
191             requestRender();
192         } finally {
193             mRenderLock.unlock();
194         }
195     }
196
197     private void layoutContentPane() {
198         mFlags &= ~FLAG_NEED_LAYOUT;
199
200         int w = getWidth();
201         int h = getHeight();
202         int displayRotation = 0;
203         int compensation = 0;
204
205         // Get the new orientation values
206         if (mOrientationSource != null) {
207             displayRotation = mOrientationSource.getDisplayRotation();
208             compensation = mOrientationSource.getCompensation();
209         } else {
210             displayRotation = 0;
211             compensation = 0;
212         }
213
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);
222             } else {
223                 mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
224             }
225         }
226         mDisplayRotation = displayRotation;
227
228         // Do the actual layout.
229         if (mCompensation % 180 != 0) {
230             int tmp = w;
231             w = h;
232             h = tmp;
233         }
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);
238         }
239         // Uncomment this to dump the view hierarchy.
240         //mContentView.dumpTree("");
241     }
242
243     @Override
244     protected void onLayout(
245             boolean changed, int left, int top, int right, int bottom) {
246         if (changed) requestLayoutContentPane();
247     }
248
249     /**
250      * Called when the context is created, possibly after automatic destruction.
251      */
252     // This is a GLSurfaceView.Renderer callback
253     @Override
254     public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
255         GL11 gl = (GL11) gl1;
256         if (mGL != null) {
257             // The GL Object has changed
258             Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
259         }
260         mRenderLock.lock();
261         try {
262             mGL = gl;
263             mCanvas = new GLCanvasImpl(gl);
264             BasicTexture.invalidateAllTextures();
265         } finally {
266             mRenderLock.unlock();
267         }
268
269         if (DEBUG_FPS || DEBUG_PROFILE) {
270             setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
271         } else {
272             setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
273         }
274     }
275
276     /**
277      * Called when the OpenGL surface is recreated without destroying the
278      * context.
279      */
280     // This is a GLSurfaceView.Renderer callback
281     @Override
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();
287         if (DEBUG_PROFILE) {
288             Log.d(TAG, "Start profiling");
289             Profile.enable(20);  // take a sample every 20ms
290         }
291         GL11 gl = (GL11) gl1;
292         Utils.assertTrue(mGL == gl);
293
294         mCanvas.setSize(width, height);
295     }
296
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;
305             mFrameCount = 0;
306         }
307         ++mFrameCount;
308     }
309
310     @Override
311     public void onDrawFrame(GL10 gl) {
312         AnimationTime.update();
313         long t0;
314         if (DEBUG_PROFILE_SLOW_ONLY) {
315             Profile.hold();
316             t0 = System.nanoTime();
317         }
318         mRenderLock.lock();
319
320         while (mFreeze) {
321             mFreezeCondition.awaitUninterruptibly();
322         }
323
324         try {
325             onDrawFrameLocked(gl);
326         } finally {
327             mRenderLock.unlock();
328         }
329
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.
333         if (mFirstDraw) {
334             mFirstDraw = false;
335             post(new Runnable() {
336                     public void run() {
337                         View root = getRootView();
338                         View cover = root.findViewById(R.id.gl_root_cover);
339                         cover.setVisibility(GONE);
340                     }
341                 });
342         }
343
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;
349
350             if (durationInMs > 34) {  // 34ms -> we skipped at least 2 frames
351                 Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
352                         durationInMs + ") -----");
353                 Profile.commit();
354             } else {
355                 Profile.drop();
356             }
357         }
358     }
359
360     private void onDrawFrameLocked(GL10 gl) {
361         if (DEBUG_FPS) outputFps();
362
363         // release the unbound textures and deleted buffers.
364         mCanvas.deleteRecycledResources();
365
366         // reset texture upload limit
367         UploadedTexture.resetUploadLimit();
368
369         mRenderRequested = false;
370
371         if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
372
373         mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
374         rotateCanvas(-mCompensation);
375         if (mContentView != null) {
376            mContentView.render(mCanvas);
377         }
378         mCanvas.restore();
379
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);
384             }
385             mAnimations.clear();
386         }
387
388         if (UploadedTexture.uploadLimitReached()) {
389             requestRender();
390         }
391
392         synchronized (mIdleListeners) {
393             if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
394         }
395
396         if (DEBUG_INVALIDATE) {
397             mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
398             mInvalidateColor = ~mInvalidateColor;
399         }
400
401         if (DEBUG_DRAWING_STAT) {
402             mCanvas.dumpStatisticsAndClear();
403         }
404     }
405
406     private void rotateCanvas(int degrees) {
407         if (degrees == 0) return;
408         int w = getWidth();
409         int h = getHeight();
410         int cx = w / 2;
411         int cy = h / 2;
412         mCanvas.translate(cx, cy);
413         mCanvas.rotate(degrees, 0, 0, 1);
414         if (degrees % 180 != 0) {
415             mCanvas.translate(-cy, -cx);
416         } else {
417             mCanvas.translate(-cx, -cy);
418         }
419     }
420
421     @Override
422     public boolean dispatchTouchEvent(MotionEvent event) {
423         if (!isEnabled()) return false;
424
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) {
430             return false;
431         }
432
433         if (mCompensation != 0) {
434             event.transform(mCompensationMatrix);
435         }
436
437         mRenderLock.lock();
438         try {
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) {
443                 mInDownState = true;
444             }
445             return handled;
446         } finally {
447             mRenderLock.unlock();
448         }
449     }
450
451     private class IdleRunner implements Runnable {
452         // true if the idle runner is in the queue
453         private boolean mActive = false;
454
455         @Override
456         public void run() {
457             OnGLIdleListener listener;
458             synchronized (mIdleListeners) {
459                 mActive = false;
460                 if (mIdleListeners.isEmpty()) return;
461                 listener = mIdleListeners.removeFirst();
462             }
463             mRenderLock.lock();
464             try {
465                 if (!listener.onGLIdle(mCanvas, mRenderRequested)) return;
466             } finally {
467                 mRenderLock.unlock();
468             }
469             synchronized (mIdleListeners) {
470                 mIdleListeners.addLast(listener);
471                 if (!mRenderRequested) enable();
472             }
473         }
474
475         public void enable() {
476             // Who gets the flag can add it to the queue
477             if (mActive) return;
478             mActive = true;
479             queueEvent(this);
480         }
481     }
482
483     @Override
484     public void lockRenderThread() {
485         mRenderLock.lock();
486     }
487
488     @Override
489     public void unlockRenderThread() {
490         mRenderLock.unlock();
491     }
492
493     @Override
494     public void onPause() {
495         unfreeze();
496         super.onPause();
497         if (DEBUG_PROFILE) {
498             Log.d(TAG, "Stop profiling");
499             Profile.disableAll();
500             Profile.dumpToFile("/sdcard/gallery.prof");
501             Profile.reset();
502         }
503     }
504
505     @Override
506     public void setOrientationSource(OrientationSource source) {
507         mOrientationSource = source;
508     }
509
510     @Override
511     public int getDisplayRotation() {
512         return mDisplayRotation;
513     }
514
515     @Override
516     public int getCompensation() {
517         return mCompensation;
518     }
519
520     @Override
521     public Matrix getCompensationMatrix() {
522         return mCompensationMatrix;
523     }
524
525     @Override
526     public void freeze() {
527         mRenderLock.lock();
528         mFreeze = true;
529         mRenderLock.unlock();
530     }
531
532     @Override
533     public void unfreeze() {
534         mRenderLock.lock();
535         mFreeze = false;
536         mFreezeCondition.signalAll();
537         mRenderLock.unlock();
538     }
539
540     @Override
541     @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
542     public void setLightsOutMode(boolean enabled) {
543         int flags = 0;
544         if (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);
548             }
549         }
550         setSystemUiVisibility(flags);
551     }
552
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
556     // lock.
557     @Override
558     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
559         unfreeze();
560         super.surfaceChanged(holder, format, w, h);
561     }
562
563     @Override
564     public void surfaceCreated(SurfaceHolder holder) {
565         unfreeze();
566         super.surfaceCreated(holder);
567     }
568
569     @Override
570     public void surfaceDestroyed(SurfaceHolder holder) {
571         unfreeze();
572         super.surfaceDestroyed(holder);
573     }
574
575     @Override
576     protected void onDetachedFromWindow() {
577         unfreeze();
578         super.onDetachedFromWindow();
579     }
580
581     @Override
582     protected void finalize() throws Throwable {
583         try {
584             unfreeze();
585         } finally {
586             super.finalize();
587         }
588     }
589 }