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
17package com.android.dreams.basic;
18
19import android.graphics.Color;
20import android.graphics.SurfaceTexture;
21import android.util.Log;
22import android.view.Choreographer;
23import android.os.SystemClock;
24
25import javax.microedition.khronos.egl.EGL10;
26import javax.microedition.khronos.egl.EGLConfig;
27import javax.microedition.khronos.egl.EGLContext;
28import javax.microedition.khronos.egl.EGLDisplay;
29import javax.microedition.khronos.egl.EGLSurface;
30
31import android.opengl.EGL14;
32import android.opengl.GLUtils;
33
34import java.nio.ByteBuffer;
35import java.nio.ByteOrder;
36import java.nio.FloatBuffer;
37import java.nio.ShortBuffer;
38
39import 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 */
48final class ColorsGLRenderer implements Choreographer.FrameCallback {
49    static final String TAG = ColorsGLRenderer.class.getSimpleName();
50    static final boolean DEBUG = false;
51
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
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
81    public void start() {
82        initGL();
83        mSquare = new Square();
84
85        mFrameNum = 0;
86        mChoreographer.postFrameCallback(this);
87    }
88
89    public void stop() {
90        mChoreographer.removeFrameCallback(this);
91
92        mSquare = null;
93        finishGL();
94    }
95
96    public void setSize(int width, int height) {
97        mWidth = width;
98        mHeight = height;
99    }
100
101    @Override
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
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
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
194    private void finishGL() {
195        mEgl.eglDestroyContext(mEglDisplay, mEglContext);
196        mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
197    }
198
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
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
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
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
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
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
289    private static void checkGlError() {
290        checkGlError("");
291    }
292
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
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];
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