Add multiBackend Android flavor for runtime OpenGL/Vulkan switching (#4288)
This commit is contained in:
committed by
GitHub
parent
ccb9af119e
commit
b0388d186d
@@ -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)
|
||||
|
||||
85
platform/android/MapLibreAndroid/src/cpp/MultiBackend.cmake
Normal file
85
platform/android/MapLibreAndroid/src/cpp/MultiBackend.cmake
Normal 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)
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user