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