| /* |
| * Copyright (C) 2012 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.dreams.basic; |
| |
| import android.graphics.Color; |
| import android.graphics.SurfaceTexture; |
| import android.util.Log; |
| import android.view.Choreographer; |
| import android.view.Surface; |
| import android.os.SystemClock; |
| |
| 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 android.opengl.EGL14; |
| import android.opengl.GLUtils; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.FloatBuffer; |
| import java.nio.ShortBuffer; |
| |
| import static android.opengl.GLES20.*; |
| |
| /** |
| * The OpenGL renderer for the {@link Colors} dream. |
| * <p> |
| * This class is single-threaded. Its methods must only be called on the |
| * rendering thread. |
| * </p> |
| */ |
| final class ColorsGLRenderer implements Choreographer.FrameCallback { |
| static final String TAG = ColorsGLRenderer.class.getSimpleName(); |
| static final boolean DEBUG = false; |
| |
| private static void LOG(String fmt, Object... args) { |
| if (!DEBUG) return; |
| Log.v(TAG, String.format(fmt, args)); |
| } |
| |
| private final Surface mSurface; |
| private int mWidth; |
| private int mHeight; |
| |
| private final Choreographer mChoreographer; |
| |
| private Square mSquare; |
| private long mLastFrameTime; |
| private int mFrameNum = 0; |
| |
| // It's so easy to use OpenGLES 2.0! |
| private EGL10 mEgl; |
| private EGLDisplay mEglDisplay; |
| private EGLContext mEglContext; |
| private EGLSurface mEglSurface; |
| |
| public ColorsGLRenderer(Surface surface, int width, int height) { |
| mSurface = surface; |
| mWidth = width; |
| mHeight = height; |
| |
| mChoreographer = Choreographer.getInstance(); |
| } |
| |
| public void start() { |
| initGL(); |
| mSquare = new Square(); |
| |
| mFrameNum = 0; |
| mChoreographer.postFrameCallback(this); |
| } |
| |
| public void stop() { |
| mChoreographer.removeFrameCallback(this); |
| |
| mSquare = null; |
| finishGL(); |
| } |
| |
| public void setSize(int width, int height) { |
| mWidth = width; |
| mHeight = height; |
| } |
| |
| @Override |
| public void doFrame(long frameTimeNanos) { |
| mFrameNum += 1; |
| |
| // Clear on first frame. |
| if (mFrameNum == 1) { |
| glClearColor(1f, 0f, 0f, 1.0f); |
| if (DEBUG) { |
| mLastFrameTime = frameTimeNanos; |
| } |
| } |
| |
| // Draw new frame. |
| checkCurrent(); |
| |
| glViewport(0, 0, mWidth, mHeight); |
| |
| if (DEBUG) { |
| final long dt = frameTimeNanos - mLastFrameTime; |
| final int fps = (int) (1e9f / dt); |
| if (0 == (mFrameNum % 10)) { |
| LOG("frame %d fps=%d", mFrameNum, fps); |
| } |
| if (fps < 40) { |
| LOG("JANK! (%d ms)", dt); |
| } |
| mLastFrameTime = frameTimeNanos; |
| } |
| |
| glClear(GL_COLOR_BUFFER_BIT); |
| checkGlError(); |
| |
| mSquare.draw(); |
| |
| if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { |
| throw new RuntimeException("Cannot swap buffers"); |
| } |
| checkEglError(); |
| |
| // Animate. Post callback to run on next vsync. |
| mChoreographer.postFrameCallback(this); |
| } |
| |
| 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())); |
| } |
| } |
| } |
| |
| private void initGL() { |
| mEgl = (EGL10) EGLContext.getEGL(); |
| |
| mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); |
| if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { |
| throw new RuntimeException("eglGetDisplay failed " |
| + GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| } |
| |
| int[] version = new int[2]; |
| if (!mEgl.eglInitialize(mEglDisplay, version)) { |
| throw new RuntimeException("eglInitialize failed " + |
| GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| } |
| |
| EGLConfig eglConfig = chooseEglConfig(); |
| if (eglConfig == null) { |
| throw new RuntimeException("eglConfig not initialized"); |
| } |
| |
| mEglContext = createContext(mEgl, mEglDisplay, eglConfig); |
| |
| mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, eglConfig, mSurface, null); |
| |
| if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { |
| int error = mEgl.eglGetError(); |
| if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { |
| Log.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); |
| return; |
| } |
| throw new RuntimeException("createWindowSurface failed " |
| + GLUtils.getEGLErrorString(error)); |
| } |
| |
| if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { |
| throw new RuntimeException("eglMakeCurrent failed " |
| + GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| } |
| } |
| |
| private void finishGL() { |
| mEgl.eglDestroyContext(mEglDisplay, mEglContext); |
| mEgl.eglDestroySurface(mEglDisplay, mEglSurface); |
| } |
| |
| private static EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { |
| int[] attrib_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; |
| return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); |
| } |
| |
| private EGLConfig chooseEglConfig() { |
| int[] configsCount = new int[1]; |
| EGLConfig[] configs = new EGLConfig[1]; |
| int[] configSpec = getConfig(); |
| if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { |
| throw new IllegalArgumentException("eglChooseConfig failed " + |
| GLUtils.getEGLErrorString(mEgl.eglGetError())); |
| } else if (configsCount[0] > 0) { |
| return configs[0]; |
| } |
| return null; |
| } |
| |
| private static int[] getConfig() { |
| return new int[] { |
| EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, |
| 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 |
| }; |
| } |
| |
| private static int buildProgram(String vertex, String fragment) { |
| int vertexShader = buildShader(vertex, GL_VERTEX_SHADER); |
| if (vertexShader == 0) return 0; |
| |
| int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER); |
| if (fragmentShader == 0) return 0; |
| |
| int program = glCreateProgram(); |
| glAttachShader(program, vertexShader); |
| checkGlError(); |
| |
| glAttachShader(program, fragmentShader); |
| checkGlError(); |
| |
| glLinkProgram(program); |
| checkGlError(); |
| |
| int[] status = new int[1]; |
| glGetProgramiv(program, GL_LINK_STATUS, status, 0); |
| if (status[0] != GL_TRUE) { |
| String error = glGetProgramInfoLog(program); |
| Log.d(TAG, "Error while linking program:\n" + error); |
| glDeleteShader(vertexShader); |
| glDeleteShader(fragmentShader); |
| glDeleteProgram(program); |
| return 0; |
| } |
| |
| return program; |
| } |
| |
| private static int buildShader(String source, int type) { |
| int shader = glCreateShader(type); |
| |
| glShaderSource(shader, source); |
| checkGlError(); |
| |
| glCompileShader(shader); |
| checkGlError(); |
| |
| int[] status = new int[1]; |
| glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0); |
| if (status[0] != GL_TRUE) { |
| String error = glGetShaderInfoLog(shader); |
| Log.d(TAG, "Error while compiling shader:\n" + error); |
| glDeleteShader(shader); |
| return 0; |
| } |
| |
| return shader; |
| } |
| |
| private void checkEglError() { |
| int error = mEgl.eglGetError(); |
| if (error != EGL10.EGL_SUCCESS) { |
| Log.w(TAG, "EGL error = 0x" + Integer.toHexString(error)); |
| } |
| } |
| |
| private static void checkGlError() { |
| checkGlError(""); |
| } |
| |
| private static void checkGlError(String what) { |
| int error = glGetError(); |
| if (error != GL_NO_ERROR) { |
| Log.w(TAG, "GL error: (" + what + ") = 0x" + Integer.toHexString(error)); |
| } |
| } |
| |
| private final static class Square { |
| // Straight from the API guide |
| private final String vertexShaderCode = |
| "attribute vec4 a_position;" + |
| "attribute vec4 a_color;" + |
| "varying vec4 v_color;" + |
| "void main() {" + |
| " gl_Position = a_position;" + |
| " v_color = a_color;" + |
| "}"; |
| |
| private final String fragmentShaderCode = |
| "precision mediump float;" + |
| "varying vec4 v_color;" + |
| "void main() {" + |
| " gl_FragColor = v_color;" + |
| "}"; |
| |
| private final FloatBuffer vertexBuffer; |
| private final FloatBuffer colorBuffer; |
| private final int mProgram; |
| private int mPositionHandle; |
| private int mColorHandle; |
| |
| private ShortBuffer drawListBuffer; |
| |
| |
| // number of coordinates per vertex in this array |
| final int COORDS_PER_VERTEX = 3; |
| float squareCoords[] = { -1f, 1f, 0f, // top left |
| -1f, -1f, 0f, // bottom left |
| 1f, -1f, 0f, // bottom right |
| 1f, 1f, 0f }; // top right |
| |
| private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices (CCW) |
| |
| private final float HUES[] = { // reverse order due to CCW winding |
| 60, // yellow |
| 120, // green |
| 343, // red |
| 200, // blue |
| }; |
| |
| private final int vertexCount = squareCoords.length / COORDS_PER_VERTEX; |
| private final int vertexStride = COORDS_PER_VERTEX * 4; // bytes per vertex |
| |
| private float cornerFrequencies[] = new float[vertexCount]; |
| private int cornerRotation; |
| |
| final int COLOR_PLANES_PER_VERTEX = 4; |
| private final int colorStride = COLOR_PLANES_PER_VERTEX * 4; // bytes per vertex |
| |
| // Set color with red, green, blue and alpha (opacity) values |
| // float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f }; |
| |
| public Square() { |
| for (int i=0; i<vertexCount; i++) { |
| cornerFrequencies[i] = 1f + (float)(Math.random() * 5); |
| } |
| cornerRotation = (int)(Math.random() * vertexCount); |
| // initialize vertex byte buffer for shape coordinates |
| ByteBuffer bb = ByteBuffer.allocateDirect( |
| // (# of coordinate values * 4 bytes per float) |
| squareCoords.length * 4); |
| bb.order(ByteOrder.nativeOrder()); |
| vertexBuffer = bb.asFloatBuffer(); |
| vertexBuffer.put(squareCoords); |
| vertexBuffer.position(0); |
| |
| bb = ByteBuffer.allocateDirect(vertexCount * colorStride); |
| bb.order(ByteOrder.nativeOrder()); |
| colorBuffer = bb.asFloatBuffer(); |
| |
| // initialize byte buffer for the draw list |
| ByteBuffer dlb = ByteBuffer.allocateDirect( |
| // (# of coordinate values * 2 bytes per short) |
| drawOrder.length * 2); |
| dlb.order(ByteOrder.nativeOrder()); |
| drawListBuffer = dlb.asShortBuffer(); |
| drawListBuffer.put(drawOrder); |
| drawListBuffer.position(0); |
| |
| mProgram = buildProgram(vertexShaderCode, fragmentShaderCode); |
| |
| // Add program to OpenGL environment |
| glUseProgram(mProgram); |
| checkGlError("glUseProgram(" + mProgram + ")"); |
| |
| // get handle to vertex shader's a_position member |
| mPositionHandle = glGetAttribLocation(mProgram, "a_position"); |
| checkGlError("glGetAttribLocation(a_position)"); |
| |
| // Enable a handle to the triangle vertices |
| glEnableVertexAttribArray(mPositionHandle); |
| |
| // Prepare the triangle coordinate data |
| glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, |
| GL_FLOAT, false, |
| vertexStride, vertexBuffer); |
| |
| mColorHandle = glGetAttribLocation(mProgram, "a_color"); |
| checkGlError("glGetAttribLocation(a_color)"); |
| glEnableVertexAttribArray(mColorHandle); |
| checkGlError("glEnableVertexAttribArray"); |
| } |
| |
| final float[] _tmphsv = new float[3]; |
| public void draw() { |
| // same thing for colors |
| long now = SystemClock.uptimeMillis(); |
| colorBuffer.clear(); |
| final float t = now / 4000f; // set the base period to 4sec |
| for(int i=0; i<vertexCount; i++) { |
| final float freq = (float) Math.sin(2 * Math.PI * t / cornerFrequencies[i]); |
| _tmphsv[0] = HUES[(i + cornerRotation) % vertexCount]; |
| _tmphsv[1] = 1f; |
| _tmphsv[2] = freq * 0.25f + 0.75f; |
| final int c = Color.HSVToColor(_tmphsv); |
| colorBuffer.put((float)((c & 0xFF0000) >> 16) / 0xFF); |
| colorBuffer.put((float)((c & 0x00FF00) >> 8) / 0xFF); |
| colorBuffer.put((float)(c & 0x0000FF) / 0xFF); |
| colorBuffer.put(/*a*/ 1f); |
| } |
| colorBuffer.position(0); |
| glVertexAttribPointer(mColorHandle, COLOR_PLANES_PER_VERTEX, |
| GL_FLOAT, false, |
| colorStride, colorBuffer); |
| checkGlError("glVertexAttribPointer"); |
| |
| // Draw the triangle |
| glDrawArrays(GL_TRIANGLE_FAN, 0, vertexCount); |
| } |
| } |
| } |