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