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