From 5d12503302dff168137d6f3b1444e4dc32ad44dd Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Thu, 14 Sep 2017 21:23:07 +0300 Subject: [android] self-contained map renderer - Isolates the GL thread in a MapRenderer class with a native peer --- .../java/com/mapbox/mapboxsdk/maps/MapView.java | 32 ++--- .../com/mapbox/mapboxsdk/maps/NativeMapView.java | 137 +++------------------ .../maps/renderer/GlSurfaceViewRenderThread.java | 25 ---- .../mapboxsdk/maps/renderer/MapRenderer.java | 119 ++++++++++++++++++ .../maps/renderer/MapRendererRunnable.java | 29 +++++ .../maps/renderer/MapRendererScheduler.java | 13 ++ .../mapboxsdk/maps/renderer/RenderThread.java | 12 -- platform/android/config.cmake | 4 + platform/android/src/android_gl_thread.hpp | 74 ----------- platform/android/src/android_renderer_frontend.cpp | 92 ++------------ platform/android/src/android_renderer_frontend.hpp | 35 +----- platform/android/src/jni.cpp | 4 + platform/android/src/map_renderer.cpp | 137 +++++++++++++++++++++ platform/android/src/map_renderer.hpp | 95 ++++++++++++++ platform/android/src/map_renderer_runnable.cpp | 49 ++++++++ platform/android/src/map_renderer_runnable.hpp | 50 ++++++++ platform/android/src/native_map_view.cpp | 62 ++-------- platform/android/src/native_map_view.hpp | 26 +--- 18 files changed, 560 insertions(+), 435 deletions(-) delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/GlSurfaceViewRenderThread.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererRunnable.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererScheduler.java delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/RenderThread.java delete mode 100644 platform/android/src/android_gl_thread.hpp create mode 100644 platform/android/src/map_renderer.cpp create mode 100644 platform/android/src/map_renderer.hpp create mode 100644 platform/android/src/map_renderer_runnable.cpp create mode 100644 platform/android/src/map_renderer_runnable.hpp 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 086f57abf6..6612110649 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 @@ -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); - } - - @Override - public void onSurfaceChanged(GL10 gl, int width, int height) { - nativeMapView.onSurfaceChanged(gl, width, height); + super.onSurfaceCreated(gl, config); } + }; - @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); } } 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 240e68bf13..62fe2be0c3 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,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) { + public NativeMapView(final MapView mapView, MapRenderer mapRenderer) { + this.scheduler = mapRenderer; + this.mapView = mapView; + Context context = mapView.getContext(); fileSource = FileSource.getInstance(context); - pixelRatio = context.getResources().getDisplayMetrics().density; - this.mapView = mapView; - String programCacheDir = context.getCacheDir().getAbsolutePath(); - nativeInitialize(this, fileSource, pixelRatio, programCacheDir); + 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,31 +137,8 @@ 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); + nativeResizeView(width, height); } public void setStyleUrl(String url) { @@ -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(); - } - - // - // 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? + scheduler.requestRender(); } - @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(); - } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/GlSurfaceViewRenderThread.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/GlSurfaceViewRenderThread.java deleted file mode 100644 index 4b8df51dbe..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/GlSurfaceViewRenderThread.java +++ /dev/null @@ -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); - } -} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java new file mode 100644 index 0000000000..772ecb79fb --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java @@ -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. + *

+ * 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. + *

+ * Called from the renderer frontend to schedule a render. + */ + @Override + public void requestRender() { + glSurfaceView.requestRender(); + } + + /** + * May be called from any thread. + *

+ * 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. + *

+ * 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(); + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererRunnable.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererRunnable.java new file mode 100644 index 0000000000..28246fe578 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererRunnable.java @@ -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(); + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererScheduler.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererScheduler.java new file mode 100644 index 0000000000..7ad4f124d8 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRendererScheduler.java @@ -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); + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/RenderThread.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/RenderThread.java deleted file mode 100644 index 3b9f0f2151..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/RenderThread.java +++ /dev/null @@ -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); -} diff --git a/platform/android/config.cmake b/platform/android/config.cmake index 539515a6e5..227334e0b5 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -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 diff --git a/platform/android/src/android_gl_thread.hpp b/platform/android/src/android_gl_thread.hpp deleted file mode 100644 index 78777aa475..0000000000 --- a/platform/android/src/android_gl_thread.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace mbgl { -namespace android { - -class AndroidGLThread : public Scheduler { -public: - using InvalidateCallback = std::function; - - template - AndroidGLThread(InvalidateCallback callback_, Args&&... args) - : renderer(std::make_unique(std::forward(args)...)) - , mailbox(std::make_shared(*this)) - , callback(callback_) - , rendererRef(*renderer, mailbox) { - } - - ~AndroidGLThread() override = default; - - ActorRef actor() const { - return rendererRef; - } - - void schedule(std::weak_ptr scheduled) override { - { - std::lock_guard lock(mutex); - queue.push(scheduled); - } - - // Invalidate so we get processing time later - callback(); - } - - // Only safe on the GL Thread - void process() { - std::unique_lock 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; - -private: - std::mutex mutex; - std::queue> queue; - - std::shared_ptr mailbox; - InvalidateCallback callback; - ActorRef rendererRef; -}; - -} // namespace android -} // namespace mbgl diff --git a/platform/android/src/android_renderer_frontend.cpp b/platform/android/src/android_renderer_frontend.cpp index 0619ba4cb5..38b1ff252b 100644 --- a/platform/android/src/android_renderer_frontend.cpp +++ b/platform/android/src/android_renderer_frontend.cpp @@ -54,124 +54,62 @@ private: ActorRef delegate; }; -AndroidRendererFrontend::AndroidRendererFrontend(float pixelRatio, - FileSource& fileSource, - Scheduler& scheduler, - std::string programCacheDir, - RequestRenderCallback requestRender, - RequestProcessingCallback requestProcessing) - : backend(std::make_unique()) - , glThread(std::make_unique( - 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(*mapRunLoop, observer); - glThread->actor().invoke(&Renderer::setObserver, rendererObserver.get()); + mapRenderer.actor().invoke(&Renderer::setObserver, rendererObserver.get()); } void AndroidRendererFrontend::update(std::shared_ptr params) { - { - // Lock on the parameters - std::lock_guard lock(updateMutex); - updateParameters = std::move(params); - } - asyncInvalidate.send(); -} - -// Called on OpenGL thread -void AndroidRendererFrontend::render() { - assert (glThread); - - std::shared_ptr params; - { - // Lock on the parameters - std::unique_lock 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 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 AndroidRendererFrontend::queryRenderedFeatures(const ScreenBox& box, const RenderedQueryOptions& options) const { - assert (glThread); // Select the right overloaded method std::vector (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 AndroidRendererFrontend::queryRenderedFeatures(const ScreenCoordinate& point, const RenderedQueryOptions& options) const { - assert (glThread); // Select the right overloaded method std::vector (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 diff --git a/platform/android/src/android_renderer_frontend.hpp b/platform/android/src/android_renderer_frontend.hpp index 57bfd62b26..598327838e 100644 --- a/platform/android/src/android_renderer_frontend.hpp +++ b/platform/android/src/android_renderer_frontend.hpp @@ -1,13 +1,9 @@ - #pragma once -#include "android_gl_thread.hpp" - #include #include #include #include -#include #include #include #include @@ -17,6 +13,8 @@ #include #include +#include "map_renderer.hpp" + namespace mbgl { class FileSource; @@ -30,15 +28,8 @@ class AndroidRendererBackend; class AndroidRendererFrontend : public RendererFrontend { public: - using RequestRenderCallback = std::function; - using RequestProcessingCallback = std::function; - - 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) override; - // Called from OpenGL Thread - void render(); - void process(); - // Feature querying std::vector queryRenderedFeatures(const ScreenCoordinate&, const RenderedQueryOptions&) const; std::vector queryRenderedFeatures(const ScreenBox&, const RenderedQueryOptions&) const; std::vector 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 backend; - std::unique_ptr glThread; std::unique_ptr rendererObserver; - - std::mutex updateMutex; - std::shared_ptr updateParameters; - - - util::AsyncTask asyncInvalidate; + MapRenderer& mapRenderer; util::RunLoop* mapRunLoop; - bool framebufferSizeChanged = true; + // TODO std::unique_ptr snapshotCallback; }; diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index f7d1e4afbc..6acb6a3664 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -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 diff --git a/platform/android/src/map_renderer.cpp b/platform/android/src/map_renderer.cpp new file mode 100644 index 0000000000..8346d2f139 --- /dev/null +++ b/platform/android/src/map_renderer.cpp @@ -0,0 +1,137 @@ +#include "map_renderer.hpp" + +#include +#include + +#include + +#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 obj, + jni::Object fileSource, + jni::jfloat pixelRatio, + jni::String programCacheDir) + : javaPeer(SeizeGenericWeakRef(_env, jni::Object(jni::NewWeakGlobalRef(_env, obj.Get()).release()))) + , threadPool(sharedThreadPool()) + , backend(std::make_unique()) + , renderer(std::make_unique(*backend, pixelRatio, FileSource::getDefaultFileSource(_env, fileSource), + *threadPool, GLContextMode::Unique, jni::Make(_env, programCacheDir))) + , mailbox(std::make_shared(*this)) + , rendererRef(*renderer, mailbox) { +} + +MapRenderer::~MapRenderer() = default; + +void MapRenderer::reset() { + assert (renderer); + renderer.reset(); +} + +ActorRef MapRenderer::actor() const { + return rendererRef; +} + +void MapRenderer::schedule(std::weak_ptr scheduled) { + // Create a runnable and schedule it on the gl thread + android::UniqueEnv _env = android::AttachEnv(); + auto runnable = std::make_unique(*_env, std::move(scheduled)); + + static auto queueEvent = javaClass.GetMethod)>(*_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(*_env, "requestRender"); + javaPeer->Call(*_env, onInvalidate); +} + +void MapRenderer::update(std::shared_ptr params) { + // Lock on the parameters + std::lock_guard lock(updateMutex); + updateParameters = std::move(params); +} + +// Called on OpenGL thread // + +void MapRenderer::render(JNIEnv&) { + assert (renderer); + + std::shared_ptr params; + { + // Lock on the parameters + std::unique_lock 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::javaClass; + +void MapRenderer::registerNative(jni::JNIEnv& env) { + // Lookup the class + MapRenderer::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release(); + +#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) + + // Register the peer + jni::RegisterNativePeer( + env, + MapRenderer::javaClass, + "nativePtr", + std::make_unique, jni::Object, 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 jObject) { + static auto field = MapRenderer::javaClass.GetField(env, "nativePtr"); + MapRenderer* mapRenderer = reinterpret_cast(jObject.Get(env, field)); + assert(mapRenderer != nullptr); + return *mapRenderer; +} + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/map_renderer.hpp b/platform/android/src/map_renderer.hpp new file mode 100644 index 0000000000..51fca48ee6 --- /dev/null +++ b/platform/android/src/map_renderer.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include + +#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 javaClass; + + static void registerNative(jni::JNIEnv&); + + static MapRenderer& getNativePeer(JNIEnv&, jni::Object); + + MapRenderer(jni::JNIEnv& _env, + jni::Object, + jni::Object, + 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); + + // Gives a handle to the Renderer to enable actions on + // any thread. + ActorRef actor() const; + + // From Scheduler. Schedules by using callbacks to the + // JVM to process the mailbox on the right thread. + void schedule(std::weak_ptr 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 javaPeer; + std::shared_ptr threadPool; + std::unique_ptr backend; + std::unique_ptr renderer; + std::shared_ptr mailbox; + ActorRef rendererRef; + + std::shared_ptr updateParameters; + std::mutex updateMutex; + + bool framebufferSizeChanged = false; +}; + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/map_renderer_runnable.cpp b/platform/android/src/map_renderer_runnable.cpp new file mode 100644 index 0000000000..df8cba5e55 --- /dev/null +++ b/platform/android/src/map_renderer_runnable.cpp @@ -0,0 +1,49 @@ +#include "map_renderer_runnable.hpp" + +#include + +namespace mbgl { +namespace android { + +MapRendererRunnable::MapRendererRunnable(jni::JNIEnv& env, std::weak_ptr mailbox_) + : mailbox(std::move(mailbox_)) { + + // Create the Java peer + jni::UniqueLocalFrame frame = jni::PushLocalFrame(env, 5); + static auto constructor = javaClass.GetConstructor(env); + auto instance = javaClass.New(env, constructor, reinterpret_cast(this)); + javaPeer = SeizeGenericWeakRef(env, jni::Object(jni::NewWeakGlobalRef(env, instance.Get()).release())); +} + +MapRendererRunnable::~MapRendererRunnable() = default; + +void MapRendererRunnable::run(jni::JNIEnv&) { + Mailbox::maybeReceive(mailbox); +} + +jni::Object MapRendererRunnable::getPeer() { + return *javaPeer; +} + +// Static methods // + +jni::Class MapRendererRunnable::javaClass; + +void MapRendererRunnable::registerNative(jni::JNIEnv& env) { + // Lookup the class + MapRendererRunnable::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release(); + +#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) + + jni::RegisterNativePeer( + env, + MapRendererRunnable::javaClass, + "nativePtr", + std::make_unique, + "nativeInitialize", + "finalize", + METHOD(&MapRendererRunnable::run, "run")); +} + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/map_renderer_runnable.hpp b/platform/android/src/map_renderer_runnable.hpp new file mode 100644 index 0000000000..75646a442d --- /dev/null +++ b/platform/android/src/map_renderer_runnable.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +#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 javaClass; + + static void registerNative(jni::JNIEnv&); + + MapRendererRunnable(jni::JNIEnv&, std::weak_ptr); + + // Only for jni registration, unused + MapRendererRunnable(jni::JNIEnv&) { + assert(false); + } + + ~MapRendererRunnable(); + + void run(jni::JNIEnv&); + + jni::Object getPeer(); + +private: + GenericUniqueWeakObject javaPeer; + std::weak_ptr mailbox; +}; + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index 51edd8079f..f7f487f227 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -9,12 +9,10 @@ #include -#include #include #include -#include #include #include #include @@ -28,7 +26,6 @@ #include #include #include -#include // 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 _obj, jni::Object jFileSource, - jni::jfloat _pixelRatio, - jni::String _programCacheDir) + jni::Object 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(pixelRatio, fileSource, - *threadPool, - jni::Make(_env, - _programCacheDir), - [this] { this->requestRender(); }, - [this] { this->requestProcessing(); }); + rendererFrontend = std::make_unique(mapRenderer); // Create the core map map = std::make_unique(*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(*_env, "requestRender"); - javaPeer->Call(*_env, onInvalidate); -} - -void NativeMapView::requestProcessing() { - android::UniqueEnv _env = android::AttachEnv(); - static auto requestProcessing = javaClass.GetMethod(*_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(width), static_cast(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(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(env, NativeMapView::javaClass, "nativePtr", - std::make_unique, jni::Object, jni::jfloat, jni::String>, + std::make_unique, jni::Object, jni::Object, 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"), diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index 7376f82c24..d88647914e 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -10,7 +9,6 @@ #include #include -#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, jni::Object, - jni::jfloat pixelRatio, - jni::String programCacheDir); + jni::Object, + 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); -- cgit v1.2.1