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