Skip to content

Commit

Permalink
FPS added to Android benchmark (#2944)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcristici authored Oct 21, 2024
1 parent 8b53102 commit 600abc6
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,75 +9,75 @@ import android.os.Handler
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.putJsonObject
import org.maplibre.android.BuildConfig.GIT_REVISION
import org.maplibre.android.camera.CameraUpdate
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
import org.maplibre.android.maps.MapLibreMap.CancelableCallback
import org.maplibre.android.testapp.BuildConfig
import org.maplibre.android.maps.MapView
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.BenchmarkInputData
import org.maplibre.android.testapp.utils.BenchmarkResult
import org.maplibre.android.testapp.utils.BenchmarkRun
import org.maplibre.android.testapp.utils.BenchmarkRunResult
import org.maplibre.android.testapp.utils.FrameTimeStore
import org.maplibre.android.testapp.utils.jsonPayload
import java.io.File
import java.util.ArrayList
import kotlin.collections.flatMap
import kotlin.collections.toTypedArray
import kotlin.coroutines.resume

suspend fun MapLibreMap.animateCameraSuspend(
cameraUpdate: CameraUpdate,
durationMs: Int
): Unit = suspendCancellableCoroutine { continuation ->
animateCamera(cameraUpdate, durationMs, object : CancelableCallback {
var resumed = false

override fun onCancel() {
continuation.cancel()
}

data class BenchmarkInputData(
val styleNames: List<String>,
val styleURLs: List<String>,
) {
init {
if (styleNames.size != styleURLs.size)
throw Error("Different size: styleNames=$styleNames, styleURLs=$styleURLs")
}
override fun onFinish() {
if (!resumed) {
resumed = true
continuation.resume(Unit)
}
}
})
}

@SuppressLint("NewApi")
fun jsonPayload(styleNames: List<String>, fpsResults: BenchmarkResults, encodingTimeResults: BenchmarkResults, renderingTimeResults: BenchmarkResults): JsonObject {
return buildJsonObject {
putJsonObject("resultsPerStyle") {
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()))
}
}
suspend fun MapView.setStyleSuspend(styleUrl: String): Unit =
suspendCancellableCoroutine { continuation ->
var listener: MapView.OnDidFinishLoadingStyleListener? = null

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()))
}
}
}
var resumed = false
listener = MapView.OnDidFinishLoadingStyleListener {
if (!resumed) {
resumed = true
listener?.let { removeOnDidFinishLoadingStyleListener(it) }
continuation.resume(Unit)
}
}
put("deviceManufacturer", JsonPrimitive(Build.MANUFACTURER))
put("model", JsonPrimitive(Build.MODEL))
put("renderer", JsonPrimitive(BuildConfig.FLAVOR))
put("debugBuild", JsonPrimitive(BuildConfig.DEBUG))
put("gitRevision", JsonPrimitive(GIT_REVISION))
addOnDidFinishLoadingStyleListener(listener)
getMapAsync { map -> map.setStyle(styleUrl) }

continuation.invokeOnCancellation {
removeOnDidFinishLoadingStyleListener(listener)
}

}
}

/**
* Benchmark using a [android.view.TextureView]
Expand All @@ -88,14 +88,6 @@ class BenchmarkActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private var handler: Handler? = null
private var delayed: Runnable? = null
private var fpsStore = FpsStore()
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
Expand Down Expand Up @@ -160,14 +152,16 @@ class BenchmarkActivity : AppCompatActivity() {
"Facebook Light",
"Americana",
"Protomaps Light",
"Versatiles Colorful"
"Versatiles Colorful",
"OpenFreeMap Bright"
),
styleURLs = listOf(
"https://maps.geo.us-east-2.amazonaws.com/maps/v0/maps/OpenDataStyle/style-descriptor?key=v1.public.eyJqdGkiOiI1NjY5ZTU4My0yNWQwLTQ5MjctODhkMS03OGUxOTY4Y2RhMzgifR_7GLT66TNRXhZJ4KyJ-GK1TPYD9DaWuc5o6YyVmlikVwMaLvEs_iqkCIydspe_vjmgUVsIQstkGoInXV_nd5CcmqRMMa-_wb66SxDdbeRDvmmkpy2Ow_LX9GJDgL2bbiCws0wupJPFDwWCWFLwpK9ICmzGvNcrPbX5uczOQL0N8V9iUvziA52a1WWkZucIf6MUViFRf3XoFkyAT15Ll0NDypAzY63Bnj8_zS8bOaCvJaQqcXM9lrbTusy8Ftq8cEbbK5aMFapXRjug7qcrzUiQ5sr0g23qdMvnKJQFfo7JuQn8vwAksxrQm6A0ByceEXSfyaBoVpFcTzEclxUomhY.NjAyMWJkZWUtMGMyOS00NmRkLThjZTMtODEyOTkzZTUyMTBi",
"https://external.xx.fbcdn.net/maps/vt/style/canterbury_1_0/?locale=en_US",
"https://americanamap.org/style.json",
"https://api.protomaps.com/styles/v2/light.json?key=e761cc7daedf832a",
"https://tiles.versatiles.org/assets/styles/colorful.json",
"https://tiles.openfreemap.org/styles/bright"
)
)
}
Expand Down Expand Up @@ -195,85 +189,64 @@ class BenchmarkActivity : AppCompatActivity() {

private fun setupMapView() {
mapView = findViewById<View>(R.id.mapView) as MapView
if (measureFrameTime) {
mapView.addOnDidFinishRenderingFrameListener { _: Boolean, frameEncodingTime: Double, frameRenderingTime: Double ->
encodingTimeStore.add(frameEncodingTime * 1e3)
renderingTimeStore.add(frameRenderingTime * 1e3)
}
}
mapView.getMapAsync { maplibreMap: MapLibreMap ->
maplibreMap.setStyle(inputData.styleURLs[0])
maplibreMap.setSwapBehaviorFlush(measureFrameTime)
if (!measureFrameTime) {
maplibreMap.setOnFpsChangedListener { fps: Double ->
fpsStore.add(fps)
val benchmarkResult = BenchmarkResult(arrayListOf())

lifecycleScope.launch {
val benchmarkRuns = inputData.styleNames.zip(inputData.styleURLs).flatMap { (styleName, styleUrl) ->
listOf(
BenchmarkRun(styleName, styleUrl, true),
BenchmarkRun(styleName, styleUrl, false)
)
}.toTypedArray()
val benchmarkIterations = 5
for (i in 0 until benchmarkIterations) {
for (benchmarkRun in benchmarkRuns) {
val benchmarkRunResult = doBenchmarkRun(maplibreMap, benchmarkRun)
val benchmarkPair = Pair(benchmarkRun, benchmarkRunResult)
benchmarkResult.runs.add(benchmarkPair)
println(jsonPayload(BenchmarkResult(arrayListOf(benchmarkPair))))
}
}
}

// Start an animation on the map as well
flyTo(maplibreMap, 0, 0,14.0)
println(jsonPayload(benchmarkResult))
storeResults(benchmarkResult)
benchmarkDone()
}
}
}

private fun flyTo(maplibreMap: MapLibreMap, place: Int, style: Int, zoom: Double) {
maplibreMap.animateCamera(
CameraUpdateFactory.newLatLngZoom(PLACES[place], zoom),
10000,
object : CancelableCallback {
override fun onCancel() {
delayed = Runnable {
delayed = null
flyTo(maplibreMap, place, style, zoom)
}
delayed?.let {
handler!!.postDelayed(it, 2000)
}
}
private suspend fun doBenchmarkRun(maplibreMap: MapLibreMap, benchmarkRun: BenchmarkRun): BenchmarkRunResult {
var numFrames = 0

override fun onFinish() {
if (place == PLACES.size - 1) { // done with tour
if (measureFrameTime) {
encodingTimeResults.addResult(
inputData.styleNames[style],
encodingTimeStore
)
encodingTimeStore.reset()

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])
flyTo(maplibreMap, 0, style + 1, zoom)
} else if (runsLeft > 0) { // start over
--runsLeft
maplibreMap.setStyle(inputData.styleURLs[0])
flyTo(maplibreMap, 0, 0, zoom)
} else {
benchmarkDone()
}
return
}
val encodingTimeStore = FrameTimeStore()
val renderingTimeStore = FrameTimeStore()

// continue with next place
flyTo(maplibreMap, place + 1, style, zoom)
}
}
)
maplibreMap.setSwapBehaviorFlush(benchmarkRun.syncRendering)

val listener = MapView.OnDidFinishRenderingFrameListener { _: Boolean, frameEncodingTime: Double, frameRenderingTime: Double ->
encodingTimeStore.add(frameEncodingTime * 1e3)
renderingTimeStore.add(frameRenderingTime * 1e3)
numFrames++;
}
mapView.addOnDidFinishRenderingFrameListener(listener)
mapView.setStyleSuspend(benchmarkRun.styleURL)
numFrames = 0

val startTime = System.nanoTime()

for (place in PLACES) {
maplibreMap.animateCameraSuspend(
CameraUpdateFactory.newLatLngZoom(place, 14.0),
10000
)
}
val endTime = System.nanoTime()
val fps = (numFrames * 1E9) / (endTime - startTime)

mapView.removeOnDidFinishRenderingFrameListener(listener)

return BenchmarkRunResult(fps, encodingTimeStore, renderingTimeStore)
}

override fun onStart() {
Expand Down Expand Up @@ -314,16 +287,14 @@ class BenchmarkActivity : AppCompatActivity() {
mapView.onLowMemory()
}

private fun storeResults() {
val payload = jsonPayload(inputData.styleNames, fpsResults, encodingTimeResults, renderingTimeResults)

private fun storeResults(benchmarkResult: BenchmarkResult) {
val payload = jsonPayload(benchmarkResult)
val dataDir = this.filesDir
val benchmarkResultsFile = File(dataDir, "benchmark_results.json")
benchmarkResultsFile.writeText(Json.encodeToString(payload))
}

private fun benchmarkDone() {
storeResults()
setResult(Activity.RESULT_OK)
finish()
}
Expand Down
Loading

0 comments on commit 600abc6

Please sign in to comment.