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