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