vtm/src/org/oscim/renderer/GLRenderer.java
Hannes Janetzek bcb529906a - extract interface of RenderTheme
- pass MapElement to RenderTheme
- refactor: join duplicated code for node and way matching
2013-10-09 01:56:00 +02:00

401 lines
10 KiB
Java

/*
* 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 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 java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.egl.EGLConfig;
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.overlays.RenderOverlay;
import org.oscim.theme.IRenderTheme;
import org.oscim.utils.GlUtils;
import org.oscim.utils.Matrix4;
import org.oscim.view.MapView;
import org.oscim.view.MapViewPosition;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.SystemClock;
import android.util.Log;
/**
* @author Hannes Janetzek
*/
public class GLRenderer implements GLSurfaceView.Renderer {
private static final String TAG = GLRenderer.class.getName();
private static final int MB = 1024 * 1024;
private static final int SHORT_BYTES = 2;
private static final int CACHE_TILES_MAX = 250;
private static final int LIMIT_BUFFERS = 16 * MB;
public static final float COORD_SCALE = 8.0f;
static int CACHE_TILES = CACHE_TILES_MAX;
private static MapView mMapView;
public static int screenWidth, screenHeight;
private static MapViewPosition mMapViewPosition;
private static MapPosition mMapPosition;
private static ShortBuffer shortBuffer;
private static short[] mFillCoords;
// bytes currently loaded in VBOs
private static int mBufferMemoryUsage;
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();
// for temporary use by callee
public final Matrix4 mvp = new Matrix4();
}
private static Matrices mMatrices;
//private
static float[] mClearColor = null;
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();
public static ReentrantLock drawlock = new ReentrantLock();
/**
* @param mapView
* the MapView
*/
public GLRenderer(MapView mapView) {
mMapView = mapView;
mMapViewPosition = mapView.getMapViewPosition();
mMapPosition = new MapPosition();
mMatrices = new Matrices();
mBoxCoords = new float[8];
// tile fill coords
short min = 0;
short max = (short) ((Tile.SIZE * COORD_SCALE));
mFillCoords = new short[8];
mFillCoords[0] = min;
mFillCoords[1] = max;
mFillCoords[2] = max;
mFillCoords[3] = max;
mFillCoords[4] = min;
mFillCoords[5] = min;
mFillCoords[6] = max;
mFillCoords[7] = min;
}
public static void setRenderTheme(IRenderTheme t) {
mClearColor = GlUtils.colorToFloat(t.getMapBackground());
mUpdateColor = true;
}
public static boolean uploadLayers(Layers layers, int newSize,
boolean addFill) {
GLES20.glBindBuffer(GL_ARRAY_BUFFER, layers.vbo.id);
// add fill coordinates
if (addFill)
newSize += 8;
ShortBuffer sbuf = shortBuffer;
if (sbuf.capacity() < newSize) {
shortBuffer = sbuf = ByteBuffer
.allocateDirect(newSize * SHORT_BYTES)
.order(ByteOrder.nativeOrder())
.asShortBuffer();
} else {
sbuf.clear();
// if (addFill)
// sbuf.position(8);
}
if (addFill)
sbuf.put(mFillCoords, 0, 8);
layers.compile(sbuf, addFill);
sbuf.flip();
if (newSize != sbuf.remaining()) {
Log.d(TAG, "wrong size: "
+ " new size: " + newSize
+ " buffer pos: " + sbuf.position()
+ " buffer limit: " + sbuf.limit()
+ " buffer fill: " + sbuf.remaining());
return false;
}
newSize *= SHORT_BYTES;
// reuse memory allocated for vbo when possible and allocated
// memory is less then four times the new data
if (layers.vbo.size > newSize && layers.vbo.size < newSize * 4
&& mBufferMemoryUsage < LIMIT_BUFFERS) {
GLES20.glBufferSubData(GL_ARRAY_BUFFER, 0, newSize, sbuf);
} else {
mBufferMemoryUsage += newSize - layers.vbo.size;
layers.vbo.size = newSize;
GLES20.glBufferData(GL_ARRAY_BUFFER, layers.vbo.size, sbuf, GL_DYNAMIC_DRAW);
}
return true;
}
public static void checkBufferUsage(boolean force) {
// try to clear some unused vbo when exceding limit
if (!force && mBufferMemoryUsage < LIMIT_BUFFERS) {
if (CACHE_TILES < CACHE_TILES_MAX)
CACHE_TILES += 50;
return;
}
Log.d(TAG, "buffer object usage: " + mBufferMemoryUsage / MB + "MB");
mBufferMemoryUsage -= BufferObject.limitUsage(2 * MB);
Log.d(TAG, "now: " + mBufferMemoryUsage / MB + "MB");
if (mBufferMemoryUsage > LIMIT_BUFFERS && CACHE_TILES > 100)
CACHE_TILES -= 50;
}
private long lastDraw = 0;
@Override
public void onDrawFrame(GL10 glUnused) {
long start = SystemClock.uptimeMillis();
long wait = 30 - (start - lastDraw);
if (wait > 5) {
//Log.d(TAG, "wait " + wait);
SystemClock.sleep(wait);
lastDraw = start + wait;
} else
lastDraw = start;
// prevent main thread recreating all tiles (updateMap)
// while rendering is going on.
drawlock.lock();
try {
draw();
} finally {
drawlock.unlock();
}
}
private static void draw() {
long start = 0;
if (MapView.debugFrameTime)
start = SystemClock.uptimeMillis();
if (mUpdateColor) {
float cc[] = mClearColor;
GLES20.glClearColor(cc[0], cc[1], cc[2], cc[3]);
mUpdateColor = false;
}
GLES20.glDepthMask(true);
GLES20.glStencilMask(0xFF);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT
| GLES20.GL_DEPTH_BUFFER_BIT
| GLES20.GL_STENCIL_BUFFER_BIT);
boolean tilesChanged = true;
boolean positionChanged = true;
// get current MapPosition, set mBoxCoords (mapping of screen to model
// coordinates)
MapPosition pos = mMapPosition;
synchronized (mMapViewPosition) {
mMapViewPosition.updateAnimation();
positionChanged |= mMapViewPosition.getMapPosition(pos);
if (positionChanged)
mMapViewPosition.getMapViewProjection(mBoxCoords);
mMapViewPosition.getMatrix(mMatrices.view, null, mMatrices.viewproj);
if (debugView) {
mMatrices.mvp.setScale(0.5f, 0.5f, 1);
mMatrices.viewproj.multiplyMM(mMatrices.mvp, mMatrices.viewproj);
}
}
/* 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 overlays */
for (int i = 0, n = overlays.length; i < n; i++) {
RenderOverlay renderLayer = overlays[i];
if (renderLayer.newData) {
renderLayer.compile();
renderLayer.newData = false;
}
if (renderLayer.isReady)
renderLayer.render(mMapPosition, mMatrices);
}
if (MapView.debugFrameTime) {
GLES20.glFinish();
Log.d(TAG, "draw took " + (SystemClock.uptimeMillis() - start));
}
if (GlUtils.checkGlOutOfMemory("finish")) {
checkBufferUsage(true);
// TODO also throw out some textures etc
}
}
public static int depthOffset(MapTile t) {
return ((t.tileX % 4) + (t.tileY % 4 * 4) + 1);
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
Log.d(TAG, "SurfaceChanged:" + mNewSurface + " " + width + "x" + height);
if (width <= 0 || height <= 0)
return;
screenWidth = width;
screenHeight = height;
mMapViewPosition.getMatrix(null, mMatrices.proj, null);
if (debugView) {
// modify this to scale only the view, to see better which tiles
// are rendered
mMatrices.mvp.setScale(0.5f, 0.5f, 1);
mMatrices.proj.multiplyMM(mMatrices.mvp, mMatrices.proj);
}
GLES20.glViewport(0, 0, width, height);
GLES20.glScissor(0, 0, width, height);
GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
GLES20.glClearStencil(0x00);
GLES20.glDisable(GLES20.GL_CULL_FACE);
GLES20.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
if (!mNewSurface) {
mMapView.redrawMap(false);
return;
}
mNewSurface = false;
ByteBuffer bbuf = ByteBuffer.allocateDirect(MB >> 2)
.order(ByteOrder.nativeOrder());
shortBuffer = bbuf.asShortBuffer();
// upload quad indices used by Texture- and LineTexRenderer
int[] vboIds = new int[1];
GLES20.glGenBuffers(1, vboIds, 0);
mQuadIndicesID = vboIds[0];
int maxIndices = maxQuads * 6;
short[] indices = new short[maxIndices];
for (int i = 0, j = 0; i < maxIndices; i += 6, j += 4) {
indices[i + 0] = (short) (j + 0);
indices[i + 1] = (short) (j + 1);
indices[i + 2] = (short) (j + 2);
indices[i + 3] = (short) (j + 2);
indices[i + 4] = (short) (j + 1);
indices[i + 5] = (short) (j + 3);
}
shortBuffer.put(indices);
shortBuffer.flip();
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER,
mQuadIndicesID);
GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER,
indices.length * 2, shortBuffer, GLES20.GL_STATIC_DRAW);
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
mBufferMemoryUsage = 0;
//mDrawTiles = null;
int numTiles = (screenWidth / (Tile.SIZE / 2) + 2)
* (screenHeight / (Tile.SIZE / 2) + 2);
// Set up vertex buffer objects
int numVBO = (CACHE_TILES + (numTiles * 2));
BufferObject.init(numVBO);
if (mClearColor != null)
mUpdateColor = true;
GLState.init();
mMapView.redrawMap(true);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Log.d(TAG, GLES20.glGetString(GLES20.GL_EXTENSIONS));
// classes that require GL context for initialization
Layers.initRenderer();
mNewSurface = true;
}
private boolean mNewSurface;
public static final boolean debugView = false;
void clearBuffer() {
mNewSurface = true;
}
}