Expose RENDERMODE_CONTINUOUSLY and RENDERMODE_WHEN_DIRTY (#2801)

This commit is contained in:
alasram
2024-09-17 12:59:58 -07:00
committed by GitHub
parent 0e0c8da72a
commit 77a464d335
11 changed files with 157 additions and 59 deletions

View File

@@ -140,10 +140,10 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
// 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 class MapView extends FrameLayout implements NativeMapView.ViewCallback {
}
}
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 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback {
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 class MapView extends FrameLayout implements NativeMapView.ViewCallback {
}
}
/**
* 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.
* <p>

View File

@@ -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 abstract class MapRenderer implements MapRendererScheduler {
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 abstract class MapRenderer implements MapRendererScheduler {
// Implement if needed
}
public abstract void setRenderingRefreshMode(RenderingRefreshMode mode);
public abstract RenderingRefreshMode getRenderingRefreshMode();
public void setOnFpsChangedListener(MapLibreMap.OnFpsChangedListener listener) {
onFpsChangedListener = listener;
}

View File

@@ -5,6 +5,7 @@ import android.graphics.PixelFormat;
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 abstract class MapLibreSurfaceView extends SurfaceView implements Surface
* <li>{@link #onResume()}
* <li>{@link #queueEvent(Runnable)}
* <li>{@link #requestRender()}
* <li>{@link #setRenderMode(int)}
* <li>{@link #setRenderingRefreshMode(MapRenderer.RenderingRefreshMode)}
* </ul>
*
* @param renderer the renderer to use to perform drawing.
@@ -118,21 +101,20 @@ public abstract class MapLibreSurfaceView extends SurfaceView implements Surface
}
/**
* 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.
* <p>
* 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.
* <p>
* 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 abstract class MapLibreSurfaceView extends SurfaceView implements Surface
*
* @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 abstract class MapLibreSurfaceView extends SurfaceView implements Surface
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 @@ public abstract class MapLibreSurfaceView extends SurfaceView implements Surface
width = 0;
height = 0;
requestRender = true;
renderMode = RENDERMODE_CONTINUOUSLY;
renderMode = MapRenderer.RenderingRefreshMode.WHEN_DIRTY;
wantRenderNotification = false;
}
@@ -311,24 +293,21 @@ public abstract class MapLibreSurfaceView extends SurfaceView implements Surface
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 abstract class MapLibreSurfaceView extends SurfaceView implements Surface
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;

View File

@@ -112,4 +112,20 @@ public class SurfaceViewMapRenderer extends MapRenderer {
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();
}
}

View File

@@ -132,4 +132,22 @@ public class TextureViewMapRenderer extends MapRenderer {
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.");
}
}

View File

@@ -6,8 +6,7 @@ import androidx.annotation.NonNull;
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 class GLSurfaceViewMapRenderer extends SurfaceViewMapRenderer {
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);
}
}

View File

@@ -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<View> {

View File

@@ -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
}
}
}

View File

@@ -50,7 +50,7 @@ public abstract class BaseTest extends AppCenter {
@Before
@CallSuper
public void beforeTest() {
initialiseMap();
initializeMap();
holdTestRunnerForStyleLoad();
}
@@ -71,15 +71,15 @@ public abstract class BaseTest extends AppCenter {
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);

View File

@@ -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

View File

@@ -13,5 +13,9 @@
android:id="@+id/menu_action_limit_to_60_fps"
android:title="Limit FPS to 60"
app:showAsAction="never" />
<item
android:id="@+id/menu_action_toggle_continuous_rendering"
android:title="Toggle continuous rendering"
app:showAsAction="never" />
</menu>