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