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