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