diff --git a/include/mbgl/gfx/renderable.hpp b/include/mbgl/gfx/renderable.hpp index dd95dc123..f6c707d6e 100644 --- a/include/mbgl/gfx/renderable.hpp +++ b/include/mbgl/gfx/renderable.hpp @@ -21,6 +21,12 @@ public: }; class Renderable { +public: + enum class SwapBehaviour { + NoFlush, + Flush + }; + protected: Renderable(const Size size_, std::unique_ptr resource_) : size(size_), diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.cpp b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.cpp index 2d8880a3e..c20b21ced 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.cpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.cpp @@ -22,6 +22,8 @@ public: backend.setViewport(0, 0, backend.getSize()); } + void swap() override { backend.swap(); } + private: AndroidRendererBackend& backend; }; @@ -62,5 +64,15 @@ void AndroidRendererBackend::markContextLost() { } } +void AndroidRendererBackend::setSwapBehavior(SwapBehaviour swapBehaviour_) { + swapBehaviour = swapBehaviour_; +} + +void AndroidRendererBackend::swap() { + if (swapBehaviour == SwapBehaviour::Flush) { + static_cast(getContext()).finish(); + } +} + } // namespace android } // namespace mbgl diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.hpp b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.hpp index cdd9df951..9b06a6cca 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.hpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.hpp @@ -19,6 +19,12 @@ public: void resizeFramebuffer(int width, int height); PremultipliedImage readFramebuffer(); + void setSwapBehavior(SwapBehaviour swapBehaviour); + void swap(); + +private: + SwapBehaviour swapBehaviour = SwapBehaviour::NoFlush; + // mbgl::gfx::RendererBackend implementation public: mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp index 4941e3316..3ea039d1f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp @@ -236,6 +236,10 @@ void MapRenderer::onSurfaceDestroyed(JNIEnv&) { resetRenderer(); } +void MapRenderer::setSwapBehaviorFlush(JNIEnv&, jboolean flush) { + backend->setSwapBehavior(flush ? gfx::Renderable::SwapBehaviour::Flush : gfx::Renderable::SwapBehaviour::NoFlush); +} + // Static methods // void MapRenderer::registerNative(jni::JNIEnv& env) { @@ -256,7 +260,8 @@ void MapRenderer::registerNative(jni::JNIEnv& env) { METHOD(&MapRenderer::onRendererReset, "nativeReset"), METHOD(&MapRenderer::onSurfaceCreated, "nativeOnSurfaceCreated"), METHOD(&MapRenderer::onSurfaceChanged, "nativeOnSurfaceChanged"), - METHOD(&MapRenderer::onSurfaceDestroyed, "nativeOnSurfaceDestroyed")); + METHOD(&MapRenderer::onSurfaceDestroyed, "nativeOnSurfaceDestroyed"), + METHOD(&MapRenderer::setSwapBehaviorFlush, "nativeSetSwapBehaviorFlush")); } MapRenderer& MapRenderer::getNativePeer(JNIEnv& env, const jni::Object& jObject) { diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp index 41f0259a5..25ffc6069 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp @@ -107,6 +107,8 @@ private: // Called on either Main or GL thread // void onRendererReset(JNIEnv&); + void setSwapBehaviorFlush(JNIEnv&, jboolean flush); + private: jni::WeakReference, jni::EnvAttachingDeleter> javaPeer; diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/native_map_view.cpp b/platform/android/MapboxGLAndroidSDK/src/cpp/native_map_view.cpp index 59f071914..988eb929a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/native_map_view.cpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/native_map_view.cpp @@ -189,11 +189,15 @@ void NativeMapView::onDidFinishRenderingFrame(MapObserver::RenderFrameStatus sta android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); - static auto onDidFinishRenderingFrame = javaClass.GetMethod(*_env, "onDidFinishRenderingFrame"); + static auto onDidFinishRenderingFrame = javaClass.GetMethod( + *_env, "onDidFinishRenderingFrame"); auto weakReference = javaPeer.get(*_env); if (weakReference) { - weakReference.Call( - *_env, onDidFinishRenderingFrame, (jboolean)(status.mode != MapObserver::RenderMode::Partial)); + weakReference.Call(*_env, + onDidFinishRenderingFrame, + (jboolean)(status.mode != MapObserver::RenderMode::Partial), + (jdouble)status.frameEncodingTime, + (jdouble)status.frameRenderingTime); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java index 72d4d3e37..7807c3d32 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java @@ -135,11 +135,11 @@ class MapChangeReceiver implements NativeMapView.StateCallback { } @Override - public void onDidFinishRenderingFrame(boolean fully) { + public void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) { try { if (!onDidFinishRenderingFrameList.isEmpty()) { for (MapView.OnDidFinishRenderingFrameListener listener : onDidFinishRenderingFrameList) { - listener.onDidFinishRenderingFrame(fully); + listener.onDidFinishRenderingFrame(fully, frameEncodingTime, frameRenderingTime); } } } catch (Throwable err) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapLibreMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapLibreMap.java index db5873653..47748d9bd 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapLibreMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapLibreMap.java @@ -103,6 +103,10 @@ public final class MapLibreMap { nativeMapView.triggerRepaint(); } + public void setSwapBehaviorFlush(boolean flush) { + nativeMapView.setSwapBehaviorFlush(flush); + } + void initialise(@NonNull Context context, @NonNull MapLibreMapOptions options) { transform.initialise(this, options); uiSettings.initialise(context, options); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapView.java index 8c927970c..d603b3ab6 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapView.java @@ -1012,7 +1012,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { * * @param fully true if all frames have been rendered, false if partially rendered */ - void onDidFinishRenderingFrame(boolean fully); + void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime); } /** @@ -1185,7 +1185,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { } @Override - public void onDidFinishRenderingFrame(boolean fully) { + public void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) { if (maplibreMap != null && maplibreMap.getStyle() != null && maplibreMap.getStyle().isFullyLoaded()) { renderCount++; if (renderCount == 3) { @@ -1357,7 +1357,7 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { } @Override - public void onDidFinishRenderingFrame(boolean fully) { + public void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) { if (maplibreMap != null) { maplibreMap.onUpdateFullyRendered(); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMap.java index 36da6e418..48993886b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMap.java @@ -239,6 +239,8 @@ interface NativeMap { void triggerRepaint(); + void setSwapBehaviorFlush(boolean flush); + // // Deprecated Annotations API // diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMapView.java index 5e1088c54..9caea12b7 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMapView.java @@ -1026,6 +1026,11 @@ final class NativeMapView implements NativeMap { nativeTriggerRepaint(); } + @Override + public void setSwapBehaviorFlush(boolean flush) { + mapRenderer.setSwapBehaviorFlush(flush); + } + @NonNull @Override public RectF getDensityDependantRectangle(final RectF rectangle) { @@ -1091,9 +1096,9 @@ final class NativeMapView implements NativeMap { } @Keep - private void onDidFinishRenderingFrame(boolean fully) { + private void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) { if (stateCallback != null) { - stateCallback.onDidFinishRenderingFrame(fully); + stateCallback.onDidFinishRenderingFrame(fully, frameEncodingTime, frameRenderingTime); } } @@ -1581,7 +1586,7 @@ final class NativeMapView implements NativeMap { void onWillStartRenderingFrame(); - void onDidFinishRenderingFrame(boolean fully); + void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime); void onWillStartRenderingMap(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java index 0e8fe35b4..7be402324 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java @@ -102,6 +102,10 @@ public abstract class MapRenderer implements MapRendererScheduler { } } + public void setSwapBehaviorFlush(boolean flush) { + nativeSetSwapBehaviorFlush(flush); + } + /** * May be called from any thread. *

@@ -134,12 +138,16 @@ public abstract class MapRenderer implements MapRendererScheduler { private native void nativeRender(); + private native void nativeSetSwapBehaviorFlush(boolean flush); + private long timeElapsed; private void updateFps() { long currentTime = System.nanoTime(); - double fps = 1E9 / ((currentTime - timeElapsed)); - onFpsChangedListener.onFpsChanged(fps); + if (timeElapsed > 0) { + double fps = 1E9 / ((currentTime - timeElapsed)); + onFpsChangedListener.onFpsChanged(fps); + } timeElapsed = currentTime; } diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt b/platform/android/MapboxGLAndroidSDK/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt index 9a41d4cc5..37d1d285a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt @@ -401,22 +401,22 @@ class MapChangeReceiverTest { mapChangeEventManager!!.addOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) - mapChangeEventManager!!?.onDidFinishRenderingFrame(true) - Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true) + mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0) + Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true, .0, .0) mapChangeEventManager!!.removeOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) - mapChangeEventManager!!?.onDidFinishRenderingFrame(true) - Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true) + mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0) + Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true,.0, .0) mapChangeEventManager!!.addOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) Logger.setLoggerDefinition(loggerDefinition) val exc: Exception = RuntimeException() Mockito.doThrow(exc).`when`(onDidFinishRenderingFrameListener) - ?.onDidFinishRenderingFrame(true) + ?.onDidFinishRenderingFrame(true, .0, .0) try { - mapChangeEventManager!!?.onDidFinishRenderingFrame(true) + mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0) Assert.fail("The exception should've been re-thrown.") } catch (throwable: RuntimeException) { Mockito.verify(loggerDefinition)?.e( @@ -427,9 +427,9 @@ class MapChangeReceiverTest { } val err: Error = ExecutionError("", Error()) Mockito.doThrow(err).`when`(onDidFinishRenderingFrameListener) - ?.onDidFinishRenderingFrame(true) + ?.onDidFinishRenderingFrame(true, .0, .0) try { - mapChangeEventManager!!?.onDidFinishRenderingFrame(true) + mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0) Assert.fail("The exception should've been re-thrown.") } catch (throwable: ExecutionError) { Mockito.verify(loggerDefinition)?.e( @@ -445,22 +445,22 @@ class MapChangeReceiverTest { mapChangeEventManager!!.addOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) - mapChangeEventManager!!?.onDidFinishRenderingFrame(false) - Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false) + mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0) + Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false, .0, .0) mapChangeEventManager!!.removeOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) - mapChangeEventManager!!?.onDidFinishRenderingFrame(false) - Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false) + mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0) + Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false, .0, .0) mapChangeEventManager!!.addOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) Logger.setLoggerDefinition(loggerDefinition) val exc: Exception = RuntimeException() Mockito.doThrow(exc).`when`(onDidFinishRenderingFrameListener) - ?.onDidFinishRenderingFrame(false) + ?.onDidFinishRenderingFrame(false, .0, .0) try { - mapChangeEventManager!!?.onDidFinishRenderingFrame(false) + mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0) Assert.fail("The exception should've been re-thrown.") } catch (throwable: RuntimeException) { Mockito.verify(loggerDefinition)?.e( @@ -471,9 +471,9 @@ class MapChangeReceiverTest { } val err: Error = ExecutionError("", Error()) Mockito.doThrow(err).`when`(onDidFinishRenderingFrameListener) - ?.onDidFinishRenderingFrame(false) + ?.onDidFinishRenderingFrame(false, .0, .0) try { - mapChangeEventManager!!?.onDidFinishRenderingFrame(false) + mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0) Assert.fail("The exception should've been re-thrown.") } catch (throwable: ExecutionError) { Mockito.verify(loggerDefinition)?.e( diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt index 3384f494c..40842838b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt @@ -60,7 +60,7 @@ class RemoveUnusedImagesTest : AppCenter() { mapView.addOnCanRemoveUnusedStyleImageListener { callbackLatch.countDown() maplibreMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 120.0), 8.0)) - mapView.addOnDidFinishRenderingFrameListener { + mapView.addOnDidFinishRenderingFrameListener{ _, _, _ -> assertNotNull(maplibreMap.style!!.getImage("small")) assertNotNull(maplibreMap.style!!.getImage("large")) latch.countDown() @@ -89,7 +89,7 @@ class RemoveUnusedImagesTest : AppCenter() { maplibreMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 120.0), 8.0)) // Wait for the next frame and check that images were removed from the style. - mapView.addOnDidFinishRenderingFrameListener { + mapView.addOnDidFinishRenderingFrameListener { _, _, _ -> if (maplibreMap.style!!.getImage("small") == null && maplibreMap.style!!.getImage("large") == null) { latch.countDown() } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt index f903d03c2..b67532a10 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt @@ -8,7 +8,6 @@ import android.os.Environment import android.os.Handler import android.view.View import android.view.WindowManager -import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -27,12 +26,14 @@ import org.maplibre.android.BuildConfig.GIT_REVISION import org.maplibre.android.camera.CameraUpdateFactory import org.maplibre.android.geometry.LatLng import org.maplibre.android.log.Logger +import org.maplibre.android.log.Logger.INFO import org.maplibre.android.maps.* import org.maplibre.android.maps.MapLibreMap.CancelableCallback import org.maplibre.android.testapp.BuildConfig import org.maplibre.android.testapp.R import org.maplibre.android.testapp.utils.FpsStore import org.maplibre.android.testapp.utils.BenchmarkResults +import org.maplibre.android.testapp.utils.FrameTimeStore import java.io.File import java.util.* @@ -52,13 +53,32 @@ data class BenchmarkInputData( * Prepares JSON payload that is sent to the API that collects benchmark results. * See https://github.com/maplibre/ci-runners */ -fun jsonPayload(results: BenchmarkResults): JsonObject { +@SuppressLint("NewApi") +fun jsonPayload(styleNames: List, fpsResults: BenchmarkResults, encodingTimeResults: BenchmarkResults, renderingTimeResults: BenchmarkResults): JsonObject { return buildJsonObject { putJsonObject("resultsPerStyle") { - for ((styleName, result) in results.resultsPerStyle) { - putJsonObject(styleName) { - put("avgFps", JsonPrimitive(result.map { it.average }.average())) - put("low1p", JsonPrimitive(result.map { it.low1p }.average())) + for (style in styleNames) { + putJsonObject(style) { + fpsResults.resultsPerStyle[style].let { results -> + if (results !== null) { + put("avgFps", JsonPrimitive(results.map { it.average }.average())) + put("low1pFps", JsonPrimitive(results.map { it.low1p }.average())) + } + } + + encodingTimeResults.resultsPerStyle[style].let { results -> + if (results !== null) { + put("avgEncodingTime", JsonPrimitive(results.map { it.average }.average())) + put("low1pEncodingTime", JsonPrimitive(results.map { it.low1p }.average())) + } + } + + renderingTimeResults.resultsPerStyle[style].let { results -> + if (results !== null) { + put("avgRenderingTime", JsonPrimitive(results.map { it.average }.average())) + put("low1pRenderingTime", JsonPrimitive(results.map { it.low1p }.average())) + } + } } } } @@ -77,12 +97,16 @@ class BenchmarkActivity : AppCompatActivity() { private val TAG = "BenchmarkActivity" private lateinit var mapView: MapView - private lateinit var maplibreMap: MapLibreMap private var handler: Handler? = null private var delayed: Runnable? = null private var fpsStore = FpsStore() - private var results = BenchmarkResults() + private var encodingTimeStore = FrameTimeStore() + private var renderingTimeStore = FrameTimeStore() + private var fpsResults = BenchmarkResults() + private var encodingTimeResults = BenchmarkResults() + private var renderingTimeResults = BenchmarkResults() private var runsLeft = 5 + private var measureFrameTime = true // the styles used for the benchmark // can be overridden adding with developer-config.xml @@ -160,6 +184,8 @@ class BenchmarkActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Logger.setVerbosity(INFO) + setContentView(R.layout.activity_benchmark) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -179,10 +205,20 @@ class BenchmarkActivity : AppCompatActivity() { private fun setupMapView(savedInstanceState: Bundle?) { mapView = findViewById(R.id.mapView) as MapView + if (measureFrameTime) { + mapView.addOnDidFinishRenderingFrameListener { fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double -> + encodingTimeStore.add(frameEncodingTime * 1e3) + renderingTimeStore.add(frameRenderingTime * 1e3) + } + } mapView.getMapAsync { maplibreMap: MapLibreMap -> - this@BenchmarkActivity.maplibreMap = maplibreMap maplibreMap.setStyle(inputData.styleURLs[0]) - setFpsView(maplibreMap) + maplibreMap.setSwapBehaviorFlush(measureFrameTime) + if (!measureFrameTime) { + maplibreMap.setOnFpsChangedListener { fps: Double -> + fpsStore.add(fps) + } + } // Start an animation on the map as well flyTo(maplibreMap, 0, 0,14.0) @@ -206,10 +242,29 @@ class BenchmarkActivity : AppCompatActivity() { override fun onFinish() { if (place == PLACES.size - 1) { // done with tour - results.addResult(inputData.styleNames[style], fpsStore) - fpsStore.reset() + if (measureFrameTime) { + encodingTimeResults.addResult( + inputData.styleNames[style], + encodingTimeStore + ) + encodingTimeStore.reset() - println("FPS results $results") + println("Encoding time results $encodingTimeResults") + + renderingTimeResults.addResult( + inputData.styleNames[style], + renderingTimeStore + ) + renderingTimeStore.reset() + + println("Rendering time results $renderingTimeResults") + } else { + fpsResults.addResult(inputData.styleNames[style], fpsStore) + fpsStore.reset() + + println("FPS results $fpsResults") + } + println("Benchmark ${jsonPayload(inputData.styleNames, fpsResults, encodingTimeResults, renderingTimeResults)}") if (style < inputData.styleURLs.size - 1) { // continue with next style maplibreMap.setStyle(inputData.styleURLs[style + 1]) @@ -231,12 +286,6 @@ class BenchmarkActivity : AppCompatActivity() { ) } - private fun setFpsView(maplibreMap: MapLibreMap) { - maplibreMap.setOnFpsChangedListener { fps: Double -> - fpsStore.add(fps) - } - } - override fun onStart() { super.onStart() mapView.onStart() @@ -284,7 +333,7 @@ class BenchmarkActivity : AppCompatActivity() { val client = OkHttpClient() - val payload = jsonPayload(results) + val payload = jsonPayload(inputData.styleNames, fpsResults, encodingTimeResults, renderingTimeResults) Logger.i(TAG, "Sending JSON payload to API: $payload") val request = Request.Builder() diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/MapFragmentActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/MapFragmentActivity.kt index 4a7f7f7a6..d18b91dc3 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/MapFragmentActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/MapFragmentActivity.kt @@ -77,7 +77,7 @@ class MapFragmentActivity : } } - override fun onDidFinishRenderingFrame(fully: Boolean) { + override fun onDidFinishRenderingFrame(fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double) { if (initialCameraAnimation && fully && maplibreMap != null) { maplibreMap.animateCamera( CameraUpdateFactory.newCameraPosition(CameraPosition.Builder().tilt(45.0).build()), diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt index 876617a7b..6c1a5f6d8 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt @@ -75,7 +75,7 @@ class SupportMapFragmentActivity : mapView.removeOnDidFinishRenderingFrameListener(this) } - override fun onDidFinishRenderingFrame(fully: Boolean) { + override fun onDidFinishRenderingFrame(fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double) { if (initialCameraAnimation && fully && maplibreMap != null) { maplibreMap.animateCamera( CameraUpdateFactory.newCameraPosition(CameraPosition.Builder().tilt(45.0).build()), diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt index cd062541b..7462750ce 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt @@ -20,7 +20,7 @@ class SnapshotActivity : AppCompatActivity(), OnMapReadyCallback { private lateinit var maplibreMap: MapLibreMap private val idleListener = object : MapView.OnDidFinishRenderingFrameListener { - override fun onDidFinishRenderingFrame(fully: Boolean) { + override fun onDidFinishRenderingFrame(fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double) { if (fully) { binding.mapView.removeOnDidFinishRenderingFrameListener(this) Logger.v(TAG, LOG_MESSAGE) diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt index 5f40e1bf8..854139668 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt @@ -58,7 +58,7 @@ class MapChangeActivity : AppCompatActivity() { mapView.addOnDidFinishLoadingMapListener(OnDidFinishLoadingMapListener { Timber.v("OnDidFinishLoadingMap") }) mapView.addOnDidFinishLoadingStyleListener(OnDidFinishLoadingStyleListener { Timber.v("OnDidFinishLoadingStyle") }) mapView.addOnDidFinishRenderingFrameListener( - OnDidFinishRenderingFrameListener { fully: Boolean -> + OnDidFinishRenderingFrameListener { fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double -> Timber.v( "OnDidFinishRenderingFrame: fully: %s", fully diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/utils/BenchmarkUtils.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/utils/BenchmarkUtils.kt index ef0b820c1..203457d6a 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/utils/BenchmarkUtils.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/utils/BenchmarkUtils.kt @@ -23,6 +23,27 @@ class FpsStore { } } +class FrameTimeStore { + private val timeValues = ArrayList(100000) + + fun add(time: Double) { + timeValues.add(time) + } + + fun reset() { + timeValues.clear() + } + + fun low1p(): Double { + timeValues.sort() + return timeValues.slice((99 * timeValues.size / 100)..timeValues.size - 1).average() + } + + fun average(): Double { + return timeValues.average() + } +} + /** * Result of single benchmark run */ @@ -41,4 +62,14 @@ data class BenchmarkResults( ) resultsPerStyle[styleName] = newResults } + + fun addResult(styleName: String, frameTimeStore: FrameTimeStore) { + val newResults = resultsPerStyle.getValue(styleName).plus( + BenchmarkResult( + frameTimeStore.average(), + frameTimeStore.low1p() + ) + ) + resultsPerStyle[styleName] = newResults + } } \ No newline at end of file diff --git a/platform/default/include/mbgl/gfx/headless_backend.hpp b/platform/default/include/mbgl/gfx/headless_backend.hpp index 1dc775531..e6a3c9e53 100644 --- a/platform/default/include/mbgl/gfx/headless_backend.hpp +++ b/platform/default/include/mbgl/gfx/headless_backend.hpp @@ -15,11 +15,6 @@ namespace gfx { // of readStillImage. class HeadlessBackend : public gfx::Renderable { public: - enum class SwapBehaviour { - NoFlush, - Flush - }; - // Factory. static std::unique_ptr Create(const Size size = {256, 256}, SwapBehaviour swapBehavior = SwapBehaviour::NoFlush, diff --git a/platform/default/src/mbgl/gl/headless_backend.cpp b/platform/default/src/mbgl/gl/headless_backend.cpp index 816c38e70..ccfe6936d 100644 --- a/platform/default/src/mbgl/gl/headless_backend.cpp +++ b/platform/default/src/mbgl/gl/headless_backend.cpp @@ -111,7 +111,7 @@ namespace gfx { template <> std::unique_ptr Backend::Create( - const Size size, gfx::HeadlessBackend::SwapBehaviour swapBehavior, const gfx::ContextMode contextMode) { + const Size size, gfx::Renderable::SwapBehaviour swapBehavior, const gfx::ContextMode contextMode) { return std::make_unique(size, swapBehavior, contextMode); }