diff options
Diffstat (limited to 'src/com/android/dreams/basic/ColorsGLRenderer.java')
-rw-r--r-- | src/com/android/dreams/basic/ColorsGLRenderer.java | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/src/com/android/dreams/basic/ColorsGLRenderer.java b/src/com/android/dreams/basic/ColorsGLRenderer.java new file mode 100644 index 0000000..3452261 --- /dev/null +++ b/src/com/android/dreams/basic/ColorsGLRenderer.java @@ -0,0 +1,439 @@ +/* + * 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.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 javax.microedition.khronos.opengles.GL; + +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 final void LOG(String fmt, Object... args) { + if (!DEBUG) return; + Log.v(TAG, String.format(fmt, args)); + } + + private final SurfaceTexture 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 EGLConfig mEglConfig; + private EGLContext mEglContext; + private EGLSurface mEglSurface; + private GL mGL; + + public ColorsGLRenderer(SurfaceTexture 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 t2 = frameTimeNanos; + final long dt = t2 - 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 = t2; + } + + 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())); + } + + mEglConfig = chooseEglConfig(); + if (mEglConfig == null) { + throw new RuntimeException("eglConfig not initialized"); + } + + mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); + + mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, 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())); + } + + mGL = mEglContext.getGL(); + } + + private void finishGL() { + mEgl.eglDestroyContext(mEglDisplay, mEglContext); + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + } + + private 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, 8, + 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); + } + } +} |