GLRootView.java revision 7d19f7f4281f232b9dceee4a5df390c03e2bd16b
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 com.android.gallery3d.anim.CanvasAnimation;
20import com.android.gallery3d.common.Utils;
21import com.android.gallery3d.util.GalleryUtils;
22import com.android.gallery3d.util.Profile;
23
24import android.app.Activity;
25import android.content.Context;
26import android.graphics.PixelFormat;
27import android.graphics.Rect;
28import android.opengl.GLSurfaceView;
29import android.os.Process;
30import android.os.SystemClock;
31import android.util.AttributeSet;
32import android.util.DisplayMetrics;
33import android.view.MotionEvent;
34
35import java.util.ArrayList;
36import java.util.LinkedList;
37import java.util.concurrent.locks.ReentrantLock;
38import javax.microedition.khronos.egl.EGLConfig;
39import javax.microedition.khronos.opengles.GL10;
40import javax.microedition.khronos.opengles.GL11;
41
42// The root component of all <code>GLView</code>s. The rendering is done in GL
43// thread while the event handling is done in the main thread.  To synchronize
44// the two threads, the entry points of this package need to synchronize on the
45// <code>GLRootView</code> instance unless it can be proved that the rendering
46// thread won't access the same thing as the method. The entry points include:
47// (1) The public methods of HeadUpDisplay
48// (2) The public methods of CameraHeadUpDisplay
49// (3) The overridden methods in GLRootView.
50public class GLRootView extends GLSurfaceView
51        implements GLSurfaceView.Renderer, GLRoot {
52    private static final String TAG = "GLRootView";
53
54    private static final boolean DEBUG_FPS = false;
55    private int mFrameCount = 0;
56    private long mFrameCountingStart = 0;
57
58    private static final boolean DEBUG_INVALIDATE = false;
59    private int mInvalidateColor = 0;
60
61    private static final boolean DEBUG_DRAWING_STAT = false;
62
63    private static final boolean DEBUG_PROFILE = false;
64    private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
65
66    private static final int FLAG_INITIALIZED = 1;
67    private static final int FLAG_NEED_LAYOUT = 2;
68
69    private GL11 mGL;
70    private GLCanvasImpl mCanvas;
71
72    private GLView mContentView;
73    private DisplayMetrics mDisplayMetrics;
74
75    private int mFlags = FLAG_NEED_LAYOUT;
76    private volatile boolean mRenderRequested = false;
77
78    private Rect mClipRect = new Rect();
79    private int mClipRetryCount = 0;
80
81    private final GalleryEGLConfigChooser mEglConfigChooser =
82            new GalleryEGLConfigChooser();
83
84    private final ArrayList<CanvasAnimation> mAnimations =
85            new ArrayList<CanvasAnimation>();
86
87    private final LinkedList<OnGLIdleListener> mIdleListeners =
88            new LinkedList<OnGLIdleListener>();
89
90    private final IdleRunner mIdleRunner = new IdleRunner();
91
92    private final ReentrantLock mRenderLock = new ReentrantLock();
93
94    private long mLastDrawFinishTime;
95    private boolean mInDownState = false;
96
97    public GLRootView(Context context) {
98        this(context, null);
99    }
100
101    public GLRootView(Context context, AttributeSet attrs) {
102        super(context, attrs);
103        mFlags |= FLAG_INITIALIZED;
104        setBackgroundDrawable(null);
105        setEGLConfigChooser(mEglConfigChooser);
106        setRenderer(this);
107        getHolder().setFormat(PixelFormat.RGB_565);
108        AnimationTime.setNow();
109
110        // Uncomment this to enable gl error check.
111        //setDebugFlags(DEBUG_CHECK_GL_ERROR);
112    }
113
114    public GalleryEGLConfigChooser getEGLConfigChooser() {
115        return mEglConfigChooser;
116    }
117
118    @Override
119    public boolean hasStencil() {
120        return getEGLConfigChooser().getStencilBits() > 0;
121    }
122
123    @Override
124    public void registerLaunchedAnimation(CanvasAnimation animation) {
125        // Register the newly launched animation so that we can set the start
126        // time more precisely. (Usually, it takes much longer for first
127        // rendering, so we set the animation start time as the time we
128        // complete rendering)
129        mAnimations.add(animation);
130    }
131
132    @Override
133    public void addOnGLIdleListener(OnGLIdleListener listener) {
134        synchronized (mIdleListeners) {
135            mIdleListeners.addLast(listener);
136            mIdleRunner.enable();
137        }
138    }
139
140    @Override
141    public void setContentPane(GLView content) {
142        if (mContentView == content) return;
143        if (mContentView != null) {
144            if (mInDownState) {
145                long now = SystemClock.uptimeMillis();
146                MotionEvent cancelEvent = MotionEvent.obtain(
147                        now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
148                mContentView.dispatchTouchEvent(cancelEvent);
149                cancelEvent.recycle();
150                mInDownState = false;
151            }
152            mContentView.detachFromRoot();
153            BasicTexture.yieldAllTextures();
154        }
155        mContentView = content;
156        if (content != null) {
157            content.attachToRoot(this);
158            requestLayoutContentPane();
159        }
160    }
161
162    public GLView getContentPane() {
163        return mContentView;
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        int width = getWidth();
198        int height = getHeight();
199        Log.i(TAG, "layout content pane " + width + "x" + height);
200        if (mContentView != null && width != 0 && height != 0) {
201            mContentView.layout(0, 0, width, height);
202        }
203        // Uncomment this to dump the view hierarchy.
204        //mContentView.dumpTree("");
205    }
206
207    @Override
208    protected void onLayout(
209            boolean changed, int left, int top, int right, int bottom) {
210        if (changed) requestLayoutContentPane();
211    }
212
213    /**
214     * Called when the context is created, possibly after automatic destruction.
215     */
216    // This is a GLSurfaceView.Renderer callback
217    @Override
218    public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
219        GL11 gl = (GL11) gl1;
220        if (mGL != null) {
221            // The GL Object has changed
222            Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
223        }
224        mGL = gl;
225        mCanvas = new GLCanvasImpl(gl);
226        if (DEBUG_FPS || DEBUG_PROFILE) {
227            setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
228        } else {
229            setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
230        }
231    }
232
233    /**
234     * Called when the OpenGL surface is recreated without destroying the
235     * context.
236     */
237    // This is a GLSurfaceView.Renderer callback
238    @Override
239    public void onSurfaceChanged(GL10 gl1, int width, int height) {
240        Log.i(TAG, "onSurfaceChanged: " + width + "x" + height
241                + ", gl10: " + gl1.toString());
242        Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
243        GalleryUtils.setRenderThread();
244        BasicTexture.invalidateAllTextures();
245        if (DEBUG_PROFILE) {
246            Log.d(TAG, "Start profiling");
247            Profile.enable(20);  // take a sample every 20ms
248        }
249        GL11 gl = (GL11) gl1;
250        Utils.assertTrue(mGL == gl);
251
252        mCanvas.setSize(width, height);
253
254        mClipRect.set(0, 0, width, height);
255        mClipRetryCount = 2;
256    }
257
258    private void outputFps() {
259        long now = System.nanoTime();
260        if (mFrameCountingStart == 0) {
261            mFrameCountingStart = now;
262        } else if ((now - mFrameCountingStart) > 1000000000) {
263            Log.d(TAG, "fps: " + (double) mFrameCount
264                    * 1000000000 / (now - mFrameCountingStart));
265            mFrameCountingStart = now;
266            mFrameCount = 0;
267        }
268        ++mFrameCount;
269    }
270
271    @Override
272    public void onDrawFrame(GL10 gl) {
273        long t0;
274        if (DEBUG_PROFILE_SLOW_ONLY) {
275            Profile.hold();
276            t0 = System.nanoTime();
277        }
278        mRenderLock.lock();
279        try {
280            onDrawFrameLocked(gl);
281        } finally {
282            mRenderLock.unlock();
283        }
284        if (DEBUG_PROFILE_SLOW_ONLY) {
285            long t = System.nanoTime();
286            long durationInMs = (t - mLastDrawFinishTime) / 1000000;
287            long durationDrawInMs = (t - t0) / 1000000;
288            mLastDrawFinishTime = t;
289
290            if (durationInMs > 34) {  // 34ms -> we skipped at least 2 frames
291                Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
292                        durationInMs + ") -----");
293                Profile.commit();
294            } else {
295                Profile.drop();
296            }
297        }
298    }
299
300    private void onDrawFrameLocked(GL10 gl) {
301        if (DEBUG_FPS) outputFps();
302
303        // release the unbound textures and deleted buffers.
304        mCanvas.deleteRecycledResources();
305
306        // reset texture upload limit
307        UploadedTexture.resetUploadLimit();
308
309        mRenderRequested = false;
310
311        if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
312
313        // OpenGL seems having a bug causing us not being able to reset the
314        // scissor box in "onSurfaceChanged()". We have to do it in the second
315        // onDrawFrame().
316        if (mClipRetryCount > 0) {
317            --mClipRetryCount;
318            Rect clip = mClipRect;
319            gl.glScissor(clip.left, clip.top, clip.width(), clip.height());
320        }
321
322        AnimationTime.setNow();
323        if (mContentView != null) {
324           mContentView.render(mCanvas);
325        }
326
327        if (!mAnimations.isEmpty()) {
328            long now = AnimationTime.get();
329            for (int i = 0, n = mAnimations.size(); i < n; i++) {
330                mAnimations.get(i).setStartTime(now);
331            }
332            mAnimations.clear();
333        }
334
335        if (UploadedTexture.uploadLimitReached()) {
336            requestRender();
337        }
338
339        synchronized (mIdleListeners) {
340            if (!mRenderRequested && !mIdleListeners.isEmpty()) {
341                mIdleRunner.enable();
342            }
343        }
344
345        if (DEBUG_INVALIDATE) {
346            mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
347            mInvalidateColor = ~mInvalidateColor;
348        }
349
350        if (DEBUG_DRAWING_STAT) {
351            mCanvas.dumpStatisticsAndClear();
352        }
353    }
354
355    @Override
356    public boolean dispatchTouchEvent(MotionEvent event) {
357        int action = event.getAction();
358        if (action == MotionEvent.ACTION_CANCEL
359                || action == MotionEvent.ACTION_UP) {
360            mInDownState = false;
361        } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
362            return false;
363        }
364        mRenderLock.lock();
365        try {
366            // If this has been detached from root, we don't need to handle event
367            boolean handled = mContentView != null
368                    && mContentView.dispatchTouchEvent(event);
369            if (action == MotionEvent.ACTION_DOWN && handled) {
370                mInDownState = true;
371            }
372            return handled;
373        } finally {
374            mRenderLock.unlock();
375        }
376    }
377
378    public DisplayMetrics getDisplayMetrics() {
379        if (mDisplayMetrics == null) {
380            mDisplayMetrics = new DisplayMetrics();
381            ((Activity) getContext()).getWindowManager()
382                    .getDefaultDisplay().getMetrics(mDisplayMetrics);
383        }
384        return mDisplayMetrics;
385    }
386
387    public GLCanvas getCanvas() {
388        return mCanvas;
389    }
390
391    private class IdleRunner implements Runnable {
392        // true if the idle runner is in the queue
393        private boolean mActive = false;
394
395        @Override
396        public void run() {
397            OnGLIdleListener listener;
398            synchronized (mIdleListeners) {
399                mActive = false;
400                if (mRenderRequested) return;
401                if (mIdleListeners.isEmpty()) return;
402                listener = mIdleListeners.removeFirst();
403            }
404            mRenderLock.lock();
405            try {
406                if (!listener.onGLIdle(GLRootView.this, mCanvas)) return;
407            } finally {
408                mRenderLock.unlock();
409            }
410            synchronized (mIdleListeners) {
411                mIdleListeners.addLast(listener);
412                enable();
413            }
414        }
415
416        public void enable() {
417            // Who gets the flag can add it to the queue
418            if (mActive) return;
419            mActive = true;
420            queueEvent(this);
421        }
422    }
423
424    @Override
425    public void lockRenderThread() {
426        mRenderLock.lock();
427    }
428
429    @Override
430    public void unlockRenderThread() {
431        mRenderLock.unlock();
432    }
433
434    @Override
435    public void onPause() {
436        super.onPause();
437        if (DEBUG_PROFILE) {
438            Log.d(TAG, "Stop profiling");
439            Profile.disableAll();
440            Profile.dumpToFile("/sdcard/gallery.prof");
441            Profile.reset();
442        }
443    }
444}
445