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