Add Android Vulkan backend (#2711)

Co-authored-by: Bart Louwers <bart.louwers@gmail.com>
This commit is contained in:
Adrian Cojocaru
2024-09-06 17:40:57 +03:00
committed by GitHub
parent 4b5d06d908
commit ea09bdf2b6
79 changed files with 2580 additions and 1400 deletions

View File

@@ -0,0 +1,49 @@
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,183 @@
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();
mSurfaceViewWeakRef = surfaceViewWeakRef;
}
@Override
protected void guardedRun() throws InterruptedException {
wantRenderNotification = false;
boolean sizeChanged = false;
boolean initSurface = false;
boolean destroySurface = 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();
}
if (paused && graphicsSurfaceCreated) {
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
if (view != null) {
destroySurface = true;
graphicsSurfaceCreated = false;
}
renderThreadManager.notifyAll();
}
// lost surface
if (!hasSurface && !waitingForSurface) {
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
if (view != null) {
destroySurface = true;
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()) {
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;
}
MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get();
if (destroySurface) {
if (view != null) {
view.renderer.onSurfaceDestroyed();
destroySurface = false;
}
}
if (initSurface) {
if (view != null) {
view.renderer.onSurfaceCreated(view.getHolder().getSurface());
initSurface = false;
}
}
if (sizeChanged) {
if (view != null) {
view.renderer.onSurfaceChanged(w, h);
sizeChanged = false;
}
}
if (view != null) {
view.renderer.onDrawFrame();
if (finishDrawingRunnable != null) {
finishDrawingRunnable.run();
finishDrawingRunnable = null;
}
}
if (wantRenderNotification) {
doRenderNotification = true;
wantRenderNotification = false;
}
}
}
private boolean graphicsSurfaceCreated;
private WeakReference<MapLibreVulkanSurfaceView> mSurfaceViewWeakRef;
}
}

View File

@@ -0,0 +1,16 @@
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);
}
}

View File

@@ -0,0 +1,114 @@
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);
}
// Thread implementation
@Override
public void run() {
try {
while (true) {
Runnable event = null;
boolean initialize = 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 (destroySurface) {
destroySurface = false;
mapRenderer.onSurfaceDestroyed();
surface = null;
break;
}
if (surfaceTexture != null && !paused && requestRender && surface == null) {
w = width;
h = height;
surface = new Surface(surfaceTexture);
initialize = true;
// 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;
}
if (requestRender && !paused) {
break;
}
// Wait until needed
lock.wait();
} // end guarded while loop
} // end guarded block
// Run event, if any
if (event != null) {
event.run();
continue;
}
if (initialize) {
mapRenderer.onSurfaceCreated(surface);
mapRenderer.onSurfaceChanged( w, h);
initialize = false;
continue;
}
// If the surface size has changed inform the map renderer.
if (sizeChanged) {
mapRenderer.onSurfaceChanged(w, h);
sizeChanged = false;
continue;
}
// Time to render a frame
mapRenderer.onDrawFrame();
}
} catch (InterruptedException err) {
// To be expected
} finally {
// Signal we're done
synchronized (lock) {
this.exited = true;
lock.notifyAll();
}
}
}
}