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.beam; 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 mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); 204 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 205 Log.e(LOG_TAG, "Could not swap buffers"); 206 mFinished = true; 207 } 208 } 209 210 long startTime = System.currentTimeMillis(); 211 212 while (!mFinished) { 213 long timeElapsedMs = System.currentTimeMillis() - startTime; 214 startTime = System.currentTimeMillis(); 215 216 checkCurrent(); 217 218 mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); 219 mGL.glLoadIdentity(); 220 221 mGL.glEnable(GL10.GL_TEXTURE_2D); 222 mGL.glEnable(GL10.GL_BLEND); 223 mGL.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE); 224 225 for (Firefly firefly : mFireflies) { 226 firefly.updatePositionAndScale(timeElapsedMs); 227 firefly.draw(mGL); 228 } 229 230 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 231 Log.e(LOG_TAG, "Could not swap buffers"); 232 mFinished = true; 233 } 234 235 long elapsed = System.currentTimeMillis() - startTime; 236 try { 237 Thread.sleep(Math.max(30 - elapsed, 0)); 238 } catch (InterruptedException e) { 239 240 } 241 } 242 finishGL(); 243 } 244 245 public void finish() { 246 mFinished = true; 247 } 248 249 void loadStarTexture() { 250 int[] textureIds = new int[1]; 251 mGL.glGenTextures(1, textureIds, 0); 252 mTextureId = textureIds[0]; 253 254 InputStream in = null; 255 try { 256 // Remember that both texture dimensions must be a power of 2! 257 in = mContext.getAssets().open("star.png"); 258 259 Bitmap bitmap = BitmapFactory.decodeStream(in); 260 mGL.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId); 261 262 mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); 263 mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); 264 265 GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); 266 267 bitmap.recycle(); 268 269 } catch (IOException e) { 270 Log.e(LOG_TAG, "IOException opening assets."); 271 } finally { 272 if (in != null) { 273 try { 274 in.close(); 275 } catch (IOException e) { } 276 } 277 } 278 } 279 280 private void checkCurrent() { 281 if (!mEglContext.equals(mEgl.eglGetCurrentContext()) || 282 !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) { 283 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 284 throw new RuntimeException("eglMakeCurrent failed " 285 + GLUtils.getEGLErrorString(mEgl.eglGetError())); 286 } 287 } 288 } 289 290 boolean initGL() { 291 // Initialize openGL engine 292 mEgl = (EGL10) EGLContext.getEGL(); 293 294 mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 295 if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { 296 Log.e(LOG_TAG, "eglGetDisplay failed " + 297 GLUtils.getEGLErrorString(mEgl.eglGetError())); 298 return false; 299 } 300 301 int[] version = new int[2]; 302 if (!mEgl.eglInitialize(mEglDisplay, version)) { 303 Log.e(LOG_TAG, "eglInitialize failed " + 304 GLUtils.getEGLErrorString(mEgl.eglGetError())); 305 return false; 306 } 307 308 mEglConfig = chooseEglConfig(); 309 if (mEglConfig == null) { 310 Log.e(LOG_TAG, "eglConfig not initialized."); 311 return false; 312 } 313 314 mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, null); 315 316 mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null); 317 318 if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { 319 int error = mEgl.eglGetError(); 320 Log.e(LOG_TAG,"createWindowSurface returned error " + Integer.toString(error)); 321 return false; 322 } 323 324 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 325 Log.e(LOG_TAG, "eglMakeCurrent failed " + 326 GLUtils.getEGLErrorString(mEgl.eglGetError())); 327 return false; 328 } 329 330 mGL = (GL10) mEglContext.getGL(); 331 332 return true; 333 } 334 335 private void finishGL() { 336 if (mEgl == null || mEglDisplay == null) { 337 // Nothing to free 338 return; 339 } 340 // Unbind the current surface and context from the display 341 mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, 342 EGL10.EGL_NO_CONTEXT); 343 344 if (mEglSurface != null) { 345 mEgl.eglDestroySurface(mEglDisplay, mEglSurface); 346 } 347 348 if (mEglContext != null) { 349 mEgl.eglDestroyContext(mEglDisplay, mEglContext); 350 } 351 } 352 353 private EGLConfig chooseEglConfig() { 354 int[] configsCount = new int[1]; 355 EGLConfig[] configs = new EGLConfig[1]; 356 if (!mEgl.eglChooseConfig(mEglDisplay, sEglConfig, configs, 1, configsCount)) { 357 throw new IllegalArgumentException("eglChooseConfig failed " + 358 GLUtils.getEGLErrorString(mEgl.eglGetError())); 359 } else if (configsCount[0] > 0) { 360 return configs[0]; 361 } 362 return null; 363 } 364 } 365 366 private class Firefly { 367 static final float TEXTURE_HEIGHT = 30f; // TODO use measurement of texture size 368 static final float SPEED = .5f; 369 370 float mX; // between -mDisplayHeight and mDisplayHeight 371 float mY; // between -mDisplayWidth and mDisplayWidth 372 float mZ; // between 0.0 (near) and 1.0 (far) 373 float mZ0; 374 float mT; 375 float mScale; 376 float mAlpha; 377 378 public Firefly() { 379 } 380 381 void reset() { 382 mX = (float) (Math.random() * mDisplayWidth) * 4 - 2 * mDisplayWidth; 383 mY = (float) (Math.random() * mDisplayHeight) * 4 - 2 * mDisplayHeight; 384 mZ0 = mZ = (float) (Math.random()) * 2 - 1; 385 mT = 0f; 386 mScale = 1.5f; 387 mAlpha = 0f; 388 } 389 390 public void updatePositionAndScale(long timeElapsedMs) { 391 mT += timeElapsedMs; 392 mZ = mZ0 + mT/1000f * SPEED; 393 mAlpha = 1f-mZ; 394 if(mZ > 1.0) reset(); 395 } 396 397 public void draw(GL10 gl) { 398 gl.glLoadIdentity(); 399 400 // Counter clockwise winding 401 gl.glFrontFace(GL10.GL_CCW); 402 403 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); 404 gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); 405 406 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer); 407 gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer); 408 409 gl.glTranslatef(mX, mY, -NEAR_CLIPPING_PLANE-mZ*(FAR_CLIPPING_PLANE-NEAR_CLIPPING_PLANE)); 410 gl.glColor4f(1, 1, 1, mAlpha); 411 412 // scale around center 413 gl.glTranslatef(TEXTURE_HEIGHT/2, TEXTURE_HEIGHT/2, 0); 414 gl.glScalef(mScale, mScale, 0); 415 gl.glTranslatef(-TEXTURE_HEIGHT/2, -TEXTURE_HEIGHT/2, 0); 416 417 gl.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_SHORT, 418 mIndexBuffer); 419 420 gl.glColor4f(1, 1, 1, 1); 421 gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); 422 gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); 423 } 424 } 425 } 426