[android] self-contained map renderer

- Isolates the GL thread in a MapRenderer class with a native peer
This commit is contained in:
Ivo van Dongen
2017-09-14 21:23:07 +03:00
committed by Ivo van Dongen
parent 15a47d116a
commit 5d12503302
18 changed files with 561 additions and 436 deletions

View File

@@ -28,7 +28,7 @@ import com.mapbox.mapboxsdk.annotations.MarkerViewManager;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.constants.Style;
import com.mapbox.mapboxsdk.egl.EGLConfigChooser;
import com.mapbox.mapboxsdk.maps.renderer.GlSurfaceViewRenderThread;
import com.mapbox.mapboxsdk.maps.renderer.MapRenderer;
import com.mapbox.mapboxsdk.maps.widgets.CompassView;
import com.mapbox.mapboxsdk.maps.widgets.MyLocationView;
import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings;
@@ -286,35 +286,31 @@ public class MapView extends FrameLayout {
glSurfaceView.setZOrderMediaOverlay(mapboxMapOptions.getRenderSurfaceOnTop());
glSurfaceView.setEGLContextClientVersion(2);
glSurfaceView.setEGLConfigChooser(new EGLConfigChooser());
glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
MapRenderer mapRenderer = new MapRenderer(getContext(), glSurfaceView) {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
MapView.this.post(new Runnable() {
@Override
public void run() {
initialiseMap();
mapboxMap.onStart();
// Initialise only once
if (mapboxMap == null) {
initialiseMap();
mapboxMap.onStart();
}
}
});
nativeMapView.onSurfaceCreated(gl, config);
super.onSurfaceCreated(gl, config);
}
};
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
nativeMapView.onSurfaceChanged(gl, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
nativeMapView.onDrawFrame(gl);
}
});
glSurfaceView.setRenderer(mapRenderer);
glSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY);
glSurfaceView.setVisibility(View.VISIBLE);
nativeMapView.setRenderThread(new GlSurfaceViewRenderThread(glSurfaceView));
nativeMapView = new NativeMapView(this, mapRenderer);
nativeMapView.resizeView(getMeasuredWidth(), getMeasuredHeight());
}
/**
@@ -527,9 +523,7 @@ public class MapView extends FrameLayout {
return;
}
if (nativeMapView == null) {
nativeMapView = new NativeMapView(this);
} else if (mapZoomButtonController != null) {
if (mapZoomButtonController != null) {
mapZoomButtonController.setVisible(visibility == View.VISIBLE);
}
}

View File

@@ -4,7 +4,6 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.graphics.RectF;
import android.opengl.GLSurfaceView;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -20,7 +19,8 @@ import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.geometry.ProjectedMeters;
import com.mapbox.mapboxsdk.maps.renderer.RenderThread;
import com.mapbox.mapboxsdk.maps.renderer.MapRenderer;
import com.mapbox.mapboxsdk.maps.renderer.MapRendererScheduler;
import com.mapbox.mapboxsdk.storage.FileSource;
import com.mapbox.mapboxsdk.style.layers.CannotAddLayerException;
import com.mapbox.mapboxsdk.style.layers.Filter;
@@ -36,13 +36,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import timber.log.Timber;
// Class that wraps the native methods for convenience
final class NativeMapView implements GLSurfaceView.Renderer {
final class NativeMapView {
// Flag to indicating destroy was called
private boolean destroyed = false;
@@ -53,12 +50,12 @@ final class NativeMapView implements GLSurfaceView.Renderer {
// Used for callbacks
private MapView mapView;
// Used to schedule work on the render thread
private RenderThread renderThread;
//Hold a reference to prevent it from being GC'd as long as it's used on the native side
private final FileSource fileSource;
// Used to schedule work on the MapRenderer Thread
private MapRendererScheduler scheduler;
// Device density
private final float pixelRatio;
@@ -73,25 +70,21 @@ final class NativeMapView implements GLSurfaceView.Renderer {
// Constructors
//
public NativeMapView(final MapView mapView) {
Context context = mapView.getContext();
fileSource = FileSource.getInstance(context);
pixelRatio = context.getResources().getDisplayMetrics().density;
public NativeMapView(final MapView mapView, MapRenderer mapRenderer) {
this.scheduler = mapRenderer;
this.mapView = mapView;
String programCacheDir = context.getCacheDir().getAbsolutePath();
nativeInitialize(this, fileSource, pixelRatio, programCacheDir);
Context context = mapView.getContext();
fileSource = FileSource.getInstance(context);
pixelRatio = context.getResources().getDisplayMetrics().density;
nativeInitialize(this, fileSource, mapRenderer, pixelRatio);
}
//
// Methods
//
public void setRenderThread(RenderThread renderThread) {
this.renderThread = renderThread;
}
private boolean isDestroyedOn(String callingMethod) {
if (destroyed && !TextUtils.isEmpty(callingMethod)) {
Timber.e(
@@ -113,14 +106,7 @@ final class NativeMapView implements GLSurfaceView.Renderer {
return;
}
requestRender();
}
public void render() {
if (isDestroyedOn("render")) {
return;
}
nativeRender();
scheduler.requestRender();
}
public void resizeView(int width, int height) {
@@ -151,33 +137,10 @@ final class NativeMapView implements GLSurfaceView.Renderer {
+ "capping value at 65535 instead of %s", height);
height = 65535;
}
nativeResizeView(width, height);
}
private void resizeFramebuffer(int fbWidth, int fbHeight) {
if (isDestroyedOn("resizeFramebuffer")) {
return;
}
if (fbWidth < 0) {
throw new IllegalArgumentException("fbWidth cannot be negative.");
}
if (fbHeight < 0) {
throw new IllegalArgumentException("fbHeight cannot be negative.");
}
if (fbWidth > 65535) {
throw new IllegalArgumentException(
"fbWidth cannot be greater than 65535.");
}
if (fbHeight > 65535) {
throw new IllegalArgumentException(
"fbHeight cannot be greater than 65535.");
}
nativeResizeFramebuffer(fbWidth, fbHeight);
}
public void setStyleUrl(String url) {
if (isDestroyedOn("setStyleUrl")) {
return;
@@ -873,31 +836,6 @@ final class NativeMapView implements GLSurfaceView.Renderer {
// Callbacks
//
/**
* Called from JNI whenever the native map
* needs rendering.
*/
protected void requestRender() {
if (renderThread != null) {
renderThread.requestRender();
}
}
/**
* Called from JNI when work needs to be processed on
* the Renderer Thread.
*/
protected void requestProcessing() {
if (renderThread != null) {
renderThread.queueEvent(new Runnable() {
@Override
public void run() {
nativeProcess();
}
});
}
}
protected void onMapChanged(int rawChange) {
if (mapView != null) {
mapView.onMapChange(rawChange);
@@ -928,33 +866,13 @@ final class NativeMapView implements GLSurfaceView.Renderer {
private native void nativeInitialize(NativeMapView nativeMapView,
FileSource fileSource,
float pixelRatio,
String programCacheDir);
MapRenderer mapRenderer,
float pixelRatio);
private native void nativeDestroy();
private native void nativeProcess();
private native void nativeInitializeDisplay();
private native void nativeTerminateDisplay();
private native void nativeInitializeContext();
private native void nativeTerminateContext();
private native void nativeCreateSurface(Object surface);
private native void nativeDestroySurface();
private native void nativeUpdate();
private native void nativeRender();
private native void nativeResizeView(int width, int height);
private native void nativeResizeFramebuffer(int fbWidth, int fbHeight);
private native void nativeSetStyleUrl(String url);
private native String nativeGetStyleUrl();
@@ -1160,30 +1078,7 @@ final class NativeMapView implements GLSurfaceView.Renderer {
void addSnapshotCallback(@NonNull MapboxMap.SnapshotReadyCallback callback) {
snapshotReadyCallback = callback;
scheduleTakeSnapshot();
renderThread.requestRender();
scheduler.requestRender();
}
//
// GLSurfaceView.Renderer
//
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Timber.i("[%s] onSurfaceCreated", Thread.currentThread().getName());
//TODO: callback to map to re-create renderer?
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Timber.i("[%s] onSurfaceChanged %sx%s", Thread.currentThread().getName(), width, height);
// Sets the current view port to the new size.
gl.glViewport(0, 0, width, height);
resizeFramebuffer(width, height);
// resizeView(width, height); Done from MapView#onSizeChanged
}
@Override
public void onDrawFrame(GL10 gl) {
nativeRender();
}
}

View File

@@ -1,25 +0,0 @@
package com.mapbox.mapboxsdk.maps.renderer;
import android.opengl.GLSurfaceView;
/**
* {@link RenderThread} implementation that schedules using the
* {@link GLSurfaceView} thread.
*/
public class GlSurfaceViewRenderThread implements RenderThread {
private final GLSurfaceView surfaceView;
public GlSurfaceViewRenderThread(GLSurfaceView surfaceView) {
this.surfaceView = surfaceView;
}
@Override
public void requestRender() {
surfaceView.requestRender();
}
@Override
public void queueEvent(Runnable runnable) {
surfaceView.queueEvent(runnable);
}
}

View File

@@ -0,0 +1,119 @@
package com.mapbox.mapboxsdk.maps.renderer;
import android.content.Context;
import android.opengl.GLSurfaceView;
import com.mapbox.mapboxsdk.storage.FileSource;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* The {@link MapRenderer} encapsulates the GL thread.
* <p>
* Performs actions on the GL thread to manage the GL resources and
* render on the one end and acts as a scheduler to request work to
* be performed on the GL thread on the other.
*/
public class MapRenderer implements GLSurfaceView.Renderer, MapRendererScheduler {
// Holds the pointer to the native peer after initialisation
private long nativePtr = 0;
private final GLSurfaceView glSurfaceView;
public MapRenderer(Context context, GLSurfaceView glSurfaceView) {
this.glSurfaceView = glSurfaceView;
FileSource fileSource = FileSource.getInstance(context);
float pixelRatio = context.getResources().getDisplayMetrics().density;
String programCacheDir = context.getCacheDir().getAbsolutePath();
// Initialise native peer
nativeInitialize(this, fileSource, pixelRatio, programCacheDir);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
nativeOnSurfaceCreated();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
if (width < 0) {
throw new IllegalArgumentException("fbWidth cannot be negative.");
}
if (height < 0) {
throw new IllegalArgumentException("fbHeight cannot be negative.");
}
if (width > 65535) {
throw new IllegalArgumentException(
"fbWidth cannot be greater than 65535.");
}
if (height > 65535) {
throw new IllegalArgumentException(
"fbHeight cannot be greater than 65535.");
}
gl.glViewport(0, 0, width, height);
nativeOnSurfaceChanged(width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
nativeRender();
}
/**
* May be called from any thread.
* <p>
* Called from the renderer frontend to schedule a render.
*/
@Override
public void requestRender() {
glSurfaceView.requestRender();
}
/**
* May be called from any thread.
* <p>
* Schedules work to be performed on the MapRenderer thread.
*
* @param runnable the runnable to execute
*/
@Override
public void queueEvent(Runnable runnable) {
glSurfaceView.queueEvent(runnable);
}
/**
* May be called from any thread.
* <p>
* Called from the native peer to schedule work on the GL
* thread. Explicit override for easier to read jni code.
*
* @param runnable the runnable to execute
* @see MapRendererRunnable
*/
void queueEvent(MapRendererRunnable runnable) {
this.queueEvent((Runnable) runnable);
}
private native void nativeInitialize(MapRenderer self,
FileSource fileSource,
float pixelRatio,
String programCacheDir);
@Override
protected native void finalize() throws Throwable;
private native void nativeOnSurfaceCreated();
private native void nativeOnSurfaceChanged(int width, int height);
private native void nativeRender();
}

View File

@@ -0,0 +1,29 @@
package com.mapbox.mapboxsdk.maps.renderer;
/**
* Peer class for {@link Runnable}s to be scheduled on the {@link MapRenderer} thread.
* The actual work is performed in the native peer.
*/
class MapRendererRunnable implements Runnable {
// Holds the pointer to the native peer after initialisation
private final long nativePtr;
/**
* Constructed from the native peer constructor
*
* @param nativePtr the native peer's memory address
*/
MapRendererRunnable(long nativePtr) {
this.nativePtr = nativePtr;
}
@Override
public native void run();
@Override
protected native void finalize() throws Throwable;
private native void nativeInitialize();
}

View File

@@ -0,0 +1,13 @@
package com.mapbox.mapboxsdk.maps.renderer;
/**
* Can be used to schedule work on the map renderer
* thread or request a render.
*/
public interface MapRendererScheduler {
void requestRender();
void queueEvent(Runnable runnable);
}

View File

@@ -1,12 +0,0 @@
package com.mapbox.mapboxsdk.maps.renderer;
/**
* Created by ivo on 11/09/2017.
*/
public interface RenderThread {
void requestRender();
void queueEvent(Runnable runnable);
}