From bccfcddfcff117aedf78a5892b11c53e716cee25 Mon Sep 17 00:00:00 2001 From: Adrian Cojocaru Date: Wed, 13 May 2026 17:07:13 +0300 Subject: [PATCH] Add asynchronous deletion option for Android renderer backend (#4287) --- .../src/cpp/android_gl_renderer_backend.cpp | 8 +--- .../MapLibreAndroid/src/cpp/map_renderer.cpp | 6 ++- .../MapLibreAndroid/src/cpp/map_renderer.hpp | 3 ++ .../src/cpp/native_map_options.cpp | 6 +++ .../src/cpp/native_map_options.hpp | 1 + .../src/cpp/native_map_view.cpp | 2 + .../android/maps/MapLibreMapOptions.java | 40 +++++++++++++++++++ .../android/maps/NativeMapOptions.java | 8 ++++ .../src/main/res/values/attrs.xml | 2 + 9 files changed, 68 insertions(+), 8 deletions(-) diff --git a/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp index 8bed72649..49aadd27e 100644 --- a/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp @@ -37,13 +37,7 @@ AndroidGLRendererBackend::AndroidGLRendererBackend() : gl::RendererBackend(gfx::ContextMode::Unique), mbgl::gfx::Renderable({64, 64}, std::make_unique(*this)) {} -AndroidGLRendererBackend::~AndroidGLRendererBackend() { -#ifndef NDEBUG - if (context && static_cast(*context).getCleanupOnDestruction()) { - assert(eglGetCurrentContext() != EGL_NO_CONTEXT); - } -#endif -} +AndroidGLRendererBackend::~AndroidGLRendererBackend() = default; gl::ProcAddress AndroidGLRendererBackend::getExtensionFunctionPointer(const char* name) { assert(gfx::BackendScope::exists()); diff --git a/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp b/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp index e79e593bf..a5696c3b4 100644 --- a/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp @@ -196,7 +196,11 @@ void MapRenderer::requestSnapshot(SnapshotCallback callback) { void MapRenderer::resetRenderer() { renderer.reset(); - backend.reset(); + + if (!asyncRendererCleanup) { + backend.reset(); + } + window.reset(); swapBehaviorFlush = false; } diff --git a/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp b/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp index 0d68818a4..c6500359b 100644 --- a/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp @@ -92,6 +92,8 @@ public: AndroidRendererBackend& getRendererBackend() const { return *backend; } const TaggedScheduler& getThreadPool() const { return threadPool; } + void SetAsyncRendererCleanup(bool value) { asyncRendererCleanup = value; } + protected: // Called from the GL Thread // @@ -156,6 +158,7 @@ private: bool framebufferSizeChanged = false; bool swapBehaviorFlush = false; + bool asyncRendererCleanup = false; mapbox::base::WeakPtrFactory weakFactory{this}; // Do not add members here, see `WeakPtrFactory` diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_options.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_options.cpp index 45182fcfe..ad217c729 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_options.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_options.cpp @@ -41,5 +41,11 @@ bool NativeMapOptions::crossSourceCollisions(jni::JNIEnv &env, const jni::Object return obj.Get(env, crossSourceCollisionsField); } +bool NativeMapOptions::asyncRendererCleanup(jni::JNIEnv &env, const jni::Object &obj) { + auto &javaClass = jni::Class::Singleton(env); + auto asyncRendererCleanupField = javaClass.GetField(env, "asyncRendererCleanup"); + return obj.Get(env, asyncRendererCleanupField); +} + } // namespace android } // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_options.hpp b/platform/android/MapLibreAndroid/src/cpp/native_map_options.hpp index b951af617..79ac13487 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_options.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_options.hpp @@ -19,6 +19,7 @@ public: static util::ActionJournalOptions getActionJournalOptions(jni::JNIEnv&, const jni::Object&); static float pixelRatio(jni::JNIEnv&, const jni::Object&); static bool crossSourceCollisions(jni::JNIEnv&, const jni::Object&); + static bool asyncRendererCleanup(jni::JNIEnv&, const jni::Object&); }; } // namespace android diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp index d817e23dd..c351840a8 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp @@ -74,6 +74,8 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env, return; } + mapRenderer.SetAsyncRendererCleanup(NativeMapOptions::asyncRendererCleanup(_env, jNativeMapOptions)); + // Create a renderer frontend rendererFrontend = AndroidRendererFrontend::create(_env, jMapRenderer); diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMapOptions.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMapOptions.java index c54bd5c50..ebaf423e1 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMapOptions.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMapOptions.java @@ -100,6 +100,8 @@ public class MapLibreMapOptions implements Parcelable { private long actionJournalLogFileCount = 5; private int actionJournalRenderingReportInterval = 60; + private boolean asyncRendererCleanup = false; + /** * Creates a new MapLibreMapOptions object. * @@ -163,6 +165,8 @@ public class MapLibreMapOptions implements Parcelable { actionJournalLogFileSize = in.readLong(); actionJournalLogFileCount = in.readLong(); actionJournalRenderingReportInterval = in.readInt(); + + asyncRendererCleanup = in.readByte() != 0; } /** @@ -332,6 +336,10 @@ public class MapLibreMapOptions implements Parcelable { maplibreMapOptions.actionJournalRenderingReportInterval( typedArray.getInteger(R.styleable.maplibre_MapView_maplibre_actionJournalRenderingReportInterval, 60) ); + + maplibreMapOptions.asyncRendererCleanup( + typedArray.getBoolean(R.styleable.maplibre_MapView_maplibre_asyncRendererCleanup, false) + ); } finally { typedArray.recycle(); } @@ -828,6 +836,22 @@ public class MapLibreMapOptions implements Parcelable { return this; } + /** + * By default, backend cleanup executes on the render thread + * (with the main thread waiting for the task to finish). + * Enabling this defers cleanup work to the garbage collector/finalzer thread. This can reduce + * shutdown stalls on the main thread, but introduces a non-deterministic cleanup timing and + * might expose possible OpenGL driver issues. + * + * @param asyncRendererCleanup true to enable, false to disable + * @return This + */ + @NonNull + public MapLibreMapOptions asyncRendererCleanup(boolean asyncRendererCleanup) { + this.asyncRendererCleanup = asyncRendererCleanup; + return this; + } + /** * Enable local ideograph font family, defaults to true. * @@ -966,6 +990,15 @@ public class MapLibreMapOptions implements Parcelable { return actionJournalRenderingReportInterval; } + /** + * Check whether the renderer backend is async + * + * @return true if async + */ + public boolean getAsyncRendererCleanup() { + return asyncRendererCleanup; + } + /** * Set the flag to render the map surface on top of another surface. * @@ -1356,6 +1389,8 @@ public class MapLibreMapOptions implements Parcelable { dest.writeLong(actionJournalLogFileSize); dest.writeLong(actionJournalLogFileCount); dest.writeInt(actionJournalRenderingReportInterval); + + dest.writeByte((byte) (asyncRendererCleanup ? 1 : 0)); } @Override @@ -1496,6 +1531,10 @@ public class MapLibreMapOptions implements Parcelable { return false; } + if (asyncRendererCleanup != options.asyncRendererCleanup) { + return false; + } + return false; } @@ -1548,6 +1587,7 @@ public class MapLibreMapOptions implements Parcelable { result = 31 * result + (int) actionJournalLogFileSize; result = 31 * result + (int) actionJournalLogFileCount; result = 31 * result + (int) actionJournalRenderingReportInterval; + result = 31 * result + (asyncRendererCleanup ? 1 : 0); return result; } } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapOptions.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapOptions.java index 30c45a94f..fbfb5f234 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapOptions.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapOptions.java @@ -10,6 +10,7 @@ public class NativeMapOptions { private final long actionJournalLogFileSize; private final long actionJournalLogFileCount; private final int actionJournalRenderingReportInterval; + private final boolean asyncRendererCleanup; public NativeMapOptions(MapLibreMapOptions options) { pixelRatio = options.getPixelRatio(); @@ -20,6 +21,7 @@ public class NativeMapOptions { actionJournalLogFileSize = options.getActionJournalLogFileSize(); actionJournalLogFileCount = options.getActionJournalLogFileCount(); actionJournalRenderingReportInterval = options.getActionJournalRenderingReportInterval(); + asyncRendererCleanup = options.getAsyncRendererCleanup(); } public NativeMapOptions(float pixelRatio, boolean crossSourceCollisions) { @@ -31,6 +33,8 @@ public class NativeMapOptions { actionJournalLogFileSize = 0; actionJournalLogFileCount = 0; actionJournalRenderingReportInterval = 0; + + asyncRendererCleanup = false; } public float pixelRatio() { @@ -60,4 +64,8 @@ public class NativeMapOptions { public int actionJournalRenderingReportInterval() { return actionJournalRenderingReportInterval; } + + public boolean asyncRendererCleanup() { + return asyncRendererCleanup; + } } diff --git a/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml b/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml index e38086bdb..eabee6de1 100644 --- a/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml +++ b/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml @@ -18,6 +18,8 @@ + +