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

@@ -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);
}
}
}
}