Add multiBackend Android flavor for runtime OpenGL/Vulkan switching (#4288)
This commit is contained in:
committed by
GitHub
parent
ccb9af119e
commit
b0388d186d
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
package org.maplibre.android.maps.renderer.surfaceview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MapLibreVulkanSurfaceView extends MapLibreSurfaceView {
|
||||
|
||||
protected final WeakReference<MapLibreVulkanSurfaceView> viewWeakReference = new WeakReference<>(this);
|
||||
|
||||
public MapLibreVulkanSurfaceView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public MapLibreVulkanSurfaceView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createRenderThread() {
|
||||
renderThread = new VulkanThread(viewWeakReference);
|
||||
}
|
||||
|
||||
static class VulkanThread extends MapLibreSurfaceView.RenderThread {
|
||||
VulkanThread(WeakReference<MapLibreVulkanSurfaceView> surfaceViewWeakRef) {
|
||||
super(surfaceViewWeakRef.get().renderThreadManager);
|
||||
mSurfaceViewWeakRef = surfaceViewWeakRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void guardedRun() throws InterruptedException {
|
||||
wantRenderNotification = false;
|
||||
|
||||
boolean sizeChanged = false;
|
||||
boolean initSurface = false;
|
||||
boolean wantRenderNotification = false;
|
||||
boolean doRenderNotification = false;
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
Runnable event = null;
|
||||
Runnable finishDrawingRunnable = null;
|
||||
|
||||
while (true) {
|
||||
synchronized (renderThreadManager) {
|
||||
while (true) {
|
||||
if (shouldExit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventQueue.isEmpty()) {
|
||||
event = eventQueue.remove(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the pause state.
|
||||
if (paused != requestPaused) {
|
||||
paused = requestPaused;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
// lost surface
|
||||
if (!hasSurface && !waitingForSurface) {
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null && graphicsSurfaceCreated) {
|
||||
view.renderer.onSurfaceDestroyed();
|
||||
}
|
||||
graphicsSurfaceCreated = false;
|
||||
waitingForSurface = true;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
// acquired surface
|
||||
if (hasSurface && waitingForSurface) {
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
initSurface = true;
|
||||
graphicsSurfaceCreated = true;
|
||||
}
|
||||
waitingForSurface = false;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
if (doRenderNotification) {
|
||||
this.wantRenderNotification = false;
|
||||
doRenderNotification = false;
|
||||
renderComplete = true;
|
||||
renderThreadManager.notifyAll();
|
||||
}
|
||||
|
||||
if (this.finishDrawingRunnable != null) {
|
||||
finishDrawingRunnable = this.finishDrawingRunnable;
|
||||
this.finishDrawingRunnable = null;
|
||||
}
|
||||
|
||||
// Ready to draw?
|
||||
if (readyToDraw() && graphicsSurfaceCreated) {
|
||||
if (this.sizeChanged) {
|
||||
sizeChanged = true;
|
||||
w = width;
|
||||
h = height;
|
||||
this.wantRenderNotification = true;
|
||||
this.sizeChanged = false;
|
||||
}
|
||||
requestRender = false;
|
||||
renderThreadManager.notifyAll();
|
||||
if (this.wantRenderNotification) {
|
||||
wantRenderNotification = true;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
if (finishDrawingRunnable != null) {
|
||||
Log.w(TAG, "Warning, !readyToDraw() but waiting for "
|
||||
+ "draw finished! Early reporting draw finished.");
|
||||
finishDrawingRunnable.run();
|
||||
finishDrawingRunnable = null;
|
||||
}
|
||||
}
|
||||
// By design, this is the only place in a RenderThread thread where we wait().
|
||||
renderThreadManager.wait();
|
||||
}
|
||||
} // end of synchronized(sRenderThreadManager)
|
||||
|
||||
if (event != null) {
|
||||
event.run();
|
||||
event = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (initSurface) {
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.renderer.onSurfaceCreated(view.getHolder().getSurface());
|
||||
initSurface = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeChanged) {
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.renderer.onSurfaceChanged(w, h);
|
||||
sizeChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
|
||||
if (view != null) {
|
||||
view.renderer.onDrawFrame();
|
||||
if (finishDrawingRunnable != null) {
|
||||
finishDrawingRunnable.run();
|
||||
finishDrawingRunnable = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wantRenderNotification) {
|
||||
doRenderNotification = true;
|
||||
wantRenderNotification = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ableToDraw() {
|
||||
return graphicsSurfaceCreated && readyToDraw();
|
||||
}
|
||||
|
||||
private boolean graphicsSurfaceCreated;
|
||||
|
||||
private final WeakReference<MapLibreVulkanSurfaceView> mSurfaceViewWeakRef;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.maplibre.android.maps.renderer.surfaceview;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class VulkanSurfaceViewMapRenderer extends SurfaceViewMapRenderer {
|
||||
|
||||
public VulkanSurfaceViewMapRenderer(Context context,
|
||||
@NonNull MapLibreVulkanSurfaceView surfaceView,
|
||||
String localIdeographFontFamily) {
|
||||
super(context, surfaceView, localIdeographFontFamily);
|
||||
|
||||
this.surfaceView.setRenderer(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
package org.maplibre.android.maps.renderer.textureview;
|
||||
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
public class VulkanTextureViewRenderThread extends TextureViewRenderThread {
|
||||
|
||||
private Surface surface;
|
||||
|
||||
@UiThread
|
||||
public VulkanTextureViewRenderThread(@NonNull TextureView textureView, @NonNull TextureViewMapRenderer mapRenderer) {
|
||||
super(textureView, mapRenderer);
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (surface != null) {
|
||||
mapRenderer.onSurfaceDestroyed();
|
||||
surface.release();
|
||||
surface = null;
|
||||
}
|
||||
|
||||
hasNativeSurface = false;
|
||||
}
|
||||
|
||||
// Thread implementation
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
while (true) {
|
||||
Runnable event = null;
|
||||
boolean createSurface = false;
|
||||
boolean sizeChanged = false;
|
||||
int w = -1;
|
||||
int h = -1;
|
||||
|
||||
// Guarded block
|
||||
synchronized (lock) {
|
||||
while (true) {
|
||||
|
||||
if (shouldExit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If any events are scheduled, pop one for processing
|
||||
if (!eventQueue.isEmpty()) {
|
||||
event = eventQueue.remove(0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.destroySurface) {
|
||||
if (surface != null) {
|
||||
mapRenderer.onSurfaceDestroyed();
|
||||
surface.release();
|
||||
surface = null;
|
||||
}
|
||||
|
||||
this.hasNativeSurface = false;
|
||||
this.destroySurface = false;
|
||||
lock.notifyAll();
|
||||
break;
|
||||
}
|
||||
|
||||
if (surfaceTexture != null && !paused && requestRender) {
|
||||
|
||||
w = width;
|
||||
h = height;
|
||||
|
||||
if (surface == null) {
|
||||
surface = new Surface(surfaceTexture);
|
||||
this.hasNativeSurface = true;
|
||||
createSurface = true;
|
||||
}
|
||||
|
||||
if (this.sizeChanged) {
|
||||
sizeChanged = true;
|
||||
this.sizeChanged = false;
|
||||
}
|
||||
|
||||
// Reset the request render flag now, so we can catch new requests
|
||||
// while rendering
|
||||
requestRender = false;
|
||||
|
||||
// Break the guarded loop and continue to process
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait until needed
|
||||
lock.wait();
|
||||
|
||||
} // end guarded while loop
|
||||
|
||||
} // end guarded block
|
||||
|
||||
// Run event, if any
|
||||
if (event != null) {
|
||||
event.run();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (createSurface) {
|
||||
mapRenderer.onSurfaceCreated(surface);
|
||||
mapRenderer.onSurfaceChanged(w, h);
|
||||
createSurface = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the surface size has changed inform the map renderer.
|
||||
if (sizeChanged) {
|
||||
mapRenderer.onSurfaceChanged(w, h);
|
||||
sizeChanged = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (surface == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Time to render a frame
|
||||
mapRenderer.onDrawFrame();
|
||||
}
|
||||
|
||||
} catch (InterruptedException err) {
|
||||
// To be expected
|
||||
} finally {
|
||||
|
||||
// Signal we're done
|
||||
synchronized (lock) {
|
||||
cleanup();
|
||||
this.exited = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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