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