Add multiBackend Android flavor for runtime OpenGL/Vulkan switching (#4288)

This commit is contained in:
Veniamin Vynohradov
2026-08-06 16:54:58 +03:00
committed by GitHub
parent ccb9af119e
commit b0388d186d
47 changed files with 896 additions and 163 deletions

View File

@@ -2,8 +2,18 @@ cmake_minimum_required(VERSION 3.10)
project(MapLibreAndroid)
add_subdirectory(../../../../../
../../../../../${ANDROID_ABI})
# (a) Multi-flavor build is self-contained in MultiBackend.cmake and only entered for the
# Gradle `multiBackend` flavor (which passes -DMLN_ANDROID_MULTI_BACKEND=ON). It never touches
# the shared top-level CMakeLists; the single-backend path below is unaffected.
if(MLN_ANDROID_MULTI_BACKEND)
include(${CMAKE_CURRENT_SOURCE_DIR}/MultiBackend.cmake)
return()
endif()
# (b) Build core into THIS configure's own build dir (was: ../../../../../${ANDROID_ABI}, an
# out-of-tree path both multi sub-builds collided on). Each configure now has its own dir, so
# the multi sub-builds isolate automatically -- no MLN_ANDROID_TOP_BINARY_DIR guard needed.
add_subdirectory(../../../../../ ${CMAKE_CURRENT_BINARY_DIR}/maplibre-core)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

View File

@@ -0,0 +1,85 @@
# Android multi-backend build (Gradle `multiBackend` flavor only).
#
# Builds libmaplibre.so (Vulkan) and libmaplibre-opengl.so (OpenGL) by invoking this directory's
# stock single-backend build twice via ExternalProject, and surfaces both to AGP as SHARED
# targets so both .so land in the multiBackend AAR.
#
# Reached via include() from src/cpp/CMakeLists.txt while project(MapLibreAndroid) is in effect,
# so the add_library targets below land in the AGP-invoked MapLibreAndroid project (AGP's CMake
# file-API query only sees targets declared there) and CMAKE_CURRENT_SOURCE_DIR stays src/cpp.
include(ExternalProject)
set(_src ${CMAKE_CURRENT_SOURCE_DIR}) # src/cpp -- the single-backend entry
# Forward the exact Android toolchain AGP handed this configure to each sub-build. Each sub-build
# takes the single-backend path (MLN_ANDROID_MULTI_BACKEND=OFF) and builds core into its own
# BINARY_DIR (see src/cpp/CMakeLists.txt edit (b)), so the two sub-builds isolate automatically.
set(_fwd
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
-DANDROID_ABI=${ANDROID_ABI}
-DANDROID_PLATFORM=${ANDROID_PLATFORM}
-DANDROID_NDK=${ANDROID_NDK}
-DANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN}
-DANDROID_STL=${ANDROID_STL}
-DANDROID_CPP_FEATURES=${ANDROID_CPP_FEATURES}
-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=${ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DMLN_ANDROID_MULTI_BACKEND=OFF)
# Distinct BINARY_DIR per backend => distinct core build dir => no shared static-lib / LTO dir.
set(_gl ${CMAKE_BINARY_DIR}/multi-opengl)
set(_vk ${CMAKE_BINARY_DIR}/multi-vulkan)
ExternalProject_Add(maplibre-opengl-build
SOURCE_DIR ${_src}
BINARY_DIR ${_gl}
CMAKE_ARGS
${_fwd}
-DMLN_WITH_OPENGL=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${_gl}/lib
BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --target maplibre
INSTALL_COMMAND ""
BUILD_ALWAYS 1)
ExternalProject_Add(maplibre-vulkan-build
SOURCE_DIR ${_src}
BINARY_DIR ${_vk}
CMAKE_ARGS
${_fwd}
-DMLN_WITH_VULKAN=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${_vk}/lib
BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --target maplibre
INSTALL_COMMAND ""
BUILD_ALWAYS 1)
# Surface both externally-built .so to AGP via stub SHARED targets whose output file is
# overwritten with the real backend .so. AGP packages targets it builds; ExternalProject outputs
# are opaque to it and IMPORTED targets aren't packaged -- hence this bridge.
set(_stub ${CMAKE_BINARY_DIR}/maplibre_multi_stub.cpp)
file(WRITE ${_stub} "extern \"C\" void maplibre_multi_backend_stub() {}\n")
add_library(maplibre-opengl SHARED ${_stub}) # -> libmaplibre-opengl.so
add_dependencies(maplibre-opengl maplibre-opengl-build)
add_custom_command(TARGET maplibre-opengl POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${_gl}/lib/libmaplibre.so
$<TARGET_FILE:maplibre-opengl>
VERBATIM)
add_library(maplibre-vulkan SHARED ${_stub})
# Produce libmaplibre.so (not libmaplibre-vulkan.so): Vulkan is the multiBackend AAR's
# default/primary library, matching the SDK-wide convention where Vulkan is the default
# backend and OpenGL ships as the explicitly-named "-opengl" variant.
set_target_properties(maplibre-vulkan PROPERTIES OUTPUT_NAME maplibre)
add_dependencies(maplibre-vulkan maplibre-vulkan-build)
add_custom_command(TARGET maplibre-vulkan POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${_vk}/lib/libmaplibre.so
$<TARGET_FILE:maplibre-vulkan>
VERBATIM)
# AGP's defaultConfig nativeTargets always lists "maplibre"; provide a custom target so
# `cmake --build . --target maplibre` triggers both .so builds.
add_custom_target(maplibre DEPENDS maplibre-opengl maplibre-vulkan)
install(TARGETS maplibre-opengl maplibre-vulkan LIBRARY DESTINATION lib)

View File

@@ -53,23 +53,7 @@ public final class MapLibre {
@UiThread
@NonNull
public static synchronized MapLibre getInstance(@NonNull Context context) {
ThreadUtils.init(context);
ThreadUtils.checkThread(TAG);
if (INSTANCE == null) {
Context appContext = context.getApplicationContext();
FileSource.initializeFileDirsPaths(appContext);
INSTANCE = new MapLibre(appContext, null);
ConnectivityReceiver.instance(appContext);
}
TileServerOptions tileServerOptions = TileServerOptions.get(WellKnownTileServer.MapLibre);
INSTANCE.tileServerOptions = tileServerOptions;
INSTANCE.apiKey = null;
FileSource fileSource = FileSource.getInstance(context);
fileSource.setTileServerOptions(tileServerOptions);
fileSource.setApiKey(null);
return INSTANCE;
return getInstance(context, null, WellKnownTileServer.MapLibre);
}
/**
@@ -89,12 +73,42 @@ public final class MapLibre {
@UiThread
@NonNull
public static synchronized MapLibre getInstance(@NonNull Context context, @Nullable String apiKey,
WellKnownTileServer tileServer) {
@NonNull WellKnownTileServer tileServer) {
return getInstance(context, apiKey, tileServer, null);
}
/**
* Get an instance of MapLibre, explicitly picking the rendering engine to use.
* <p>
* This class manages the API key, application context, and connectivity state.
* </p>
*
* @param context Android context which holds or is an application context
* @param apiKey api key
* @param tileServer the tile server whose predefined configuration will be used to
* bootstrap the SDK. The predefined configuration includes
* rules for converting resource URLs between normal and canonical forms
* and set of predefined styles available on the server.
* @param type the rendering engine to use; ignored if MapLibre has already been
* initialized in this process. If null, the default rendering engine will be used.
* @return the single instance of MapLibre
* @throws UnsupportedOperationException if {@code type} isn't supported by this
* build of the SDK (single-backend flavors only support their compiled-in backend).
*/
@UiThread
@NonNull
public static synchronized MapLibre getInstance(@NonNull Context context, @Nullable String apiKey,
@NonNull WellKnownTileServer tileServer,
@Nullable RenderingEngine.Type type) {
ThreadUtils.init(context);
ThreadUtils.checkThread(TAG);
if (INSTANCE == null) {
Timber.plant();
Context appContext = context.getApplicationContext();
if (type == null) {
type = RenderingEngine.getDefaultRenderingEngine(appContext);
}
RenderingEngine.setCurrentType(type);
FileSource.initializeFileDirsPaths(appContext);
INSTANCE = new MapLibre(appContext, apiKey);
ConnectivityReceiver.instance(appContext);

View File

@@ -7,18 +7,25 @@ import android.view.TextureView;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.surfaceview.GLSurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.surfaceview.MapLibreGLSurfaceView;
import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.textureview.GLTextureViewRenderThread;
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
/**
* Shared factory used by MapRenderer.create(). The shape of renderer construction
* (anonymous subclass wiring of initCallback, render-thread attachment) lives here;
* the backend-specific concrete-type instantiation is delegated to the
* flavor-provided {@link RendererStrategy}.
*/
@Keep
public class MapRendererFactory {
public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, TextureView textureView,
String localFontFamily, boolean translucentSurface,
Runnable initCallback) {
public final class MapRendererFactory {
private MapRendererFactory() {}
public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context,
TextureView textureView,
String localFontFamily,
boolean translucentSurface,
Runnable initCallback) {
TextureViewMapRenderer mapRenderer = new TextureViewMapRenderer(context, textureView,
localFontFamily, translucentSurface) {
@Override
@@ -27,23 +34,15 @@ public class MapRendererFactory {
super.onSurfaceCreated(surface);
}
};
mapRenderer.setRenderThread(new GLTextureViewRenderThread(textureView, mapRenderer));
RendererStrategy.attachTextureRenderThread(textureView, mapRenderer);
return mapRenderer;
}
public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@NonNull Context context, String localFontFamily,
boolean renderSurfaceOnTop, Runnable initCallback) {
MapLibreGLSurfaceView surfaceView = new MapLibreGLSurfaceView(context);
surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop);
return new GLSurfaceViewMapRenderer(context, surfaceView, localFontFamily) {
@Override
public void onSurfaceCreated(Surface surface) {
initCallback.run();
super.onSurfaceCreated(surface);
}
};
public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@NonNull Context context,
String localFontFamily,
boolean renderSurfaceOnTop,
Runnable initCallback) {
return RendererStrategy.createSurfaceViewRenderer(
context, localFontFamily, renderSurfaceOnTop, initCallback);
}
}

View File

@@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Vulkan support is optional in the multiBackend flavor; the app falls back
to OpenGL when Vulkan is unavailable. -->
<uses-feature
android:name="android.hardware.vulkan.version"
android:version="0x400003"
android:required="false" />
</manifest>

View File

@@ -0,0 +1,70 @@
package org.maplibre.android;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
/**
* multiBackend flavor: the rendering engine is selectable at runtime.
*
* <p>Call {@link #setCurrentType(Type)} before the first call to
* {@link MapLibre#getInstance(android.content.Context)} (or any other path that
* triggers native library loading). {@link #setCurrentType(Type)} throws once
* {@link MapLibre#getInstance(android.content.Context)} has been called, since the
* native library is loaded by then and the selection can no longer take effect for
* the current process.</p>
*/
@Keep
public final class RenderingEngine {
/**
* Matches the android.hardware.vulkan.version `uses-feature` declared for this flavor.
*/
private static final int REQUIRED_VULKAN_VERSION = 0x400003;
public enum Type {
OPENGL,
VULKAN
}
private static volatile Type currentType = Type.VULKAN;
private RenderingEngine() {}
@NonNull
public static Type getCurrentType() {
return currentType;
}
/**
* @throws IllegalStateException if {@link MapLibre#getInstance(android.content.Context)}
* has already been called in this process.
*/
static void setCurrentType(@NonNull Type type) {
if (MapLibre.hasInstance()) {
throw new IllegalStateException(
"RenderingEngine.setCurrentType() must be called before MapLibre.getInstance().");
}
currentType = type;
}
/**
* Determines which rendering engine to use when none was explicitly requested:
* Vulkan if this device's hardware supports it, OpenGL otherwise.
*/
@NonNull
static Type getDefaultRenderingEngine(@NonNull Context context) {
return isVulkanSupported(context) ? Type.VULKAN : Type.OPENGL;
}
private static boolean isVulkanSupported(@NonNull Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return false;
}
return context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, REQUIRED_VULKAN_VERSION);
}
}

View File

@@ -0,0 +1,41 @@
package org.maplibre.android.maps.renderer;
import android.content.Context;
import android.view.TextureView;
import androidx.annotation.NonNull;
import org.maplibre.android.RenderingEngine;
import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
/**
* multiBackend flavor glue. Dispatches to {@link OpenGLRendererStrategy} or
* {@link VulkanRendererStrategy} based on {@link RenderingEngine#getCurrentType()}
* at the moment the renderer is constructed.
*/
final class RendererStrategy {
private RendererStrategy() {}
static void attachTextureRenderThread(@NonNull TextureView textureView,
@NonNull TextureViewMapRenderer renderer) {
if (RenderingEngine.getCurrentType() == RenderingEngine.Type.VULKAN) {
VulkanRendererStrategy.attachTextureRenderThread(textureView, renderer);
} else {
OpenGLRendererStrategy.attachTextureRenderThread(textureView, renderer);
}
}
static SurfaceViewMapRenderer createSurfaceViewRenderer(@NonNull Context context,
String localFontFamily,
boolean renderSurfaceOnTop,
Runnable initCallback) {
if (RenderingEngine.getCurrentType() == RenderingEngine.Type.VULKAN) {
return VulkanRendererStrategy.createSurfaceViewRenderer(
context, localFontFamily, renderSurfaceOnTop, initCallback);
}
return OpenGLRendererStrategy.createSurfaceViewRenderer(
context, localFontFamily, renderSurfaceOnTop, initCallback);
}
}

View File

@@ -0,0 +1,31 @@
package org.maplibre.android.module.loader;
import org.maplibre.android.LibraryLoader;
import org.maplibre.android.LibraryLoaderProvider;
import org.maplibre.android.RenderingEngine;
/**
* multiBackend flavor: when asked to load "maplibre", routes to either
* libmaplibre.so (Vulkan, the default name) or libmaplibre-opengl.so based on
* {@link RenderingEngine#getCurrentType()}. Any other library name is loaded
* verbatim.
*/
public class LibraryLoaderProviderImpl implements LibraryLoaderProvider {
@Override
public LibraryLoader getDefaultLibraryLoader() {
return new MultiBackendLibraryLoader();
}
private static class MultiBackendLibraryLoader extends LibraryLoader {
@Override
public void load(String name) {
if ("maplibre".equals(name)
&& RenderingEngine.getCurrentType() == RenderingEngine.Type.OPENGL) {
System.loadLibrary("maplibre-opengl");
} else {
System.loadLibrary(name);
}
}
}
}

View File

@@ -0,0 +1,70 @@
package org.maplibre.android;
import android.content.Context;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
/**
* The rendering engine baked into this build of the SDK.
*
* <p>In single-backend flavors (opengl, vulkan, ...) the engine is fixed at build
* time and {@link #setCurrentType(Type)} only accepts the matching value. In the
* multiBackend flavor this class is mutable and {@code setCurrentType(...)} must
* be called before {@link MapLibre#getInstance(android.content.Context)} to pick
* a backend for the process lifetime.</p>
*/
@Keep
public final class RenderingEngine {
/** Supported rendering backends. WebGPU flavors report the closest match. */
public enum Type {
OPENGL,
VULKAN
}
private static volatile Type currentType = Type.OPENGL;
private RenderingEngine() {}
/**
* @return the rendering backend used by the currently loaded library.
*/
@NonNull
public static Type getCurrentType() {
return currentType;
}
/**
* This flavor's engine is fixed at compile time, so there's nothing to select —
* always {@link Type#OPENGL}.
*/
@NonNull
static Type getDefaultRenderingEngine(@NonNull Context context) {
return Type.OPENGL;
}
/**
* Single-backend flavor: this is a no-op when {@code type} matches the
* compiled-in backend, and throws otherwise. Use the multiBackend flavor of
* the SDK if you need to switch backends at runtime.
*
* @param type the desired backend
* @throws IllegalStateException if {@link MapLibre#getInstance(android.content.Context)}
* has already been called in this process.
* @throws UnsupportedOperationException if {@code type} differs from the
* compiled-in backend
*/
static void setCurrentType(@NonNull Type type) {
if (MapLibre.hasInstance()) {
throw new IllegalStateException(
"RenderingEngine.setCurrentType() must be called before MapLibre.getInstance().");
}
if (type != Type.OPENGL) {
throw new UnsupportedOperationException(
"This MapLibre Android build supports only " + Type.OPENGL
+ ". Use the multiBackend flavor to switch backends at runtime.");
}
currentType = type;
}
}

View File

@@ -0,0 +1,32 @@
package org.maplibre.android.maps.renderer;
import android.content.Context;
import android.view.TextureView;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
/**
* OpenGL flavor glue. Delegates straight to {@link OpenGLRendererStrategy}.
* Exists only so the shared {@link MapRendererFactory} in main can call
* {@code RendererStrategy.X(...)} without a per-flavor import.
*/
final class RendererStrategy {
private RendererStrategy() {}
static void attachTextureRenderThread(@NonNull TextureView textureView,
@NonNull TextureViewMapRenderer renderer) {
OpenGLRendererStrategy.attachTextureRenderThread(textureView, renderer);
}
static SurfaceViewMapRenderer createSurfaceViewRenderer(@NonNull Context context,
String localFontFamily,
boolean renderSurfaceOnTop,
Runnable initCallback) {
return OpenGLRendererStrategy.createSurfaceViewRenderer(
context, localFontFamily, renderSurfaceOnTop, initCallback);
}
}

View File

@@ -4,25 +4,15 @@ import org.maplibre.android.LibraryLoader;
import org.maplibre.android.LibraryLoaderProvider;
/**
* Concrete implementation of a native library loader.
* <p>
* </p>
* OpenGL flavor: loads the single-backend libmaplibre.so via System.loadLibrary.
*/
public class LibraryLoaderProviderImpl implements LibraryLoaderProvider {
/**
* Creates and returns a the default Library Loader.
*
* @return the default library loader
*/
@Override
public LibraryLoader getDefaultLibraryLoader() {
return new SystemLibraryLoader();
}
/**
* Concrete implementation of a LibraryLoader using System.loadLibrary.
*/
private static class SystemLibraryLoader extends LibraryLoader {
@Override
public void load(String name) {

View File

@@ -0,0 +1,42 @@
package org.maplibre.android.maps.renderer;
import android.content.Context;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.surfaceview.GLSurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.surfaceview.MapLibreGLSurfaceView;
import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.textureview.GLTextureViewRenderThread;
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
/**
* OpenGL concrete impl behind {@link MapRendererFactory}. Lives alongside the
* other GL renderer helpers in src/sharedRenderer/opengl/.
*/
final class OpenGLRendererStrategy {
private OpenGLRendererStrategy() {}
static void attachTextureRenderThread(@NonNull TextureView textureView,
@NonNull TextureViewMapRenderer renderer) {
renderer.setRenderThread(new GLTextureViewRenderThread(textureView, renderer));
}
static SurfaceViewMapRenderer createSurfaceViewRenderer(@NonNull Context context,
String localFontFamily,
boolean renderSurfaceOnTop,
Runnable initCallback) {
MapLibreGLSurfaceView surfaceView = new MapLibreGLSurfaceView(context);
surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop);
return new GLSurfaceViewMapRenderer(context, surfaceView, localFontFamily) {
@Override
public void onSurfaceCreated(Surface surface) {
initCallback.run();
super.onSurfaceCreated(surface);
}
};
}
}

View File

@@ -0,0 +1,42 @@
package org.maplibre.android.maps.renderer;
import android.content.Context;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.surfaceview.MapLibreVulkanSurfaceView;
import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.surfaceview.VulkanSurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
import org.maplibre.android.maps.renderer.textureview.VulkanTextureViewRenderThread;
/**
* Vulkan concrete impl behind {@link MapRendererFactory}. Lives alongside the
* other Vulkan renderer helpers in src/sharedRenderer/vulkan/.
*/
final class VulkanRendererStrategy {
private VulkanRendererStrategy() {}
static void attachTextureRenderThread(@NonNull TextureView textureView,
@NonNull TextureViewMapRenderer renderer) {
renderer.setRenderThread(new VulkanTextureViewRenderThread(textureView, renderer));
}
static SurfaceViewMapRenderer createSurfaceViewRenderer(@NonNull Context context,
String localFontFamily,
boolean renderSurfaceOnTop,
Runnable initCallback) {
MapLibreVulkanSurfaceView surfaceView = new MapLibreVulkanSurfaceView(context);
surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop);
return new VulkanSurfaceViewMapRenderer(context, surfaceView, localFontFamily) {
@Override
public void onSurfaceCreated(Surface surface) {
initCallback.run();
super.onSurfaceCreated(surface);
}
};
}
}

View File

@@ -0,0 +1,56 @@
package org.maplibre.android;
import android.content.Context;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
/**
* Vulkan (and WebGPU) flavor: the rendering engine is fixed at build time.
* See the opengl flavor's javadoc for the multiBackend contrast.
*/
@Keep
public final class RenderingEngine {
public enum Type {
OPENGL,
VULKAN
}
private static volatile Type currentType = Type.VULKAN;
private RenderingEngine() {}
@NonNull
public static Type getCurrentType() {
return currentType;
}
/**
* This flavor's engine is fixed at compile time, so there's nothing to select —
* always {@link Type#VULKAN}.
*/
@NonNull
static Type getDefaultRenderingEngine(@NonNull Context context) {
return Type.VULKAN;
}
/**
* @throws IllegalStateException if {@link MapLibre#getInstance(android.content.Context)}
* has already been called in this process.
* @throws UnsupportedOperationException if {@code type} differs from the
* compiled-in backend
*/
static void setCurrentType(@NonNull Type type) {
if (MapLibre.hasInstance()) {
throw new IllegalStateException(
"RenderingEngine.setCurrentType() must be called before MapLibre.getInstance().");
}
if (type != Type.VULKAN) {
throw new UnsupportedOperationException(
"This MapLibre Android build supports only " + Type.VULKAN
+ ". Use the multiBackend flavor to switch backends at runtime.");
}
currentType = type;
}
}

View File

@@ -1,49 +0,0 @@
package org.maplibre.android.maps.renderer;
import android.content.Context;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.surfaceview.MapLibreVulkanSurfaceView;
import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.surfaceview.VulkanSurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
import org.maplibre.android.maps.renderer.textureview.VulkanTextureViewRenderThread;
@Keep
public class MapRendererFactory {
public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, TextureView textureView,
String localFontFamily, boolean translucentSurface,
Runnable initCallback) {
TextureViewMapRenderer mapRenderer = new TextureViewMapRenderer(context, textureView,
localFontFamily, translucentSurface) {
@Override
protected void onSurfaceCreated(Surface surface) {
initCallback.run();
super.onSurfaceCreated(surface);
}
};
mapRenderer.setRenderThread(new VulkanTextureViewRenderThread(textureView, mapRenderer));
return mapRenderer;
}
public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@NonNull Context context, String localFontFamily,
boolean renderSurfaceOnTop, Runnable initCallback) {
MapLibreVulkanSurfaceView surfaceView = new MapLibreVulkanSurfaceView(context);
surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop);
return new VulkanSurfaceViewMapRenderer(context, surfaceView, localFontFamily) {
@Override
public void onSurfaceCreated(Surface surface) {
initCallback.run();
super.onSurfaceCreated(surface);
}
};
}
}

View File

@@ -0,0 +1,32 @@
package org.maplibre.android.maps.renderer;
import android.content.Context;
import android.view.TextureView;
import androidx.annotation.NonNull;
import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer;
import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer;
/**
* Vulkan flavor glue. Delegates straight to {@link VulkanRendererStrategy}.
* Exists only so the shared {@link MapRendererFactory} in main can call
* {@code RendererStrategy.X(...)} without a per-flavor import.
*/
final class RendererStrategy {
private RendererStrategy() {}
static void attachTextureRenderThread(@NonNull TextureView textureView,
@NonNull TextureViewMapRenderer renderer) {
VulkanRendererStrategy.attachTextureRenderThread(textureView, renderer);
}
static SurfaceViewMapRenderer createSurfaceViewRenderer(@NonNull Context context,
String localFontFamily,
boolean renderSurfaceOnTop,
Runnable initCallback) {
return VulkanRendererStrategy.createSurfaceViewRenderer(
context, localFontFamily, renderSurfaceOnTop, initCallback);
}
}

View File

@@ -0,0 +1,23 @@
package org.maplibre.android.module.loader;
import org.maplibre.android.LibraryLoader;
import org.maplibre.android.LibraryLoaderProvider;
/**
* Vulkan and WebGPU flavors: loads the single-backend libmaplibre.so via
* System.loadLibrary.
*/
public class LibraryLoaderProviderImpl implements LibraryLoaderProvider {
@Override
public LibraryLoader getDefaultLibraryLoader() {
return new SystemLibraryLoader();
}
private static class SystemLibraryLoader extends LibraryLoader {
@Override
public void load(String name) {
System.loadLibrary(name);
}
}
}