/*
* Copyright 2012-2014 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.renderer.bucket.RenderBucket.HAIRLINE;
import static org.oscim.renderer.bucket.RenderBucket.LINE;
import static org.oscim.renderer.bucket.RenderBucket.MESH;
import static org.oscim.renderer.bucket.RenderBucket.POLYGON;
import static org.oscim.renderer.bucket.RenderBucket.TEXLINE;
import java.nio.ShortBuffer;
import org.oscim.backend.GL;
import org.oscim.core.Tile;
import org.oscim.layers.tile.MapTile.TileData;
import org.oscim.renderer.BufferObject;
import org.oscim.renderer.MapRenderer;
import org.oscim.theme.styles.AreaStyle;
import org.oscim.theme.styles.LineStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is primarily intended for rendering the vector elements of a
* MapTile. It can be used for other purposes as well but some optimizations
* (and limitations) probably wont make sense in different contexts.
*/
public class RenderBuckets extends TileData {
static final Logger log = LoggerFactory.getLogger(RenderBuckets.class);
public final static int[] VERTEX_SHORT_CNT = {
4, // LINE_VERTEX
6, // TEXLINE_VERTEX
2, // POLY_VERTEX
2, // MESH_VERTEX
4, // EXTRUSION_VERTEX
2, // HAIRLINE_VERTEX
6, // SYMBOL
6, // BITMAP
};
private final static int SHORT_BYTES = 2;
private RenderBucket buckets;
/**
* VBO holds all vertex data to draw lines and polygons after compilation.
* Layout:
* 16 bytes fill coordinates,
* n bytes polygon vertices,
* m bytes lines vertices
* ...
*/
public BufferObject vbo;
public BufferObject ibo;
/**
* To not need to switch VertexAttribPointer positions all the time:
* 1. polygons are packed in VBO at offset 0
* 2. lines afterwards at lineOffset
* 3. other buckets keep their byte offset in offset
*/
public int[] offset = { 0, 0 };
private RenderBucket mCurBucket;
/**
* add the LineBucket for a level with a given Line style. Levels are
* ordered from bottom (0) to top
*/
public LineBucket addLineBucket(int level, LineStyle style) {
LineBucket l = (LineBucket) getBucket(level, LINE);
if (l == null)
return null;
// FIXME l.scale = style.width;
l.scale = 1;
l.line = style;
return l;
}
public PolygonBucket addPolygonBucket(int level, AreaStyle style) {
PolygonBucket l = (PolygonBucket) getBucket(level, POLYGON);
if (l == null)
return null;
l.area = style;
return l;
}
public MeshBucket addMeshBucket(int level, AreaStyle style) {
MeshBucket l = (MeshBucket) getBucket(level, MESH);
if (l == null)
return null;
l.area = style;
return l;
}
public HairLineBucket addHairLineBucket(int level, LineStyle style) {
HairLineBucket ll = getHairLineBucket(level);
if (ll == null)
return null;
ll.line = style;
return ll;
}
/**
* Get or add the LineBucket for a level. Levels are ordered from
* bottom (0) to top
*/
public LineBucket getLineBucket(int level) {
return (LineBucket) getBucket(level, LINE);
}
/**
* Get or add the MeshBucket for a level. Levels are ordered from
* bottom (0) to top
*/
public MeshBucket getMeshBucket(int level) {
return (MeshBucket) getBucket(level, MESH);
}
/**
* Get or add the PolygonBucket for a level. Levels are ordered from
* bottom (0) to top
*/
public PolygonBucket getPolygonBucket(int level) {
return (PolygonBucket) getBucket(level, POLYGON);
}
/**
* Get or add the TexLineBucket for a level. Levels are ordered from
* bottom (0) to top
*/
public LineTexBucket getLineTexBucket(int level) {
return (LineTexBucket) getBucket(level, TEXLINE);
}
/**
* Get or add the TexLineBucket for a level. Levels are ordered from
* bottom (0) to top
*/
public HairLineBucket getHairLineBucket(int level) {
return (HairLineBucket) getBucket(level, HAIRLINE);
}
/**
* Set new bucket items and clear previous.
*/
public void set(RenderBucket buckets) {
for (RenderBucket l = this.buckets; l != null; l = l.next)
l.clear();
this.buckets = buckets;
}
/**
* @return internal linked list of RenderBucket items
*/
public RenderBucket get() {
return buckets;
}
private RenderBucket getBucket(int level, int type) {
RenderBucket bucket = null;
if (mCurBucket != null && mCurBucket.level == level) {
bucket = mCurBucket;
if (bucket.type != type) {
log.error("BUG wrong bucket {} {} on level {}",
Integer.valueOf(bucket.type),
Integer.valueOf(type),
Integer.valueOf(level));
throw new IllegalArgumentException();
}
return bucket;
}
RenderBucket b = buckets;
if (b == null || b.level > level) {
/* insert new bucket at start */
b = null;
} else {
if (mCurBucket != null && level > mCurBucket.level)
b = mCurBucket;
while (true) {
/* found bucket */
if (b.level == level) {
bucket = b;
break;
}
/* insert bucket between current and next bucket */
if (b.next == null || b.next.level > level)
break;
b = b.next;
}
}
if (bucket == null) {
/* add a new RenderElement */
if (type == LINE)
bucket = new LineBucket(level);
else if (type == POLYGON)
bucket = new PolygonBucket(level);
else if (type == TEXLINE)
bucket = new LineTexBucket(level);
else if (type == MESH)
bucket = new MeshBucket(level);
else if (type == HAIRLINE)
bucket = new HairLineBucket(level);
if (bucket == null)
throw new IllegalArgumentException();
if (b == null) {
/** insert at start */
bucket.next = buckets;
buckets = bucket;
} else {
bucket.next = b.next;
b.next = bucket;
}
}
/* check if found buckets matches requested type */
if (bucket.type != type) {
log.error("BUG wrong bucket {} {} on level {}",
Integer.valueOf(bucket.type),
Integer.valueOf(type),
Integer.valueOf(level));
throw new IllegalArgumentException();
}
mCurBucket = bucket;
return bucket;
}
private int countVboSize() {
int vboShorts = 0;
for (RenderBucket l = buckets; l != null; l = l.next)
vboShorts += l.numVertices * VERTEX_SHORT_CNT[l.type];
return vboShorts;
}
private int countIboSize() {
int numIndices = 0;
for (RenderBucket l = buckets; l != null; l = l.next)
numIndices += l.numIndices;
return numIndices;
}
public void setFrom(RenderBuckets buckets) {
if (buckets == this)
throw new IllegalArgumentException("Cannot set from oneself!");
set(buckets.buckets);
mCurBucket = null;
buckets.buckets = null;
buckets.mCurBucket = null;
}
/** cleanup only when buckets are not used by tile or bucket anymore! */
public void clear() {
/* NB: set null calls clear() on each bucket! */
set(null);
mCurBucket = null;
vbo = BufferObject.release(vbo);
ibo = BufferObject.release(ibo);
}
/** cleanup only when buckets are not used by tile or bucket anymore! */
public void clearBuckets() {
/* NB: set null calls clear() on each bucket! */
for (RenderBucket l = buckets; l != null; l = l.next)
l.clear();
mCurBucket = null;
}
@Override
protected void dispose() {
clear();
}
public void prepare() {
for (RenderBucket l = buckets; l != null; l = l.next)
l.prepare();
}
public void bind() {
if (vbo != null)
vbo.bind();
if (ibo != null)
ibo.bind();
}
public boolean compile(boolean addFill) {
int vboSize = countVboSize();
if (vboSize <= 0) {
vbo = BufferObject.release(vbo);
ibo = BufferObject.release(ibo);
return false;
}
if (addFill)
vboSize += 8;
ShortBuffer vboData = MapRenderer.getShortBuffer(vboSize);
if (addFill)
vboData.put(fillCoords, 0, 8);
ShortBuffer iboData = null;
int iboSize = countIboSize();
if (iboSize > 0) {
iboData = MapRenderer.getShortBuffer(iboSize);
}
int pos = addFill ? 4 : 0;
for (RenderBucket l = buckets; l != null; l = l.next) {
if (l.type == POLYGON) {
l.compile(vboData, iboData);
l.vertexOffset = pos;
pos += l.numVertices;
}
}
offset[LINE] = vboData.position() * SHORT_BYTES;
pos = 0;
for (RenderBucket l = buckets; l != null; l = l.next) {
if (l.type == LINE) {
l.compile(vboData, iboData);
l.vertexOffset = pos;
pos += l.numVertices;
}
}
for (RenderBucket l = buckets; l != null; l = l.next) {
if (l.type != LINE && l.type != POLYGON) {
l.compile(vboData, iboData);
}
}
if (vboSize != vboData.position()) {
log.debug("wrong vertex buffer size: "
+ " new size: " + vboSize
+ " buffer pos: " + vboData.position()
+ " buffer limit: " + vboData.limit()
+ " buffer fill: " + vboData.remaining());
return false;
}
if (iboSize > 0 && iboSize != iboData.position()) {
log.debug("wrong indice buffer size: "
+ " new size: " + iboSize
+ " buffer pos: " + iboData.position()
+ " buffer limit: " + iboData.limit()
+ " buffer fill: " + iboData.remaining());
return false;
}
if (vbo == null)
vbo = BufferObject.get(GL.ARRAY_BUFFER, vboSize);
vbo.loadBufferData(vboData.flip(), vboSize * 2);
if (iboSize > 0) {
if (ibo == null)
ibo = BufferObject.get(GL.ELEMENT_ARRAY_BUFFER, iboSize);
ibo.loadBufferData(iboData.flip(), iboSize * 2);
}
return true;
}
private static short[] fillCoords;
static {
short s = (short) (Tile.SIZE * MapRenderer.COORD_SCALE);
fillCoords = new short[] { 0, s, s, s, 0, 0, s, 0 };
}
public static void initRenderer() {
LineBucket.Renderer.init();
LineTexBucket.Renderer.init();
PolygonBucket.Renderer.init();
TextureBucket.Renderer.init();
BitmapBucket.Renderer.init();
MeshBucket.Renderer.init();
HairLineBucket.Renderer.init();
}
}