/*
* Copyright 2012, 2013 Hannes Janetzek
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer.bucket;
import static org.oscim.backend.GLAdapter.gl;
import java.util.ArrayList;
import javax.annotation.CheckReturnValue;
import org.oscim.backend.CanvasAdapter;
import org.oscim.backend.GL;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.backend.canvas.Color;
import org.oscim.renderer.GLState;
import org.oscim.renderer.GLUtils;
import org.oscim.utils.pool.Inlist;
import org.oscim.utils.pool.SyncPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TextureItem extends Inlist<TextureItem> {
static final Logger log = LoggerFactory.getLogger(TextureItem.class);
static final boolean dbg = false;
/** texture ID */
private int id;
/** current settings */
public final int width;
public final int height;
public final boolean repeat;
/** vertex offset from which this texture is referenced */
/* FIXME dont put this here! */
public int offset;
public int indices;
/** temporary Bitmap */
public Bitmap bitmap;
/** do not release the texture when TextureItem is released. */
private TextureItem ref;
private int used = 0;
/** texture data is ready */
boolean loaded;
final TexturePool pool;
private TextureItem(TexturePool pool, int id) {
this(pool, id, pool.mWidth, pool.mHeight, false);
}
public TextureItem(Bitmap bitmap) {
this(bitmap, false);
}
public TextureItem(Bitmap bitmap, boolean repeat) {
this(NOPOOL, -1, bitmap.getWidth(), bitmap.getHeight(), repeat);
this.bitmap = bitmap;
}
private TextureItem(TexturePool pool, int id, int width, int height, boolean repeat) {
this.id = id;
this.width = width;
this.height = height;
this.pool = pool;
this.repeat = repeat;
}
public static TextureItem clone(TextureItem ti) {
TextureItem clone = new TextureItem(NOPOOL, ti.id, ti.width, ti.height, ti.repeat);
clone.id = ti.id;
clone.ref = (ti.ref == null) ? ti : ti.ref;
clone.loaded = ti.loaded;
clone.ref.used++;
return clone;
}
/**
* Upload Image to Texture
* [on GL-Thread]
*/
public void upload() {
if (loaded)
return;
if (ref == null) {
pool.uploadTexture(this);
} else {
/* load referenced texture */
ref.upload();
id = ref.id;
}
loaded = true;
}
/**
* Bind Texture for rendering
* [on GL-Thread]
*/
public void bind() {
if (loaded)
GLState.bindTex2D(id);
else
upload();
}
/**
* Dispose TextureItem
* [Threadsafe]
*
* @return this.next
*/
@CheckReturnValue
public TextureItem dispose() {
TextureItem n = this.next;
this.next = null;
pool.release(this);
return n;
}
public static class TexturePool extends SyncPool<TextureItem> {
private final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(10);
private final int mHeight;
private final int mWidth;
private final boolean mUseBitmapPool;
//private final int mBitmapFormat;
//private final int mBitmapType;
protected int mTexCnt = 0;
public TexturePool(int maxFill, int width, int height) {
super(maxFill);
mWidth = width;
mHeight = height;
mUseBitmapPool = true;
}
public TexturePool(int maxFill) {
super(maxFill);
mWidth = 0;
mHeight = 0;
mUseBitmapPool = false;
}
@Override
public TextureItem releaseAll(TextureItem t) {
throw new RuntimeException("use TextureItem.dispose()");
}
/**
* Retrieve a TextureItem from pool.
*/
public synchronized TextureItem get() {
TextureItem t = super.get();
if (!mUseBitmapPool)
return t;
synchronized (mBitmaps) {
int size = mBitmaps.size();
if (size == 0)
t.bitmap = CanvasAdapter.newBitmap(mWidth, mHeight, 0);
else {
t.bitmap = mBitmaps.remove(size - 1);
t.bitmap.eraseColor(Color.TRANSPARENT);
}
}
return t;
}
public synchronized TextureItem get(Bitmap bitmap) {
TextureItem t = super.get();
t.bitmap = bitmap;
return t;
}
@Override
protected TextureItem createItem() {
return new TextureItem(this, -1);
}
@Override
protected boolean clearItem(TextureItem t) {
if (t.used > 0)
return false;
if (t.ref != null) {
/* dispose texture if this clone holds the last handle */
if (t.ref.used == 0) {
t.ref.dispose();
return false;
}
t.ref.used--;
return false;
}
t.loaded = false;
if (mUseBitmapPool)
releaseBitmap(t);
return t.id >= 0;
}
@Override
protected void freeItem(TextureItem t) {
if (t.ref == null && t.used == 0 && t.id >= 0) {
mTexCnt--;
synchronized (disposedTextures) {
disposedTextures.add(Integer.valueOf(t.id));
t.id = -1;
}
}
}
protected void releaseBitmap(TextureItem t) {
if (t.bitmap == null)
return;
synchronized (mBitmaps) {
mBitmaps.add(t.bitmap);
t.bitmap = null;
}
}
private void uploadTexture(TextureItem t) {
if (t.bitmap == null)
throw new RuntimeException("Missing bitmap for texture");
if (t.id < 0) {
int[] textureIds = GLUtils.glGenTextures(1);
t.id = textureIds[0];
initTexture(t);
if (dbg)
log.debug("fill:" + getFill()
+ " count:" + mTexCnt
+ " new texture " + t.id);
mTexCnt++;
t.bitmap.uploadToTexture(false);
} else {
GLState.bindTex2D(t.id);
/* use faster subimage upload */
t.bitmap.uploadToTexture(true);
}
if (dbg)
GLUtils.checkGlError(TextureItem.class.getName());
if (mUseBitmapPool)
releaseBitmap(t);
}
protected void initTexture(TextureItem t) {
GLState.bindTex2D(t.id);
gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER,
GL.LINEAR);
gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER,
GL.LINEAR);
if (t.repeat) {
gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S,
GL.REPEAT);
gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T,
GL.REPEAT);
} else {
gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S,
GL.CLAMP_TO_EDGE);
gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T,
GL.CLAMP_TO_EDGE);
}
}
};
/* Pool for not-pooled textures. Disposed items will only be released
* on the GL-Thread and will not be put back in any pool. */
final static TexturePool NOPOOL = new TexturePool(0);
final static ArrayList<Integer> disposedTextures = new ArrayList<Integer>();
/**
* Disposed textures are released by MapRenderer after each frame
*/
public static void disposeTextures() {
synchronized (disposedTextures) {
int size = disposedTextures.size();
if (size > 0) {
int[] tmp = new int[size];
for (int i = 0; i < size; i++)
tmp[i] = disposedTextures.get(i).intValue();
disposedTextures.clear();
GLUtils.glDeleteTextures(size, tmp);
}
}
}
}