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