blob: 0da3bae96dfbded706ad62a04ab3fba6630732a5 [file] [log] [blame]
/*
* 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.RectF;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Arrays;
import javax.microedition.khronos.opengles.GL11;
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 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 OFFSET_FILL_RECT = 0;
private static final int GL_TARGET = GL11.GL_TEXTURE_2D;
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 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_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 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 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 IntArray mSaveFlags = new IntArray();
private int mCurrentMatrixIndex = 0;
// Viewport size
private int mWidth;
private int mHeight;
// Projection matrix
private float[] mProjectionMatrix = new float[MATRIX_SIZE];
// GL programs
private int mTextureProgram;
// 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 -- texture
private static final int INDEX_TEXTURE_MATRIX = 2;
private static final int INDEX_TEXTURE_SAMPLER = 3;
private static final int INDEX_ALPHA = 4;
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();
}
}
private 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
};
private final IntArray mUnboundTextures = new IntArray();
// Temporary variables used within calculations
private final float[] mTempMatrix = new float[32];
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);
FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES);
mBoxCoordinates = uploadBuffer(boxBuffer);
int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER);
int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER);
mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader,
mTextureParameters);
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);
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();
}
// 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 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 save(int saveFlags) {
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 restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
if (restoreMatrix) {
mCurrentMatrixIndex -= MATRIX_SIZE;
}
}
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 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) {
outRect.set(0, 0, texture.getWidth(), texture.getHeight());
}
@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);
}
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());
}
private ShaderParameter[] prepareTexture(BasicTexture texture) {
ShaderParameter[] params;
int program;
params = mTextureParameters;
program = mTextureProgram;
prepareTexture(texture, program, params);
return params;
}
private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
deleteRecycledResources();
GLES20.glUseProgram(program);
checkError();
GLES20.glDisable(GLES20.GL_BLEND);
checkError();
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
checkError();
texture.onBind(this);
GLES20.glBindTexture(GL_TARGET, texture.getId());
checkError();
GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
checkError();
GLES20.glUniform1f(params[INDEX_ALPHA].handle, 1);
checkError();
}
@Override
public boolean unloadTexture(BasicTexture texture) {
boolean unload = texture.isLoaded();
if (unload) {
synchronized (mUnboundTextures) {
mUnboundTextures.add(texture.getId());
}
}
return unload;
}
@Override
public void deleteRecycledResources() {
synchronized (mUnboundTextures) {
IntArray ids = mUnboundTextures;
if (mUnboundTextures.size() > 0) {
mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0);
ids.clear();
}
}
}
@Override
public void setTextureParameters(BasicTexture texture) {
GLES20.glBindTexture(GL_TARGET, texture.getId());
checkError();
GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GL_TARGET, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GL_TARGET, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
}
@Override
public void initializeTextureSize(BasicTexture texture, int format, int type) {
GLES20.glBindTexture(GL_TARGET, texture.getId());
checkError();
int width = texture.getTextureWidth();
int height = texture.getTextureHeight();
GLES20.glTexImage2D(GL_TARGET, 0, format, width, height, 0, format, type, null);
}
@Override
public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
GLES20.glBindTexture(GL_TARGET, texture.getId());
checkError();
GLUtils.texImage2D(GL_TARGET, 0, bitmap, 0);
}
@Override
public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
int format, int type) {
GLES20.glBindTexture(GL_TARGET, texture.getId());
checkError();
GLUtils.texSubImage2D(GL_TARGET, 0, xOffset, yOffset, bitmap, format, type);
}
@Override
public int uploadBuffer(FloatBuffer buf) {
return uploadBuffer(buf, FLOAT_SIZE);
}
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);
}
}
@Override
public GLId getGLId() {
return mGLId;
}
}