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 */
16package com.android.gallery3d.glrenderer;
17
18import android.graphics.Bitmap;
19import android.graphics.RectF;
20import android.opengl.GLES20;
21import android.opengl.GLUtils;
22import android.opengl.Matrix;
23import android.util.Log;
24
25import java.nio.Buffer;
26import java.nio.ByteBuffer;
27import java.nio.ByteOrder;
28import java.nio.FloatBuffer;
29import java.util.Arrays;
30
31import javax.microedition.khronos.opengles.GL11;
32
33public class GLES20Canvas implements GLCanvas {
34    // ************** Constants **********************
35    private static final String TAG = GLES20Canvas.class.getSimpleName();
36    private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE;
37
38    private static final int COORDS_PER_VERTEX = 2;
39    private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE;
40
41    private static final int COUNT_FILL_VERTEX = 4;
42    private static final int OFFSET_FILL_RECT = 0;
43
44    private static final int GL_TARGET = GL11.GL_TEXTURE_2D;
45
46    private static final float[] BOX_COORDINATES = {
47            0, 0, // Fill rectangle
48            1, 0,
49            0, 1,
50            1, 1,
51            0, 0, // Draw line
52            1, 1,
53            0, 0, // Draw rectangle outline
54            0, 1,
55            1, 1,
56            1, 0,
57    };
58
59    private static final String POSITION_ATTRIBUTE = "aPosition";
60    private static final String MATRIX_UNIFORM = "uMatrix";
61    private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix";
62    private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler";
63    private static final String ALPHA_UNIFORM = "uAlpha";
64
65    private static final String TEXTURE_VERTEX_SHADER = ""
66            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
67            + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n"
68            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
69            + "varying vec2 vTextureCoord;\n"
70            + "void main() {\n"
71            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
72            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
73            + "  vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n"
74            + "}\n";
75
76    private static final String TEXTURE_FRAGMENT_SHADER = ""
77            + "precision mediump float;\n"
78            + "varying vec2 vTextureCoord;\n"
79            + "uniform float " + ALPHA_UNIFORM + ";\n"
80            + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n"
81            + "void main() {\n"
82            + "  gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
83            + "  gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
84            + "}\n";
85
86    private static final int INITIAL_RESTORE_STATE_SIZE = 8;
87    private static final int MATRIX_SIZE = 16;
88
89    // Keep track of restore state
90    private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE];
91    private IntArray mSaveFlags = new IntArray();
92
93    private int mCurrentMatrixIndex = 0;
94
95    // Viewport size
96    private int mWidth;
97    private int mHeight;
98
99    // Projection matrix
100    private float[] mProjectionMatrix = new float[MATRIX_SIZE];
101
102    // GL programs
103    private int mTextureProgram;
104
105    // GL buffer containing BOX_COORDINATES
106    private int mBoxCoordinates;
107
108    // Handle indices -- common
109    private static final int INDEX_POSITION = 0;
110    private static final int INDEX_MATRIX = 1;
111
112    // Handle indices -- texture
113    private static final int INDEX_TEXTURE_MATRIX = 2;
114    private static final int INDEX_TEXTURE_SAMPLER = 3;
115    private static final int INDEX_ALPHA = 4;
116
117    private abstract static class ShaderParameter {
118        public int handle;
119        protected final String mName;
120
121        public ShaderParameter(String name) {
122            mName = name;
123        }
124
125        public abstract void loadHandle(int program);
126    }
127
128    private static class UniformShaderParameter extends ShaderParameter {
129        public UniformShaderParameter(String name) {
130            super(name);
131        }
132
133        @Override
134        public void loadHandle(int program) {
135            handle = GLES20.glGetUniformLocation(program, mName);
136            checkError();
137        }
138    }
139
140    private static class AttributeShaderParameter extends ShaderParameter {
141        public AttributeShaderParameter(String name) {
142            super(name);
143        }
144
145        @Override
146        public void loadHandle(int program) {
147            handle = GLES20.glGetAttribLocation(program, mName);
148            checkError();
149        }
150    }
151
152    private ShaderParameter[] mTextureParameters = {
153            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
154            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
155            new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
156            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
157            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
158    };
159
160    private final IntArray mUnboundTextures = new IntArray();
161
162    // Temporary variables used within calculations
163    private final float[] mTempMatrix = new float[32];
164    private final RectF mTempSourceRect = new RectF();
165    private final RectF mTempTargetRect = new RectF();
166    private final float[] mTempTextureMatrix = new float[MATRIX_SIZE];
167    private final int[] mTempIntArray = new int[1];
168
169    private static final GLId mGLId = new GLES20IdImpl();
170
171    public GLES20Canvas() {
172        Matrix.setIdentityM(mTempTextureMatrix, 0);
173        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
174
175        FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES);
176        mBoxCoordinates = uploadBuffer(boxBuffer);
177
178        int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER);
179        int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER);
180
181        mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader,
182                mTextureParameters);
183        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
184        checkError();
185    }
186
187    private static FloatBuffer createBuffer(float[] values) {
188        // First create an nio buffer, then create a VBO from it.
189        int size = values.length * FLOAT_SIZE;
190        FloatBuffer buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder())
191                .asFloatBuffer();
192        buffer.put(values, 0, values.length).position(0);
193        return buffer;
194    }
195
196    private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) {
197        int program = GLES20.glCreateProgram();
198        checkError();
199        if (program == 0) {
200            throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError());
201        }
202        GLES20.glAttachShader(program, vertexShader);
203        checkError();
204        GLES20.glAttachShader(program, fragmentShader);
205        checkError();
206        GLES20.glLinkProgram(program);
207        checkError();
208        int[] mLinkStatus = mTempIntArray;
209        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0);
210        if (mLinkStatus[0] != GLES20.GL_TRUE) {
211            Log.e(TAG, "Could not link program: ");
212            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
213            GLES20.glDeleteProgram(program);
214            program = 0;
215        }
216        for (int i = 0; i < params.length; i++) {
217            params[i].loadHandle(program);
218        }
219        return program;
220    }
221
222    private static int loadShader(int type, String shaderCode) {
223        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
224        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
225        int shader = GLES20.glCreateShader(type);
226
227        // add the source code to the shader and compile it
228        GLES20.glShaderSource(shader, shaderCode);
229        checkError();
230        GLES20.glCompileShader(shader);
231        checkError();
232
233        return shader;
234    }
235
236    @Override
237    public void setSize(int width, int height) {
238        mWidth = width;
239        mHeight = height;
240        GLES20.glViewport(0, 0, mWidth, mHeight);
241        checkError();
242        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
243        Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1);
244        Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);
245        Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);
246    }
247
248    @Override
249    public void clearBuffer() {
250        GLES20.glClearColor(0f, 0f, 0f, 1f);
251        checkError();
252        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
253        checkError();
254    }
255
256    // This is a faster version of translate(x, y, z) because
257    // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
258    // (3) we unroll the loop
259    @Override
260    public void translate(float x, float y) {
261        int index = mCurrentMatrixIndex;
262        float[] m = mMatrices;
263        m[index + 12] += m[index + 0] * x + m[index + 4] * y;
264        m[index + 13] += m[index + 1] * x + m[index + 5] * y;
265        m[index + 14] += m[index + 2] * x + m[index + 6] * y;
266        m[index + 15] += m[index + 3] * x + m[index + 7] * y;
267    }
268
269    @Override
270    public void rotate(float angle, float x, float y, float z) {
271        if (angle == 0f) {
272            return;
273        }
274        float[] temp = mTempMatrix;
275        Matrix.setRotateM(temp, 0, angle, x, y, z);
276        float[] matrix = mMatrices;
277        int index = mCurrentMatrixIndex;
278        Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0);
279        System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE);
280    }
281
282    @Override
283    public void save(int saveFlags) {
284        boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
285        if (saveMatrix) {
286            int currentIndex = mCurrentMatrixIndex;
287            mCurrentMatrixIndex += MATRIX_SIZE;
288            if (mMatrices.length <= mCurrentMatrixIndex) {
289                mMatrices = Arrays.copyOf(mMatrices, mMatrices.length * 2);
290            }
291            System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE);
292        }
293        mSaveFlags.add(saveFlags);
294    }
295
296    @Override
297    public void restore() {
298        int restoreFlags = mSaveFlags.removeLast();
299        boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
300        if (restoreMatrix) {
301            mCurrentMatrixIndex -= MATRIX_SIZE;
302        }
303    }
304
305    private void setPosition(ShaderParameter[] params, int offset) {
306        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates);
307        checkError();
308        GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX,
309                GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE);
310        checkError();
311        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
312        checkError();
313    }
314
315    private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width,
316            float height) {
317        setMatrix(params, x, y, width, height);
318        int positionHandle = params[INDEX_POSITION].handle;
319        GLES20.glEnableVertexAttribArray(positionHandle);
320        checkError();
321        GLES20.glDrawArrays(type, 0, count);
322        checkError();
323        GLES20.glDisableVertexAttribArray(positionHandle);
324        checkError();
325    }
326
327    private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) {
328        Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
329        Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
330        Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0);
331        GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE);
332        checkError();
333    }
334
335    @Override
336    public void drawTexture(BasicTexture texture, int x, int y, int width, int height) {
337        if (width <= 0 || height <= 0) {
338            return;
339        }
340        copyTextureCoordinates(texture, mTempSourceRect);
341        mTempTargetRect.set(x, y, x + width, y + height);
342        convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
343        drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
344    }
345
346    private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) {
347        outRect.set(0, 0, texture.getWidth(), texture.getHeight());
348    }
349
350    @Override
351    public void drawTexture(BasicTexture texture, RectF source, RectF target) {
352        if (target.width() <= 0 || target.height() <= 0) {
353            return;
354        }
355        mTempSourceRect.set(source);
356        mTempTargetRect.set(target);
357
358        convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
359        drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
360    }
361
362    private void drawTextureRect(BasicTexture texture, RectF source, RectF target) {
363        setTextureMatrix(source);
364        drawTextureRect(texture, mTempTextureMatrix, target);
365    }
366
367    private void setTextureMatrix(RectF source) {
368        mTempTextureMatrix[0] = source.width();
369        mTempTextureMatrix[5] = source.height();
370        mTempTextureMatrix[12] = source.left;
371        mTempTextureMatrix[13] = source.top;
372    }
373
374    // This function changes the source coordinate to the texture coordinates.
375    // It also clips the source and target coordinates if it is beyond the
376    // bound of the texture.
377    private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) {
378        int width = texture.getWidth();
379        int height = texture.getHeight();
380        int texWidth = texture.getTextureWidth();
381        int texHeight = texture.getTextureHeight();
382        // Convert to texture coordinates
383        source.left /= texWidth;
384        source.right /= texWidth;
385        source.top /= texHeight;
386        source.bottom /= texHeight;
387
388        // Clip if the rendering range is beyond the bound of the texture.
389        float xBound = (float) width / texWidth;
390        if (source.right > xBound) {
391            target.right = target.left + target.width() * (xBound - source.left) / source.width();
392            source.right = xBound;
393        }
394        float yBound = (float) height / texHeight;
395        if (source.bottom > yBound) {
396            target.bottom = target.top + target.height() * (yBound - source.top) / source.height();
397            source.bottom = yBound;
398        }
399    }
400
401    private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) {
402        ShaderParameter[] params = prepareTexture(texture);
403        setPosition(params, OFFSET_FILL_RECT);
404        GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0);
405        checkError();
406        draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top,
407                target.width(), target.height());
408    }
409
410    private ShaderParameter[] prepareTexture(BasicTexture texture) {
411        ShaderParameter[] params;
412        int program;
413        params = mTextureParameters;
414        program = mTextureProgram;
415        prepareTexture(texture, program, params);
416        return params;
417    }
418
419    private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
420        deleteRecycledResources();
421        GLES20.glUseProgram(program);
422        checkError();
423        GLES20.glDisable(GLES20.GL_BLEND);
424        checkError();
425        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
426        checkError();
427        texture.onBind(this);
428        GLES20.glBindTexture(GL_TARGET, texture.getId());
429        checkError();
430        GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
431        checkError();
432        GLES20.glUniform1f(params[INDEX_ALPHA].handle, 1);
433        checkError();
434    }
435
436    @Override
437    public boolean unloadTexture(BasicTexture texture) {
438        boolean unload = texture.isLoaded();
439        if (unload) {
440            synchronized (mUnboundTextures) {
441                mUnboundTextures.add(texture.getId());
442            }
443        }
444        return unload;
445    }
446
447    @Override
448    public void deleteRecycledResources() {
449        synchronized (mUnboundTextures) {
450            IntArray ids = mUnboundTextures;
451            if (mUnboundTextures.size() > 0) {
452                mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0);
453                ids.clear();
454            }
455        }
456    }
457
458    @Override
459    public void setTextureParameters(BasicTexture texture) {
460        GLES20.glBindTexture(GL_TARGET, texture.getId());
461        checkError();
462        GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
463        GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
464        GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
465        GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
466    }
467
468    @Override
469    public void initializeTextureSize(BasicTexture texture, int format, int type) {
470        GLES20.glBindTexture(GL_TARGET, texture.getId());
471        checkError();
472        int width = texture.getTextureWidth();
473        int height = texture.getTextureHeight();
474        GLES20.glTexImage2D(GL_TARGET, 0, format, width, height, 0, format, type, null);
475    }
476
477    @Override
478    public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
479        GLES20.glBindTexture(GL_TARGET, texture.getId());
480        checkError();
481        GLUtils.texImage2D(GL_TARGET, 0, bitmap, 0);
482    }
483
484    @Override
485    public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
486            int format, int type) {
487        GLES20.glBindTexture(GL_TARGET, texture.getId());
488        checkError();
489        GLUtils.texSubImage2D(GL_TARGET, 0, xOffset, yOffset, bitmap, format, type);
490    }
491
492    @Override
493    public int uploadBuffer(FloatBuffer buf) {
494        return uploadBuffer(buf, FLOAT_SIZE);
495    }
496
497    private int uploadBuffer(Buffer buffer, int elementSize) {
498        mGLId.glGenBuffers(1, mTempIntArray, 0);
499        checkError();
500        int bufferId = mTempIntArray[0];
501        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId);
502        checkError();
503        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer,
504                GLES20.GL_STATIC_DRAW);
505        checkError();
506        return bufferId;
507    }
508
509    public static void checkError() {
510        int error = GLES20.glGetError();
511        if (error != 0) {
512            Throwable t = new Throwable();
513            Log.e(TAG, "GL error: " + error, t);
514        }
515    }
516
517    @Override
518    public GLId getGLId() {
519        return mGLId;
520    }
521}
522