blob: abb2325bd943145174534d586f4095550f9a13d9 [file] [log] [blame]
/*
* 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 android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Stack;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;
// The root component of all <code>GLView</code>s. The rendering is done in GL
// thread while the event handling is done in the main thread. To synchronize
// the two threads, the entry points of this package need to synchronize on the
// <code>GLRootView</code> instance unless it can be proved that the rendering
// thread won't access the same thing as the method. The entry points include:
// (1) The public methods of HeadUpDisplay
// (2) The public methods of CameraHeadUpDisplay
// (3) The overridden methods in GLRootView.
public class GLRootView extends GLSurfaceView
implements GLSurfaceView.Renderer {
private static final String TAG = "GLRootView";
private final boolean ENABLE_FPS_TEST = false;
private int mFrameCount = 0;
private long mFrameCountingStart = 0;
// We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
private static final int VERTEX_BUFFER_SIZE = 16 * 2;
// We need 22 indices for a normal nine-patch image
private static final int INDEX_BUFFER_SIZE = 22;
private static final int FLAG_INITIALIZED = 1;
private static final int FLAG_NEED_LAYOUT = 2;
private static boolean mTexture2DEnabled;
private GL11 mGL;
private GLView mContentView;
private DisplayMetrics mDisplayMetrics;
private final ArrayList<Animation> mAnimations = new ArrayList<Animation>();
private final Stack<Transformation> mFreeTransform =
new Stack<Transformation>();
private final Transformation mTransformation = new Transformation();
private final Stack<Transformation> mTransformStack =
new Stack<Transformation>();
private float mLastAlpha = mTransformation.getAlpha();
private final float mMatrixValues[] = new float[16];
private final float mUvBuffer[] = new float[VERTEX_BUFFER_SIZE];
private final float mXyBuffer[] = new float[VERTEX_BUFFER_SIZE];
private final byte mIndexBuffer[] = new byte[INDEX_BUFFER_SIZE];
private final int mNinePatchX[] = new int[4];
private final int mNinePatchY[] = new int[4];
private final float mNinePatchU[] = new float[4];
private final float mNinePatchV[] = new float[4];
private FloatBuffer mXyPointer;
private FloatBuffer mUvPointer;
private ByteBuffer mIndexPointer;
private int mFlags = FLAG_NEED_LAYOUT;
private long mAnimationTime;
private final CameraEGLConfigChooser mEglConfigChooser = new CameraEGLConfigChooser();
public GLRootView(Context context) {
this(context, null);
}
public GLRootView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
void registerLaunchedAnimation(Animation animation) {
// Register the newly launched animation so that we can set the start
// time more precisely. (Usually, it takes much longer for the first
// rendering, so we set the animation start time as the time we
// complete rendering)
mAnimations.add(animation);
}
public long currentAnimationTimeMillis() {
return mAnimationTime;
}
public Transformation obtainTransformation() {
if (!mFreeTransform.isEmpty()) {
Transformation t = mFreeTransform.pop();
t.clear();
return t;
}
return new Transformation();
}
public void freeTransformation(Transformation freeTransformation) {
mFreeTransform.push(freeTransformation);
}
public Transformation getTransformation() {
return mTransformation;
}
public Transformation pushTransform() {
Transformation trans = obtainTransformation();
trans.set(mTransformation);
mTransformStack.push(trans);
return mTransformation;
}
public void popTransform() {
Transformation trans = mTransformStack.pop();
mTransformation.set(trans);
freeTransformation(trans);
}
public CameraEGLConfigChooser getEGLConfigChooser() {
return mEglConfigChooser;
}
private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
}
private void initialize() {
mFlags |= FLAG_INITIALIZED;
setEGLConfigChooser(mEglConfigChooser);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);
setRenderer(this);
int size = VERTEX_BUFFER_SIZE * Float.SIZE / Byte.SIZE;
mXyPointer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
mUvPointer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
mIndexPointer = allocateDirectNativeOrderBuffer(INDEX_BUFFER_SIZE);
}
public void setContentPane(GLView content) {
mContentView = content;
content.onAttachToRoot(this);
// no parent for the content pane
content.onAddToParent(null);
requestLayoutContentPane();
}
public GLView getContentPane() {
return mContentView;
}
void handleLowMemory() {
//TODO: delete texture from GL
}
public synchronized void requestLayoutContentPane() {
if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
// "View" system will invoke onLayout() for initialization(bug ?), we
// have to ignore it since the GLThread is not ready yet.
if ((mFlags & FLAG_INITIALIZED) == 0) return;
mFlags |= FLAG_NEED_LAYOUT;
requestRender();
}
private synchronized void layoutContentPane() {
mFlags &= ~FLAG_NEED_LAYOUT;
int width = getWidth();
int height = getHeight();
Log.v(TAG, "layout content pane " + width + "x" + height);
if (mContentView != null && width != 0 && height != 0) {
mContentView.layout(0, 0, width, height);
}
}
@Override
protected void onLayout(
boolean changed, int left, int top, int right, int bottom) {
if (changed) requestLayoutContentPane();
}
/**
* Called when the context is created, possibly after automatic destruction.
*/
// This is a GLSurfaceView.Renderer callback
public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
GL11 gl = (GL11) gl1;
if (mGL != null) {
// The GL Object has changed
Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
}
mGL = gl;
if (!ENABLE_FPS_TEST) {
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
} else {
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
// Disable unused state
gl.glDisable(GL11.GL_LIGHTING);
// Enable used features
gl.glEnable(GL11.GL_BLEND);
gl.glEnable(GL11.GL_SCISSOR_TEST);
gl.glEnable(GL11.GL_STENCIL_TEST);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glEnable(GL11.GL_TEXTURE_2D);
mTexture2DEnabled = true;
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.glVertexPointer(2, GL11.GL_FLOAT, 0, mXyPointer);
gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, mUvPointer);
}
/**
* Called when the OpenGL surface is recreated without destroying the
* context.
*/
// This is a GLSurfaceView.Renderer callback
public void onSurfaceChanged(GL10 gl1, int width, int height) {
Log.v(TAG, "onSurfaceChanged: " + width + "x" + height
+ ", gl10: " + gl1.toString());
GL11 gl = (GL11) gl1;
mGL = gl;
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL11.GL_PROJECTION);
gl.glLoadIdentity();
GLU.gluOrtho2D(gl, 0, width, 0, height);
Matrix matrix = mTransformation.getMatrix();
matrix.reset();
matrix.preTranslate(0, getHeight());
matrix.preScale(1, -1);
}
private void setAlphaValue(float alpha) {
if (mLastAlpha == alpha) return;
GL11 gl = mGL;
mLastAlpha = alpha;
if (alpha >= 0.95f) {
gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
} else {
gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
gl.glColor4f(alpha, alpha, alpha, alpha);
}
}
public void drawRect(int x, int y, int width, int height) {
float matrix[] = mMatrixValues;
mTransformation.getMatrix().getValues(matrix);
drawRect(x, y, width, height, matrix);
}
private static void putRectangle(float x, float y,
float width, float height, float[] buffer, FloatBuffer pointer) {
buffer[0] = x;
buffer[1] = y;
buffer[2] = x + width;
buffer[3] = y;
buffer[4] = x;
buffer[5] = y + height;
buffer[6] = x + width;
buffer[7] = y + height;
pointer.put(buffer, 0, 8).position(0);
}
private void drawRect(
int x, int y, int width, int height, float matrix[]) {
GL11 gl = mGL;
gl.glPushMatrix();
gl.glMultMatrixf(toGLMatrix(matrix), 0);
putRectangle(x, y, width, height, mXyBuffer, mXyPointer);
gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 4);
gl.glPopMatrix();
}
public void drawNinePatch(
NinePatchTexture tex, int x, int y, int width, int height) {
NinePatchChunk chunk = tex.getNinePatchChunk();
// The code should be easily extended to handle the general cases by
// allocating more space for buffers. But let's just handle the only
// use case.
if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
throw new RuntimeException("unsupported nine patch");
}
if (!tex.bind(this, mGL)) {
throw new RuntimeException("cannot bind" + tex.toString());
}
if (width <= 0 || height <= 0) return ;
int divX[] = mNinePatchX;
int divY[] = mNinePatchY;
float divU[] = mNinePatchU;
float divV[] = mNinePatchV;
int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
setAlphaValue(mTransformation.getAlpha());
Matrix matrix = mTransformation.getMatrix();
matrix.getValues(mMatrixValues);
GL11 gl = mGL;
gl.glPushMatrix();
gl.glMultMatrixf(toGLMatrix(mMatrixValues), 0);
gl.glTranslatef(x, y, 0);
drawMesh(divX, divY, divU, divV, nx, ny);
gl.glPopMatrix();
}
/**
* Stretches the texture according to the nine-patch rules. It will
* linearly distribute the strechy parts defined in the nine-patch chunk to
* the target area.
*
* <pre>
* source
* /--------------^---------------\
* u0 u1 u2 u3 u4 u5
* div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
* | div0 div1 div2 div3 |
* | | / / / /
* | | / / / /
* | | / / / /
* |fffff|ssss|fff|sss|ffff| ---> x
* x0 x1 x2 x3 x4 x5
* \----------v------------/
* target
*
* f: fixed segment
* s: stretchy segment
* </pre>
*
* @param div the stretch parts defined in nine-patch chunk
* @param source the length of the texture
* @param target the length on the drawing plan
* @param u output, the positions of these dividers in the texture
* coordinate
* @param x output, the corresponding position of these dividers on the
* drawing plan
* @return the number of these dividers.
*/
private int stretch(
int x[], float u[], int div[], int source, int target) {
int textureSize = Util.nextPowerOf2(source);
float textureBound = (source - 0.5f) / textureSize;
int stretch = 0;
for (int i = 0, n = div.length; i < n; i += 2) {
stretch += div[i + 1] - div[i];
}
float remaining = target - source + stretch;
int lastX = 0;
int lastU = 0;
x[0] = 0;
u[0] = 0;
for (int i = 0, n = div.length; i < n; i += 2) {
// fixed segment
x[i + 1] = lastX + (div[i] - lastU);
u[i + 1] = Math.min((float) div[i] / textureSize, textureBound);
// stretchy segment
float partU = div[i + 1] - div[i];
int partX = (int)(remaining * partU / stretch + 0.5f);
remaining -= partX;
stretch -= partU;
lastX = x[i + 1] + partX;
lastU = div[i + 1];
x[i + 2] = lastX;
u[i + 2] = Math.min((float) lastU / textureSize, textureBound);
}
// the last fixed segment
x[div.length + 1] = target;
u[div.length + 1] = textureBound;
// remove segments with length 0.
int last = 0;
for (int i = 1, n = div.length + 2; i < n; ++i) {
if (x[last] == x[i]) continue;
x[++last] = x[i];
u[last] = u[i];
}
return last + 1;
}
private void drawMesh(
int x[], int y[], float u[], float v[], int nx, int ny) {
/*
* Given a 3x3 nine-patch image, the vertex order is defined as the
* following graph:
*
* (0) (1) (2) (3)
* | /| /| /|
* | / | / | / |
* (4) (5) (6) (7)
* | \ | \ | \ |
* | \| \| \|
* (8) (9) (A) (B)
* | /| /| /|
* | / | / | / |
* (C) (D) (E) (F)
*
* And we draw the triangle strip in the following index order:
*
* index: 04152637B6A5948C9DAEBF
*/
int pntCount = 0;
float xy[] = mXyBuffer;
float uv[] = mUvBuffer;
for (int j = 0; j < ny; ++j) {
for (int i = 0; i < nx; ++i) {
int xIndex = (pntCount++) << 1;
int yIndex = xIndex + 1;
xy[xIndex] = x[i];
xy[yIndex] = y[j];
uv[xIndex] = u[i];
uv[yIndex] = v[j];
}
}
mUvPointer.put(uv, 0, pntCount << 1).position(0);
mXyPointer.put(xy, 0, pntCount << 1).position(0);
int idxCount = 1;
byte index[] = mIndexBuffer;
for (int i = 0, bound = nx * (ny - 1); true;) {
// normal direction
--idxCount;
for (int j = 0; j < nx; ++j, ++i) {
index[idxCount++] = (byte) i;
index[idxCount++] = (byte) (i + nx);
}
if (i >= bound) break;
// reverse direction
int sum = i + i + nx - 1;
--idxCount;
for (int j = 0; j < nx; ++j, ++i) {
index[idxCount++] = (byte) (sum - i);
index[idxCount++] = (byte) (sum - i + nx);
}
if (i >= bound) break;
}
mIndexPointer.put(index, 0, idxCount).position(0);
mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP,
idxCount, GL11.GL_UNSIGNED_BYTE, mIndexPointer);
}
private float[] mapPoints(Matrix matrix, int x1, int y1, int x2, int y2) {
float[] point = mXyBuffer;
point[0] = x1; point[1] = y1; point[2] = x2; point[3] = y2;
matrix.mapPoints(point, 0, point, 0, 4);
return point;
}
public void clipRect(int x, int y, int width, int height) {
float point[] = mapPoints(
mTransformation.getMatrix(), x, y + height, x + width, y);
// 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]) {
x = (int) point[2];
width = (int) point[0] - x;
} else {
x = (int) point[0];
width = (int) point[2] - x;
}
if (point[1] > point[3]) {
y = (int) point[3];
height = (int) point[1] - y;
} else {
y = (int) point[1];
height = (int) point[3] - y;
}
mGL.glScissor(x, y, width, height);
}
public void clearClip() {
mGL.glScissor(0, 0, getWidth(), getHeight());
}
private static float[] toGLMatrix(float v[]) {
v[15] = v[8]; v[13] = v[5]; v[5] = v[4]; v[4] = v[1];
v[12] = v[2]; v[1] = v[3]; v[3] = v[6];
v[2] = v[6] = v[8] = v[9] = 0;
v[10] = 1;
return v;
}
public void drawColor(int x, int y, int width, int height, int color) {
float alpha = mTransformation.getAlpha();
GL11 gl = mGL;
if (mTexture2DEnabled) {
// Set mLastAlpha to an invalid value, so that it will reset again
// in setAlphaValue(float) later.
mLastAlpha = -1.0f;
gl.glDisable(GL11.GL_TEXTURE_2D);
mTexture2DEnabled = false;
}
alpha /= 256.0f;
gl.glColor4f(Color.red(color) * alpha, Color.green(color) * alpha,
Color.blue(color) * alpha, Color.alpha(color) * alpha);
drawRect(x, y, width, height);
}
public void drawTexture(
BasicTexture texture, int x, int y, int width, int height) {
drawTexture(texture, x, y, width, height, mTransformation.getAlpha());
}
public void drawTexture(BasicTexture texture,
int x, int y, int width, int height, float alpha) {
if (!mTexture2DEnabled) {
mGL.glEnable(GL11.GL_TEXTURE_2D);
mTexture2DEnabled = true;
}
if (!texture.bind(this, mGL)) {
throw new RuntimeException("cannot bind" + texture.toString());
}
if (width <= 0 || height <= 0) return ;
Matrix matrix = mTransformation.getMatrix();
matrix.getValues(mMatrixValues);
// Test whether it has been rotated or flipped, if so, glDrawTexiOES
// won't work
if (isMatrixRotatedOrFlipped(mMatrixValues)) {
putRectangle(0, 0,
(texture.mWidth - 0.5f) / texture.mTextureWidth,
(texture.mHeight - 0.5f) / texture.mTextureHeight,
mUvBuffer, mUvPointer);
setAlphaValue(alpha);
drawRect(x, y, width, height, mMatrixValues);
} else {
// draw the rect from bottom-left to top-right
float points[] = mapPoints(matrix, x, y + height, x + width, y);
x = (int) points[0];
y = (int) points[1];
width = (int) points[2] - x;
height = (int) points[3] - y;
if (width > 0 && height > 0) {
setAlphaValue(alpha);
((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height);
}
}
}
private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
return matrix[Matrix.MSKEW_X] != 0 || matrix[Matrix.MSKEW_Y] != 0
|| matrix[Matrix.MSCALE_X] < 0 || matrix[Matrix.MSCALE_Y] > 0;
}
public synchronized void onDrawFrame(GL10 gl) {
if (ENABLE_FPS_TEST) {
long now = System.nanoTime();
if (mFrameCountingStart == 0) {
mFrameCountingStart = now;
} else if ((now - mFrameCountingStart) > 1000000000) {
Log.v(TAG, "fps: " + (double) mFrameCount
* 1000000000 / (now - mFrameCountingStart));
mFrameCountingStart = now;
mFrameCount = 0;
}
++mFrameCount;
}
if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
clearClip();
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_STENCIL_BUFFER_BIT);
gl.glEnable(GL11.GL_BLEND);
gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
mAnimationTime = SystemClock.uptimeMillis();
if (mContentView != null) {
mContentView.render(GLRootView.this, (GL11) gl);
}
long now = SystemClock.uptimeMillis();
for (Animation animation : mAnimations) {
animation.setStartTime(now);
}
mAnimations.clear();
}
@Override
public synchronized boolean dispatchTouchEvent(MotionEvent event) {
// If this has been detached from root, we don't need to handle event
return mContentView != null
? mContentView.dispatchTouchEvent(event)
: false;
}
public DisplayMetrics getDisplayMetrics() {
if (mDisplayMetrics == null) {
mDisplayMetrics = new DisplayMetrics();
((Activity) getContext()).getWindowManager()
.getDefaultDisplay().getMetrics(mDisplayMetrics);
}
return mDisplayMetrics;
}
public void copyTexture2D(
RawTexture texture, int x, int y, int width, int height)
throws GLOutOfMemoryException {
Matrix matrix = mTransformation.getMatrix();
matrix.getValues(mMatrixValues);
if (isMatrixRotatedOrFlipped(mMatrixValues)) {
throw new IllegalArgumentException("cannot support rotated matrix");
}
float points[] = mapPoints(matrix, 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;
int newWidth = Util.nextPowerOf2(width);
int newHeight = Util.nextPowerOf2(height);
int glError = GL11.GL_NO_ERROR;
gl.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId());
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_RGBA, x, y, newWidth, newHeight, 0);
glError = gl.glGetError();
if (glError == GL11.GL_OUT_OF_MEMORY) {
throw new GLOutOfMemoryException();
}
if (glError != GL11.GL_NO_ERROR) {
throw new RuntimeException(
"Texture copy fail, glError " + glError);
}
texture.setSize(width, height);
texture.setTextureSize(newWidth, newHeight);
}
}