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