GLRootView.java revision 7b83fb8e3a8978b33a6b9bfc56d85fe2c1a9cf06
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 GLCanvas mCanvas;
71
72    private GLView mContentView;
73
74    private int mFlags = FLAG_NEED_LAYOUT;
75    private volatile boolean mRenderRequested = false;
76
77    private Rect mClipRect = new Rect();
78    private int mClipRetryCount = 0;
79
80    private final GalleryEGLConfigChooser mEglConfigChooser =
81            new GalleryEGLConfigChooser();
82
83    private final ArrayList<CanvasAnimation> mAnimations =
84            new ArrayList<CanvasAnimation>();
85
86    private final LinkedList<OnGLIdleListener> mIdleListeners =
87            new LinkedList<OnGLIdleListener>();
88
89    private final IdleRunner mIdleRunner = new IdleRunner();
90
91    private final ReentrantLock mRenderLock = new ReentrantLock();
92
93    private long mLastDrawFinishTime;
94    private boolean mInDownState = false;
95
96    public GLRootView(Context context) {
97        this(context, null);
98    }
99
100    public GLRootView(Context context, AttributeSet attrs) {
101        super(context, attrs);
102        mFlags |= FLAG_INITIALIZED;
103        setBackgroundDrawable(null);
104        setEGLConfigChooser(mEglConfigChooser);
105        setRenderer(this);
106        getHolder().setFormat(PixelFormat.RGB_565);
107        AnimationTime.setNow();
108
109        // Uncomment this to enable gl error check.
110        //setDebugFlags(DEBUG_CHECK_GL_ERROR);
111    }
112
113    @Override
114    public void registerLaunchedAnimation(CanvasAnimation animation) {
115        // Register the newly launched animation so that we can set the start
116        // time more precisely. (Usually, it takes much longer for first
117        // rendering, so we set the animation start time as the time we
118        // complete rendering)
119        mAnimations.add(animation);
120    }
121
122    @Override
123    public void addOnGLIdleListener(OnGLIdleListener listener) {
124        synchronized (mIdleListeners) {
125            mIdleListeners.addLast(listener);
126            mIdleRunner.enable();
127        }
128    }
129
130    @Override
131    public void setContentPane(GLView content) {
132        if (mContentView == content) return;
133        if (mContentView != null) {
134            if (mInDownState) {
135                long now = SystemClock.uptimeMillis();
136                MotionEvent cancelEvent = MotionEvent.obtain(
137                        now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
138                mContentView.dispatchTouchEvent(cancelEvent);
139                cancelEvent.recycle();
140                mInDownState = false;
141            }
142            mContentView.detachFromRoot();
143            BasicTexture.yieldAllTextures();
144        }
145        mContentView = content;
146        if (content != null) {
147            content.attachToRoot(this);
148            requestLayoutContentPane();
149        }
150    }
151
152    @Override
153    public void requestRender() {
154        if (DEBUG_INVALIDATE) {
155            StackTraceElement e = Thread.currentThread().getStackTrace()[4];
156            String caller = e.getFileName() + ":" + e.getLineNumber() + " ";
157            Log.d(TAG, "invalidate: " + caller);
158        }
159        if (mRenderRequested) return;
160        mRenderRequested = true;
161        super.requestRender();
162    }
163
164    @Override
165    public void requestLayoutContentPane() {
166        mRenderLock.lock();
167        try {
168            if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
169
170            // "View" system will invoke onLayout() for initialization(bug ?), we
171            // have to ignore it since the GLThread is not ready yet.
172            if ((mFlags & FLAG_INITIALIZED) == 0) return;
173
174            mFlags |= FLAG_NEED_LAYOUT;
175            requestRender();
176        } finally {
177            mRenderLock.unlock();
178        }
179    }
180
181    private void layoutContentPane() {
182        mFlags &= ~FLAG_NEED_LAYOUT;
183        int width = getWidth();
184        int height = getHeight();
185        Log.i(TAG, "layout content pane " + width + "x" + height);
186        if (mContentView != null && width != 0 && height != 0) {
187            mContentView.layout(0, 0, width, height);
188        }
189        // Uncomment this to dump the view hierarchy.
190        //mContentView.dumpTree("");
191    }
192
193    @Override
194    protected void onLayout(
195            boolean changed, int left, int top, int right, int bottom) {
196        if (changed) requestLayoutContentPane();
197    }
198
199    /**
200     * Called when the context is created, possibly after automatic destruction.
201     */
202    // This is a GLSurfaceView.Renderer callback
203    @Override
204    public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
205        GL11 gl = (GL11) gl1;
206        if (mGL != null) {
207            // The GL Object has changed
208            Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
209        }
210        mGL = gl;
211        mCanvas = new GLCanvasImpl(gl);
212        if (DEBUG_FPS || DEBUG_PROFILE) {
213            setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
214        } else {
215            setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
216        }
217    }
218
219    /**
220     * Called when the OpenGL surface is recreated without destroying the
221     * context.
222     */
223    // This is a GLSurfaceView.Renderer callback
224    @Override
225    public void onSurfaceChanged(GL10 gl1, int width, int height) {
226        Log.i(TAG, "onSurfaceChanged: " + width + "x" + height
227                + ", gl10: " + gl1.toString());
228        Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
229        GalleryUtils.setRenderThread();
230        BasicTexture.invalidateAllTextures();
231        if (DEBUG_PROFILE) {
232            Log.d(TAG, "Start profiling");
233            Profile.enable(20);  // take a sample every 20ms
234        }
235        GL11 gl = (GL11) gl1;
236        Utils.assertTrue(mGL == gl);
237
238        mCanvas.setSize(width, height);
239
240        mClipRect.set(0, 0, width, height);
241        mClipRetryCount = 2;
242    }
243
244    private void outputFps() {
245        long now = System.nanoTime();
246        if (mFrameCountingStart == 0) {
247            mFrameCountingStart = now;
248        } else if ((now - mFrameCountingStart) > 1000000000) {
249            Log.d(TAG, "fps: " + (double) mFrameCount
250                    * 1000000000 / (now - mFrameCountingStart));
251            mFrameCountingStart = now;
252            mFrameCount = 0;
253        }
254        ++mFrameCount;
255    }
256
257    @Override
258    public void onDrawFrame(GL10 gl) {
259        long t0;
260        if (DEBUG_PROFILE_SLOW_ONLY) {
261            Profile.hold();
262            t0 = System.nanoTime();
263        }
264        mRenderLock.lock();
265        try {
266            onDrawFrameLocked(gl);
267        } finally {
268            mRenderLock.unlock();
269        }
270        if (DEBUG_PROFILE_SLOW_ONLY) {
271            long t = System.nanoTime();
272            long durationInMs = (t - mLastDrawFinishTime) / 1000000;
273            long durationDrawInMs = (t - t0) / 1000000;
274            mLastDrawFinishTime = t;
275
276            if (durationInMs > 34) {  // 34ms -> we skipped at least 2 frames
277                Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
278                        durationInMs + ") -----");
279                Profile.commit();
280            } else {
281                Profile.drop();
282            }
283        }
284    }
285
286    private void onDrawFrameLocked(GL10 gl) {
287        if (DEBUG_FPS) outputFps();
288
289        // release the unbound textures and deleted buffers.
290        mCanvas.deleteRecycledResources();
291
292        // reset texture upload limit
293        UploadedTexture.resetUploadLimit();
294
295        mRenderRequested = false;
296
297        if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
298
299        // OpenGL seems having a bug causing us not being able to reset the
300        // scissor box in "onSurfaceChanged()". We have to do it in the second
301        // onDrawFrame().
302        if (mClipRetryCount > 0) {
303            --mClipRetryCount;
304            Rect clip = mClipRect;
305            gl.glScissor(clip.left, clip.top, clip.width(), clip.height());
306        }
307
308        AnimationTime.setNow();
309        if (mContentView != null) {
310           mContentView.render(mCanvas);
311        }
312
313        if (!mAnimations.isEmpty()) {
314            long now = AnimationTime.get();
315            for (int i = 0, n = mAnimations.size(); i < n; i++) {
316                mAnimations.get(i).setStartTime(now);
317            }
318            mAnimations.clear();
319        }
320
321        if (UploadedTexture.uploadLimitReached()) {
322            requestRender();
323        }
324
325        synchronized (mIdleListeners) {
326            if (!mRenderRequested && !mIdleListeners.isEmpty()) {
327                mIdleRunner.enable();
328            }
329        }
330
331        if (DEBUG_INVALIDATE) {
332            mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
333            mInvalidateColor = ~mInvalidateColor;
334        }
335
336        if (DEBUG_DRAWING_STAT) {
337            mCanvas.dumpStatisticsAndClear();
338        }
339    }
340
341    @Override
342    public boolean dispatchTouchEvent(MotionEvent event) {
343        int action = event.getAction();
344        if (action == MotionEvent.ACTION_CANCEL
345                || action == MotionEvent.ACTION_UP) {
346            mInDownState = false;
347        } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
348            return false;
349        }
350        mRenderLock.lock();
351        try {
352            // If this has been detached from root, we don't need to handle event
353            boolean handled = mContentView != null
354                    && mContentView.dispatchTouchEvent(event);
355            if (action == MotionEvent.ACTION_DOWN && handled) {
356                mInDownState = true;
357            }
358            return handled;
359        } finally {
360            mRenderLock.unlock();
361        }
362    }
363
364    private class IdleRunner implements Runnable {
365        // true if the idle runner is in the queue
366        private boolean mActive = false;
367
368        @Override
369        public void run() {
370            OnGLIdleListener listener;
371            synchronized (mIdleListeners) {
372                mActive = false;
373                if (mRenderRequested) return;
374                if (mIdleListeners.isEmpty()) return;
375                listener = mIdleListeners.removeFirst();
376            }
377            mRenderLock.lock();
378            try {
379                if (!listener.onGLIdle(GLRootView.this, mCanvas)) return;
380            } finally {
381                mRenderLock.unlock();
382            }
383            synchronized (mIdleListeners) {
384                mIdleListeners.addLast(listener);
385                enable();
386            }
387        }
388
389        public void enable() {
390            // Who gets the flag can add it to the queue
391            if (mActive) return;
392            mActive = true;
393            queueEvent(this);
394        }
395    }
396
397    @Override
398    public void lockRenderThread() {
399        mRenderLock.lock();
400    }
401
402    @Override
403    public void unlockRenderThread() {
404        mRenderLock.unlock();
405    }
406
407    @Override
408    public void onPause() {
409        super.onPause();
410        if (DEBUG_PROFILE) {
411            Log.d(TAG, "Stop profiling");
412            Profile.disableAll();
413            Profile.dumpToFile("/sdcard/gallery.prof");
414            Profile.reset();
415        }
416    }
417}
418