aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJorge Ruesga <jorge@ruesga.com>2014-09-10 23:52:05 +0200
committerJorge Ruesga <jorge@ruesga.com>2014-09-14 10:52:53 +0200
commitfaa982e7beda54bf28e0dbf1488428093bbbf485 (patch)
treefe23f0f6c899f2b93da12c789a529564054a107e
parent94c617a88428b64dfd486f4a3ed28547e09ef9c8 (diff)
downloadandroid_packages_wallpapers_PhotoPhase-faa982e7beda54bf28e0dbf1488428093bbbf485.tar.gz
android_packages_wallpapers_PhotoPhase-faa982e7beda54bf28e0dbf1488428093bbbf485.tar.bz2
android_packages_wallpapers_PhotoPhase-faa982e7beda54bf28e0dbf1488428093bbbf485.zip
photophase: use etc1 compression when available
When avaliable this help to decrease the memory footprint. ETC1 compression requieres to: - be supported by opengl - picture couldn't have an alpha channel - compression times shouldn't be too higher (< 1000 ms) - initial pictures doesn't be compressed (to speed up boot) Change-Id: I87e41db3ca7f2ccb82d4af2763609f11d7e67121 Signed-off-by: Jorge Ruesga <jorge@ruesga.com>
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/TextureManager.java20
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffect.java8
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/shapes/OopsShape.java5
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/utils/BitmapUtils.java3
-rw-r--r--src/org/cyanogenmod/wallpapers/photophase/utils/GLESUtil.java115
5 files changed, 124 insertions, 27 deletions
diff --git a/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java b/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java
index 417cfb9..65987c3 100644
--- a/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java
+++ b/src/org/cyanogenmod/wallpapers/photophase/TextureManager.java
@@ -89,22 +89,27 @@ public class TextureManager implements OnMediaPictureDiscoveredListener {
effect = mEffects.getNextEffect();
}
+ boolean enqueue = false;
+ synchronized (mSync) {
+ enqueue = mPendingRequests.size() == 0;
+ }
+
// Load and bind to the GLES context. The effect is applied when the image
// is associated to the destination target (only if aspect ratio will be applied)
if (!Preferences.General.isFixAspectRatio()) {
ti = GLESUtil.loadTexture(
- mImage, mDimensions, effect, mDimensions, false);
+ mImage, mDimensions, effect, mDimensions, false, enqueue);
} else {
- ti = GLESUtil.loadTexture(mImage, mDimensions, null, null, false);
+ ti = GLESUtil.loadTexture(mImage, mDimensions, null, null, false, enqueue);
ti.effect = effect;
}
synchronized (mSync) {
// Notify the new images to all pending frames
- if (mPendingRequests.size() > 0) {
+ if (!enqueue) {
// Invalid textures are also reported, so requestor can handle it
TextureRequestor requestor = mPendingRequests.remove(0);
- fixAspectRatio(requestor, ti);
+ fixAspectRatio(requestor, ti, false);
requestor.setTextureHandle(ti);
// Clean up memory
@@ -247,7 +252,7 @@ public class TextureManager implements OnMediaPictureDiscoveredListener {
synchronized (mSync) {
try {
GLESTextureInfo ti = mQueue.remove();
- fixAspectRatio(requestor, ti);
+ fixAspectRatio(requestor, ti, true);
requestor.setTextureHandle(ti);
// Clean up memory
@@ -431,8 +436,9 @@ public class TextureManager implements OnMediaPictureDiscoveredListener {
* @param request The requestor target
* @param ti The original texture information
* @param effect The effect to apply to the destination picture
+ * @param compress Compress the texture
*/
- void fixAspectRatio(TextureRequestor requestor, GLESTextureInfo ti) {
+ void fixAspectRatio(TextureRequestor requestor, GLESTextureInfo ti, boolean compress) {
// Check if we have to apply any correction to the image
if (Preferences.General.isFixAspectRatio()) {
// Transform requestor dimensions to screen dimensions
@@ -449,7 +455,7 @@ public class TextureManager implements OnMediaPictureDiscoveredListener {
pixels.width(),
pixels.height(),
ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
- GLESTextureInfo dst = GLESUtil.loadTexture(thumb, ti.effect, pixels);
+ GLESTextureInfo dst = GLESUtil.loadTexture(thumb, ti.effect, pixels, compress);
// Destroy references
int[] textures = new int[]{ti.handle};
diff --git a/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffect.java b/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffect.java
index 0900322..bbfb2bc 100644
--- a/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffect.java
+++ b/src/org/cyanogenmod/wallpapers/photophase/effects/PhotoPhaseEffect.java
@@ -158,12 +158,14 @@ public abstract class PhotoPhaseEffect extends Effect {
mIdentityEffect.apply(inputTexId, width, height, outputTexId);
// Create the framebuffer
- GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20. GL_TEXTURE_2D, outputTexId, 0);
+ GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
+ GLES20. GL_TEXTURE_2D, outputTexId, 0);
GLESUtil.glesCheckError("glFramebufferTexture2D");
// Check if the buffer was built successfully
- if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
- // Something when wrong. Throw an exception
+ final int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
+ if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
+ // Something was wrong. Throw an exception
GLESUtil.glesCheckError("glCheckFramebufferStatus");
int error = GLES20.glGetError();
throw new android.opengl.GLException(error, GLUtils.getEGLErrorString(error));
diff --git a/src/org/cyanogenmod/wallpapers/photophase/shapes/OopsShape.java b/src/org/cyanogenmod/wallpapers/photophase/shapes/OopsShape.java
index 195a1e4..5ef4bd9 100644
--- a/src/org/cyanogenmod/wallpapers/photophase/shapes/OopsShape.java
+++ b/src/org/cyanogenmod/wallpapers/photophase/shapes/OopsShape.java
@@ -142,9 +142,10 @@ public class OopsShape implements DrawableShape {
mMessage = ctx.getString(resourceMessageId);
// Load the textures
- mOopsImageTexture = GLESUtil.loadTexture(ctx, R.drawable.bg_cid_oops, null, null, false);
+ mOopsImageTexture = GLESUtil.loadTexture(ctx, R.drawable.bg_cid_oops, null,
+ null, false, false);
Bitmap textBitmap = text2Bitmap(ctx, mMessage);
- mOopsTextTexture = GLESUtil.loadTexture(textBitmap, null, null);
+ mOopsTextTexture = GLESUtil.loadTexture(textBitmap, null, null, false);
// Recycle
mOopsImageTexture.bitmap.recycle();
diff --git a/src/org/cyanogenmod/wallpapers/photophase/utils/BitmapUtils.java b/src/org/cyanogenmod/wallpapers/photophase/utils/BitmapUtils.java
index bbd3dec..ebfb4f6 100644
--- a/src/org/cyanogenmod/wallpapers/photophase/utils/BitmapUtils.java
+++ b/src/org/cyanogenmod/wallpapers/photophase/utils/BitmapUtils.java
@@ -41,6 +41,7 @@ public class BitmapUtils {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferQualityOverSpeed = false;
options.inPreferredConfig = Bitmap.Config.RGB_565;
+ options.inDither = false;
return BitmapFactory.decodeStream(bitmap, null, options);
}
@@ -56,6 +57,7 @@ public class BitmapUtils {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
+ options.inPreferredConfig = Bitmap.Config.RGB_565;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
// Calculate inSampleSize (use 1024 as maximum size, the minimum supported
@@ -70,7 +72,6 @@ public class BitmapUtils {
options.inPreferQualityOverSpeed = false;
options.inPurgeable = true;
options.inInputShareable = true;
- options.inDither = true;
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
if (bitmap == null) {
return null;
diff --git a/src/org/cyanogenmod/wallpapers/photophase/utils/GLESUtil.java b/src/org/cyanogenmod/wallpapers/photophase/utils/GLESUtil.java
index 8b34601..1d7c8bc 100644
--- a/src/org/cyanogenmod/wallpapers/photophase/utils/GLESUtil.java
+++ b/src/org/cyanogenmod/wallpapers/photophase/utils/GLESUtil.java
@@ -22,6 +22,9 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.media.effect.Effect;
+import android.opengl.ETC1Util;
+import android.opengl.ETC1Util.ETC1Texture;
+import android.opengl.ETC1;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Log;
@@ -31,6 +34,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
/**
@@ -46,7 +52,10 @@ public final class GLESUtil {
public static final String DEBUG_GL_MEMOBJS_NEW_TAG = "MEMOBJS_NEW";
public static final String DEBUG_GL_MEMOBJS_DEL_TAG = "MEMOBJS_DEL";
- private static final Object sSync = new Object();
+ private static final Object SYNC = new Object();
+
+ private static final int MAX_ACEPTABLE_COMPRESSION_TIME = 1000;
+ private static boolean sDisabledTextureCompression = false;
/**
* A helper class to deal with OpenGL float colors.
@@ -330,10 +339,11 @@ public final class GLESUtil {
* @param effect The effect to apply to the image or null if no effect is needed
* @param dimen The new dimensions
* @param recycle If the bitmap should be recycled
+ * @param compress Try to compress the bitmap into ETC1
* @return GLESTextureInfo The texture info
*/
- public static GLESTextureInfo loadTexture(
- File file, Rect dimensions, Effect effect, Rect dimen, boolean recycle) {
+ public static GLESTextureInfo loadTexture(File file, Rect dimensions, Effect effect,
+ Rect dimen, boolean recycle, boolean compress) {
Bitmap bitmap = null;
try {
// Decode and associate the bitmap (invert the desired dimensions)
@@ -344,7 +354,7 @@ public final class GLESUtil {
}
if (DEBUG) Log.d(TAG, "image: " + file.getAbsolutePath());
- GLESTextureInfo ti = loadTexture(bitmap, effect, dimen);
+ GLESTextureInfo ti = loadTexture(bitmap, effect, dimen, compress);
ti.path = file;
return ti;
@@ -370,10 +380,11 @@ public final class GLESUtil {
* @param effect The effect to apply to the image or null if no effect is needed
* @param dimen The new dimensions
* @param recycle If the bitmap should be recycled
+ * @param compress Try to compress the bitmap into ETC1
* @return GLESTextureInfo The texture info
*/
- public static GLESTextureInfo loadTexture(
- Context ctx, int resourceId, Effect effect, Rect dimen, boolean recycle) {
+ public static GLESTextureInfo loadTexture(Context ctx, int resourceId, Effect effect,
+ Rect dimen, boolean recycle, boolean compress) {
Bitmap bitmap = null;
InputStream raw = null;
try {
@@ -387,7 +398,7 @@ public final class GLESUtil {
}
if (DEBUG) Log.d(TAG, "resourceId: " + resourceId);
- GLESTextureInfo ti = loadTexture(bitmap, effect, dimen);
+ GLESTextureInfo ti = loadTexture(bitmap, effect, dimen, compress);
return ti;
} catch (Exception e) {
@@ -418,9 +429,11 @@ public final class GLESUtil {
* @param bitmap The bitmap reference
* @param effect The effect to apply to the image or null if no effect is needed
* @param dimen The new dimensions
+ * @param compress Try to compress the bitmap into ETC1
* @return GLESTextureInfo The texture info
*/
- public static GLESTextureInfo loadTexture(Bitmap bitmap, Effect effect, Rect dimen) {
+ public static GLESTextureInfo loadTexture(Bitmap bitmap, Effect effect, Rect dimen,
+ boolean compress) {
// Check that we have a valid image name reference
if (bitmap == null) {
return new GLESTextureInfo();
@@ -455,18 +468,59 @@ public final class GLESUtil {
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLESUtil.glesCheckError("glTexParameteri");
- // Load the texture
- GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
- if (!GLES20.glIsTexture(textureHandles[0])) {
- Log.e(TAG, "Failed to load a valid texture");
- return new GLESTextureInfo();
+ // Load the texture. Check weather the device supports ETC1 compressed format
+ // AOSP ETC1 implementation doesn't support alpha channel
+ boolean fallback = true;
+ int bytesPerPixel = bitmap.getRowBytes() / bitmap.getWidth();
+ boolean hasAlpha = bitmap.hasAlpha() || (bytesPerPixel < 2 || bytesPerPixel > 3);
+ if (!sDisabledTextureCompression && compress && ETC1Util.isETC1Supported() && !hasAlpha) {
+ // Compress the texture
+ long start = System.currentTimeMillis();
+ ETC1Texture texture = createETC1CompressedTextureFromBitmap(bitmap);
+ if (texture != null) {
+ try {
+ long time = System.currentTimeMillis() - start;
+ if (DEBUG) {
+ Log.d(TAG, "Compression time: " + time + " ms");
+ }
+ if (time > MAX_ACEPTABLE_COMPRESSION_TIME) {
+ sDisabledTextureCompression = true;
+ Log.e(TAG, "Excessive compression time (" + time + " ms). " +
+ "Disabling compression");
+ }
+
+ // Load the compressed texture
+ int width = texture.getWidth();
+ int height = texture.getHeight();
+ Buffer data = texture.getData();
+ int imageSize = data.remaining();
+ GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D, 0, ETC1.ETC1_RGB8_OES,
+ width, height, 0, imageSize, data);
+ GLESUtil.glesCheckError("glCompressedTexImage2D");
+ fallback = !GLES20.glIsTexture(textureHandles[0]);
+ } finally {
+ texture = null;
+ }
+ }
+ }
+ if (fallback) {
+ if (DEBUG) {
+ Log.d(TAG, "Fallback to uncompressed texture.");
+ }
+
+ // Fallback to uncompressed texture
+ GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
+ if (!GLES20.glIsTexture(textureHandles[0])) {
+ Log.e(TAG, "Failed to load a valid texture");
+ return new GLESTextureInfo();
+ }
}
// Has a effect?
int handle = textureHandles[0];
if (effect != null) {
// Apply the effect (we need a thread-safe call here)
- synchronized (sSync) {
+ synchronized (SYNC) {
// No more than 1024 (the minimum supported by all the gles20 devices)
int w = Math.min(dimen.width(), 1024);
int h = Math.min(dimen.width(), 1024);
@@ -553,4 +607,37 @@ public final class GLESUtil {
}
}
+ /**
+ * Method that compress a uncompressed bitmap to an compressed ETC1 texture
+ *
+ * @param bitmap The uncompressed bitmap
+ * @return ETC1Texture The ETC1 compressed texture
+ */
+ private static ETC1Texture createETC1CompressedTextureFromBitmap(Bitmap bitmap) {
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+ int dataSize = bitmap.getRowBytes() * height;
+ int bytesPerPixel = bitmap.getRowBytes() / width;
+ int stride = bitmap.getRowBytes();
+
+ ByteBuffer dataBuffer = ByteBuffer.allocateDirect(dataSize).order(ByteOrder.nativeOrder());
+ try {
+ bitmap.copyPixelsToBuffer(dataBuffer);
+ dataBuffer.position(0);
+
+ int encodedImageSize = ETC1.getEncodedDataSize(width, height);
+ ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize).
+ order(ByteOrder.nativeOrder());
+ try {
+ ETC1.encodeImage(dataBuffer, width, height, bytesPerPixel, stride, compressedImage);
+ return new ETC1Util.ETC1Texture(width, height, compressedImage);
+ } catch (IllegalArgumentException ex) {
+ compressedImage = null;
+ }
+ } finally {
+ dataBuffer = null;
+ }
+ return null;
+ }
+
}