summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvo van Dongen <info@ivovandongen.nl>2017-10-02 15:41:02 +0300
committerTobrun <tobrun@mapbox.com>2017-11-03 09:19:39 -0700
commite9bb4d1582dafcb4ca87bdfc6bb58bac2d405954 (patch)
tree38e0337b1781d4bd0f94deb27a55a4b2943440e3
parentfd5f357d1d244a13c5b51b66829f47beda4ca1ed (diff)
downloadqtlocation-mapboxgl-e9bb4d1582dafcb4ca87bdfc6bb58bac2d405954.tar.gz
[android] texture view map renderer
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java3
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewMapRenderer.java96
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java442
4 files changed, 545 insertions, 0 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
index c316ea27d8..b5546f2cc4 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
@@ -386,6 +386,10 @@ public class MapView extends FrameLayout {
mapCallback.clearOnMapReadyCallbacks();
nativeMapView.destroy();
nativeMapView = null;
+
+ if (mapRenderer != null) {
+ mapRenderer.onDestroy();
+ }
}
@Override
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java
index a7c0fa3778..961438ed14 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/MapRenderer.java
@@ -41,6 +41,9 @@ public abstract class MapRenderer implements MapRendererScheduler {
// Implement if needed
}
+ public void onDestroy() {
+ // Implement if needed
+ }
public void setOnFpsChangedListener(MapboxMap.OnFpsChangedListener listener) {
onFpsChangedListener = listener;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewMapRenderer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewMapRenderer.java
new file mode 100644
index 0000000000..8cd724a828
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewMapRenderer.java
@@ -0,0 +1,96 @@
+package com.mapbox.mapboxsdk.maps.renderer.textureview;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.view.TextureView;
+
+import com.mapbox.mapboxsdk.maps.renderer.MapRenderer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * The {@link TextureViewMapRenderer} encapsulates the GL thread and
+ * {@link TextureView} specifics to render the map.
+ *
+ * @see MapRenderer
+ */
+public class TextureViewMapRenderer extends MapRenderer {
+ private TextureViewRenderThread renderThread;
+
+ /**
+ * Create a {@link MapRenderer} for the given {@link TextureView}
+ *
+ * @param context the current Context
+ * @param textureView the TextureView
+ */
+ public TextureViewMapRenderer(@NonNull Context context, @NonNull TextureView textureView) {
+ super(context);
+ renderThread = new TextureViewRenderThread(textureView, this);
+ renderThread.start();
+ }
+
+ /**
+ * Overridden to provide package access
+ */
+ @Override
+ protected void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ super.onSurfaceCreated(gl, config);
+ }
+
+ /**
+ * Overridden to provide package access
+ */
+ @Override
+ protected void onSurfaceChanged(GL10 gl, int width, int height) {
+ super.onSurfaceChanged(gl, width, height);
+ }
+
+ /**
+ * Overridden to provide package access
+ */
+ @Override
+ protected void onDrawFrame(GL10 gl) {
+ super.onDrawFrame(gl);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void requestRender() {
+ renderThread.requestRender();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void queueEvent(Runnable runnable) {
+ renderThread.queueEvent(runnable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onPause() {
+ renderThread.onPause();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onResume() {
+ renderThread.onResume();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDestroy() {
+ renderThread.onDestroy();
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java
new file mode 100644
index 0000000000..3ed55b634a
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java
@@ -0,0 +1,442 @@
+package com.mapbox.mapboxsdk.maps.renderer.textureview;
+
+import android.graphics.SurfaceTexture;
+import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
+import android.view.TextureView;
+
+import com.mapbox.mapboxsdk.maps.renderer.egl.EGLConfigChooser;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGL11;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+import timber.log.Timber;
+
+/**
+ * The render thread is responsible for managing the communication between the
+ * ui thread and the render thread it creates. Also, the EGL and GL contexts
+ * are managed from here.
+ */
+class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextureListener {
+
+ private final TextureViewMapRenderer mapRenderer;
+ private final EGLHolder eglHolder;
+
+ // Lock used for synchronization
+ private final Object lock = new Object();
+
+ // Guarded by lock
+ private final ArrayList<Runnable> eventQueue = new ArrayList<>();
+ private SurfaceTexture surface;
+ private int width;
+ private int height;
+ private boolean requestRender;
+ private boolean sizeChanged;
+ private boolean paused;
+ private boolean destroyContext;
+ private boolean destroySurface;
+ private boolean shouldExit;
+ private boolean exited;
+
+ /**
+ * Create a render thread for the given TextureView / Maprenderer combination.
+ *
+ * @param textureView the TextureView
+ * @param mapRenderer the MapRenderer
+ */
+ @UiThread
+ TextureViewRenderThread(@NonNull TextureView textureView, @NonNull TextureViewMapRenderer mapRenderer) {
+ textureView.setSurfaceTextureListener(this);
+ this.mapRenderer = mapRenderer;
+ this.eglHolder = new EGLHolder(new WeakReference<>(textureView));
+ }
+
+ // SurfaceTextureListener methods
+
+ @UiThread
+ @Override
+ public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) {
+ synchronized (lock) {
+ this.surface = surface;
+ this.width = width;
+ this.height = height;
+ this.requestRender = true;
+ lock.notifyAll();
+ }
+ }
+
+ @Override
+ @UiThread
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, final int width, final int height) {
+ synchronized (lock) {
+ this.width = width;
+ this.height = height;
+ this.sizeChanged = true;
+ this.requestRender = true;
+ lock.notifyAll();
+ }
+ }
+
+ @Override
+ @UiThread
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ synchronized (lock) {
+ this.surface = null;
+ this.destroySurface = true;
+ this.requestRender = false;
+ lock.notifyAll();
+ }
+ return true;
+ }
+
+ @Override
+ @UiThread
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ // Ignored
+ }
+
+ // MapRenderer delegate methods
+
+ /**
+ * May be called from any thread
+ */
+ void requestRender() {
+ synchronized (lock) {
+ requestRender = true;
+ lock.notifyAll();
+ }
+ }
+
+ /**
+ * May be called from any thread
+ */
+ void queueEvent(Runnable runnable) {
+ if (runnable == null) {
+ throw new IllegalArgumentException("runnable must not be null");
+ }
+ synchronized (lock) {
+ eventQueue.add(runnable);
+ lock.notifyAll();
+ }
+ }
+
+
+ @UiThread
+ void onPause() {
+ synchronized (lock) {
+ this.paused = true;
+ lock.notifyAll();
+ }
+ }
+
+ @UiThread
+ void onResume() {
+ synchronized (lock) {
+ this.paused = false;
+ lock.notifyAll();
+ }
+ }
+
+
+ @UiThread
+ void onDestroy() {
+ synchronized (lock) {
+ this.shouldExit = true;
+ lock.notifyAll();
+
+ // Wait for the thread to exit
+ while (!this.exited) {
+ try {
+ lock.wait();
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+
+ // Thread implementation
+
+ @Override
+ public void run() {
+ try {
+
+ while (true) {
+ Runnable event = null;
+ boolean initializeEGL = false;
+ boolean recreateSurface = 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) {
+ eglHolder.destroySurface();
+ destroySurface = false;
+ break;
+ }
+
+ if (destroyContext) {
+ eglHolder.destroyContext();
+ destroyContext = false;
+ break;
+ }
+
+ if (surface != null && !paused && requestRender) {
+
+ w = width;
+ h = height;
+
+ // Initialize EGL if needed
+ if (eglHolder.eglContext == EGL10.EGL_NO_CONTEXT) {
+ initializeEGL = true;
+ break;
+ }
+
+ // Check if the size has changed
+ if (sizeChanged) {
+ recreateSurface = true;
+ sizeChanged = false;
+ break;
+ }
+
+ // 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;
+ }
+
+ GL10 gl = eglHolder.createGL();
+
+ // Initialize EGL
+ if (initializeEGL) {
+ eglHolder.prepare();
+ eglHolder.createSurface();
+ mapRenderer.onSurfaceCreated(gl, eglHolder.eglConfig);
+ mapRenderer.onSurfaceChanged(gl, w, h);
+ continue;
+ }
+
+ // If the surface size has changed inform the map renderer.
+ if (recreateSurface) {
+ eglHolder.createSurface();
+ mapRenderer.onSurfaceChanged(gl, w, h);
+ continue;
+ }
+
+ // Don't continue without a surface
+ if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) {
+ continue;
+ }
+
+ // Time to render a frame
+ mapRenderer.onDrawFrame(gl);
+
+ // Swap and check the result
+ int swapError = eglHolder.swap();
+ switch (swapError) {
+ case EGL10.EGL_SUCCESS:
+ break;
+ case EGL11.EGL_CONTEXT_LOST:
+ Timber.w("Context lost. Waiting for re-aquire");
+ synchronized (lock) {
+ surface = null;
+ destroySurface = true;
+ destroyContext = true;
+ }
+ break;
+ default:
+ Timber.w("eglSwapBuffer error: %s. Waiting or new surface", swapError);
+ // Probably lost the surface. Clear the current one and
+ // wait for a new one to be set
+ synchronized (lock) {
+ surface = null;
+ destroySurface = true;
+ }
+ }
+
+ }
+
+ } catch (InterruptedException err) {
+ // To be expected
+ } finally {
+ // Cleanup
+ eglHolder.cleanup();
+
+ // Signal we're done
+ synchronized (lock) {
+ this.exited = true;
+ lock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Holds the EGL state and offers methods to mutate it.
+ */
+ private static class EGLHolder {
+ private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+ private final WeakReference<TextureView> textureViewWeakRef;
+
+ private EGL10 egl;
+ private EGLConfig eglConfig;
+ private EGLDisplay eglDisplay = EGL10.EGL_NO_DISPLAY;
+ private EGLContext eglContext = EGL10.EGL_NO_CONTEXT;
+ private EGLSurface eglSurface = EGL10.EGL_NO_SURFACE;
+
+ EGLHolder(WeakReference<TextureView> textureViewWeakRef) {
+ this.textureViewWeakRef = textureViewWeakRef;
+ }
+
+ void prepare() {
+ this.egl = (EGL10) EGLContext.getEGL();
+
+ // Only re-initialize display when needed
+ if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
+ this.eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+ if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
+ throw new RuntimeException("eglGetDisplay failed");
+ }
+
+ int[] version = new int[2];
+ if (!egl.eglInitialize(eglDisplay, version)) {
+ throw new RuntimeException("eglInitialize failed");
+ }
+ }
+
+ if (textureViewWeakRef == null) {
+ // No texture view present
+ eglConfig = null;
+ eglContext = EGL10.EGL_NO_CONTEXT;
+ } else /*if (eglContext == EGL10.EGL_NO_CONTEXT)*/ {
+ eglConfig = new EGLConfigChooser().chooseConfig(egl, eglDisplay);
+ int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
+ eglContext = egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+ }
+
+ if (eglContext == EGL10.EGL_NO_CONTEXT) {
+ throw new RuntimeException("createContext");
+ }
+ }
+
+ GL10 createGL() {
+ return (GL10) eglContext.getGL();
+ }
+
+ boolean createSurface() {
+ // The window size has changed, so we need to create a new surface.
+ destroySurface();
+
+ // Create an EGL surface we can render into.
+ TextureView view = textureViewWeakRef.get();
+ if (view != null) {
+ int[] surfaceAttribs = {EGL10.EGL_NONE};
+ eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, view.getSurfaceTexture(), surfaceAttribs);
+ } else {
+ eglSurface = EGL10.EGL_NO_SURFACE;
+ }
+
+ if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) {
+ int error = egl.eglGetError();
+ if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+ Timber.e("createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+ }
+ return false;
+ }
+
+ return makeCurrent();
+ }
+
+ boolean makeCurrent() {
+ if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
+ // Could not make the context current, probably because the underlying
+ // SurfaceView surface has been destroyed.
+ Timber.w("eglMakeCurrent: %s", egl.eglGetError());
+ return false;
+ }
+
+ return true;
+ }
+
+ int swap() {
+ if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) {
+ return egl.eglGetError();
+ }
+ return EGL10.EGL_SUCCESS;
+ }
+
+ private void destroySurface() {
+ if (eglSurface == EGL10.EGL_NO_SURFACE) {
+ return;
+ }
+
+ if (!egl.eglDestroySurface(eglDisplay, eglSurface)) {
+ Timber.e("Could not destroy egl surface. Display %s, Surface %s", eglDisplay, eglSurface);
+ throw new RuntimeException("eglDestroyContext: " + egl.eglGetError());
+ }
+ }
+
+ private void destroyContext() {
+ if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) {
+ return;
+ }
+
+ if (!egl.eglDestroyContext(eglDisplay, eglContext)) {
+ Timber.e("Could not destroy egl context. Display %s, Context %s", eglDisplay, eglContext);
+ throw new RuntimeException("eglDestroyContext: " + egl.eglGetError());
+ }
+ }
+
+ private void terminate() {
+ if (eglDisplay != EGL10.EGL_NO_DISPLAY) {
+ if (!egl.eglTerminate(eglDisplay)) {
+ Timber.w("Could not terminate egl. Display %s", eglDisplay);
+ }
+ eglDisplay = EGL10.EGL_NO_DISPLAY;
+ }
+ }
+
+ void cleanup() {
+ destroySurface();
+ destroyContext();
+ terminate();
+ }
+ }
+}