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