[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);
}

View File

@@ -205,6 +205,10 @@ add_library(mbgl-android STATIC
# Native map
platform/android/src/native_map_view.cpp
platform/android/src/native_map_view.hpp
platform/android/src/map_renderer.cpp
platform/android/src/map_renderer.hpp
platform/android/src/map_renderer_runnable.cpp
platform/android/src/map_renderer_runnable.hpp
# Java core classes
platform/android/src/java/util.cpp

View File

@@ -1,74 +0,0 @@
#pragma once
#include <mbgl/actor/actor.hpp>
#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <functional>
#include <memory>
#include <mutex>
#include <queue>
#include <utility>
namespace mbgl {
namespace android {
class AndroidGLThread : public Scheduler {
public:
using InvalidateCallback = std::function<void ()>;
template <class... Args>
AndroidGLThread(InvalidateCallback callback_, Args&&... args)
: renderer(std::make_unique<Renderer>(std::forward<Args>(args)...))
, mailbox(std::make_shared<Mailbox>(*this))
, callback(callback_)
, rendererRef(*renderer, mailbox) {
}
~AndroidGLThread() override = default;
ActorRef<Renderer> actor() const {
return rendererRef;
}
void schedule(std::weak_ptr<Mailbox> scheduled) override {
{
std::lock_guard<std::mutex> lock(mutex);
queue.push(scheduled);
}
// Invalidate so we get processing time later
callback();
}
// Only safe on the GL Thread
void process() {
std::unique_lock<std::mutex> lock(mutex);
if (queue.empty()) {
return;
}
auto scheduled = queue.front();
queue.pop();
lock.unlock();
Mailbox::maybeReceive(scheduled);
}
// Only safe to access on the GL Thread
std::unique_ptr<Renderer> renderer;
private:
std::mutex mutex;
std::queue<std::weak_ptr<Mailbox>> queue;
std::shared_ptr<Mailbox> mailbox;
InvalidateCallback callback;
ActorRef<Renderer> rendererRef;
};
} // namespace android
} // namespace mbgl

View File

@@ -54,124 +54,62 @@ private:
ActorRef<RendererObserver> delegate;
};
AndroidRendererFrontend::AndroidRendererFrontend(float pixelRatio,
FileSource& fileSource,
Scheduler& scheduler,
std::string programCacheDir,
RequestRenderCallback requestRender,
RequestProcessingCallback requestProcessing)
: backend(std::make_unique<AndroidRendererBackend>())
, glThread(std::make_unique<AndroidGLThread>(
std::move(requestProcessing),
*backend,
pixelRatio,
fileSource,
scheduler,
GLContextMode::Unique,
programCacheDir
))
, asyncInvalidate([requestRender=std::move(requestRender)]() {
requestRender();
})
AndroidRendererFrontend::AndroidRendererFrontend(MapRenderer& mapRenderer_)
: mapRenderer(mapRenderer_)
, mapRunLoop(util::RunLoop::Get()) {
}
AndroidRendererFrontend::~AndroidRendererFrontend() = default;
void AndroidRendererFrontend::reset() {
assert(glThread);
glThread.reset();
mapRenderer.reset();
rendererObserver.reset();
}
void AndroidRendererFrontend::setObserver(RendererObserver& observer) {
assert (glThread);
assert (util::RunLoop::Get());
rendererObserver = std::make_unique<ForwardingRendererObserver>(*mapRunLoop, observer);
glThread->actor().invoke(&Renderer::setObserver, rendererObserver.get());
mapRenderer.actor().invoke(&Renderer::setObserver, rendererObserver.get());
}
void AndroidRendererFrontend::update(std::shared_ptr<UpdateParameters> params) {
{
// Lock on the parameters
std::lock_guard<std::mutex> lock(updateMutex);
updateParameters = std::move(params);
}
asyncInvalidate.send();
}
// Called on OpenGL thread
void AndroidRendererFrontend::render() {
assert (glThread);
std::shared_ptr<UpdateParameters> params;
{
// Lock on the parameters
std::unique_lock<std::mutex> lock(updateMutex);
if (!updateParameters) return;
// Hold on to the update parameters during render
params = updateParameters;
}
// Activate the backend
BackendScope backendGuard { *backend };
// Ensure that the "current" scheduler on the render thread is
// actually the scheduler from the orchestration thread
Scheduler::SetCurrent(glThread.get());
if (framebufferSizeChanged) {
backend->updateViewPort();
framebufferSizeChanged = false;
}
glThread->renderer->render(*params);
}
void AndroidRendererFrontend::process() {
// Process the gl thread mailbox
glThread->process();
mapRenderer.update(std::move(params));
mapRenderer.requestRender();
}
void AndroidRendererFrontend::onLowMemory() {
assert (glThread);
glThread->actor().invoke(&Renderer::onLowMemory);
mapRenderer.actor().invoke(&Renderer::onLowMemory);
}
std::vector<Feature> AndroidRendererFrontend::querySourceFeatures(const std::string& sourceID,
const SourceQueryOptions& options) const {
assert (glThread);
// Waits for the result from the orchestration thread and returns
return glThread->actor().ask(&Renderer::querySourceFeatures, sourceID, options).get();
return mapRenderer.actor().ask(&Renderer::querySourceFeatures, sourceID, options).get();
}
std::vector<Feature> AndroidRendererFrontend::queryRenderedFeatures(const ScreenBox& box,
const RenderedQueryOptions& options) const {
assert (glThread);
// 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 glThread->actor().ask(fn, box, options).get();
return mapRenderer.actor().ask(fn, box, options).get();
}
std::vector<Feature> AndroidRendererFrontend::queryRenderedFeatures(const ScreenCoordinate& point,
const RenderedQueryOptions& options) const {
assert (glThread);
// 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 glThread->actor().ask(fn, point, options).get();
return mapRenderer.actor().ask(fn, point, options).get();
}
AnnotationIDs AndroidRendererFrontend::queryPointAnnotations(const ScreenBox& box) const {
assert (glThread);
// Waits for the result from the orchestration thread and returns
return glThread->actor().ask(&Renderer::queryPointAnnotations, box).get();
return mapRenderer.actor().ask(&Renderer::queryPointAnnotations, box).get();
}
void AndroidRendererFrontend::requestSnapshot(SnapshotCallback callback_) {
@@ -182,12 +120,6 @@ void AndroidRendererFrontend::requestSnapshot(SnapshotCallback callback_) {
});
}
void AndroidRendererFrontend::resizeFramebuffer(int width, int height) {
backend->resizeFramebuffer(width, height);
framebufferSizeChanged = true;
asyncInvalidate.send();
}
} // namespace android
} // namespace mbgl

View File

@@ -1,13 +1,9 @@
#pragma once
#include "android_gl_thread.hpp"
#include <mbgl/actor/actor.hpp>
#include <mbgl/annotation/annotation.hpp>
#include <mbgl/renderer/renderer_backend.hpp>
#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>
@@ -17,6 +13,8 @@
#include <vector>
#include <string>
#include "map_renderer.hpp"
namespace mbgl {
class FileSource;
@@ -30,15 +28,8 @@ class AndroidRendererBackend;
class AndroidRendererFrontend : public RendererFrontend {
public:
using RequestRenderCallback = std::function<void ()>;
using RequestProcessingCallback = std::function<void ()>;
AndroidRendererFrontend(float pixelRatio,
mbgl::FileSource&,
mbgl::Scheduler&,
std::string programCacheDir,
RequestRenderCallback,
RequestProcessingCallback);
AndroidRendererFrontend(MapRenderer&);
~AndroidRendererFrontend() override;
void reset() override;
@@ -46,19 +37,12 @@ public:
void update(std::shared_ptr<UpdateParameters>) override;
// Called from OpenGL Thread
void render();
void process();
// Feature querying
std::vector<Feature> queryRenderedFeatures(const ScreenCoordinate&, const RenderedQueryOptions&) const;
std::vector<Feature> queryRenderedFeatures(const ScreenBox&, const RenderedQueryOptions&) const;
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();
@@ -67,19 +51,12 @@ public:
void requestSnapshot(SnapshotCallback);
private:
std::unique_ptr<AndroidRendererBackend> backend;
std::unique_ptr<AndroidGLThread> glThread;
std::unique_ptr<RendererObserver> rendererObserver;
std::mutex updateMutex;
std::shared_ptr<UpdateParameters> updateParameters;
util::AsyncTask asyncInvalidate;
MapRenderer& mapRenderer;
util::RunLoop* mapRunLoop;
bool framebufferSizeChanged = true;
// TODO
std::unique_ptr<SnapshotCallback> snapshotCallback;
};

View File

@@ -32,6 +32,8 @@
#include "gson/json_object.hpp"
#include "gson/json_primitive.hpp"
#include "java_types.hpp"
#include "map_renderer.hpp"
#include "map_renderer_runnable.hpp"
#include "native_map_view.hpp"
#include "offline/offline_manager.hpp"
#include "offline/offline_region.hpp"
@@ -144,6 +146,8 @@ void registerNatives(JavaVM *vm) {
Polyline::registerNative(env);
// Map
MapRenderer::registerNative(env);
MapRendererRunnable::registerNative(env);
NativeMapView::registerNative(env);
// Http

View File

@@ -0,0 +1,137 @@
#include "map_renderer.hpp"
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/util/shared_thread_pool.hpp>
#include <string>
#include "attach_env.hpp"
#include "android_renderer_backend.hpp"
#include "map_renderer_runnable.hpp"
#include "file_source.hpp"
namespace mbgl {
namespace android {
MapRenderer::MapRenderer(jni::JNIEnv& _env,
jni::Object<MapRenderer> obj,
jni::Object<FileSource> fileSource,
jni::jfloat pixelRatio,
jni::String programCacheDir)
: javaPeer(SeizeGenericWeakRef(_env, jni::Object<MapRenderer>(jni::NewWeakGlobalRef(_env, obj.Get()).release())))
, threadPool(sharedThreadPool())
, backend(std::make_unique<AndroidRendererBackend>())
, renderer(std::make_unique<Renderer>(*backend, pixelRatio, FileSource::getDefaultFileSource(_env, fileSource),
*threadPool, GLContextMode::Unique, jni::Make<std::string>(_env, programCacheDir)))
, mailbox(std::make_shared<Mailbox>(*this))
, rendererRef(*renderer, mailbox) {
}
MapRenderer::~MapRenderer() = default;
void MapRenderer::reset() {
assert (renderer);
renderer.reset();
}
ActorRef<Renderer> MapRenderer::actor() const {
return rendererRef;
}
void MapRenderer::schedule(std::weak_ptr<Mailbox> scheduled) {
// Create a runnable and schedule it on the gl thread
android::UniqueEnv _env = android::AttachEnv();
auto runnable = std::make_unique<MapRendererRunnable>(*_env, std::move(scheduled));
static auto queueEvent = javaClass.GetMethod<void (jni::Object<MapRendererRunnable>)>(*_env, "queueEvent");
javaPeer->Call(*_env, queueEvent, runnable->getPeer());
// Release the object as it will be destroyed on GC of the Java Peer
runnable.release();
}
void MapRenderer::requestRender() {
android::UniqueEnv _env = android::AttachEnv();
static auto onInvalidate = javaClass.GetMethod<void ()>(*_env, "requestRender");
javaPeer->Call(*_env, onInvalidate);
}
void MapRenderer::update(std::shared_ptr<UpdateParameters> params) {
// Lock on the parameters
std::lock_guard<std::mutex> lock(updateMutex);
updateParameters = std::move(params);
}
// Called on OpenGL thread //
void MapRenderer::render(JNIEnv&) {
assert (renderer);
std::shared_ptr<UpdateParameters> params;
{
// Lock on the parameters
std::unique_lock<std::mutex> lock(updateMutex);
if (!updateParameters) return;
// Hold on to the update parameters during render
params = updateParameters;
}
// Activate the backend
BackendScope backendGuard { *backend };
// Ensure that the "current" scheduler on the render thread is
// this scheduler.
Scheduler::SetCurrent(this);
if (framebufferSizeChanged) {
backend->updateViewPort();
framebufferSizeChanged = false;
}
renderer->render(*params);
}
void MapRenderer::onSurfaceCreated(JNIEnv&) {
// TODO (re-)create Context, Backend and Renderer
}
void MapRenderer::onSurfaceChanged(JNIEnv&, jint width, jint height) {
backend->resizeFramebuffer(width, height);
framebufferSizeChanged = true;
requestRender();
}
// Static methods //
jni::Class<MapRenderer> MapRenderer::javaClass;
void MapRenderer::registerNative(jni::JNIEnv& env) {
// Lookup the class
MapRenderer::javaClass = *jni::Class<MapRenderer>::Find(env).NewGlobalRef(env).release();
#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
// Register the peer
jni::RegisterNativePeer<MapRenderer>(
env,
MapRenderer::javaClass,
"nativePtr",
std::make_unique<MapRenderer, JNIEnv&, jni::Object<MapRenderer>, jni::Object<FileSource>, jni::jfloat, jni::String>,
"nativeInitialize",
"finalize",
METHOD(&MapRenderer::render, "nativeRender"),
METHOD(&MapRenderer::onSurfaceCreated, "nativeOnSurfaceCreated"),
METHOD(&MapRenderer::onSurfaceChanged, "nativeOnSurfaceChanged")
);
}
MapRenderer& MapRenderer::getNativePeer(JNIEnv& env, jni::Object<MapRenderer> jObject) {
static auto field = MapRenderer::javaClass.GetField<jlong>(env, "nativePtr");
MapRenderer* mapRenderer = reinterpret_cast<MapRenderer *>(jObject.Get(env, field));
assert(mapRenderer != nullptr);
return *mapRenderer;
}
} // namespace android
} // namespace mbgl

View File

@@ -0,0 +1,95 @@
#pragma once
#include <mbgl/actor/actor_ref.hpp>
#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <memory>
#include <utility>
#include <jni/jni.hpp>
#include "jni/generic_global_ref_deleter.hpp"
namespace mbgl {
class Renderer;
class RendererBackend;
class ThreadPool;
class UpdateParameters;
namespace android {
class AndroidRendererBackend;
class FileSource;
/**
* The MapRenderer is a peer class that encapsulates the actions
* performed on the GL Thread.
*
* The public methods are safe to call from the main thread, others are not.
*/
class MapRenderer : public Scheduler {
public:
static constexpr auto Name() { return "com/mapbox/mapboxsdk/maps/renderer/MapRenderer"; };
static jni::Class<MapRenderer> javaClass;
static void registerNative(jni::JNIEnv&);
static MapRenderer& getNativePeer(JNIEnv&, jni::Object<MapRenderer>);
MapRenderer(jni::JNIEnv& _env,
jni::Object<MapRenderer>,
jni::Object<FileSource>,
jni::jfloat pixelRatio,
jni::String programCacheDir);
~MapRenderer() override;
// Resets the renderer to clean up on the calling thread
void reset();
// Sets the new update parameters to use on subsequent
// renders. Be sure to trigger a render with
// requestRender().
void update(std::shared_ptr<UpdateParameters>);
// Gives a handle to the Renderer to enable actions on
// any thread.
ActorRef<Renderer> actor() const;
// From Scheduler. Schedules by using callbacks to the
// JVM to process the mailbox on the right thread.
void schedule(std::weak_ptr<Mailbox> scheduled) override;
void requestRender();
private:
// Called from the GL Thread //
// Renders a frame.
void render(JNIEnv&);
void onSurfaceCreated(JNIEnv&);
void onSurfaceChanged(JNIEnv&, jint width, jint height);
private:
GenericUniqueWeakObject<MapRenderer> javaPeer;
std::shared_ptr<ThreadPool> threadPool;
std::unique_ptr<AndroidRendererBackend> backend;
std::unique_ptr<Renderer> renderer;
std::shared_ptr<Mailbox> mailbox;
ActorRef<Renderer> rendererRef;
std::shared_ptr<UpdateParameters> updateParameters;
std::mutex updateMutex;
bool framebufferSizeChanged = false;
};
} // namespace android
} // namespace mbgl

View File

@@ -0,0 +1,49 @@
#include "map_renderer_runnable.hpp"
#include <mbgl/util/logging.hpp>
namespace mbgl {
namespace android {
MapRendererRunnable::MapRendererRunnable(jni::JNIEnv& env, std::weak_ptr<Mailbox> mailbox_)
: mailbox(std::move(mailbox_)) {
// Create the Java peer
jni::UniqueLocalFrame frame = jni::PushLocalFrame(env, 5);
static auto constructor = javaClass.GetConstructor<jlong>(env);
auto instance = javaClass.New(env, constructor, reinterpret_cast<jlong>(this));
javaPeer = SeizeGenericWeakRef(env, jni::Object<MapRendererRunnable>(jni::NewWeakGlobalRef(env, instance.Get()).release()));
}
MapRendererRunnable::~MapRendererRunnable() = default;
void MapRendererRunnable::run(jni::JNIEnv&) {
Mailbox::maybeReceive(mailbox);
}
jni::Object<MapRendererRunnable> MapRendererRunnable::getPeer() {
return *javaPeer;
}
// Static methods //
jni::Class<MapRendererRunnable> MapRendererRunnable::javaClass;
void MapRendererRunnable::registerNative(jni::JNIEnv& env) {
// Lookup the class
MapRendererRunnable::javaClass = *jni::Class<MapRendererRunnable>::Find(env).NewGlobalRef(env).release();
#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
jni::RegisterNativePeer<MapRendererRunnable>(
env,
MapRendererRunnable::javaClass,
"nativePtr",
std::make_unique<MapRendererRunnable, JNIEnv&>,
"nativeInitialize",
"finalize",
METHOD(&MapRendererRunnable::run, "run"));
}
} // namespace android
} // namespace mbgl

View File

@@ -0,0 +1,50 @@
#pragma once
#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <memory>
#include <utility>
#include <jni/jni.hpp>
#include "jni/generic_global_ref_deleter.hpp"
namespace mbgl {
namespace android {
/**
* The MapRendererRunnable is a peer class that encapsulates
* a scheduled mailbox in a Java Runnable so it can be
* scheduled on the map renderer thread.
*
*/
class MapRendererRunnable {
public:
static constexpr auto Name() { return "com/mapbox/mapboxsdk/maps/renderer/MapRendererRunnable"; };
static jni::Class<MapRendererRunnable> javaClass;
static void registerNative(jni::JNIEnv&);
MapRendererRunnable(jni::JNIEnv&, std::weak_ptr<Mailbox>);
// Only for jni registration, unused
MapRendererRunnable(jni::JNIEnv&) {
assert(false);
}
~MapRendererRunnable();
void run(jni::JNIEnv&);
jni::Object<MapRendererRunnable> getPeer();
private:
GenericUniqueWeakObject<MapRendererRunnable> javaPeer;
std::weak_ptr<Mailbox> mailbox;
};
} // namespace android
} // namespace mbgl

View File

@@ -9,12 +9,10 @@
#include <sys/system_properties.h>
#include <EGL/egl.h>
#include <android/native_window_jni.h>
#include <jni/jni.hpp>
#include <mbgl/renderer/backend_scope.hpp>
#include <mbgl/math/minmax.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/event.hpp>
@@ -28,7 +26,6 @@
#include <mbgl/style/style.hpp>
#include <mbgl/style/image.hpp>
#include <mbgl/style/filter.hpp>
#include <mbgl/renderer/renderer.hpp>
// Java -> C++ conversion
#include "style/android_conversion.hpp"
@@ -43,8 +40,9 @@
#include "jni.hpp"
#include "attach_env.hpp"
#include "android_renderer_backend.hpp"
#include "map_renderer.hpp"
#include "android_renderer_frontend.hpp"
#include "file_source.hpp"
#include "bitmap.hpp"
#include "run_loop_impl.hpp"
#include "java/util.hpp"
@@ -59,8 +57,8 @@ namespace android {
NativeMapView::NativeMapView(jni::JNIEnv& _env,
jni::Object<NativeMapView> _obj,
jni::Object<FileSource> jFileSource,
jni::jfloat _pixelRatio,
jni::String _programCacheDir)
jni::Object<MapRenderer> jMapRenderer,
jni::jfloat _pixelRatio)
: javaPeer(_obj.NewWeakGlobalRef(_env)),
pixelRatio(_pixelRatio),
threadPool(sharedThreadPool()) {
@@ -71,15 +69,12 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env,
return;
}
// Get native peers
mbgl::FileSource& fileSource = mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource);
MapRenderer& mapRenderer = MapRenderer::getNativePeer(_env, jMapRenderer);
// Create a renderer frontend
rendererFrontend = std::make_unique<AndroidRendererFrontend>(pixelRatio, fileSource,
*threadPool,
jni::Make<std::string>(_env,
_programCacheDir),
[this] { this->requestRender(); },
[this] { this->requestProcessing(); });
rendererFrontend = std::make_unique<AndroidRendererFrontend>(mapRenderer);
// Create the core map
map = std::make_unique<mbgl::Map>(*rendererFrontend, *this,
@@ -97,24 +92,6 @@ NativeMapView::~NativeMapView() {
vm = nullptr;
}
/**
* Callback to java NativeMapView#requestRender().
*
* Called to schedule a render on the next
* runloop iteration.
*/
void NativeMapView::requestRender() {
android::UniqueEnv _env = android::AttachEnv();
static auto onInvalidate = javaClass.GetMethod<void ()>(*_env, "requestRender");
javaPeer->Call(*_env, onInvalidate);
}
void NativeMapView::requestProcessing() {
android::UniqueEnv _env = android::AttachEnv();
static auto requestProcessing = javaClass.GetMethod<void ()>(*_env, "requestProcessing");
javaPeer->Call(*_env, requestProcessing);
}
/**
* From mbgl::RendererBackend. Callback to java NativeMapView#onMapChanged(int).
*
@@ -194,31 +171,12 @@ void NativeMapView::onSourceChanged(mbgl::style::Source&) {
// JNI Methods //
// Called from the OpenGL renderer thread
void NativeMapView::render(jni::JNIEnv& ) {
rendererFrontend->render();
// TODO
updateFps();
}
// Called from the OpenGL renderer thread
void NativeMapView::process(jni::JNIEnv&) {
rendererFrontend->process();
}
void NativeMapView::resizeView(jni::JNIEnv&, int w, int h) {
width = util::max(64, w);
height = util::max(64, h);
map->setSize({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) });
}
void NativeMapView::resizeFramebuffer(jni::JNIEnv&, int w, int h) {
rendererFrontend->resizeFramebuffer(w, h);
// framebufferSizeChanged = true;
// invalidate();
}
jni::String NativeMapView::getStyleUrl(jni::JNIEnv& env) {
return jni::Make<jni::String>(env, map->getStyle().getURL());
}
@@ -969,6 +927,7 @@ jni::jboolean NativeMapView::getPrefetchesTiles(JNIEnv&) {
// Private methods //
// TODO
void NativeMapView::updateFps() {
if (!fpsEnabled) {
return;
@@ -1008,13 +967,10 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
// Register the peer
jni::RegisterNativePeer<NativeMapView>(env, NativeMapView::javaClass, "nativePtr",
std::make_unique<NativeMapView, JNIEnv&, jni::Object<NativeMapView>, jni::Object<FileSource>, jni::jfloat, jni::String>,
std::make_unique<NativeMapView, JNIEnv&, jni::Object<NativeMapView>, jni::Object<FileSource>, jni::Object<MapRenderer>, jni::jfloat>,
"nativeInitialize",
"nativeDestroy",
METHOD(&NativeMapView::render, "nativeRender"),
METHOD(&NativeMapView::process, "nativeProcess"),
METHOD(&NativeMapView::resizeView, "nativeResizeView"),
METHOD(&NativeMapView::resizeFramebuffer, "nativeResizeFramebuffer"),
METHOD(&NativeMapView::getStyleUrl, "nativeGetStyleUrl"),
METHOD(&NativeMapView::setStyleUrl, "nativeSetStyleUrl"),
METHOD(&NativeMapView::getStyleJson, "nativeGetStyleJson"),

View File

@@ -1,6 +1,5 @@
#pragma once
#include <mbgl/renderer/renderer_backend.hpp>
#include <mbgl/map/change.hpp>
#include <mbgl/map/camera.hpp>
#include <mbgl/map/map.hpp>
@@ -10,7 +9,6 @@
#include <mbgl/storage/default_file_source.hpp>
#include <mbgl/storage/network_status.hpp>
#include "file_source.hpp"
#include "annotation/marker.hpp"
#include "annotation/polygon.hpp"
#include "annotation/polyline.hpp"
@@ -37,6 +35,8 @@ namespace mbgl {
namespace android {
class AndroidRendererFrontend;
class FileSource;
class MapRenderer;
class NativeMapView : public MapObserver {
public:
@@ -50,8 +50,8 @@ public:
NativeMapView(jni::JNIEnv&,
jni::Object<NativeMapView>,
jni::Object<FileSource>,
jni::jfloat pixelRatio,
jni::String programCacheDir);
jni::Object<MapRenderer>,
jni::jfloat pixelRatio);
virtual ~NativeMapView();
@@ -72,28 +72,10 @@ public:
void onDidFinishLoadingStyle() override;
void onSourceChanged(mbgl::style::Source&) override;
// Signal the view system, we want to redraw
void requestRender();
// Request processing on the GL Thread
void requestProcessing();
// JNI //
// Called on OpenGL Thread
void onSurfaceCreated(jni::JNIEnv&);
// Called on OpenGL Thread
void process(jni::JNIEnv&);
// Called on OpenGL Thread
void render(jni::JNIEnv&);
void resizeView(jni::JNIEnv&, int, int);
// Called on OpenGL Thread
void resizeFramebuffer(jni::JNIEnv&, int, int);
jni::String getStyleUrl(jni::JNIEnv&);
void setStyleUrl(jni::JNIEnv&, jni::String);