summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvo van Dongen <info@ivovandongen.nl>2017-07-21 16:15:30 +0300
committerIvo van Dongen <info@ivovandongen.nl>2017-09-13 14:37:10 +0300
commitd7f258097561289fa96d26275b2bed64455fcc70 (patch)
tree9074d460132f09f6c934a06db26cfc008dd53acc
parent621f2c56d0084fdb25879a90c206c762d6b51eeb (diff)
downloadqtlocation-mapboxgl-d7f258097561289fa96d26275b2bed64455fcc70.tar.gz
[android] async rendering
- introduces GLSurfaceView - introduces Orchestration thread - renders on the gl thread
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java180
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java52
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml2
-rw-r--r--platform/android/src/android_renderer_frontend.cpp145
-rw-r--r--platform/android/src/android_renderer_frontend.hpp38
-rwxr-xr-xplatform/android/src/native_map_view.cpp459
-rwxr-xr-xplatform/android/src/native_map_view.hpp38
7 files changed, 279 insertions, 635 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
index 1fa53f6515..04ebca4409 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
@@ -1,10 +1,9 @@
package com.mapbox.mapboxsdk.maps;
import android.content.Context;
-import android.graphics.Canvas;
import android.graphics.PointF;
-import android.graphics.SurfaceTexture;
import android.os.Build;
+import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.IntDef;
@@ -16,10 +15,6 @@ import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -45,8 +40,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
import timber.log.Timber;
+import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY;
+
/**
* <p>
* A {@code MapView} provides an embeddable map interface.
@@ -69,7 +69,6 @@ public class MapView extends FrameLayout {
private NativeMapView nativeMapView;
private MapboxMapOptions mapboxMapOptions;
private boolean destroyed;
- private boolean hasSurface;
private MyLocationView myLocationView;
private CompassView compassView;
@@ -82,6 +81,8 @@ public class MapView extends FrameLayout {
private Bundle savedInstanceState;
private final CopyOnWriteArrayList<OnMapChangedListener> onMapChangedListeners = new CopyOnWriteArrayList<>();
+ private GLSurfaceView glSurfaceView;
+
@UiThread
public MapView(@NonNull Context context) {
super(context);
@@ -230,10 +231,38 @@ public class MapView extends FrameLayout {
}
private void initialiseDrawingSurface() {
- SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
- surfaceView.setZOrderMediaOverlay(mapboxMapOptions.getRenderSurfaceOnTop());
- surfaceView.getHolder().addCallback(new SurfaceCallback());
- surfaceView.setVisibility(View.VISIBLE);
+ glSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceView);
+ glSurfaceView.setZOrderMediaOverlay(mapboxMapOptions.getRenderSurfaceOnTop());
+ // TODO port config chooser from native code
+ glSurfaceView.setEGLConfigChooser(8, 8, 8, 0 /** TODO: What alpha value do we need here?? */, 16, 8);
+ glSurfaceView.setEGLContextClientVersion(2);
+ glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
+
+ @Override
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ MapView.this.post(new Runnable() {
+ @Override
+ public void run() {
+ initialiseMap();
+ mapboxMap.onStart();
+ }
+ });
+
+ nativeMapView.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.setRenderMode(RENDERMODE_WHEN_DIRTY);
+ glSurfaceView.setVisibility(View.VISIBLE);
}
/**
@@ -264,7 +293,9 @@ public class MapView extends FrameLayout {
*/
@UiThread
public void onResume() {
- // replaced by onStart in v5.0.0
+ if (glSurfaceView != null) {
+ glSurfaceView.onResume();
+ }
}
/**
@@ -272,7 +303,9 @@ public class MapView extends FrameLayout {
*/
@UiThread
public void onPause() {
- // replaced by onStop in v5.0.0
+ if (glSurfaceView != null) {
+ glSurfaceView.onPause();
+ }
}
/**
@@ -415,24 +448,10 @@ public class MapView extends FrameLayout {
// Called when the map needs to be rerendered
// Called via JNI from NativeMapView
protected void onInvalidate() {
- postInvalidate();
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (isInEditMode()) {
- return;
- }
-
- if (destroyed) {
- return;
- }
-
- if (!hasSurface) {
- return;
+ if (glSurfaceView != null) {
+ glSurfaceView.requestRender();
}
- nativeMapView.render();
+ // TODO: removable? postInvalidate();
}
@Override
@@ -446,103 +465,6 @@ public class MapView extends FrameLayout {
}
}
- private class SurfaceCallback implements SurfaceHolder.Callback {
-
- private Surface surface;
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- if (nativeMapView == null) {
- nativeMapView = new NativeMapView(MapView.this);
- nativeMapView.createSurface(surface = holder.getSurface());
- nativeMapView.resizeView(getWidth(), getHeight());
- initialiseMap();
- mapboxMap.onStart();
- } else {
- nativeMapView.createSurface(surface = holder.getSurface());
- }
-
- hasSurface = true;
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- if (destroyed) {
- return;
- }
- nativeMapView.resizeFramebuffer(width, height);
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- hasSurface = false;
-
- if (nativeMapView != null) {
- // occurs when activity goes to background
- nativeMapView.destroySurface();
- }
- surface.release();
- }
- }
-
- // This class handles TextureView callbacks
- private class SurfaceTextureListener implements TextureView.SurfaceTextureListener {
-
- private Surface surface;
-
- // Called when the native surface texture has been created
- // Must do all EGL/GL ES initialization here
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- if (nativeMapView == null) {
- nativeMapView = new NativeMapView(MapView.this);
- nativeMapView.createSurface(this.surface = new Surface(surface));
- nativeMapView.resizeFramebuffer(width, height);
- nativeMapView.resizeView(width, height);
- initialiseMap();
- mapboxMap.onStart();
- } else {
- nativeMapView.createSurface(this.surface = new Surface(surface));
- }
-
- hasSurface = true;
- }
-
- // Called when the native surface texture has been destroyed
- // Must do all EGL/GL ES destruction here
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- hasSurface = false;
-
- if (nativeMapView != null) {
- nativeMapView.destroySurface();
- }
- this.surface.release();
- return true;
- }
-
- // Called when the format or size of the native surface texture has been changed
- // Must handle window resizing here.
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
- if (destroyed) {
- return;
- }
-
- nativeMapView.resizeFramebuffer(width, height);
- }
-
- // Called when the SurfaceTexure frame is drawn to screen
- // Must sync with UI here
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
- if (destroyed) {
- return;
- }
- mapboxMap.onUpdateRegionChange();
- }
- }
-
//
// View events
//
@@ -562,7 +484,9 @@ public class MapView extends FrameLayout {
return;
}
- if (nativeMapView != null) {
+ if (nativeMapView == null) {
+ nativeMapView = new NativeMapView(this);
+ } else if (mapZoomButtonController != null) {
mapZoomButtonController.setVisible(visibility == View.VISIBLE);
}
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
index 4f5037e4b3..d1faa910bb 100755
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
@@ -4,12 +4,12 @@ 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;
import android.text.TextUtils;
import android.util.DisplayMetrics;
-import android.view.Surface;
import com.mapbox.mapboxsdk.LibraryLoader;
import com.mapbox.mapboxsdk.annotations.Icon;
@@ -35,10 +35,13 @@ 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 {
+final class NativeMapView implements GLSurfaceView.Renderer {
// Flag to indicating destroy was called
private boolean destroyed = false;
@@ -97,20 +100,6 @@ final class NativeMapView {
destroyed = true;
}
- public void createSurface(Surface surface) {
- if (isDestroyedOn("createSurface")) {
- return;
- }
- nativeCreateSurface(surface);
- }
-
- public void destroySurface() {
- if (isDestroyedOn("destroySurface")) {
- return;
- }
- nativeDestroySurface();
- }
-
public void update() {
if (isDestroyedOn("update")) {
return;
@@ -156,7 +145,7 @@ final class NativeMapView {
nativeResizeView(width, height);
}
- public void resizeFramebuffer(int fbWidth, int fbHeight) {
+ private void resizeFramebuffer(int fbWidth, int fbHeight) {
if (isDestroyedOn("resizeFramebuffer")) {
return;
}
@@ -1141,6 +1130,33 @@ final class NativeMapView {
void addSnapshotCallback(@NonNull MapboxMap.SnapshotReadyCallback callback) {
snapshotReadyCallback = callback;
scheduleTakeSnapshot();
- render();
+ mapView.onInvalidate();
+ // TODO. this should do a request render
+ //render();
+ }
+
+ //
+ // 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) {
+ Timber.i("[%s] onDrawFrame", Thread.currentThread().getName());
+ nativeRender();
}
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml
index 6d07de7baa..e2f1823327 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <SurfaceView
+ <android.opengl.GLSurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/platform/android/src/android_renderer_frontend.cpp b/platform/android/src/android_renderer_frontend.cpp
index 597bebe40d..872fc4d57a 100644
--- a/platform/android/src/android_renderer_frontend.cpp
+++ b/platform/android/src/android_renderer_frontend.cpp
@@ -1,34 +1,100 @@
#include "android_renderer_frontend.hpp"
+#include <mbgl/actor/scheduler.hpp>
#include <mbgl/renderer/backend_scope.hpp>
#include <mbgl/renderer/renderer.hpp>
+#include <mbgl/renderer/renderer_observer.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/util/thread.hpp>
+#include <mbgl/util/run_loop.hpp>
+
+#include "android_renderer_backend.hpp"
namespace mbgl {
namespace android {
-AndroidRendererFrontend::AndroidRendererFrontend(
- std::unique_ptr<Renderer> renderer_,
- RendererBackend& backend_,
- InvalidateCallback invalidate)
- : renderer(std::move(renderer_))
- , backend(backend_)
+// Forwards RendererObserver signals to the given
+// Delegate RendererObserver on the given RunLoop
+class ForwardingRendererObserver : public RendererObserver {
+public:
+ ForwardingRendererObserver(util::RunLoop& mapRunLoop_, RendererObserver& delegate_)
+ : mapRunLoop(mapRunLoop_)
+ , delegate(delegate_) {}
+
+ void onInvalidate() override {
+ mapRunLoop.invoke([&]() {
+ delegate.onInvalidate();
+ });
+ }
+
+ void onResourceError(std::exception_ptr err) override {
+ mapRunLoop.invoke([&, err]() {
+ delegate.onResourceError(err);
+ });
+ }
+
+ void onWillStartRenderingMap() override {
+ mapRunLoop.invoke([&]() {
+ delegate.onWillStartRenderingMap();
+ });
+ }
+
+ void onWillStartRenderingFrame() override {
+ mapRunLoop.invoke([&]() {
+ delegate.onWillStartRenderingFrame();
+ });
+ }
+
+ void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded) override {
+ mapRunLoop.invoke([&, mode, repaintNeeded]() {
+ delegate.onDidFinishRenderingFrame(mode, repaintNeeded);
+ });
+ }
+
+ void onDidFinishRenderingMap() override {
+ mapRunLoop.invoke([&]() {
+ delegate.onDidFinishRenderingMap();
+ });
+ }
+
+private:
+ util::RunLoop& mapRunLoop;
+ RendererObserver& delegate;
+};
+
+AndroidRendererFrontend::AndroidRendererFrontend(float pixelRatio,
+ FileSource& fileSource,
+ Scheduler& scheduler,
+ std::string programCacheDir,
+ InvalidateCallback invalidate)
+ : backend(std::make_unique<AndroidRendererBackend>())
+ , renderer(std::make_unique<util::Thread<Renderer>>(
+ "Orchestration Thread",
+ *backend,
+ pixelRatio,
+ fileSource,
+ scheduler,
+ GLContextMode::Unique,
+ programCacheDir
+ ))
, asyncInvalidate([=, invalidate=std::move(invalidate)]() {
invalidate();
- }) {
+ })
+ , mapRunLoop(util::RunLoop::Get()) {
}
AndroidRendererFrontend::~AndroidRendererFrontend() = default;
void AndroidRendererFrontend::reset() {
- assert (renderer);
- if (renderer) {
- renderer.reset();
- }
+ assert(renderer);
+ renderer.reset();
}
void AndroidRendererFrontend::setObserver(RendererObserver& observer) {
assert (renderer);
- renderer->setObserver(&observer);
+ assert (util::RunLoop::Get());
+ rendererObserver = std::make_unique<ForwardingRendererObserver>(*mapRunLoop, observer);
+ renderer->actor().invoke(&Renderer::setObserver, rendererObserver.get());
}
void AndroidRendererFrontend::update(std::shared_ptr<UpdateParameters> params) {
@@ -36,37 +102,78 @@ void AndroidRendererFrontend::update(std::shared_ptr<UpdateParameters> params) {
asyncInvalidate.send();
}
+// Called on OpenGL thread
void AndroidRendererFrontend::render() {
assert (renderer);
if (!updateParameters) return;
- BackendScope guard { backend };
+ BackendScope backendGuard { *backend };
+
+ util::BlockingThreadGuard<Renderer> rendererGuard { *renderer };
- renderer->render(*updateParameters);
+ if (framebufferSizeChanged) {
+ backend->updateViewPort();
+ framebufferSizeChanged = false;
+ }
+
+ rendererGuard.object().render(*updateParameters);
}
void AndroidRendererFrontend::onLowMemory() {
assert (renderer);
- renderer->onLowMemory();
+ renderer->actor().invoke(&Renderer::onLowMemory);
}
std::vector<Feature> AndroidRendererFrontend::querySourceFeatures(const std::string& sourceID,
const SourceQueryOptions& options) const {
- return renderer->querySourceFeatures(sourceID, options);
+ assert (renderer);
+ // Waits for the result from the orchestration thread and returns
+ return renderer->actor().ask(&Renderer::querySourceFeatures, sourceID, options).get();
}
std::vector<Feature> AndroidRendererFrontend::queryRenderedFeatures(const ScreenBox& box,
const RenderedQueryOptions& options) const {
- return renderer->queryRenderedFeatures(box, options);
+ assert (renderer);
+
+ // Select the right overloaded method
+ std::vector<Feature> (Renderer::*fn)(const ScreenBox&, const RenderedQueryOptions&) const = &Renderer::queryRenderedFeatures;
+
+ // Waits for the result from the orchestration thread and returns
+ return renderer->actor().ask(fn, box, options).get();
}
std::vector<Feature> AndroidRendererFrontend::queryRenderedFeatures(const ScreenCoordinate& point,
const RenderedQueryOptions& options) const {
- return renderer->queryRenderedFeatures(point, options);
+ assert (renderer);
+
+ // Select the right overloaded method
+ std::vector<Feature> (Renderer::*fn)(const ScreenCoordinate&, const RenderedQueryOptions&) const = &Renderer::queryRenderedFeatures;
+
+ // Waits for the result from the orchestration thread and returns
+ return renderer->actor().ask(fn, point, options).get();
}
AnnotationIDs AndroidRendererFrontend::queryPointAnnotations(const ScreenBox& box) const {
- return renderer->queryPointAnnotations(box);
+ assert (renderer);
+
+ // Waits for the result from the orchestration thread and returns
+ return renderer->actor().ask(&Renderer::queryPointAnnotations, box).get();
+}
+
+void AndroidRendererFrontend::requestSnapshot(SnapshotCallback callback_) {
+ snapshotCallback = std::make_unique<SnapshotCallback>([callback=std::move(callback_), runloop=util::RunLoop::Get()](PremultipliedImage image){
+ runloop->invoke([&]() {
+ callback(std::move(image));
+ });
+ });
+}
+
+void AndroidRendererFrontend::resizeFramebuffer(int width, int height) {
+ util::BlockingThreadGuard<Renderer> guard { *renderer };
+ backend->resizeFramebuffer(width, height);
+ framebufferSizeChanged = true;
+ // TODO: thread safe?
+ asyncInvalidate.send();
}
} // namespace android
diff --git a/platform/android/src/android_renderer_frontend.hpp b/platform/android/src/android_renderer_frontend.hpp
index 107bbbe0b6..ca7c27b196 100644
--- a/platform/android/src/android_renderer_frontend.hpp
+++ b/platform/android/src/android_renderer_frontend.hpp
@@ -6,29 +6,48 @@
#include <mbgl/renderer/renderer_frontend.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/geo.hpp>
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/run_loop.hpp>
#include <functional>
#include <memory>
#include <vector>
+#include <string>
namespace mbgl {
class Renderer;
+
+class FileSource;
+class Scheduler;
class RenderedQueryOptions;
class SourceQueryOptions;
+namespace util {
+template <class> class Thread;
+} // namespace util
+
namespace android {
+class AndroidRendererBackend;
+
class AndroidRendererFrontend : public RendererFrontend {
public:
using InvalidateCallback = std::function<void ()>;
- AndroidRendererFrontend(std::unique_ptr<Renderer>, RendererBackend&, InvalidateCallback);
+
+ AndroidRendererFrontend(float pixelRatio,
+ mbgl::FileSource&,
+ mbgl::Scheduler&,
+ std::string programCacheDir,
+ InvalidateCallback);
~AndroidRendererFrontend() override;
void reset() override;
void setObserver(RendererObserver&) override;
void update(std::shared_ptr<UpdateParameters>) override;
+
+ // Called from OpenGL Thread
void render();
// Feature querying
@@ -37,14 +56,27 @@ public:
std::vector<Feature> querySourceFeatures(const std::string& sourceID, const SourceQueryOptions&) const;
AnnotationIDs queryPointAnnotations(const ScreenBox& box) const;
+ // RenderBackend proxy - Called from OpenGL Thread
+ void resizeFramebuffer(int width, int height);
+
// Memory
void onLowMemory();
+ // Snapshot - Callback will be called on calling thread through RunLoop
+ using SnapshotCallback = std::function<void (PremultipliedImage)>;
+ void requestSnapshot(SnapshotCallback);
+
private:
- std::unique_ptr<Renderer> renderer;
- RendererBackend& backend;
+ std::unique_ptr<AndroidRendererBackend> backend;
+ std::unique_ptr<util::Thread<Renderer>> renderer;
+ std::unique_ptr<RendererObserver> rendererObserver;
std::shared_ptr<UpdateParameters> updateParameters;
util::AsyncTask asyncInvalidate;
+
+ util::RunLoop* mapRunLoop;
+
+ bool framebufferSizeChanged = true;
+ std::unique_ptr<SnapshotCallback> snapshotCallback;
};
} // namespace android
diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp
index a0d88e8cf1..1a0dc34bbc 100755
--- a/platform/android/src/native_map_view.cpp
+++ b/platform/android/src/native_map_view.cpp
@@ -61,8 +61,7 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env,
jni::Object<FileSource> jFileSource,
jni::jfloat _pixelRatio,
jni::String _programCacheDir)
- : rendererBackend(std::make_unique<AndroidRendererBackend>()),
- javaPeer(_obj.NewWeakGlobalRef(_env)),
+ : javaPeer(_obj.NewWeakGlobalRef(_env)),
pixelRatio(_pixelRatio),
threadPool(sharedThreadPool()) {
@@ -72,16 +71,13 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env,
return;
}
- auto& fileSource = mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource);
-
- // Create a renderer
- auto renderer = std::make_unique<Renderer>(*rendererBackend, pixelRatio, fileSource,
- *threadPool, GLContextMode::Unique,
- jni::Make<std::string>(_env, _programCacheDir));
+ mbgl::FileSource& fileSource = mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource);
// Create a renderer frontend
- rendererFrontend = std::make_unique<AndroidRendererFrontend>(std::move(renderer),
- *rendererBackend,
+ rendererFrontend = std::make_unique<AndroidRendererFrontend>(pixelRatio, fileSource,
+ *threadPool,
+ jni::Make<std::string>(_env,
+ _programCacheDir),
[this] { this->invalidate(); });
// Create the core map
@@ -90,29 +86,21 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env,
static_cast<uint32_t>(height) }, pixelRatio,
fileSource, *threadPool, MapMode::Continuous,
ConstrainMode::HeightOnly, ViewportMode::Default);
-
- // initialize egl components
- _initializeDisplay();
- _initializeContext();
}
/**
* Called through NativeMapView#destroy()
*/
NativeMapView::~NativeMapView() {
- _terminateContext();
- _destroySurface();
- _terminateDisplay();
-
map.reset();
-
vm = nullptr;
}
/**
* Callback to java NativeMapView#onInvalidate().
*
- * May be called from any thread
+ * Called to invalidate the View and schedule a render on the next
+ * runloop iteration.
*/
void NativeMapView::invalidate() {
android::UniqueEnv _env = android::AttachEnv();
@@ -199,48 +187,12 @@ void NativeMapView::onSourceChanged(mbgl::style::Source&) {
// JNI Methods //
-void NativeMapView::createSurface(jni::JNIEnv& env, jni::Object<> _surface) {
- _createSurface(ANativeWindow_fromSurface(&env, jni::Unwrap(*_surface)));
-}
-
-void NativeMapView::destroySurface(jni::JNIEnv&) {
- _destroySurface();
-}
-
-void NativeMapView::render(jni::JNIEnv& env) {
- BackendScope guard { *rendererBackend };
-
- if (framebufferSizeChanged) {
- rendererBackend->updateViewPort();
- framebufferSizeChanged = false;
- }
-
+// Called from the OpenGL renderer thread
+void NativeMapView::render(jni::JNIEnv& ) {
rendererFrontend->render();
- if(snapshot){
- snapshot = false;
-
- // take snapshot
- auto image = rendererBackend->readFramebuffer();
- auto bitmap = Bitmap::CreateBitmap(env, std::move(image));
-
- // invoke Mapview#OnSnapshotReady
- android::UniqueEnv _env = android::AttachEnv();
- static auto onSnapshotReady = javaClass.GetMethod<void (jni::Object<Bitmap>)>(*_env, "onSnapshotReady");
- javaPeer->Call(*_env, onSnapshotReady, bitmap);
- }
-
- if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) {
- if (!eglSwapBuffers(display, surface)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglSwapBuffers() returned error %d",
- eglGetError());
- throw std::runtime_error("eglSwapBuffers() failed");
- }
-
- updateFps();
- } else {
- mbgl::Log::Info(mbgl::Event::Android, "Not swapping as we are not ready");
- }
+ // TODO
+ updateFps();
}
void NativeMapView::update(jni::JNIEnv&) {
@@ -254,9 +206,9 @@ void NativeMapView::resizeView(jni::JNIEnv&, int w, int h) {
}
void NativeMapView::resizeFramebuffer(jni::JNIEnv&, int w, int h) {
- rendererBackend->resizeFramebuffer(w, h);
- framebufferSizeChanged = true;
- invalidate();
+ rendererFrontend->resizeFramebuffer(w, h);
+// framebufferSizeChanged = true;
+// invalidate();
}
jni::String NativeMapView::getStyleUrl(jni::JNIEnv& env) {
@@ -476,7 +428,15 @@ void NativeMapView::setContentPadding(JNIEnv&, double top, double left, double b
}
void NativeMapView::scheduleSnapshot(jni::JNIEnv&) {
- snapshot = true;
+ rendererFrontend->requestSnapshot([&](PremultipliedImage image) {
+ auto _env = android::AttachEnv();
+ // Convert image to bitmap
+ auto bitmap = Bitmap::CreateBitmap(*_env, std::move(image));
+
+ // invoke Mapview#OnSnapshotReady
+ static auto onSnapshotReady = javaClass.GetMethod<void (jni::Object<Bitmap>)>(*_env, "onSnapshotReady");
+ javaPeer->Call(*_env, onSnapshotReady, bitmap);
+ });
}
void NativeMapView::enableFps(jni::JNIEnv&, jni::jboolean enable) {
@@ -1001,371 +961,6 @@ jni::jboolean NativeMapView::getPrefetchesTiles(JNIEnv&) {
// Private methods //
-void NativeMapView::_initializeDisplay() {
- assert(display == EGL_NO_DISPLAY);
- assert(config == nullptr);
- assert(format < 0);
-
- display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (display == EGL_NO_DISPLAY) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetDisplay() returned error %d", eglGetError());
- throw std::runtime_error("eglGetDisplay() failed");
- }
-
- EGLint major, minor;
- if (!eglInitialize(display, &major, &minor)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglInitialize() returned error %d", eglGetError());
- throw std::runtime_error("eglInitialize() failed");
- }
- if ((major <= 1) && (minor < 3)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "EGL version is too low, need 1.3, got %d.%d", major,
- minor);
- throw std::runtime_error("EGL version is too low");
- }
-
- // Detect if we are in emulator.
- const bool inEmulator = []() {
- char prop[PROP_VALUE_MAX];
- __system_property_get("ro.kernel.qemu", prop);
- return strtol(prop, nullptr, 0) == 1;
- }();
-
- if (inEmulator) {
- // XXX https://code.google.com/p/android/issues/detail?id=78977
- mbgl::Log::Warning(mbgl::Event::Android, "Running SDK in emulator!");
- }
-
- if (!eglBindAPI(EGL_OPENGL_ES_API)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglBindAPI(EGL_OPENGL_ES_API) returned error %d", eglGetError());
- throw std::runtime_error("eglBindAPI() failed");
- }
-
- // Get all configs at least RGB 565 with 16 depth and 8 stencil
- EGLint configAttribs[] = {
- EGL_CONFIG_CAVEAT, EGL_NONE,
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
- EGL_BUFFER_SIZE, 16,
- EGL_RED_SIZE, 5,
- EGL_GREEN_SIZE, 6,
- EGL_BLUE_SIZE, 5,
- EGL_DEPTH_SIZE, 16,
- EGL_STENCIL_SIZE, 8,
- (inEmulator ? EGL_NONE : EGL_CONFORMANT), EGL_OPENGL_ES2_BIT,
- (inEmulator ? EGL_NONE : EGL_COLOR_BUFFER_TYPE), EGL_RGB_BUFFER,
- EGL_NONE
- };
-
- EGLint numConfigs;
- if (!eglChooseConfig(display, configAttribs, nullptr, 0, &numConfigs)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig(NULL) returned error %d",
- eglGetError());
- throw std::runtime_error("eglChooseConfig() failed");
- }
- if (numConfigs < 1) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig() returned no configs.");
- throw std::runtime_error("eglChooseConfig() failed");
- }
-
- const auto configs = std::make_unique<EGLConfig[]>(numConfigs);
- if (!eglChooseConfig(display, configAttribs, configs.get(), numConfigs, &numConfigs)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig() returned error %d", eglGetError());
- throw std::runtime_error("eglChooseConfig() failed");
- }
-
- config = chooseConfig(configs.get(), numConfigs);
- if (config == nullptr) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "No config chosen");
- throw std::runtime_error("No config chosen");
- }
-
- if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib() returned error %d",
- eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-}
-
-// Quality
-typedef enum {
- Format16Bit = 3,
- Format32BitNoAlpha = 1,
- Format32BitAlpha = 2,
- Format24Bit = 0,
- Unknown = 4
-} BufferFormat;
-
-typedef enum {
- Format16Depth8Stencil = 1,
- Format24Depth8Stencil = 0,
-} DepthStencilFormat;
-
-// Tuple is <buffer_format, depth_stencil_format, is_not_conformant, is_caveat, config_num,
-// config_id>
-typedef std::tuple<BufferFormat, DepthStencilFormat, bool, bool, int, EGLConfig> ConfigProperties;
-
-EGLConfig NativeMapView::chooseConfig(const EGLConfig configs[], EGLint numConfigs) {
- // Create a list of configs that pass our filters
- std::list<ConfigProperties> configList;
- for (int i = 0; i < numConfigs; i++) {
- EGLint caveat, conformant, bits, red, green, blue, alpha, alphaMask, depth, stencil,
- sampleBuffers, samples;
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_CONFIG_CAVEAT, &caveat)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_CONFIG_CAVEAT) returned error %d",
- eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_CONFORMANT, &conformant)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_CONFORMANT) returned error %d", eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_BUFFER_SIZE, &bits)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_BUFFER_SIZE) returned error %d",
- eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_RED_SIZE, &red)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_RED_SIZE) returned error %d", eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_GREEN_SIZE, &green)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_GREEN_SIZE) returned error %d", eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_BLUE_SIZE, &blue)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_BLUE_SIZE) returned error %d", eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_ALPHA_SIZE, &alpha)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_ALPHA_SIZE) returned error %d", eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_ALPHA_MASK_SIZE, &alphaMask)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_ALPHA_MASK_SIZE) returned error %d",
- eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_DEPTH_SIZE, &depth)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_DEPTH_SIZE) returned error %d", eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_STENCIL_SIZE, &stencil)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_STENCIL_SIZE) returned error %d",
- eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLE_BUFFERS, &sampleBuffers)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_SAMPLE_BUFFERS) returned error %d",
- eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLES, &samples)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglGetConfigAttrib(EGL_SAMPLES) returned error %d", eglGetError());
- throw std::runtime_error("eglGetConfigAttrib() failed");
- }
-
- bool configOk = true;
- configOk &= (depth == 24) || (depth == 16);
- configOk &= stencil == 8;
- configOk &= sampleBuffers == 0;
- configOk &= samples == 0;
-
- // Filter our configs first for depth, stencil and anti-aliasing
- if (configOk) {
- // Work out the config's buffer format
- BufferFormat bufferFormat;
- if ((bits == 16) && (red == 5) && (green == 6) && (blue == 5) && (alpha == 0)) {
- bufferFormat = Format16Bit;
- } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) {
- bufferFormat = Format32BitNoAlpha;
- } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 8)) {
- bufferFormat = Format32BitAlpha;
- } else if ((bits == 24) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) {
- bufferFormat = Format24Bit;
- } else {
- bufferFormat = Unknown;
- }
-
- // Work out the config's depth stencil format
- DepthStencilFormat depthStencilFormat;
- if ((depth == 16) && (stencil == 8)) {
- depthStencilFormat = Format16Depth8Stencil;
- } else {
- depthStencilFormat = Format24Depth8Stencil;
- }
-
- bool isNotConformant = (conformant & EGL_OPENGL_ES2_BIT) != EGL_OPENGL_ES2_BIT;
- bool isCaveat = caveat != EGL_NONE;
- EGLConfig configId = configs[i];
-
- // Ignore formats we don't recognise
- if (bufferFormat != Unknown) {
- configList.push_back(std::make_tuple(bufferFormat, depthStencilFormat,
- isNotConformant, isCaveat, i, configId));
- }
- }
- }
-
- if (configList.empty()) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "Config list was empty.");
- }
-
- // Sort the configs to find the best one
- configList.sort();
- bool isConformant = !std::get<2>(configList.front());
- bool isCaveat = std::get<3>(configList.front());
- EGLConfig configId = std::get<5>(configList.front());
-
- if (isCaveat) {
- mbgl::Log::Warning(mbgl::Event::OpenGL, "Chosen config has a caveat.");
- }
- if (!isConformant) {
- mbgl::Log::Warning(mbgl::Event::OpenGL, "Chosen config is not conformant.");
- }
-
- return configId;
-}
-
-void NativeMapView::_terminateDisplay() {
- if (display != EGL_NO_DISPLAY) {
- // Destroy the surface first, if it still exists. This call needs a valid surface.
- if (surface != EGL_NO_SURFACE) {
- if (!eglDestroySurface(display, surface)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d",
- eglGetError());
- throw std::runtime_error("eglDestroySurface() failed");
- }
- surface = EGL_NO_SURFACE;
- }
-
- if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError());
- throw std::runtime_error("eglMakeCurrent() failed");
- }
-
- if (!eglTerminate(display)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglTerminate() returned error %d",
- eglGetError());
- throw std::runtime_error("eglTerminate() failed");
- }
- }
-
- display = EGL_NO_DISPLAY;
- config = nullptr;
- format = -1;
-}
-
-void NativeMapView::_initializeContext() {
- assert(display != EGL_NO_DISPLAY);
- assert(context == EGL_NO_CONTEXT);
- assert(config != nullptr);
-
- const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
- context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
- if (context == EGL_NO_CONTEXT) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateContext() returned error %d",
- eglGetError());
- throw std::runtime_error("eglCreateContext() failed");
- }
-}
-
-void NativeMapView::_terminateContext() {
- if (display != EGL_NO_DISPLAY) {
-
- if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
- mbgl::Log::Error(mbgl::Event::OpenGL,
- "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError());
- throw std::runtime_error("eglMakeCurrent() failed");
- }
-
- if (context != EGL_NO_CONTEXT) {
- if (!eglDestroyContext(display, context)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroyContext() returned error %d",
- eglGetError());
- throw std::runtime_error("eglDestroyContext() failed");
- }
- }
- }
-
- context = EGL_NO_CONTEXT;
-}
-
-void NativeMapView::_createSurface(ANativeWindow *window_) {
- assert(window == nullptr);
- assert(window_ != nullptr);
- window = window_;
-
- assert(display != EGL_NO_DISPLAY);
- assert(surface == EGL_NO_SURFACE);
- assert(config != nullptr);
- assert(format >= 0);
-
- ANativeWindow_setBuffersGeometry(window, 0, 0, format);
-
- const EGLint surfaceAttribs[] = {EGL_NONE};
- surface = eglCreateWindowSurface(display, config, window, surfaceAttribs);
- if (surface == EGL_NO_SURFACE) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateWindowSurface() returned error %d",
- eglGetError());
- throw std::runtime_error("eglCreateWindowSurface() failed");
- }
-
- if (firstRender) {
- firstRender = false;
-
- BackendScope guard(*rendererBackend);
-
- if (!eglMakeCurrent(display, surface, surface, context)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d",
- eglGetError());
- throw std::runtime_error("eglMakeCurrent() failed");
- }
- }
-}
-
-void NativeMapView::_destroySurface() {
- if (surface != EGL_NO_SURFACE) {
- if (!eglDestroySurface(display, surface)) {
- mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d",
- eglGetError());
- throw std::runtime_error("eglDestroySurface() failed");
- }
- }
-
- surface = EGL_NO_SURFACE;
- firstRender = true;
-
- if (window != nullptr) {
- ANativeWindow_release(window);
- window = nullptr;
- }
-}
-
void NativeMapView::updateFps() {
if (!fpsEnabled) {
return;
@@ -1412,8 +1007,6 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
METHOD(&NativeMapView::update, "nativeUpdate"),
METHOD(&NativeMapView::resizeView, "nativeResizeView"),
METHOD(&NativeMapView::resizeFramebuffer, "nativeResizeFramebuffer"),
- METHOD(&NativeMapView::createSurface, "nativeCreateSurface"),
- METHOD(&NativeMapView::destroySurface, "nativeDestroySurface"),
METHOD(&NativeMapView::getStyleUrl, "nativeGetStyleUrl"),
METHOD(&NativeMapView::setStyleUrl, "nativeSetStyleUrl"),
METHOD(&NativeMapView::getStyleJson, "nativeGetStyleJson"),
@@ -1498,5 +1091,5 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
);
}
-}
-}
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp
index 7b7c3b5830..5cd25abbaa 100755
--- a/platform/android/src/native_map_view.hpp
+++ b/platform/android/src/native_map_view.hpp
@@ -36,7 +36,6 @@
namespace mbgl {
namespace android {
-class AndroidRendererBackend;
class AndroidRendererFrontend;
class NativeMapView : public MapObserver {
@@ -77,18 +76,19 @@ public:
// JNI //
+ // Called on OpenGL Thread
+ void onSurfaceCreated(jni::JNIEnv&);
+
+ // Called on OpenGL Thread
void render(jni::JNIEnv&);
void update(jni::JNIEnv&);
void resizeView(jni::JNIEnv&, int, int);
+ // Called on OpenGL Thread
void resizeFramebuffer(jni::JNIEnv&, int, int);
- void createSurface(jni::JNIEnv&, jni::Object<>);
-
- void destroySurface(jni::JNIEnv&);
-
jni::String getStyleUrl(jni::JNIEnv&);
void setStyleUrl(jni::JNIEnv&, jni::String);
@@ -256,25 +256,11 @@ public:
jni::jboolean getPrefetchesTiles(JNIEnv&);
private:
- void _initializeDisplay();
-
- void _terminateDisplay();
-
- void _initializeContext();
-
- void _terminateContext();
-
- void _createSurface(ANativeWindow*);
-
- void _destroySurface();
-
- EGLConfig chooseConfig(const EGLConfig configs[], EGLint numConfigs);
void updateFps();
private:
std::unique_ptr<AndroidRendererFrontend> rendererFrontend;
- std::unique_ptr<AndroidRendererBackend> rendererBackend;
JavaVM *vm = nullptr;
jni::UniqueWeakObject<NativeMapView> javaPeer;
@@ -282,28 +268,14 @@ private:
std::string styleUrl;
std::string apiKey;
- ANativeWindow *window = nullptr;
-
- EGLConfig config = nullptr;
- EGLint format = -1;
-
- EGLDisplay display = EGL_NO_DISPLAY;
- EGLSurface surface = EGL_NO_SURFACE;
- EGLContext context = EGL_NO_CONTEXT;
-
-
float pixelRatio;
bool fpsEnabled = false;
- bool snapshot = false;
- bool firstRender = true;
double fps = 0.0;
// Minimum texture size according to OpenGL ES 2.0 specification.
int width = 64;
int height = 64;
- bool framebufferSizeChanged = true;
-
// Ensure these are initialised last
std::shared_ptr<mbgl::ThreadPool> threadPool;
std::unique_ptr<mbgl::Map> map;