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.camera.ui; 18 19 import com.android.camera.Util; 20 21 import android.app.Activity; 22 import android.content.Context; 23 import android.graphics.Matrix; 24 import android.graphics.PixelFormat; 25 import android.opengl.GLSurfaceView; 26 import android.opengl.GLU; 27 import android.os.ConditionVariable; 28 import android.os.Looper; 29 import android.os.Process; 30 import android.os.SystemClock; 31 import android.util.AttributeSet; 32 import android.util.DisplayMetrics; 33 import android.util.Log; 34 import android.view.MotionEvent; 35 import android.view.animation.Animation; 36 import android.view.animation.Transformation; 37 38 import java.nio.ByteBuffer; 39 import java.nio.ByteOrder; 40 import java.util.ArrayList; 41 import java.util.Stack; 42 import java.util.concurrent.Callable; 43 import java.util.concurrent.FutureTask; 44 45 import javax.microedition.khronos.egl.EGLConfig; 46 import javax.microedition.khronos.opengles.GL10; 47 import javax.microedition.khronos.opengles.GL11; 48 import javax.microedition.khronos.opengles.GL11Ext; 49 50 public class GLRootView extends GLSurfaceView 51 implements GLSurfaceView.Renderer { 52 private static final String TAG = "GLRootView"; 53 54 private final boolean ENABLE_FPS_TEST = false; 55 private int mFrameCount = 0; 56 private long mFrameCountingStart = 0; 57 58 private static final int VERTEX_BUFFER_SIZE = 8; 59 60 private static final int FLAG_INITIALIZED = 1; 61 private static final int FLAG_NEED_LAYOUT = 2; 62 63 private static float sPixelDensity = -1f; 64 65 private GL11 mGL; 66 private GLView mContentView; 67 private DisplayMetrics mDisplayMetrics; 68 69 private final ArrayList<Animation> mAnimations = new ArrayList<Animation>(); 70 71 private final Stack<Transformation> mFreeTransform = 72 new Stack<Transformation>(); 73 74 private final Transformation mTransformation = new Transformation(); 75 private final Stack<Transformation> mTransformStack = 76 new Stack<Transformation>(); 77 78 private float mLastAlpha = mTransformation.getAlpha(); 79 80 private final float mMatrixValues[] = new float[16]; 81 82 private final float mCoordBuffer[] = new float[8]; 83 private final float mPointBuffer[] = new float[4]; 84 85 private ByteBuffer mVertexBuffer; 86 private ByteBuffer mTexCoordBuffer; 87 88 private int mFlags = FLAG_NEED_LAYOUT; 89 private long mAnimationTime; 90 91 private Thread mGLThread; 92 93 private boolean mIsQueueActive = true; 94 95 private int mFirstWidth; 96 private int mFirstHeight; 97 98 // TODO: move this part (handler) into GLSurfaceView 99 private final Looper mLooper; 100 101 public GLRootView(Context context) { 102 this(context, null); 103 } 104 105 public GLRootView(Context context, AttributeSet attrs) { 106 super(context, attrs); 107 initialize(); 108 mLooper = Looper.getMainLooper(); 109 } 110 111 void registerLaunchedAnimation(Animation animation) { 112 // Register the newly launched animation so that we can set the start 113 // time more precisely. (Usually, it takes much longer for the first 114 // rendering, so we set the animation start time as the time we 115 // complete rendering) 116 mAnimations.add(animation); 117 } 118 119 public long currentAnimationTimeMillis() { 120 return mAnimationTime; 121 } 122 123 public synchronized static float dpToPixel(Context context, float dp) { 124 if (sPixelDensity < 0) { 125 DisplayMetrics metrics = new DisplayMetrics(); 126 ((Activity) context).getWindowManager() 127 .getDefaultDisplay().getMetrics(metrics); 128 sPixelDensity = metrics.density; 129 } 130 return sPixelDensity * dp; 131 } 132 133 public static int dpToPixel(Context context, int dp) { 134 return (int)(dpToPixel(context, (float) dp) + .5f); 135 } 136 137 public Transformation obtainTransformation() { 138 if (!mFreeTransform.isEmpty()) { 139 Transformation t = mFreeTransform.pop(); 140 t.clear(); 141 return t; 142 } 143 return new Transformation(); 144 } 145 146 public void freeTransformation(Transformation freeTransformation) { 147 mFreeTransform.push(freeTransformation); 148 } 149 150 public Transformation getTransformation() { 151 return mTransformation; 152 } 153 154 public Transformation pushTransform() { 155 Transformation trans = obtainTransformation(); 156 trans.set(mTransformation); 157 mTransformStack.push(trans); 158 return mTransformation; 159 } 160 161 public void popTransform() { 162 Transformation trans = mTransformStack.pop(); 163 mTransformation.set(trans); 164 freeTransformation(trans); 165 } 166 167 public void runInGLThread(Runnable runnable) { 168 if (Thread.currentThread() == mGLThread) { 169 runnable.run(); 170 } else { 171 queueEvent(runnable); 172 } 173 } 174 175 private void initialize() { 176 mFlags |= FLAG_INITIALIZED; 177 setEGLConfigChooser(8, 8, 8, 8, 0, 4); 178 getHolder().setFormat(PixelFormat.TRANSLUCENT); 179 setZOrderOnTop(true); 180 181 setRenderer(this); 182 183 mVertexBuffer = ByteBuffer 184 .allocateDirect(VERTEX_BUFFER_SIZE * Float.SIZE / Byte.SIZE) 185 .order(ByteOrder.nativeOrder()); 186 mVertexBuffer.asFloatBuffer() 187 .put(new float[] {0, 0, 1, 0, 0, 1, 1, 1}) 188 .position(0); 189 mTexCoordBuffer = ByteBuffer 190 .allocateDirect(VERTEX_BUFFER_SIZE * Float.SIZE / Byte.SIZE) 191 .order(ByteOrder.nativeOrder()); 192 } 193 194 public void setContentPane(GLView content) { 195 mContentView = content; 196 content.onAttachToRoot(this); 197 198 // no parent for the content pane 199 content.onAddToParent(null); 200 requestLayoutContentPane(); 201 } 202 203 public GLView getContentPane() { 204 return mContentView; 205 } 206 207 void handleLowMemory() { 208 //TODO: delete texture from GL 209 } 210 211 public synchronized void requestLayoutContentPane() { 212 if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return; 213 214 // "View" system will invoke onLayout() for initialization(bug ?), we 215 // have to ignore it since the GLThread is not ready yet. 216 if ((mFlags & FLAG_INITIALIZED) == 0) return; 217 218 mFlags |= FLAG_NEED_LAYOUT; 219 requestRender(); 220 } 221 222 private synchronized void layoutContentPane() { 223 mFlags &= ~FLAG_NEED_LAYOUT; 224 int width = getWidth(); 225 int height = getHeight(); 226 Log.v(TAG, "layout content pane " + width + "x" + height); 227 if (mContentView != null && width != 0 && height != 0) { 228 mContentView.layout(0, 0, width, height); 229 } 230 } 231 232 @Override 233 protected void onLayout( 234 boolean changed, int left, int top, int right, int bottom) { 235 if (changed) requestLayoutContentPane(); 236 } 237 238 /** 239 * Called when the context is created, possibly after automatic destruction. 240 */ 241 // This is a GLSurfaceView.Renderer callback 242 public void onSurfaceCreated(GL10 gl1, EGLConfig config) { 243 GL11 gl = (GL11) gl1; 244 if (mGL != null) { 245 // The GL Object has changed 246 Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl); 247 } 248 mGL = gl; 249 250 if (!ENABLE_FPS_TEST) { 251 setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); 252 } else { 253 setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); 254 } 255 256 // Increase the priority of the render thread 257 Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); 258 mGLThread = Thread.currentThread(); 259 260 // Disable unused state 261 gl.glDisable(GL11.GL_LIGHTING); 262 263 // Enable used features 264 gl.glEnable(GL11.GL_BLEND); 265 gl.glEnable(GL11.GL_SCISSOR_TEST); 266 gl.glEnable(GL11.GL_STENCIL_TEST); 267 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); 268 gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); 269 gl.glEnable(GL11.GL_TEXTURE_2D); 270 271 gl.glTexEnvf(GL11.GL_TEXTURE_ENV, 272 GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE); 273 274 // Set the background color 275 gl.glClearColor(0f, 0f, 0f, 0f); 276 gl.glClearStencil(0); 277 gl.glVertexPointer(2, GL11.GL_FLOAT, 0, mVertexBuffer); 278 gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, mTexCoordBuffer); 279 } 280 281 /** 282 * Called when the OpenGL surface is recreated without destroying the 283 * context. 284 */ 285 // This is a GLSurfaceView.Renderer callback 286 public void onSurfaceChanged(GL10 gl1, int width, int height) { 287 Log.v(TAG, "onSurfaceChanged: " + width + "x" + height 288 + ", gl10: " + gl1.toString()); 289 GL11 gl = (GL11) gl1; 290 mGL = gl; 291 gl.glViewport(0, 0, width, height); 292 293 gl.glMatrixMode(GL11.GL_PROJECTION); 294 gl.glLoadIdentity(); 295 296 GLU.gluOrtho2D(gl, 0, width, 0, height); 297 Matrix matrix = mTransformation.getMatrix(); 298 matrix.reset(); 299 matrix.preTranslate(0, getHeight()); 300 matrix.preScale(1, -1); 301 } 302 303 private void setAlphaValue(float alpha) { 304 if (mLastAlpha == alpha) return; 305 306 GL11 gl = mGL; 307 mLastAlpha = alpha; 308 if (alpha >= 0.95f) { 309 gl.glTexEnvf(GL11.GL_TEXTURE_ENV, 310 GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE); 311 } else { 312 gl.glTexEnvf(GL11.GL_TEXTURE_ENV, 313 GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); 314 gl.glColor4f(alpha, alpha, alpha, alpha); 315 } 316 } 317 318 public void drawRect(int x, int y, int width, int height) { 319 float matrix[] = mMatrixValues; 320 mTransformation.getMatrix().getValues(matrix); 321 drawRect(x, y, width, height, matrix, mTransformation.getAlpha()); 322 } 323 324 private void drawRect( 325 int x, int y, int width, int height, float matrix[], float alpha) { 326 GL11 gl = mGL; 327 gl.glPushMatrix(); 328 setAlphaValue(alpha); 329 gl.glMultMatrixf(toGLMatrix(matrix), 0); 330 gl.glTranslatef(x, y, 0); 331 gl.glScalef(width, height, 1); 332 gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 4); 333 gl.glPopMatrix(); 334 } 335 336 public void drawRect(int x, int y, int width, int height, float alpha) { 337 float matrix[] = mMatrixValues; 338 mTransformation.getMatrix().getValues(matrix); 339 drawRect(x, y, width, height, matrix, alpha); 340 } 341 342 private float[] mapPoints(Matrix matrix, int x1, int y1, int x2, int y2) { 343 float[] point = mPointBuffer; 344 point[0] = x1; point[1] = y1; point[2] = x2; point[3] = y2; 345 matrix.mapPoints(point); 346 return point; 347 } 348 349 public void clipRect(int x, int y, int width, int height) { 350 float point[] = mapPoints( 351 mTransformation.getMatrix(), x, y + height, x + width, y); 352 353 // mMatrix could be a rotation matrix. In this case, we need to find 354 // the boundaries after rotation. (only handle 90 * n degrees) 355 if (point[0] > point[2]) { 356 x = (int) point[2]; 357 width = (int) point[0] - x; 358 } else { 359 x = (int) point[0]; 360 width = (int) point[2] - x; 361 } 362 if (point[1] > point[3]) { 363 y = (int) point[3]; 364 height = (int) point[1] - y; 365 } else { 366 y = (int) point[1]; 367 height = (int) point[3] - y; 368 } 369 mGL.glScissor(x, y, width, height); 370 } 371 372 public void clearClip() { 373 mGL.glScissor(0, 0, getWidth(), getHeight()); 374 } 375 376 private static float[] toGLMatrix(float v[]) { 377 v[15] = v[8]; v[13] = v[5]; v[5] = v[4]; v[4] = v[1]; 378 v[12] = v[2]; v[1] = v[3]; v[3] = v[6]; 379 v[2] = v[6] = v[8] = v[9] = 0; 380 v[10] = 1; 381 return v; 382 } 383 384 public void drawTexture( 385 Texture texture, int x, int y, int width, int height, float alpha) { 386 387 if (!texture.bind(this, mGL)) { 388 throw new RuntimeException("cannot bind" + texture.toString()); 389 } 390 if (width <= 0 || height <= 0) return ; 391 392 Matrix matrix = mTransformation.getMatrix(); 393 matrix.getValues(mMatrixValues); 394 395 // Test whether it has been rotated or flipped, if so, glDrawTexiOES 396 // won't work 397 if (isMatrixRotatedOrFlipped(mMatrixValues)) { 398 texture.getTextureCoords(mCoordBuffer, 0); 399 mTexCoordBuffer.asFloatBuffer().put(mCoordBuffer).position(0); 400 drawRect(x, y, width, height, mMatrixValues, alpha); 401 } else { 402 // draw the rect from bottom-left to top-right 403 float points[] = mapPoints(matrix, x, y + height, x + width, y); 404 x = (int) points[0]; 405 y = (int) points[1]; 406 width = (int) points[2] - x; 407 height = (int) points[3] - y; 408 if (width > 0 && height > 0) { 409 setAlphaValue(alpha); 410 ((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height); 411 } 412 } 413 } 414 415 private static boolean isMatrixRotatedOrFlipped(float matrix[]) { 416 return matrix[Matrix.MSKEW_X] != 0 || matrix[Matrix.MSKEW_Y] != 0 417 || matrix[Matrix.MSCALE_X] < 0 || matrix[Matrix.MSCALE_Y] > 0; 418 } 419 420 public void drawTexture( 421 Texture texture, int x, int y, int width, int height) { 422 drawTexture(texture, x, y, width, height, mTransformation.getAlpha()); 423 } 424 425 // This is a GLSurfaceView.Renderer callback 426 public void onDrawFrame(GL10 gl) { 427 if (ENABLE_FPS_TEST) { 428 long now = System.nanoTime(); 429 if (mFrameCountingStart == 0) { 430 mFrameCountingStart = now; 431 } else if ((now - mFrameCountingStart) > 1000000000) { 432 Log.v(TAG, "fps: " + (double) mFrameCount 433 * 1000000000 / (now - mFrameCountingStart)); 434 mFrameCountingStart = now; 435 mFrameCount = 0; 436 } 437 ++mFrameCount; 438 } 439 440 if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane(); 441 clearClip(); 442 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_STENCIL_BUFFER_BIT); 443 gl.glEnable(GL11.GL_BLEND); 444 gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); 445 446 mAnimationTime = SystemClock.uptimeMillis(); 447 if (mContentView != null) { 448 mContentView.render(GLRootView.this, (GL11) gl); 449 } 450 long now = SystemClock.uptimeMillis(); 451 for (Animation animation : mAnimations) { 452 animation.setStartTime(now); 453 } 454 mAnimations.clear(); 455 } 456 457 @Override 458 public boolean dispatchTouchEvent(MotionEvent event) { 459 // If this has been detached from root, we don't need to handle event 460 if (!mIsQueueActive) return false; 461 FutureTask<Boolean> task = new FutureTask<Boolean>( 462 new TouchEventHandler(event)); 463 queueEventOrThrowException(task); 464 try { 465 return task.get(); 466 } catch (Exception e) { 467 throw new RuntimeException(e); 468 } 469 } 470 471 private class TouchEventHandler implements Callable<Boolean> { 472 473 private final MotionEvent mEvent; 474 475 public TouchEventHandler(MotionEvent event) { 476 mEvent = event; 477 } 478 479 public Boolean call() throws Exception { 480 if (mContentView == null) return false; 481 return mContentView.dispatchTouchEvent(mEvent); 482 } 483 } 484 485 public DisplayMetrics getDisplayMetrics() { 486 if (mDisplayMetrics == null) { 487 mDisplayMetrics = new DisplayMetrics(); 488 ((Activity) getContext()).getWindowManager() 489 .getDefaultDisplay().getMetrics(mDisplayMetrics); 490 } 491 return mDisplayMetrics; 492 } 493 494 public void copyTexture2D( 495 RawTexture texture, int x, int y, int width, int height) 496 throws GLOutOfMemoryException { 497 Matrix matrix = mTransformation.getMatrix(); 498 matrix.getValues(mMatrixValues); 499 500 if (isMatrixRotatedOrFlipped(mMatrixValues)) { 501 throw new IllegalArgumentException("cannot support rotated matrix"); 502 } 503 float points[] = mapPoints(matrix, x, y + height, x + width, y); 504 x = (int) points[0]; 505 y = (int) points[1]; 506 width = (int) points[2] - x; 507 height = (int) points[3] - y; 508 509 GL11 gl = mGL; 510 int newWidth = Util.nextPowerOf2(width); 511 int newHeight = Util.nextPowerOf2(height); 512 int glError = GL11.GL_NO_ERROR; 513 514 gl.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId()); 515 516 int[] cropRect = {0, 0, width, height}; 517 gl.glTexParameteriv(GL11.GL_TEXTURE_2D, 518 GL11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, 0); 519 gl.glTexParameteri(GL11.GL_TEXTURE_2D, 520 GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); 521 gl.glTexParameteri(GL11.GL_TEXTURE_2D, 522 GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); 523 gl.glTexParameterf(GL11.GL_TEXTURE_2D, 524 GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); 525 gl.glTexParameterf(GL11.GL_TEXTURE_2D, 526 GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); 527 gl.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0, 528 GL11.GL_RGBA, x, y, newWidth, newHeight, 0); 529 glError = gl.glGetError(); 530 531 if (glError == GL11.GL_OUT_OF_MEMORY) { 532 throw new GLOutOfMemoryException(); 533 } 534 535 if (glError != GL11.GL_NO_ERROR) { 536 throw new RuntimeException( 537 "Texture copy fail, glError " + glError); 538 } 539 540 texture.setSize(width, height); 541 texture.setTexCoordSize( 542 (float) width / newWidth, (float) height / newHeight); 543 } 544 545 public synchronized void queueEventOrThrowException(Runnable runnable) { 546 if (!mIsQueueActive) { 547 throw new IllegalStateException("GLThread has exit"); 548 } 549 super.queueEvent(runnable); 550 } 551 552 @Override 553 protected void onDetachedFromWindow() { 554 final ConditionVariable var = new ConditionVariable(); 555 synchronized (this) { 556 mIsQueueActive = false; 557 queueEvent(new Runnable() { 558 public void run() { 559 var.open(); 560 } 561 }); 562 } 563 564 // Make sure all the runnables in the event queue is executed. 565 var.block(); 566 super.onDetachedFromWindow(); 567 } 568 569 protected Looper getTimerLooper() { 570 return mLooper; 571 } 572 } 573