summaryrefslogtreecommitdiffstats
path: root/src/com/android/nfc/FireflyRenderer.java
diff options
context:
space:
mode:
authorMartijn Coenen <maco@google.com>2012-03-28 13:56:50 -0700
committerMartijn Coenen <maco@google.com>2012-03-28 16:35:53 -0700
commitf570dca6b0fa0ce883f5c950815e43acae4f51b2 (patch)
treed3030fc50c11f6cb8d8e5cf9d7a16241eec5f6ac /src/com/android/nfc/FireflyRenderer.java
parentee3d9ed14206e672e54a2db803484dacc0e0bbd4 (diff)
downloadpackages_apps_nfc-f570dca6b0fa0ce883f5c950815e43acae4f51b2.zip
packages_apps_nfc-f570dca6b0fa0ce883f5c950815e43acae4f51b2.tar.gz
packages_apps_nfc-f570dca6b0fa0ce883f5c950815e43acae4f51b2.tar.bz2
Dejank Android Beam animation.
After profiling, it turns out the Beam animation was mostly janky because of the initial buffer allocations that take place in the graphics driver. Modified both the animation and firefly renderers to swap the buffers three times before drawing anything, thereby making sure the buffers are allocated. Do not scale the screenshot while rendering fireflies, as that can cause jank as well. Also modified the FireflyRenderer to keep most of its state alive between subsequent Beams, causing it to come up faster. Cleaned up some old code and added documentation. Change-Id: Ica231861dd7ede178ad2ec1ab09aa00d1c876624
Diffstat (limited to 'src/com/android/nfc/FireflyRenderer.java')
-rw-r--r--src/com/android/nfc/FireflyRenderer.java424
1 files changed, 424 insertions, 0 deletions
diff --git a/src/com/android/nfc/FireflyRenderer.java b/src/com/android/nfc/FireflyRenderer.java
new file mode 100644
index 0000000..4ce58b4
--- /dev/null
+++ b/src/com/android/nfc/FireflyRenderer.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.nfc;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+public class FireflyRenderer {
+ private static final String LOG_TAG = "NfcFireflyThread";
+
+ static final int NUM_FIREFLIES = 200;
+
+ static final float NEAR_CLIPPING_PLANE = 50f;
+ static final float FAR_CLIPPING_PLANE = 100f;
+
+ // All final variables below only need to be allocated once
+ // and can be reused between subsequent Beams
+ static final int[] sEglConfig = {
+ EGL10.EGL_RED_SIZE, 8,
+ EGL10.EGL_GREEN_SIZE, 8,
+ EGL10.EGL_BLUE_SIZE, 8,
+ EGL10.EGL_ALPHA_SIZE, 0,
+ EGL10.EGL_DEPTH_SIZE, 0,
+ EGL10.EGL_STENCIL_SIZE, 0,
+ EGL10.EGL_NONE
+ };
+
+ // Vertices for drawing a 32x32 rect
+ static final float mVertices[] = {
+ 0.0f, 0.0f, 0.0f, // 0, Top Left
+ 0.0f, 32.0f, 0.0f, // 1, Bottom Left
+ 32.0f, 32.0f, 0.0f, // 2, Bottom Right
+ 32.0f, 0.0f, 0.0f, // 3, Top Right
+ };
+
+ // Mapping coordinates for the texture
+ static final float mTextCoords[] = {
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+ 1.0f, 1.0f,
+ 0.0f, 1.0f
+ };
+
+ // Connecting order (draws a square)
+ static final short[] mIndices = { 0, 1, 2, 0, 2, 3 };
+
+ final Context mContext;
+
+ // Buffer holding the vertices
+ final FloatBuffer mVertexBuffer;
+
+ // Buffer holding the indices
+ final ShortBuffer mIndexBuffer;
+
+ // Buffer holding the texture mapping coordinates
+ final FloatBuffer mTextureBuffer;
+
+ final Firefly[] mFireflies;
+
+ FireflyRenderThread mFireflyRenderThread;
+
+ // The surface to render the flies on, including width and height
+ SurfaceTexture mSurface;
+ int mDisplayWidth;
+ int mDisplayHeight;
+
+ public FireflyRenderer(Context context) {
+ mContext = context;
+
+ // First, build the vertex, texture and index buffers
+ ByteBuffer vbb = ByteBuffer.allocateDirect(mVertices.length * 4); // Float => 4 bytes
+ vbb.order(ByteOrder.nativeOrder());
+ mVertexBuffer = vbb.asFloatBuffer();
+ mVertexBuffer.put(mVertices);
+ mVertexBuffer.position(0);
+
+ ByteBuffer ibb = ByteBuffer.allocateDirect(mIndices.length * 2); // Short => 2 bytes
+ ibb.order(ByteOrder.nativeOrder());
+ mIndexBuffer = ibb.asShortBuffer();
+ mIndexBuffer.put(mIndices);
+ mIndexBuffer.position(0);
+
+ ByteBuffer tbb = ByteBuffer.allocateDirect(mTextCoords.length * 4);
+ tbb.order(ByteOrder.nativeOrder());
+ mTextureBuffer = tbb.asFloatBuffer();
+ mTextureBuffer.put(mTextCoords);
+ mTextureBuffer.position(0);
+
+ mFireflies = new Firefly[NUM_FIREFLIES];
+ for (int i = 0; i < NUM_FIREFLIES; i++) {
+ mFireflies[i] = new Firefly();
+ }
+ }
+
+ /**
+ * Starts rendering fireflies on the given surface.
+ * Must be called from the UI-thread.
+ */
+ public void start(SurfaceTexture surface, int width, int height) {
+ mSurface = surface;
+ mDisplayWidth = width;
+ mDisplayHeight = height;
+
+ mFireflyRenderThread = new FireflyRenderThread();
+ mFireflyRenderThread.start();
+ }
+
+ /**
+ * Stops rendering fireflies.
+ * Must be called from the UI-thread.
+ */
+ public void stop() {
+ if (mFireflyRenderThread != null) {
+ mFireflyRenderThread.finish();
+ try {
+ mFireflyRenderThread.join();
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Couldn't wait for FireflyRenderThread.");
+ }
+ mFireflyRenderThread = null;
+ }
+ }
+
+ private class FireflyRenderThread extends Thread {
+ EGL10 mEgl;
+ EGLDisplay mEglDisplay;
+ EGLConfig mEglConfig;
+ EGLContext mEglContext;
+ EGLSurface mEglSurface;
+ GL10 mGL;
+
+ // Holding the handle to the texture
+ int mTextureId;
+
+ // Read/written by multiple threads
+ volatile boolean mFinished;
+
+ @Override
+ public void run() {
+ if (!initGL()) {
+ Log.e(LOG_TAG, "Failed to initialize OpenGL.");
+ return;
+ }
+ loadStarTexture();
+
+ mGL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
+ mGL.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
+
+ // make adjustments for screen ratio
+ mGL.glMatrixMode(GL10.GL_PROJECTION);
+ mGL.glLoadIdentity();
+ mGL.glFrustumf(-mDisplayWidth, mDisplayWidth, mDisplayHeight, -mDisplayHeight, NEAR_CLIPPING_PLANE, FAR_CLIPPING_PLANE);
+
+ // Switch back to modelview
+ mGL.glMatrixMode(GL10.GL_MODELVIEW);
+ mGL.glLoadIdentity();
+
+ mGL.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
+ mGL.glDepthMask(true);
+
+
+ for (Firefly firefly : mFireflies) {
+ firefly.reset();
+ }
+
+ for (int i = 0; i < 3; i++) {
+ // Call eglSwapBuffers 3 times - this will allocate the necessary
+ // buffers, and make sure the animation looks smooth from the start.
+ if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+ Log.e(LOG_TAG, "Could not swap buffers");
+ mFinished = true;
+ }
+ }
+
+ long startTime = System.currentTimeMillis();
+
+ while (!mFinished) {
+ long timeElapsedMs = System.currentTimeMillis() - startTime;
+ startTime = System.currentTimeMillis();
+
+ checkCurrent();
+
+ mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
+ mGL.glLoadIdentity();
+
+ mGL.glEnable(GL10.GL_TEXTURE_2D);
+ mGL.glEnable(GL10.GL_BLEND);
+ mGL.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
+
+ for (Firefly firefly : mFireflies) {
+ firefly.updatePositionAndScale(timeElapsedMs);
+ firefly.draw(mGL);
+ }
+
+ if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+ Log.e(LOG_TAG, "Could not swap buffers");
+ mFinished = true;
+ }
+
+ long elapsed = System.currentTimeMillis() - startTime;
+ try {
+ Thread.sleep(Math.max(30 - elapsed, 0));
+ } catch (InterruptedException e) {
+
+ }
+ }
+ finishGL();
+ }
+
+ public void finish() {
+ mFinished = true;
+ }
+
+ void loadStarTexture() {
+ int[] textureIds = new int[1];
+ mGL.glGenTextures(1, textureIds, 0);
+ mTextureId = textureIds[0];
+
+ InputStream in = null;
+ try {
+ // Remember that both texture dimensions must be a power of 2!
+ in = mContext.getAssets().open("star.png");
+
+ Bitmap bitmap = BitmapFactory.decodeStream(in);
+ mGL.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
+
+ mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
+ mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+
+ GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
+
+ bitmap.recycle();
+
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "IOException opening assets.");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) { }
+ }
+ }
+ }
+
+ private void checkCurrent() {
+ if (!mEglContext.equals(mEgl.eglGetCurrentContext()) ||
+ !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) {
+ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ throw new RuntimeException("eglMakeCurrent failed "
+ + GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ }
+ }
+ }
+
+ boolean initGL() {
+ // Initialize openGL engine
+ mEgl = (EGL10) EGLContext.getEGL();
+
+ mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+ if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+ Log.e(LOG_TAG, "eglGetDisplay failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ return false;
+ }
+
+ int[] version = new int[2];
+ if (!mEgl.eglInitialize(mEglDisplay, version)) {
+ Log.e(LOG_TAG, "eglInitialize failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ return false;
+ }
+
+ mEglConfig = chooseEglConfig();
+ if (mEglConfig == null) {
+ Log.e(LOG_TAG, "eglConfig not initialized.");
+ return false;
+ }
+
+ mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, null);
+
+ mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null);
+
+ if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+ int error = mEgl.eglGetError();
+ Log.e(LOG_TAG,"createWindowSurface returned error " + Integer.toString(error));
+ return false;
+ }
+
+ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ Log.e(LOG_TAG, "eglMakeCurrent failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ return false;
+ }
+
+ mGL = (GL10) mEglContext.getGL();
+
+ return true;
+ }
+
+ private void finishGL() {
+ if (mEgl == null || mEglDisplay == null) {
+ // Nothing to free
+ return;
+ }
+ // Unbind the current surface and context from the display
+ mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_CONTEXT);
+
+ if (mEglSurface != null) {
+ mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+ }
+
+ if (mEglContext != null) {
+ mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+ }
+ }
+
+ private EGLConfig chooseEglConfig() {
+ int[] configsCount = new int[1];
+ EGLConfig[] configs = new EGLConfig[1];
+ if (!mEgl.eglChooseConfig(mEglDisplay, sEglConfig, configs, 1, configsCount)) {
+ throw new IllegalArgumentException("eglChooseConfig failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ } else if (configsCount[0] > 0) {
+ return configs[0];
+ }
+ return null;
+ }
+ }
+
+ private class Firefly {
+ static final float TEXTURE_HEIGHT = 30f; // TODO use measurement of texture size
+ static final float SPEED = .5f;
+
+ float mX; // between -mDisplayHeight and mDisplayHeight
+ float mY; // between -mDisplayWidth and mDisplayWidth
+ float mZ; // between 0.0 (near) and 1.0 (far)
+ float mZ0;
+ float mT;
+ float mScale;
+ float mAlpha;
+
+ public Firefly() {
+ }
+
+ void reset() {
+ mX = (float) (Math.random() * mDisplayWidth) * 4 - 2 * mDisplayWidth;
+ mY = (float) (Math.random() * mDisplayHeight) * 4 - 2 * mDisplayHeight;
+ mZ0 = mZ = (float) (Math.random()) * 2 - 1;
+ mT = 0f;
+ mScale = 1.5f;
+ mAlpha = 0f;
+ }
+
+ public void updatePositionAndScale(long timeElapsedMs) {
+ mT += timeElapsedMs;
+ mZ = mZ0 + mT/1000f * SPEED;
+ mAlpha = 1f-mZ;
+ if(mZ > 1.0) reset();
+ }
+
+ public void draw(GL10 gl) {
+ gl.glLoadIdentity();
+
+ // Counter clockwise winding
+ gl.glFrontFace(GL10.GL_CCW);
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+ gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+
+ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
+ gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer);
+
+ gl.glTranslatef(mX, mY, -NEAR_CLIPPING_PLANE-mZ*(FAR_CLIPPING_PLANE-NEAR_CLIPPING_PLANE));
+ gl.glColor4f(1, 1, 1, mAlpha);
+
+ // scale around center
+ gl.glTranslatef(TEXTURE_HEIGHT/2, TEXTURE_HEIGHT/2, 0);
+ gl.glScalef(mScale, mScale, 0);
+ gl.glTranslatef(-TEXTURE_HEIGHT/2, -TEXTURE_HEIGHT/2, 0);
+
+ gl.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_SHORT,
+ mIndexBuffer);
+
+ gl.glColor4f(1, 1, 1, 1);
+ gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+ gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
+ }
+ }
+}