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