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 GLRootView(Context context)114 public GLRootView(Context context) { 115 this(context, null); 116 } 117 GLRootView(Context context, AttributeSet attrs)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 registerLaunchedAnimation(CanvasAnimation animation)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 addOnGLIdleListener(OnGLIdleListener listener)149 public void addOnGLIdleListener(OnGLIdleListener listener) { 150 synchronized (mIdleListeners) { 151 mIdleListeners.addLast(listener); 152 mIdleRunner.enable(); 153 } 154 } 155 156 @Override setContentPane(GLView content)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 requestRenderForced()179 public void requestRenderForced() { 180 superRequestRender(); 181 } 182 183 @Override requestRender()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 superRequestRender()206 private void superRequestRender() { 207 super.requestRender(); 208 } 209 210 @Override requestLayoutContentPane()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 layoutContentPane()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 onLayout( boolean changed, int left, int top, int right, int bottom)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 onSurfaceCreated(GL10 gl1, EGLConfig config)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 onSurfaceChanged(GL10 gl1, int width, int height)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 outputFps()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 onDrawFrame(GL10 gl)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 onDrawFrameLocked(GL10 gl)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 } else { 413 // Make sure we always draw something to prevent displaying garbage 414 mCanvas.clearBuffer(); 415 } 416 mCanvas.restore(); 417 418 if (!mAnimations.isEmpty()) { 419 long now = AnimationTime.get(); 420 for (int i = 0, n = mAnimations.size(); i < n; i++) { 421 mAnimations.get(i).setStartTime(now); 422 } 423 mAnimations.clear(); 424 } 425 426 if (UploadedTexture.uploadLimitReached()) { 427 requestRender(); 428 } 429 430 synchronized (mIdleListeners) { 431 if (!mIdleListeners.isEmpty()) mIdleRunner.enable(); 432 } 433 434 if (DEBUG_INVALIDATE) { 435 mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor); 436 mInvalidateColor = ~mInvalidateColor; 437 } 438 439 if (DEBUG_DRAWING_STAT) { 440 mCanvas.dumpStatisticsAndClear(); 441 } 442 } 443 rotateCanvas(int degrees)444 private void rotateCanvas(int degrees) { 445 if (degrees == 0) return; 446 int w = getWidth(); 447 int h = getHeight(); 448 int cx = w / 2; 449 int cy = h / 2; 450 mCanvas.translate(cx, cy); 451 mCanvas.rotate(degrees, 0, 0, 1); 452 if (degrees % 180 != 0) { 453 mCanvas.translate(-cy, -cx); 454 } else { 455 mCanvas.translate(-cx, -cy); 456 } 457 } 458 459 @Override dispatchTouchEvent(MotionEvent event)460 public boolean dispatchTouchEvent(MotionEvent event) { 461 if (!isEnabled()) return false; 462 463 int action = event.getAction(); 464 if (action == MotionEvent.ACTION_CANCEL 465 || action == MotionEvent.ACTION_UP) { 466 mInDownState = false; 467 } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) { 468 return false; 469 } 470 471 if (mCompensation != 0) { 472 event = MotionEventHelper.transformEvent(event, mCompensationMatrix); 473 } 474 475 mRenderLock.lock(); 476 try { 477 // If this has been detached from root, we don't need to handle event 478 boolean handled = mContentView != null 479 && mContentView.dispatchTouchEvent(event); 480 if (action == MotionEvent.ACTION_DOWN && handled) { 481 mInDownState = true; 482 } 483 return handled; 484 } finally { 485 mRenderLock.unlock(); 486 } 487 } 488 489 private class IdleRunner implements Runnable { 490 // true if the idle runner is in the queue 491 private boolean mActive = false; 492 493 @Override run()494 public void run() { 495 OnGLIdleListener listener; 496 synchronized (mIdleListeners) { 497 mActive = false; 498 if (mIdleListeners.isEmpty()) return; 499 listener = mIdleListeners.removeFirst(); 500 } 501 mRenderLock.lock(); 502 boolean keepInQueue; 503 try { 504 keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested); 505 } finally { 506 mRenderLock.unlock(); 507 } 508 synchronized (mIdleListeners) { 509 if (keepInQueue) mIdleListeners.addLast(listener); 510 if (!mRenderRequested && !mIdleListeners.isEmpty()) enable(); 511 } 512 } 513 enable()514 public void enable() { 515 // Who gets the flag can add it to the queue 516 if (mActive) return; 517 mActive = true; 518 queueEvent(this); 519 } 520 } 521 522 @Override lockRenderThread()523 public void lockRenderThread() { 524 mRenderLock.lock(); 525 } 526 527 @Override unlockRenderThread()528 public void unlockRenderThread() { 529 mRenderLock.unlock(); 530 } 531 532 @Override onPause()533 public void onPause() { 534 unfreeze(); 535 super.onPause(); 536 if (DEBUG_PROFILE) { 537 Log.d(TAG, "Stop profiling"); 538 Profile.disableAll(); 539 Profile.dumpToFile("/sdcard/gallery.prof"); 540 Profile.reset(); 541 } 542 } 543 544 @Override setOrientationSource(OrientationSource source)545 public void setOrientationSource(OrientationSource source) { 546 mOrientationSource = source; 547 } 548 549 @Override getDisplayRotation()550 public int getDisplayRotation() { 551 return mDisplayRotation; 552 } 553 554 @Override getCompensation()555 public int getCompensation() { 556 return mCompensation; 557 } 558 559 @Override getCompensationMatrix()560 public Matrix getCompensationMatrix() { 561 return mCompensationMatrix; 562 } 563 564 @Override freeze()565 public void freeze() { 566 mRenderLock.lock(); 567 mFreeze = true; 568 mRenderLock.unlock(); 569 } 570 571 @Override unfreeze()572 public void unfreeze() { 573 mRenderLock.lock(); 574 mFreeze = false; 575 mFreezeCondition.signalAll(); 576 mRenderLock.unlock(); 577 } 578 579 @Override 580 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) setLightsOutMode(boolean enabled)581 public void setLightsOutMode(boolean enabled) { 582 if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return; 583 584 int flags = 0; 585 if (enabled) { 586 flags = STATUS_BAR_HIDDEN; 587 if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) { 588 flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE); 589 } 590 } 591 setSystemUiVisibility(flags); 592 } 593 594 // We need to unfreeze in the following methods and in onPause(). 595 // These methods will wait on GLThread. If we have freezed the GLRootView, 596 // the GLThread will wait on main thread to call unfreeze and cause dead 597 // lock. 598 @Override surfaceChanged(SurfaceHolder holder, int format, int w, int h)599 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 600 unfreeze(); 601 super.surfaceChanged(holder, format, w, h); 602 } 603 604 @Override surfaceCreated(SurfaceHolder holder)605 public void surfaceCreated(SurfaceHolder holder) { 606 unfreeze(); 607 super.surfaceCreated(holder); 608 } 609 610 @Override surfaceDestroyed(SurfaceHolder holder)611 public void surfaceDestroyed(SurfaceHolder holder) { 612 unfreeze(); 613 super.surfaceDestroyed(holder); 614 } 615 616 @Override onDetachedFromWindow()617 protected void onDetachedFromWindow() { 618 unfreeze(); 619 super.onDetachedFromWindow(); 620 } 621 622 @Override finalize()623 protected void finalize() throws Throwable { 624 try { 625 unfreeze(); 626 } finally { 627 super.finalize(); 628 } 629 } 630 } 631