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