package com.glview.hwui.cache;
import javax.microedition.khronos.opengles.GL11;
import android.opengl.GLUtils;
import android.os.Build;
import android.support.v4.util.LruCache;
import android.util.Log;
import com.glview.App;
import com.glview.content.GLContext;
import com.glview.graphics.Bitmap;
import com.glview.hwui.Caches;
import com.glview.hwui.GLId;
import com.glview.hwui.Texture;
import com.glview.libgdx.graphics.opengl.GL;
import com.glview.libgdx.graphics.opengl.GL20;
public class TextureCache {
final static String TAG = "TextureCache";
final static int MB = 1024 * 1024;
final static int DEFAULT_TEXTURE_CACHE_SIZE = 24 * MB; //24MB
final static int LARGE_TEXTURE_CACHE_SIZE = 32 * MB; //32MB
final static int LARGER_TEXTURE_CACHE_SIZE = 64 * MB; //64MB
final static float DEFAULT_TEXTURE_CACHE_FLUSH_RATE = 0.6f;
TextureLruCache mCache;
int[] mBuffer = new int[1];
float mFlushRate;
int mSize;
public TextureCache() {
final boolean isLargeHeap = GLContext.get().isLargeHeap();
mSize = DEFAULT_TEXTURE_CACHE_SIZE;
final int memoryClass = isLargeHeap ? GLContext.get().getLargeMemoryClass() : GLContext.get().getMemoryClass();
if (memoryClass <= 48) {
mSize = memoryClass / 2 * MB;
} else if (memoryClass >= 256) {
mSize = LARGER_TEXTURE_CACHE_SIZE;
} else if (memoryClass >= 128) {
mSize = LARGE_TEXTURE_CACHE_SIZE;
}
Log.d(TAG, "TextureCache size=" + mSize);
mFlushRate = DEFAULT_TEXTURE_CACHE_FLUSH_RATE;
mCache = new TextureLruCache(mSize);
}
public Texture get(Bitmap bitmap) {
if (bitmap == null) return null;
try {
Texture texture = mCache.get(bitmap);
boolean sizeChanged = false;
if (texture == null) {
if (!canMakeTextureFromBitmap(bitmap)) {
return null;
}
texture = create(bitmap);
if (!generateTexture(bitmap, texture, false)) {
return null;
}
sizeChanged = true;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && texture.mGenerationId != bitmap.getGenerationId()) {
if (bitmap.getRowBytes() * bitmap.getHeight() != texture.getByteCount()) {
sizeChanged = true;
mCache.remove(bitmap);
}
if (!generateTexture(bitmap, texture, true)) {
mCache.remove(bitmap);
return null;
}
}
if (sizeChanged) {
if (mCache.maxSize() < texture.getByteCount()) {
mCache.resize(texture.getByteCount());
} else if (mCache.maxSize() > mSize) {
mCache.resize(mSize);
}
mCache.put(bitmap, texture);
}
return texture;
} catch (Throwable throwable) {
Log.w(TAG, "getTexture fail", throwable);
}
return null;
}
boolean canMakeTextureFromBitmap(Bitmap bitmap) {
if (bitmap.isRecycled() || bitmap.getBitmap() == null) {
return false;
}
return true;
}
Texture create(Bitmap bitmap) {
Texture texture = new Texture();
texture.setWidth(bitmap.getWidth());
texture.setHeight(bitmap.getHeight());
android.graphics.Bitmap aBitmap = bitmap.getBitmap();
if (aBitmap != null && !aBitmap.isRecycled()) {
int format = GLUtils.getInternalFormat(aBitmap);
int type = GLUtils.getType(aBitmap);
texture.setFormat(format);
texture.setType(type);
}
return texture;
}
void deleteTexture(Texture texture) {
Caches.getInstance().deleteTexture(texture);
}
boolean hasNpot() {
return Caches.getInstance().extensions.hasNPot();
}
public void generateTexture(Texture texture) {
final GL gl = App.getGL();
boolean regenerate = false;
if (texture.mId > 0) {
regenerate = true;
} else {
GLId.glGenTextures(1, mBuffer, 0);
texture.mId = mBuffer[0];
}
Caches.getInstance().bindTexture(texture);
gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, texture.getFormat(),
texture.getWidth(), texture.getHeight(),
0, texture.getFormat(), texture.getType(), null);
if (!regenerate) {
gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S,
GL20.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T,
GL20.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL20.GL_TEXTURE_2D,
GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_LINEAR);
gl.glTexParameterf(GL20.GL_TEXTURE_2D,
GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_LINEAR);
}
}
boolean generateTexture(Bitmap bitmap, Texture texture, boolean regenerate) {
android.graphics.Bitmap aBitmap = bitmap.getBitmap();
if (aBitmap == null || aBitmap.isRecycled()) {
return false;
}
final boolean canMipMap = hasNpot();
final boolean resize = !regenerate
|| bitmap.getWidth() != texture.getWidth()
|| bitmap.getHeight() != texture.getHeight()
|| (regenerate && canMipMap && texture.isMipMap() && bitmap.hasMipMap());
if (!regenerate) {
if (texture.mId > 0) {
deleteTexture(texture);
}
GLId.glGenTextures(1, mBuffer, 0);
texture.mId = mBuffer[0];
}
texture.mGenerationId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 ? bitmap.getGenerationId() : 0;
texture.setWidth(bitmap.getWidth());
texture.setHeight(bitmap.getHeight());
texture.setByteCount(bitmap.getRowBytes() * bitmap.getHeight());
Caches.getInstance().bindTexture(texture);
GL gl = App.getGL();
if (resize) {
GLUtils.texImage2D(GL20.GL_TEXTURE_2D, 0, texture.getFormat(), aBitmap, texture.getType(), 0);
} else {
GLUtils.texSubImage2D(GL20.GL_TEXTURE_2D, 0, 0, 0, aBitmap, texture.getFormat(), texture.getType());
}
if (canMipMap) {
texture.setMipMap(bitmap.hasMipMap());
if (texture.isMipMap() && (gl instanceof GL20)) {
((GL20) gl).glGenerateMipmap(GL20.GL_TEXTURE_2D);
}
}
if (!regenerate) {
gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S,
GL20.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T,
GL20.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL20.GL_TEXTURE_2D,
GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_LINEAR);
gl.glTexParameterf(GL20.GL_TEXTURE_2D,
GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_LINEAR);
}
bitmap.freeBitmap();
return true;
}
public void clear() {
mCache.evictAll();
}
public void flush() {
if (mFlushRate > 1f || mCache.size() == 0) {
return;
}
if (mFlushRate <= 0.0f) {
clear();
return;
}
int targetSize = (int) (mSize * mFlushRate);
if (mCache.size() > targetSize) {
mCache.trimToSize(targetSize);
}
}
int getEntrySize(Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
class TextureLruCache extends LruCache<Object, Texture> {
public TextureLruCache(int maxSize) {
super(maxSize);
}
@Override
protected int sizeOf(Object key, Texture value) {
return value.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, Object bitmap,
Texture oldValue, Texture newValue) {
if (oldValue != newValue) {
deleteTexture(oldValue);
}
}
@Override
protected Texture create(Object bitmap) {
return null;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
evictAll();
}
}
}