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