Home | History | Annotate | Download | only in ui
      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