| /* |
| * 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.glrenderer; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.Rect; |
| 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.ApiHelper; |
| 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; |
| |
| public class GLES20Canvas implements GLCanvas { |
| // ************** 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 float[] BOUNDS_COORDINATES = { |
| 0, 0, 0, 1, |
| 1, 1, 0, 1, |
| }; |
| |
| 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 *= " + 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 *= " + 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 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; |
| |
| // 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]; |
| |
| private static final GLId mGLId = new GLES20IdImpl(); |
| |
| public GLES20Canvas() { |
| Matrix.setIdentityM(mTempTextureMatrix, 0); |
| Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); |
| mAlphas[mCurrentAlphaIndex] = 1f; |
| mTargetTextures.add(null); |
| |
| 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); |
| GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); |
| checkError(); |
| |
| if (ApiHelper.USE_888_PIXEL_FORMAT) { |
| GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 2); |
| } else { |
| GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); |
| } |
| } |
| |
| 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); |
| } |
| 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; |
| } |
| } |
| |
| @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(); |
| GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); |
| 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(); |
| if (texture.isFlippedVertically()) { |
| save(SAVE_FLAG_MATRIX); |
| translate(0, target.centerY()); |
| scale(1, -1, 1); |
| translate(0, -target.centerY()); |
| } |
| draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top, |
| target.width(), target.height()); |
| if (texture.isFlippedVertically()) { |
| restore(); |
| } |
| 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.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 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) { |
| mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0); |
| ids.clear(); |
| } |
| |
| ids = mDeleteBuffers; |
| if (ids.size() > 0) { |
| mGLId.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) { |
| mGLId.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; |
| } |
| |
| public 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()); |
| } |
| |
| @Override |
| public void recoverFromLightCycle() { |
| GLES20.glViewport(0, 0, mWidth, mHeight); |
| GLES20.glDisable(GLES20.GL_DEPTH_TEST); |
| GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); |
| checkError(); |
| } |
| |
| @Override |
| public void getBounds(Rect bounds, int x, int y, int width, int height) { |
| Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f); |
| Matrix.scaleM(mTempMatrix, 0, width, height, 1f); |
| Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE, mTempMatrix, 0, BOUNDS_COORDINATES, 0); |
| Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE + 4, mTempMatrix, 0, BOUNDS_COORDINATES, 4); |
| bounds.left = Math.round(mTempMatrix[MATRIX_SIZE]); |
| bounds.right = Math.round(mTempMatrix[MATRIX_SIZE + 4]); |
| bounds.top = Math.round(mTempMatrix[MATRIX_SIZE + 1]); |
| bounds.bottom = Math.round(mTempMatrix[MATRIX_SIZE + 5]); |
| bounds.sort(); |
| } |
| |
| @Override |
| public GLId getGLId() { |
| return mGLId; |
| } |
| } |