From 347d7c19c0a70f91252163b14e37583eea83fdd5 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Fri, 21 Jul 2017 16:15:30 +0300 Subject: [android] async rendering - introduces GLSurfaceView - introduces Orchestration thread - renders on the gl thread --- .../com/mapbox/mapboxsdk/egl/EGLConfigChooser.java | 293 +++++++++++++ .../mapbox/mapboxsdk/egl/EGLConfigException.java | 21 + .../java/com/mapbox/mapboxsdk/maps/MapView.java | 179 +++----- .../com/mapbox/mapboxsdk/maps/NativeMapView.java | 51 ++- .../java/com/mapbox/mapboxsdk/utils/Compare.java | 27 ++ .../main/res/layout/mapbox_mapview_internal.xml | 2 +- platform/android/src/android_gl_thread.hpp | 76 ++++ platform/android/src/android_renderer_frontend.cpp | 170 ++++++-- platform/android/src/android_renderer_frontend.hpp | 41 +- platform/android/src/native_map_view.cpp | 459 ++------------------- platform/android/src/native_map_view.hpp | 38 +- 11 files changed, 713 insertions(+), 644 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigChooser.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigException.java create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/Compare.java create mode 100644 platform/android/src/android_gl_thread.hpp diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigChooser.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigChooser.java new file mode 100644 index 0000000000..7fc70716da --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigChooser.java @@ -0,0 +1,293 @@ +package com.mapbox.mapboxsdk.egl; + +import android.opengl.GLSurfaceView; +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +import timber.log.Timber; + +import static com.mapbox.mapboxsdk.utils.Compare.compare; +import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_MASK_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_BUFFER_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_COLOR_BUFFER_TYPE; +import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT; +import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_NONE; +import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE; +import static javax.microedition.khronos.egl.EGL10.EGL_RGB_BUFFER; +import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES; +import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS; +import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE; +import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE; +import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT; + +/** + * Selects the right EGLConfig needed for `mapbox-gl-native` + */ +public class EGLConfigChooser implements GLSurfaceView.EGLConfigChooser { + + /** + * Requires API level 17 + * + * @see android.opengl.EGL14.EGL_CONFORMANT; + */ + @SuppressWarnings("JavadocReference") + private static final int EGL_CONFORMANT = 0x3042; + + /** + * Requires API level 17 + * + * @see android.opengl.EGL14.EGL_OPENGL_ES2_BIT; + */ + @SuppressWarnings("JavadocReference") + private static final int EGL_OPENGL_ES2_BIT = 0x0004; + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + int[] configAttribs = getConfigAttributes(); + + // Determine number of possible configurations + int[] numConfigs = getNumberOfConfigurations(egl, display, configAttribs); + if (numConfigs[0] < 1) { + Timber.e("eglChooseConfig() returned no configs."); + throw new EGLConfigException("eglChooseConfig() failed"); + } + + // Get all possible configurations + EGLConfig[] possibleConfigurations = getPossibleConfigurations(egl, display, configAttribs, numConfigs); + + // Choose best match + EGLConfig config = chooseBestMatchConfig(egl, display, possibleConfigurations); + if (config == null) { + Timber.e("No config chosen"); + throw new EGLConfigException("No config chosen"); + } + + return config; + } + + private int[] getNumberOfConfigurations(EGL10 egl, EGLDisplay display, int[] configAttributes) { + int[] numConfigs = new int[1]; + if (!egl.eglChooseConfig(display, configAttributes, null, 0, numConfigs)) { + Timber.e("eglChooseConfig(NULL) returned error %d", egl.eglGetError()); + throw new EGLConfigException("eglChooseConfig() failed"); + } + return numConfigs; + } + + private EGLConfig[] getPossibleConfigurations(EGL10 egl, EGLDisplay display, + int[] configAttributes, int[] numConfigs) { + EGLConfig[] configs = new EGLConfig[numConfigs[0]]; + if (!egl.eglChooseConfig(display, configAttributes, configs, numConfigs[0], numConfigs)) { + Timber.e("eglChooseConfig() returned error %d", egl.eglGetError()); + throw new EGLConfigException("eglChooseConfig() failed"); + } + return configs; + } + + // Quality + enum BufferFormat { + Format16Bit(3), + Format32BitNoAlpha(1), + Format32BitAlpha(2), + Format24Bit(0), + Unknown(4); + + int value; + + BufferFormat(int value) { + this.value = value; + } + } + + enum DepthStencilFormat { + Format16Depth8Stencil(1), + Format24Depth8Stencil(0); + + int value; + + DepthStencilFormat(int value) { + this.value = value; + } + } + + private EGLConfig chooseBestMatchConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { + class Config implements Comparable { + private final BufferFormat bufferFormat; + private final DepthStencilFormat depthStencilFormat; + private final boolean isNotConformant; + private final boolean isCaveat; + private final int index; + private final EGLConfig config; + + public Config(BufferFormat bufferFormat, DepthStencilFormat depthStencilFormat, + boolean isNotConformant, boolean isCaveat, int index, EGLConfig config) { + this.bufferFormat = bufferFormat; + this.depthStencilFormat = depthStencilFormat; + this.isNotConformant = isNotConformant; + this.isCaveat = isCaveat; + this.index = index; + this.config = config; + } + + + @Override + public int compareTo(@NonNull Config other) { + int i = compare(bufferFormat.value, other.bufferFormat.value); + if (i != 0) { + return i; + } + + i = compare(depthStencilFormat.value, other.depthStencilFormat.value); + if (i != 0) { + return i; + } + + i = compare(isNotConformant, other.isNotConformant); + if (i != 0) { + return i; + } + + i = compare(isCaveat, other.isCaveat); + if (i != 0) { + return i; + } + + i = compare(index, other.index); + if (i != 0) { + return i; + } + + return 0; + } + } + + List matches = new ArrayList<>(); + + int i = 0; + for (EGLConfig config : configs) { + i++; + + int caveat = getConfigAttr(egl, display, config, EGL_CONFIG_CAVEAT); + int conformant = getConfigAttr(egl, display, config, EGL_CONFORMANT); + int bits = getConfigAttr(egl, display, config, EGL_BUFFER_SIZE); + int red = getConfigAttr(egl, display, config, EGL_RED_SIZE); + int green = getConfigAttr(egl, display, config, EGL_GREEN_SIZE); + int blue = getConfigAttr(egl, display, config, EGL_BLUE_SIZE); + int alpha = getConfigAttr(egl, display, config, EGL_ALPHA_SIZE); + int alphaMask = getConfigAttr(egl, display, config, EGL_ALPHA_MASK_SIZE); + int depth = getConfigAttr(egl, display, config, EGL_DEPTH_SIZE); + int stencil = getConfigAttr(egl, display, config, EGL_STENCIL_SIZE); + int sampleBuffers = getConfigAttr(egl, display, config, EGL_SAMPLE_BUFFERS); + int samples = getConfigAttr(egl, display, config, EGL_SAMPLES); + + boolean configOk = (depth == 24) || (depth == 16); + configOk &= stencil == 8; + configOk &= sampleBuffers == 0; + configOk &= samples == 0; + + // Filter our configs first for depth, stencil and anti-aliasing + if (configOk) { + // Work out the config's buffer format + BufferFormat bufferFormat; + if ((bits == 16) && (red == 5) && (green == 6) && (blue == 5) && (alpha == 0)) { + bufferFormat = BufferFormat.Format16Bit; + } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) { + bufferFormat = BufferFormat.Format32BitNoAlpha; + } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 8)) { + bufferFormat = BufferFormat.Format32BitAlpha; + } else if ((bits == 24) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) { + bufferFormat = BufferFormat.Format24Bit; + } else { + bufferFormat = BufferFormat.Unknown; + } + + // Work out the config's depth stencil format + DepthStencilFormat depthStencilFormat; + if ((depth == 16) && (stencil == 8)) { + depthStencilFormat = DepthStencilFormat.Format16Depth8Stencil; + } else { + depthStencilFormat = DepthStencilFormat.Format24Depth8Stencil; + } + + boolean isNotConformant = (conformant & EGL_OPENGL_ES2_BIT) != EGL_OPENGL_ES2_BIT; + boolean isCaveat = caveat != EGL_NONE; + + // Ignore formats we don't recognise + if (bufferFormat != BufferFormat.Unknown) { + matches.add(new Config(bufferFormat, depthStencilFormat, isNotConformant, isCaveat, i, config)); + } + } + + } + + // Sort + Collections.sort(matches); + + if (matches.size() == 0) { + throw new EGLConfigException("No matching configurations after filtering"); + } + + Config bestMatch = matches.get(0); + + if (bestMatch.isCaveat) { + Timber.w("Chosen config has a caveat."); + } + + if (bestMatch.isNotConformant) { + Timber.w("Chosen config is not conformant."); + } + + return bestMatch.config; + } + + private int getConfigAttr(EGL10 egl, EGLDisplay display, EGLConfig config, int attributeName) { + int[] attributevalue = new int[1]; + if (!egl.eglGetConfigAttrib(display, config, attributeName, attributevalue)) { + Timber.e("eglGetConfigAttrib(%d) returned error %d", attributeName, egl.eglGetError()); + throw new EGLConfigException("eglGetConfigAttrib() failed"); + } + return attributevalue[0]; + } + + + private int[] getConfigAttributes() { + boolean emulator = inEmulator(); + Timber.i("In emulator: %s", emulator); + + // Get all configs at least RGB 565 with 16 depth and 8 stencil + return new int[] { + EGL_CONFIG_CAVEAT, EGL_NONE, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_BUFFER_SIZE, 16, + EGL_RED_SIZE, 5, + EGL_GREEN_SIZE, 6, + EGL_BLUE_SIZE, 5, + EGL_ALPHA_SIZE, 0, + EGL_DEPTH_SIZE, 16, + EGL_STENCIL_SIZE, 8, + (emulator ? EGL_NONE : EGL_CONFORMANT), EGL_OPENGL_ES2_BIT, + (emulator ? EGL_NONE : EGL_COLOR_BUFFER_TYPE), EGL_RGB_BUFFER, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + } + + /** + * Detect if we are in emulator. + */ + private boolean inEmulator() { + return System.getProperty("ro.kernel.qemu") != null; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigException.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigException.java new file mode 100644 index 0000000000..3f576d0eda --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/egl/EGLConfigException.java @@ -0,0 +1,21 @@ +package com.mapbox.mapboxsdk.egl; + +/** + * Used for EGL configuration exceptions + */ +public class EGLConfigException extends RuntimeException { + public EGLConfigException() { + } + + public EGLConfigException(String message) { + super(message); + } + + public EGLConfigException(String message, Throwable cause) { + super(message, cause); + } + + public EGLConfigException(Throwable cause) { + super(cause); + } +} 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 4d2019d68b..a920bed720 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 @@ -1,10 +1,9 @@ package com.mapbox.mapboxsdk.maps; import android.content.Context; -import android.graphics.Canvas; import android.graphics.PointF; -import android.graphics.SurfaceTexture; import android.os.Build; +import android.opengl.GLSurfaceView; import android.os.Bundle; import android.support.annotation.CallSuper; import android.support.annotation.IntDef; @@ -16,10 +15,6 @@ import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.Surface; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -32,6 +27,7 @@ import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.Style; +import com.mapbox.mapboxsdk.egl.EGLConfigChooser; import com.mapbox.mapboxsdk.maps.widgets.CompassView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; @@ -45,10 +41,14 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + import timber.log.Timber; import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION; import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_WAIT_IDLE; +import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY; /** *

@@ -72,7 +72,6 @@ public class MapView extends FrameLayout { private NativeMapView nativeMapView; private MapboxMapOptions mapboxMapOptions; private boolean destroyed; - private boolean hasSurface; private MyLocationView myLocationView; private CompassView compassView; @@ -86,6 +85,8 @@ public class MapView extends FrameLayout { private Bundle savedInstanceState; private final CopyOnWriteArrayList onMapChangedListeners = new CopyOnWriteArrayList<>(); + private GLSurfaceView glSurfaceView; + @UiThread public MapView(@NonNull Context context) { super(context); @@ -280,10 +281,37 @@ public class MapView extends FrameLayout { } private void initialiseDrawingSurface() { - SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView); - surfaceView.setZOrderMediaOverlay(mapboxMapOptions.getRenderSurfaceOnTop()); - surfaceView.getHolder().addCallback(new SurfaceCallback()); - surfaceView.setVisibility(View.VISIBLE); + glSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceView); + glSurfaceView.setZOrderMediaOverlay(mapboxMapOptions.getRenderSurfaceOnTop()); + glSurfaceView.setEGLContextClientVersion(2); + glSurfaceView.setEGLConfigChooser(new EGLConfigChooser()); + glSurfaceView.setRenderer(new GLSurfaceView.Renderer() { + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + MapView.this.post(new Runnable() { + @Override + public void run() { + initialiseMap(); + mapboxMap.onStart(); + } + }); + + nativeMapView.onSurfaceCreated(gl, config); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + nativeMapView.onSurfaceChanged(gl, width, height); + } + + @Override + public void onDrawFrame(GL10 gl) { + nativeMapView.onDrawFrame(gl); + } + }); + glSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY); + glSurfaceView.setVisibility(View.VISIBLE); } /** @@ -314,7 +342,9 @@ public class MapView extends FrameLayout { */ @UiThread public void onResume() { - // replaced by onStart in v5.0.0 + if (glSurfaceView != null) { + glSurfaceView.onResume(); + } } /** @@ -322,7 +352,9 @@ public class MapView extends FrameLayout { */ @UiThread public void onPause() { - // replaced by onStop in v5.0.0 + if (glSurfaceView != null) { + glSurfaceView.onPause(); + } } /** @@ -465,24 +497,10 @@ public class MapView extends FrameLayout { // Called when the map needs to be rerendered // Called via JNI from NativeMapView protected void onInvalidate() { - postInvalidate(); - } - - @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (isInEditMode()) { - return; + if (glSurfaceView != null) { + glSurfaceView.requestRender(); } - - if (destroyed) { - return; - } - - if (!hasSurface) { - return; - } - nativeMapView.render(); + // TODO: removable? postInvalidate(); } @Override @@ -496,103 +514,6 @@ public class MapView extends FrameLayout { } } - private class SurfaceCallback implements SurfaceHolder.Callback { - - private Surface surface; - - @Override - public void surfaceCreated(SurfaceHolder holder) { - if (nativeMapView == null) { - nativeMapView = new NativeMapView(MapView.this); - nativeMapView.createSurface(surface = holder.getSurface()); - nativeMapView.resizeView(getWidth(), getHeight()); - initialiseMap(); - mapboxMap.onStart(); - } else { - nativeMapView.createSurface(surface = holder.getSurface()); - } - - hasSurface = true; - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - if (destroyed) { - return; - } - nativeMapView.resizeFramebuffer(width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - hasSurface = false; - - if (nativeMapView != null) { - // occurs when activity goes to background - nativeMapView.destroySurface(); - } - surface.release(); - } - } - - // This class handles TextureView callbacks - private class SurfaceTextureListener implements TextureView.SurfaceTextureListener { - - private Surface surface; - - // Called when the native surface texture has been created - // Must do all EGL/GL ES initialization here - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - if (nativeMapView == null) { - nativeMapView = new NativeMapView(MapView.this); - nativeMapView.createSurface(this.surface = new Surface(surface)); - nativeMapView.resizeFramebuffer(width, height); - nativeMapView.resizeView(width, height); - initialiseMap(); - mapboxMap.onStart(); - } else { - nativeMapView.createSurface(this.surface = new Surface(surface)); - } - - hasSurface = true; - } - - // Called when the native surface texture has been destroyed - // Must do all EGL/GL ES destruction here - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - hasSurface = false; - - if (nativeMapView != null) { - nativeMapView.destroySurface(); - } - this.surface.release(); - return true; - } - - // Called when the format or size of the native surface texture has been changed - // Must handle window resizing here. - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - if (destroyed) { - return; - } - - nativeMapView.resizeFramebuffer(width, height); - } - - // Called when the SurfaceTexure frame is drawn to screen - // Must sync with UI here - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - if (destroyed) { - return; - } - mapboxMap.onUpdateRegionChange(); - } - } - // // View events // @@ -612,7 +533,9 @@ public class MapView extends FrameLayout { return; } - if (nativeMapView != null) { + if (nativeMapView == null) { + nativeMapView = new NativeMapView(this); + } else if (mapZoomButtonController != null) { mapZoomButtonController.setVisible(visibility == View.VISIBLE); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index 4f5037e4b3..a97b7cc8f2 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -4,12 +4,12 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.PointF; import android.graphics.RectF; +import android.opengl.GLSurfaceView; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.DisplayMetrics; -import android.view.Surface; import com.mapbox.mapboxsdk.LibraryLoader; import com.mapbox.mapboxsdk.annotations.Icon; @@ -35,10 +35,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + import timber.log.Timber; // Class that wraps the native methods for convenience -final class NativeMapView { +final class NativeMapView implements GLSurfaceView.Renderer { // Flag to indicating destroy was called private boolean destroyed = false; @@ -97,20 +100,6 @@ final class NativeMapView { destroyed = true; } - public void createSurface(Surface surface) { - if (isDestroyedOn("createSurface")) { - return; - } - nativeCreateSurface(surface); - } - - public void destroySurface() { - if (isDestroyedOn("destroySurface")) { - return; - } - nativeDestroySurface(); - } - public void update() { if (isDestroyedOn("update")) { return; @@ -156,7 +145,7 @@ final class NativeMapView { nativeResizeView(width, height); } - public void resizeFramebuffer(int fbWidth, int fbHeight) { + private void resizeFramebuffer(int fbWidth, int fbHeight) { if (isDestroyedOn("resizeFramebuffer")) { return; } @@ -1141,6 +1130,32 @@ final class NativeMapView { void addSnapshotCallback(@NonNull MapboxMap.SnapshotReadyCallback callback) { snapshotReadyCallback = callback; scheduleTakeSnapshot(); - render(); + mapView.onInvalidate(); + // TODO. this should do a request render + //render(); + } + + // + // GLSurfaceView.Renderer + // + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + Timber.i("[%s] onSurfaceCreated", Thread.currentThread().getName()); + //TODO: callback to map to re-create renderer? + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + Timber.i("[%s] onSurfaceChanged %sx%s", Thread.currentThread().getName(), width, height); + // Sets the current view port to the new size. + gl.glViewport(0, 0, width, height); + resizeFramebuffer(width, height); + // resizeView(width, height); Done from MapView#onSizeChanged + } + + @Override + public void onDrawFrame(GL10 gl) { + nativeRender(); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/Compare.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/Compare.java new file mode 100644 index 0000000000..c7d7a13a3d --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/Compare.java @@ -0,0 +1,27 @@ +package com.mapbox.mapboxsdk.utils; + +/** + * Comparisons from std sdk, which aren't available in API level <= 15 + */ +public class Compare { + + /** + * @see Integer#compare(int, int) + * @param x left side + * @param y right side + * @return std compare value + */ + public static int compare(int x, int y) { + return (x < y) ? -1 : ((x == y) ? 0 : 1); + } + + /** + * @see Boolean#compare(boolean, boolean) + * @param x left side + * @param y right side + * @return std compare value + */ + public static int compare(boolean x, boolean y) { + return (x == y) ? 0 : (x ? 1 : -1); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml index 6d07de7baa..e2f1823327 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml @@ -1,7 +1,7 @@ - +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace mbgl { +namespace android { + +class AndroidGLThread : public Scheduler { +public: + using InvalidateCallback = std::function; + + template + AndroidGLThread(InvalidateCallback callback_, Args&&... args) + : renderer(std::make_unique(std::forward(args)...)) + , mailbox(std::make_shared(*this)) + , callback(callback_) + , rendererRef(*renderer, mailbox) { + } + + ~AndroidGLThread() override = default; + + ActorRef actor() const { + return rendererRef; + } + + void schedule(std::weak_ptr scheduled) override { + { + std::lock_guard lock(mutex); + queue.push(scheduled); + } + + // Invalidate so we get processing time later + callback(); + } + + // Only safe on the GL Thread + void process() { + while (true) { + std::unique_lock lock(mutex); + + if (queue.empty()) { + return; + } + + auto scheduled = queue.front(); + queue.pop(); + + lock.unlock(); + + Mailbox::maybeReceive(scheduled); + } + } + + // Only safe to access on the GL Thread + std::unique_ptr renderer; + +private: + std::mutex mutex; + std::queue> queue; + + std::shared_ptr mailbox; + InvalidateCallback callback; + ActorRef rendererRef; +}; + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/android_renderer_frontend.cpp b/platform/android/src/android_renderer_frontend.cpp index 597bebe40d..cdcd8eddfe 100644 --- a/platform/android/src/android_renderer_frontend.cpp +++ b/platform/android/src/android_renderer_frontend.cpp @@ -1,72 +1,188 @@ #include "android_renderer_frontend.hpp" -#include -#include +#include +#include +#include +#include +#include +#include + +#include "android_renderer_backend.hpp" namespace mbgl { namespace android { -AndroidRendererFrontend::AndroidRendererFrontend( - std::unique_ptr renderer_, - RendererBackend& backend_, - InvalidateCallback invalidate) - : renderer(std::move(renderer_)) - , backend(backend_) - , asyncInvalidate([=, invalidate=std::move(invalidate)]() { +// Forwards RendererObserver signals to the given +// Delegate RendererObserver on the given RunLoop +class ForwardingRendererObserver : public RendererObserver { +public: + ForwardingRendererObserver(util::RunLoop& mapRunLoop, RendererObserver& delegate_) + : mailbox(std::make_shared(mapRunLoop)) + , delegate(delegate_, mailbox) { + } + + ~ForwardingRendererObserver() { + mailbox->close(); + } + + void onInvalidate() override { + delegate.invoke(&RendererObserver::onInvalidate); + } + + void onResourceError(std::exception_ptr err) override { + delegate.invoke(&RendererObserver::onResourceError, err); + } + + void onWillStartRenderingMap() override { + delegate.invoke(&RendererObserver::onWillStartRenderingMap); + } + + void onWillStartRenderingFrame() override { + delegate.invoke(&RendererObserver::onWillStartRenderingFrame); + } + + void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded) override { + delegate.invoke(&RendererObserver::onDidFinishRenderingFrame, mode, repaintNeeded); + } + + void onDidFinishRenderingMap() override { + delegate.invoke(&RendererObserver::onDidFinishRenderingMap); + } + +private: + std::shared_ptr mailbox; + ActorRef delegate; +}; + +AndroidRendererFrontend::AndroidRendererFrontend(float pixelRatio, + FileSource& fileSource, + Scheduler& scheduler, + std::string programCacheDir, + InvalidateCallback invalidate) + : backend(std::make_unique()) + , glThread(std::make_unique( + invalidate, + *backend, + pixelRatio, + fileSource, + scheduler, + GLContextMode::Unique, + programCacheDir + )) + , asyncInvalidate([=]() { invalidate(); - }) { + }) + , mapRunLoop(util::RunLoop::Get()) { } AndroidRendererFrontend::~AndroidRendererFrontend() = default; void AndroidRendererFrontend::reset() { - assert (renderer); - if (renderer) { - renderer.reset(); - } + assert(glThread); + glThread.reset(); } void AndroidRendererFrontend::setObserver(RendererObserver& observer) { - assert (renderer); - renderer->setObserver(&observer); + assert (glThread); + assert (util::RunLoop::Get()); + rendererObserver = std::make_unique(*mapRunLoop, observer); + glThread->actor().invoke(&Renderer::setObserver, rendererObserver.get()); } void AndroidRendererFrontend::update(std::shared_ptr params) { - updateParameters = std::move(params); + { + // Lock on the parameters + std::lock_guard lock(updateMutex); + updateParameters = std::move(params); + } asyncInvalidate.send(); } +// Called on OpenGL thread void AndroidRendererFrontend::render() { - assert (renderer); - if (!updateParameters) return; + assert (glThread); + + std::shared_ptr params; + { + // Lock on the parameters + std::unique_lock lock(updateMutex); + if (!updateParameters) return; + + // Hold on to the update parameters during render + params = updateParameters; + } + + // Process the gl thread mailbox + glThread->process(); - BackendScope guard { backend }; + // Activate the backend + BackendScope backendGuard { *backend }; - renderer->render(*updateParameters); + // Ensure that the "current" scheduler on the render thread is + // actually the scheduler from the orchestration thread + Scheduler::SetCurrent(glThread.get()); + + if (framebufferSizeChanged) { + backend->updateViewPort(); + framebufferSizeChanged = false; + } + + glThread->renderer->render(*params); } void AndroidRendererFrontend::onLowMemory() { - assert (renderer); - renderer->onLowMemory(); + assert (glThread); + glThread->actor().invoke(&Renderer::onLowMemory); } std::vector AndroidRendererFrontend::querySourceFeatures(const std::string& sourceID, const SourceQueryOptions& options) const { - return renderer->querySourceFeatures(sourceID, options); + assert (glThread); + // Waits for the result from the orchestration thread and returns + return glThread->actor().ask(&Renderer::querySourceFeatures, sourceID, options).get(); } std::vector AndroidRendererFrontend::queryRenderedFeatures(const ScreenBox& box, const RenderedQueryOptions& options) const { - return renderer->queryRenderedFeatures(box, options); + assert (glThread); + + // Select the right overloaded method + std::vector (Renderer::*fn)(const ScreenBox&, const RenderedQueryOptions&) const = &Renderer::queryRenderedFeatures; + + // Waits for the result from the orchestration thread and returns + return glThread->actor().ask(fn, box, options).get(); } std::vector AndroidRendererFrontend::queryRenderedFeatures(const ScreenCoordinate& point, const RenderedQueryOptions& options) const { - return renderer->queryRenderedFeatures(point, options); + assert (glThread); + + // Select the right overloaded method + std::vector (Renderer::*fn)(const ScreenCoordinate&, const RenderedQueryOptions&) const = &Renderer::queryRenderedFeatures; + + // Waits for the result from the orchestration thread and returns + return glThread->actor().ask(fn, point, options).get(); } AnnotationIDs AndroidRendererFrontend::queryPointAnnotations(const ScreenBox& box) const { - return renderer->queryPointAnnotations(box); + assert (glThread); + + // Waits for the result from the orchestration thread and returns + return glThread->actor().ask(&Renderer::queryPointAnnotations, box).get(); +} + +void AndroidRendererFrontend::requestSnapshot(SnapshotCallback callback_) { + snapshotCallback = std::make_unique([callback=std::move(callback_), runloop=util::RunLoop::Get()](PremultipliedImage image){ + runloop->invoke([&]() { + callback(std::move(image)); + }); + }); +} + +void AndroidRendererFrontend::resizeFramebuffer(int width, int height) { + backend->resizeFramebuffer(width, height); + framebufferSizeChanged = true; + asyncInvalidate.send(); } } // namespace android diff --git a/platform/android/src/android_renderer_frontend.hpp b/platform/android/src/android_renderer_frontend.hpp index 107bbbe0b6..f2e951bd05 100644 --- a/platform/android/src/android_renderer_frontend.hpp +++ b/platform/android/src/android_renderer_frontend.hpp @@ -1,34 +1,50 @@ #pragma once +#include "android_gl_thread.hpp" + +#include #include #include #include #include #include +#include +#include #include #include #include +#include namespace mbgl { -class Renderer; +class FileSource; +class Scheduler; class RenderedQueryOptions; class SourceQueryOptions; namespace android { +class AndroidRendererBackend; + class AndroidRendererFrontend : public RendererFrontend { public: using InvalidateCallback = std::function; - AndroidRendererFrontend(std::unique_ptr, RendererBackend&, InvalidateCallback); + + AndroidRendererFrontend(float pixelRatio, + mbgl::FileSource&, + mbgl::Scheduler&, + std::string programCacheDir, + InvalidateCallback); ~AndroidRendererFrontend() override; void reset() override; void setObserver(RendererObserver&) override; void update(std::shared_ptr) override; + + // Called from OpenGL Thread void render(); // Feature querying @@ -37,14 +53,31 @@ public: std::vector querySourceFeatures(const std::string& sourceID, const SourceQueryOptions&) const; AnnotationIDs queryPointAnnotations(const ScreenBox& box) const; + // RenderBackend proxy - Called from OpenGL Thread + void resizeFramebuffer(int width, int height); + // Memory void onLowMemory(); + // Snapshot - Callback will be called on calling thread through RunLoop + using SnapshotCallback = std::function; + void requestSnapshot(SnapshotCallback); + private: - std::unique_ptr renderer; - RendererBackend& backend; + std::unique_ptr backend; + std::unique_ptr glThread; + std::unique_ptr rendererObserver; + + std::mutex updateMutex; std::shared_ptr updateParameters; + + util::AsyncTask asyncInvalidate; + + util::RunLoop* mapRunLoop; + + bool framebufferSizeChanged = true; + std::unique_ptr snapshotCallback; }; } // namespace android diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index a0d88e8cf1..1a0dc34bbc 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -61,8 +61,7 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env, jni::Object jFileSource, jni::jfloat _pixelRatio, jni::String _programCacheDir) - : rendererBackend(std::make_unique()), - javaPeer(_obj.NewWeakGlobalRef(_env)), + : javaPeer(_obj.NewWeakGlobalRef(_env)), pixelRatio(_pixelRatio), threadPool(sharedThreadPool()) { @@ -72,16 +71,13 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env, return; } - auto& fileSource = mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource); - - // Create a renderer - auto renderer = std::make_unique(*rendererBackend, pixelRatio, fileSource, - *threadPool, GLContextMode::Unique, - jni::Make(_env, _programCacheDir)); + mbgl::FileSource& fileSource = mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource); // Create a renderer frontend - rendererFrontend = std::make_unique(std::move(renderer), - *rendererBackend, + rendererFrontend = std::make_unique(pixelRatio, fileSource, + *threadPool, + jni::Make(_env, + _programCacheDir), [this] { this->invalidate(); }); // Create the core map @@ -90,29 +86,21 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env, static_cast(height) }, pixelRatio, fileSource, *threadPool, MapMode::Continuous, ConstrainMode::HeightOnly, ViewportMode::Default); - - // initialize egl components - _initializeDisplay(); - _initializeContext(); } /** * Called through NativeMapView#destroy() */ NativeMapView::~NativeMapView() { - _terminateContext(); - _destroySurface(); - _terminateDisplay(); - map.reset(); - vm = nullptr; } /** * Callback to java NativeMapView#onInvalidate(). * - * May be called from any thread + * Called to invalidate the View and schedule a render on the next + * runloop iteration. */ void NativeMapView::invalidate() { android::UniqueEnv _env = android::AttachEnv(); @@ -199,48 +187,12 @@ void NativeMapView::onSourceChanged(mbgl::style::Source&) { // JNI Methods // -void NativeMapView::createSurface(jni::JNIEnv& env, jni::Object<> _surface) { - _createSurface(ANativeWindow_fromSurface(&env, jni::Unwrap(*_surface))); -} - -void NativeMapView::destroySurface(jni::JNIEnv&) { - _destroySurface(); -} - -void NativeMapView::render(jni::JNIEnv& env) { - BackendScope guard { *rendererBackend }; - - if (framebufferSizeChanged) { - rendererBackend->updateViewPort(); - framebufferSizeChanged = false; - } - +// Called from the OpenGL renderer thread +void NativeMapView::render(jni::JNIEnv& ) { rendererFrontend->render(); - if(snapshot){ - snapshot = false; - - // take snapshot - auto image = rendererBackend->readFramebuffer(); - auto bitmap = Bitmap::CreateBitmap(env, std::move(image)); - - // invoke Mapview#OnSnapshotReady - android::UniqueEnv _env = android::AttachEnv(); - static auto onSnapshotReady = javaClass.GetMethod)>(*_env, "onSnapshotReady"); - javaPeer->Call(*_env, onSnapshotReady, bitmap); - } - - if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) { - if (!eglSwapBuffers(display, surface)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglSwapBuffers() returned error %d", - eglGetError()); - throw std::runtime_error("eglSwapBuffers() failed"); - } - - updateFps(); - } else { - mbgl::Log::Info(mbgl::Event::Android, "Not swapping as we are not ready"); - } + // TODO + updateFps(); } void NativeMapView::update(jni::JNIEnv&) { @@ -254,9 +206,9 @@ void NativeMapView::resizeView(jni::JNIEnv&, int w, int h) { } void NativeMapView::resizeFramebuffer(jni::JNIEnv&, int w, int h) { - rendererBackend->resizeFramebuffer(w, h); - framebufferSizeChanged = true; - invalidate(); + rendererFrontend->resizeFramebuffer(w, h); +// framebufferSizeChanged = true; +// invalidate(); } jni::String NativeMapView::getStyleUrl(jni::JNIEnv& env) { @@ -476,7 +428,15 @@ void NativeMapView::setContentPadding(JNIEnv&, double top, double left, double b } void NativeMapView::scheduleSnapshot(jni::JNIEnv&) { - snapshot = true; + rendererFrontend->requestSnapshot([&](PremultipliedImage image) { + auto _env = android::AttachEnv(); + // Convert image to bitmap + auto bitmap = Bitmap::CreateBitmap(*_env, std::move(image)); + + // invoke Mapview#OnSnapshotReady + static auto onSnapshotReady = javaClass.GetMethod)>(*_env, "onSnapshotReady"); + javaPeer->Call(*_env, onSnapshotReady, bitmap); + }); } void NativeMapView::enableFps(jni::JNIEnv&, jni::jboolean enable) { @@ -1001,371 +961,6 @@ jni::jboolean NativeMapView::getPrefetchesTiles(JNIEnv&) { // Private methods // -void NativeMapView::_initializeDisplay() { - assert(display == EGL_NO_DISPLAY); - assert(config == nullptr); - assert(format < 0); - - display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (display == EGL_NO_DISPLAY) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetDisplay() returned error %d", eglGetError()); - throw std::runtime_error("eglGetDisplay() failed"); - } - - EGLint major, minor; - if (!eglInitialize(display, &major, &minor)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglInitialize() returned error %d", eglGetError()); - throw std::runtime_error("eglInitialize() failed"); - } - if ((major <= 1) && (minor < 3)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "EGL version is too low, need 1.3, got %d.%d", major, - minor); - throw std::runtime_error("EGL version is too low"); - } - - // Detect if we are in emulator. - const bool inEmulator = []() { - char prop[PROP_VALUE_MAX]; - __system_property_get("ro.kernel.qemu", prop); - return strtol(prop, nullptr, 0) == 1; - }(); - - if (inEmulator) { - // XXX https://code.google.com/p/android/issues/detail?id=78977 - mbgl::Log::Warning(mbgl::Event::Android, "Running SDK in emulator!"); - } - - if (!eglBindAPI(EGL_OPENGL_ES_API)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglBindAPI(EGL_OPENGL_ES_API) returned error %d", eglGetError()); - throw std::runtime_error("eglBindAPI() failed"); - } - - // Get all configs at least RGB 565 with 16 depth and 8 stencil - EGLint configAttribs[] = { - EGL_CONFIG_CAVEAT, EGL_NONE, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_BUFFER_SIZE, 16, - EGL_RED_SIZE, 5, - EGL_GREEN_SIZE, 6, - EGL_BLUE_SIZE, 5, - EGL_DEPTH_SIZE, 16, - EGL_STENCIL_SIZE, 8, - (inEmulator ? EGL_NONE : EGL_CONFORMANT), EGL_OPENGL_ES2_BIT, - (inEmulator ? EGL_NONE : EGL_COLOR_BUFFER_TYPE), EGL_RGB_BUFFER, - EGL_NONE - }; - - EGLint numConfigs; - if (!eglChooseConfig(display, configAttribs, nullptr, 0, &numConfigs)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig(NULL) returned error %d", - eglGetError()); - throw std::runtime_error("eglChooseConfig() failed"); - } - if (numConfigs < 1) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig() returned no configs."); - throw std::runtime_error("eglChooseConfig() failed"); - } - - const auto configs = std::make_unique(numConfigs); - if (!eglChooseConfig(display, configAttribs, configs.get(), numConfigs, &numConfigs)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig() returned error %d", eglGetError()); - throw std::runtime_error("eglChooseConfig() failed"); - } - - config = chooseConfig(configs.get(), numConfigs); - if (config == nullptr) { - mbgl::Log::Error(mbgl::Event::OpenGL, "No config chosen"); - throw std::runtime_error("No config chosen"); - } - - if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib() returned error %d", - eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } -} - -// Quality -typedef enum { - Format16Bit = 3, - Format32BitNoAlpha = 1, - Format32BitAlpha = 2, - Format24Bit = 0, - Unknown = 4 -} BufferFormat; - -typedef enum { - Format16Depth8Stencil = 1, - Format24Depth8Stencil = 0, -} DepthStencilFormat; - -// Tuple is -typedef std::tuple ConfigProperties; - -EGLConfig NativeMapView::chooseConfig(const EGLConfig configs[], EGLint numConfigs) { - // Create a list of configs that pass our filters - std::list configList; - for (int i = 0; i < numConfigs; i++) { - EGLint caveat, conformant, bits, red, green, blue, alpha, alphaMask, depth, stencil, - sampleBuffers, samples; - - if (!eglGetConfigAttrib(display, configs[i], EGL_CONFIG_CAVEAT, &caveat)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_CONFIG_CAVEAT) returned error %d", - eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_CONFORMANT, &conformant)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_CONFORMANT) returned error %d", eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_BUFFER_SIZE, &bits)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_BUFFER_SIZE) returned error %d", - eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_RED_SIZE, &red)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_RED_SIZE) returned error %d", eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_GREEN_SIZE, &green)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_GREEN_SIZE) returned error %d", eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_BLUE_SIZE, &blue)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_BLUE_SIZE) returned error %d", eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_ALPHA_SIZE, &alpha)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_ALPHA_SIZE) returned error %d", eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_ALPHA_MASK_SIZE, &alphaMask)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_ALPHA_MASK_SIZE) returned error %d", - eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_DEPTH_SIZE, &depth)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_DEPTH_SIZE) returned error %d", eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_STENCIL_SIZE, &stencil)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_STENCIL_SIZE) returned error %d", - eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLE_BUFFERS, &sampleBuffers)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_SAMPLE_BUFFERS) returned error %d", - eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLES, &samples)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglGetConfigAttrib(EGL_SAMPLES) returned error %d", eglGetError()); - throw std::runtime_error("eglGetConfigAttrib() failed"); - } - - bool configOk = true; - configOk &= (depth == 24) || (depth == 16); - configOk &= stencil == 8; - configOk &= sampleBuffers == 0; - configOk &= samples == 0; - - // Filter our configs first for depth, stencil and anti-aliasing - if (configOk) { - // Work out the config's buffer format - BufferFormat bufferFormat; - if ((bits == 16) && (red == 5) && (green == 6) && (blue == 5) && (alpha == 0)) { - bufferFormat = Format16Bit; - } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) { - bufferFormat = Format32BitNoAlpha; - } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 8)) { - bufferFormat = Format32BitAlpha; - } else if ((bits == 24) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) { - bufferFormat = Format24Bit; - } else { - bufferFormat = Unknown; - } - - // Work out the config's depth stencil format - DepthStencilFormat depthStencilFormat; - if ((depth == 16) && (stencil == 8)) { - depthStencilFormat = Format16Depth8Stencil; - } else { - depthStencilFormat = Format24Depth8Stencil; - } - - bool isNotConformant = (conformant & EGL_OPENGL_ES2_BIT) != EGL_OPENGL_ES2_BIT; - bool isCaveat = caveat != EGL_NONE; - EGLConfig configId = configs[i]; - - // Ignore formats we don't recognise - if (bufferFormat != Unknown) { - configList.push_back(std::make_tuple(bufferFormat, depthStencilFormat, - isNotConformant, isCaveat, i, configId)); - } - } - } - - if (configList.empty()) { - mbgl::Log::Error(mbgl::Event::OpenGL, "Config list was empty."); - } - - // Sort the configs to find the best one - configList.sort(); - bool isConformant = !std::get<2>(configList.front()); - bool isCaveat = std::get<3>(configList.front()); - EGLConfig configId = std::get<5>(configList.front()); - - if (isCaveat) { - mbgl::Log::Warning(mbgl::Event::OpenGL, "Chosen config has a caveat."); - } - if (!isConformant) { - mbgl::Log::Warning(mbgl::Event::OpenGL, "Chosen config is not conformant."); - } - - return configId; -} - -void NativeMapView::_terminateDisplay() { - if (display != EGL_NO_DISPLAY) { - // Destroy the surface first, if it still exists. This call needs a valid surface. - if (surface != EGL_NO_SURFACE) { - if (!eglDestroySurface(display, surface)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d", - eglGetError()); - throw std::runtime_error("eglDestroySurface() failed"); - } - surface = EGL_NO_SURFACE; - } - - if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError()); - throw std::runtime_error("eglMakeCurrent() failed"); - } - - if (!eglTerminate(display)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglTerminate() returned error %d", - eglGetError()); - throw std::runtime_error("eglTerminate() failed"); - } - } - - display = EGL_NO_DISPLAY; - config = nullptr; - format = -1; -} - -void NativeMapView::_initializeContext() { - assert(display != EGL_NO_DISPLAY); - assert(context == EGL_NO_CONTEXT); - assert(config != nullptr); - - const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; - context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); - if (context == EGL_NO_CONTEXT) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateContext() returned error %d", - eglGetError()); - throw std::runtime_error("eglCreateContext() failed"); - } -} - -void NativeMapView::_terminateContext() { - if (display != EGL_NO_DISPLAY) { - - if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { - mbgl::Log::Error(mbgl::Event::OpenGL, - "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError()); - throw std::runtime_error("eglMakeCurrent() failed"); - } - - if (context != EGL_NO_CONTEXT) { - if (!eglDestroyContext(display, context)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroyContext() returned error %d", - eglGetError()); - throw std::runtime_error("eglDestroyContext() failed"); - } - } - } - - context = EGL_NO_CONTEXT; -} - -void NativeMapView::_createSurface(ANativeWindow *window_) { - assert(window == nullptr); - assert(window_ != nullptr); - window = window_; - - assert(display != EGL_NO_DISPLAY); - assert(surface == EGL_NO_SURFACE); - assert(config != nullptr); - assert(format >= 0); - - ANativeWindow_setBuffersGeometry(window, 0, 0, format); - - const EGLint surfaceAttribs[] = {EGL_NONE}; - surface = eglCreateWindowSurface(display, config, window, surfaceAttribs); - if (surface == EGL_NO_SURFACE) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateWindowSurface() returned error %d", - eglGetError()); - throw std::runtime_error("eglCreateWindowSurface() failed"); - } - - if (firstRender) { - firstRender = false; - - BackendScope guard(*rendererBackend); - - if (!eglMakeCurrent(display, surface, surface, context)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d", - eglGetError()); - throw std::runtime_error("eglMakeCurrent() failed"); - } - } -} - -void NativeMapView::_destroySurface() { - if (surface != EGL_NO_SURFACE) { - if (!eglDestroySurface(display, surface)) { - mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d", - eglGetError()); - throw std::runtime_error("eglDestroySurface() failed"); - } - } - - surface = EGL_NO_SURFACE; - firstRender = true; - - if (window != nullptr) { - ANativeWindow_release(window); - window = nullptr; - } -} - void NativeMapView::updateFps() { if (!fpsEnabled) { return; @@ -1412,8 +1007,6 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::update, "nativeUpdate"), METHOD(&NativeMapView::resizeView, "nativeResizeView"), METHOD(&NativeMapView::resizeFramebuffer, "nativeResizeFramebuffer"), - METHOD(&NativeMapView::createSurface, "nativeCreateSurface"), - METHOD(&NativeMapView::destroySurface, "nativeDestroySurface"), METHOD(&NativeMapView::getStyleUrl, "nativeGetStyleUrl"), METHOD(&NativeMapView::setStyleUrl, "nativeSetStyleUrl"), METHOD(&NativeMapView::getStyleJson, "nativeGetStyleJson"), @@ -1498,5 +1091,5 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { ); } -} -} +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index 074906dbab..0539a29b46 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -36,7 +36,6 @@ namespace mbgl { namespace android { -class AndroidRendererBackend; class AndroidRendererFrontend; class NativeMapView : public MapObserver { @@ -78,18 +77,19 @@ public: // JNI // + // Called on OpenGL Thread + void onSurfaceCreated(jni::JNIEnv&); + + // Called on OpenGL Thread void render(jni::JNIEnv&); void update(jni::JNIEnv&); void resizeView(jni::JNIEnv&, int, int); + // Called on OpenGL Thread void resizeFramebuffer(jni::JNIEnv&, int, int); - void createSurface(jni::JNIEnv&, jni::Object<>); - - void destroySurface(jni::JNIEnv&); - jni::String getStyleUrl(jni::JNIEnv&); void setStyleUrl(jni::JNIEnv&, jni::String); @@ -257,25 +257,11 @@ public: jni::jboolean getPrefetchesTiles(JNIEnv&); private: - void _initializeDisplay(); - - void _terminateDisplay(); - - void _initializeContext(); - - void _terminateContext(); - - void _createSurface(ANativeWindow*); - - void _destroySurface(); - - EGLConfig chooseConfig(const EGLConfig configs[], EGLint numConfigs); void updateFps(); private: std::unique_ptr rendererFrontend; - std::unique_ptr rendererBackend; JavaVM *vm = nullptr; jni::UniqueWeakObject javaPeer; @@ -283,28 +269,14 @@ private: std::string styleUrl; std::string apiKey; - ANativeWindow *window = nullptr; - - EGLConfig config = nullptr; - EGLint format = -1; - - EGLDisplay display = EGL_NO_DISPLAY; - EGLSurface surface = EGL_NO_SURFACE; - EGLContext context = EGL_NO_CONTEXT; - - float pixelRatio; bool fpsEnabled = false; - bool snapshot = false; - bool firstRender = true; double fps = 0.0; // Minimum texture size according to OpenGL ES 2.0 specification. int width = 64; int height = 64; - bool framebufferSizeChanged = true; - // Ensure these are initialised last std::shared_ptr threadPool; std::unique_ptr map; -- cgit v1.2.1