8e88cb8478
Change-Id: Id79d11ed08085c4fe356739498a8b77952e30392
1010 lines
37 KiB
Java
1010 lines
37 KiB
Java
/*
|
|
* 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.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();
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|