From 77a464d3355a65b54972b2ade48a8e80d7df049b Mon Sep 17 00:00:00 2001 From: alasram Date: Tue, 17 Sep 2024 12:59:58 -0700 Subject: [PATCH] Expose RENDERMODE_CONTINUOUSLY and RENDERMODE_WHEN_DIRTY (#2801) --- .../org/maplibre/android/maps/MapView.java | 38 +++++++++-- .../android/maps/renderer/MapRenderer.java | 25 ++++++- .../surfaceview/MapLibreSurfaceView.java | 67 +++++++------------ .../surfaceview/SurfaceViewMapRenderer.java | 16 +++++ .../textureview/TextureViewMapRenderer.java | 18 +++++ .../surfaceview/GLSurfaceViewMapRenderer.java | 5 +- .../maplibre/android/maps/MapLibreMapTest.kt | 14 ++++ .../android/maps/NativeMapViewTest.kt | 10 +++ .../android/testapp/activity/BaseTest.java | 10 +-- .../activity/maplayout/DebugModeActivity.kt | 9 +++ .../src/main/res/menu/menu_debug.xml | 4 ++ 11 files changed, 157 insertions(+), 59 deletions(-) diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java index d041ad762c5..4c62ff47ec9 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java @@ -140,10 +140,10 @@ protected void initialize(@NonNull final Context context, @NonNull final MapLibr // add accessibility support setContentDescription(context.getString(R.string.maplibre_mapActionDescription)); setWillNotDraw(false); - initialiseDrawingSurface(options); + initializeDrawingSurface(options); } - private void initialiseMap() { + private void initializeMap() { Context context = getContext(); // callback for focal point invalidation @@ -305,7 +305,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } } - private void initialiseDrawingSurface(MapLibreMapOptions options) { + private void initializeDrawingSurface(MapLibreMapOptions options) { mapRenderer = MapRenderer.create(options, getContext(), () -> MapView.this.onSurfaceCreated()); renderView = mapRenderer.getView(); @@ -321,9 +321,9 @@ private void onSurfaceCreated() { post(new Runnable() { @Override public void run() { - // Initialise only when not destroyed and only once + // Initialize only when not destroyed and only once if (!destroyed && maplibreMap == null) { - MapView.this.initialiseMap(); + MapView.this.initializeMap(); maplibreMap.onStart(); } } @@ -465,6 +465,34 @@ public void setMaximumFps(int maximumFps) { } } + /** + * Set the rendering refresh mode and wake up the render thread if it is sleeping. + * + * @param mode can be: + * {@link MapRenderer.RenderingRefreshMode#CONTINUOUS} or {@link MapRenderer.RenderingRefreshMode#WHEN_DIRTY} + * default is {@link MapRenderer.RenderingRefreshMode#WHEN_DIRTY} + */ + public void setRenderingRefreshMode(MapRenderer.RenderingRefreshMode mode) { + if (mapRenderer != null) { + mapRenderer.setRenderingRefreshMode(mode); + } else { + throw new IllegalStateException("Calling MapView#setRenderingRefreshMode before mapRenderer is created."); + } + } + + /** + * Get the rendering refresh mode + * + * @return one of the MapRenderer.RenderingRefreshMode modes + * @see #setRenderingRefreshMode + */ + public MapRenderer.RenderingRefreshMode getRenderingRefreshMode() { + if (mapRenderer == null) { + throw new IllegalStateException("Calling MapView#getRenderingRefreshMode before mapRenderer is created."); + } + return mapRenderer.getRenderingRefreshMode(); + } + /** * Returns if the map has been destroyed. *

diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java index 992e07b6df2..24ca1af3170 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java @@ -30,7 +30,24 @@ public abstract class MapRenderer implements MapRendererScheduler { private static final String TAG = "Mbgl-MapRenderer"; - // Holds the pointer to the native peer after initialisation + /** + * Rendering presentation refresh mode. + */ + public enum RenderingRefreshMode { + /** + * The map is rendered only in response to an event that affects the rendering of the map. + * This mode is preferred to improve battery life and overall system performance + */ + WHEN_DIRTY, + + /** + * The map is repeatedly re-rendered at the refresh rate of the display. + * This mode is preferred when benchmarking the rendering + */ + CONTINUOUS, + } + + // Holds the pointer to the native peer after initialization private long nativePtr = 0; private double expectedRenderTime = 0; private MapLibreMap.OnFpsChangedListener onFpsChangedListener; @@ -57,7 +74,7 @@ public static MapRenderer create(MapLibreMapOptions options, @NonNull Context co public MapRenderer(@NonNull Context context, String localIdeographFontFamily) { float pixelRatio = context.getResources().getDisplayMetrics().density; - // Initialise native peer + // Initialize native peer nativeInitialize(this, pixelRatio, localIdeographFontFamily); } @@ -83,6 +100,10 @@ public void onDestroy() { // Implement if needed } + public abstract void setRenderingRefreshMode(RenderingRefreshMode mode); + + public abstract RenderingRefreshMode getRenderingRefreshMode(); + public void setOnFpsChangedListener(MapLibreMap.OnFpsChangedListener listener) { onFpsChangedListener = listener; } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreSurfaceView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreSurfaceView.java index d69baa0b12b..b30c0642b91 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreSurfaceView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreSurfaceView.java @@ -5,6 +5,7 @@ import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceView; +import org.maplibre.android.maps.renderer.MapRenderer; import androidx.annotation.NonNull; @@ -21,24 +22,6 @@ public abstract class MapLibreSurfaceView extends SurfaceView implements Surface protected boolean detached; - /** - * The renderer only renders - * when the surface is created, or when {@link #requestRender} is called. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - * @see #requestRender() - */ - public static final int RENDERMODE_WHEN_DIRTY = 0; - /** - * The renderer is called - * continuously to re-render the scene. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - */ - public static final int RENDERMODE_CONTINUOUSLY = 1; - /** * Standard View constructor. In order to render something, you * must call {@link #setRenderer} to register a renderer. @@ -104,7 +87,7 @@ public void setDetachedListener(@NonNull OnSurfaceViewDetachedListener detachedL *

  • {@link #onResume()} *
  • {@link #queueEvent(Runnable)} *
  • {@link #requestRender()} - *
  • {@link #setRenderMode(int)} + *
  • {@link #setRenderingRefreshMode(MapRenderer.RenderingRefreshMode)} * * * @param renderer the renderer to use to perform drawing. @@ -118,21 +101,20 @@ public void setRenderer(SurfaceViewMapRenderer renderer) { } /** - * Set the rendering mode. When renderMode is - * RENDERMODE_CONTINUOUSLY, the renderer is called - * repeatedly to re-render the scene. When renderMode - * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface - * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. + * Set the rendering refresh mode to CONTINUOUS or WHEN_DIRTY. + * Defaults to MapRenderer.RenderingRefreshMode.WHEN_DIRTY. + * The renderer is called repeatedly to re-render the scene in continuous mode otherwise + * the renderer is called when the surface is created, or when {@link #requestRender} is called. *

    - * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance + * Using WHEN_DIRTY can improve battery life and overall system performance * by allowing the GPU and CPU to idle when the view does not need to be updated. *

    * This method can only be called after {@link #setRenderer(SurfaceViewMapRenderer)} * - * @param renderMode one of the RENDERMODE_X constants + * @param mode one of the MapRenderer.RenderingRefreshMode constants */ - public void setRenderMode(int renderMode) { - renderThread.setRenderMode(renderMode); + public void setRenderingRefreshMode(MapRenderer.RenderingRefreshMode mode) { + renderThread.setRenderingRefreshMode(mode); } /** @@ -141,14 +123,14 @@ public void setRenderMode(int renderMode) { * * @return the current rendering mode. */ - public int getRenderMode() { - return renderThread.getRenderMode(); + public MapRenderer.RenderingRefreshMode getRenderingRefreshMode() { + return renderThread.getRenderingRefreshMode(); } /** * Request that the renderer render a frame. * This method is typically used when the render mode has been set to - * RENDERMODE_WHEN_DIRTY, so that frames are only rendered on demand. + * MapRenderer.RenderingRefreshMode.WHEN_DIRTY, so that frames are only rendered on demand. * May be called * from any thread. Must not be called before a renderer has been set. */ @@ -249,13 +231,13 @@ public void waitForEmpty() { protected void onAttachedToWindow() { super.onAttachedToWindow(); if (detached && (renderer != null)) { - int renderMode = RENDERMODE_CONTINUOUSLY; + MapRenderer.RenderingRefreshMode renderMode = MapRenderer.RenderingRefreshMode.WHEN_DIRTY; if (renderThread != null) { - renderMode = renderThread.getRenderMode(); + renderMode = renderThread.getRenderingRefreshMode(); } createRenderThread(); - if (renderMode != RENDERMODE_CONTINUOUSLY) { - renderThread.setRenderMode(renderMode); + if (renderMode != MapRenderer.RenderingRefreshMode.WHEN_DIRTY) { + renderThread.setRenderingRefreshMode(renderMode); } renderThread.start(); } @@ -289,7 +271,7 @@ abstract static class RenderThread extends Thread { width = 0; height = 0; requestRender = true; - renderMode = RENDERMODE_CONTINUOUSLY; + renderMode = MapRenderer.RenderingRefreshMode.WHEN_DIRTY; wantRenderNotification = false; } @@ -311,24 +293,21 @@ public void run() { protected boolean readyToDraw() { return (!paused) && hasSurface && (width > 0) && (height > 0) - && (requestRender || (renderMode == RENDERMODE_CONTINUOUSLY)); + && (requestRender || (renderMode == MapRenderer.RenderingRefreshMode.CONTINUOUS)); } public boolean ableToDraw() { return readyToDraw(); } - public void setRenderMode(int renderMode) { - if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { - throw new IllegalArgumentException("renderMode"); - } + public void setRenderingRefreshMode(MapRenderer.RenderingRefreshMode mode) { synchronized (renderThreadManager) { - this.renderMode = renderMode; + this.renderMode = mode; renderThreadManager.notifyAll(); } } - public int getRenderMode() { + public MapRenderer.RenderingRefreshMode getRenderingRefreshMode() { synchronized (renderThreadManager) { return renderMode; } @@ -503,7 +482,7 @@ public void waitForEmpty() { protected boolean waitingForSurface; protected int width; protected int height; - protected int renderMode; + protected MapRenderer.RenderingRefreshMode renderMode; protected boolean requestRender; protected boolean wantRenderNotification; protected boolean renderComplete; diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java index 468f7f6cdfe..99445e8e14a 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java @@ -112,4 +112,20 @@ public void queueEvent(Runnable runnable) { public void waitForEmpty() { surfaceView.waitForEmpty(); } + + /** + * {@inheritDoc} + */ + @Override + public void setRenderingRefreshMode(MapRenderer.RenderingRefreshMode mode) { + surfaceView.setRenderingRefreshMode(mode); + } + + /** + * {@inheritDoc} + */ + @Override + public MapRenderer.RenderingRefreshMode getRenderingRefreshMode() { + return surfaceView.getRenderingRefreshMode(); + } } \ No newline at end of file diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java index 1ec1b0b9fd3..64623ea3acf 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java @@ -132,4 +132,22 @@ public void onDestroy() { public boolean isTranslucentSurface() { return translucentSurface; } + + /** + * {@inheritDoc} + */ + @Override + public void setRenderingRefreshMode(MapRenderer.RenderingRefreshMode mode) { + throw new RuntimeException("setRenderingRefreshMode is not supported for TextureViewMapRenderer. " + + "Use SurfaceViewMapRenderer to set the rendering refresh mode."); + } + + /** + * {@inheritDoc} + */ + @Override + public MapRenderer.RenderingRefreshMode getRenderingRefreshMode() { + throw new RuntimeException("getRenderingRefreshMode is not supported for TextureViewMapRenderer. " + + "Use SurfaceViewMapRenderer to set the rendering refresh mode."); + } } diff --git a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java index 0a8f1a0d7d1..9d05530a912 100644 --- a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java +++ b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java @@ -6,8 +6,7 @@ import org.maplibre.android.maps.renderer.egl.EGLConfigChooser; import org.maplibre.android.maps.renderer.egl.EGLContextFactory; import org.maplibre.android.maps.renderer.egl.EGLWindowSurfaceFactory; - -import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY; +import org.maplibre.android.maps.renderer.MapRenderer; public class GLSurfaceViewMapRenderer extends SurfaceViewMapRenderer { @@ -20,7 +19,7 @@ public GLSurfaceViewMapRenderer(Context context, surfaceView.setEGLWindowSurfaceFactory(new EGLWindowSurfaceFactory()); surfaceView.setEGLConfigChooser(new EGLConfigChooser()); surfaceView.setRenderer(this); - surfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY); + surfaceView.setRenderingRefreshMode(MapRenderer.RenderingRefreshMode.WHEN_DIRTY); surfaceView.setPreserveEGLContextOnPause(true); } } \ No newline at end of file diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/MapLibreMapTest.kt b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/MapLibreMapTest.kt index 48a9334f90d..38e2467c931 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/MapLibreMapTest.kt +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/MapLibreMapTest.kt @@ -19,6 +19,7 @@ import org.maplibre.android.annotations.PolylineOptions import org.maplibre.android.exceptions.InvalidMarkerPositionException import org.maplibre.android.geometry.LatLng import org.maplibre.android.maps.MapLibreMap.InfoWindowAdapter +import org.maplibre.android.maps.renderer.MapRenderer.RenderingRefreshMode import org.maplibre.android.testapp.R import org.maplibre.android.testapp.activity.EspressoTest @@ -480,6 +481,19 @@ class MapLibreMapTest : EspressoTest() { } } + @Test + fun testRenderingRefreshMode() { + validateTestSetup() + rule.runOnUiThread { + mapView = rule.getActivity().findViewById(R.id.mapView) + // Default RenderingRefreshMode is WHEN_DIRTY + assertTrue(mapView.getRenderingRefreshMode() == RenderingRefreshMode.WHEN_DIRTY) + // Switch to CONTINUOUS rendering + mapView.setRenderingRefreshMode(RenderingRefreshMode.CONTINUOUS) + assertTrue(mapView.getRenderingRefreshMode() == RenderingRefreshMode.CONTINUOUS) + } + } + @Deprecated("remove this class when removing deprecated annotations") inner class MapLibreMapAction internal constructor(private val invokeViewAction: InvokeViewAction) : ViewAction { override fun getConstraints(): Matcher { diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt index c84c6b21550..0de98bb754c 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt @@ -437,6 +437,8 @@ class NativeMapViewTest : AppCenter() { class DummyRenderer(context: Context) : MapRenderer(context, null) { + private var renderingRefreshMode: RenderingRefreshMode = RenderingRefreshMode.WHEN_DIRTY + override fun requestRender() { // no-op } @@ -452,5 +454,13 @@ class NativeMapViewTest : AppCenter() { override fun getView(): View? { return null; } + + override fun setRenderingRefreshMode(mode : RenderingRefreshMode) { + renderingRefreshMode = mode + } + + override fun getRenderingRefreshMode() : RenderingRefreshMode{ + return renderingRefreshMode + } } } diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/activity/BaseTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/activity/BaseTest.java index 505e740a211..2faf4b74682 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/activity/BaseTest.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/activity/BaseTest.java @@ -50,7 +50,7 @@ public abstract class BaseTest extends AppCenter { @Before @CallSuper public void beforeTest() { - initialiseMap(); + initializeMap(); holdTestRunnerForStyleLoad(); } @@ -71,15 +71,15 @@ protected void validateTestSetup() { if (!MapLibre.isConnected()) { Timber.e("Not connected to the internet while running test"); } - assertNotNull("MapView isn't initialised", mapView); - assertNotNull("MapLibreMap isn't initialised", maplibreMap); - assertNotNull("Style isn't initialised", maplibreMap.getStyle()); + assertNotNull("MapView isn't initialized", mapView); + assertNotNull("MapLibreMap isn't initialized", maplibreMap); + assertNotNull("Style isn't initialized", maplibreMap.getStyle()); assertTrue("Style isn't fully loaded", maplibreMap.getStyle().isFullyLoaded()); } protected abstract Class getActivityClass(); - private void initialiseMap() { + private void initializeMap() { try { rule.runOnUiThread(() -> { mapView = rule.getActivity().findViewById(R.id.mapView); diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/DebugModeActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/DebugModeActivity.kt index 0690fdcb192..9a20d570c99 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/DebugModeActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/DebugModeActivity.kt @@ -11,6 +11,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton import org.maplibre.android.maps.* import org.maplibre.android.maps.MapLibreMap.OnCameraMoveListener import org.maplibre.android.maps.MapLibreMap.OnFpsChangedListener +import org.maplibre.android.maps.renderer.MapRenderer import org.maplibre.android.style.layers.Layer import org.maplibre.android.style.layers.Property import org.maplibre.android.style.layers.PropertyFactory @@ -29,6 +30,7 @@ open class DebugModeActivity : AppCompatActivity(), OnMapReadyCallback, OnFpsCha private var actionBarDrawerToggle: ActionBarDrawerToggle? = null private var currentStyleIndex = 0 private var isReportFps = true + private var isContinuousRendering = false private var fpsView: TextView? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -167,6 +169,13 @@ open class DebugModeActivity : AppCompatActivity(), OnMapReadyCallback, OnFpsCha mapView.setMaximumFps(30) } else if (itemId == R.id.menu_action_limit_to_60_fps) { mapView.setMaximumFps(60) + } else if (itemId == R.id.menu_action_toggle_continuous_rendering) { + isContinuousRendering = !isContinuousRendering + if (isContinuousRendering) { + mapView.setRenderingRefreshMode(MapRenderer.RenderingRefreshMode.CONTINUOUS) + } else { + mapView.setRenderingRefreshMode(MapRenderer.RenderingRefreshMode.WHEN_DIRTY) + } } return actionBarDrawerToggle!!.onOptionsItemSelected(item) || super.onOptionsItemSelected( item diff --git a/platform/android/MapLibreAndroidTestApp/src/main/res/menu/menu_debug.xml b/platform/android/MapLibreAndroidTestApp/src/main/res/menu/menu_debug.xml index fb6ac23ba57..9651987d60d 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/res/menu/menu_debug.xml +++ b/platform/android/MapLibreAndroidTestApp/src/main/res/menu/menu_debug.xml @@ -13,5 +13,9 @@ android:id="@+id/menu_action_limit_to_60_fps" android:title="Limit FPS to 60" app:showAsAction="never" /> +