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 package com.android.gallery3d.glrenderer;
17 
18 import android.graphics.Bitmap;
19 import android.graphics.RectF;
20 import android.opengl.GLES20;
21 import android.opengl.GLUtils;
22 import android.opengl.Matrix;
23 import android.util.Log;
24 
25 import java.nio.Buffer;
26 import java.nio.ByteBuffer;
27 import java.nio.ByteOrder;
28 import java.nio.FloatBuffer;
29 import java.util.Arrays;
30 
31 import javax.microedition.khronos.opengles.GL11;
32 
33 public 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 
ShaderParameter(String name)121         public ShaderParameter(String name) {
122             mName = name;
123         }
124 
loadHandle(int program)125         public abstract void loadHandle(int program);
126     }
127 
128     private static class UniformShaderParameter extends ShaderParameter {
UniformShaderParameter(String name)129         public UniformShaderParameter(String name) {
130             super(name);
131         }
132 
133         @Override
loadHandle(int program)134         public void loadHandle(int program) {
135             handle = GLES20.glGetUniformLocation(program, mName);
136             checkError();
137         }
138     }
139 
140     private static class AttributeShaderParameter extends ShaderParameter {
AttributeShaderParameter(String name)141         public AttributeShaderParameter(String name) {
142             super(name);
143         }
144 
145         @Override
loadHandle(int program)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 
GLES20Canvas()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 
createBuffer(float[] values)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 
assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params)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 
loadShader(int type, String shaderCode)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
setSize(int width, int height)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
clearBuffer()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
translate(float x, float y)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
rotate(float angle, float x, float y, float z)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
save(int saveFlags)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
restore()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 
setPosition(ShaderParameter[] params, int offset)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 
draw(ShaderParameter[] params, int type, int count, float x, float y, float width, float height)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 
setMatrix(ShaderParameter[] params, float x, float y, float width, float height)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
drawTexture(BasicTexture texture, int x, int y, int width, int height)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 
copyTextureCoordinates(BasicTexture texture, RectF outRect)346     private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) {
347         outRect.set(0, 0, texture.getWidth(), texture.getHeight());
348     }
349 
350     @Override
drawTexture(BasicTexture texture, RectF source, RectF target)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 
drawTextureRect(BasicTexture texture, RectF source, RectF target)362     private void drawTextureRect(BasicTexture texture, RectF source, RectF target) {
363         setTextureMatrix(source);
364         drawTextureRect(texture, mTempTextureMatrix, target);
365     }
366 
setTextureMatrix(RectF source)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.
convertCoordinate(RectF source, RectF target, BasicTexture 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 
drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target)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 
prepareTexture(BasicTexture texture)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 
prepareTexture(BasicTexture texture, int program, ShaderParameter[] params)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
unloadTexture(BasicTexture texture)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
deleteRecycledResources()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
setTextureParameters(BasicTexture texture)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
initializeTextureSize(BasicTexture texture, int format, int type)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
initializeTexture(BasicTexture texture, Bitmap bitmap)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
texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, int format, int type)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
uploadBuffer(FloatBuffer buf)493     public int uploadBuffer(FloatBuffer buf) {
494         return uploadBuffer(buf, FLOAT_SIZE);
495     }
496 
uploadBuffer(Buffer buffer, int elementSize)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 
checkError()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
getGLId()518     public GLId getGLId() {
519         return mGLId;
520     }
521 }
522