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