vtm/src/org/mapsforge/android/glrenderer/MapRenderer.java
Hannes Janetzek 165221f42a tweaks
2013-10-09 01:17:33 +02:00

1438 lines
38 KiB
Java

/*
* 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 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.mapsforge.android.glrenderer;
import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_BLEND;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_DEPTH_TEST;
import static android.opengl.GLES20.GL_DITHER;
import static android.opengl.GLES20.GL_DYNAMIC_DRAW;
import static android.opengl.GLES20.GL_EQUAL;
import static android.opengl.GLES20.GL_EXTENSIONS;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_INVERT;
import static android.opengl.GLES20.GL_NEVER;
import static android.opengl.GLES20.GL_ONE_MINUS_SRC_ALPHA;
import static android.opengl.GLES20.GL_SCISSOR_TEST;
import static android.opengl.GLES20.GL_SRC_ALPHA;
import static android.opengl.GLES20.GL_STENCIL_BUFFER_BIT;
import static android.opengl.GLES20.GL_STENCIL_TEST;
import static android.opengl.GLES20.GL_TRIANGLE_FAN;
import static android.opengl.GLES20.GL_TRIANGLE_STRIP;
import static android.opengl.GLES20.GL_ZERO;
import static android.opengl.GLES20.glBindBuffer;
import static android.opengl.GLES20.glBlendFunc;
import static android.opengl.GLES20.glBufferData;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glClearStencil;
import static android.opengl.GLES20.glColorMask;
import static android.opengl.GLES20.glDepthMask;
import static android.opengl.GLES20.glDisable;
import static android.opengl.GLES20.glDisableVertexAttribArray;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnable;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glFinish;
import static android.opengl.GLES20.glGenBuffers;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetString;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glScissor;
import static android.opengl.GLES20.glStencilFunc;
import static android.opengl.GLES20.glStencilMask;
import static android.opengl.GLES20.glStencilOp;
import static android.opengl.GLES20.glUniform1f;
import static android.opengl.GLES20.glUniform2f;
import static android.opengl.GLES20.glUniform4f;
import static android.opengl.GLES20.glUniform4fv;
import static android.opengl.GLES20.glUniformMatrix4fv;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import org.mapsforge.android.DebugSettings;
import org.mapsforge.android.MapView;
import org.mapsforge.android.mapgenerator.IMapGenerator;
import org.mapsforge.android.mapgenerator.JobParameters;
import org.mapsforge.android.mapgenerator.MapGeneratorJob;
import org.mapsforge.android.mapgenerator.TileCacheKey;
import org.mapsforge.android.mapgenerator.TileDistanceSort;
import org.mapsforge.android.utils.GlConfigChooser;
import org.mapsforge.android.utils.GlUtils;
import org.mapsforge.core.MapPosition;
import org.mapsforge.core.MercatorProjection;
import org.mapsforge.core.Tile;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.SystemClock;
import android.util.FloatMath;
import android.util.Log;
/**
* TODO - use proxy child/parent tile nearer to current tile (currently it is always parent first) - use stencil instead
* of scissor mask for rotation - draw up to two parents above current tile, maybe prefetch parent
*/
public class MapRenderer implements org.mapsforge.android.IMapRenderer {
private static final String TAG = "MapRenderer";
private static final int MB = 1024 * 1024;
// private boolean mTriangulate = false;
private static int CACHE_TILES_MAX = 400;
private static int CACHE_TILES = CACHE_TILES_MAX;
private static int LIMIT_BUFFERS = 20 * MB;
private static final int OES_HALF_FLOAT = 0x8D61;
private static final int FLOAT_BYTES = 4;
private static final int SHORT_BYTES = 2;
private static final int POLYGON_VERTICES_DATA_POS_OFFSET = 0;
private static final int LINE_VERTICES_DATA_POS_OFFSET = 0;
private static final int LINE_VERTICES_DATA_TEX_OFFSET = 8;
private static int STENCIL_BITS = 8;
private final MapView mMapView;
private final ArrayList<MapGeneratorJob> mJobList;
private final ArrayList<VertexBufferObject> mVBOs;
private final TileCacheKey mTileCacheKey;
private final HashMap<TileCacheKey, GLMapTile> mTiles;
private final ArrayList<GLMapTile> mTileList;
private final TileDistanceSort mTileDistanceSort;
private DebugSettings mDebugSettings;
private JobParameters mJobParameter;
private MapPosition mMapPosition, mPrevMapPosition;
private int mWidth, mHeight;
private float mAspect;
// draw position is updated from current position in onDrawFrame
// keeping the position consistent while drawing
private double mDrawX, mDrawY, mDrawZ, mCurX, mCurY, mCurZ;
private float mDrawScale, mCurScale;
// current center tile
private long mTileX, mTileY;
private FloatBuffer floatBuffer[];
private ShortBuffer shortBuffer[];
boolean useHalfFloat = false;
// bytes currently loaded in VBOs
private int mBufferMemoryUsage;
// flag set by updateVisibleList when current visible tiles changed.
// used in onDrawFrame to nextTiles to curTiles
private boolean mUpdateTiles;
class TilesData {
int cnt = 0;
final GLMapTile[] tiles;
TilesData(int numTiles) {
tiles = new GLMapTile[numTiles];
}
}
private float[] mMVPMatrix = new float[16];
// private float[] mMMatrix = new float[16];
// private float[] mRMatrix = new float[16];
// newTiles is set in updateVisibleList and synchronized swapped
// with nextTiles on main thread.
// nextTiles is swapped with curTiles in onDrawFrame in GL thread.
private TilesData newTiles, nextTiles, curTiles;
private boolean mInitial;
// shader handles
private int gLineProgram;
private int gLineVertexPositionHandle;
private int gLineTexturePositionHandle;
private int gLineColorHandle;
private int gLineMatrixHandle;
private int gLineModeHandle;
private int gLineWidthHandle;
private int gPolygonProgram;
private int gPolygonVertexPositionHandle;
private int gPolygonMatrixHandle;
private int gPolygonColorHandle;
/**
*
*/
public boolean timing = false;
/**
* @param mapView
* the MapView
*/
public MapRenderer(MapView mapView) {
Log.d(TAG, "init MapRenderer");
mMapView = mapView;
mDebugSettings = mapView.getDebugSettings();
mVBOs = new ArrayList<VertexBufferObject>();
mJobList = new ArrayList<MapGeneratorJob>();
mTiles = new HashMap<TileCacheKey, GLMapTile>(CACHE_TILES * 2);
mTileList = new ArrayList<GLMapTile>();
mTileCacheKey = new TileCacheKey();
mTileDistanceSort = new TileDistanceSort();
Matrix.setIdentityM(mMVPMatrix, 0);
mInitial = true;
mUpdateTiles = false;
}
private void updateTileDistances() {
int h = (Tile.TILE_SIZE >> 1);
byte zoom = mMapPosition.zoomLevel;
long x = mTileX * (Tile.TILE_SIZE) + h;
long y = mTileY * (Tile.TILE_SIZE) + h;
int diff;
long dx, dy;
// TODO this could be optimized to consider move/zoom direction
for (int i = 0, n = mTileList.size(); i < n; i++) {
GLMapTile t = mTileList.get(i);
diff = (t.zoomLevel - zoom);
if (diff != 0) {
if (diff > 0) {
// tile zoom level is child of current
dx = ((t.pixelX + h) >> diff) - x;
dy = ((t.pixelY + h) >> diff) - y;
} else {
// tile zoom level is parent of current
dx = ((t.pixelX + h) << -diff) - x;
dy = ((t.pixelY + h) << -diff) - y;
}
if (diff == -1) {
dy *= mAspect;
t.distance = 2 + ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy));
} else {
// load parent before current layer (kind of progressive transmission :)
t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy));
// prefer lower zoom level, i.e. it covers a larger area
t.distance *= (1 + (diff > 0 ? diff * 4 : -diff * 2));
}
} else {
dx = (t.pixelX + h) - x;
dy = (t.pixelY + h) - y;
dy *= mAspect;
t.distance = (1 + ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)));// * 2;
}
// Log.d(TAG, t + " " + t.distance);
}
}
private void limitCache(int remove) {
byte z = mMapPosition.zoomLevel;
for (int j = mTileList.size() - 1, cnt = 0; cnt < remove && j > 0; j--, cnt++) {
GLMapTile t = mTileList.remove(j);
if (t.isActive) {
// Log.d(TAG, "EEEK removing active tile");
mTileList.add(t);
continue;
}
// check if this tile is used as proxy for not yet drawn active tile
if (t.isDrawn || t.newData || t.isLoading) {
if (t.zoomLevel == z + 1) {
if (t.parent != null && t.parent.isActive
&& !(t.parent.isDrawn || t.parent.newData)) {
mTileList.add(t);
Log.d(TAG, "EEEK removing active proxy child");
continue;
}
} else if (t.zoomLevel == z - 1) {
GLMapTile c = null;
for (int i = 0; i < 4; i++) {
c = t.child[i];
if (c != null && c.isActive && !(c.isDrawn || c.newData))
break;
c = null;
}
if (c != null) {
Log.d(TAG, "EEEK removing active proxy parent");
mTileList.add(t);
continue;
}
} else if (t.zoomLevel == z - 2) {
GLMapTile c = null, c2 = null;
for (int i = 0; i < 4; i++) {
c = t.child[i];
if (c != null) {
for (int k = 0; k < 4; k++) {
c2 = c.child[k];
if (c2 != null && c2.isActive
&& !(c2.isDrawn || c2.newData))
break;
c2 = null;
}
if (c2 != null)
break;
}
c = null;
}
if (c != null) {
Log.d(TAG, "EEEK removing active second level proxy parent");
mTileList.add(t);
continue;
}
}
}
mTileCacheKey.set(t.tileX, t.tileY, t.zoomLevel);
mTiles.remove(mTileCacheKey);
// clear references to this tile
for (int i = 0; i < 4; i++) {
if (t.child[i] != null)
t.child[i].parent = null;
}
if (t.parent != null) {
for (int i = 0; i < 4; i++) {
if (t.parent.child[i] == t) {
t.parent.child[i] = null;
break;
}
}
}
if (t.lineVBO != null) {
synchronized (mVBOs) {
mVBOs.add(t.lineVBO);
mVBOs.add(t.polygonVBO);
t.lineVBO = null;
t.polygonVBO = null;
}
}
}
}
private boolean updateVisibleList(double x, double y) {
byte zoomLevel = mMapPosition.zoomLevel;
float scale = mMapPosition.scale;
double add = 1.0f / scale;
int offsetX = (int) ((mWidth >> 1) * add);
int offsetY = (int) ((mHeight >> 1) * add);
long pixelRight = (long) x + offsetX;
long pixelBottom = (long) y + offsetY;
long pixelLeft = (long) x - offsetX;
long pixelTop = (long) y - offsetY;
long tileLeft = MercatorProjection.pixelXToTileX(pixelLeft, zoomLevel) - 1;
long tileTop = MercatorProjection.pixelYToTileY(pixelTop, zoomLevel) - 1;
long tileRight = MercatorProjection.pixelXToTileX(pixelRight, zoomLevel) + 1;
long tileBottom = MercatorProjection.pixelYToTileY(pixelBottom, zoomLevel) + 1;
mJobList.clear();
mJobParameter = mMapView.getJobParameters();
int tiles = 0;
if (newTiles == null)
return false;
int max = newTiles.tiles.length - 1;
long limit = (long) Math.pow(2, zoomLevel) - 1;
if (tileTop < 0)
tileTop = 0;
if (tileLeft < 0)
tileLeft = 0;
if (tileBottom >= limit)
tileBottom = limit;
if (tileRight >= limit)
tileRight = limit;
for (long tileY = tileTop; tileY <= tileBottom; tileY++) {
for (long tileX = tileLeft; tileX <= tileRight; tileX++) {
// FIXME
if (tiles == max)
break;
GLMapTile tile = mTiles.get(mTileCacheKey.set(tileX, tileY, zoomLevel));
if (tile == null) {
tile = new GLMapTile(tileX, tileY, zoomLevel);
TileCacheKey key = new TileCacheKey(mTileCacheKey);
// FIXME use sparse matrix or sth.
if (mTiles.put(key, tile) != null)
Log.d(TAG, "eeek collision");
mTileList.add(tile);
mTileCacheKey.set((tileX >> 1), (tileY >> 1), (byte) (zoomLevel - 1));
tile.parent = mTiles.get(mTileCacheKey);
int idx = (int) ((tileX & 0x01) + 2 * (tileY & 0x01));
// set this tile to be child of its parent
if (tile.parent != null) {
tile.parent.child[idx] = tile;
}
else if (zoomLevel > 0) {
tile.parent = new GLMapTile(tileX >> 1, tileY >> 1,
(byte) (zoomLevel - 1));
key = new TileCacheKey(mTileCacheKey);
if (mTiles.put(key, tile.parent) != null)
Log.d(TAG, "eeek collision");
mTileList.add(tile.parent);
setChildren(tile.parent);
}
setChildren(tile);
}
newTiles.tiles[tiles++] = tile;
if (!tile.isDrawn && !tile.newData && !tile.isLoading) {
MapGeneratorJob job = new MapGeneratorJob(tile, mJobParameter,
mDebugSettings);
mJobList.add(job);
}
// prefetch parent
// if (tile.parent != null && !tile.parent.isDrawn && !tile.parent.newData
// && !tile.parent.isLoading) {
// MapGeneratorJob job = new MapGeneratorJob(tile.parent, mJobParameter,
// mDebugSettings);
// if (!mJobList.contains(job))
// mJobList.add(job);
// }
}
}
updateTileDistances();
// scramble tile draw order, might help to make draw calls independent... just a guess :)
for (int i = 1; i < tiles / 2; i += 2) {
GLMapTile tmp = newTiles.tiles[i];
newTiles.tiles[i] = newTiles.tiles[tiles - i];
newTiles.tiles[tiles - i] = tmp;
}
int removes = mTiles.size() - CACHE_TILES;
if (removes > 0)
Collections.sort(mTileList, mTileDistanceSort);
synchronized (this) {
for (int i = 0; i < nextTiles.cnt; i++)
nextTiles.tiles[i].isActive = false;
for (int i = 0; i < curTiles.cnt; i++)
curTiles.tiles[i].isActive = true;
for (int i = 0; i < tiles; i++)
newTiles.tiles[i].isActive = true;
TilesData tmp = nextTiles;
nextTiles = newTiles;
nextTiles.cnt = tiles;
newTiles = tmp;
mUpdateTiles = true;
}
limitCache(removes);
if (mJobList.size() > 0)
mMapView.addJobs(mJobList);
return true;
}
private void setChildren(GLMapTile tile) {
long xx = tile.tileX << 1;
long yy = tile.tileY << 1;
byte z = (byte) (tile.zoomLevel + 1);
tile.child[0] = mTiles.get(mTileCacheKey.set(xx, yy, z));
tile.child[1] = mTiles.get(mTileCacheKey.set(xx + 1, yy, z));
tile.child[2] = mTiles.get(mTileCacheKey.set(xx, yy + 1, z));
tile.child[3] = mTiles.get(mTileCacheKey.set(xx + 1, yy + 1, z));
// set this tile to be parent of its children
for (int i = 0; i < 4; i++) {
if (tile.child[i] != null)
tile.child[i].parent = tile;
}
}
/**
*
*/
@Override
public synchronized void redrawTiles(boolean clear) {
boolean changedPos = false;
boolean changedZoom = false;
mMapPosition = mMapView.getMapPosition().getMapPosition();
if (mMapPosition == null) {
Log.d(TAG, ">>> no map position");
return;
}
if (clear) {
mInitial = true;
for (GLMapTile t : mTileList) {
t.isDrawn = false;
t.isLoading = false;
t.newData = false;
}
}
byte zoomLevel = mMapPosition.zoomLevel;
float scale = mMapPosition.scale;
double x = MercatorProjection.longitudeToPixelX(
mMapPosition.geoPoint.getLongitude(), zoomLevel);
double y = MercatorProjection.latitudeToPixelY(
mMapPosition.geoPoint.getLatitude(), zoomLevel);
long tileX = MercatorProjection.pixelXToTileX(x, zoomLevel);
long tileY = MercatorProjection.pixelYToTileY(y, zoomLevel);
if (mInitial || mPrevMapPosition.zoomLevel != zoomLevel)
changedZoom = true;
else if (tileX != mTileX || tileY != mTileY)
changedPos = true;
mInitial = false;
mTileX = tileX;
mTileY = tileY;
mPrevMapPosition = mMapPosition;
if (changedZoom)
updateVisibleList(x, y);
synchronized (this) {
// do not change position while drawing
mCurX = x;
mCurY = y;
mCurZ = zoomLevel;
mCurScale = scale;
}
if (!timing)
mMapView.requestRender();
if (changedPos)
updateVisibleList(x, y);
}
@Override
public boolean passTile(MapGeneratorJob mapGeneratorJob) {
if (!timing && mapGeneratorJob.tile.isVisible)
mMapView.requestRender();
return true;
}
private PolygonLayer[] mFillPolys;
private void fillPolygons(int count) {
boolean blend = false;
// draw to framebuffer
glColorMask(true, true, true, true);
// do not modify stencil buffer
glStencilMask(0);
for (int c = 0; c < count; c++) {
PolygonLayer l = mFillPolys[c];
float alpha = 1.0f;
if (l.fadeLevel >= mDrawZ) {
alpha = (mDrawScale > 1.3f ? mDrawScale : 1.3f) - alpha;
if (alpha > 1.0f)
alpha = 1.0f;
if (!blend) {
glEnable(GL_BLEND);
blend = true;
}
} else if (blend) {
glDisable(GL_BLEND);
blend = false;
}
glUniform4f(gPolygonColorHandle,
l.colors[0], l.colors[1], l.colors[2], alpha);
// set stencil buffer mask used to draw this layer
glStencilFunc(GL_EQUAL, 0xff, 1 << c);
// draw tile fill coordinates
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
if (blend)
glDisable(GL_BLEND);
}
private boolean drawPolygons(GLMapTile tile, int diff) {
int cnt = 0;
if (tile.polygonLayers == null || tile.polygonLayers.array == null)
return true;
glScissor(tile.sx, tile.sy, tile.sw, tile.sh);
if (mLastBoundVBO != tile.polygonVBO.id) {
mLastBoundVBO = tile.polygonVBO.id;
glBindBuffer(GL_ARRAY_BUFFER, tile.polygonVBO.id);
if (useHalfFloat) {
glVertexAttribPointer(gPolygonVertexPositionHandle, 2,
OES_HALF_FLOAT, false, 0,
POLYGON_VERTICES_DATA_POS_OFFSET);
} else {
glVertexAttribPointer(gPolygonVertexPositionHandle, 2,
GL_FLOAT, false, 0,
POLYGON_VERTICES_DATA_POS_OFFSET);
}
// glBindBuffer(GL_ARRAY_BUFFER, 0);
}
setMatrix(tile, diff);
glUniformMatrix4fv(gPolygonMatrixHandle, 1, false, mMVPMatrix, 0);
boolean firstPass = true;
for (int i = 0, n = tile.polygonLayers.array.length; i < n; i++) {
PolygonLayer l = tile.polygonLayers.array[i];
// fade out polygon layers (set in RederTheme)
if (l.fadeLevel > 0 && l.fadeLevel > mDrawZ)
continue;
if (cnt == 0) {
// disable drawing to framebuffer
glColorMask(false, false, false, false);
// never pass the test, i.e. always apply first stencil op (sfail)
glStencilFunc(GL_NEVER, 0, 0xff);
if (firstPass)
firstPass = false;
else {
// eeek, nexus! - cant do old-school polygons
// glFinish();
// clear stencilbuffer
glStencilMask(0xFF);
// glClear(GL_STENCIL_BUFFER_BIT);
// clear stencilbuffer (tile region)
glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
// stencil op for stencil method polygon drawing
glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT);
}
mFillPolys[cnt] = l;
// set stencil mask to draw to
glStencilMask(1 << cnt++);
glDrawArrays(GL_TRIANGLE_FAN, l.offset, l.verticesCnt);
// draw up to 8 layers into stencil buffer
if (cnt == STENCIL_BITS) {
fillPolygons(cnt);
cnt = 0;
}
}
if (cnt > 0) {
fillPolygons(cnt);
// eeek, nexus! - cant do old-school polygons
// glFinish();
}
return true;
}
private int mLastBoundVBO;
// private boolean drawTriangles(GLMapTile tile, int diff) {
//
// if (tile.meshLayers == null || tile.meshLayers.array == null)
// return true;
//
// glScissor(tile.sx, tile.sy, tile.sw, tile.sh);
//
// if (mLastBoundVBO != tile.polygonVBO.id) {
// mLastBoundVBO = tile.polygonVBO.id;
// glBindBuffer(GL_ARRAY_BUFFER, tile.polygonVBO.id);
//
// if (useHalfFloat) {
// glVertexAttribPointer(gPolygonVertexPositionHandle, 2,
// OES_HALF_FLOAT, false, 0,
// POLYGON_VERTICES_DATA_POS_OFFSET);
// } else {
// glVertexAttribPointer(gPolygonVertexPositionHandle, 2,
// GL_FLOAT, false, 0,
// POLYGON_VERTICES_DATA_POS_OFFSET);
// }
//
// // glBindBuffer(GL_ARRAY_BUFFER, 0);
// }
// setMatrix(tile, diff);
// glUniformMatrix4fv(gPolygonMatrixHandle, 1, false, mMVPMatrix, 0);
//
// MeshLayer[] layers = tile.meshLayers.array;
//
// for (int i = 0, n = layers.length; i < n; i++) {
// MeshLayer l = layers[i];
// glUniform4fv(gPolygonColorHandle, 1, l.colors, 0);
//
// // glUniform4f(gPolygonColorHandle, 1, 0, 0, 1);
//
// // System.out.println("draw: " + l.offset + " " + l.verticesCnt);
// glDrawArrays(GL_TRIANGLES, l.offset, l.verticesCnt);
// }
//
// return true;
// }
private boolean drawLines(GLMapTile tile, int diff) {
float z = 1;
if (tile.lineLayers == null || tile.lineLayers.array == null)
return false;
glScissor(tile.sx, tile.sy, tile.sw, tile.sh);
if (mLastBoundVBO != tile.lineVBO.id) {
mLastBoundVBO = tile.lineVBO.id;
glBindBuffer(GL_ARRAY_BUFFER, tile.lineVBO.id);
if (useHalfFloat) {
glVertexAttribPointer(gLineVertexPositionHandle, 2, OES_HALF_FLOAT,
false, 8, LINE_VERTICES_DATA_POS_OFFSET);
glVertexAttribPointer(gLineTexturePositionHandle, 2, OES_HALF_FLOAT,
false, 8, LINE_VERTICES_DATA_TEX_OFFSET >> 1);
} else {
glVertexAttribPointer(gLineVertexPositionHandle, 2, GL_FLOAT,
false, 16, LINE_VERTICES_DATA_POS_OFFSET);
glVertexAttribPointer(gLineTexturePositionHandle, 2, GL_FLOAT,
false, 16, LINE_VERTICES_DATA_TEX_OFFSET);
}
// glBindBuffer(GL_ARRAY_BUFFER, 0);
}
if (diff != 0)
z = (diff > 0) ? (1 << diff) : 1.0f / (1 << -diff);
setMatrix(tile, diff);
glUniformMatrix4fv(gLineMatrixHandle, 1, false, mMVPMatrix, 0);
LineLayer[] layers = tile.lineLayers.array;
boolean drawOutlines = false;
boolean drawFixed = false;
// stroke scale factor
float wdiv = 1.0f / FloatMath.sqrt(mDrawScale / z);
// linear scale for fixed lines
float fdiv = 0.9f / (mDrawScale / z);
// int cnt = 0;
for (int i = 0, n = layers.length; i < n; i++) {
LineLayer l = layers[i];
// set line width and mode
if ((i == 0) || (l.isOutline != drawOutlines) || (l.isFixed != drawFixed)) {
drawOutlines = l.isOutline;
drawFixed = l.isFixed;
if (drawOutlines) {
glUniform2f(gLineModeHandle, 0, wdiv);
} else if (!drawFixed) {
glUniform2f(gLineModeHandle, 0, wdiv * 0.98f);
}
}
if (drawFixed) {
if (l.width < 1.0)
glUniform2f(gLineModeHandle, 0.4f, fdiv);
else
glUniform2f(gLineModeHandle, 0, fdiv);
}
glUniform4fv(gLineColorHandle, 1, l.colors, 0);
if (drawOutlines) {
for (int j = 0, m = l.outlines.size(); j < m; j++) {
LineLayer o = l.outlines.get(j);
if (mSimpleLines)
glUniform1f(gLineWidthHandle, o.width);
glDrawArrays(GL_TRIANGLE_STRIP, o.offset, o.verticesCnt);
}
}
else {
if (mSimpleLines)
glUniform1f(gLineWidthHandle, l.width);
glDrawArrays(GL_TRIANGLE_STRIP, l.offset, l.verticesCnt);
}
}
return true;
}
private void setMatrix(GLMapTile tile, int diff) {
float x, y, scale;
float z = 1;
if (diff == 0) {
scale = (float) (mDrawScale * 2.0 / mHeight);
x = (float) (mDrawX - tile.x);
y = (float) (tile.y - mDrawY);
} else {
z = (diff > 0) ? (1 << diff) : 1.0f / (1 << -diff);
scale = (float) (mDrawScale * 2.0 / mHeight / z);
x = (float) (mDrawX * z - tile.x);
y = (float) (tile.y - mDrawY * z);
}
mMVPMatrix[12] = -x * (scale * mAspect);
mMVPMatrix[13] = -y * (scale);
mMVPMatrix[0] = (scale * mAspect);
mMVPMatrix[5] = (scale);
}
private boolean setTileScissor(GLMapTile tile, float div) {
double dx, dy, scale;
if (div == 0) {
dx = tile.pixelX - mDrawX;
dy = tile.pixelY - mDrawY;
scale = mDrawScale;
} else {
dx = tile.pixelX - mDrawX * div;
dy = tile.pixelY - mDrawY * div;
scale = mDrawScale / div;
}
int sx = (int) (dx * scale);
int sy = (int) (dy * scale);
int sw = (int) ((dx + Tile.TILE_SIZE) * scale) - sx;
int sh = (int) ((dy + Tile.TILE_SIZE) * scale) - sy;
sx = (mWidth >> 1) + sx;
sy = (mHeight >> 1) - (sy + sh);
// shrink width/height to screen intersection
if (sx < 0) {
sw += sx;
sx = 0;
}
if (sy < 0) {
sh += sy;
sy = 0;
}
if (sw + sx > mWidth)
sw = mWidth - sx;
if (sh + (sy - sh) > mHeight)
sh = mHeight - sy;
if (sw <= 0 || sh <= 0) {
tile.isVisible = false;
return false;
}
tile.isVisible = true;
tile.sx = sx;
tile.sy = sy;
tile.sw = sw;
tile.sh = sh;
return true;
}
private void drawProxyLines(GLMapTile tile) {
if (tile.parent != null && tile.parent.isDrawn) {
tile.parent.sx = tile.sx;
tile.parent.sy = tile.sy;
tile.parent.sw = tile.sw;
tile.parent.sh = tile.sh;
drawLines(tile.parent, -1);
} else {
int drawn = 0;
// scissor coordinates already set for polygons
for (int i = 0; i < 4; i++) {
GLMapTile c = tile.child[i];
if (c != null && c.isDrawn && c.isVisible) {
drawLines(c, 1);
drawn++;
}
}
if (drawn < 4 && tile.parent != null) {
GLMapTile p = tile.parent.parent;
if (p != null && p.isDrawn) {
p.sx = tile.sx;
p.sy = tile.sy;
p.sw = tile.sw;
p.sh = tile.sh;
drawLines(p, -2);
}
}
}
}
private void drawProxyPolygons(GLMapTile tile) {
if (tile.parent != null && tile.parent.isDrawn) {
tile.parent.sx = tile.sx;
tile.parent.sy = tile.sy;
tile.parent.sw = tile.sw;
tile.parent.sh = tile.sh;
drawPolygons(tile.parent, -1);
} else {
int drawn = 0;
for (int i = 0; i < 4; i++) {
GLMapTile c = tile.child[i];
if (c != null && c.isDrawn && setTileScissor(c, 2)) {
drawPolygons(c, 1);
drawn++;
}
}
if (drawn < 4 && tile.parent != null) {
GLMapTile p = tile.parent.parent;
if (p != null && p.isDrawn) {
p.sx = tile.sx;
p.sy = tile.sy;
p.sw = tile.sw;
p.sh = tile.sh;
drawPolygons(p, -2);
}
}
}
}
// private void drawProxyTriangles(GLMapTile tile) {
// if (tile.parent != null && tile.parent.isDrawn) {
// tile.parent.sx = tile.sx;
// tile.parent.sy = tile.sy;
// tile.parent.sw = tile.sw;
// tile.parent.sh = tile.sh;
// drawTriangles(tile.parent, -1);
// } else {
// int drawn = 0;
//
// for (int i = 0; i < 4; i++) {
// GLMapTile c = tile.child[i];
//
// if (c != null && c.isDrawn && setTileScissor(c, 2)) {
// drawTriangles(c, 1);
// drawn++;
// }
// }
//
// if (drawn < 4 && tile.parent != null) {
// GLMapTile p = tile.parent.parent;
// if (p != null && p.isDrawn) {
// p.sx = tile.sx;
// p.sy = tile.sy;
// p.sw = tile.sw;
// p.sh = tile.sh;
// drawTriangles(p, -2);
// }
// }
// }
// }
private int uploadCnt = 0;
private boolean uploadTileData(GLMapTile tile) {
// double start = SystemClock.uptimeMillis();
// use multiple buffers to avoid overwriting buffer while current data is uploaded
// (or rather the blocking which is required to avoid this)
if (uploadCnt >= 10) {
// mMapView.requestRender();
// return false;
uploadCnt = 0;
glFinish();
}
// Upload line data to vertex buffer object
if (tile.lineVBO == null) {
synchronized (mVBOs) {
if (mVBOs.size() < 2) {
Log.d(TAG, "uploadTileData, no VBOs left");
return false;
}
tile.lineVBO = mVBOs.remove(mVBOs.size() - 1);
tile.polygonVBO = mVBOs.remove(mVBOs.size() - 1);
}
}
if (useHalfFloat)
shortBuffer[uploadCnt * 2] = tile.lineLayers
.compileLayerData(shortBuffer[uploadCnt * 2]);
else
floatBuffer[uploadCnt * 2] = tile.lineLayers
.compileLayerData(floatBuffer[uploadCnt * 2]);
if (tile.lineLayers.size > 0) {
mBufferMemoryUsage -= tile.lineVBO.size;
glBindBuffer(GL_ARRAY_BUFFER, tile.lineVBO.id);
// glBufferData(GL_ARRAY_BUFFER, 0, null, GL_DYNAMIC_DRAW);
if (useHalfFloat) {
tile.lineVBO.size = tile.lineLayers.size * SHORT_BYTES;
glBufferData(GL_ARRAY_BUFFER, tile.lineVBO.size,
shortBuffer[uploadCnt * 2], GL_DYNAMIC_DRAW);
} else {
tile.lineVBO.size = tile.lineLayers.size * FLOAT_BYTES;
glBufferData(GL_ARRAY_BUFFER, tile.lineVBO.size,
floatBuffer[uploadCnt * 2], GL_DYNAMIC_DRAW);
}
mBufferMemoryUsage += tile.lineVBO.size;
} else {
tile.lineLayers = null;
}
// if (!mTriangulate) {
if (useHalfFloat)
shortBuffer[uploadCnt * 2 + 1] = tile.polygonLayers
.compileLayerData(shortBuffer[uploadCnt * 2 + 1]);
else
floatBuffer[uploadCnt * 2 + 1] = tile.polygonLayers
.compileLayerData(floatBuffer[uploadCnt * 2 + 1]);
// Upload polygon data to vertex buffer object
if (tile.polygonLayers.size > 0) {
mBufferMemoryUsage -= tile.polygonVBO.size;
glBindBuffer(GL_ARRAY_BUFFER, tile.polygonVBO.id);
// glBufferData(GL_ARRAY_BUFFER, 0, null,
// GL_DYNAMIC_DRAW);
if (useHalfFloat) {
tile.polygonVBO.size = tile.polygonLayers.size * SHORT_BYTES;
glBufferData(GL_ARRAY_BUFFER, tile.polygonVBO.size,
shortBuffer[uploadCnt * 2 + 1], GL_DYNAMIC_DRAW);
} else {
tile.polygonVBO.size = tile.polygonLayers.size * FLOAT_BYTES;
glBufferData(GL_ARRAY_BUFFER, tile.polygonVBO.size,
floatBuffer[uploadCnt * 2 + 1], GL_DYNAMIC_DRAW);
}
mBufferMemoryUsage += tile.polygonVBO.size;
} else {
tile.polygonLayers = null;
}
// }
// else {
// if (useHalfFloat)
// shortBuffer[uploadCnt * 2 + 1] = tile.meshLayers
// .compileLayerData(shortBuffer[uploadCnt * 2 + 1]);
// else
// floatBuffer[uploadCnt * 2 + 1] = tile.meshLayers
// .compileLayerData(floatBuffer[uploadCnt * 2 + 1]);
//
// // Upload triangle data to vertex buffer object
// if (tile.meshLayers.size > 0) {
// mBufferMemoryUsage -= tile.polygonVBO.size;
//
// glBindBuffer(GL_ARRAY_BUFFER, tile.polygonVBO.id);
// // glBufferData(GL_ARRAY_BUFFER, 0, null,
// // GL_DYNAMIC_DRAW);
//
// if (useHalfFloat) {
// tile.polygonVBO.size = tile.meshLayers.size * SHORT_BYTES;
// glBufferData(GL_ARRAY_BUFFER, tile.polygonVBO.size,
// shortBuffer[uploadCnt * 2 + 1], GL_DYNAMIC_DRAW);
// } else {
// tile.polygonVBO.size = tile.meshLayers.size * FLOAT_BYTES;
// glBufferData(GL_ARRAY_BUFFER, tile.polygonVBO.size,
// floatBuffer[uploadCnt * 2 + 1], GL_DYNAMIC_DRAW);
// }
// mBufferMemoryUsage += tile.polygonVBO.size;
//
// } else {
// tile.meshLayers = null;
// }
// }
tile.newData = false;
tile.isDrawn = true;
tile.isLoading = false;
// double compile = SystemClock.uptimeMillis();
// glFinish();
// double now = SystemClock.uptimeMillis();
// Log.d(TAG, tile + " - upload took: " + (now - start) + " "
// + (mBufferMemoryUsage / 1024) + "kb");
uploadCnt++;
return true;
}
// private long startTime = SystemClock.uptimeMillis();
@Override
public void onDrawFrame(GL10 glUnused) {
long start = 0, poly_time = 0, clear_time = 0;
if (mMapPosition == null)
return;
if (timing)
start = SystemClock.uptimeMillis();
glStencilMask(0xFF);
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// long endTime = SystemClock.uptimeMillis();
// long dt = endTime - startTime;
// if (dt < 33)
// try {
// Thread.sleep(33 - dt);
// } catch (InterruptedException e) {
// Log.d(TAG, "interrupt");
// return;
// }
// startTime = SystemClock.uptimeMillis();
synchronized (this) {
mDrawX = mCurX;
mDrawY = mCurY;
mDrawZ = mCurZ;
mDrawScale = mCurScale;
if (mUpdateTiles) {
TilesData tmp = curTiles;
curTiles = nextTiles;
nextTiles = tmp;
mUpdateTiles = false;
}
}
int tileCnt = curTiles.cnt;
GLMapTile[] tiles = curTiles.tiles;
if (mBufferMemoryUsage > LIMIT_BUFFERS) {
Log.d(TAG, "buffer object usage: " + mBufferMemoryUsage / MB + "MB");
synchronized (mVBOs) {
for (VertexBufferObject vbo : mVBOs) {
if (vbo.size == 0)
continue;
mBufferMemoryUsage -= vbo.size;
glBindBuffer(GL_ARRAY_BUFFER, vbo.id);
glBufferData(GL_ARRAY_BUFFER, 0, null,
GL_DYNAMIC_DRAW);
vbo.size = 0;
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
Log.d(TAG, " > " + mBufferMemoryUsage / MB + "MB");
if (CACHE_TILES > 50)
CACHE_TILES -= 50;
} else if (CACHE_TILES < CACHE_TILES_MAX) {
CACHE_TILES += 50;
}
uploadCnt = 0;
mLastBoundVBO = -1;
// check visible tiles, set tile clip scissors, upload new vertex data
for (int i = 0; i < tileCnt; i++) {
GLMapTile tile = tiles[i];
if (!setTileScissor(tile, 1))
continue;
if (tile.newData) {
uploadTileData(tile);
continue;
}
if (!tile.isDrawn) {
if (tile.parent != null) {
if (tile.parent.newData)
uploadTileData(tile.parent);
} else {
if (tile.child[0] != null && tile.child[0].newData)
uploadTileData(tile.child[0]);
if (tile.child[1] != null && tile.child[1].newData)
uploadTileData(tile.child[1]);
if (tile.child[2] != null && tile.child[2].newData)
uploadTileData(tile.child[2]);
if (tile.child[3] != null && tile.child[3].newData)
uploadTileData(tile.child[3]);
}
}
}
if (GlUtils.checkGlOutOfMemory("upload: " + mBufferMemoryUsage)
&& LIMIT_BUFFERS > MB)
LIMIT_BUFFERS -= MB;
if (timing)
clear_time = (SystemClock.uptimeMillis() - start);
glEnable(GL_SCISSOR_TEST);
glUseProgram(gPolygonProgram);
glEnableVertexAttribArray(gPolygonVertexPositionHandle);
// if (!mTriangulate) {
glDisable(GL_BLEND);
// Draw Polygons
glEnable(GL_STENCIL_TEST);
// glEnableVertexAttribArray(gPolygonVertexPositionHandle);
for (int i = 0; i < tileCnt; i++) {
if (tiles[i].isVisible) {
GLMapTile tile = tiles[i];
if (tile.isDrawn)
drawPolygons(tile, 0);
else
drawProxyPolygons(tile);
}
}
// GlUtils.checkGlError("polygons");
glDisable(GL_STENCIL_TEST);
// } else {
// // Draw Triangles
// for (int i = 0; i < tileCnt; i++) {
// if (tiles[i].isVisible) {
// GLMapTile tile = tiles[i];
//
// if (tile.isDrawn)
// drawTriangles(tile, 0);
// else
// drawProxyTriangles(tile);
// }
// }
// // GlUtils.checkGlError("triangles");
// }
// required on GalaxyII, Android 2.3.3 (cant just VAA enable once...)
glDisableVertexAttribArray(gPolygonVertexPositionHandle);
if (timing) {
glFinish();
poly_time = (SystemClock.uptimeMillis() - start);
}
// Draw lines
glEnable(GL_BLEND);
glUseProgram(gLineProgram);
glEnableVertexAttribArray(gLineVertexPositionHandle);
glEnableVertexAttribArray(gLineTexturePositionHandle);
for (int i = 0; i < tileCnt; i++) {
if (tiles[i].isVisible) {
GLMapTile tile = tiles[i];
if (tile.isDrawn)
drawLines(tile, 0);
else
drawProxyLines(tile);
}
}
if (timing) {
glFinish();
Log.d(TAG, "draw took " + (SystemClock.uptimeMillis() - start) + " "
+ clear_time + " " + poly_time);
}
glDisableVertexAttribArray(gLineVertexPositionHandle);
glDisableVertexAttribArray(gLineTexturePositionHandle);
// GlUtils.checkGlError("lines");
}
private int[] mVboIds;
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
mVBOs.clear();
mTiles.clear();
mTileList.clear();
curTiles = newTiles = nextTiles = null;
mBufferMemoryUsage = 0;
if (width <= 0 || height <= 0)
return;
STENCIL_BITS = GlConfigChooser.stencilSize;
mFillPolys = new PolygonLayer[STENCIL_BITS];
mWidth = width;
mHeight = height;
mAspect = (float) height / width;
glViewport(0, 0, width, height);
int tiles = (mWidth / Tile.TILE_SIZE + 4) * (mHeight / Tile.TILE_SIZE + 4);
curTiles = new TilesData(tiles);
newTiles = new TilesData(tiles);
nextTiles = new TilesData(tiles);
// Set up vertex buffer objects
int numVBO = (CACHE_TILES + tiles) * 2;
mVboIds = new int[numVBO];
glGenBuffers(numVBO, mVboIds, 0);
for (int i = 0; i < numVBO; i++)
mVBOs.add(new VertexBufferObject(mVboIds[i]));
mDebugSettings = mMapView.getDebugSettings();
mJobParameter = mMapView.getJobParameters();
mInitial = true;
mMapView.redrawTiles();
}
private boolean mSimpleLines = false;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Set up the program for rendering lines
gLineProgram = GlUtils.createProgram(Shaders.gLineVertexShader,
Shaders.gLineFragmentShader);
if (gLineProgram == 0) {
mSimpleLines = true;
Log.e(TAG, "trying simple line program.");
gLineProgram = GlUtils.createProgram(Shaders.gLineVertexShader,
Shaders.gLineFragmentShaderSimple);
if (gLineProgram == 0) {
Log.e(TAG, "Could not create line program.");
return;
}
}
String ext = glGetString(GL_EXTENSIONS);
if (ext.indexOf("GL_OES_vertex_half_float") >= 0) {
useHalfFloat = true;
shortBuffer = new ShortBuffer[20];
}
else {
floatBuffer = new FloatBuffer[20];
}
Log.d(TAG, "Extensions: " + ext);
gLineMatrixHandle = glGetUniformLocation(gLineProgram, "u_center");
gLineModeHandle = glGetUniformLocation(gLineProgram, "u_mode");
gLineColorHandle = glGetUniformLocation(gLineProgram, "u_color");
gLineVertexPositionHandle = GLES20
.glGetAttribLocation(gLineProgram, "a_position");
gLineTexturePositionHandle = glGetAttribLocation(gLineProgram, "a_st");
if (mSimpleLines)
gLineWidthHandle = glGetUniformLocation(gLineProgram, "u_width");
// Set up the program for rendering polygons
gPolygonProgram = GlUtils.createProgram(Shaders.gPolygonVertexShader,
Shaders.gPolygonFragmentShader);
if (gPolygonProgram == 0) {
Log.e(TAG, "Could not create polygon program.");
return;
}
gPolygonMatrixHandle = glGetUniformLocation(gPolygonProgram, "u_center");
gPolygonVertexPositionHandle = glGetAttribLocation(gPolygonProgram,
"a_position");
gPolygonColorHandle = glGetUniformLocation(gPolygonProgram, "u_color");
// glUseProgram(gPolygonProgram);
// glEnableVertexAttribArray(gPolygonVertexPositionHandle);
//
// glUseProgram(gLineProgram);
// glEnableVertexAttribArray(gLineVertexPositionHandle);
// glEnableVertexAttribArray(gLineTexturePositionHandle);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glDepthMask(false);
glDisable(GL_DITHER);
glClearColor(0.98f, 0.98f, 0.97f, 1.0f);
glClearStencil(0);
}
@Override
public boolean processedTile() {
return true;
}
public IMapGenerator getMapGenerator() {
return new MapGenerator();
}
@Override
public IMapGenerator createMapGenerator() {
return new MapGenerator();
}
}