/* * Copyright (C) 2010 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 com.android.gallery3d.common.Utils; import com.android.gallery3d.util.IntArray; import android.graphics.Rect; import android.graphics.RectF; import android.opengl.GLU; import android.opengl.Matrix; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.Stack; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11Ext; public class GLCanvasImpl implements GLCanvas { @SuppressWarnings("unused") private static final String TAG = "GLCanvasImp"; private static final float OPAQUE_ALPHA = 0.95f; private static final int OFFSET_FILL_RECT = 0; private static final int OFFSET_DRAW_LINE = 4; private static final int OFFSET_DRAW_RECT = 6; private static final float[] BOX_COORDINATES = { 0, 0, 1, 0, 0, 1, 1, 1, // used for filling a rectangle 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 final float mMatrixValues[] = new float[16]; private final float mTextureMatrixValues[] = new float[16]; // mapPoints needs 10 input and output numbers. private final float mMapPointsBuffer[] = new float[10]; private final float mTextureColor[] = new float[4]; private int mBoxCoords; private final GLState mGLState; private long mAnimationTime; private float mAlpha; private final Rect mClipRect = new Rect(); private final Stack<ConfigState> mRestoreStack = new Stack<ConfigState>(); private ConfigState mRecycledRestoreAction; private final RectF mDrawTextureSourceRect = new RectF(); private final RectF mDrawTextureTargetRect = new RectF(); private final float[] mTempMatrix = new float[32]; private final IntArray mUnboundTextures = new IntArray(); private final IntArray mDeleteBuffers = new IntArray(); private int mHeight; private boolean mBlendEnabled = true; // Drawing statistics int mCountDrawLine; int mCountFillRect; int mCountDrawMesh; int mCountTextureRect; int mCountTextureOES; GLCanvasImpl(GL11 gl) { mGL = gl; mGLState = new GLState(gl); initialize(); } public void setSize(int width, int height) { Utils.assertTrue(width >= 0 && height >= 0); mHeight = height; GL11 gl = mGL; gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL11.GL_PROJECTION); gl.glLoadIdentity(); GLU.gluOrtho2D(gl, 0, width, 0, height); gl.glMatrixMode(GL11.GL_MODELVIEW); gl.glLoadIdentity(); float matrix[] = mMatrixValues; Matrix.setIdentityM(matrix, 0); Matrix.translateM(matrix, 0, 0, mHeight, 0); Matrix.scaleM(matrix, 0, 1, -1, 1); mClipRect.set(0, 0, width, height); gl.glScissor(0, 0, width, height); } public long currentAnimationTimeMillis() { return mAnimationTime; } public void setAlpha(float alpha) { Utils.assertTrue(alpha >= 0 && alpha <= 1); mAlpha = alpha; } public void multiplyAlpha(float alpha) { Utils.assertTrue(alpha >= 0 && alpha <= 1); mAlpha *= alpha; } public float getAlpha() { return mAlpha; } private static ByteBuffer allocateDirectNativeOrderBuffer(int size) { return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); } private void initialize() { GL11 gl = mGL; // 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(); xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0); int[] name = new int[1]; gl.glGenBuffers(1, name, 0); mBoxCoords = name[0]; gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords); gl.glBufferData(GL11.GL_ARRAY_BUFFER, xyBuffer.capacity() * (Float.SIZE / Byte.SIZE), xyBuffer, GL11.GL_STATIC_DRAW); gl.glVertexPointer(2, GL11.GL_FLOAT, 0, 0); gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); // Enable the texture coordinate array for Texture 1 gl.glClientActiveTexture(GL11.GL_TEXTURE1); gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); gl.glClientActiveTexture(GL11.GL_TEXTURE0); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); // mMatrixValues will be initialized in setSize() mAlpha = 1.0f; } public void drawRect(float x, float y, float width, float height, GLPaint paint) { GL11 gl = mGL; mGLState.setColorMode(paint.getColor(), mAlpha); mGLState.setLineWidth(paint.getLineWidth()); mGLState.setLineSmooth(paint.getAntiAlias()); saveTransform(); translate(x, y, 0); scale(width, height, 1); gl.glLoadMatrixf(mMatrixValues, 0); gl.glDrawArrays(GL11.GL_LINE_LOOP, OFFSET_DRAW_RECT, 4); restoreTransform(); mCountDrawLine++; } public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) { GL11 gl = mGL; mGLState.setColorMode(paint.getColor(), mAlpha); mGLState.setLineWidth(paint.getLineWidth()); mGLState.setLineSmooth(paint.getAntiAlias()); saveTransform(); translate(x1, y1, 0); scale(x2 - x1, y2 - y1, 1); gl.glLoadMatrixf(mMatrixValues, 0); gl.glDrawArrays(GL11.GL_LINE_STRIP, OFFSET_DRAW_LINE, 2); restoreTransform(); mCountDrawLine++; } public void fillRect(float x, float y, float width, float height, int color) { mGLState.setColorMode(color, mAlpha); GL11 gl = mGL; saveTransform(); translate(x, y, 0); scale(width, height, 1); gl.glLoadMatrixf(mMatrixValues, 0); gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4); restoreTransform(); mCountFillRect++; } public void translate(float x, float y, float z) { Matrix.translateM(mMatrixValues, 0, x, y, z); } public void scale(float sx, float sy, float sz) { Matrix.scaleM(mMatrixValues, 0, sx, sy, sz); } public void rotate(float angle, float x, float y, float z) { float[] temp = mTempMatrix; Matrix.setRotateM(temp, 0, angle, x, y, z); Matrix.multiplyMM(temp, 16, mMatrixValues, 0, temp, 0); System.arraycopy(temp, 16, mMatrixValues, 0, 16); } public void multiplyMatrix(float matrix[], int offset) { float[] temp = mTempMatrix; Matrix.multiplyMM(temp, 0, mMatrixValues, 0, matrix, offset); System.arraycopy(temp, 0, mMatrixValues, 0, 16); } private void textureRect(float x, float y, float width, float height) { GL11 gl = mGL; saveTransform(); translate(x, y, 0); scale(width, height, 1); gl.glLoadMatrixf(mMatrixValues, 0); gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4); restoreTransform(); mCountTextureRect++; } public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, int uvBuffer, int indexBuffer, int indexCount) { float alpha = mAlpha; if (!bindTexture(tex)) return; mGLState.setBlendEnabled(mBlendEnabled && (!tex.isOpaque() || alpha < OPAQUE_ALPHA)); mGLState.setTextureAlpha(alpha); // Reset the texture matrix. We will set our own texture coordinates // below. setTextureCoords(0, 0, 1, 1); saveTransform(); translate(x, y, 0); mGL.glLoadMatrixf(mMatrixValues, 0); mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, xyBuffer); mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0); mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, uvBuffer); mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); mGL.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, indexBuffer); mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP, indexCount, GL11.GL_UNSIGNED_BYTE, 0); mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords); mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0); mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); restoreTransform(); mCountDrawMesh++; } private float[] mapPoints(float matrix[], int x1, int y1, int x2, int y2) { float[] point = mMapPointsBuffer; int srcOffset = 6; point[srcOffset] = x1; point[srcOffset + 1] = y1; point[srcOffset + 2] = 0; point[srcOffset + 3] = 1; int resultOffset = 0; Matrix.multiplyMV(point, resultOffset, matrix, 0, point, srcOffset); point[resultOffset] /= point[resultOffset + 3]; point[resultOffset + 1] /= point[resultOffset + 3]; // map the second point point[srcOffset] = x2; point[srcOffset + 1] = y2; resultOffset = 2; Matrix.multiplyMV(point, resultOffset, matrix, 0, point, srcOffset); point[resultOffset] /= point[resultOffset + 3]; point[resultOffset + 1] /= point[resultOffset + 3]; return point; } public boolean clipRect(int left, int top, int right, int bottom) { float point[] = mapPoints(mMatrixValues, left, top, right, bottom); // mMatrix could be a rotation matrix. In this case, we need to find // the boundaries after rotation. (only handle 90 * n degrees) if (point[0] > point[2]) { left = (int) point[2]; right = (int) point[0]; } else { left = (int) point[0]; right = (int) point[2]; } if (point[1] > point[3]) { top = (int) point[3]; bottom = (int) point[1]; } else { top = (int) point[1]; bottom = (int) point[3]; } Rect clip = mClipRect; boolean intersect = clip.intersect(left, top, right, bottom); if (!intersect) clip.set(0, 0, 0, 0); mGL.glScissor(clip.left, clip.top, clip.width(), clip.height()); return intersect; } private void drawBoundTexture( BasicTexture texture, int x, int y, int width, int height) { // Test whether it has been rotated or flipped, if so, glDrawTexiOES // won't work if (isMatrixRotatedOrFlipped(mMatrixValues)) { if (texture.hasBorder()) { setTextureCoords( 1.0f / texture.getTextureWidth(), 1.0f / texture.getTextureHeight(), (texture.getWidth() - 1.0f) / texture.getTextureWidth(), (texture.getHeight() - 1.0f) / texture.getTextureHeight()); } else { setTextureCoords(0, 0, (float) texture.getWidth() / texture.getTextureWidth(), (float) texture.getHeight() / texture.getTextureHeight()); } textureRect(x, y, width, height); } else { // draw the rect from bottom-left to top-right float points[] = mapPoints( mMatrixValues, x, y + height, x + width, y); x = Math.round(points[0]); y = Math.round(points[1]); width = Math.round(points[2]) - x; height = Math.round(points[3]) - y; if (width > 0 && height > 0) { ((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height); mCountTextureOES++; } } } public void drawTexture( BasicTexture texture, int x, int y, int width, int height) { drawTexture(texture, x, y, width, height, mAlpha); } public void setBlendEnabled(boolean enabled) { mBlendEnabled = enabled; } public void drawTexture(BasicTexture texture, int x, int y, int width, int height, float alpha) { if (width <= 0 || height <= 0) return; mGLState.setBlendEnabled(mBlendEnabled && (!texture.isOpaque() || alpha < OPAQUE_ALPHA)); if (!bindTexture(texture)) return; mGLState.setTextureAlpha(alpha); drawBoundTexture(texture, x, y, width, height); } public void drawTexture(BasicTexture texture, RectF source, RectF target) { if (target.width() <= 0 || target.height() <= 0) return; // Copy the input to avoid changing it. mDrawTextureSourceRect.set(source); mDrawTextureTargetRect.set(target); source = mDrawTextureSourceRect; target = mDrawTextureTargetRect; mGLState.setBlendEnabled(mBlendEnabled && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA)); if (!bindTexture(texture)) return; convertCoordinate(source, target, texture); setTextureCoords(source); mGLState.setTextureAlpha(mAlpha); textureRect(target.left, target.top, target.width(), target.height()); } // 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 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; } } public void drawMixed(BasicTexture from, int toColor, float ratio, int x, int y, int w, int h) { drawMixed(from, toColor, ratio, x, y, w, h, mAlpha); } public void drawMixed(BasicTexture from, BasicTexture to, float ratio, int x, int y, int w, int h) { drawMixed(from, to, ratio, x, y, w, h, mAlpha); } private boolean bindTexture(BasicTexture texture) { if (!texture.onBind(this)) return false; mGLState.setTexture2DEnabled(true); mGL.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId()); return true; } private void setTextureColor(float r, float g, float b, float alpha) { float[] color = mTextureColor; color[0] = r; color[1] = g; color[2] = b; color[3] = alpha; } private void drawMixed(BasicTexture from, int toColor, float ratio, int x, int y, int width, int height, float alpha) { if (ratio <= 0) { drawTexture(from, x, y, width, height, alpha); return; } else if (ratio >= 1) { fillRect(x, y, width, height, toColor); return; } mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA)); final GL11 gl = mGL; if (!bindTexture(from)) return; // // The formula we want: // alpha * ((1 - ratio) * from + ratio * to) // The formula that GL supports is in the form of: // combo * (modulate * from) + (1 - combo) * to // // So, we have combo = 1 - alpha * ratio // and modulate = alpha * (1f - ratio) / combo // float comboRatio = 1 - alpha * ratio; // handle the case that (1 - comboRatio) == 0 if (alpha < OPAQUE_ALPHA) { mGLState.setTextureAlpha(alpha * (1f - ratio) / comboRatio); } else { mGLState.setTextureAlpha(1f); } // Interpolate the RGB and alpha values between both textures. mGLState.setTexEnvMode(GL11.GL_COMBINE); // Specify the interpolation factor via the alpha component of // GL_TEXTURE_ENV_COLORs. // RGB component are get from toColor and will used as SRC1 float colorAlpha = (float) (toColor >>> 24) / (0xff * 0xff); setTextureColor(((toColor >>> 16) & 0xff) * colorAlpha, ((toColor >>> 8) & 0xff) * colorAlpha, (toColor & 0xff) * colorAlpha, comboRatio); gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_RGB, GL11.GL_CONSTANT); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_ALPHA, GL11.GL_CONSTANT); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA); // Wire up the interpolation factor for RGB. gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA); // Wire up the interpolation factor for alpha. gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA); drawBoundTexture(from, x, y, width, height); mGLState.setTexEnvMode(GL11.GL_REPLACE); } private void drawMixed(BasicTexture from, BasicTexture to, float ratio, int x, int y, int width, int height, float alpha) { if (ratio <= 0) { drawTexture(from, x, y, width, height, alpha); return; } else if (ratio >= 1) { drawTexture(to, x, y, width, height, alpha); return; } // In the current implementation the two textures must have the // same size. Utils.assertTrue(from.getWidth() == to.getWidth() && from.getHeight() == to.getHeight()); mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() || !to.isOpaque() || alpha < OPAQUE_ALPHA)); final GL11 gl = mGL; if (!bindTexture(from)) return; // // The formula we want: // alpha * ((1 - ratio) * from + ratio * to) // The formula that GL supports is in the form of: // combo * (modulate * from) + (1 - combo) * to // // So, we have combo = 1 - alpha * ratio // and modulate = alpha * (1f - ratio) / combo // float comboRatio = 1 - alpha * ratio; // handle the case that (1 - comboRatio) == 0 if (alpha < OPAQUE_ALPHA) { mGLState.setTextureAlpha(alpha * (1f - ratio) / comboRatio); } else { mGLState.setTextureAlpha(1f); } gl.glActiveTexture(GL11.GL_TEXTURE1); if (!bindTexture(to)) { // Disable TEXTURE1. gl.glDisable(GL11.GL_TEXTURE_2D); // Switch back to the default texture unit. gl.glActiveTexture(GL11.GL_TEXTURE0); return; } gl.glEnable(GL11.GL_TEXTURE_2D); // Interpolate the RGB and alpha values between both textures. mGLState.setTexEnvMode(GL11.GL_COMBINE); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE); // Specify the interpolation factor via the alpha component of // GL_TEXTURE_ENV_COLORs. // We don't use the RGB color, so just give them 0s. setTextureColor(0, 0, 0, comboRatio); gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0); // Wire up the interpolation factor for RGB. gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA); // Wire up the interpolation factor for alpha. gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA); // Draw the combined texture. drawBoundTexture(to, x, y, width, height); // Disable TEXTURE1. gl.glDisable(GL11.GL_TEXTURE_2D); // Switch back to the default texture unit. gl.glActiveTexture(GL11.GL_TEXTURE0); } // TODO: the code only work for 2D should get fixed for 3D or removed private static final int MSKEW_X = 4; private static final int MSKEW_Y = 1; private static final int MSCALE_X = 0; private static final int MSCALE_Y = 5; private static boolean isMatrixRotatedOrFlipped(float matrix[]) { final float eps = 1e-5f; return Math.abs(matrix[MSKEW_X]) > eps || Math.abs(matrix[MSKEW_Y]) > eps || matrix[MSCALE_X] < -eps || matrix[MSCALE_Y] > eps; } public BasicTexture copyTexture(int x, int y, int width, int height) { if (isMatrixRotatedOrFlipped(mMatrixValues)) { throw new IllegalArgumentException("cannot support rotated matrix"); } float points[] = mapPoints(mMatrixValues, x, y + height, x + width, y); x = (int) points[0]; y = (int) points[1]; width = (int) points[2] - x; height = (int) points[3] - y; GL11 gl = mGL; RawTexture texture = RawTexture.newInstance(this); gl.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId()); texture.setSize(width, height); int[] cropRect = {0, 0, width, height}; gl.glTexParameteriv(GL11.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, 0); gl.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); gl.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, x, y, texture.getTextureWidth(), texture.getTextureHeight(), 0); return texture; } private static class GLState { private final GL11 mGL; private int mTexEnvMode = GL11.GL_REPLACE; private float mTextureAlpha = 1.0f; private boolean mTexture2DEnabled = true; private boolean mBlendEnabled = true; private float mLineWidth = 1.0f; private boolean mLineSmooth = false; public GLState(GL11 gl) { mGL = gl; // Disable unused state gl.glDisable(GL11.GL_LIGHTING); // Enable used features gl.glEnable(GL11.GL_DITHER); gl.glEnable(GL11.GL_SCISSOR_TEST); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glEnable(GL11.GL_TEXTURE_2D); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE); // Set the background color gl.glClearColor(0f, 0f, 0f, 0f); gl.glClearStencil(0); gl.glEnable(GL11.GL_BLEND); gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); // We use 565 or 8888 format, so set the alignment to 2 bytes/pixel. gl.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2); } public void setTexEnvMode(int mode) { if (mTexEnvMode == mode) return; mTexEnvMode = mode; mGL.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, mode); } public void setLineWidth(float width) { if (mLineWidth == width) return; mLineWidth = width; mGL.glLineWidth(width); } public void setLineSmooth(boolean enabled) { if (mLineSmooth == enabled) return; mLineSmooth = enabled; if (enabled) { mGL.glEnable(GL11.GL_LINE_SMOOTH); } else { mGL.glDisable(GL11.GL_LINE_SMOOTH); } } public void setTextureAlpha(float alpha) { if (mTextureAlpha == alpha) return; mTextureAlpha = alpha; if (alpha >= OPAQUE_ALPHA) { // The alpha is need for those texture without alpha channel mGL.glColor4f(1, 1, 1, 1); setTexEnvMode(GL11.GL_REPLACE); } else { mGL.glColor4f(alpha, alpha, alpha, alpha); setTexEnvMode(GL11.GL_MODULATE); } } public void setColorMode(int color, float alpha) { setBlendEnabled(!Utils.isOpaque(color) || alpha < OPAQUE_ALPHA); // Set mTextureAlpha to an invalid value, so that it will reset // again in setTextureAlpha(float) later. mTextureAlpha = -1.0f; setTexture2DEnabled(false); float prealpha = (color >>> 24) * alpha * 65535f / 255f / 255f; mGL.glColor4x( Math.round(((color >> 16) & 0xFF) * prealpha), Math.round(((color >> 8) & 0xFF) * prealpha), Math.round((color & 0xFF) * prealpha), Math.round(255 * prealpha)); } public void setTexture2DEnabled(boolean enabled) { if (mTexture2DEnabled == enabled) return; mTexture2DEnabled = enabled; if (enabled) { mGL.glEnable(GL11.GL_TEXTURE_2D); } else { mGL.glDisable(GL11.GL_TEXTURE_2D); } } public void setBlendEnabled(boolean enabled) { if (mBlendEnabled == enabled) return; mBlendEnabled = enabled; if (enabled) { mGL.glEnable(GL11.GL_BLEND); } else { mGL.glDisable(GL11.GL_BLEND); } } } public GL11 getGLInstance() { return mGL; } public void setCurrentAnimationTimeMillis(long time) { Utils.assertTrue(time >= 0); mAnimationTime = time; } public void clearBuffer() { mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); } private void setTextureCoords(RectF source) { setTextureCoords(source.left, source.top, source.right, source.bottom); } private void setTextureCoords(float left, float top, float right, float bottom) { mGL.glMatrixMode(GL11.GL_TEXTURE); mTextureMatrixValues[0] = right - left; mTextureMatrixValues[5] = bottom - top; mTextureMatrixValues[10] = 1; mTextureMatrixValues[12] = left; mTextureMatrixValues[13] = top; mTextureMatrixValues[15] = 1; mGL.glLoadMatrixf(mTextureMatrixValues, 0); mGL.glMatrixMode(GL11.GL_MODELVIEW); } // unloadTexture and deleteBuffer can be called from the finalizer thread, // so we synchronized on the mUnboundTextures object. public boolean unloadTexture(BasicTexture t) { synchronized (mUnboundTextures) { if (!t.isLoaded(this)) return false; mUnboundTextures.add(t.mId); return true; } } public void deleteBuffer(int bufferId) { synchronized (mUnboundTextures) { mDeleteBuffers.add(bufferId); } } public void deleteRecycledResources() { synchronized (mUnboundTextures) { IntArray ids = mUnboundTextures; if (ids.size() > 0) { mGL.glDeleteTextures(ids.size(), ids.getInternalArray(), 0); ids.clear(); } ids = mDeleteBuffers; if (ids.size() > 0) { mGL.glDeleteBuffers(ids.size(), ids.getInternalArray(), 0); ids.clear(); } } } public int save() { return save(SAVE_FLAG_ALL); } public int save(int saveFlags) { ConfigState config = obtainRestoreConfig(); if ((saveFlags & SAVE_FLAG_ALPHA) != 0) { config.mAlpha = mAlpha; } else { config.mAlpha = -1; } if ((saveFlags & SAVE_FLAG_CLIP) != 0) { config.mRect.set(mClipRect); } else { config.mRect.left = Integer.MAX_VALUE; } if ((saveFlags & SAVE_FLAG_MATRIX) != 0) { System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16); } else { config.mMatrix[0] = Float.NEGATIVE_INFINITY; } mRestoreStack.push(config); return mRestoreStack.size() - 1; } public void restore() { if (mRestoreStack.isEmpty()) throw new IllegalStateException(); ConfigState config = mRestoreStack.pop(); config.restore(this); freeRestoreConfig(config); } private void freeRestoreConfig(ConfigState action) { action.mNextFree = mRecycledRestoreAction; mRecycledRestoreAction = action; } private ConfigState obtainRestoreConfig() { if (mRecycledRestoreAction != null) { ConfigState result = mRecycledRestoreAction; mRecycledRestoreAction = result.mNextFree; return result; } return new ConfigState(); } private static class ConfigState { float mAlpha; Rect mRect = new Rect(); float mMatrix[] = new float[16]; ConfigState mNextFree; public void restore(GLCanvasImpl canvas) { if (mAlpha >= 0) canvas.setAlpha(mAlpha); if (mRect.left != Integer.MAX_VALUE) { Rect rect = mRect; canvas.mClipRect.set(rect); canvas.mGL.glScissor( rect.left, rect.top, rect.width(), rect.height()); } if (mMatrix[0] != Float.NEGATIVE_INFINITY) { System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16); } } } public void dumpStatisticsAndClear() { String line = String.format( "MESH:%d, TEX_OES:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh, mCountTextureRect, mCountTextureOES, mCountFillRect, mCountDrawLine); mCountDrawMesh = 0; mCountTextureRect = 0; mCountTextureOES = 0; mCountFillRect = 0; mCountDrawLine = 0; Log.d(TAG, line); } private void saveTransform() { System.arraycopy(mMatrixValues, 0, mTempMatrix, 0, 16); } private void restoreTransform() { System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16); } }