OSDN Git Service

Add GLES20 canvas implementation.
authorGeorge Mount <mount@google.com>
Fri, 16 Nov 2012 23:44:26 +0000 (15:44 -0800)
committerGeorge Mount <mount@google.com>
Wed, 5 Dec 2012 19:18:20 +0000 (11:18 -0800)
Change-Id: I5680909f31dc097599d0e063aa1f6daba834d3e2

12 files changed:
gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
src/com/android/gallery3d/ui/GLCanvas.java
src/com/android/gallery3d/ui/GLCanvasImpl.java
src/com/android/gallery3d/ui/GLES20Canvas.java [new file with mode: 0644]
src/com/android/gallery3d/ui/GLRootView.java
src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java
src/com/android/gallery3d/ui/NinePatchTexture.java
src/com/android/gallery3d/ui/RawTexture.java
src/com/android/gallery3d/util/IntArray.java
tests/src/com/android/gallery3d/ui/GLCanvasStub.java
tests/src/com/android/gallery3d/ui/GLCanvasTest.java
tests/src/com/android/gallery3d/ui/TextureTest.java

index e043274..56adcb1 100644 (file)
@@ -176,6 +176,9 @@ public class ApiHelper {
     public static final boolean HAS_OBJECT_ANIMATION =
             Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
 
+    public static final boolean HAS_GLES20_REQUIRED =
+            Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
     public static int getIntFieldIfExists(Class<?> klass, String fieldName,
             Class<?> obj, int defaultVal) {
         try {
index d1f5ba2..1dbee5d 100644 (file)
@@ -19,6 +19,10 @@ package com.android.gallery3d.ui;
 import android.graphics.Bitmap;
 import android.graphics.RectF;
 
+import com.android.gallery3d.common.ApiHelper;
+
+import javax.microedition.khronos.opengles.GL11;
+
 //
 // GLCanvas gives a convenient interface to draw using OpenGL.
 //
@@ -30,12 +34,31 @@ public abstract class GLCanvas {
         Additive, Mix,
     }
 
-    private static GLId sGLId = new GLIdImpl();
+    private static GLCanvas sInstance = instantiateCanvas();
+    private static GLId sGLId = instantiateGLId();
 
     public static GLId getGLId() {
         return sGLId;
     }
 
+    public static GLCanvas getInstance() {
+        return sInstance;
+    }
+
+    private static GLId instantiateGLId() {
+        return ApiHelper.HAS_GLES20_REQUIRED ? (GLES20Canvas) sInstance : new GLIdImpl();
+    }
+
+    private static GLCanvas instantiateCanvas() {
+        return ApiHelper.HAS_GLES20_REQUIRED ? new GLES20Canvas() : new GLCanvasImpl();
+    }
+
+    public static int getEGLContextClientVersion() {
+        return ApiHelper.HAS_GLES20_REQUIRED ? 2 : 1;
+    }
+
+    public abstract void initialize(GL11 gl);
+
     // Tells GLCanvas the size of the underlying GL surface. This should be
     // called before first drawing and when the size of GL surface is changed.
     // This is called by GLRoot and should not be called by the clients
@@ -188,10 +211,18 @@ public abstract class GLCanvas {
     /**
      * Generates buffers and uploads the buffer data.
      *
-     * @param buffers An array of buffers to upload
-     * @return The buffer IDs that were generated.
+     * @param buffer The buffer to upload
+     * @return The buffer ID that was generated.
+     */
+    public abstract int uploadBuffer(java.nio.FloatBuffer buffer);
+
+    /**
+     * Generates buffers and uploads the element array buffer data.
+     *
+     * @param buffer The buffer to upload
+     * @return The buffer ID that was generated.
      */
-    public abstract int[] uploadBuffers(java.nio.Buffer[] buffers);
+    public abstract int uploadBuffer(java.nio.ByteBuffer buffer);
 
     /**
      * Sets the blending algorithm if a texture is not opaque.
@@ -217,7 +248,7 @@ public abstract class GLCanvas {
 
     /**
      * Start/stop updating the stencil buffer.
-     * 
+     *
      * @param update True if the stencil should be updated, false otherwise.
      */
     public abstract void updateStencil(boolean update);
index ad49ec1..54c231c 100644 (file)
@@ -50,7 +50,7 @@ public class GLCanvasImpl extends GLCanvas {
             0, 0, 1, 1,              // used for drawing a line
             0, 0, 0, 1, 1, 1, 1, 0}; // used for drawing the outline of a rectangle
 
-    private final GL11 mGL;
+    private GL11 mGL;
 
     private final float mMatrixValues[] = new float[16];
     private final float mTextureMatrixValues[] = new float[16];
@@ -63,7 +63,7 @@ public class GLCanvasImpl extends GLCanvas {
 
     private int mBoxCoords;
 
-    private final GLState mGLState;
+    private GLState mGLState;
     private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
 
     private float mAlpha;
@@ -91,10 +91,7 @@ public class GLCanvasImpl extends GLCanvas {
     int mCountTextureRect;
     int mCountTextureOES;
 
-    GLCanvasImpl(GL11 gl) {
-        mGL = gl;
-        mGLState = new GLState(gl);
-        initialize();
+    GLCanvasImpl() {
     }
 
     @Override
@@ -146,9 +143,10 @@ public class GLCanvasImpl extends GLCanvas {
         return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
     }
 
-    private void initialize() {
-        GL11 gl = mGL;
-
+    @Override
+    public void initialize(GL11 gl) {
+        mGL = gl;
+        mGLState = new GLState(gl);
         // First create an nio buffer, then create a VBO from it.
         int size = BOX_COORDINATES.length * Float.SIZE / Byte.SIZE;
         FloatBuffer xyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
@@ -977,27 +975,24 @@ public class GLCanvasImpl extends GLCanvas {
     }
 
     @Override
-    public int[] uploadBuffers(Buffer[] buffers) {
-        int[] bufferIds = new int[buffers.length];
-        GLId glId = getGLId();
-        glId.glGenBuffers(bufferIds.length, bufferIds, 0);
+    public int uploadBuffer(FloatBuffer buf) {
+        return uploadBuffer(buf, Float.SIZE / Byte.SIZE);
+    }
 
-        for (int i = 0; i < bufferIds.length; i++) {
-            Buffer buf = buffers[i];
-            int elementSize = 0;
-            if (buf instanceof FloatBuffer) {
-                elementSize = Float.SIZE / Byte.SIZE;
-            } else if (buf instanceof ByteBuffer) {
-                elementSize = 1;
-            } else {
-                Utils.fail("Unknown element size for %s", buf.getClass().getSimpleName());
-            }
-            mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferIds[i]);
-            mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf,
-                    GL11.GL_STATIC_DRAW);
-        }
+    @Override
+    public int uploadBuffer(ByteBuffer buf) {
+        return uploadBuffer(buf, 1);
+    }
 
-        return bufferIds;
+    private int uploadBuffer(Buffer buf, int elementSize) {
+        int[] bufferIds = new int[1];
+        GLId glId = getGLId();
+        glId.glGenBuffers(bufferIds.length, bufferIds, 0);
+        int bufferId = bufferIds[0];
+        mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId);
+        mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf,
+                GL11.GL_STATIC_DRAW);
+        return bufferId;
     }
 
     @Override
diff --git a/src/com/android/gallery3d/ui/GLES20Canvas.java b/src/com/android/gallery3d/ui/GLES20Canvas.java
new file mode 100644 (file)
index 0000000..b720a77
--- /dev/null
@@ -0,0 +1,1068 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.gallery3d.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.IntArray;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+public class GLES20Canvas extends GLCanvas implements GLId {
+    // ************** Constants **********************
+    private static final String TAG = GLES20Canvas.class.getSimpleName();
+    private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE;
+    private static final float OPAQUE_ALPHA = 0.95f;
+
+    private static final int COORDS_PER_VERTEX = 2;
+    private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE;
+
+    private static final int COUNT_FILL_VERTEX = 4;
+    private static final int COUNT_LINE_VERTEX = 2;
+    private static final int COUNT_RECT_VERTEX = 4;
+    private static final int OFFSET_FILL_RECT = 0;
+    private static final int OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX;
+    private static final int OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX;
+
+    private static final float[] BOX_COORDINATES = {
+            0, 0, // Fill rectangle
+            1, 0,
+            0, 1,
+            1, 1,
+            0, 0, // Draw line
+            1, 1,
+            0, 0, // Draw rectangle outline
+            0, 1,
+            1, 1,
+            1, 0,
+    };
+
+    private static final String POSITION_ATTRIBUTE = "aPosition";
+    private static final String COLOR_UNIFORM = "uColor";
+    private static final String MATRIX_UNIFORM = "uMatrix";
+    private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix";
+    private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler";
+    private static final String ALPHA_UNIFORM = "uAlpha";
+    private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate";
+
+    private static final String DRAW_VERTEX_SHADER = ""
+            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+            + "void main() {\n"
+            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+            + "}\n";
+
+    private static final String DRAW_FRAGMENT_SHADER = ""
+            + "precision mediump float;\n"
+            + "uniform vec4 " + COLOR_UNIFORM + ";\n"
+            + "void main() {\n"
+            + "  gl_FragColor = " + COLOR_UNIFORM + ";\n"
+            + "}\n";
+
+    private static final String TEXTURE_VERTEX_SHADER = ""
+            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+            + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n"
+            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "void main() {\n"
+            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+            + "  vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n"
+            + "}\n";
+
+    private static final String MESH_VERTEX_SHADER = ""
+            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+            + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "void main() {\n"
+            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+            + "  vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n"
+            + "}\n";
+
+    private static final String TEXTURE_FRAGMENT_SHADER = ""
+            + "precision mediump float;\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "uniform float " + ALPHA_UNIFORM + ";\n"
+            + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+            + "void main() {\n"
+            + "  gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
+            + "  gl_FragColor.a *= " + ALPHA_UNIFORM + ";\n"
+            + "}\n";
+
+    private static final String OES_TEXTURE_FRAGMENT_SHADER = ""
+            + "#extension GL_OES_EGL_image_external : require\n"
+            + "precision mediump float;\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "uniform float " + ALPHA_UNIFORM + ";\n"
+            + "uniform samplerExternalOES " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+            + "void main() {\n"
+            + "  gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
+            + "  gl_FragColor.a *= " + ALPHA_UNIFORM + ";\n"
+            + "}\n";
+
+    private static final int INITIAL_RESTORE_STATE_SIZE = 8;
+    private static final int MATRIX_SIZE = 16;
+
+    // Keep track of restore state
+    private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE];
+    private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE];
+    private IntArray mSaveFlags = new IntArray();
+    private ArrayList<Blending> mBlendings = new ArrayList<Blending>();
+
+    private int mCurrentAlphaIndex = 0;
+    private int mCurrentMatrixIndex = 0;
+
+    // Viewport size
+    private int mWidth;
+    private int mHeight;
+
+    // Projection matrix
+    private float[] mProjectionMatrix = new float[MATRIX_SIZE];
+
+    // Screen size for when we aren't bound to a texture
+    private int mScreenWidth;
+    private int mScreenHeight;
+
+    // GL programs
+    private int mDrawProgram;
+    private int mTextureProgram;
+    private int mOesTextureProgram;
+    private int mMeshProgram;
+
+    // GL buffer containing BOX_COORDINATES
+    private int mBoxCoordinates;
+
+    // Handle indices -- common
+    private static final int INDEX_POSITION = 0;
+    private static final int INDEX_MATRIX = 1;
+
+    // Handle indices -- draw
+    private static final int INDEX_COLOR = 2;
+
+    // Handle indices -- texture
+    private static final int INDEX_TEXTURE_MATRIX = 2;
+    private static final int INDEX_TEXTURE_SAMPLER = 3;
+    private static final int INDEX_ALPHA = 4;
+
+    // Handle indices -- mesh
+    private static final int INDEX_TEXTURE_COORD = 2;
+
+    private abstract static class ShaderParameter {
+        public int handle;
+        protected final String mName;
+
+        public ShaderParameter(String name) {
+            mName = name;
+        }
+
+        public abstract void loadHandle(int program);
+    }
+
+    private static class UniformShaderParameter extends ShaderParameter {
+        public UniformShaderParameter(String name) {
+            super(name);
+        }
+
+        @Override
+        public void loadHandle(int program) {
+            handle = GLES20.glGetUniformLocation(program, mName);
+            checkError();
+        }
+    }
+
+    private static class AttributeShaderParameter extends ShaderParameter {
+        public AttributeShaderParameter(String name) {
+            super(name);
+        }
+
+        @Override
+        public void loadHandle(int program) {
+            handle = GLES20.glGetAttribLocation(program, mName);
+            checkError();
+        }
+    }
+
+    ShaderParameter[] mDrawParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR
+    };
+    ShaderParameter[] mTextureParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
+            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+    };
+    ShaderParameter[] mOesTextureParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
+            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+    };
+    ShaderParameter[] mMeshParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD
+            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+    };
+
+    private final IntArray mUnboundTextures = new IntArray();
+    private final IntArray mDeleteBuffers = new IntArray();
+
+    // Keep track of statistics for debugging
+    private int mCountDrawMesh = 0;
+    private int mCountTextureRect = 0;
+    private int mCountFillRect = 0;
+    private int mCountDrawLine = 0;
+
+    private int mNextTextureId = 1;
+
+    // Buffer for framebuffer IDs -- we keep track so we can switch the attached
+    // texture.
+    private int[] mFrameBuffer = new int[1];
+
+    // Bound textures.
+    private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>();
+
+    // Temporary variables used within calculations
+    private final float[] mTempMatrix = new float[32];
+    private final float[] mTempColor = new float[4];
+    private final RectF mTempSourceRect = new RectF();
+    private final RectF mTempTargetRect = new RectF();
+    private final float[] mTempTextureMatrix = new float[MATRIX_SIZE];
+    private final int[] mTempIntArray = new int[1];
+
+    public GLES20Canvas() {
+        Matrix.setIdentityM(mTempTextureMatrix, 0);
+        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
+        mAlphas[mCurrentAlphaIndex] = 1f;
+        mTargetTextures.add(null);
+    }
+
+    @Override
+    public void initialize(GL11 gl) {
+        FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES);
+        mBoxCoordinates = uploadBuffer(boxBuffer);
+
+        int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER);
+        int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER);
+        int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER);
+        int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER);
+        int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER);
+        int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
+                OES_TEXTURE_FRAGMENT_SHADER);
+
+        mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters);
+        mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader,
+                mTextureParameters);
+        mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader,
+                mOesTextureParameters);
+        mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters);
+
+        mBlendings.clear();
+        mBlendings.add(null);
+        setBlending(Blending.Mix);
+    }
+
+    private static FloatBuffer createBuffer(float[] values) {
+        // First create an nio buffer, then create a VBO from it.
+        int size = values.length * FLOAT_SIZE;
+        FloatBuffer buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder())
+                .asFloatBuffer();
+        buffer.put(values, 0, values.length).position(0);
+        return buffer;
+    }
+
+    private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) {
+        int program = GLES20.glCreateProgram();
+        checkError();
+        if (program == 0) {
+            throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError());
+        }
+        GLES20.glAttachShader(program, vertexShader);
+        checkError();
+        GLES20.glAttachShader(program, fragmentShader);
+        checkError();
+        GLES20.glLinkProgram(program);
+        checkError();
+        int[] mLinkStatus = mTempIntArray;
+        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0);
+        if (mLinkStatus[0] != GLES20.GL_TRUE) {
+            Log.e(TAG, "Could not link program: ");
+            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+            GLES20.glDeleteProgram(program);
+            program = 0;
+        }
+        for (int i = 0; i < params.length; i++) {
+            params[i].loadHandle(program);
+        }
+        return program;
+    }
+
+    private static int loadShader(int type, String shaderCode) {
+        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
+        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
+        int shader = GLES20.glCreateShader(type);
+
+        // add the source code to the shader and compile it
+        GLES20.glShaderSource(shader, shaderCode);
+        checkError();
+        GLES20.glCompileShader(shader);
+        checkError();
+
+        return shader;
+    }
+
+    @Override
+    public void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+        GLES20.glViewport(0, 0, mWidth, mHeight);
+        checkError();
+        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
+        Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1);
+        if (getTargetTexture() == null) {
+            mScreenWidth = width;
+            mScreenHeight = height;
+            Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);
+            Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);
+        }
+    }
+
+    @Override
+    public void clearBuffer() {
+        GLES20.glClearColor(0f, 0f, 0f, 1f);
+        checkError();
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        checkError();
+    }
+
+    @Override
+    public void clearBuffer(float[] argb) {
+        GLES20.glClearColor(argb[1], argb[2], argb[3], argb[0]);
+        checkError();
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        checkError();
+    }
+
+    @Override
+    public float getAlpha() {
+        return mAlphas[mCurrentAlphaIndex];
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        mAlphas[mCurrentAlphaIndex] = alpha;
+    }
+
+    @Override
+    public void multiplyAlpha(float alpha) {
+        setAlpha(getAlpha() * alpha);
+    }
+
+    @Override
+    public void translate(float x, float y, float z) {
+        Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z);
+    }
+
+    // This is a faster version of translate(x, y, z) because
+    // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
+    // (3) we unroll the loop
+    @Override
+    public void translate(float x, float y) {
+        int index = mCurrentMatrixIndex;
+        float[] m = mMatrices;
+        m[index + 12] += m[index + 0] * x + m[index + 4] * y;
+        m[index + 13] += m[index + 1] * x + m[index + 5] * y;
+        m[index + 14] += m[index + 2] * x + m[index + 6] * y;
+        m[index + 15] += m[index + 3] * x + m[index + 7] * y;
+    }
+
+    @Override
+    public void scale(float sx, float sy, float sz) {
+        Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz);
+    }
+
+    @Override
+    public void rotate(float angle, float x, float y, float z) {
+        if (angle == 0f) {
+            return;
+        }
+        float[] temp = mTempMatrix;
+        Matrix.setRotateM(temp, 0, angle, x, y, z);
+        float[] matrix = mMatrices;
+        int index = mCurrentMatrixIndex;
+        Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0);
+        System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE);
+    }
+
+    @Override
+    public void multiplyMatrix(float[] matrix, int offset) {
+        float[] temp = mTempMatrix;
+        float[] currentMatrix = mMatrices;
+        int index = mCurrentMatrixIndex;
+        Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset);
+        System.arraycopy(temp, 0, currentMatrix, index, 16);
+    }
+
+    @Override
+    public void save() {
+        save(SAVE_FLAG_ALL);
+    }
+
+    @Override
+    public void save(int saveFlags) {
+        boolean saveAlpha = (saveFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
+        if (saveAlpha) {
+            float currentAlpha = getAlpha();
+            mCurrentAlphaIndex++;
+            if (mAlphas.length <= mCurrentAlphaIndex) {
+                mAlphas = Arrays.copyOf(mAlphas, mAlphas.length * 2);
+            }
+            mAlphas[mCurrentAlphaIndex] = currentAlpha;
+        }
+        boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
+        if (saveMatrix) {
+            int currentIndex = mCurrentMatrixIndex;
+            mCurrentMatrixIndex += MATRIX_SIZE;
+            if (mMatrices.length <= mCurrentMatrixIndex) {
+                mMatrices = Arrays.copyOf(mMatrices, mMatrices.length * 2);
+            }
+            System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE);
+        }
+        boolean saveBlending = (saveFlags & SAVE_FLAG_BLEND) == SAVE_FLAG_BLEND;
+        if (saveBlending) {
+            mBlendings.add(mBlendings.get(mBlendings.size() - 1));
+        }
+        mSaveFlags.add(saveFlags);
+    }
+
+    @Override
+    public void restore() {
+        int restoreFlags = mSaveFlags.removeLast();
+        boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
+        if (restoreAlpha) {
+            mCurrentAlphaIndex--;
+        }
+        boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
+        if (restoreMatrix) {
+            mCurrentMatrixIndex -= MATRIX_SIZE;
+        }
+        boolean restoreBlending = (restoreFlags & SAVE_FLAG_BLEND) == SAVE_FLAG_BLEND;
+        if (restoreBlending) {
+            setBlending(mBlendings.get(mBlendings.size() - 2));
+            mBlendings.remove(mBlendings.size() - 1);
+        }
+    }
+
+    @Override
+    public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {
+        draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1,
+                paint);
+        mCountDrawLine++;
+    }
+
+    @Override
+    public void drawRect(float x, float y, float width, float height, GLPaint paint) {
+        draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint);
+        mCountDrawLine++;
+    }
+
+    private void draw(int type, int offset, int count, float x, float y, float width, float height,
+            GLPaint paint) {
+        draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth());
+    }
+
+    private void draw(int type, int offset, int count, float x, float y, float width, float height,
+            int color, float lineWidth) {
+        prepareDraw(offset, color, lineWidth);
+        draw(mDrawParameters, type, count, x, y, width, height);
+    }
+
+    private void prepareDraw(int offset, int color, float lineWidth) {
+        GLES20.glUseProgram(mDrawProgram);
+        checkError();
+        if (lineWidth > 0) {
+            GLES20.glLineWidth(lineWidth);
+            checkError();
+        }
+        float[] colorArray = getColor(color);
+        boolean blendingEnabled = (colorArray[3] < 1f);
+        enableBlending(blendingEnabled);
+        if (blendingEnabled) {
+            GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
+            checkError();
+        }
+
+        GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0);
+        setPosition(mDrawParameters, offset);
+        checkError();
+    }
+
+    private float[] getColor(int color) {
+        float alpha = ((color >>> 24) & 0xFF) / 255f * getAlpha();
+        float red = ((color >>> 16) & 0xFF) / 255f * alpha;
+        float green = ((color >>> 8) & 0xFF) / 255f * alpha;
+        float blue = (color & 0xFF) / 255f * alpha;
+        mTempColor[0] = red;
+        mTempColor[1] = green;
+        mTempColor[2] = blue;
+        mTempColor[3] = alpha;
+        return mTempColor;
+    }
+
+    private void enableBlending(boolean enableBlending) {
+        if (enableBlending) {
+            GLES20.glEnable(GLES20.GL_BLEND);
+            checkError();
+        } else {
+            GLES20.glDisable(GLES20.GL_BLEND);
+            checkError();
+        }
+    }
+
+    private void setPosition(ShaderParameter[] params, int offset) {
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates);
+        checkError();
+        GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX,
+                GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE);
+        checkError();
+    }
+
+    private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width,
+            float height) {
+        setMatrix(params, x, y, width, height);
+        int positionHandle = params[INDEX_POSITION].handle;
+        GLES20.glEnableVertexAttribArray(positionHandle);
+        checkError();
+        GLES20.glDrawArrays(type, 0, count);
+        checkError();
+        GLES20.glDisableVertexAttribArray(positionHandle);
+        checkError();
+    }
+
+    private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) {
+        Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
+        Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
+        Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0);
+        GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE);
+        checkError();
+    }
+
+    @Override
+    public void fillRect(float x, float y, float width, float height, int color) {
+        draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height,
+                color, 0f);
+        mCountFillRect++;
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, int x, int y, int width, int height) {
+        if (width <= 0 || height <= 0) {
+            return;
+        }
+        copyTextureCoordinates(texture, mTempSourceRect);
+        mTempTargetRect.set(x, y, x + width, y + height);
+        convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
+        drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
+    }
+
+    private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) {
+        int left = 0;
+        int top = 0;
+        int right = texture.getWidth();
+        int bottom = texture.getHeight();
+        if (texture.hasBorder()) {
+            left = 1;
+            top = 1;
+            right -= 1;
+            bottom -= 1;
+        }
+        outRect.set(left, top, right, bottom);
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, RectF source, RectF target) {
+        if (target.width() <= 0 || target.height() <= 0) {
+            return;
+        }
+        mTempSourceRect.set(source);
+        mTempTargetRect.set(target);
+
+        convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
+        drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w,
+            int h) {
+        if (w <= 0 || h <= 0) {
+            return;
+        }
+        mTempTargetRect.set(x, y, x + w, y + h);
+        drawTextureRect(texture, textureTransform, mTempTargetRect);
+    }
+
+    private void drawTextureRect(BasicTexture texture, RectF source, RectF target) {
+        setTextureMatrix(source);
+        drawTextureRect(texture, mTempTextureMatrix, target);
+    }
+
+    private void setTextureMatrix(RectF source) {
+        mTempTextureMatrix[0] = source.width();
+        mTempTextureMatrix[5] = source.height();
+        mTempTextureMatrix[12] = source.left;
+        mTempTextureMatrix[13] = source.top;
+    }
+
+    // This function changes the source coordinate to the texture coordinates.
+    // It also clips the source and target coordinates if it is beyond the
+    // bound of the texture.
+    private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) {
+        int width = texture.getWidth();
+        int height = texture.getHeight();
+        int texWidth = texture.getTextureWidth();
+        int texHeight = texture.getTextureHeight();
+        // Convert to texture coordinates
+        source.left /= texWidth;
+        source.right /= texWidth;
+        source.top /= texHeight;
+        source.bottom /= texHeight;
+
+        // Clip if the rendering range is beyond the bound of the texture.
+        float xBound = (float) width / texWidth;
+        if (source.right > xBound) {
+            target.right = target.left + target.width() * (xBound - source.left) / source.width();
+            source.right = xBound;
+        }
+        float yBound = (float) height / texHeight;
+        if (source.bottom > yBound) {
+            target.bottom = target.top + target.height() * (yBound - source.top) / source.height();
+            source.bottom = yBound;
+        }
+    }
+
+    private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) {
+        ShaderParameter[] params = prepareTexture(texture);
+        setPosition(params, OFFSET_FILL_RECT);
+        GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0);
+        checkError();
+        draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top,
+                target.width(), target.height());
+        mCountTextureRect++;
+    }
+
+    private ShaderParameter[] prepareTexture(BasicTexture texture) {
+        ShaderParameter[] params;
+        int program;
+        if (texture.getTarget() == GLES20.GL_TEXTURE_2D) {
+            params = mTextureParameters;
+            program = mTextureProgram;
+        } else {
+            params = mOesTextureParameters;
+            program = mOesTextureProgram;
+        }
+        prepareTexture(texture, program, params);
+        return params;
+    }
+
+    private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
+        GLES20.glUseProgram(program);
+        checkError();
+        enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        checkError();
+        texture.onBind(this);
+        GLES20.glBindTexture(texture.getTarget(), texture.getId());
+        checkError();
+        GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
+        checkError();
+        GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha());
+        checkError();
+    }
+
+    @Override
+    public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer,
+            int indexBuffer, int indexCount) {
+        prepareTexture(texture, mMeshProgram, mMeshParameters);
+
+        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
+        checkError();
+
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer);
+        checkError();
+        int positionHandle = mMeshParameters[INDEX_POSITION].handle;
+        GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
+                VERTEX_STRIDE, 0);
+        checkError();
+
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer);
+        checkError();
+        int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle;
+        GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
+                false, VERTEX_STRIDE, 0);
+        checkError();
+
+        GLES20.glEnableVertexAttribArray(positionHandle);
+        checkError();
+        GLES20.glEnableVertexAttribArray(texCoordHandle);
+        checkError();
+
+        setMatrix(mMeshParameters, x, y, 1, 1);
+        GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0);
+        checkError();
+
+        GLES20.glDisableVertexAttribArray(positionHandle);
+        checkError();
+        GLES20.glDisableVertexAttribArray(texCoordHandle);
+        checkError();
+        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
+        checkError();
+        mCountDrawMesh++;
+    }
+
+    @Override
+    public void drawMixed(BasicTexture texture, int toColor, float ratio, int x, int y, int w, int h) {
+        copyTextureCoordinates(texture, mTempSourceRect);
+        mTempTargetRect.set(x, y, x + w, y + h);
+        drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect);
+    }
+
+    @Override
+    public void drawMixed(BasicTexture texture, int toColor, float ratio, RectF source, RectF target) {
+        if (target.width() <= 0 || target.height() <= 0) {
+            return;
+        }
+        save(SAVE_FLAG_ALPHA);
+
+        float currentAlpha = getAlpha();
+        float cappedRatio = Math.min(1f, Math.max(0f, ratio));
+
+        float textureAlpha = (1f - cappedRatio) * currentAlpha;
+        setAlpha(textureAlpha);
+        drawTexture(texture, source, target);
+
+        float colorAlpha = cappedRatio * currentAlpha;
+        setAlpha(colorAlpha);
+        fillRect(target.left, target.top, target.width(), target.height(), toColor);
+
+        restore();
+    }
+
+    @Override
+    public boolean unloadTexture(BasicTexture texture) {
+        boolean unload = texture.isLoaded();
+        if (unload) {
+            synchronized (mUnboundTextures) {
+                mUnboundTextures.add(texture.getId());
+            }
+        }
+        return unload;
+    }
+
+    @Override
+    public void deleteBuffer(int bufferId) {
+        synchronized (mUnboundTextures) {
+            mDeleteBuffers.add(bufferId);
+        }
+    }
+
+    @Override
+    public void deleteRecycledResources() {
+        synchronized (mUnboundTextures) {
+            IntArray ids = mUnboundTextures;
+            if (mUnboundTextures.size() > 0) {
+                glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0);
+                ids.clear();
+            }
+
+            ids = mDeleteBuffers;
+            if (ids.size() > 0) {
+                glDeleteBuffers(null, ids.size(), ids.getInternalArray(), 0);
+                ids.clear();
+            }
+        }
+    }
+
+    @Override
+    public void dumpStatisticsAndClear() {
+        String line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh,
+                mCountTextureRect, mCountFillRect, mCountDrawLine);
+        mCountDrawMesh = 0;
+        mCountTextureRect = 0;
+        mCountFillRect = 0;
+        mCountDrawLine = 0;
+        Log.d(TAG, line);
+    }
+
+    @Override
+    public void endRenderTarget() {
+        RawTexture oldTexture = mTargetTextures.remove(mTargetTextures.size() - 1);
+        RawTexture texture = getTargetTexture();
+        setRenderTarget(oldTexture, texture);
+        restore(); // restore matrix and alpha
+    }
+
+    @Override
+    public void beginRenderTarget(RawTexture texture) {
+        save(); // save matrix and alpha and blending
+        RawTexture oldTexture = getTargetTexture();
+        mTargetTextures.add(texture);
+        setRenderTarget(oldTexture, texture);
+    }
+
+    private RawTexture getTargetTexture() {
+        return mTargetTextures.get(mTargetTextures.size() - 1);
+    }
+
+    private void setRenderTarget(BasicTexture oldTexture, RawTexture texture) {
+        if (oldTexture == null && texture != null) {
+            GLES20.glGenFramebuffers(1, mFrameBuffer, 0);
+            checkError();
+            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]);
+            checkError();
+        } else if (oldTexture != null && texture == null) {
+            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+            checkError();
+            GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0);
+            checkError();
+        }
+
+        if (texture == null) {
+            setSize(mScreenWidth, mScreenHeight);
+        } else {
+            setSize(texture.getWidth(), texture.getHeight());
+
+            if (!texture.isLoaded()) {
+                texture.prepare(this);
+            }
+
+            GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
+                    texture.getTarget(), texture.getId(), 0);
+            checkError();
+
+            checkFramebufferStatus();
+        }
+    }
+
+    private static void checkFramebufferStatus() {
+        int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
+        if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
+            String msg = "";
+            switch (status) {
+                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+                    msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
+                    break;
+                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+                    msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+                    break;
+                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+                    msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
+                    break;
+                case GLES20.GL_FRAMEBUFFER_UNSUPPORTED:
+                    msg = "GL_FRAMEBUFFER_UNSUPPORTED";
+                    break;
+            }
+            throw new RuntimeException(msg + ":" + Integer.toHexString(status));
+        }
+    }
+
+    @Override
+    public void setTextureParameters(BasicTexture texture) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+    }
+
+    @Override
+    public void initializeTextureSize(BasicTexture texture, int format, int type) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        int width = texture.getTextureWidth();
+        int height = texture.getTextureHeight();
+        GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null);
+    }
+
+    @Override
+    public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        GLUtils.texImage2D(target, 0, bitmap, 0);
+    }
+
+    @Override
+    public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
+            int format, int type) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
+    }
+
+    @Override
+    public int uploadBuffer(FloatBuffer buf) {
+        return uploadBuffer(buf, FLOAT_SIZE);
+    }
+
+    @Override
+    public int uploadBuffer(ByteBuffer buf) {
+        return uploadBuffer(buf, 1);
+    }
+
+    private int uploadBuffer(Buffer buffer, int elementSize) {
+        glGenBuffers(1, mTempIntArray, 0);
+        checkError();
+        int bufferId = mTempIntArray[0];
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId);
+        checkError();
+        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer,
+                GLES20.GL_STATIC_DRAW);
+        checkError();
+        return bufferId;
+    }
+
+    @Override
+    public void setBlending(Blending blending) {
+        Blending currentBlending = mBlendings.get(mBlendings.size() - 1);
+        if (currentBlending == blending) {
+            return; // nothing to change
+        }
+        mBlendings.set(mBlendings.size() - 1, blending);
+        int srcFunc = GLES20.GL_ONE;
+        int dstFunc;
+        switch (blending) {
+            case Additive:
+                dstFunc = GLES20.GL_ONE;
+                break;
+            case Mix:
+                dstFunc = GLES20.GL_ONE_MINUS_SRC_ALPHA;
+                break;
+            default:
+                Utils.fail("Unknown blend: " + blending);
+                dstFunc = GLES20.GL_ONE_MINUS_SRC_ALPHA;
+                break;
+        }
+        GLES20.glBlendFunc(srcFunc, dstFunc);
+        checkError();
+    }
+
+    @Override
+    public int generateTexture() {
+        // Can use anything as a lock. No need to create a new object.
+        synchronized (mTempIntArray) {
+            return mNextTextureId++;
+        }
+    }
+
+    @Override
+    public void glGenBuffers(int n, int[] buffers, int offset) {
+        GLES20.glGenBuffers(n, buffers, offset);
+        checkError();
+    }
+
+    @Override
+    public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
+        GLES20.glDeleteTextures(n, textures, offset);
+        checkError();
+    }
+
+    @Override
+    public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
+        GLES20.glDeleteBuffers(n, buffers, offset);
+        checkError();
+    }
+
+    @Override
+    public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
+        GLES20.glDeleteFramebuffers(n, buffers, offset);
+        checkError();
+    }
+
+    @Override
+    public void enableStencil() {
+        GLES20.glEnable(GLES20.GL_STENCIL_TEST);
+    }
+
+    @Override
+    public void disableStencil() {
+        GLES20.glDisable(GLES20.GL_STENCIL_TEST);
+    }
+
+    @Override
+    public void clearStencilBuffer() {
+        GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT);
+    }
+
+    @Override
+    public void updateStencil(boolean update) {
+        int passOp = update ? GLES20.GL_REPLACE : GLES20.GL_KEEP;
+        GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, passOp);
+    }
+
+    @Override
+    public void drawOnlyOutsideStencil(boolean onlyOutside) {
+        int func = onlyOutside ? GLES20.GL_NOTEQUAL : GLES20.GL_ALWAYS;
+        GLES20.glStencilFunc(func, 1, 1);
+    }
+
+    private static void checkError() {
+        int error = GLES20.glGetError();
+        if (error != 0) {
+            Throwable t = new Throwable();
+            Log.e(TAG, "GL error: " + error, t);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static void printMatrix(String message, float[] m, int offset) {
+        StringBuilder b = new StringBuilder(message);
+        for (int i = 0; i < MATRIX_SIZE; i++) {
+            b.append(' ');
+            if (i % 4 == 0) {
+                b.append('\n');
+            }
+            b.append(m[offset + i]);
+        }
+        Log.v(TAG, b.toString());
+    }
+
+}
index ea457f7..e2268fa 100644 (file)
@@ -117,6 +117,7 @@ public class GLRootView extends GLSurfaceView
         super(context, attrs);
         mFlags |= FLAG_INITIALIZED;
         setBackgroundDrawable(null);
+        setEGLContextClientVersion(GLCanvas.getEGLContextClientVersion());
         setEGLConfigChooser(mEglConfigChooser);
         setRenderer(this);
         if (ApiHelper.USE_888_PIXEL_FORMAT) {
@@ -283,7 +284,8 @@ public class GLRootView extends GLSurfaceView
         mRenderLock.lock();
         try {
             mGL = gl;
-            mCanvas = new GLCanvasImpl(gl);
+            mCanvas = GLCanvas.getInstance();
+            mCanvas.initialize(gl);
             BasicTexture.invalidateAllTextures();
         } finally {
             mRenderLock.unlock();
index deeb3b7..f57a312 100644 (file)
@@ -49,12 +49,35 @@ class GalleryEGLConfigChooser implements EGLConfigChooser {
             EGL10.EGL_NONE
     };
 
+    private final int mConfig2Spec565[] = new int[] {
+            EGL10.EGL_RED_SIZE, 5,
+            EGL10.EGL_GREEN_SIZE, 6,
+            EGL10.EGL_BLUE_SIZE, 5,
+            EGL10.EGL_ALPHA_SIZE, 0,
+            EGL10.EGL_RENDERABLE_TYPE, 4, /* EGL_OPENGL_ES2_BIT */
+            EGL10.EGL_NONE
+    };
+
+    private final int mConfig2Spec888[] = new int[] {
+            EGL10.EGL_RED_SIZE, 8,
+            EGL10.EGL_GREEN_SIZE, 8,
+            EGL10.EGL_BLUE_SIZE, 8,
+            EGL10.EGL_ALPHA_SIZE, 0,
+            EGL10.EGL_RENDERABLE_TYPE, 4, /* EGL_OPENGL_ES2_BIT */
+            EGL10.EGL_NONE
+    };
+
     @Override
     public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
         int[] numConfig = new int[1];
-        int mConfigSpec[] = ApiHelper.USE_888_PIXEL_FORMAT
-                ? mConfigSpec888 : mConfigSpec565;
-        if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, numConfig)) {
+
+        int configSpec[];
+        if (GLCanvas.getEGLContextClientVersion() == 2) {
+            configSpec = ApiHelper.USE_888_PIXEL_FORMAT ? mConfig2Spec888 : mConfig2Spec565;
+        } else {
+            configSpec = ApiHelper.USE_888_PIXEL_FORMAT ? mConfigSpec888 : mConfigSpec565;
+        }
+        if (!egl.eglChooseConfig(display, configSpec, null, 0, numConfig)) {
             throw new RuntimeException("eglChooseConfig failed");
         }
 
@@ -64,7 +87,7 @@ class GalleryEGLConfigChooser implements EGLConfigChooser {
 
         EGLConfig[] configs = new EGLConfig[numConfig[0]];
         if (!egl.eglChooseConfig(display,
-                mConfigSpec, configs, configs.length, numConfig)) {
+                configSpec, configs, configs.length, numConfig)) {
             throw new RuntimeException();
         }
 
index b60504c..f5c6145 100644 (file)
@@ -23,7 +23,6 @@ import android.graphics.Rect;
 
 import com.android.gallery3d.common.Utils;
 
-import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
@@ -198,7 +197,9 @@ class NinePatchInstance {
     private ByteBuffer mIndexBuffer;
 
     // Names for buffer names: xy, uv, index.
-    private int[] mBufferNames;
+    private int mXyBufferName = -1;
+    private int mUvBufferName;
+    private int mIndexBufferName;
 
     private int mIdxCount;
 
@@ -395,10 +396,9 @@ class NinePatchInstance {
     }
 
     private void prepareBuffers(GLCanvas canvas) {
-        Buffer[] buffers = {
-                mXyBuffer, mUvBuffer, mIndexBuffer
-        };
-        mBufferNames = canvas.uploadBuffers(buffers);
+        mXyBufferName = canvas.uploadBuffer(mXyBuffer);
+        mUvBufferName = canvas.uploadBuffer(mUvBuffer);
+        mIndexBufferName = canvas.uploadBuffer(mIndexBuffer);
 
         // These buffers are never used again.
         mXyBuffer = null;
@@ -407,19 +407,18 @@ class NinePatchInstance {
     }
 
     public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
-        if (mBufferNames == null) {
+        if (mXyBufferName == -1) {
             prepareBuffers(canvas);
         }
-        canvas.drawMesh(tex, x, y, mBufferNames[0], mBufferNames[1],
-                mBufferNames[2], mIdxCount);
+        canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount);
     }
 
     public void recycle(GLCanvas canvas) {
-        if (mBufferNames != null) {
-            canvas.deleteBuffer(mBufferNames[0]);
-            canvas.deleteBuffer(mBufferNames[1]);
-            canvas.deleteBuffer(mBufferNames[2]);
-            mBufferNames = null;
+        if (mXyBuffer == null) {
+            canvas.deleteBuffer(mXyBufferName);
+            canvas.deleteBuffer(mUvBufferName);
+            canvas.deleteBuffer(mIndexBufferName);
+            mXyBufferName = -1;
         }
     }
 }
index e67848f..53aef9e 100644 (file)
@@ -16,6 +16,8 @@
 
 package com.android.gallery3d.ui;
 
+import android.opengl.GLES20;
+
 import javax.microedition.khronos.opengles.GL11;
 
 public class RawTexture extends BasicTexture {
@@ -26,8 +28,6 @@ public class RawTexture extends BasicTexture {
     public RawTexture(int width, int height, boolean opaque) {
         mOpaque = opaque;
         setSize(width, height);
-        GLId glId = GLCanvas.getGLId();
-        mId = glId.generateTexture();
     }
 
     @Override
@@ -36,8 +36,10 @@ public class RawTexture extends BasicTexture {
     }
 
     protected void prepare(GLCanvas canvas) {
-        canvas.setTextureParameters(this);
+        GLId glId = GLCanvas.getGLId();
+        mId = glId.generateTexture();
         canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
+        canvas.setTextureParameters(this);
         mState = STATE_LOADED;
         setAssociatedCanvas(canvas);
     }
index 082089a..2c4dc2c 100644 (file)
@@ -31,6 +31,11 @@ public class IntArray {
         mData[mSize++] = value;
     }
 
+    public int removeLast() {
+        mSize--;
+        return mData[mSize];
+    }
+
     public int size() {
         return mSize;
     }
index afa34ac..e993223 100644 (file)
@@ -19,7 +19,8 @@ package com.android.gallery3d.ui;
 import android.graphics.Bitmap;
 import android.graphics.RectF;
 
-import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
 
 import javax.microedition.khronos.opengles.GL11;
 
@@ -132,8 +133,12 @@ public class GLCanvasStub extends GLCanvas {
             int format, int type) {
     }
     @Override
-    public int[] uploadBuffers(Buffer[] buffers) {
-        return null;
+    public int uploadBuffer(ByteBuffer buffer) {
+        return 0;
+    }
+    @Override
+    public int uploadBuffer(FloatBuffer buffer) {
+        return 0;
     }
     @Override
     public void setBlending(Blending blending) {
@@ -153,4 +158,7 @@ public class GLCanvasStub extends GLCanvas {
     @Override
     public void drawOnlyOutsideStencil(boolean onlyOutside) {
     }
+    @Override
+    public void initialize(GL11 gl) {
+    }
 }
index 8df7a3d..ddbddad 100644 (file)
@@ -39,7 +39,8 @@ public class GLCanvasTest extends TestCase {
     @SmallTest
     public void testSetSize() {
         GL11 glStub = new GLStub();
-        GLCanvas canvas = new GLCanvasImpl(glStub);
+        GLCanvas canvas = new GLCanvasImpl();
+        canvas.initialize(glStub);
         canvas.setSize(100, 200);
         canvas.setSize(1000, 100);
         try {
@@ -57,7 +58,8 @@ public class GLCanvasTest extends TestCase {
 
     private static class ClearBufferTest extends GLMock {
         void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
+            GLCanvas canvas = new GLCanvasImpl();
+            canvas.initialize(this);
             assertEquals(0, mGLClearCalled);
             canvas.clearBuffer();
             assertEquals(GL10.GL_COLOR_BUFFER_BIT, mGLClearMask);
@@ -79,7 +81,8 @@ public class GLCanvasTest extends TestCase {
                 0x7F010101, 0xFEFEFDFC, 0x017F8081, 0x027F8081, 0x2ADE4C4D
             };
 
-            GLCanvas canvas = new GLCanvasImpl(this);
+            GLCanvas canvas = new GLCanvasImpl();
+            canvas.initialize(this);
             canvas.setSize(400, 300);
             // Test one color to make sure blend function is set.
             assertEquals(0, mGLColorCalled);
@@ -107,7 +110,8 @@ public class GLCanvasTest extends TestCase {
     @SmallTest
     public void testSetGetMultiplyAlpha() {
         GL11 glStub = new GLStub();
-        GLCanvas canvas = new GLCanvasImpl(glStub);
+        GLCanvas canvas = new GLCanvasImpl();
+        canvas.initialize(glStub);
 
         canvas.setAlpha(1f);
         assertEquals(1f, canvas.getAlpha());
@@ -146,7 +150,8 @@ public class GLCanvasTest extends TestCase {
 
     private static class AlphaTest extends GLMock {
         void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
+            GLCanvas canvas = new GLCanvasImpl();
+            canvas.initialize(this);
             canvas.setSize(400, 300);
 
             assertEquals(0, mGLColorCalled);
@@ -188,7 +193,8 @@ public class GLCanvasTest extends TestCase {
         }
 
         void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
+            GLCanvas canvas = new GLCanvasImpl();
+            canvas.initialize(this);
             canvas.setSize(400, 300);
             canvas.drawLine(2, 7, 1, 8, newColorPaint(0) /* color */);
             assertTrue(mGLVertexArrayEnabled);
@@ -232,7 +238,8 @@ public class GLCanvasTest extends TestCase {
         }
 
         void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
+            GLCanvas canvas = new GLCanvasImpl();
+            canvas.initialize(this);
             canvas.setSize(400, 300);
             canvas.fillRect(2, 7, 1, 8, 0 /* color */);
             assertTrue(mGLVertexArrayEnabled);
@@ -294,7 +301,8 @@ public class GLCanvasTest extends TestCase {
         }
 
         void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
+            GLCanvas canvas = new GLCanvasImpl();
+            canvas.initialize(this);
             canvas.setSize(40, 50);
             int color = 0;
 
index 36446b3..361bf7b 100644 (file)
@@ -48,6 +48,7 @@ public class TextureTest extends TestCase {
             return GL11.GL_TEXTURE_2D;
         }
 
+        @Override
         public boolean isOpaque() {
             mOpaqueCalled++;
             return true;
@@ -61,7 +62,8 @@ public class TextureTest extends TestCase {
     @SmallTest
     public void testBasicTexture() {
         GL11 glStub = new GLStub();
-        GLCanvas canvas = new GLCanvasImpl(glStub);
+        GLCanvas canvas = new GLCanvasImpl();
+        canvas.initialize(glStub);
         MyBasicTexture texture = new MyBasicTexture(canvas, 47);
 
         assertEquals(47, texture.getId());
@@ -81,7 +83,8 @@ public class TextureTest extends TestCase {
         assertTrue(texture.isLoaded());
 
         // For a different GL, it's not loaded.
-        GLCanvas canvas2 = new GLCanvasImpl(new GLStub());
+        GLCanvas canvas2 = new GLCanvasImpl();
+        canvas2.initialize(glStub);
         assertFalse(texture.isLoaded());
 
         assertEquals(0, texture.mOnBindCalled);
@@ -135,7 +138,8 @@ public class TextureTest extends TestCase {
     @SmallTest
     public void testUploadedTexture() {
         GL11 glStub = new GLStub();
-        GLCanvas canvas = new GLCanvasImpl(glStub);
+        GLCanvas canvas = new GLCanvasImpl();
+        canvas.initialize(glStub);
         MyUploadedTexture texture = new MyUploadedTexture();
 
         // draw it and the bitmap should be fetched.
@@ -181,6 +185,7 @@ public class TextureTest extends TestCase {
             return GL11.GL_TEXTURE_2D;
         }
 
+        @Override
         public boolean isOpaque() {
             return true;
         }