1 /* 2 * Copyright (C) 2011 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 18 package com.android.nfc; 19 20 import android.content.Context; 21 import android.graphics.Bitmap; 22 import android.graphics.BitmapFactory; 23 import android.graphics.SurfaceTexture; 24 import android.opengl.GLUtils; 25 import android.util.Log; 26 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.nio.ByteBuffer; 30 import java.nio.ByteOrder; 31 import java.nio.FloatBuffer; 32 import java.nio.ShortBuffer; 33 34 import javax.microedition.khronos.egl.EGL10; 35 import javax.microedition.khronos.egl.EGLConfig; 36 import javax.microedition.khronos.egl.EGLContext; 37 import javax.microedition.khronos.egl.EGLDisplay; 38 import javax.microedition.khronos.egl.EGLSurface; 39 import javax.microedition.khronos.opengles.GL10; 40 41 public class FireflyRenderer { 42 private static final String LOG_TAG = "NfcFireflyThread"; 43 44 static final int NUM_FIREFLIES = 200; 45 46 static final float NEAR_CLIPPING_PLANE = 50f; 47 static final float FAR_CLIPPING_PLANE = 100f; 48 49 // All final variables below only need to be allocated once 50 // and can be reused between subsequent Beams 51 static final int[] sEglConfig = { 52 EGL10.EGL_RED_SIZE, 8, 53 EGL10.EGL_GREEN_SIZE, 8, 54 EGL10.EGL_BLUE_SIZE, 8, 55 EGL10.EGL_ALPHA_SIZE, 0, 56 EGL10.EGL_DEPTH_SIZE, 0, 57 EGL10.EGL_STENCIL_SIZE, 0, 58 EGL10.EGL_NONE 59 }; 60 61 // Vertices for drawing a 32x32 rect 62 static final float mVertices[] = { 63 0.0f, 0.0f, 0.0f, // 0, Top Left 64 0.0f, 32.0f, 0.0f, // 1, Bottom Left 65 32.0f, 32.0f, 0.0f, // 2, Bottom Right 66 32.0f, 0.0f, 0.0f, // 3, Top Right 67 }; 68 69 // Mapping coordinates for the texture 70 static final float mTextCoords[] = { 71 0.0f, 0.0f, 72 1.0f, 0.0f, 73 1.0f, 1.0f, 74 0.0f, 1.0f 75 }; 76 77 // Connecting order (draws a square) 78 static final short[] mIndices = { 0, 1, 2, 0, 2, 3 }; 79 80 final Context mContext; 81 82 // Buffer holding the vertices 83 final FloatBuffer mVertexBuffer; 84 85 // Buffer holding the indices 86 final ShortBuffer mIndexBuffer; 87 88 // Buffer holding the texture mapping coordinates 89 final FloatBuffer mTextureBuffer; 90 91 final Firefly[] mFireflies; 92 93 FireflyRenderThread mFireflyRenderThread; 94 95 // The surface to render the flies on, including width and height 96 SurfaceTexture mSurface; 97 int mDisplayWidth; 98 int mDisplayHeight; 99 100 public FireflyRenderer(Context context) { 101 mContext = context; 102 103 // First, build the vertex, texture and index buffers 104 ByteBuffer vbb = ByteBuffer.allocateDirect(mVertices.length * 4); // Float => 4 bytes 105 vbb.order(ByteOrder.nativeOrder()); 106 mVertexBuffer = vbb.asFloatBuffer(); 107 mVertexBuffer.put(mVertices); 108 mVertexBuffer.position(0); 109 110 ByteBuffer ibb = ByteBuffer.allocateDirect(mIndices.length * 2); // Short => 2 bytes 111 ibb.order(ByteOrder.nativeOrder()); 112 mIndexBuffer = ibb.asShortBuffer(); 113 mIndexBuffer.put(mIndices); 114 mIndexBuffer.position(0); 115 116 ByteBuffer tbb = ByteBuffer.allocateDirect(mTextCoords.length * 4); 117 tbb.order(ByteOrder.nativeOrder()); 118 mTextureBuffer = tbb.asFloatBuffer(); 119 mTextureBuffer.put(mTextCoords); 120 mTextureBuffer.position(0); 121 122 mFireflies = new Firefly[NUM_FIREFLIES]; 123 for (int i = 0; i < NUM_FIREFLIES; i++) { 124 mFireflies[i] = new Firefly(); 125 } 126 } 127 128 /** 129 * Starts rendering fireflies on the given surface. 130 * Must be called from the UI-thread. 131 */ 132 public void start(SurfaceTexture surface, int width, int height) { 133 mSurface = surface; 134 mDisplayWidth = width; 135 mDisplayHeight = height; 136 137 mFireflyRenderThread = new FireflyRenderThread(); 138 mFireflyRenderThread.start(); 139 } 140 141 /** 142 * Stops rendering fireflies. 143 * Must be called from the UI-thread. 144 */ 145 public void stop() { 146 if (mFireflyRenderThread != null) { 147 mFireflyRenderThread.finish(); 148 try { 149 mFireflyRenderThread.join(); 150 } catch (InterruptedException e) { 151 Log.e(LOG_TAG, "Couldn't wait for FireflyRenderThread."); 152 } 153 mFireflyRenderThread = null; 154 } 155 } 156 157 private class FireflyRenderThread extends Thread { 158 EGL10 mEgl; 159 EGLDisplay mEglDisplay; 160 EGLConfig mEglConfig; 161 EGLContext mEglContext; 162 EGLSurface mEglSurface; 163 GL10 mGL; 164 165 // Holding the handle to the texture 166 int mTextureId; 167 168 // Read/written by multiple threads 169 volatile boolean mFinished; 170 171 @Override 172 public void run() { 173 if (!initGL()) { 174 Log.e(LOG_TAG, "Failed to initialize OpenGL."); 175 return; 176 } 177 loadStarTexture(); 178 179 mGL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 180 181 mGL.glViewport(0, 0, mDisplayWidth, mDisplayHeight); 182 183 // make adjustments for screen ratio 184 mGL.glMatrixMode(GL10.GL_PROJECTION); 185 mGL.glLoadIdentity(); 186 mGL.glFrustumf(-mDisplayWidth, mDisplayWidth, mDisplayHeight, -mDisplayHeight, NEAR_CLIPPING_PLANE, FAR_CLIPPING_PLANE); 187 188 // Switch back to modelview 189 mGL.glMatrixMode(GL10.GL_MODELVIEW); 190 mGL.glLoadIdentity(); 191 192 mGL.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); 193 mGL.glDepthMask(true); 194 195 196 for (Firefly firefly : mFireflies) { 197 firefly.reset(); 198 } 199 200 for (int i = 0; i < 3; i++) { 201 // Call eglSwapBuffers 3 times - this will allocate the necessary 202 // buffers, and make sure the animation looks smooth from the start. 203 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 204 Log.e(LOG_TAG, "Could not swap buffers"); 205 mFinished = true; 206 } 207 } 208 209 long startTime = System.currentTimeMillis(); 210 211 while (!mFinished) { 212 long timeElapsedMs = System.currentTimeMillis() - startTime; 213 startTime = System.currentTimeMillis(); 214 215 checkCurrent(); 216 217 mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); 218 mGL.glLoadIdentity(); 219 220 mGL.glEnable(GL10.GL_TEXTURE_2D); 221 mGL.glEnable(GL10.GL_BLEND); 222 mGL.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE); 223 224 for (Firefly firefly : mFireflies) { 225 firefly.updatePositionAndScale(timeElapsedMs); 226 firefly.draw(mGL); 227 } 228 229 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 230 Log.e(LOG_TAG, "Could not swap buffers"); 231 mFinished = true; 232 } 233 234 long elapsed = System.currentTimeMillis() - startTime; 235 try { 236 Thread.sleep(Math.max(30 - elapsed, 0)); 237 } catch (InterruptedException e) { 238 239 } 240 } 241 finishGL(); 242 } 243 244 public void finish() { 245 mFinished = true; 246 } 247 248 void loadStarTexture() { 249 int[] textureIds = new int[1]; 250 mGL.glGenTextures(1, textureIds, 0); 251 mTextureId = textureIds[0]; 252 253 InputStream in = null; 254 try { 255 // Remember that both texture dimensions must be a power of 2! 256 in = mContext.getAssets().open("star.png"); 257 258 Bitmap bitmap = BitmapFactory.decodeStream(in); 259 mGL.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId); 260 261 mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); 262 mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); 263 264 GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); 265 266 bitmap.recycle(); 267 268 } catch (IOException e) { 269 Log.e(LOG_TAG, "IOException opening assets."); 270 } finally { 271 if (in != null) { 272 try { 273 in.close(); 274 } catch (IOException e) { } 275 } 276 } 277 } 278 279 private void checkCurrent() { 280 if (!mEglContext.equals(mEgl.eglGetCurrentContext()) || 281 !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) { 282 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 283 throw new RuntimeException("eglMakeCurrent failed " 284 + GLUtils.getEGLErrorString(mEgl.eglGetError())); 285 } 286 } 287 } 288 289 boolean initGL() { 290 // Initialize openGL engine 291 mEgl = (EGL10) EGLContext.getEGL(); 292 293 mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 294 if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { 295 Log.e(LOG_TAG, "eglGetDisplay failed " + 296 GLUtils.getEGLErrorString(mEgl.eglGetError())); 297 return false; 298 } 299 300 int[] version = new int[2]; 301 if (!mEgl.eglInitialize(mEglDisplay, version)) { 302 Log.e(LOG_TAG, "eglInitialize failed " + 303 GLUtils.getEGLErrorString(mEgl.eglGetError())); 304 return false; 305 } 306 307 mEglConfig = chooseEglConfig(); 308 if (mEglConfig == null) { 309 Log.e(LOG_TAG, "eglConfig not initialized."); 310 return false; 311 } 312 313 mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, null); 314 315 mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null); 316 317 if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { 318 int error = mEgl.eglGetError(); 319 Log.e(LOG_TAG,"createWindowSurface returned error " + Integer.toString(error)); 320 return false; 321 } 322 323 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 324 Log.e(LOG_TAG, "eglMakeCurrent failed " + 325 GLUtils.getEGLErrorString(mEgl.eglGetError())); 326 return false; 327 } 328 329 mGL = (GL10) mEglContext.getGL(); 330 331 return true; 332 } 333 334 private void finishGL() { 335 if (mEgl == null || mEglDisplay == null) { 336 // Nothing to free 337 return; 338 } 339 // Unbind the current surface and context from the display 340 mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, 341 EGL10.EGL_NO_CONTEXT); 342 343 if (mEglSurface != null) { 344 mEgl.eglDestroySurface(mEglDisplay, mEglSurface); 345 } 346 347 if (mEglContext != null) { 348 mEgl.eglDestroyContext(mEglDisplay, mEglContext); 349 } 350 } 351 352 private EGLConfig chooseEglConfig() { 353 int[] configsCount = new int[1]; 354 EGLConfig[] configs = new EGLConfig[1]; 355 if (!mEgl.eglChooseConfig(mEglDisplay, sEglConfig, configs, 1, configsCount)) { 356 throw new IllegalArgumentException("eglChooseConfig failed " + 357 GLUtils.getEGLErrorString(mEgl.eglGetError())); 358 } else if (configsCount[0] > 0) { 359 return configs[0]; 360 } 361 return null; 362 } 363 } 364 365 private class Firefly { 366 static final float TEXTURE_HEIGHT = 30f; // TODO use measurement of texture size 367 static final float SPEED = .5f; 368 369 float mX; // between -mDisplayHeight and mDisplayHeight 370 float mY; // between -mDisplayWidth and mDisplayWidth 371 float mZ; // between 0.0 (near) and 1.0 (far) 372 float mZ0; 373 float mT; 374 float mScale; 375 float mAlpha; 376 377 public Firefly() { 378 } 379 380 void reset() { 381 mX = (float) (Math.random() * mDisplayWidth) * 4 - 2 * mDisplayWidth; 382 mY = (float) (Math.random() * mDisplayHeight) * 4 - 2 * mDisplayHeight; 383 mZ0 = mZ = (float) (Math.random()) * 2 - 1; 384 mT = 0f; 385 mScale = 1.5f; 386 mAlpha = 0f; 387 } 388 389 public void updatePositionAndScale(long timeElapsedMs) { 390 mT += timeElapsedMs; 391 mZ = mZ0 + mT/1000f * SPEED; 392 mAlpha = 1f-mZ; 393 if(mZ > 1.0) reset(); 394 } 395 396 public void draw(GL10 gl) { 397 gl.glLoadIdentity(); 398 399 // Counter clockwise winding 400 gl.glFrontFace(GL10.GL_CCW); 401 402 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); 403 gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); 404 405 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer); 406 gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer); 407 408 gl.glTranslatef(mX, mY, -NEAR_CLIPPING_PLANE-mZ*(FAR_CLIPPING_PLANE-NEAR_CLIPPING_PLANE)); 409 gl.glColor4f(1, 1, 1, mAlpha); 410 411 // scale around center 412 gl.glTranslatef(TEXTURE_HEIGHT/2, TEXTURE_HEIGHT/2, 0); 413 gl.glScalef(mScale, mScale, 0); 414 gl.glTranslatef(-TEXTURE_HEIGHT/2, -TEXTURE_HEIGHT/2, 0); 415 416 gl.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_SHORT, 417 mIndexBuffer); 418 419 gl.glColor4f(1, 1, 1, 1); 420 gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); 421 gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); 422 } 423 } 424 } 425