1 /* 2 * Copyright (C) 2012 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.dreams.basic; 18 19 import android.graphics.Color; 20 import android.graphics.SurfaceTexture; 21 import android.util.Log; 22 import android.view.Choreographer; 23 import android.os.SystemClock; 24 25 import javax.microedition.khronos.egl.EGL10; 26 import javax.microedition.khronos.egl.EGLConfig; 27 import javax.microedition.khronos.egl.EGLContext; 28 import javax.microedition.khronos.egl.EGLDisplay; 29 import javax.microedition.khronos.egl.EGLSurface; 30 31 import android.opengl.EGL14; 32 import android.opengl.GLUtils; 33 34 import java.nio.ByteBuffer; 35 import java.nio.ByteOrder; 36 import java.nio.FloatBuffer; 37 import java.nio.ShortBuffer; 38 39 import static android.opengl.GLES20.*; 40 41 /** 42 * The OpenGL renderer for the {@link Colors} dream. 43 * <p> 44 * This class is single-threaded. Its methods must only be called on the 45 * rendering thread. 46 * </p> 47 */ 48 final class ColorsGLRenderer implements Choreographer.FrameCallback { 49 static final String TAG = ColorsGLRenderer.class.getSimpleName(); 50 static final boolean DEBUG = false; 51 LOG(String fmt, Object... args)52 private static void LOG(String fmt, Object... args) { 53 if (!DEBUG) return; 54 Log.v(TAG, String.format(fmt, args)); 55 } 56 57 private final SurfaceTexture mSurface; 58 private int mWidth; 59 private int mHeight; 60 61 private final Choreographer mChoreographer; 62 63 private Square mSquare; 64 private long mLastFrameTime; 65 private int mFrameNum = 0; 66 67 // It's so easy to use OpenGLES 2.0! 68 private EGL10 mEgl; 69 private EGLDisplay mEglDisplay; 70 private EGLContext mEglContext; 71 private EGLSurface mEglSurface; 72 ColorsGLRenderer(SurfaceTexture surface, int width, int height)73 public ColorsGLRenderer(SurfaceTexture surface, int width, int height) { 74 mSurface = surface; 75 mWidth = width; 76 mHeight = height; 77 78 mChoreographer = Choreographer.getInstance(); 79 } 80 start()81 public void start() { 82 initGL(); 83 mSquare = new Square(); 84 85 mFrameNum = 0; 86 mChoreographer.postFrameCallback(this); 87 } 88 stop()89 public void stop() { 90 mChoreographer.removeFrameCallback(this); 91 92 mSquare = null; 93 finishGL(); 94 } 95 setSize(int width, int height)96 public void setSize(int width, int height) { 97 mWidth = width; 98 mHeight = height; 99 } 100 101 @Override doFrame(long frameTimeNanos)102 public void doFrame(long frameTimeNanos) { 103 mFrameNum += 1; 104 105 // Clear on first frame. 106 if (mFrameNum == 1) { 107 glClearColor(1f, 0f, 0f, 1.0f); 108 if (DEBUG) { 109 mLastFrameTime = frameTimeNanos; 110 } 111 } 112 113 // Draw new frame. 114 checkCurrent(); 115 116 glViewport(0, 0, mWidth, mHeight); 117 118 if (DEBUG) { 119 final long dt = frameTimeNanos - mLastFrameTime; 120 final int fps = (int) (1e9f / dt); 121 if (0 == (mFrameNum % 10)) { 122 LOG("frame %d fps=%d", mFrameNum, fps); 123 } 124 if (fps < 40) { 125 LOG("JANK! (%d ms)", dt); 126 } 127 mLastFrameTime = frameTimeNanos; 128 } 129 130 glClear(GL_COLOR_BUFFER_BIT); 131 checkGlError(); 132 133 mSquare.draw(); 134 135 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 136 throw new RuntimeException("Cannot swap buffers"); 137 } 138 checkEglError(); 139 140 // Animate. Post callback to run on next vsync. 141 mChoreographer.postFrameCallback(this); 142 } 143 checkCurrent()144 private void checkCurrent() { 145 if (!mEglContext.equals(mEgl.eglGetCurrentContext()) || 146 !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) { 147 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 148 throw new RuntimeException("eglMakeCurrent failed " 149 + GLUtils.getEGLErrorString(mEgl.eglGetError())); 150 } 151 } 152 } 153 initGL()154 private void initGL() { 155 mEgl = (EGL10) EGLContext.getEGL(); 156 157 mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 158 if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { 159 throw new RuntimeException("eglGetDisplay failed " 160 + GLUtils.getEGLErrorString(mEgl.eglGetError())); 161 } 162 163 int[] version = new int[2]; 164 if (!mEgl.eglInitialize(mEglDisplay, version)) { 165 throw new RuntimeException("eglInitialize failed " + 166 GLUtils.getEGLErrorString(mEgl.eglGetError())); 167 } 168 169 EGLConfig eglConfig = chooseEglConfig(); 170 if (eglConfig == null) { 171 throw new RuntimeException("eglConfig not initialized"); 172 } 173 174 mEglContext = createContext(mEgl, mEglDisplay, eglConfig); 175 176 mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, eglConfig, mSurface, null); 177 178 if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { 179 int error = mEgl.eglGetError(); 180 if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { 181 Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 182 return; 183 } 184 throw new RuntimeException("createWindowSurface failed " 185 + GLUtils.getEGLErrorString(error)); 186 } 187 188 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 189 throw new RuntimeException("eglMakeCurrent failed " 190 + GLUtils.getEGLErrorString(mEgl.eglGetError())); 191 } 192 } 193 finishGL()194 private void finishGL() { 195 mEgl.eglDestroyContext(mEglDisplay, mEglContext); 196 mEgl.eglDestroySurface(mEglDisplay, mEglSurface); 197 } 198 createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig)199 private static EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { 200 int[] attrib_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; 201 return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); 202 } 203 chooseEglConfig()204 private EGLConfig chooseEglConfig() { 205 int[] configsCount = new int[1]; 206 EGLConfig[] configs = new EGLConfig[1]; 207 int[] configSpec = getConfig(); 208 if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { 209 throw new IllegalArgumentException("eglChooseConfig failed " + 210 GLUtils.getEGLErrorString(mEgl.eglGetError())); 211 } else if (configsCount[0] > 0) { 212 return configs[0]; 213 } 214 return null; 215 } 216 getConfig()217 private static int[] getConfig() { 218 return new int[] { 219 EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, 220 EGL10.EGL_RED_SIZE, 8, 221 EGL10.EGL_GREEN_SIZE, 8, 222 EGL10.EGL_BLUE_SIZE, 8, 223 EGL10.EGL_ALPHA_SIZE, 0, 224 EGL10.EGL_DEPTH_SIZE, 0, 225 EGL10.EGL_STENCIL_SIZE, 0, 226 EGL10.EGL_NONE 227 }; 228 } 229 buildProgram(String vertex, String fragment)230 private static int buildProgram(String vertex, String fragment) { 231 int vertexShader = buildShader(vertex, GL_VERTEX_SHADER); 232 if (vertexShader == 0) return 0; 233 234 int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER); 235 if (fragmentShader == 0) return 0; 236 237 int program = glCreateProgram(); 238 glAttachShader(program, vertexShader); 239 checkGlError(); 240 241 glAttachShader(program, fragmentShader); 242 checkGlError(); 243 244 glLinkProgram(program); 245 checkGlError(); 246 247 int[] status = new int[1]; 248 glGetProgramiv(program, GL_LINK_STATUS, status, 0); 249 if (status[0] != GL_TRUE) { 250 String error = glGetProgramInfoLog(program); 251 Log.d(TAG, "Error while linking program:\n" + error); 252 glDeleteShader(vertexShader); 253 glDeleteShader(fragmentShader); 254 glDeleteProgram(program); 255 return 0; 256 } 257 258 return program; 259 } 260 buildShader(String source, int type)261 private static int buildShader(String source, int type) { 262 int shader = glCreateShader(type); 263 264 glShaderSource(shader, source); 265 checkGlError(); 266 267 glCompileShader(shader); 268 checkGlError(); 269 270 int[] status = new int[1]; 271 glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0); 272 if (status[0] != GL_TRUE) { 273 String error = glGetShaderInfoLog(shader); 274 Log.d(TAG, "Error while compiling shader:\n" + error); 275 glDeleteShader(shader); 276 return 0; 277 } 278 279 return shader; 280 } 281 checkEglError()282 private void checkEglError() { 283 int error = mEgl.eglGetError(); 284 if (error != EGL10.EGL_SUCCESS) { 285 Log.w(TAG, "EGL error = 0x" + Integer.toHexString(error)); 286 } 287 } 288 checkGlError()289 private static void checkGlError() { 290 checkGlError(""); 291 } 292 checkGlError(String what)293 private static void checkGlError(String what) { 294 int error = glGetError(); 295 if (error != GL_NO_ERROR) { 296 Log.w(TAG, "GL error: (" + what + ") = 0x" + Integer.toHexString(error)); 297 } 298 } 299 300 private final static class Square { 301 // Straight from the API guide 302 private final String vertexShaderCode = 303 "attribute vec4 a_position;" + 304 "attribute vec4 a_color;" + 305 "varying vec4 v_color;" + 306 "void main() {" + 307 " gl_Position = a_position;" + 308 " v_color = a_color;" + 309 "}"; 310 311 private final String fragmentShaderCode = 312 "precision mediump float;" + 313 "varying vec4 v_color;" + 314 "void main() {" + 315 " gl_FragColor = v_color;" + 316 "}"; 317 318 private final FloatBuffer vertexBuffer; 319 private final FloatBuffer colorBuffer; 320 private final int mProgram; 321 private int mPositionHandle; 322 private int mColorHandle; 323 324 private ShortBuffer drawListBuffer; 325 326 327 // number of coordinates per vertex in this array 328 final int COORDS_PER_VERTEX = 3; 329 float squareCoords[] = { -1f, 1f, 0f, // top left 330 -1f, -1f, 0f, // bottom left 331 1f, -1f, 0f, // bottom right 332 1f, 1f, 0f }; // top right 333 334 private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices (CCW) 335 336 private final float HUES[] = { // reverse order due to CCW winding 337 60, // yellow 338 120, // green 339 343, // red 340 200, // blue 341 }; 342 343 private final int vertexCount = squareCoords.length / COORDS_PER_VERTEX; 344 private final int vertexStride = COORDS_PER_VERTEX * 4; // bytes per vertex 345 346 private float cornerFrequencies[] = new float[vertexCount]; 347 private int cornerRotation; 348 349 final int COLOR_PLANES_PER_VERTEX = 4; 350 private final int colorStride = COLOR_PLANES_PER_VERTEX * 4; // bytes per vertex 351 352 // Set color with red, green, blue and alpha (opacity) values 353 // float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f }; 354 Square()355 public Square() { 356 for (int i=0; i<vertexCount; i++) { 357 cornerFrequencies[i] = 1f + (float)(Math.random() * 5); 358 } 359 cornerRotation = (int)(Math.random() * vertexCount); 360 // initialize vertex byte buffer for shape coordinates 361 ByteBuffer bb = ByteBuffer.allocateDirect( 362 // (# of coordinate values * 4 bytes per float) 363 squareCoords.length * 4); 364 bb.order(ByteOrder.nativeOrder()); 365 vertexBuffer = bb.asFloatBuffer(); 366 vertexBuffer.put(squareCoords); 367 vertexBuffer.position(0); 368 369 bb = ByteBuffer.allocateDirect(vertexCount * colorStride); 370 bb.order(ByteOrder.nativeOrder()); 371 colorBuffer = bb.asFloatBuffer(); 372 373 // initialize byte buffer for the draw list 374 ByteBuffer dlb = ByteBuffer.allocateDirect( 375 // (# of coordinate values * 2 bytes per short) 376 drawOrder.length * 2); 377 dlb.order(ByteOrder.nativeOrder()); 378 drawListBuffer = dlb.asShortBuffer(); 379 drawListBuffer.put(drawOrder); 380 drawListBuffer.position(0); 381 382 mProgram = buildProgram(vertexShaderCode, fragmentShaderCode); 383 384 // Add program to OpenGL environment 385 glUseProgram(mProgram); 386 checkGlError("glUseProgram(" + mProgram + ")"); 387 388 // get handle to vertex shader's a_position member 389 mPositionHandle = glGetAttribLocation(mProgram, "a_position"); 390 checkGlError("glGetAttribLocation(a_position)"); 391 392 // Enable a handle to the triangle vertices 393 glEnableVertexAttribArray(mPositionHandle); 394 395 // Prepare the triangle coordinate data 396 glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, 397 GL_FLOAT, false, 398 vertexStride, vertexBuffer); 399 400 mColorHandle = glGetAttribLocation(mProgram, "a_color"); 401 checkGlError("glGetAttribLocation(a_color)"); 402 glEnableVertexAttribArray(mColorHandle); 403 checkGlError("glEnableVertexAttribArray"); 404 } 405 406 final float[] _tmphsv = new float[3]; draw()407 public void draw() { 408 // same thing for colors 409 long now = SystemClock.uptimeMillis(); 410 colorBuffer.clear(); 411 final float t = now / 4000f; // set the base period to 4sec 412 for(int i=0; i<vertexCount; i++) { 413 final float freq = (float) Math.sin(2 * Math.PI * t / cornerFrequencies[i]); 414 _tmphsv[0] = HUES[(i + cornerRotation) % vertexCount]; 415 _tmphsv[1] = 1f; 416 _tmphsv[2] = freq * 0.25f + 0.75f; 417 final int c = Color.HSVToColor(_tmphsv); 418 colorBuffer.put((float)((c & 0xFF0000) >> 16) / 0xFF); 419 colorBuffer.put((float)((c & 0x00FF00) >> 8) / 0xFF); 420 colorBuffer.put((float)(c & 0x0000FF) / 0xFF); 421 colorBuffer.put(/*a*/ 1f); 422 } 423 colorBuffer.position(0); 424 glVertexAttribPointer(mColorHandle, COLOR_PLANES_PER_VERTEX, 425 GL_FLOAT, false, 426 colorStride, colorBuffer); 427 checkGlError("glVertexAttribPointer"); 428 429 // Draw the triangle 430 glDrawArrays(GL_TRIANGLE_FAN, 0, vertexCount); 431 } 432 } 433 } 434