summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/glrenderer
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/glrenderer')
-rw-r--r--src/com/android/gallery3d/glrenderer/BasicTexture.java212
-rw-r--r--src/com/android/gallery3d/glrenderer/BitmapTexture.java54
-rw-r--r--src/com/android/gallery3d/glrenderer/CanvasTexture.java52
-rw-r--r--src/com/android/gallery3d/glrenderer/ColorTexture.java63
-rw-r--r--src/com/android/gallery3d/glrenderer/ExtTexture.java60
-rw-r--r--src/com/android/gallery3d/glrenderer/FadeInTexture.java43
-rw-r--r--src/com/android/gallery3d/glrenderer/FadeOutTexture.java42
-rw-r--r--src/com/android/gallery3d/glrenderer/FadeTexture.java81
-rw-r--r--src/com/android/gallery3d/glrenderer/GLCanvas.java217
-rw-r--r--src/com/android/gallery3d/glrenderer/GLES11Canvas.java997
-rw-r--r--src/com/android/gallery3d/glrenderer/GLES11IdImpl.java68
-rw-r--r--src/com/android/gallery3d/glrenderer/GLES20Canvas.java1009
-rw-r--r--src/com/android/gallery3d/glrenderer/GLES20IdImpl.java42
-rw-r--r--src/com/android/gallery3d/glrenderer/GLId.java33
-rw-r--r--src/com/android/gallery3d/glrenderer/GLPaint.java41
-rw-r--r--src/com/android/gallery3d/glrenderer/MultiLineTexture.java52
-rw-r--r--src/com/android/gallery3d/glrenderer/NinePatchChunk.java82
-rw-r--r--src/com/android/gallery3d/glrenderer/NinePatchTexture.java424
-rw-r--r--src/com/android/gallery3d/glrenderer/RawTexture.java73
-rw-r--r--src/com/android/gallery3d/glrenderer/ResourceTexture.java53
-rw-r--r--src/com/android/gallery3d/glrenderer/StringTexture.java88
-rw-r--r--src/com/android/gallery3d/glrenderer/Texture.java44
-rw-r--r--src/com/android/gallery3d/glrenderer/TextureUploader.java105
-rw-r--r--src/com/android/gallery3d/glrenderer/TiledTexture.java349
-rw-r--r--src/com/android/gallery3d/glrenderer/UploadedTexture.java298
25 files changed, 4582 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/glrenderer/BasicTexture.java b/src/com/android/gallery3d/glrenderer/BasicTexture.java
new file mode 100644
index 000000000..2e77b903f
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BasicTexture.java
@@ -0,0 +1,212 @@
+/*
+ * 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.glrenderer;
+
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+
+import java.util.WeakHashMap;
+
+// BasicTexture is a Texture corresponds to a real GL texture.
+// The state of a BasicTexture indicates whether its data is loaded to GL memory.
+// If a BasicTexture is loaded into GL memory, it has a GL texture id.
+public abstract class BasicTexture implements Texture {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "BasicTexture";
+ protected static final int UNSPECIFIED = -1;
+
+ protected static final int STATE_UNLOADED = 0;
+ protected static final int STATE_LOADED = 1;
+ protected static final int STATE_ERROR = -1;
+
+ // Log a warning if a texture is larger along a dimension
+ private static final int MAX_TEXTURE_SIZE = 4096;
+
+ protected int mId = -1;
+ protected int mState;
+
+ protected int mWidth = UNSPECIFIED;
+ protected int mHeight = UNSPECIFIED;
+
+ protected int mTextureWidth;
+ protected int mTextureHeight;
+
+ private boolean mHasBorder;
+
+ protected GLCanvas mCanvasRef = null;
+ private static WeakHashMap<BasicTexture, Object> sAllTextures
+ = new WeakHashMap<BasicTexture, Object>();
+ private static ThreadLocal sInFinalizer = new ThreadLocal();
+
+ protected BasicTexture(GLCanvas canvas, int id, int state) {
+ setAssociatedCanvas(canvas);
+ mId = id;
+ mState = state;
+ synchronized (sAllTextures) {
+ sAllTextures.put(this, null);
+ }
+ }
+
+ protected BasicTexture() {
+ this(null, 0, STATE_UNLOADED);
+ }
+
+ protected void setAssociatedCanvas(GLCanvas canvas) {
+ mCanvasRef = canvas;
+ }
+
+ /**
+ * Sets the content size of this texture. In OpenGL, the actual texture
+ * size must be of power of 2, the size of the content may be smaller.
+ */
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ mTextureWidth = width > 0 ? Utils.nextPowerOf2(width) : 0;
+ mTextureHeight = height > 0 ? Utils.nextPowerOf2(height) : 0;
+ if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
+ Log.w(TAG, String.format("texture is too large: %d x %d",
+ mTextureWidth, mTextureHeight), new Exception());
+ }
+ }
+
+ public boolean isFlippedVertically() {
+ return false;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ // Returns the width rounded to the next power of 2.
+ public int getTextureWidth() {
+ return mTextureWidth;
+ }
+
+ // Returns the height rounded to the next power of 2.
+ public int getTextureHeight() {
+ return mTextureHeight;
+ }
+
+ // Returns true if the texture has one pixel transparent border around the
+ // actual content. This is used to avoid jigged edges.
+ //
+ // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
+ // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
+ // covered by the texture will use the color of the edge texel. If we add
+ // the transparent border, the color of the edge texel will be mixed with
+ // appropriate amount of transparent.
+ //
+ // Currently our background is black, so we can draw the thumbnails without
+ // enabling blending.
+ public boolean hasBorder() {
+ return mHasBorder;
+ }
+
+ protected void setBorder(boolean hasBorder) {
+ mHasBorder = hasBorder;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y) {
+ canvas.drawTexture(this, x, y, getWidth(), getHeight());
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+ canvas.drawTexture(this, x, y, w, h);
+ }
+
+ // onBind is called before GLCanvas binds this texture.
+ // It should make sure the data is uploaded to GL memory.
+ abstract protected boolean onBind(GLCanvas canvas);
+
+ // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
+ abstract protected int getTarget();
+
+ public boolean isLoaded() {
+ return mState == STATE_LOADED;
+ }
+
+ // recycle() is called when the texture will never be used again,
+ // so it can free all resources.
+ public void recycle() {
+ freeResource();
+ }
+
+ // yield() is called when the texture will not be used temporarily,
+ // so it can free some resources.
+ // The default implementation unloads the texture from GL memory, so
+ // the subclass should make sure it can reload the texture to GL memory
+ // later, or it will have to override this method.
+ public void yield() {
+ freeResource();
+ }
+
+ private void freeResource() {
+ GLCanvas canvas = mCanvasRef;
+ if (canvas != null && mId != -1) {
+ canvas.unloadTexture(this);
+ mId = -1; // Don't free it again.
+ }
+ mState = STATE_UNLOADED;
+ setAssociatedCanvas(null);
+ }
+
+ @Override
+ protected void finalize() {
+ sInFinalizer.set(BasicTexture.class);
+ recycle();
+ sInFinalizer.set(null);
+ }
+
+ // This is for deciding if we can call Bitmap's recycle().
+ // We cannot call Bitmap's recycle() in finalizer because at that point
+ // the finalizer of Bitmap may already be called so recycle() will crash.
+ public static boolean inFinalizer() {
+ return sInFinalizer.get() != null;
+ }
+
+ public static void yieldAllTextures() {
+ synchronized (sAllTextures) {
+ for (BasicTexture t : sAllTextures.keySet()) {
+ t.yield();
+ }
+ }
+ }
+
+ public static void invalidateAllTextures() {
+ synchronized (sAllTextures) {
+ for (BasicTexture t : sAllTextures.keySet()) {
+ t.mState = STATE_UNLOADED;
+ t.setAssociatedCanvas(null);
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/BitmapTexture.java b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
new file mode 100644
index 000000000..100b0b3b9
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
@@ -0,0 +1,54 @@
+/*
+ * 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.glrenderer;
+
+import android.graphics.Bitmap;
+
+import junit.framework.Assert;
+
+// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
+//
+// The texture does not own the Bitmap. The user should make sure the Bitmap
+// is valid during the texture's lifetime. When the texture is recycled, it
+// does not free the Bitmap.
+public class BitmapTexture extends UploadedTexture {
+ protected Bitmap mContentBitmap;
+
+ public BitmapTexture(Bitmap bitmap) {
+ this(bitmap, false);
+ }
+
+ public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
+ super(hasBorder);
+ Assert.assertTrue(bitmap != null && !bitmap.isRecycled());
+ mContentBitmap = bitmap;
+ }
+
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ // Do nothing.
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ return mContentBitmap;
+ }
+
+ public Bitmap getBitmap() {
+ return mContentBitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/CanvasTexture.java b/src/com/android/gallery3d/glrenderer/CanvasTexture.java
new file mode 100644
index 000000000..bff9d4baa
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/CanvasTexture.java
@@ -0,0 +1,52 @@
+/*
+ * 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.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+
+// CanvasTexture is a texture whose content is the drawing on a Canvas.
+// The subclasses should override onDraw() to draw on the bitmap.
+// By default CanvasTexture is not opaque.
+abstract class CanvasTexture extends UploadedTexture {
+ protected Canvas mCanvas;
+ private final Config mConfig;
+
+ public CanvasTexture(int width, int height) {
+ mConfig = Config.ARGB_8888;
+ setSize(width, height);
+ setOpaque(false);
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig);
+ mCanvas = new Canvas(bitmap);
+ onDraw(mCanvas, bitmap);
+ return bitmap;
+ }
+
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ if (!inFinalizer()) {
+ bitmap.recycle();
+ }
+ }
+
+ abstract protected void onDraw(Canvas canvas, Bitmap backing);
+}
diff --git a/src/com/android/gallery3d/glrenderer/ColorTexture.java b/src/com/android/gallery3d/glrenderer/ColorTexture.java
new file mode 100644
index 000000000..904c78e1b
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/ColorTexture.java
@@ -0,0 +1,63 @@
+/*
+ * 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.glrenderer;
+
+import com.android.gallery3d.common.Utils;
+
+// ColorTexture is a texture which fills the rectangle with the specified color.
+public class ColorTexture implements Texture {
+
+ private final int mColor;
+ private int mWidth;
+ private int mHeight;
+
+ public ColorTexture(int color) {
+ mColor = color;
+ mWidth = 1;
+ mHeight = 1;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y) {
+ draw(canvas, x, y, mWidth, mHeight);
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+ canvas.fillRect(x, y, w, h, mColor);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return Utils.isOpaque(mColor);
+ }
+
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/ExtTexture.java b/src/com/android/gallery3d/glrenderer/ExtTexture.java
new file mode 100644
index 000000000..af76300b1
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/ExtTexture.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+// ExtTexture is a texture whose content comes from a external texture.
+// Before drawing, setSize() should be called.
+public class ExtTexture extends BasicTexture {
+
+ private int mTarget;
+
+ public ExtTexture(GLCanvas canvas, int target) {
+ GLId glId = canvas.getGLId();
+ mId = glId.generateTexture();
+ mTarget = target;
+ }
+
+ private void uploadToCanvas(GLCanvas canvas) {
+ canvas.setTextureParameters(this);
+ setAssociatedCanvas(canvas);
+ mState = STATE_LOADED;
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ if (!isLoaded()) {
+ uploadToCanvas(canvas);
+ }
+
+ return true;
+ }
+
+ @Override
+ public int getTarget() {
+ return mTarget;
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return true;
+ }
+
+ @Override
+ public void yield() {
+ // we cannot free the texture because we have no backup.
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/FadeInTexture.java b/src/com/android/gallery3d/glrenderer/FadeInTexture.java
new file mode 100644
index 000000000..838d465f5
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/FadeInTexture.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 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;
+
+
+// FadeInTexture is a texture which begins with a color, then gradually animates
+// into a given texture.
+public class FadeInTexture extends FadeTexture implements Texture {
+ @SuppressWarnings("unused")
+ private static final String TAG = "FadeInTexture";
+
+ private final int mColor;
+ private final TiledTexture mTexture;
+
+ public FadeInTexture(int color, TiledTexture texture) {
+ super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
+ mColor = color;
+ mTexture = texture;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+ if (isAnimating()) {
+ mTexture.drawMixed(canvas, mColor, getRatio(), x, y, w, h);
+ } else {
+ mTexture.draw(canvas, x, y, w, h);
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/FadeOutTexture.java b/src/com/android/gallery3d/glrenderer/FadeOutTexture.java
new file mode 100644
index 000000000..b05f3b631
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/FadeOutTexture.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 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;
+
+
+// FadeOutTexture is a texture which begins with a given texture, then gradually animates
+// into fading out totally.
+public class FadeOutTexture extends FadeTexture {
+ @SuppressWarnings("unused")
+ private static final String TAG = "FadeOutTexture";
+
+ private final BasicTexture mTexture;
+
+ public FadeOutTexture(BasicTexture texture) {
+ super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
+ mTexture = texture;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+ if (isAnimating()) {
+ canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
+ canvas.setAlpha(getRatio());
+ mTexture.draw(canvas, x, y, w, h);
+ canvas.restore();
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/FadeTexture.java b/src/com/android/gallery3d/glrenderer/FadeTexture.java
new file mode 100644
index 000000000..002c90f5c
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/FadeTexture.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 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 com.android.gallery3d.common.Utils;
+import com.android.gallery3d.ui.AnimationTime;
+
+// FadeTexture is a texture which fades the given texture along the time.
+public abstract class FadeTexture implements Texture {
+ @SuppressWarnings("unused")
+ private static final String TAG = "FadeTexture";
+
+ // The duration of the fading animation in milliseconds
+ public static final int DURATION = 180;
+
+ private final long mStartTime;
+ private final int mWidth;
+ private final int mHeight;
+ private final boolean mIsOpaque;
+ private boolean mIsAnimating;
+
+ public FadeTexture(int width, int height, boolean opaque) {
+ mWidth = width;
+ mHeight = height;
+ mIsOpaque = opaque;
+ mStartTime = now();
+ mIsAnimating = true;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y) {
+ draw(canvas, x, y, mWidth, mHeight);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return mIsOpaque;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ public boolean isAnimating() {
+ if (mIsAnimating) {
+ if (now() - mStartTime >= DURATION) {
+ mIsAnimating = false;
+ }
+ }
+ return mIsAnimating;
+ }
+
+ protected float getRatio() {
+ float r = (float)(now() - mStartTime) / DURATION;
+ return Utils.clamp(1.0f - r, 0.0f, 1.0f);
+ }
+
+ private long now() {
+ return AnimationTime.get();
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLCanvas.java b/src/com/android/gallery3d/glrenderer/GLCanvas.java
new file mode 100644
index 000000000..305e90521
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLCanvas.java
@@ -0,0 +1,217 @@
+/*
+ * 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.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import javax.microedition.khronos.opengles.GL11;
+
+//
+// GLCanvas gives a convenient interface to draw using OpenGL.
+//
+// When a rectangle is specified in this interface, it means the region
+// [x, x+width) * [y, y+height)
+//
+public interface GLCanvas {
+
+ public GLId getGLId();
+
+ // Tells GLCanvas the size of the underlying GL surface. This should be
+ // called before first drawing and when the size of GL surface is changed.
+ // This is called by GLRoot and should not be called by the clients
+ // who only want to draw on the GLCanvas. Both width and height must be
+ // nonnegative.
+ public abstract void setSize(int width, int height);
+
+ // Clear the drawing buffers. This should only be used by GLRoot.
+ public abstract void clearBuffer();
+
+ public abstract void clearBuffer(float[] argb);
+
+ // Sets and gets the current alpha, alpha must be in [0, 1].
+ public abstract void setAlpha(float alpha);
+
+ public abstract float getAlpha();
+
+ // (current alpha) = (current alpha) * alpha
+ public abstract void multiplyAlpha(float alpha);
+
+ // Change the current transform matrix.
+ public abstract void translate(float x, float y, float z);
+
+ public abstract void translate(float x, float y);
+
+ public abstract void scale(float sx, float sy, float sz);
+
+ public abstract void rotate(float angle, float x, float y, float z);
+
+ public abstract void multiplyMatrix(float[] mMatrix, int offset);
+
+ // Pushes the configuration state (matrix, and alpha) onto
+ // a private stack.
+ public abstract void save();
+
+ // Same as save(), but only save those specified in saveFlags.
+ public abstract void save(int saveFlags);
+
+ public static final int SAVE_FLAG_ALL = 0xFFFFFFFF;
+ public static final int SAVE_FLAG_ALPHA = 0x01;
+ public static final int SAVE_FLAG_MATRIX = 0x02;
+
+ // Pops from the top of the stack as current configuration state (matrix,
+ // alpha, and clip). This call balances a previous call to save(), and is
+ // used to remove all modifications to the configuration state since the
+ // last save call.
+ public abstract void restore();
+
+ // Draws a line using the specified paint from (x1, y1) to (x2, y2).
+ // (Both end points are included).
+ public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
+
+ // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
+ // (Both end points are included).
+ public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
+
+ // Fills the specified rectangle with the specified color.
+ public abstract void fillRect(float x, float y, float width, float height, int color);
+
+ // Draws a texture to the specified rectangle.
+ public abstract void drawTexture(
+ BasicTexture texture, int x, int y, int width, int height);
+
+ public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
+ int uvBuffer, int indexBuffer, int indexCount);
+
+ // Draws the source rectangle part of the texture to the target rectangle.
+ public abstract void drawTexture(BasicTexture texture, RectF source, RectF target);
+
+ // Draw a texture with a specified texture transform.
+ public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform,
+ int x, int y, int w, int h);
+
+ // Draw two textures to the specified rectangle. The actual texture used is
+ // from * (1 - ratio) + to * ratio
+ // The two textures must have the same size.
+ public abstract void drawMixed(BasicTexture from, int toColor,
+ float ratio, int x, int y, int w, int h);
+
+ // Draw a region of a texture and a specified color to the specified
+ // rectangle. The actual color used is from * (1 - ratio) + to * ratio.
+ // The region of the texture is defined by parameter "src". The target
+ // rectangle is specified by parameter "target".
+ public abstract void drawMixed(BasicTexture from, int toColor,
+ float ratio, RectF src, RectF target);
+
+ // Unloads the specified texture from the canvas. The resource allocated
+ // to draw the texture will be released. The specified texture will return
+ // to the unloaded state. This function should be called only from
+ // BasicTexture or its descendant
+ public abstract boolean unloadTexture(BasicTexture texture);
+
+ // Delete the specified buffer object, similar to unloadTexture.
+ public abstract void deleteBuffer(int bufferId);
+
+ // Delete the textures and buffers in GL side. This function should only be
+ // called in the GL thread.
+ public abstract void deleteRecycledResources();
+
+ // Dump statistics information and clear the counters. For debug only.
+ public abstract void dumpStatisticsAndClear();
+
+ public abstract void beginRenderTarget(RawTexture texture);
+
+ public abstract void endRenderTarget();
+
+ /**
+ * Sets texture parameters to use GL_CLAMP_TO_EDGE for both
+ * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
+ * GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
+ * bindTexture() must be called prior to this.
+ *
+ * @param texture The texture to set parameters on.
+ */
+ public abstract void setTextureParameters(BasicTexture texture);
+
+ /**
+ * Initializes the texture to a size by calling texImage2D on it.
+ *
+ * @param texture The texture to initialize the size.
+ * @param format The texture format (e.g. GL_RGBA)
+ * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
+ */
+ public abstract void initializeTextureSize(BasicTexture texture, int format, int type);
+
+ /**
+ * Initializes the texture to a size by calling texImage2D on it.
+ *
+ * @param texture The texture to initialize the size.
+ * @param bitmap The bitmap to initialize the bitmap with.
+ */
+ public abstract void initializeTexture(BasicTexture texture, Bitmap bitmap);
+
+ /**
+ * Calls glTexSubImage2D to upload a bitmap to the texture.
+ *
+ * @param texture The target texture to write to.
+ * @param xOffset Specifies a texel offset in the x direction within the
+ * texture array.
+ * @param yOffset Specifies a texel offset in the y direction within the
+ * texture array.
+ * @param format The texture format (e.g. GL_RGBA)
+ * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
+ */
+ public abstract void texSubImage2D(BasicTexture texture, int xOffset, int yOffset,
+ Bitmap bitmap,
+ int format, int type);
+
+ /**
+ * Generates buffers and uploads the buffer data.
+ *
+ * @param buffer The buffer to upload
+ * @return The buffer ID that was generated.
+ */
+ public abstract int uploadBuffer(java.nio.FloatBuffer buffer);
+
+ /**
+ * Generates buffers and uploads the element array buffer data.
+ *
+ * @param buffer The buffer to upload
+ * @return The buffer ID that was generated.
+ */
+ public abstract int uploadBuffer(java.nio.ByteBuffer buffer);
+
+ /**
+ * After LightCycle makes GL calls, this method is called to restore the GL
+ * configuration to the one expected by GLCanvas.
+ */
+ public abstract void recoverFromLightCycle();
+
+ /**
+ * Gets the bounds given by x, y, width, and height as well as the internal
+ * matrix state. There is no special handling for non-90-degree rotations.
+ * It only considers the lower-left and upper-right corners as the bounds.
+ *
+ * @param bounds The output bounds to write to.
+ * @param x The left side of the input rectangle.
+ * @param y The bottom of the input rectangle.
+ * @param width The width of the input rectangle.
+ * @param height The height of the input rectangle.
+ */
+ public abstract void getBounds(Rect bounds, int x, int y, int width, int height);
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES11Canvas.java b/src/com/android/gallery3d/glrenderer/GLES11Canvas.java
new file mode 100644
index 000000000..7013c3d1f
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES11Canvas.java
@@ -0,0 +1,997 @@
+/*
+ * 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.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.opengl.GLU;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.IntArray;
+
+import junit.framework.Assert;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+public class GLES11Canvas 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 GL11 mGL;
+
+ private final float mMatrixValues[] = new float[16];
+ private final float mTextureMatrixValues[] = new float[16];
+
+ // The results of mapPoints are stored in this buffer, and the order is
+ // x1, y1, x2, y2.
+ private final float mMapPointsBuffer[] = new float[4];
+
+ private final float mTextureColor[] = new float[4];
+
+ private int mBoxCoords;
+
+ private GLState mGLState;
+ private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
+
+ private float mAlpha;
+ private final ArrayList<ConfigState> mRestoreStack = new ArrayList<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 mScreenWidth;
+ private int mScreenHeight;
+ private boolean mBlendEnabled = true;
+ private int mFrameBuffer[] = new int[1];
+ private static float[] sCropRect = new float[4];
+
+ private RawTexture mTargetTexture;
+
+ // Drawing statistics
+ int mCountDrawLine;
+ int mCountFillRect;
+ int mCountDrawMesh;
+ int mCountTextureRect;
+ int mCountTextureOES;
+
+ private static GLId mGLId = new GLES11IdImpl();
+
+ public GLES11Canvas(GL11 gl) {
+ mGL = gl;
+ mGLState = new GLState(gl);
+ // 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];
+ mGLId.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 and mAlpha will be initialized in setSize()
+ }
+
+ @Override
+ public void setSize(int width, int height) {
+ Assert.assertTrue(width >= 0 && height >= 0);
+
+ if (mTargetTexture == null) {
+ mScreenWidth = width;
+ mScreenHeight = height;
+ }
+ mAlpha = 1.0f;
+
+ 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);
+ // to match the graphic coordinate system in android, we flip it vertically.
+ if (mTargetTexture == null) {
+ Matrix.translateM(matrix, 0, 0, height, 0);
+ Matrix.scaleM(matrix, 0, 1, -1, 1);
+ }
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ Assert.assertTrue(alpha >= 0 && alpha <= 1);
+ mAlpha = alpha;
+ }
+
+ @Override
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public void multiplyAlpha(float alpha) {
+ Assert.assertTrue(alpha >= 0 && alpha <= 1);
+ mAlpha *= alpha;
+ }
+
+ private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
+ return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+ }
+
+ @Override
+ 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());
+
+ saveTransform();
+ translate(x, y);
+ scale(width, height, 1);
+
+ gl.glLoadMatrixf(mMatrixValues, 0);
+ gl.glDrawArrays(GL11.GL_LINE_LOOP, OFFSET_DRAW_RECT, 4);
+
+ restoreTransform();
+ mCountDrawLine++;
+ }
+
+ @Override
+ 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());
+
+ saveTransform();
+ translate(x1, y1);
+ scale(x2 - x1, y2 - y1, 1);
+
+ gl.glLoadMatrixf(mMatrixValues, 0);
+ gl.glDrawArrays(GL11.GL_LINE_STRIP, OFFSET_DRAW_LINE, 2);
+
+ restoreTransform();
+ mCountDrawLine++;
+ }
+
+ @Override
+ public void fillRect(float x, float y, float width, float height, int color) {
+ mGLState.setColorMode(color, mAlpha);
+ GL11 gl = mGL;
+
+ saveTransform();
+ translate(x, y);
+ scale(width, height, 1);
+
+ gl.glLoadMatrixf(mMatrixValues, 0);
+ gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
+
+ restoreTransform();
+ mCountFillRect++;
+ }
+
+ @Override
+ public void translate(float x, float y, float z) {
+ Matrix.translateM(mMatrixValues, 0, 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) {
+ float[] m = mMatrixValues;
+ m[12] += m[0] * x + m[4] * y;
+ m[13] += m[1] * x + m[5] * y;
+ m[14] += m[2] * x + m[6] * y;
+ m[15] += m[3] * x + m[7] * y;
+ }
+
+ @Override
+ public void scale(float sx, float sy, float sz) {
+ Matrix.scaleM(mMatrixValues, 0, sx, sy, sz);
+ }
+
+ @Override
+ public void rotate(float angle, float x, float y, float z) {
+ if (angle == 0) return;
+ 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);
+ }
+
+ @Override
+ 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);
+ scale(width, height, 1);
+
+ gl.glLoadMatrixf(mMatrixValues, 0);
+ gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
+
+ restoreTransform();
+ mCountTextureRect++;
+ }
+
+ @Override
+ 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);
+
+ 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++;
+ }
+
+ // Transforms two points by the given matrix m. The result
+ // {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned.
+ private float[] mapPoints(float m[], int x1, int y1, int x2, int y2) {
+ float[] r = mMapPointsBuffer;
+
+ // Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused.
+ float x3 = m[0] * x1 + m[4] * y1 + m[12];
+ float y3 = m[1] * x1 + m[5] * y1 + m[13];
+ float w3 = m[3] * x1 + m[7] * y1 + m[15];
+ r[0] = x3 / w3;
+ r[1] = y3 / w3;
+
+ // Same for x2 y2.
+ float x4 = m[0] * x2 + m[4] * y2 + m[12];
+ float y4 = m[1] * x2 + m[5] * y2 + m[13];
+ float w4 = m[3] * x2 + m[7] * y2 + m[15];
+ r[2] = x4 / w4;
+ r[3] = y4 / w4;
+
+ return r;
+ }
+
+ 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 = (int) (points[0] + 0.5f);
+ y = (int) (points[1] + 0.5f);
+ width = (int) (points[2] + 0.5f) - x;
+ height = (int) (points[3] + 0.5f) - y;
+ if (width > 0 && height > 0) {
+ ((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height);
+ mCountTextureOES++;
+ }
+ }
+ }
+
+ @Override
+ public void drawTexture(
+ BasicTexture texture, int x, int y, int width, int height) {
+ drawTexture(texture, x, y, width, height, mAlpha);
+ }
+
+ private 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);
+ }
+
+ @Override
+ 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());
+ }
+
+ @Override
+ public void drawTexture(BasicTexture texture, float[] mTextureTransform,
+ int x, int y, int w, int h) {
+ mGLState.setBlendEnabled(mBlendEnabled
+ && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
+ if (!bindTexture(texture)) return;
+ setTextureCoords(mTextureTransform);
+ mGLState.setTextureAlpha(mAlpha);
+ textureRect(x, y, w, h);
+ }
+
+ // 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;
+ }
+ }
+
+ @Override
+ 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);
+ }
+
+ private boolean bindTexture(BasicTexture texture) {
+ if (!texture.onBind(this)) return false;
+ int target = texture.getTarget();
+ mGLState.setTextureTarget(target);
+ mGL.glBindTexture(target, 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 setMixedColor(int toColor, float ratio, float alpha) {
+ //
+ // The formula we want:
+ // alpha * ((1 - ratio) * from + ratio * to)
+ //
+ // The formula that GL supports is in the form of:
+ // combo * from + (1 - combo) * to * scale
+ //
+ // So, we have combo = alpha * (1 - ratio)
+ // and scale = alpha * ratio / (1 - combo)
+ //
+ float combo = alpha * (1 - ratio);
+ float scale = alpha * ratio / (1 - combo);
+
+ // 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 colorScale = scale * (toColor >>> 24) / (0xff * 0xff);
+ setTextureColor(((toColor >>> 16) & 0xff) * colorScale,
+ ((toColor >>> 8) & 0xff) * colorScale,
+ (toColor & 0xff) * colorScale, combo);
+ GL11 gl = mGL;
+ 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);
+
+ }
+
+ @Override
+ public void drawMixed(BasicTexture from, int toColor, float ratio,
+ RectF source, RectF target) {
+ if (target.width() <= 0 || target.height() <= 0) return;
+
+ if (ratio <= 0.01f) {
+ drawTexture(from, source, target);
+ return;
+ } else if (ratio >= 1) {
+ fillRect(target.left, target.top, target.width(), target.height(), toColor);
+ return;
+ }
+
+ float alpha = mAlpha;
+
+ // Copy the input to avoid changing it.
+ mDrawTextureSourceRect.set(source);
+ mDrawTextureTargetRect.set(target);
+ source = mDrawTextureSourceRect;
+ target = mDrawTextureTargetRect;
+
+ mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
+ || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA));
+
+ if (!bindTexture(from)) return;
+
+ // Interpolate the RGB and alpha values between both textures.
+ mGLState.setTexEnvMode(GL11.GL_COMBINE);
+ setMixedColor(toColor, ratio, alpha);
+ convertCoordinate(source, target, from);
+ setTextureCoords(source);
+ textureRect(target.left, target.top, target.width(), target.height());
+ mGLState.setTexEnvMode(GL11.GL_REPLACE);
+ }
+
+ private void drawMixed(BasicTexture from, int toColor,
+ float ratio, int x, int y, int width, int height, float alpha) {
+ // change from 0 to 0.01f to prevent getting divided by zero below
+ if (ratio <= 0.01f) {
+ 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;
+
+ // Interpolate the RGB and alpha values between both textures.
+ mGLState.setTexEnvMode(GL11.GL_COMBINE);
+ setMixedColor(toColor, ratio, alpha);
+
+ drawBoundTexture(from, x, y, width, height);
+ mGLState.setTexEnvMode(GL11.GL_REPLACE);
+ }
+
+ // 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;
+ }
+
+ private static class GLState {
+
+ private final GL11 mGL;
+
+ private int mTexEnvMode = GL11.GL_REPLACE;
+ private float mTextureAlpha = 1.0f;
+ private int mTextureTarget = GL11.GL_TEXTURE_2D;
+ 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.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.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 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;
+
+ setTextureTarget(0);
+
+ 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));
+ }
+
+ // target is a value like GL_TEXTURE_2D. If target = 0, texturing is disabled.
+ public void setTextureTarget(int target) {
+ if (mTextureTarget == target) return;
+ if (mTextureTarget != 0) {
+ mGL.glDisable(mTextureTarget);
+ }
+ mTextureTarget = target;
+ if (mTextureTarget != 0) {
+ mGL.glEnable(mTextureTarget);
+ }
+ }
+
+ public void setBlendEnabled(boolean enabled) {
+ if (mBlendEnabled == enabled) return;
+ mBlendEnabled = enabled;
+ if (enabled) {
+ mGL.glEnable(GL11.GL_BLEND);
+ } else {
+ mGL.glDisable(GL11.GL_BLEND);
+ }
+ }
+ }
+
+ @Override
+ public void clearBuffer(float[] argb) {
+ if(argb != null && argb.length == 4) {
+ mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]);
+ } else {
+ mGL.glClearColor(0, 0, 0, 1);
+ }
+ mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
+ }
+
+ @Override
+ public void clearBuffer() {
+ clearBuffer(null);
+ }
+
+ 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);
+ }
+
+ private void setTextureCoords(float[] mTextureTransform) {
+ mGL.glMatrixMode(GL11.GL_TEXTURE);
+ mGL.glLoadMatrixf(mTextureTransform, 0);
+ mGL.glMatrixMode(GL11.GL_MODELVIEW);
+ }
+
+ // unloadTexture and deleteBuffer can be called from the finalizer thread,
+ // so we synchronized on the mUnboundTextures object.
+ @Override
+ public boolean unloadTexture(BasicTexture t) {
+ synchronized (mUnboundTextures) {
+ if (!t.isLoaded()) return false;
+ mUnboundTextures.add(t.mId);
+ return true;
+ }
+ }
+
+ @Override
+ public void deleteBuffer(int bufferId) {
+ synchronized (mUnboundTextures) {
+ mDeleteBuffers.add(bufferId);
+ }
+ }
+
+ @Override
+ public void deleteRecycledResources() {
+ synchronized (mUnboundTextures) {
+ IntArray ids = mUnboundTextures;
+ if (ids.size() > 0) {
+ mGLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0);
+ ids.clear();
+ }
+
+ ids = mDeleteBuffers;
+ if (ids.size() > 0) {
+ mGLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0);
+ ids.clear();
+ }
+ }
+ }
+
+ @Override
+ public void save() {
+ save(SAVE_FLAG_ALL);
+ }
+
+ @Override
+ public void save(int saveFlags) {
+ ConfigState config = obtainRestoreConfig();
+
+ if ((saveFlags & SAVE_FLAG_ALPHA) != 0) {
+ config.mAlpha = mAlpha;
+ } else {
+ config.mAlpha = -1;
+ }
+
+ if ((saveFlags & SAVE_FLAG_MATRIX) != 0) {
+ System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16);
+ } else {
+ config.mMatrix[0] = Float.NEGATIVE_INFINITY;
+ }
+
+ mRestoreStack.add(config);
+ }
+
+ @Override
+ public void restore() {
+ if (mRestoreStack.isEmpty()) throw new IllegalStateException();
+ ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1);
+ 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;
+ float mMatrix[] = new float[16];
+ ConfigState mNextFree;
+
+ public void restore(GLES11Canvas canvas) {
+ if (mAlpha >= 0) canvas.setAlpha(mAlpha);
+ if (mMatrix[0] != Float.NEGATIVE_INFINITY) {
+ System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16);
+ }
+ }
+ }
+
+ @Override
+ 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);
+ }
+
+ private void setRenderTarget(RawTexture texture) {
+ GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL;
+
+ if (mTargetTexture == null && texture != null) {
+ mGLId.glGenBuffers(1, mFrameBuffer, 0);
+ gl11ep.glBindFramebufferOES(
+ GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]);
+ }
+ if (mTargetTexture != null && texture == null) {
+ gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0);
+ gl11ep.glDeleteFramebuffersOES(1, mFrameBuffer, 0);
+ }
+
+ mTargetTexture = texture;
+ if (texture == null) {
+ setSize(mScreenWidth, mScreenHeight);
+ } else {
+ setSize(texture.getWidth(), texture.getHeight());
+
+ if (!texture.isLoaded()) texture.prepare(this);
+
+ gl11ep.glFramebufferTexture2DOES(
+ GL11ExtensionPack.GL_FRAMEBUFFER_OES,
+ GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES,
+ GL11.GL_TEXTURE_2D, texture.getId(), 0);
+
+ checkFramebufferStatus(gl11ep);
+ }
+ }
+
+ @Override
+ public void endRenderTarget() {
+ RawTexture texture = mTargetStack.remove(mTargetStack.size() - 1);
+ setRenderTarget(texture);
+ restore(); // restore matrix and alpha
+ }
+
+ @Override
+ public void beginRenderTarget(RawTexture texture) {
+ save(); // save matrix and alpha
+ mTargetStack.add(mTargetTexture);
+ setRenderTarget(texture);
+ }
+
+ private static void checkFramebufferStatus(GL11ExtensionPack gl11ep) {
+ int status = gl11ep.glCheckFramebufferStatusOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES);
+ if (status != GL11ExtensionPack.GL_FRAMEBUFFER_COMPLETE_OES) {
+ String msg = "";
+ switch (status) {
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
+ msg = "FRAMEBUFFER_FORMATS";
+ break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
+ msg = "FRAMEBUFFER_ATTACHMENT";
+ break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
+ msg = "FRAMEBUFFER_MISSING_ATTACHMENT";
+ break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_OES:
+ msg = "FRAMEBUFFER_DRAW_BUFFER";
+ break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_OES:
+ msg = "FRAMEBUFFER_READ_BUFFER";
+ break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_UNSUPPORTED_OES:
+ msg = "FRAMEBUFFER_UNSUPPORTED";
+ break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
+ msg = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+ break;
+ }
+ throw new RuntimeException(msg + ":" + Integer.toHexString(status));
+ }
+ }
+
+ @Override
+ public void setTextureParameters(BasicTexture texture) {
+ int width = texture.getWidth();
+ int height = texture.getHeight();
+ // Define a vertically flipped crop rectangle for OES_draw_texture.
+ // The four values in sCropRect are: left, bottom, width, and
+ // height. Negative value of width or height means flip.
+ sCropRect[0] = 0;
+ sCropRect[1] = height;
+ sCropRect[2] = width;
+ sCropRect[3] = -height;
+
+ // Set texture parameters.
+ int target = texture.getTarget();
+ mGL.glBindTexture(target, texture.getId());
+ mGL.glTexParameterfv(target, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
+ mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
+ mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
+ mGL.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
+ mGL.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
+ }
+
+ @Override
+ public void initializeTextureSize(BasicTexture texture, int format, int type) {
+ int target = texture.getTarget();
+ mGL.glBindTexture(target, texture.getId());
+ int width = texture.getTextureWidth();
+ int height = texture.getTextureHeight();
+ mGL.glTexImage2D(target, 0, format, width, height, 0, format, type, null);
+ }
+
+ @Override
+ public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
+ int target = texture.getTarget();
+ mGL.glBindTexture(target, texture.getId());
+ 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();
+ mGL.glBindTexture(target, texture.getId());
+ GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
+ }
+
+ @Override
+ public int uploadBuffer(FloatBuffer buf) {
+ return uploadBuffer(buf, Float.SIZE / Byte.SIZE);
+ }
+
+ @Override
+ public int uploadBuffer(ByteBuffer buf) {
+ return uploadBuffer(buf, 1);
+ }
+
+ private int uploadBuffer(Buffer buf, int elementSize) {
+ int[] bufferIds = new int[1];
+ mGLId.glGenBuffers(bufferIds.length, bufferIds, 0);
+ int bufferId = bufferIds[0];
+ mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId);
+ mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf,
+ GL11.GL_STATIC_DRAW);
+ return bufferId;
+ }
+
+ @Override
+ public void recoverFromLightCycle() {
+ // This is only required for GLES20
+ }
+
+ @Override
+ public void getBounds(Rect bounds, int x, int y, int width, int height) {
+ // This is only required for GLES20
+ }
+
+ @Override
+ public GLId getGLId() {
+ return mGLId;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES11IdImpl.java b/src/com/android/gallery3d/glrenderer/GLES11IdImpl.java
new file mode 100644
index 000000000..e4793730f
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES11IdImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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 javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+/**
+ * Open GL ES 1.1 implementation for generating and destroying texture IDs and
+ * buffer IDs
+ */
+public class GLES11IdImpl implements GLId {
+ private static int sNextId = 1;
+ // Mutex for sNextId
+ private static Object sLock = new Object();
+
+ @Override
+ public int generateTexture() {
+ synchronized (sLock) {
+ return sNextId++;
+ }
+ }
+
+ @Override
+ public void glGenBuffers(int n, int[] buffers, int offset) {
+ synchronized (sLock) {
+ while (n-- > 0) {
+ buffers[offset + n] = sNextId++;
+ }
+ }
+ }
+
+ @Override
+ public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
+ synchronized (sLock) {
+ gl.glDeleteTextures(n, textures, offset);
+ }
+ }
+
+ @Override
+ public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
+ synchronized (sLock) {
+ gl.glDeleteBuffers(n, buffers, offset);
+ }
+ }
+
+ @Override
+ public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
+ synchronized (sLock) {
+ gl11ep.glDeleteFramebuffersOES(n, buffers, offset);
+ }
+ }
+
+
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
new file mode 100644
index 000000000..4ead1315e
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
@@ -0,0 +1,1009 @@
+/*
+ * 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;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java b/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
new file mode 100644
index 000000000..6cd7149cb
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
@@ -0,0 +1,42 @@
+package com.android.gallery3d.glrenderer;
+
+import android.opengl.GLES20;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+public class GLES20IdImpl implements GLId {
+ private final int[] mTempIntArray = new int[1];
+
+ @Override
+ public int generateTexture() {
+ GLES20.glGenTextures(1, mTempIntArray, 0);
+ GLES20Canvas.checkError();
+ return mTempIntArray[0];
+ }
+
+ @Override
+ public void glGenBuffers(int n, int[] buffers, int offset) {
+ GLES20.glGenBuffers(n, buffers, offset);
+ GLES20Canvas.checkError();
+ }
+
+ @Override
+ public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
+ GLES20.glDeleteTextures(n, textures, offset);
+ GLES20Canvas.checkError();
+ }
+
+
+ @Override
+ public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
+ GLES20.glDeleteBuffers(n, buffers, offset);
+ GLES20Canvas.checkError();
+ }
+
+ @Override
+ public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
+ GLES20.glDeleteFramebuffers(n, buffers, offset);
+ GLES20Canvas.checkError();
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLId.java b/src/com/android/gallery3d/glrenderer/GLId.java
new file mode 100644
index 000000000..3cec558f6
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLId.java
@@ -0,0 +1,33 @@
+/*
+ * 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 javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+// This mimics corresponding GL functions.
+public interface GLId {
+ public int generateTexture();
+
+ public void glGenBuffers(int n, int[] buffers, int offset);
+
+ public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
+
+ public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
+
+ public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLPaint.java b/src/com/android/gallery3d/glrenderer/GLPaint.java
new file mode 100644
index 000000000..16b220690
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLPaint.java
@@ -0,0 +1,41 @@
+/*
+ * 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.glrenderer;
+
+import junit.framework.Assert;
+
+public class GLPaint {
+ private float mLineWidth = 1f;
+ private int mColor = 0;
+
+ public void setColor(int color) {
+ mColor = color;
+ }
+
+ public int getColor() {
+ return mColor;
+ }
+
+ public void setLineWidth(float width) {
+ Assert.assertTrue(width >= 0);
+ mLineWidth = width;
+ }
+
+ public float getLineWidth() {
+ return mLineWidth;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/MultiLineTexture.java b/src/com/android/gallery3d/glrenderer/MultiLineTexture.java
new file mode 100644
index 000000000..82839f107
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/MultiLineTexture.java
@@ -0,0 +1,52 @@
+/*
+ * 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.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+
+
+// MultiLineTexture is a texture shows the content of a specified String.
+//
+// To create a MultiLineTexture, use the newInstance() method and specify
+// the String, the font size, and the color.
+class MultiLineTexture extends CanvasTexture {
+ private final Layout mLayout;
+
+ private MultiLineTexture(Layout layout) {
+ super(layout.getWidth(), layout.getHeight());
+ mLayout = layout;
+ }
+
+ public static MultiLineTexture newInstance(
+ String text, int maxWidth, float textSize, int color,
+ Layout.Alignment alignment) {
+ TextPaint paint = StringTexture.getDefaultPaint(textSize, color);
+ Layout layout = new StaticLayout(text, 0, text.length(), paint,
+ maxWidth, alignment, 1, 0, true, null, 0);
+
+ return new MultiLineTexture(layout);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas, Bitmap backing) {
+ mLayout.draw(canvas);
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/NinePatchChunk.java b/src/com/android/gallery3d/glrenderer/NinePatchChunk.java
new file mode 100644
index 000000000..9dc326622
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/NinePatchChunk.java
@@ -0,0 +1,82 @@
+/*
+ * 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.glrenderer;
+
+import android.graphics.Rect;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+// See "frameworks/base/include/utils/ResourceTypes.h" for the format of
+// NinePatch chunk.
+class NinePatchChunk {
+
+ public static final int NO_COLOR = 0x00000001;
+ public static final int TRANSPARENT_COLOR = 0x00000000;
+
+ public Rect mPaddings = new Rect();
+
+ public int mDivX[];
+ public int mDivY[];
+ public int mColor[];
+
+ private static void readIntArray(int[] data, ByteBuffer buffer) {
+ for (int i = 0, n = data.length; i < n; ++i) {
+ data[i] = buffer.getInt();
+ }
+ }
+
+ private static void checkDivCount(int length) {
+ if (length == 0 || (length & 0x01) != 0) {
+ throw new RuntimeException("invalid nine-patch: " + length);
+ }
+ }
+
+ public static NinePatchChunk deserialize(byte[] data) {
+ ByteBuffer byteBuffer =
+ ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
+
+ byte wasSerialized = byteBuffer.get();
+ if (wasSerialized == 0) return null;
+
+ NinePatchChunk chunk = new NinePatchChunk();
+ chunk.mDivX = new int[byteBuffer.get()];
+ chunk.mDivY = new int[byteBuffer.get()];
+ chunk.mColor = new int[byteBuffer.get()];
+
+ checkDivCount(chunk.mDivX.length);
+ checkDivCount(chunk.mDivY.length);
+
+ // skip 8 bytes
+ byteBuffer.getInt();
+ byteBuffer.getInt();
+
+ chunk.mPaddings.left = byteBuffer.getInt();
+ chunk.mPaddings.right = byteBuffer.getInt();
+ chunk.mPaddings.top = byteBuffer.getInt();
+ chunk.mPaddings.bottom = byteBuffer.getInt();
+
+ // skip 4 bytes
+ byteBuffer.getInt();
+
+ readIntArray(chunk.mDivX, byteBuffer);
+ readIntArray(chunk.mDivY, byteBuffer);
+ readIntArray(chunk.mColor, byteBuffer);
+
+ return chunk;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/glrenderer/NinePatchTexture.java b/src/com/android/gallery3d/glrenderer/NinePatchTexture.java
new file mode 100644
index 000000000..d0ddc46c3
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/NinePatchTexture.java
@@ -0,0 +1,424 @@
+/*
+ * 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.glrenderer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+
+import com.android.gallery3d.common.Utils;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+// NinePatchTexture is a texture backed by a NinePatch resource.
+//
+// getPaddings() returns paddings specified in the NinePatch.
+// getNinePatchChunk() returns the layout data specified in the NinePatch.
+//
+public class NinePatchTexture extends ResourceTexture {
+ @SuppressWarnings("unused")
+ private static final String TAG = "NinePatchTexture";
+ private NinePatchChunk mChunk;
+ private SmallCache<NinePatchInstance> mInstanceCache
+ = new SmallCache<NinePatchInstance>();
+
+ public NinePatchTexture(Context context, int resId) {
+ super(context, resId);
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ if (mBitmap != null) return mBitmap;
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ Bitmap bitmap = BitmapFactory.decodeResource(
+ mContext.getResources(), mResId, options);
+ mBitmap = bitmap;
+ setSize(bitmap.getWidth(), bitmap.getHeight());
+ byte[] chunkData = bitmap.getNinePatchChunk();
+ mChunk = chunkData == null
+ ? null
+ : NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
+ if (mChunk == null) {
+ throw new RuntimeException("invalid nine-patch image: " + mResId);
+ }
+ return bitmap;
+ }
+
+ public Rect getPaddings() {
+ // get the paddings from nine patch
+ if (mChunk == null) onGetBitmap();
+ return mChunk.mPaddings;
+ }
+
+ public NinePatchChunk getNinePatchChunk() {
+ if (mChunk == null) onGetBitmap();
+ return mChunk;
+ }
+
+ // This is a simple cache for a small number of things. Linear search
+ // is used because the cache is small. It also tries to remove less used
+ // item when the cache is full by moving the often-used items to the front.
+ private static class SmallCache<V> {
+ private static final int CACHE_SIZE = 16;
+ private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
+ private int[] mKey = new int[CACHE_SIZE];
+ private V[] mValue = (V[]) new Object[CACHE_SIZE];
+ private int mCount; // number of items in this cache
+
+ // Puts a value into the cache. If the cache is full, also returns
+ // a less used item, otherwise returns null.
+ public V put(int key, V value) {
+ if (mCount == CACHE_SIZE) {
+ V old = mValue[CACHE_SIZE - 1]; // remove the last item
+ mKey[CACHE_SIZE - 1] = key;
+ mValue[CACHE_SIZE - 1] = value;
+ return old;
+ } else {
+ mKey[mCount] = key;
+ mValue[mCount] = value;
+ mCount++;
+ return null;
+ }
+ }
+
+ public V get(int key) {
+ for (int i = 0; i < mCount; i++) {
+ if (mKey[i] == key) {
+ // Move the accessed item one position to the front, so it
+ // will less likely to be removed when cache is full. Only
+ // do this if the cache is starting to get full.
+ if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
+ int tmpKey = mKey[i];
+ mKey[i] = mKey[i - 1];
+ mKey[i - 1] = tmpKey;
+
+ V tmpValue = mValue[i];
+ mValue[i] = mValue[i - 1];
+ mValue[i - 1] = tmpValue;
+ }
+ return mValue[i];
+ }
+ }
+ return null;
+ }
+
+ public void clear() {
+ for (int i = 0; i < mCount; i++) {
+ mValue[i] = null; // make sure it's can be garbage-collected.
+ }
+ mCount = 0;
+ }
+
+ public int size() {
+ return mCount;
+ }
+
+ public V valueAt(int i) {
+ return mValue[i];
+ }
+ }
+
+ private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
+ int key = w;
+ key = (key << 16) | h;
+ NinePatchInstance instance = mInstanceCache.get(key);
+
+ if (instance == null) {
+ instance = new NinePatchInstance(this, w, h);
+ NinePatchInstance removed = mInstanceCache.put(key, instance);
+ if (removed != null) {
+ removed.recycle(canvas);
+ }
+ }
+
+ return instance;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+ if (!isLoaded()) {
+ mInstanceCache.clear();
+ }
+
+ if (w != 0 && h != 0) {
+ findInstance(canvas, w, h).draw(canvas, this, x, y);
+ }
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ GLCanvas canvas = mCanvasRef;
+ if (canvas == null) return;
+ int n = mInstanceCache.size();
+ for (int i = 0; i < n; i++) {
+ NinePatchInstance instance = mInstanceCache.valueAt(i);
+ instance.recycle(canvas);
+ }
+ mInstanceCache.clear();
+ }
+}
+
+// This keeps data for a specialization of NinePatchTexture with the size
+// (width, height). We pre-compute the coordinates for efficiency.
+class NinePatchInstance {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "NinePatchInstance";
+
+ // 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, plus 2 for each
+ // transparent region. Current there are at most 1 transparent region.
+ private static final int INDEX_BUFFER_SIZE = 22 + 2;
+
+ private FloatBuffer mXyBuffer;
+ private FloatBuffer mUvBuffer;
+ private ByteBuffer mIndexBuffer;
+
+ // Names for buffer names: xy, uv, index.
+ private int mXyBufferName = -1;
+ private int mUvBufferName;
+ private int mIndexBufferName;
+
+ private int mIdxCount;
+
+ public NinePatchInstance(NinePatchTexture tex, int width, int height) {
+ NinePatchChunk chunk = tex.getNinePatchChunk();
+
+ if (width <= 0 || height <= 0) {
+ throw new RuntimeException("invalid dimension");
+ }
+
+ // 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");
+ }
+
+ float divX[] = new float[4];
+ float divY[] = new float[4];
+ float divU[] = new float[4];
+ float divV[] = new float[4];
+
+ int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
+ int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
+
+ prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
+ }
+
+ /**
+ * 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 static int stretch(
+ float x[], float u[], int div[], int source, int target) {
+ int textureSize = Utils.nextPowerOf2(source);
+ float textureBound = (float) source / textureSize;
+
+ float stretch = 0;
+ for (int i = 0, n = div.length; i < n; i += 2) {
+ stretch += div[i + 1] - div[i];
+ }
+
+ float remaining = target - source + stretch;
+
+ float lastX = 0;
+ float lastU = 0;
+
+ x[0] = 0;
+ u[0] = 0;
+ for (int i = 0, n = div.length; i < n; i += 2) {
+ // Make the stretchy segment a little smaller to prevent sampling
+ // on neighboring fixed segments.
+ // fixed segment
+ x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
+ u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
+
+ // stretchy segment
+ float partU = div[i + 1] - div[i];
+ float partX = remaining * partU / stretch;
+ remaining -= partX;
+ stretch -= partU;
+
+ lastX = x[i + 1] + partX;
+ lastU = div[i + 1];
+ x[i + 2] = lastX - 0.5f;
+ u[i + 2] = Math.min((lastU - 0.5f)/ 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[i] - x[last]) < 1f) continue;
+ x[++last] = x[i];
+ u[last] = u[i];
+ }
+ return last + 1;
+ }
+
+ private void prepareVertexData(float x[], float y[], float u[], float v[],
+ int nx, int ny, int[] color) {
+ /*
+ * 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[] = new float[VERTEX_BUFFER_SIZE];
+ float uv[] = new float[VERTEX_BUFFER_SIZE];
+ 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];
+ }
+ }
+
+ int idxCount = 1;
+ boolean isForward = false;
+ byte index[] = new byte[INDEX_BUFFER_SIZE];
+ for (int row = 0; row < ny - 1; row++) {
+ --idxCount;
+ isForward = !isForward;
+
+ int start, end, inc;
+ if (isForward) {
+ start = 0;
+ end = nx;
+ inc = 1;
+ } else {
+ start = nx - 1;
+ end = -1;
+ inc = -1;
+ }
+
+ for (int col = start; col != end; col += inc) {
+ int k = row * nx + col;
+ if (col != start) {
+ int colorIdx = row * (nx - 1) + col;
+ if (isForward) colorIdx--;
+ if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
+ index[idxCount] = index[idxCount - 1];
+ ++idxCount;
+ index[idxCount++] = (byte) k;
+ }
+ }
+
+ index[idxCount++] = (byte) k;
+ index[idxCount++] = (byte) (k + nx);
+ }
+ }
+
+ mIdxCount = idxCount;
+
+ int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
+ mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
+ mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
+ mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
+
+ mXyBuffer.put(xy, 0, pntCount * 2).position(0);
+ mUvBuffer.put(uv, 0, pntCount * 2).position(0);
+ mIndexBuffer.put(index, 0, idxCount).position(0);
+ }
+
+ private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
+ return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+ }
+
+ private void prepareBuffers(GLCanvas canvas) {
+ mXyBufferName = canvas.uploadBuffer(mXyBuffer);
+ mUvBufferName = canvas.uploadBuffer(mUvBuffer);
+ mIndexBufferName = canvas.uploadBuffer(mIndexBuffer);
+
+ // These buffers are never used again.
+ mXyBuffer = null;
+ mUvBuffer = null;
+ mIndexBuffer = null;
+ }
+
+ public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
+ if (mXyBufferName == -1) {
+ prepareBuffers(canvas);
+ }
+ canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount);
+ }
+
+ public void recycle(GLCanvas canvas) {
+ if (mXyBuffer == null) {
+ canvas.deleteBuffer(mXyBufferName);
+ canvas.deleteBuffer(mUvBufferName);
+ canvas.deleteBuffer(mIndexBufferName);
+ mXyBufferName = -1;
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/RawTexture.java b/src/com/android/gallery3d/glrenderer/RawTexture.java
new file mode 100644
index 000000000..93f0fdff9
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/RawTexture.java
@@ -0,0 +1,73 @@
+/*
+ * 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.glrenderer;
+
+import android.util.Log;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class RawTexture extends BasicTexture {
+ private static final String TAG = "RawTexture";
+
+ private final boolean mOpaque;
+ private boolean mIsFlipped;
+
+ public RawTexture(int width, int height, boolean opaque) {
+ mOpaque = opaque;
+ setSize(width, height);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return mOpaque;
+ }
+
+ @Override
+ public boolean isFlippedVertically() {
+ return mIsFlipped;
+ }
+
+ public void setIsFlippedVertically(boolean isFlipped) {
+ mIsFlipped = isFlipped;
+ }
+
+ protected void prepare(GLCanvas canvas) {
+ GLId glId = canvas.getGLId();
+ mId = glId.generateTexture();
+ canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
+ canvas.setTextureParameters(this);
+ mState = STATE_LOADED;
+ setAssociatedCanvas(canvas);
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ if (isLoaded()) return true;
+ Log.w(TAG, "lost the content due to context change");
+ return false;
+ }
+
+ @Override
+ public void yield() {
+ // we cannot free the texture because we have no backup.
+ }
+
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/ResourceTexture.java b/src/com/android/gallery3d/glrenderer/ResourceTexture.java
new file mode 100644
index 000000000..eb8e8a517
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/ResourceTexture.java
@@ -0,0 +1,53 @@
+/*
+ * 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.glrenderer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import junit.framework.Assert;
+
+// ResourceTexture is a texture whose Bitmap is decoded from a resource.
+// By default ResourceTexture is not opaque.
+public class ResourceTexture extends UploadedTexture {
+
+ protected final Context mContext;
+ protected final int mResId;
+
+ public ResourceTexture(Context context, int resId) {
+ Assert.assertNotNull(context);
+ mContext = context;
+ mResId = resId;
+ setOpaque(false);
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ return BitmapFactory.decodeResource(
+ mContext.getResources(), mResId, options);
+ }
+
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ if (!inFinalizer()) {
+ bitmap.recycle();
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/StringTexture.java b/src/com/android/gallery3d/glrenderer/StringTexture.java
new file mode 100644
index 000000000..56ca29753
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/StringTexture.java
@@ -0,0 +1,88 @@
+/*
+ * 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.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.Typeface;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.FloatMath;
+
+// StringTexture is a texture shows the content of a specified String.
+//
+// To create a StringTexture, use the newInstance() method and specify
+// the String, the font size, and the color.
+public class StringTexture extends CanvasTexture {
+ private final String mText;
+ private final TextPaint mPaint;
+ private final FontMetricsInt mMetrics;
+
+ private StringTexture(String text, TextPaint paint,
+ FontMetricsInt metrics, int width, int height) {
+ super(width, height);
+ mText = text;
+ mPaint = paint;
+ mMetrics = metrics;
+ }
+
+ public static TextPaint getDefaultPaint(float textSize, int color) {
+ TextPaint paint = new TextPaint();
+ paint.setTextSize(textSize);
+ paint.setAntiAlias(true);
+ paint.setColor(color);
+ paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
+ return paint;
+ }
+
+ public static StringTexture newInstance(
+ String text, float textSize, int color) {
+ return newInstance(text, getDefaultPaint(textSize, color));
+ }
+
+ public static StringTexture newInstance(
+ String text, float textSize, int color,
+ float lengthLimit, boolean isBold) {
+ TextPaint paint = getDefaultPaint(textSize, color);
+ if (isBold) {
+ paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+ }
+ if (lengthLimit > 0) {
+ text = TextUtils.ellipsize(
+ text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();
+ }
+ return newInstance(text, paint);
+ }
+
+ private static StringTexture newInstance(String text, TextPaint paint) {
+ FontMetricsInt metrics = paint.getFontMetricsInt();
+ int width = (int) FloatMath.ceil(paint.measureText(text));
+ int height = metrics.bottom - metrics.top;
+ // The texture size needs to be at least 1x1.
+ if (width <= 0) width = 1;
+ if (height <= 0) height = 1;
+ return new StringTexture(text, paint, metrics, width, height);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas, Bitmap backing) {
+ canvas.translate(0, -mMetrics.ascent);
+ canvas.drawText(mText, 0, 0, mPaint);
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/Texture.java b/src/com/android/gallery3d/glrenderer/Texture.java
new file mode 100644
index 000000000..3dcae4aec
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/Texture.java
@@ -0,0 +1,44 @@
+/*
+ * 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.glrenderer;
+
+
+// Texture is a rectangular image which can be drawn on GLCanvas.
+// The isOpaque() function gives a hint about whether the texture is opaque,
+// so the drawing can be done faster.
+//
+// This is the current texture hierarchy:
+//
+// Texture
+// -- ColorTexture
+// -- FadeInTexture
+// -- BasicTexture
+// -- UploadedTexture
+// -- BitmapTexture
+// -- Tile
+// -- ResourceTexture
+// -- NinePatchTexture
+// -- CanvasTexture
+// -- StringTexture
+//
+public interface Texture {
+ public int getWidth();
+ public int getHeight();
+ public void draw(GLCanvas canvas, int x, int y);
+ public void draw(GLCanvas canvas, int x, int y, int w, int h);
+ public boolean isOpaque();
+}
diff --git a/src/com/android/gallery3d/glrenderer/TextureUploader.java b/src/com/android/gallery3d/glrenderer/TextureUploader.java
new file mode 100644
index 000000000..f17ab845c
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/TextureUploader.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
+
+import java.util.ArrayDeque;
+
+public class TextureUploader implements OnGLIdleListener {
+ private static final int INIT_CAPACITY = 64;
+ private static final int QUOTA_PER_FRAME = 1;
+
+ private final ArrayDeque<UploadedTexture> mFgTextures =
+ new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+ private final ArrayDeque<UploadedTexture> mBgTextures =
+ new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+ private final GLRoot mGLRoot;
+ private volatile boolean mIsQueued = false;
+
+ public TextureUploader(GLRoot root) {
+ mGLRoot = root;
+ }
+
+ public synchronized void clear() {
+ while (!mFgTextures.isEmpty()) {
+ mFgTextures.pop().setIsUploading(false);
+ }
+ while (!mBgTextures.isEmpty()) {
+ mBgTextures.pop().setIsUploading(false);
+ }
+ }
+
+ // caller should hold synchronized on "this"
+ private void queueSelfIfNeed() {
+ if (mIsQueued) return;
+ mIsQueued = true;
+ mGLRoot.addOnGLIdleListener(this);
+ }
+
+ public synchronized void addBgTexture(UploadedTexture t) {
+ if (t.isContentValid()) return;
+ mBgTextures.addLast(t);
+ t.setIsUploading(true);
+ queueSelfIfNeed();
+ }
+
+ public synchronized void addFgTexture(UploadedTexture t) {
+ if (t.isContentValid()) return;
+ mFgTextures.addLast(t);
+ t.setIsUploading(true);
+ queueSelfIfNeed();
+ }
+
+ private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
+ int uploadQuota, boolean isBackground) {
+ while (uploadQuota > 0) {
+ UploadedTexture t;
+ synchronized (this) {
+ if (deque.isEmpty()) break;
+ t = deque.removeFirst();
+ t.setIsUploading(false);
+ if (t.isContentValid()) continue;
+
+ // this has to be protected by the synchronized block
+ // to prevent the inner bitmap get recycled
+ t.updateContent(canvas);
+ }
+
+ // It will took some more time for a texture to be drawn for
+ // the first time.
+ // Thus, when scrolling, if a new column appears on screen,
+ // it may cause a UI jank even these textures are uploaded.
+ if (isBackground) t.draw(canvas, 0, 0);
+ --uploadQuota;
+ }
+ return uploadQuota;
+ }
+
+ @Override
+ public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+ int uploadQuota = QUOTA_PER_FRAME;
+ uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
+ if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
+ upload(canvas, mBgTextures, uploadQuota, true);
+ synchronized (this) {
+ mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
+ return mIsQueued;
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/TiledTexture.java b/src/com/android/gallery3d/glrenderer/TiledTexture.java
new file mode 100644
index 000000000..6ca1de088
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/TiledTexture.java
@@ -0,0 +1,349 @@
+/*
+ * 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.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+import android.os.SystemClock;
+
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+// This class is similar to BitmapTexture, except the bitmap is
+// split into tiles. By doing so, we may increase the time required to
+// upload the whole bitmap but we reduce the time of uploading each tile
+// so it make the animation more smooth and prevents jank.
+public class TiledTexture implements Texture {
+ private static final int CONTENT_SIZE = 254;
+ private static final int BORDER_SIZE = 1;
+ private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
+ private static final int INIT_CAPACITY = 8;
+
+ // We are targeting at 60fps, so we have 16ms for each frame.
+ // In this 16ms, we use about 4~8 ms to upload tiles.
+ private static final long UPLOAD_TILE_LIMIT = 4; // ms
+
+ private static Tile sFreeTileHead = null;
+ private static final Object sFreeTileLock = new Object();
+
+ private static Bitmap sUploadBitmap;
+ private static Canvas sCanvas;
+ private static Paint sBitmapPaint;
+ private static Paint sPaint;
+
+ private int mUploadIndex = 0;
+
+ private final Tile[] mTiles; // Can be modified in different threads.
+ // Should be protected by "synchronized."
+ private final int mWidth;
+ private final int mHeight;
+ private final RectF mSrcRect = new RectF();
+ private final RectF mDestRect = new RectF();
+
+ public static class Uploader implements OnGLIdleListener {
+ private final ArrayDeque<TiledTexture> mTextures =
+ new ArrayDeque<TiledTexture>(INIT_CAPACITY);
+
+ private final GLRoot mGlRoot;
+ private boolean mIsQueued = false;
+
+ public Uploader(GLRoot glRoot) {
+ mGlRoot = glRoot;
+ }
+
+ public synchronized void clear() {
+ mTextures.clear();
+ }
+
+ public synchronized void addTexture(TiledTexture t) {
+ if (t.isReady()) return;
+ mTextures.addLast(t);
+
+ if (mIsQueued) return;
+ mIsQueued = true;
+ mGlRoot.addOnGLIdleListener(this);
+ }
+
+ @Override
+ public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+ ArrayDeque<TiledTexture> deque = mTextures;
+ synchronized (this) {
+ long now = SystemClock.uptimeMillis();
+ long dueTime = now + UPLOAD_TILE_LIMIT;
+ while (now < dueTime && !deque.isEmpty()) {
+ TiledTexture t = deque.peekFirst();
+ if (t.uploadNextTile(canvas)) {
+ deque.removeFirst();
+ mGlRoot.requestRender();
+ }
+ now = SystemClock.uptimeMillis();
+ }
+ mIsQueued = !mTextures.isEmpty();
+
+ // return true to keep this listener in the queue
+ return mIsQueued;
+ }
+ }
+ }
+
+ private static class Tile extends UploadedTexture {
+ public int offsetX;
+ public int offsetY;
+ public Bitmap bitmap;
+ public Tile nextFreeTile;
+ public int contentWidth;
+ public int contentHeight;
+
+ @Override
+ public void setSize(int width, int height) {
+ contentWidth = width;
+ contentHeight = height;
+ mWidth = width + 2 * BORDER_SIZE;
+ mHeight = height + 2 * BORDER_SIZE;
+ mTextureWidth = TILE_SIZE;
+ mTextureHeight = TILE_SIZE;
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ int x = BORDER_SIZE - offsetX;
+ int y = BORDER_SIZE - offsetY;
+ int r = bitmap.getWidth() + x;
+ int b = bitmap.getHeight() + y;
+ sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint);
+ bitmap = null;
+
+ // draw borders if need
+ if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
+ if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
+ if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
+ if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
+
+ return sUploadBitmap;
+ }
+
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ // do nothing
+ }
+ }
+
+ private static void freeTile(Tile tile) {
+ tile.invalidateContent();
+ tile.bitmap = null;
+ synchronized (sFreeTileLock) {
+ tile.nextFreeTile = sFreeTileHead;
+ sFreeTileHead = tile;
+ }
+ }
+
+ private static Tile obtainTile() {
+ synchronized (sFreeTileLock) {
+ Tile result = sFreeTileHead;
+ if (result == null) return new Tile();
+ sFreeTileHead = result.nextFreeTile;
+ result.nextFreeTile = null;
+ return result;
+ }
+ }
+
+ private boolean uploadNextTile(GLCanvas canvas) {
+ if (mUploadIndex == mTiles.length) return true;
+
+ synchronized (mTiles) {
+ Tile next = mTiles[mUploadIndex++];
+
+ // Make sure tile has not already been recycled by the time
+ // this is called (race condition in onGLIdle)
+ if (next.bitmap != null) {
+ boolean hasBeenLoad = next.isLoaded();
+ next.updateContent(canvas);
+
+ // It will take some time for a texture to be drawn for the first
+ // time. When scrolling, we need to draw several tiles on the screen
+ // at the same time. It may cause a UI jank even these textures has
+ // been uploaded.
+ if (!hasBeenLoad) next.draw(canvas, 0, 0);
+ }
+ }
+ return mUploadIndex == mTiles.length;
+ }
+
+ public TiledTexture(Bitmap bitmap) {
+ mWidth = bitmap.getWidth();
+ mHeight = bitmap.getHeight();
+ ArrayList<Tile> list = new ArrayList<Tile>();
+
+ for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
+ for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
+ Tile tile = obtainTile();
+ tile.offsetX = x;
+ tile.offsetY = y;
+ tile.bitmap = bitmap;
+ tile.setSize(
+ Math.min(CONTENT_SIZE, mWidth - x),
+ Math.min(CONTENT_SIZE, mHeight - y));
+ list.add(tile);
+ }
+ }
+ mTiles = list.toArray(new Tile[list.size()]);
+ }
+
+ public boolean isReady() {
+ return mUploadIndex == mTiles.length;
+ }
+
+ // Can be called in UI thread.
+ public void recycle() {
+ synchronized (mTiles) {
+ for (int i = 0, n = mTiles.length; i < n; ++i) {
+ freeTile(mTiles[i]);
+ }
+ }
+ }
+
+ public static void freeResources() {
+ sUploadBitmap = null;
+ sCanvas = null;
+ sBitmapPaint = null;
+ sPaint = null;
+ }
+
+ public static void prepareResources() {
+ sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
+ sCanvas = new Canvas(sUploadBitmap);
+ sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
+ sPaint = new Paint();
+ sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
+ sPaint.setColor(Color.TRANSPARENT);
+ }
+
+ // We want to draw the "source" on the "target".
+ // This method is to find the "output" rectangle which is
+ // the corresponding area of the "src".
+ // (x,y) target
+ // (x0,y0) source +---------------+
+ // +----------+ | |
+ // | src | | output |
+ // | +--+ | linear map | +----+ |
+ // | +--+ | ----------> | | | |
+ // | | by (scaleX, scaleY) | +----+ |
+ // +----------+ | |
+ // Texture +---------------+
+ // Canvas
+ private static void mapRect(RectF output,
+ RectF src, float x0, float y0, float x, float y, float scaleX,
+ float scaleY) {
+ output.set(x + (src.left - x0) * scaleX,
+ y + (src.top - y0) * scaleY,
+ x + (src.right - x0) * scaleX,
+ y + (src.bottom - y0) * scaleY);
+ }
+
+ // Draws a mixed color of this texture and a specified color onto the
+ // a rectangle. The used color is: from * (1 - ratio) + to * ratio.
+ public void drawMixed(GLCanvas canvas, int color, float ratio,
+ int x, int y, int width, int height) {
+ RectF src = mSrcRect;
+ RectF dest = mDestRect;
+ float scaleX = (float) width / mWidth;
+ float scaleY = (float) height / mHeight;
+ synchronized (mTiles) {
+ for (int i = 0, n = mTiles.length; i < n; ++i) {
+ Tile t = mTiles[i];
+ src.set(0, 0, t.contentWidth, t.contentHeight);
+ src.offset(t.offsetX, t.offsetY);
+ mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
+ src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+ canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
+ }
+ }
+ }
+
+ // Draws the texture on to the specified rectangle.
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int width, int height) {
+ RectF src = mSrcRect;
+ RectF dest = mDestRect;
+ float scaleX = (float) width / mWidth;
+ float scaleY = (float) height / mHeight;
+ synchronized (mTiles) {
+ for (int i = 0, n = mTiles.length; i < n; ++i) {
+ Tile t = mTiles[i];
+ src.set(0, 0, t.contentWidth, t.contentHeight);
+ src.offset(t.offsetX, t.offsetY);
+ mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
+ src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+ canvas.drawTexture(t, mSrcRect, mDestRect);
+ }
+ }
+ }
+
+ // Draws a sub region of this texture on to the specified rectangle.
+ public void draw(GLCanvas canvas, RectF source, RectF target) {
+ RectF src = mSrcRect;
+ RectF dest = mDestRect;
+ float x0 = source.left;
+ float y0 = source.top;
+ float x = target.left;
+ float y = target.top;
+ float scaleX = target.width() / source.width();
+ float scaleY = target.height() / source.height();
+
+ synchronized (mTiles) {
+ for (int i = 0, n = mTiles.length; i < n; ++i) {
+ Tile t = mTiles[i];
+ src.set(0, 0, t.contentWidth, t.contentHeight);
+ src.offset(t.offsetX, t.offsetY);
+ if (!src.intersect(source)) continue;
+ mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
+ src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+ canvas.drawTexture(t, src, dest);
+ }
+ }
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y) {
+ draw(canvas, x, y, mWidth, mHeight);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return false;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
new file mode 100644
index 000000000..f41a979b7
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -0,0 +1,298 @@
+/*
+ * 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.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.opengl.GLUtils;
+
+import junit.framework.Assert;
+
+import java.util.HashMap;
+
+import javax.microedition.khronos.opengles.GL11;
+
+// UploadedTextures use a Bitmap for the content of the texture.
+//
+// Subclasses should implement onGetBitmap() to provide the Bitmap and
+// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
+// is not needed anymore.
+//
+// isContentValid() is meaningful only when the isLoaded() returns true.
+// It means whether the content needs to be updated.
+//
+// The user of this class should call recycle() when the texture is not
+// needed anymore.
+//
+// By default an UploadedTexture is opaque (so it can be drawn faster without
+// blending). The user or subclass can override it using setOpaque().
+public abstract class UploadedTexture extends BasicTexture {
+
+ // To prevent keeping allocation the borders, we store those used borders here.
+ // Since the length will be power of two, it won't use too much memory.
+ private static HashMap<BorderKey, Bitmap> sBorderLines =
+ new HashMap<BorderKey, Bitmap>();
+ private static BorderKey sBorderKey = new BorderKey();
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "Texture";
+ private boolean mContentValid = true;
+
+ // indicate this textures is being uploaded in background
+ private boolean mIsUploading = false;
+ private boolean mOpaque = true;
+ private boolean mThrottled = false;
+ private static int sUploadedCount;
+ private static final int UPLOAD_LIMIT = 100;
+
+ protected Bitmap mBitmap;
+ private int mBorder;
+
+ protected UploadedTexture() {
+ this(false);
+ }
+
+ protected UploadedTexture(boolean hasBorder) {
+ super(null, 0, STATE_UNLOADED);
+ if (hasBorder) {
+ setBorder(true);
+ mBorder = 1;
+ }
+ }
+
+ protected void setIsUploading(boolean uploading) {
+ mIsUploading = uploading;
+ }
+
+ public boolean isUploading() {
+ return mIsUploading;
+ }
+
+ private static class BorderKey implements Cloneable {
+ public boolean vertical;
+ public Config config;
+ public int length;
+
+ @Override
+ public int hashCode() {
+ int x = config.hashCode() ^ length;
+ return vertical ? x : -x;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof BorderKey)) return false;
+ BorderKey o = (BorderKey) object;
+ return vertical == o.vertical
+ && config == o.config && length == o.length;
+ }
+
+ @Override
+ public BorderKey clone() {
+ try {
+ return (BorderKey) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ protected void setThrottled(boolean throttled) {
+ mThrottled = throttled;
+ }
+
+ private static Bitmap getBorderLine(
+ boolean vertical, Config config, int length) {
+ BorderKey key = sBorderKey;
+ key.vertical = vertical;
+ key.config = config;
+ key.length = length;
+ Bitmap bitmap = sBorderLines.get(key);
+ if (bitmap == null) {
+ bitmap = vertical
+ ? Bitmap.createBitmap(1, length, config)
+ : Bitmap.createBitmap(length, 1, config);
+ sBorderLines.put(key.clone(), bitmap);
+ }
+ return bitmap;
+ }
+
+ private Bitmap getBitmap() {
+ if (mBitmap == null) {
+ mBitmap = onGetBitmap();
+ int w = mBitmap.getWidth() + mBorder * 2;
+ int h = mBitmap.getHeight() + mBorder * 2;
+ if (mWidth == UNSPECIFIED) {
+ setSize(w, h);
+ }
+ }
+ return mBitmap;
+ }
+
+ private void freeBitmap() {
+ Assert.assertTrue(mBitmap != null);
+ onFreeBitmap(mBitmap);
+ mBitmap = null;
+ }
+
+ @Override
+ public int getWidth() {
+ if (mWidth == UNSPECIFIED) getBitmap();
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ if (mWidth == UNSPECIFIED) getBitmap();
+ return mHeight;
+ }
+
+ protected abstract Bitmap onGetBitmap();
+
+ protected abstract void onFreeBitmap(Bitmap bitmap);
+
+ protected void invalidateContent() {
+ if (mBitmap != null) freeBitmap();
+ mContentValid = false;
+ mWidth = UNSPECIFIED;
+ mHeight = UNSPECIFIED;
+ }
+
+ /**
+ * Whether the content on GPU is valid.
+ */
+ public boolean isContentValid() {
+ return isLoaded() && mContentValid;
+ }
+
+ /**
+ * Updates the content on GPU's memory.
+ * @param canvas
+ */
+ public void updateContent(GLCanvas canvas) {
+ if (!isLoaded()) {
+ if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
+ return;
+ }
+ uploadToCanvas(canvas);
+ } else if (!mContentValid) {
+ Bitmap bitmap = getBitmap();
+ int format = GLUtils.getInternalFormat(bitmap);
+ int type = GLUtils.getType(bitmap);
+ canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
+ freeBitmap();
+ mContentValid = true;
+ }
+ }
+
+ public static void resetUploadLimit() {
+ sUploadedCount = 0;
+ }
+
+ public static boolean uploadLimitReached() {
+ return sUploadedCount > UPLOAD_LIMIT;
+ }
+
+ private void uploadToCanvas(GLCanvas canvas) {
+
+ Bitmap bitmap = getBitmap();
+ if (bitmap != null) {
+ try {
+ int bWidth = bitmap.getWidth();
+ int bHeight = bitmap.getHeight();
+ int width = bWidth + mBorder * 2;
+ int height = bHeight + mBorder * 2;
+ int texWidth = getTextureWidth();
+ int texHeight = getTextureHeight();
+
+ Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
+
+ // Upload the bitmap to a new texture.
+ mId = canvas.getGLId().generateTexture();
+ canvas.setTextureParameters(this);
+
+ if (bWidth == texWidth && bHeight == texHeight) {
+ canvas.initializeTexture(this, bitmap);
+ } else {
+ int format = GLUtils.getInternalFormat(bitmap);
+ int type = GLUtils.getType(bitmap);
+ Config config = bitmap.getConfig();
+
+ canvas.initializeTextureSize(this, format, type);
+ canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
+
+ if (mBorder > 0) {
+ // Left border
+ Bitmap line = getBorderLine(true, config, texHeight);
+ canvas.texSubImage2D(this, 0, 0, line, format, type);
+
+ // Top border
+ line = getBorderLine(false, config, texWidth);
+ canvas.texSubImage2D(this, 0, 0, line, format, type);
+ }
+
+ // Right border
+ if (mBorder + bWidth < texWidth) {
+ Bitmap line = getBorderLine(true, config, texHeight);
+ canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
+ }
+
+ // Bottom border
+ if (mBorder + bHeight < texHeight) {
+ Bitmap line = getBorderLine(false, config, texWidth);
+ canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
+ }
+ }
+ } finally {
+ freeBitmap();
+ }
+ // Update texture state.
+ setAssociatedCanvas(canvas);
+ mState = STATE_LOADED;
+ mContentValid = true;
+ } else {
+ mState = STATE_ERROR;
+ throw new RuntimeException("Texture load fail, no bitmap");
+ }
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ updateContent(canvas);
+ return isContentValid();
+ }
+
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+
+ public void setOpaque(boolean isOpaque) {
+ mOpaque = isOpaque;
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return mOpaque;
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ if (mBitmap != null) freeBitmap();
+ }
+}