Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ option(MLN_USE_RUST "Use components in Rust" OFF)
option(MLN_TEXT_SHAPING_HARFBUZZ "Use haffbuzz to shape complex text" ON)
option(MLN_CREATE_AUTORELEASEPOOL "Create autoreleasepool in render loop" OFF)
option(MLN_CREATE_AMALGAMATION "Create static amalgamation of core (requires amerge)" OFF)
option(MLN_ANDROID_MULTI_BACKEND "Android only: build both OpenGL and Vulkan versions" OFF)

include(cmake/validate-backend-options.cmake)
include(cmake/clang-tidy.cmake)
Expand All @@ -35,6 +36,85 @@ endif()

project("MapLibre Native" LANGUAGES CXX C)

if(MLN_WITH_MULTI_BACKEND)
if(NOT ANDROID)
message(FATAL_ERROR
"MLN_WITH_MULTI_BACKEND is only supported on Android. "
"On desktop, build mbgl-glfw with a single -DMLN_WITH_OPENGL=ON or -DMLN_WITH_VULKAN=ON.")
endif()

# Android: invoke the Android NDK CMakeLists twice via ExternalProject_Add
# (once per backend) and surface the results to AGP as two distinct
# add_library(SHARED) targets so both .so files end up in the AAR.
# Reached via add_subdirectory(...) from the Android src/cpp CMakeLists.
include(ExternalProject)

set(_multi_android_source_dir ${PROJECT_SOURCE_DIR}/platform/android/MapLibreAndroid/src/cpp)

set(_multi_forward_args
-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}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DMLN_ANDROID_MULTI_BACKEND=OFF)

set(_multi_opengl_dir ${CMAKE_BINARY_DIR}/multi-opengl)
set(_multi_vulkan_dir ${CMAKE_BINARY_DIR}/multi-vulkan)

ExternalProject_Add(maplibre-opengl-build
SOURCE_DIR ${_multi_android_source_dir}
BINARY_DIR ${_multi_opengl_dir}
CMAKE_ARGS
${_multi_forward_args}
-DMLN_WITH_OPENGL=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${_multi_opengl_dir}/lib
BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --target maplibre
INSTALL_COMMAND ""
BUILD_ALWAYS 1)

ExternalProject_Add(maplibre-vulkan-build
SOURCE_DIR ${_multi_android_source_dir}
BINARY_DIR ${_multi_vulkan_dir}
CMAKE_ARGS
${_multi_forward_args}
-DMLN_WITH_VULKAN=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${_multi_vulkan_dir}/lib
BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --target maplibre
INSTALL_COMMAND ""
BUILD_ALWAYS 1)

set(_multi_stub ${CMAKE_BINARY_DIR}/maplibre_multi_stub.cpp)
file(WRITE ${_multi_stub}
"extern \"C\" void maplibre_multi_backend_stub() {}\n")

add_library(maplibre-opengl SHARED ${_multi_stub})
# Produce libmaplibre.so (not libmaplibre-opengl.so) so the OpenGL output
# matches the single-backend OpenGL AAR's library name.
set_target_properties(maplibre-opengl PROPERTIES OUTPUT_NAME maplibre)
add_dependencies(maplibre-opengl maplibre-opengl-build)
add_custom_command(TARGET maplibre-opengl POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${_multi_opengl_dir}/lib/libmaplibre.so
$<TARGET_FILE:maplibre-opengl>
VERBATIM)

add_library(maplibre-vulkan SHARED ${_multi_stub})
add_dependencies(maplibre-vulkan maplibre-vulkan-build)
add_custom_command(TARGET maplibre-vulkan POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${_multi_vulkan_dir}/lib/libmaplibre.so
$<TARGET_FILE:maplibre-vulkan>
VERBATIM)

install(TARGETS maplibre-opengl maplibre-vulkan LIBRARY DESTINATION lib)

return()
endif()

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER MapLibre)

Expand Down
7 changes: 5 additions & 2 deletions cmake/validate-backend-options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ endif()
if (MLN_WITH_WEBGPU)
math(EXPR backend_count "${backend_count} + 1")
endif()
if (MLN_ANDROID_MULTI_BACKEND)
math(EXPR backend_count "${backend_count} + 1")
endif()

if (backend_count EQUAL 0)
message(FATAL_ERROR
"You need to set a rendering backend. "
"Set exactly one of: MLN_WITH_OPENGL, MLN_WITH_METAL, MLN_WITH_VULKAN, or MLN_WITH_WEBGPU.")
"Set exactly one of: MLN_WITH_OPENGL, MLN_WITH_METAL, MLN_WITH_VULKAN, MLN_WITH_WEBGPU, or MLN_ANDROID_MULTI_BACKEND.")
elseif (backend_count GREATER 1)
message(FATAL_ERROR
"Multiple rendering backends selected. "
"Please enable only one of: MLN_WITH_OPENGL, MLN_WITH_METAL, MLN_WITH_VULKAN, or MLN_WITH_WEBGPU.")
"Please enable only one of: MLN_WITH_OPENGL, MLN_WITH_METAL, MLN_WITH_VULKAN, MLN_WITH_WEBGPU, or MLN_ANDROID_MULTI_BACKEND.")
endif()
24 changes: 22 additions & 2 deletions platform/android/MapLibreAndroid/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,38 @@ android {
}
}
}
create("multiBackend") {
dimension = "renderer"
externalNativeBuild {
cmake {
arguments("-DMLN_ANDROID_MULTI_BACKEND=ON")
targets("maplibre-opengl", "maplibre-vulkan")
}
}
}
}

sourceSets {
getByName("opengl") {
java.srcDirs("src/opengl/java/")
java.srcDirs("src/opengl/java/", "src/sharedRenderer/opengl/java/")
}
getByName("vulkan") {
java.srcDirs("src/vulkan/java/", "src/sharedRenderer/vulkan/java/")
}
listOf("webgpuDawn", "webgpuWgpu").forEach {
getByName(it) {
java.srcDirs("src/vulkan/java")
java.srcDirs("src/vulkan/java/", "src/sharedRenderer/vulkan/java/")
manifest.srcFile("src/vulkan/AndroidManifest.xml")
}
}
getByName("multiBackend") {
java.srcDirs(
"src/multiBackend/java/",
"src/sharedRenderer/opengl/java/",
"src/sharedRenderer/vulkan/java/"
)
manifest.srcFile("src/multiBackend/AndroidManifest.xml")
}
}

// Build native libraries
Expand Down
10 changes: 10 additions & 0 deletions platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@ cmake_minimum_required(VERSION 3.10)

project(MapLibreAndroid)

# Always invoke the top-level CMakeLists. When MLN_ANDROID_MULTI_BACKEND=ON
# (set by the Gradle multiBackend flavor via -D), the top-level handles the
# dual-backend ExternalProject_Add setup, declares maplibre-opengl (output
# libmaplibre.so) and maplibre-vulkan (output libmaplibre-vulkan.so) SHARED
# targets, and returns from its scope. The matching `if` below then skips
# this file's normal single-backend maplibre target.
add_subdirectory(../../../../../
../../../../../${ANDROID_ABI})

if(MLN_ANDROID_MULTI_BACKEND)
return()
endif()

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,25 @@
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
Expand All @@ -27,23 +34,15 @@ protected void onSurfaceCreated(Surface surface) {
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);
}
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.maplibre.android;

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). Once the native library is loaded the
* selection is effectively locked for the process lifetime — subsequent
* {@code setCurrentType} calls update this field, but the loaded .so does
* not change.</p>
*/
@Keep
public final class RenderingEngine {

public enum Type {
OPENGL,
VULKAN
}

private static volatile Type currentType = Type.OPENGL;

private RenderingEngine() {}

@NonNull
public static Type getCurrentType() {
return currentType;
}

public static void setCurrentType(@NonNull Type type) {
currentType = type;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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 (OpenGL, the default name) or libmaplibre-vulkan.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.VULKAN) {
System.loadLibrary("maplibre-vulkan");
} else {
System.loadLibrary(name);
}
}
}
}
Loading
Loading