MapView base layer is now a TileLayer

- extract MapView base layer into TileLayer
- extract MapTile loading from GLRenderer
- move all tile layer related classes to layers.tile.*
- make Overlay subclass of InputLayer, which extends Layer
This commit is contained in:
Hannes Janetzek
2013-04-21 23:20:23 +02:00
parent 1c779f2f60
commit 6eb3b9221b
32 changed files with 1398 additions and 1340 deletions

View File

@@ -18,8 +18,6 @@ import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_DYNAMIC_DRAW;
import static android.opengl.GLES20.GL_ONE;
import static android.opengl.GLES20.GL_ONE_MINUS_SRC_ALPHA;
import static org.oscim.generator.JobTile.STATE_NEW_DATA;
import static org.oscim.generator.JobTile.STATE_READY;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -31,6 +29,7 @@ import javax.microedition.khronos.opengles.GL10;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.layers.tile.MapTile;
import org.oscim.renderer.layer.Layers;
import org.oscim.renderer.layer.TextureItem;
import org.oscim.renderer.overlays.RenderOverlay;
@@ -62,7 +61,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
static int CACHE_TILES = CACHE_TILES_MAX;
private static MapView mMapView;
static int screenWidth, screenHeight;
public static int screenWidth, screenHeight;
private static MapViewPosition mMapViewPosition;
private static MapPosition mMapPosition;
@@ -76,6 +75,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
private static float[] mBoxCoords;
public class Matrices {
// do not modify any of these
public final Matrix4 viewproj = new Matrix4();
public final Matrix4 proj = new Matrix4();
public final Matrix4 view = new Matrix4();
@@ -89,88 +89,14 @@ public class GLRenderer implements GLSurfaceView.Renderer {
//private
static float[] mClearColor = null;
static int mQuadIndicesID;
final static int maxQuads = 64;
public static int mQuadIndicesID;
public final static int maxQuads = 64;
private static boolean mUpdateColor = false;
// drawlock to synchronize Main- and GL-Thread
// static ReentrantLock tilelock = new ReentrantLock();
static ReentrantLock drawlock = new ReentrantLock();
// Add additional tiles that serve as placeholer when flipping
// over date-line.
// I dont really like this but cannot think of a better solution:
// the other option would be to run scanbox each time for upload,
// drawing, proxies and text layer. needing to add placeholder only
// happens rarely, unless you live on Fidschi
/* package */static int mNumTileHolder;
/* package */static TileSet mDrawTiles;
// scanline fill class used to check tile visibility
private static ScanBox mScanBox = new ScanBox() {
@Override
void setVisible(int y, int x1, int x2) {
int cnt = mDrawTiles.cnt;
MapTile[] tiles = mDrawTiles.tiles;
for (int i = 0; i < cnt; i++) {
MapTile t = tiles[i];
if (t.tileY == y && t.tileX >= x1 && t.tileX < x2)
t.isVisible = true;
}
int xmax = 1 << mZoom;
if (x1 >= 0 && x2 < xmax)
return;
// add placeholder tiles to show both sides
// of date line. a little too complicated...
for (int x = x1; x < x2; x++) {
MapTile holder = null;
MapTile tile = null;
boolean found = false;
if (x >= 0 && x < xmax)
continue;
int xx = x;
if (x < 0)
xx = xmax + x;
else
xx = x - xmax;
if (xx < 0 || xx >= xmax)
continue;
for (int i = cnt; i < cnt + mNumTileHolder; i++)
if (tiles[i].tileX == x && tiles[i].tileY == y) {
found = true;
break;
}
if (found)
continue;
for (int i = 0; i < cnt; i++)
if (tiles[i].tileX == xx && tiles[i].tileY == y) {
tile = tiles[i];
break;
}
if (tile == null)
continue;
holder = new MapTile(x, y, (byte) mZoom);
holder.isVisible = true;
holder.holder = tile;
tile.isVisible = true;
tiles[cnt + mNumTileHolder++] = holder;
}
}
};
public static ReentrantLock drawlock = new ReentrantLock();
/**
* @param mapView
@@ -204,8 +130,6 @@ public class GLRenderer implements GLSurfaceView.Renderer {
mUpdateColor = true;
}
private static int uploadCnt = 0;
public static boolean uploadLayers(Layers layers, int newSize,
boolean addFill) {
@@ -258,7 +182,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
return true;
}
private static void checkBufferUsage(boolean force) {
public static void checkBufferUsage(boolean force) {
// try to clear some unused vbo when exceding limit
if (!force && mBufferMemoryUsage < LIMIT_BUFFERS) {
@@ -300,100 +224,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
}
}
private static Object tilelock = new Object();
/** set tile isVisible flag true for tiles that intersect view */
private static void updateTileVisibility() {
MapPosition pos = mMapPosition;
MapTile[] tiles = mDrawTiles.tiles;
// lock tiles while updating isVisible state
synchronized (GLRenderer.tilelock) {
int tileZoom = tiles[0].zoomLevel;
for (int i = 0; i < mDrawTiles.cnt; i++)
tiles[i].isVisible = false;
// count placeholder tiles
mNumTileHolder = 0;
// check visibile tiles
mScanBox.scan(pos.x, pos.y, pos.scale, tileZoom, mBoxCoords);
}
}
private static void uploadTileData(MapTile tile) {
tile.state = STATE_READY;
if (tile.layers == null)
return;
int newSize = tile.layers.getSize();
if (newSize > 0) {
if (tile.layers.vbo == null)
tile.layers.vbo = BufferObject.get(newSize);
if (!uploadLayers(tile.layers, newSize, true)) {
Log.d(TAG, "BUG uploadTileData " + tile + " failed!");
BufferObject.release(tile.layers.vbo);
tile.layers.vbo = null;
tile.layers.clear();
tile.layers = null;
}
}
}
/** compile tile layer data and upload to VBOs */
private static void compileTileLayers(MapTile[] tiles, int tileCnt) {
uploadCnt = 0;
for (int i = 0; i < tileCnt; i++) {
MapTile tile = tiles[i];
if (!tile.isVisible)
continue;
if (tile.state == STATE_READY)
continue;
if (tile.state == STATE_NEW_DATA) {
uploadTileData(tile);
continue;
}
if (tile.holder != null) {
// load tile that is referenced by this holder
if (tile.holder.state == STATE_NEW_DATA)
uploadTileData(tile.holder);
tile.state = tile.holder.state;
continue;
}
// check near relatives than can serve as proxy
if ((tile.proxies & MapTile.PROXY_PARENT) != 0) {
MapTile rel = tile.rel.parent.tile;
if (rel.state == STATE_NEW_DATA)
uploadTileData(rel);
// dont load child proxies
continue;
}
for (int c = 0; c < 4; c++) {
if ((tile.proxies & 1 << c) == 0)
continue;
MapTile rel = tile.rel.child[c].tile;
if (rel != null && rel.state == STATE_NEW_DATA)
uploadTileData(rel);
}
}
if (uploadCnt > 0)
checkBufferUsage(false);
}
private static void draw() {
long start = 0;
@@ -407,35 +238,14 @@ public class GLRenderer implements GLSurfaceView.Renderer {
mUpdateColor = false;
}
// Note: it seems faster to also clear the stencil buffer even
// when not needed. probaly otherwise it is masked out from the
// depth buffer as they share the same memory region afaik
// or for a better reason see OpenGL Insights chapter 23.
GLES20.glDepthMask(true);
GLES20.glStencilMask(0xFF);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT
| GLES20.GL_DEPTH_BUFFER_BIT
| GLES20.GL_STENCIL_BUFFER_BIT);
int serial = 0;
if (mDrawTiles != null)
serial = mDrawTiles.serial;
// get current tiles to draw
mDrawTiles = mMapView.getTileManager().getActiveTiles(mDrawTiles);
if (mDrawTiles == null || mDrawTiles.cnt == 0)
return;
boolean tilesChanged = false;
boolean positionChanged = false;
// check if the tiles have changed...
if (serial != mDrawTiles.serial) {
tilesChanged = true;
// FIXME needed?
positionChanged = true;
}
boolean tilesChanged = true;
boolean positionChanged = true;
// get current MapPosition, set mBoxCoords (mapping of screen to model
// coordinates)
@@ -457,38 +267,22 @@ public class GLRenderer implements GLSurfaceView.Renderer {
}
}
int tileCnt = mDrawTiles.cnt;
MapTile[] tiles = mDrawTiles.tiles;
if (positionChanged)
updateTileVisibility();
tileCnt += mNumTileHolder;
/* prepare tile for rendering */
compileTileLayers(tiles, tileCnt);
tilesChanged |= (uploadCnt > 0);
/* update overlays */
RenderOverlay[] overlays = mMapView.getOverlayManager().getRenderLayers();
for (int i = 0, n = overlays.length; i < n; i++)
overlays[i].update(mMapPosition, positionChanged, tilesChanged, mMatrices);
/* draw base layer */
TileRenderer.draw(tiles, tileCnt, pos, mMatrices);
/* draw overlays */
for (int i = 0, n = overlays.length; i < n; i++) {
RenderOverlay renderOverlay = overlays[i];
RenderOverlay renderLayer = overlays[i];
if (renderOverlay.newData) {
renderOverlay.compile();
renderOverlay.newData = false;
if (renderLayer.newData) {
renderLayer.compile();
renderLayer.newData = false;
}
if (renderOverlay.isReady)
renderOverlay.render(mMapPosition, mMatrices);
if (renderLayer.isReady)
renderLayer.render(mMapPosition, mMatrices);
}
if (MapView.debugFrameTime) {
@@ -506,44 +300,6 @@ public class GLRenderer implements GLSurfaceView.Renderer {
return ((t.tileX % 4) + (t.tileY % 4 * 4) + 1);
}
// get a TileSet of currently visible tiles
public static TileSet getVisibleTiles(TileSet td) {
if (mDrawTiles == null)
return td;
// ensure tiles keep visible state
synchronized (GLRenderer.tilelock) {
MapTile[] newTiles = mDrawTiles.tiles;
int cnt = mDrawTiles.cnt;
if (td == null)
td = new TileSet(newTiles.length);
// unlock previous tiles
for (int i = 0; i < td.cnt; i++)
td.tiles[i].unlock();
// lock tiles to not be removed from cache
td.cnt = 0;
for (int i = 0; i < cnt; i++) {
MapTile t = newTiles[i];
if (t.isVisible && t.state == STATE_READY) {
t.lock();
td.tiles[td.cnt++] = t;
}
}
}
return td;
}
public static void releaseTiles(TileSet td) {
for (int i = 0; i < td.cnt; i++) {
td.tiles[i].unlock();
td.tiles[i] = null;
}
td.cnt = 0;
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
Log.d(TAG, "SurfaceChanged:" + mNewSurface + " " + width + "x" + height);
@@ -608,7 +364,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
mBufferMemoryUsage = 0;
mDrawTiles = null;
//mDrawTiles = null;
int numTiles = (screenWidth / (Tile.SIZE / 2) + 2)
* (screenHeight / (Tile.SIZE / 2) + 2);
@@ -624,18 +380,21 @@ public class GLRenderer implements GLSurfaceView.Renderer {
mMapView.redrawMap(true);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// String ext = GLES20.glGetString(GLES20.GL_EXTENSIONS);
// Log.d(TAG, "Extensions: " + ext);
// classes that require GL context for initialization
public static void initRenderer() {
LineRenderer.init();
LineTexRenderer.init();
PolygonRenderer.init();
TextureRenderer.init();
TextureItem.init(10);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Log.d(TAG, GLES20.glGetString(GLES20.GL_EXTENSIONS));
// classes that require GL context for initialization
initRenderer();
mNewSurface = true;
}

View File

@@ -1,139 +0,0 @@
/*
* Copyright 2012, 2013 Hannes Janetzek
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General 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 License for more details.
*
* You should have received a copy of the GNU Lesser General License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.renderer;
import org.oscim.generator.JobTile;
import org.oscim.renderer.layer.Layers;
import org.oscim.renderer.layer.TextItem;
/**
* Extends Tile class for concurrent use in TileManager,
* TileGenerator and GLRenderer threads.
*/
public final class MapTile extends JobTile {
public double x;
public double y;
/**
* Tile data set by TileGenerator.
*/
public TextItem labels;
public Layers layers;
/**
* Tile is in view region. Set by GLRenderer.
*/
public boolean isVisible;
/**
* Pointer to access relatives in QuadTree
*/
public QuadTree rel;
int lastDraw = 0;
// keep track which tiles are locked as proxy for this tile
public final static int PROXY_CHILD1 = 1 << 0;
public final static int PROXY_CHILD2 = 1 << 1;
public final static int PROXY_CHILD3 = 1 << 2;
public final static int PROXY_CHILD4 = 1 << 3;
public final static int PROXY_PARENT = 1 << 4;
public final static int PROXY_GRAMPA = 1 << 5;
public final static int PROXY_HOLDER = 1 << 6;
public byte proxies;
// check which labels were joined
// public final static int JOIN_T = 1 << 0;
// public final static int JOIN_B = 1 << 1;
// public final static int JOIN_L = 1 << 2;
// public final static int JOIN_R = 1 << 3;
// public final static int JOINED = 15;
// public byte joined;
// counting the tiles that use this tile as proxy
byte refs;
// up to 255 Threads may lock a tile
byte locked;
// only used GLRenderer when this tile sits in for another tile.
// e.g. x:-1,y:0,z:1 for x:1,y:0
MapTile holder;
MapTile(int tileX, int tileY, byte zoomLevel) {
super(tileX, tileY, zoomLevel);
this.x = (double)tileX / (1 << zoomLevel);
this.y = (double)tileY / (1 << zoomLevel);
}
/**
* @return true if tile could be referenced by another thread
*/
boolean isLocked() {
return locked > 0 || refs > 0;
}
void lock() {
if (locked++ > 0)
return;
// lock all tiles that could serve as proxy
MapTile p = rel.parent.tile;
if (p != null && (p.state != 0)) {
proxies |= PROXY_PARENT;
p.refs++;
}
p = rel.parent.parent.tile;
if (p != null && (p.state != 0)) {
proxies |= PROXY_GRAMPA;
p.refs++;
}
for (int j = 0; j < 4; j++) {
if (rel.child[j] != null) {
p = rel.child[j].tile;
if (p != null && (p.state != 0)) {
proxies |= (1 << j);
p.refs++;
}
}
}
}
void unlock() {
if (--locked > 0 || proxies == 0)
return;
if ((proxies & PROXY_PARENT) != 0)
rel.parent.tile.refs--;
if ((proxies & PROXY_GRAMPA) != 0)
rel.parent.parent.tile.refs--;
for (int i = 0; i < 4; i++) {
if ((proxies & (1 << i)) != 0)
rel.child[i].tile.refs--;
}
proxies = 0;
}
public void addLabel(TextItem t){
t.next = labels;
labels = t;
}
}

View File

@@ -1,148 +0,0 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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;
import android.util.Log;
public class QuadTree {
private static String TAG = QuadTree.class.getName();
// pointer to tile 0/0/0
private static QuadTree root;
// parent pointer is used to link pool items
private static QuadTree pool;
public QuadTree parent;
// .... x y
// 0 => 0 0
// 1 => 1 0
// 2 => 0 1
// 3 => 1 1
public final QuadTree[] child = new QuadTree[4];
int refs = 0;
byte id;
public MapTile tile;
static void init() {
pool = null;
root = new QuadTree();
root.parent = root;
}
static boolean remove(MapTile t) {
if (t.rel == null) {
// Bad Things(tm) happened
Log.d(TAG, "BUG already removed " + t);
return true;
}
QuadTree cur = t.rel;
QuadTree next;
for (; cur != root;) {
// keep pointer to parent
next = cur.parent;
cur.refs--;
// if current node has no children
if (cur.refs == 0) {
// unhook from parent
next.child[cur.id] = null;
// add item back to pool
cur.parent = pool;
pool = cur;
}
cur = next;
}
root.refs--;
t.rel.tile = null;
t.rel = null;
return true;
}
static QuadTree add(MapTile tile) {
int x = tile.tileX;
int y = tile.tileY;
int z = tile.zoomLevel;
// if (x < 0 || x >= 1 << z) {
// Log.d(TAG, "invalid position");
// return null;
// }
// if (y < 0 || y >= 1 << z) {
// Log.d(TAG, "invalid position");
// return null;
// }
QuadTree leaf = root;
for (int level = z - 1; level >= 0; level--) {
int id = ((x >> level) & 1) | ((y >> level) & 1) << 1;
leaf.refs++;
QuadTree cur = leaf.child[id];
if (cur != null) {
leaf = cur;
continue;
}
if (pool != null) {
cur = pool;
pool = pool.parent;
} else {
cur = new QuadTree();
}
cur.refs = 0;
cur.id = (byte) id;
cur.parent = leaf;
cur.parent.child[id] = cur;
leaf = cur;
}
leaf.refs++;
leaf.tile = tile;
tile.rel = leaf;
return leaf;
}
static MapTile getTile(int x, int y, int z) {
QuadTree leaf = root;
for (int level = z - 1; level >= 0; level--) {
leaf = leaf.child[((x >> level) & 1) | ((y >> level) & 1) << 1];
if (leaf == null)
return null;
if (level == 0) {
return leaf.tile;
}
}
return null;
}
}

View File

@@ -78,7 +78,7 @@ public abstract class ScanBox {
protected int mZoom;
abstract void setVisible(int y, int x1, int x2);
protected abstract void setVisible(int y, int x1, int x2);
public void scan(double x, double y, double scale, int zoom, float[] box) {
mZoom = zoom;

View File

@@ -1,600 +0,0 @@
/*
* Copyright 2013 Hannes Janetzek
*
* 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;
import static org.oscim.generator.JobTile.STATE_LOADING;
import static org.oscim.generator.JobTile.STATE_NEW_DATA;
import static org.oscim.generator.JobTile.STATE_NONE;
import java.util.ArrayList;
import java.util.Arrays;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.generator.JobTile;
import org.oscim.generator.TileDistanceSort;
import org.oscim.renderer.layer.TextItem;
import org.oscim.utils.FastMath;
import org.oscim.view.MapView;
import org.oscim.view.MapViewPosition;
import android.util.Log;
/**
* @TODO
* - prefetching to cache file
* - this class should probably not be in 'renderer' -> tilemap?
* - make it general for reuse in tile-overlays
*/
public class TileManager {
static final String TAG = TileManager.class.getSimpleName();
private final static int MAX_ZOOMLEVEL = 17;
private final static int MIN_ZOOMLEVEL = 2;
// limit number tiles with new data not uploaded to GL
// TODO this should depend on the number of tiles displayed
private static final int MAX_TILES_IN_QUEUE = 40;
// cache limit threshold
private static final int CACHE_THRESHOLD = 30;
private final MapView mMapView;
private final MapViewPosition mMapViewPosition;
private boolean mInitialized;
// cache for all tiles
private MapTile[] mTiles;
// actual number of tiles in mTiles
private int mTilesCount;
// current end position in mTiles
private int mTilesSize;
// counter for tiles with new data not uploaded to GL
private volatile int mTilesForUpload;
// new tile jobs for MapWorkers
private final ArrayList<JobTile> mJobs;
// counter to check whether current TileSet has changed
private static int mUpdateSerial;
// lock for TileSets while updating MapTile locks
private final Object mTilelock = new Object();
private TileSet mCurrentTiles;
/* package */TileSet mNewTiles;
private final float[] mBoxCoords = new float[8];
public TileManager(MapView mapView) {
mMapView = mapView;
mMapViewPosition = mapView.getMapViewPosition();
mJobs = new ArrayList<JobTile>();
mTiles = new MapTile[GLRenderer.CACHE_TILES];
mTilesSize = 0;
mTilesForUpload = 0;
mUpdateSerial = 0;
mInitialized = false;
}
public void destroy() {
mInitialized = false;
// there might be some leaks in here
// ... free static pools
}
public synchronized void init(int width, int height) {
// sync with GLRender thread
// ... and labeling thread?
GLRenderer.drawlock.lock();
if (mInitialized) {
// pass VBOs and VertexItems back to pools
for (int i = 0; i < mTilesSize; i++)
clearTile(mTiles[i]);
}
//else {
// mInitialized is set when surface changed
// and VBOs might be lost
// VertexPool.init();
//}
// clear cache index
QuadTree.init();
// clear references to cached MapTiles
Arrays.fill(mTiles, null);
mTilesSize = 0;
mTilesCount = 0;
// clear all references to previous tiles
for (TileSet td : mTileSets) {
Arrays.fill(td.tiles, null);
td.cnt = 0;
}
// set up TileSet large enough to hold current tiles
int num = Math.max(width, height);
int size = Tile.SIZE >> 1;
int numTiles = (num * num) / (size * size) * 4;
mNewTiles = new TileSet(numTiles);
mCurrentTiles = new TileSet(numTiles);
Log.d(TAG, "max tiles: " + numTiles);
mInitialized = true;
GLRenderer.drawlock.unlock();
}
/**
* 1. Update mCurrentTiles TileSet of currently visible tiles.
* 2. Add not yet loaded (or loading) tiles to JobQueue.
* 3. Manage cache
*
* @param pos
* current MapPosition
*/
public synchronized void update(MapPosition pos) {
// clear JobQueue and set tiles to state == NONE.
// one could also append new tiles and sort in JobQueue
// but this has the nice side-effect that MapWorkers dont
// start with old jobs while new jobs are calculated, which
// should increase the chance that they are free when new
// jobs come in.
mMapView.addJobs(null);
// load some tiles more than currently visible (* 0.75)
double scale = pos.scale * 0.9f;
int tileZoom = FastMath.clamp(pos.zoomLevel, MIN_ZOOMLEVEL, MAX_ZOOMLEVEL);
mMapViewPosition.getMapViewProjection(mBoxCoords);
// scan visible tiles. callback function calls 'addTile'
// which updates mNewTiles
mNewTiles.cnt = 0;
mScanBox.scan(pos.x, pos.y, scale, tileZoom, mBoxCoords);
MapTile[] newTiles = mNewTiles.tiles;
MapTile[] curTiles = mCurrentTiles.tiles;
boolean changed = (mNewTiles.cnt != mCurrentTiles.cnt);
Arrays.sort(mNewTiles.tiles, 0, mNewTiles.cnt, TileSet.coordComparator);
if (!changed) {
for (int i = 0, n = mNewTiles.cnt; i < n; i++) {
if (newTiles[i] != curTiles[i]) {
changed = true;
break;
}
}
}
if (changed) {
synchronized (mTilelock) {
// lock new tiles
for (int i = 0, n = mNewTiles.cnt; i < n; i++)
newTiles[i].lock();
// unlock previous tiles
for (int i = 0, n = mCurrentTiles.cnt; i < n; i++) {
curTiles[i].unlock();
curTiles[i] = null;
}
// make new tiles current
TileSet tmp = mCurrentTiles;
mCurrentTiles = mNewTiles;
mNewTiles = tmp;
mUpdateSerial++;
}
// request rendering as tiles changed
mMapView.render();
}
/* Add tile jobs to queue */
if (mJobs.isEmpty())
return;
JobTile[] jobs = new JobTile[mJobs.size()];
jobs = mJobs.toArray(jobs);
updateTileDistances(jobs, jobs.length, pos);
// sets tiles to state == LOADING
mMapView.addJobs(jobs);
mJobs.clear();
/* limit cache items */
int remove = mTilesCount - GLRenderer.CACHE_TILES;
if (remove > CACHE_THRESHOLD ||
mTilesForUpload > MAX_TILES_IN_QUEUE)
limitCache(pos, remove);
}
// need to keep track of TileSets to clear on reset...
private static ArrayList<TileSet> mTileSets = new ArrayList<TileSet>(2);
public TileSet getActiveTiles(TileSet td) {
if (mCurrentTiles == null)
return td;
if (td != null && td.serial == mUpdateSerial)
return td;
// dont flip new/currentTiles while copying
synchronized (mTilelock) {
MapTile[] newTiles = mCurrentTiles.tiles;
int cnt = mCurrentTiles.cnt;
// lock tiles (and their proxies) to not be removed from cache
for (int i = 0; i < cnt; i++)
newTiles[i].lock();
MapTile[] nextTiles;
if (td == null) {
td = new TileSet(newTiles.length);
mTileSets.add(td);
}
nextTiles = td.tiles;
// unlock previously active tiles
for (int i = 0, n = td.cnt; i < n; i++)
nextTiles[i].unlock();
// copy newTiles to nextTiles
System.arraycopy(newTiles, 0, nextTiles, 0, cnt);
td.serial = mUpdateSerial;
td.cnt = cnt;
}
return td;
}
// /**
// * @param tiles ...
// */
// public void releaseTiles(TileSet tiles) {
//
// }
/* package */MapTile addTile(int x, int y, int zoomLevel) {
MapTile tile;
tile = QuadTree.getTile(x, y, zoomLevel);
if (tile == null) {
tile = new MapTile(x, y, (byte) zoomLevel);
QuadTree.add(tile);
mJobs.add(tile);
addToCache(tile);
} else if (!tile.isActive()) {
mJobs.add(tile);
}
if (zoomLevel > 2) {
boolean add = false;
// prefetch parent
MapTile p = tile.rel.parent.tile;
if (p == null) {
p = new MapTile(x >> 1, y >> 1, (byte) (zoomLevel - 1));
QuadTree.add(p);
addToCache(p);
add = true;
}
if (add || !p.isActive()) {
// hack to not add tile twice
p.state = STATE_LOADING;
mJobs.add(p);
}
if (zoomLevel > 3) {
// prefetch grand parent
p = tile.rel.parent.parent.tile;
add = false;
if (p == null) {
p = new MapTile(x >> 2, y >> 2, (byte) (zoomLevel - 2));
QuadTree.add(p);
addToCache(p);
add = true;
}
if (add || !p.isActive()) {
p.state = STATE_LOADING;
mJobs.add(p);
}
}
}
return tile;
}
private void addToCache(MapTile tile) {
if (mTilesSize == mTiles.length) {
if (mTilesSize > mTilesCount) {
//Log.d(TAG, "repack: " + mTiles.length + " / " + mTilesCount);
TileDistanceSort.sort(mTiles, 0, mTilesSize);
mTilesSize = mTilesCount;
}
if (mTilesSize == mTiles.length) {
Log.d(TAG, "realloc tiles " + mTilesSize);
MapTile[] tmp = new MapTile[mTiles.length + 20];
System.arraycopy(mTiles, 0, tmp, 0, mTilesCount);
mTiles = tmp;
}
}
mTiles[mTilesSize++] = tile;
mTilesCount++;
}
private void clearTile(MapTile t) {
if (t == null)
return;
if (t.layers != null) {
// TODO move this to layers clear
if (t.layers.vbo != null) {
BufferObject.release(t.layers.vbo);
t.layers.vbo = null;
}
t.layers.clear();
t.layers = null;
}
TextItem.pool.releaseAll(t.labels);
QuadTree.remove(t);
t.state = STATE_NONE;
mTilesCount--;
}
private static void updateTileDistances(Object[] tiles, int size, MapPosition mapPosition) {
// TODO there is probably a better quad-tree distance function
int zoom = mapPosition.zoomLevel;
double x = mapPosition.x;
double y = mapPosition.y;
// half tile size at current zoom-level
final double h = 1.0 / (2 << zoom);
//long center = (long)(h * (1 << zoom));
for (int i = 0; i < size; i++) {
MapTile t = (MapTile) tiles[i];
if (t == null)
continue;
int diff = (t.zoomLevel - zoom);
double dx, dy, scale;
if (diff == 0) {
dx = (t.x + h) - x;
dy = (t.y + h) - y;
scale = 0.5f;
} else if (diff > 0) {
// tile zoom level is greater than current
// NB: distance increase by the factor 2
// with each zoom-level, so that children
// will be kept less likely than parent tiles.
double dh = 1.0 / (2 << t.zoomLevel);
dx = (t.x + dh) - x;
dy = (t.y + dh) - y;
// add tilesize/2 with each zoom-level
// so that children near the current
// map position but a few levels above
// will also be removed
//dz = diff * h;
scale = (1 << diff);
} else {
diff = -diff;
// tile zoom level is smaller than current
double dh = 1.0 / (2 << t.zoomLevel);
dx = (t.x + dh) - x;
dy = (t.y + dh) - y;
scale = 0.5f * (1 << diff);
}
if (dx < 0)
dx = -dx;
if (dy < 0)
dy = -dy;
t.distance = (float) ((dx + dy) * scale * 1024);
}
}
private void limitCache(MapPosition mapPosition, int remove) {
MapTile[] tiles = mTiles;
int size = mTilesSize;
// count tiles that have new data
mTilesForUpload = 0;
int newTileCnt = 0;
// remove tiles that were never loaded
for (int i = 0; i < size; i++) {
MapTile t = tiles[i];
if (t == null)
continue;
if (t.state == STATE_NEW_DATA)
newTileCnt++;
// make sure tile cannot be used by GL or MapWorker Thread
if ((t.state != 0) || t.isLocked()) {
continue;
}
clearTile(t);
tiles[i] = null;
remove--;
}
if (remove > 10 || newTileCnt > MAX_TILES_IN_QUEUE) {
updateTileDistances(tiles, size, mapPosition);
TileDistanceSort.sort(tiles, 0, size);
// sorting also repacks the 'sparse' filled array
// so end of mTiles is at mTilesCount now
size = mTilesSize = mTilesCount;
for (int i = size - 1; i >= 0 && remove > 0; i--) {
MapTile t = tiles[i];
if (t.isLocked()) {
// dont remove tile used by GLRenderer, or somewhere else
Log.d(TAG, "limitCache: tile still locked " + t + " " + t.distance);
// try again in next run.
//locked = true;
//break;
} else if (t.state == STATE_LOADING) {
// NOTE: when set loading to false the tile could be
// added to load queue again while still processed in
// TileGenerator => need tile.cancel flag.
// t.isLoading = false;
Log.d(TAG, "limitCache: cancel loading " + t + " " + t.distance);
} else {
if (t.state == STATE_NEW_DATA)
newTileCnt--;
remove--;
clearTile(t);
tiles[i] = null;
}
}
remove = (newTileCnt - MAX_TILES_IN_QUEUE) + 10;
//int r = remove;
for (int i = size - 1; i >= 0 && remove > 0; i--) {
MapTile t = tiles[i];
if (t != null && t.state == STATE_NEW_DATA) {
if (!t.isLocked()) {
clearTile(t);
tiles[i] = null;
remove--;
newTileCnt--;
}
}
}
mTilesForUpload += newTileCnt;
//Log.d(TAG, "cleanup load queue " + tilesForUpload + "/" + r + " - " + remove);
}
}
/**
* called from MapWorker Thread when tile is loaded by TileGenerator
*
* @param jobTile
* Tile ready for upload to GL
* @return ... caller does not care
*/
public synchronized boolean passTile(JobTile jobTile) {
MapTile tile = (MapTile) jobTile;
if (tile.state != STATE_LOADING) {
// - should rather be STATE_FAILED
// no one should be able to use this tile now, TileGenerator passed
// it, GL-Thread does nothing until newdata is set.
//Log.d(TAG, "passTile: failed loading " + tile);
return true;
}
//if (tile.vbo != null) {
// // BAD Things(tm) happend: tile is already loaded
// Log.d(TAG, "BUG: tile loaded before " + tile);
// return true;
//}
tile.state = STATE_NEW_DATA;
mTilesForUpload++;
// locked means the tile is visible or referenced by
// a tile that might be visible.
if (tile.isLocked())
mMapView.render();
return true;
}
private final ScanBox mScanBox = new ScanBox() {
@Override
public void setVisible(int y, int x1, int x2) {
MapTile[] tiles = mNewTiles.tiles;
int cnt = mNewTiles.cnt;
int maxTiles = tiles.length;
int xmax = 1 << mZoom;
for (int x = x1; x < x2; x++) {
MapTile tile = null;
if (cnt == maxTiles) {
Log.d(TAG, "reached maximum tiles " + maxTiles);
break;
}
int xx = x;
if (x < 0 || x >= xmax) {
// flip-around date line
if (x < 0)
xx = xmax + x;
else
xx = x - xmax;
if (xx < 0 || xx >= xmax)
continue;
}
// check if tile is already added
for (int i = 0; i < cnt; i++)
if (tiles[i].tileX == xx && tiles[i].tileY == y) {
tile = tiles[i];
break;
}
if (tile == null) {
tile = addTile(xx, y, mZoom);
tiles[cnt++] = tile;
}
}
mNewTiles.cnt = cnt;
}
};
}

View File

@@ -1,265 +0,0 @@
/*
* Copyright 2013 Hannes Janetzek
*
* 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;
import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.glStencilMask;
import static org.oscim.generator.JobTile.STATE_READY;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.renderer.GLRenderer.Matrices;
import org.oscim.renderer.layer.Layer;
import org.oscim.utils.FastMath;
import org.oscim.utils.Matrix4;
import android.opengl.GLES20;
/**
* This class is for rendering the Line- and PolygonLayers of visible MapTiles.
* For
* visible tiles that do not have data available yet its parent in children
* tiles are rendered when available.
*
* @author Hannes Janetzek
*/
public class TileRenderer {
//private final static String TAG = TileRenderer.class.getName();
// used to increase polygon-offset for each tile drawn.
private static int mDrawCnt;
// used to not draw a tile twice per frame.
private static int mDrawSerial = 0;
private static Matrices mMatrices;
private static final Matrix4 mProjMatrix = new Matrix4();
static void draw(MapTile[] tiles, int tileCnt, MapPosition pos, Matrices m) {
mDrawCnt = 0;
mMatrices = m;
mProjMatrix.copy(m.viewproj);
// discard z projection from tilt
mProjMatrix.setValue(10, 0);
mProjMatrix.setValue(14, 0);
GLES20.glDepthFunc(GLES20.GL_LESS);
// load texture for line caps
LineRenderer.beginLines();
// Draw visible tiles
for (int i = 0; i < tileCnt; i++) {
MapTile t = tiles[i];
if (t.isVisible && t.state == STATE_READY)
drawTile(t, pos);
}
double scale = pos.getZoomScale();
// Draw parent or children as proxy for visibile tiles that dont
// have data yet. Proxies are clipped to the region where nothing
// was drawn to depth buffer.
// TODO draw proxies for placeholder
for (int i = 0; i < tileCnt; i++) {
MapTile t = tiles[i];
if (t.isVisible && (t.state != STATE_READY) && (t.holder == null)){
boolean preferParent = (scale > 1.5) || (pos.zoomLevel - t.zoomLevel < 0);
drawProxyTile(t, pos, true, preferParent);
}
}
// Draw grandparents
for (int i = 0; i < tileCnt; i++) {
MapTile t = tiles[i];
if (t.isVisible && (t.state != STATE_READY) && (t.holder == null))
drawProxyTile(t, pos, false, false);
}
// make sure stencil buffer write is disabled
glStencilMask(0x00);
LineRenderer.endLines();
mDrawSerial++;
// dont keep the ref...
mMatrices = null;
}
private static void drawTile(MapTile tile, MapPosition pos) {
// draw parents only once
if (tile.lastDraw == mDrawSerial)
return;
tile.lastDraw = mDrawSerial;
MapTile t = tile;
if (t.holder != null)
t = t.holder;
if (t.layers == null || t.layers.vbo == null) {
//Log.d(TAG, "missing data " + (t.layers == null) + " " + (t.vbo == null));
return;
}
GLES20.glBindBuffer(GL_ARRAY_BUFFER, t.layers.vbo.id);
// place tile relative to map position
int z = tile.zoomLevel;
float div = FastMath.pow(z - pos.zoomLevel);
double curScale = Tile.SIZE * pos.scale;
double scale = (pos.scale / (1 << z));
float x = (float) ((tile.x - pos.x) * curScale);
float y = (float) ((tile.y - pos.y) * curScale);
Matrices m = mMatrices;
m.mvp.setTransScale(x, y, (float)(scale / GLRenderer.COORD_SCALE));
m.mvp.multiplyMM(mProjMatrix, m.mvp);
// set depth offset (used for clipping to tile boundaries)
GLES20.glPolygonOffset(1, mDrawCnt++);
if (mDrawCnt == 100)
mDrawCnt = 0;
// simple line shader does not take forward shortening into account
int simpleShader = (pos.tilt < 1 ? 1 : 0);
boolean clipped = false;
for (Layer l = t.layers.baseLayers; l != null;) {
switch (l.type) {
case Layer.POLYGON:
l = PolygonRenderer.draw(pos, l, m, !clipped, true);
clipped = true;
break;
case Layer.LINE:
if (!clipped) {
// draw stencil buffer clip region
PolygonRenderer.draw(pos, null, m, true, true);
clipped = true;
}
l = LineRenderer.draw(t.layers, l, pos, m, div, simpleShader);
break;
case Layer.TEXLINE:
if (!clipped) {
// draw stencil buffer clip region
PolygonRenderer.draw(pos, null, m, true, true);
clipped = true;
}
l = LineTexRenderer.draw(t.layers, l, pos, m, div);
break;
default:
// just in case
l = l.next;
}
}
// clear clip-region and could also draw 'fade-effect'
//PolygonRenderer.drawOver(m, true, 0x22000000);
PolygonRenderer.drawOver(m, false, 0);
}
private static int drawProxyChild(MapTile tile, MapPosition pos) {
int drawn = 0;
for (int i = 0; i < 4; i++) {
if ((tile.proxies & 1 << i) == 0)
continue;
MapTile c = tile.rel.child[i].tile;
if (c.state == STATE_READY) {
drawTile(c, pos);
drawn++;
}
}
return drawn;
}
// just FIXME!
private static void drawProxyTile(MapTile tile, MapPosition pos, boolean parent, boolean preferParent) {
//int diff = pos.zoomLevel - tile.zoomLevel;
QuadTree r = tile.rel;
MapTile proxy;
if (!preferParent) {
// prefer drawing children
if (drawProxyChild(tile, pos) == 4)
return;
if (parent) {
// draw parent proxy
if ((tile.proxies & MapTile.PROXY_PARENT) != 0) {
proxy = r.parent.tile;
if (proxy.state == STATE_READY) {
//Log.d(TAG, "1. draw parent " + proxy);
drawTile(proxy, pos);
}
}
} else if ((tile.proxies & MapTile.PROXY_GRAMPA) != 0) {
// check if parent was already drawn
if ((tile.proxies & MapTile.PROXY_PARENT) != 0) {
proxy = r.parent.tile;
if (proxy.state == STATE_READY)
return;
}
proxy = r.parent.parent.tile;
if (proxy.state == STATE_READY)
drawTile(proxy, pos);
}
} else {
// prefer drawing parent
if (parent) {
if ((tile.proxies & MapTile.PROXY_PARENT) != 0) {
proxy = r.parent.tile;
if (proxy != null && proxy.state == STATE_READY) {
//Log.d(TAG, "2. draw parent " + proxy);
drawTile(proxy, pos);
return;
}
}
drawProxyChild(tile, pos);
} else if ((tile.proxies & MapTile.PROXY_GRAMPA) != 0) {
// check if parent was already drawn
if ((tile.proxies & MapTile.PROXY_PARENT) != 0) {
proxy = r.parent.tile;
if (proxy.state == STATE_READY)
return;
}
// this will do nothing, just to check
if (drawProxyChild(tile, pos) > 0)
return;
proxy = r.parent.parent.tile;
if (proxy.state == STATE_READY)
drawTile(proxy, pos);
}
}
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2012 Hannes Janetzek
*
* 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;
import java.util.Comparator;
/**
* use with TileManager.getActiveTiles(TileSet) to get the current tiles. tiles
* are locked to not be modifed until getActiveTiles passes them back on a
* second invocation or TODO: implement TileManager.releaseTiles(TileSet).
*/
public final class TileSet {
public int cnt = 0;
public MapTile[] tiles;
int serial;
TileSet() {
}
TileSet(int numTiles) {
tiles = new MapTile[numTiles];
}
public MapTile getTile(int x, int y){
for (int i = 0; i < cnt; i++)
if (tiles[i].tileX == x && tiles[i].tileY == y)
return tiles[i];
return null;
}
public static Comparator<MapTile> coordComparator = new CoordComparator();
public static class CoordComparator implements Comparator<MapTile> {
@Override
public int compare(MapTile lhs, MapTile rhs) {
if (lhs.tileX == rhs.tileX) {
if (lhs.tileY == rhs.tileY)
return 0;
if (lhs.tileY < rhs.tileY)
return 1;
return -1;
}
if (lhs.tileX < rhs.tileX)
return 1;
return -1;
}
}
}

View File

@@ -20,12 +20,13 @@ import java.nio.ShortBuffer;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.generator.JobTile;
import org.oscim.layers.tile.JobTile;
import org.oscim.layers.tile.MapTile;
import org.oscim.layers.tile.TileRenderLayer;
import org.oscim.layers.tile.TileSet;
import org.oscim.renderer.GLRenderer;
import org.oscim.renderer.GLRenderer.Matrices;
import org.oscim.renderer.GLState;
import org.oscim.renderer.MapTile;
import org.oscim.renderer.TileSet;
import org.oscim.renderer.layer.ExtrusionLayer;
import org.oscim.utils.GlUtils;
import org.oscim.view.MapView;
@@ -39,8 +40,11 @@ import android.util.Log;
public class ExtrusionOverlay extends RenderOverlay {
private final static String TAG = ExtrusionOverlay.class.getName();
public ExtrusionOverlay(MapView mapView) {
private final TileRenderLayer mTileLayer;
public ExtrusionOverlay(MapView mapView, org.oscim.layers.tile.TileRenderLayer tileRenderLayer) {
super(mapView);
mTileLayer = tileRenderLayer;
}
private static int[] shaderProgram = new int[2];
@@ -92,7 +96,10 @@ public class ExtrusionOverlay extends RenderOverlay {
}
int ready = 0;
mTileSet = mMapView.getTileManager().getActiveTiles(mTileSet);
mTileSet = mTileLayer.getVisibleTiles(mTileSet);
if (mTileSet == null)
return;
MapTile[] tiles = mTileSet.tiles;
// FIXME just release tiles in this case
if (mAlpha == 0 || curPos.zoomLevel < 16) {
@@ -135,11 +142,12 @@ public class ExtrusionOverlay extends RenderOverlay {
for (int i = 0; i < mTileSet.cnt; i++) {
if (!tiles[i].isVisible)
continue;
MapTile t = tiles[i];
for (byte j = 0; j < 4; j++) {
if ((t.proxies & (1 << j)) != 0) {
MapTile c = t.rel.child[j].tile;
MapTile c = t.rel.get(j);
el = getLayer(c);
if (el == null || !el.compiled)

View File

@@ -32,18 +32,19 @@ import java.util.HashMap;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.generator.JobTile;
import org.oscim.graphics.Color;
import org.oscim.graphics.Paint.Cap;
import org.oscim.layers.tile.JobTile;
import org.oscim.layers.tile.MapTile;
import org.oscim.layers.tile.TileRenderLayer;
import org.oscim.layers.tile.TileSet;
import org.oscim.renderer.BufferObject;
import org.oscim.renderer.GLRenderer;
import org.oscim.renderer.GLRenderer.Matrices;
import org.oscim.renderer.GLState;
import org.oscim.renderer.LineRenderer;
import org.oscim.renderer.MapTile;
import org.oscim.renderer.PolygonRenderer;
import org.oscim.renderer.TextureRenderer;
import org.oscim.renderer.TileSet;
import org.oscim.renderer.layer.Layer;
import org.oscim.renderer.layer.Layers;
import org.oscim.renderer.layer.LineLayer;
@@ -199,11 +200,13 @@ public class TextOverlay extends BasicOverlay {
private float mSquareRadius;
private int mRelabelCnt;
private final TileRenderLayer mTileLayer;
public TextOverlay(MapView mapView) {
public TextOverlay(MapView mapView, TileRenderLayer baseLayer) {
super(mapView);
mMapViewPosition = mapView.getMapViewPosition();
mMapViewPosition = mapView.getMapViewPosition();
mTileLayer = baseLayer;
layers.textureLayers = new TextLayer();
mTmpLayer = new TextLayer();
mActiveTiles = new HashMap<MapTile, LabelTile>();
@@ -350,7 +353,9 @@ public class TextOverlay extends BasicOverlay {
return false;
// get current tiles
mTileSet = GLRenderer.getVisibleTiles(mTileSet);
mTileSet = mTileLayer.getVisibleTiles(mTileSet);
if (mTileSet == null)
return false;
if (mTileSet.cnt == 0)
return false;
@@ -605,7 +610,7 @@ public class TextOverlay extends BasicOverlay {
tl.labels = null;
// remove tile locks
GLRenderer.releaseTiles(mTileSet);
mTileLayer.releaseTiles(mTileSet);
// pass new labels for rendering
synchronized (this) {