GLRootView.java revision 7d19f7f4281f232b9dceee4a5df390c03e2bd16b
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 com.android.gallery3d.anim.CanvasAnimation; 20import com.android.gallery3d.common.Utils; 21import com.android.gallery3d.util.GalleryUtils; 22import com.android.gallery3d.util.Profile; 23 24import android.app.Activity; 25import android.content.Context; 26import android.graphics.PixelFormat; 27import android.graphics.Rect; 28import android.opengl.GLSurfaceView; 29import android.os.Process; 30import android.os.SystemClock; 31import android.util.AttributeSet; 32import android.util.DisplayMetrics; 33import android.view.MotionEvent; 34 35import java.util.ArrayList; 36import java.util.LinkedList; 37import java.util.concurrent.locks.ReentrantLock; 38import javax.microedition.khronos.egl.EGLConfig; 39import javax.microedition.khronos.opengles.GL10; 40import javax.microedition.khronos.opengles.GL11; 41 42// The root component of all <code>GLView</code>s. The rendering is done in GL 43// thread while the event handling is done in the main thread. To synchronize 44// the two threads, the entry points of this package need to synchronize on the 45// <code>GLRootView</code> instance unless it can be proved that the rendering 46// thread won't access the same thing as the method. The entry points include: 47// (1) The public methods of HeadUpDisplay 48// (2) The public methods of CameraHeadUpDisplay 49// (3) The overridden methods in GLRootView. 50public class GLRootView extends GLSurfaceView 51 implements GLSurfaceView.Renderer, GLRoot { 52 private static final String TAG = "GLRootView"; 53 54 private static final boolean DEBUG_FPS = false; 55 private int mFrameCount = 0; 56 private long mFrameCountingStart = 0; 57 58 private static final boolean DEBUG_INVALIDATE = false; 59 private int mInvalidateColor = 0; 60 61 private static final boolean DEBUG_DRAWING_STAT = false; 62 63 private static final boolean DEBUG_PROFILE = false; 64 private static final boolean DEBUG_PROFILE_SLOW_ONLY = false; 65 66 private static final int FLAG_INITIALIZED = 1; 67 private static final int FLAG_NEED_LAYOUT = 2; 68 69 private GL11 mGL; 70 private GLCanvasImpl mCanvas; 71 72 private GLView mContentView; 73 private DisplayMetrics mDisplayMetrics; 74 75 private int mFlags = FLAG_NEED_LAYOUT; 76 private volatile boolean mRenderRequested = false; 77 78 private Rect mClipRect = new Rect(); 79 private int mClipRetryCount = 0; 80 81 private final GalleryEGLConfigChooser mEglConfigChooser = 82 new GalleryEGLConfigChooser(); 83 84 private final ArrayList<CanvasAnimation> mAnimations = 85 new ArrayList<CanvasAnimation>(); 86 87 private final LinkedList<OnGLIdleListener> mIdleListeners = 88 new LinkedList<OnGLIdleListener>(); 89 90 private final IdleRunner mIdleRunner = new IdleRunner(); 91 92 private final ReentrantLock mRenderLock = new ReentrantLock(); 93 94 private long mLastDrawFinishTime; 95 private boolean mInDownState = false; 96 97 public GLRootView(Context context) { 98 this(context, null); 99 } 100 101 public GLRootView(Context context, AttributeSet attrs) { 102 super(context, attrs); 103 mFlags |= FLAG_INITIALIZED; 104 setBackgroundDrawable(null); 105 setEGLConfigChooser(mEglConfigChooser); 106 setRenderer(this); 107 getHolder().setFormat(PixelFormat.RGB_565); 108 AnimationTime.setNow(); 109 110 // Uncomment this to enable gl error check. 111 //setDebugFlags(DEBUG_CHECK_GL_ERROR); 112 } 113 114 public GalleryEGLConfigChooser getEGLConfigChooser() { 115 return mEglConfigChooser; 116 } 117 118 @Override 119 public boolean hasStencil() { 120 return getEGLConfigChooser().getStencilBits() > 0; 121 } 122 123 @Override 124 public void registerLaunchedAnimation(CanvasAnimation animation) { 125 // Register the newly launched animation so that we can set the start 126 // time more precisely. (Usually, it takes much longer for first 127 // rendering, so we set the animation start time as the time we 128 // complete rendering) 129 mAnimations.add(animation); 130 } 131 132 @Override 133 public void addOnGLIdleListener(OnGLIdleListener listener) { 134 synchronized (mIdleListeners) { 135 mIdleListeners.addLast(listener); 136 mIdleRunner.enable(); 137 } 138 } 139 140 @Override 141 public void setContentPane(GLView content) { 142 if (mContentView == content) return; 143 if (mContentView != null) { 144 if (mInDownState) { 145 long now = SystemClock.uptimeMillis(); 146 MotionEvent cancelEvent = MotionEvent.obtain( 147 now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); 148 mContentView.dispatchTouchEvent(cancelEvent); 149 cancelEvent.recycle(); 150 mInDownState = false; 151 } 152 mContentView.detachFromRoot(); 153 BasicTexture.yieldAllTextures(); 154 } 155 mContentView = content; 156 if (content != null) { 157 content.attachToRoot(this); 158 requestLayoutContentPane(); 159 } 160 } 161 162 public GLView getContentPane() { 163 return mContentView; 164 } 165 166 @Override 167 public void requestRender() { 168 if (DEBUG_INVALIDATE) { 169 StackTraceElement e = Thread.currentThread().getStackTrace()[4]; 170 String caller = e.getFileName() + ":" + e.getLineNumber() + " "; 171 Log.d(TAG, "invalidate: " + caller); 172 } 173 if (mRenderRequested) return; 174 mRenderRequested = true; 175 super.requestRender(); 176 } 177 178 @Override 179 public void requestLayoutContentPane() { 180 mRenderLock.lock(); 181 try { 182 if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return; 183 184 // "View" system will invoke onLayout() for initialization(bug ?), we 185 // have to ignore it since the GLThread is not ready yet. 186 if ((mFlags & FLAG_INITIALIZED) == 0) return; 187 188 mFlags |= FLAG_NEED_LAYOUT; 189 requestRender(); 190 } finally { 191 mRenderLock.unlock(); 192 } 193 } 194 195 private void layoutContentPane() { 196 mFlags &= ~FLAG_NEED_LAYOUT; 197 int width = getWidth(); 198 int height = getHeight(); 199 Log.i(TAG, "layout content pane " + width + "x" + height); 200 if (mContentView != null && width != 0 && height != 0) { 201 mContentView.layout(0, 0, width, height); 202 } 203 // Uncomment this to dump the view hierarchy. 204 //mContentView.dumpTree(""); 205 } 206 207 @Override 208 protected void onLayout( 209 boolean changed, int left, int top, int right, int bottom) { 210 if (changed) requestLayoutContentPane(); 211 } 212 213 /** 214 * Called when the context is created, possibly after automatic destruction. 215 */ 216 // This is a GLSurfaceView.Renderer callback 217 @Override 218 public void onSurfaceCreated(GL10 gl1, EGLConfig config) { 219 GL11 gl = (GL11) gl1; 220 if (mGL != null) { 221 // The GL Object has changed 222 Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl); 223 } 224 mGL = gl; 225 mCanvas = new GLCanvasImpl(gl); 226 if (DEBUG_FPS || DEBUG_PROFILE) { 227 setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); 228 } else { 229 setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); 230 } 231 } 232 233 /** 234 * Called when the OpenGL surface is recreated without destroying the 235 * context. 236 */ 237 // This is a GLSurfaceView.Renderer callback 238 @Override 239 public void onSurfaceChanged(GL10 gl1, int width, int height) { 240 Log.i(TAG, "onSurfaceChanged: " + width + "x" + height 241 + ", gl10: " + gl1.toString()); 242 Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); 243 GalleryUtils.setRenderThread(); 244 BasicTexture.invalidateAllTextures(); 245 if (DEBUG_PROFILE) { 246 Log.d(TAG, "Start profiling"); 247 Profile.enable(20); // take a sample every 20ms 248 } 249 GL11 gl = (GL11) gl1; 250 Utils.assertTrue(mGL == gl); 251 252 mCanvas.setSize(width, height); 253 254 mClipRect.set(0, 0, width, height); 255 mClipRetryCount = 2; 256 } 257 258 private void outputFps() { 259 long now = System.nanoTime(); 260 if (mFrameCountingStart == 0) { 261 mFrameCountingStart = now; 262 } else if ((now - mFrameCountingStart) > 1000000000) { 263 Log.d(TAG, "fps: " + (double) mFrameCount 264 * 1000000000 / (now - mFrameCountingStart)); 265 mFrameCountingStart = now; 266 mFrameCount = 0; 267 } 268 ++mFrameCount; 269 } 270 271 @Override 272 public void onDrawFrame(GL10 gl) { 273 long t0; 274 if (DEBUG_PROFILE_SLOW_ONLY) { 275 Profile.hold(); 276 t0 = System.nanoTime(); 277 } 278 mRenderLock.lock(); 279 try { 280 onDrawFrameLocked(gl); 281 } finally { 282 mRenderLock.unlock(); 283 } 284 if (DEBUG_PROFILE_SLOW_ONLY) { 285 long t = System.nanoTime(); 286 long durationInMs = (t - mLastDrawFinishTime) / 1000000; 287 long durationDrawInMs = (t - t0) / 1000000; 288 mLastDrawFinishTime = t; 289 290 if (durationInMs > 34) { // 34ms -> we skipped at least 2 frames 291 Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" + 292 durationInMs + ") -----"); 293 Profile.commit(); 294 } else { 295 Profile.drop(); 296 } 297 } 298 } 299 300 private void onDrawFrameLocked(GL10 gl) { 301 if (DEBUG_FPS) outputFps(); 302 303 // release the unbound textures and deleted buffers. 304 mCanvas.deleteRecycledResources(); 305 306 // reset texture upload limit 307 UploadedTexture.resetUploadLimit(); 308 309 mRenderRequested = false; 310 311 if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane(); 312 313 // OpenGL seems having a bug causing us not being able to reset the 314 // scissor box in "onSurfaceChanged()". We have to do it in the second 315 // onDrawFrame(). 316 if (mClipRetryCount > 0) { 317 --mClipRetryCount; 318 Rect clip = mClipRect; 319 gl.glScissor(clip.left, clip.top, clip.width(), clip.height()); 320 } 321 322 AnimationTime.setNow(); 323 if (mContentView != null) { 324 mContentView.render(mCanvas); 325 } 326 327 if (!mAnimations.isEmpty()) { 328 long now = AnimationTime.get(); 329 for (int i = 0, n = mAnimations.size(); i < n; i++) { 330 mAnimations.get(i).setStartTime(now); 331 } 332 mAnimations.clear(); 333 } 334 335 if (UploadedTexture.uploadLimitReached()) { 336 requestRender(); 337 } 338 339 synchronized (mIdleListeners) { 340 if (!mRenderRequested && !mIdleListeners.isEmpty()) { 341 mIdleRunner.enable(); 342 } 343 } 344 345 if (DEBUG_INVALIDATE) { 346 mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor); 347 mInvalidateColor = ~mInvalidateColor; 348 } 349 350 if (DEBUG_DRAWING_STAT) { 351 mCanvas.dumpStatisticsAndClear(); 352 } 353 } 354 355 @Override 356 public boolean dispatchTouchEvent(MotionEvent event) { 357 int action = event.getAction(); 358 if (action == MotionEvent.ACTION_CANCEL 359 || action == MotionEvent.ACTION_UP) { 360 mInDownState = false; 361 } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) { 362 return false; 363 } 364 mRenderLock.lock(); 365 try { 366 // If this has been detached from root, we don't need to handle event 367 boolean handled = mContentView != null 368 && mContentView.dispatchTouchEvent(event); 369 if (action == MotionEvent.ACTION_DOWN && handled) { 370 mInDownState = true; 371 } 372 return handled; 373 } finally { 374 mRenderLock.unlock(); 375 } 376 } 377 378 public DisplayMetrics getDisplayMetrics() { 379 if (mDisplayMetrics == null) { 380 mDisplayMetrics = new DisplayMetrics(); 381 ((Activity) getContext()).getWindowManager() 382 .getDefaultDisplay().getMetrics(mDisplayMetrics); 383 } 384 return mDisplayMetrics; 385 } 386 387 public GLCanvas getCanvas() { 388 return mCanvas; 389 } 390 391 private class IdleRunner implements Runnable { 392 // true if the idle runner is in the queue 393 private boolean mActive = false; 394 395 @Override 396 public void run() { 397 OnGLIdleListener listener; 398 synchronized (mIdleListeners) { 399 mActive = false; 400 if (mRenderRequested) return; 401 if (mIdleListeners.isEmpty()) return; 402 listener = mIdleListeners.removeFirst(); 403 } 404 mRenderLock.lock(); 405 try { 406 if (!listener.onGLIdle(GLRootView.this, mCanvas)) return; 407 } finally { 408 mRenderLock.unlock(); 409 } 410 synchronized (mIdleListeners) { 411 mIdleListeners.addLast(listener); 412 enable(); 413 } 414 } 415 416 public void enable() { 417 // Who gets the flag can add it to the queue 418 if (mActive) return; 419 mActive = true; 420 queueEvent(this); 421 } 422 } 423 424 @Override 425 public void lockRenderThread() { 426 mRenderLock.lock(); 427 } 428 429 @Override 430 public void unlockRenderThread() { 431 mRenderLock.unlock(); 432 } 433 434 @Override 435 public void onPause() { 436 super.onPause(); 437 if (DEBUG_PROFILE) { 438 Log.d(TAG, "Stop profiling"); 439 Profile.disableAll(); 440 Profile.dumpToFile("/sdcard/gallery.prof"); 441 Profile.reset(); 442 } 443 } 444} 445