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