GLRootView.java revision 695e1aa059cbc2cb2988f6beb0fee8ff7952b229
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
17package com.android.gallery3d.ui;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.graphics.Matrix;
22import android.graphics.PixelFormat;
23import android.opengl.GLSurfaceView;
24import android.os.Build;
25import android.os.Process;
26import android.os.SystemClock;
27import android.util.AttributeSet;
28import android.view.MotionEvent;
29import android.view.SurfaceHolder;
30import android.view.View;
31
32import com.android.gallery3d.R;
33import com.android.gallery3d.anim.CanvasAnimation;
34import com.android.gallery3d.common.ApiHelper;
35import com.android.gallery3d.common.Utils;
36import com.android.gallery3d.util.GalleryUtils;
37import com.android.gallery3d.util.MotionEventHelper;
38import com.android.gallery3d.util.Profile;
39
40import java.util.ArrayDeque;
41import java.util.ArrayList;
42import java.util.concurrent.locks.Condition;
43import java.util.concurrent.locks.ReentrantLock;
44
45import javax.microedition.khronos.egl.EGLConfig;
46import javax.microedition.khronos.opengles.GL10;
47import javax.microedition.khronos.opengles.GL11;
48
49// The root component of all <code>GLView</code>s. The rendering is done in GL
50// thread while the event handling is done in the main thread.  To synchronize
51// the two threads, the entry points of this package need to synchronize on the
52// <code>GLRootView</code> instance unless it can be proved that the rendering
53// thread won't access the same thing as the method. The entry points include:
54// (1) The public methods of HeadUpDisplay
55// (2) The public methods of CameraHeadUpDisplay
56// (3) The overridden methods in GLRootView.
57public class GLRootView extends GLSurfaceView
58        implements GLSurfaceView.Renderer, GLRoot {
59    private static final String TAG = "GLRootView";
60
61    private static final boolean DEBUG_FPS = false;
62    private int mFrameCount = 0;
63    private long mFrameCountingStart = 0;
64
65    private static final boolean DEBUG_INVALIDATE = false;
66    private int mInvalidateColor = 0;
67
68    private static final boolean DEBUG_DRAWING_STAT = false;
69
70    private static final boolean DEBUG_PROFILE = false;
71    private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
72
73    private static final int FLAG_INITIALIZED = 1;
74    private static final int FLAG_NEED_LAYOUT = 2;
75
76    private GL11 mGL;
77    private GLCanvas mCanvas;
78    private GLView mContentView;
79
80    private OrientationSource mOrientationSource;
81    // mCompensation is the difference between the UI orientation on GLCanvas
82    // and the framework orientation. See OrientationManager for details.
83    private int mCompensation;
84    // mCompensationMatrix maps the coordinates of touch events. It is kept sync
85    // with mCompensation.
86    private Matrix mCompensationMatrix = new Matrix();
87    private int mDisplayRotation;
88
89    private int mFlags = FLAG_NEED_LAYOUT;
90    private volatile boolean mRenderRequested = false;
91
92    private final GalleryEGLConfigChooser mEglConfigChooser =
93            new GalleryEGLConfigChooser();
94
95    private final ArrayList<CanvasAnimation> mAnimations =
96            new ArrayList<CanvasAnimation>();
97
98    private final ArrayDeque<OnGLIdleListener> mIdleListeners =
99            new ArrayDeque<OnGLIdleListener>();
100
101    private final IdleRunner mIdleRunner = new IdleRunner();
102
103    private final ReentrantLock mRenderLock = new ReentrantLock();
104    private final Condition mFreezeCondition =
105            mRenderLock.newCondition();
106    private boolean mFreeze;
107
108    private long mLastDrawFinishTime;
109    private boolean mInDownState = false;
110    private boolean mFirstDraw = true;
111
112    public GLRootView(Context context) {
113        this(context, null);
114    }
115
116    public GLRootView(Context context, AttributeSet attrs) {
117        super(context, attrs);
118        mFlags |= FLAG_INITIALIZED;
119        setBackgroundDrawable(null);
120        setEGLConfigChooser(mEglConfigChooser);
121        setRenderer(this);
122        if (ApiHelper.USE_888_PIXEL_FORMAT) {
123            getHolder().setFormat(PixelFormat.RGB_888);
124        } else {
125            getHolder().setFormat(PixelFormat.RGB_565);
126        }
127
128        // Uncomment this to enable gl error check.
129        // setDebugFlags(DEBUG_CHECK_GL_ERROR);
130    }
131
132    @Override
133    public void registerLaunchedAnimation(CanvasAnimation animation) {
134        // Register the newly launched animation so that we can set the start
135        // time more precisely. (Usually, it takes much longer for first
136        // rendering, so we set the animation start time as the time we
137        // complete rendering)
138        mAnimations.add(animation);
139    }
140
141    @Override
142    public void addOnGLIdleListener(OnGLIdleListener listener) {
143        synchronized (mIdleListeners) {
144            mIdleListeners.addLast(listener);
145            mIdleRunner.enable();
146        }
147    }
148
149    @Override
150    public void setContentPane(GLView content) {
151        if (mContentView == content) return;
152        if (mContentView != null) {
153            if (mInDownState) {
154                long now = SystemClock.uptimeMillis();
155                MotionEvent cancelEvent = MotionEvent.obtain(
156                        now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
157                mContentView.dispatchTouchEvent(cancelEvent);
158                cancelEvent.recycle();
159                mInDownState = false;
160            }
161            mContentView.detachFromRoot();
162            BasicTexture.yieldAllTextures();
163        }
164        mContentView = content;
165        if (content != null) {
166            content.attachToRoot(this);
167            requestLayoutContentPane();
168        }
169    }
170
171    @Override
172    public void requestRenderForced() {
173        superRequestRender();
174    }
175
176    @Override
177    public void requestRender() {
178        if (DEBUG_INVALIDATE) {
179            StackTraceElement e = Thread.currentThread().getStackTrace()[4];
180            String caller = e.getFileName() + ":" + e.getLineNumber() + " ";
181            Log.d(TAG, "invalidate: " + caller);
182        }
183        if (mRenderRequested) return;
184        mRenderRequested = true;
185        if (ApiHelper.HAS_POST_ON_ANIMATION) {
186            postOnAnimation(mRequestRenderOnAnimationFrame);
187        } else {
188            super.requestRender();
189        }
190    }
191
192    private Runnable mRequestRenderOnAnimationFrame = new Runnable() {
193        @Override
194        public void run() {
195            superRequestRender();
196        }
197    };
198
199    private void superRequestRender() {
200        super.requestRender();
201    }
202
203    @Override
204    public void requestLayoutContentPane() {
205        mRenderLock.lock();
206        try {
207            if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
208
209            // "View" system will invoke onLayout() for initialization(bug ?), we
210            // have to ignore it since the GLThread is not ready yet.
211            if ((mFlags & FLAG_INITIALIZED) == 0) return;
212
213            mFlags |= FLAG_NEED_LAYOUT;
214            requestRender();
215        } finally {
216            mRenderLock.unlock();
217        }
218    }
219
220    private void layoutContentPane() {
221        mFlags &= ~FLAG_NEED_LAYOUT;
222
223        int w = getWidth();
224        int h = getHeight();
225        int displayRotation = 0;
226        int compensation = 0;
227
228        // Get the new orientation values
229        if (mOrientationSource != null) {
230            displayRotation = mOrientationSource.getDisplayRotation();
231            compensation = mOrientationSource.getCompensation();
232        } else {
233            displayRotation = 0;
234            compensation = 0;
235        }
236
237        if (mCompensation != compensation) {
238            mCompensation = compensation;
239            if (mCompensation % 180 != 0) {
240                mCompensationMatrix.setRotate(mCompensation);
241                // move center to origin before rotation
242                mCompensationMatrix.preTranslate(-w / 2, -h / 2);
243                // align with the new origin after rotation
244                mCompensationMatrix.postTranslate(h / 2, w / 2);
245            } else {
246                mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
247            }
248        }
249        mDisplayRotation = displayRotation;
250
251        // Do the actual layout.
252        if (mCompensation % 180 != 0) {
253            int tmp = w;
254            w = h;
255            h = tmp;
256        }
257        Log.i(TAG, "layout content pane " + w + "x" + h
258                + " (compensation " + mCompensation + ")");
259        if (mContentView != null && w != 0 && h != 0) {
260            mContentView.layout(0, 0, w, h);
261        }
262        // Uncomment this to dump the view hierarchy.
263        //mContentView.dumpTree("");
264    }
265
266    @Override
267    protected void onLayout(
268            boolean changed, int left, int top, int right, int bottom) {
269        if (changed) requestLayoutContentPane();
270    }
271
272    /**
273     * Called when the context is created, possibly after automatic destruction.
274     */
275    // This is a GLSurfaceView.Renderer callback
276    @Override
277    public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
278        GL11 gl = (GL11) gl1;
279        if (mGL != null) {
280            // The GL Object has changed
281            Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
282        }
283        mRenderLock.lock();
284        try {
285            mGL = gl;
286            mCanvas = new GLCanvasImpl(gl);
287            BasicTexture.invalidateAllTextures();
288        } finally {
289            mRenderLock.unlock();
290        }
291
292        if (DEBUG_FPS || DEBUG_PROFILE) {
293            setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
294        } else {
295            setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
296        }
297    }
298
299    /**
300     * Called when the OpenGL surface is recreated without destroying the
301     * context.
302     */
303    // This is a GLSurfaceView.Renderer callback
304    @Override
305    public void onSurfaceChanged(GL10 gl1, int width, int height) {
306        Log.i(TAG, "onSurfaceChanged: " + width + "x" + height
307                + ", gl10: " + gl1.toString());
308        Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
309        GalleryUtils.setRenderThread();
310        if (DEBUG_PROFILE) {
311            Log.d(TAG, "Start profiling");
312            Profile.enable(20);  // take a sample every 20ms
313        }
314        GL11 gl = (GL11) gl1;
315        Utils.assertTrue(mGL == gl);
316
317        mCanvas.setSize(width, height);
318    }
319
320    private void outputFps() {
321        long now = System.nanoTime();
322        if (mFrameCountingStart == 0) {
323            mFrameCountingStart = now;
324        } else if ((now - mFrameCountingStart) > 1000000000) {
325            Log.d(TAG, "fps: " + (double) mFrameCount
326                    * 1000000000 / (now - mFrameCountingStart));
327            mFrameCountingStart = now;
328            mFrameCount = 0;
329        }
330        ++mFrameCount;
331    }
332
333    @Override
334    public void onDrawFrame(GL10 gl) {
335        AnimationTime.update();
336        long t0;
337        if (DEBUG_PROFILE_SLOW_ONLY) {
338            Profile.hold();
339            t0 = System.nanoTime();
340        }
341        mRenderLock.lock();
342
343        while (mFreeze) {
344            mFreezeCondition.awaitUninterruptibly();
345        }
346
347        try {
348            onDrawFrameLocked(gl);
349        } finally {
350            mRenderLock.unlock();
351        }
352
353        // We put a black cover View in front of the SurfaceView and hide it
354        // after the first draw. This prevents the SurfaceView being transparent
355        // before the first draw.
356        if (mFirstDraw) {
357            mFirstDraw = false;
358            post(new Runnable() {
359                    @Override
360                    public void run() {
361                        View root = getRootView();
362                        View cover = root.findViewById(R.id.gl_root_cover);
363                        cover.setVisibility(GONE);
364                    }
365                });
366        }
367
368        if (DEBUG_PROFILE_SLOW_ONLY) {
369            long t = System.nanoTime();
370            long durationInMs = (t - mLastDrawFinishTime) / 1000000;
371            long durationDrawInMs = (t - t0) / 1000000;
372            mLastDrawFinishTime = t;
373
374            if (durationInMs > 34) {  // 34ms -> we skipped at least 2 frames
375                Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
376                        durationInMs + ") -----");
377                Profile.commit();
378            } else {
379                Profile.drop();
380            }
381        }
382    }
383
384    private void onDrawFrameLocked(GL10 gl) {
385        if (DEBUG_FPS) outputFps();
386
387        // release the unbound textures and deleted buffers.
388        mCanvas.deleteRecycledResources();
389
390        // reset texture upload limit
391        UploadedTexture.resetUploadLimit();
392
393        mRenderRequested = false;
394
395        if ((mOrientationSource != null
396                && mDisplayRotation != mOrientationSource.getDisplayRotation())
397                || (mFlags & FLAG_NEED_LAYOUT) != 0) {
398            layoutContentPane();
399        }
400
401        mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
402        rotateCanvas(-mCompensation);
403        if (mContentView != null) {
404           mContentView.render(mCanvas);
405        }
406        mCanvas.restore();
407
408        if (!mAnimations.isEmpty()) {
409            long now = AnimationTime.get();
410            for (int i = 0, n = mAnimations.size(); i < n; i++) {
411                mAnimations.get(i).setStartTime(now);
412            }
413            mAnimations.clear();
414        }
415
416        if (UploadedTexture.uploadLimitReached()) {
417            requestRender();
418        }
419
420        synchronized (mIdleListeners) {
421            if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
422        }
423
424        if (DEBUG_INVALIDATE) {
425            mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
426            mInvalidateColor = ~mInvalidateColor;
427        }
428
429        if (DEBUG_DRAWING_STAT) {
430            mCanvas.dumpStatisticsAndClear();
431        }
432    }
433
434    private void rotateCanvas(int degrees) {
435        if (degrees == 0) return;
436        int w = getWidth();
437        int h = getHeight();
438        int cx = w / 2;
439        int cy = h / 2;
440        mCanvas.translate(cx, cy);
441        mCanvas.rotate(degrees, 0, 0, 1);
442        if (degrees % 180 != 0) {
443            mCanvas.translate(-cy, -cx);
444        } else {
445            mCanvas.translate(-cx, -cy);
446        }
447    }
448
449    @Override
450    public boolean dispatchTouchEvent(MotionEvent event) {
451        if (!isEnabled()) return false;
452
453        int action = event.getAction();
454        if (action == MotionEvent.ACTION_CANCEL
455                || action == MotionEvent.ACTION_UP) {
456            mInDownState = false;
457        } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
458            return false;
459        }
460
461        if (mCompensation != 0) {
462            event = MotionEventHelper.transformEvent(event, mCompensationMatrix);
463        }
464
465        mRenderLock.lock();
466        try {
467            // If this has been detached from root, we don't need to handle event
468            boolean handled = mContentView != null
469                    && mContentView.dispatchTouchEvent(event);
470            if (action == MotionEvent.ACTION_DOWN && handled) {
471                mInDownState = true;
472            }
473            return handled;
474        } finally {
475            mRenderLock.unlock();
476        }
477    }
478
479    private class IdleRunner implements Runnable {
480        // true if the idle runner is in the queue
481        private boolean mActive = false;
482
483        @Override
484        public void run() {
485            OnGLIdleListener listener;
486            synchronized (mIdleListeners) {
487                mActive = false;
488                if (mIdleListeners.isEmpty()) return;
489                listener = mIdleListeners.removeFirst();
490            }
491            mRenderLock.lock();
492            boolean keepInQueue;
493            try {
494                keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested);
495            } finally {
496                mRenderLock.unlock();
497            }
498            synchronized (mIdleListeners) {
499                if (keepInQueue) mIdleListeners.addLast(listener);
500                if (!mRenderRequested && !mIdleListeners.isEmpty()) enable();
501            }
502        }
503
504        public void enable() {
505            // Who gets the flag can add it to the queue
506            if (mActive) return;
507            mActive = true;
508            queueEvent(this);
509        }
510    }
511
512    @Override
513    public void lockRenderThread() {
514        mRenderLock.lock();
515    }
516
517    @Override
518    public void unlockRenderThread() {
519        mRenderLock.unlock();
520    }
521
522    @Override
523    public void onPause() {
524        unfreeze();
525        super.onPause();
526        if (DEBUG_PROFILE) {
527            Log.d(TAG, "Stop profiling");
528            Profile.disableAll();
529            Profile.dumpToFile("/sdcard/gallery.prof");
530            Profile.reset();
531        }
532    }
533
534    @Override
535    public void setOrientationSource(OrientationSource source) {
536        mOrientationSource = source;
537    }
538
539    @Override
540    public int getDisplayRotation() {
541        return mDisplayRotation;
542    }
543
544    @Override
545    public int getCompensation() {
546        return mCompensation;
547    }
548
549    @Override
550    public Matrix getCompensationMatrix() {
551        return mCompensationMatrix;
552    }
553
554    @Override
555    public void freeze() {
556        mRenderLock.lock();
557        mFreeze = true;
558        mRenderLock.unlock();
559    }
560
561    @Override
562    public void unfreeze() {
563        mRenderLock.lock();
564        mFreeze = false;
565        mFreezeCondition.signalAll();
566        mRenderLock.unlock();
567    }
568
569    @Override
570    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
571    public void setLightsOutMode(boolean enabled) {
572        if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
573
574        int flags = 0;
575        if (enabled) {
576            flags = STATUS_BAR_HIDDEN;
577            if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
578                flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
579            }
580        }
581        setSystemUiVisibility(flags);
582    }
583
584    // We need to unfreeze in the following methods and in onPause().
585    // These methods will wait on GLThread. If we have freezed the GLRootView,
586    // the GLThread will wait on main thread to call unfreeze and cause dead
587    // lock.
588    @Override
589    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
590        unfreeze();
591        super.surfaceChanged(holder, format, w, h);
592    }
593
594    @Override
595    public void surfaceCreated(SurfaceHolder holder) {
596        unfreeze();
597        super.surfaceCreated(holder);
598    }
599
600    @Override
601    public void surfaceDestroyed(SurfaceHolder holder) {
602        unfreeze();
603        super.surfaceDestroyed(holder);
604    }
605
606    @Override
607    protected void onDetachedFromWindow() {
608        unfreeze();
609        super.onDetachedFromWindow();
610    }
611
612    @Override
613    protected void finalize() throws Throwable {
614        try {
615            unfreeze();
616        } finally {
617            super.finalize();
618        }
619    }
620}
621