Skip to content

Commit c7e4f6d

Browse files
jushgithub-actions[bot]
authored andcommitted
Fix frames not rendered when user sets refresh rate (#5802)
Fixes https://mapbox.atlassian.net/browse/MAPSAND-2287 The issue was visible in the following scenario: 1. User calls set user refresh rate to 60 (and display refresh rate is also 60) 2. A few frames are rendered, for example 30 3. User calls set user refresh rate to 30 Observed: - The `performPacing` method was returning false due to `drawFrameIndex` having lower values (due to the new refresh ratio) than `previousDrawnFrameIndex`. The fix is to reset the values every time the user or display refresh rate changes. cc @mapbox/maps-android GitOrigin-RevId: bdb1556e2603f34bc6ff88c706997a07e46e17e2
1 parent 1b39899 commit c7e4f6d

File tree

3 files changed

+112
-16
lines changed

3 files changed

+112
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Mapbox welcomes participation and contributions from everyone.
88

99
## Bug fixes 🐞
1010
* Fix potential ANR (Application Not Responding) issue when retrieving display refresh rate during map initialization by offloading the system call to a background thread with proper timeout and fallback handling.
11+
* Fix frames skipped when calling `mapView.setMaximumFps(..)` multiple times.
1112

1213
## Dependencies
1314
* Update Mapbox GeoJSON library to [v7.5.0](https://github.com/mapbox/mapbox-java/releases/tag/v7.5.0).

maps-sdk/src/main/java/com/mapbox/maps/renderer/FpsManager.kt

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ internal class FpsManager(
3939
}
4040
this.screenRefreshRate = screenRefreshRate
4141
screenRefreshPeriodNs = ONE_SECOND_NS / screenRefreshRate
42-
if (userRefreshRate != USER_DEFINED_REFRESH_RATE_NOT_SET) {
43-
userToScreenRefreshRateRatio = (userRefreshRate.toDouble() / screenRefreshRate).coerceIn(0.0, 1.0)
44-
logI(TAG, "User defined ratio is $userToScreenRefreshRateRatio")
45-
}
42+
updateUserToScreenRefreshRatio()
4643
if (LOG_STATISTICS) {
4744
logI(
4845
TAG,
@@ -56,13 +53,21 @@ internal class FpsManager(
5653
if (userRefreshRate != refreshRate) {
5754
userRefreshRate = refreshRate
5855
logI(TAG, "User set max FPS to $userRefreshRate")
59-
if (screenRefreshRate != SCREEN_METRICS_NOT_DEFINED) {
60-
userToScreenRefreshRateRatio = (userRefreshRate.toDouble() / screenRefreshRate).coerceIn(0.0, 1.0)
61-
logI(TAG, "User defined ratio is $userToScreenRefreshRateRatio")
62-
}
56+
updateUserToScreenRefreshRatio()
6357
}
6458
}
6559

60+
private fun updateUserToScreenRefreshRatio() {
61+
if (userRefreshRate == USER_DEFINED_REFRESH_RATE_NOT_SET || screenRefreshRate == SCREEN_METRICS_NOT_DEFINED) {
62+
logI(TAG, "userToScreenRefreshRateRatio is not set (userRefreshRate=$userRefreshRate, screenRefreshRate=$screenRefreshRate)")
63+
return
64+
}
65+
userToScreenRefreshRateRatio = (userRefreshRate.toDouble() / screenRefreshRate).coerceIn(0.0, 1.0)
66+
logI(TAG, "User defined ratio is $userToScreenRefreshRateRatio")
67+
// After changing the ratio we need to reset counters
68+
calculateFpsAndReset()
69+
}
70+
6671
/**
6772
* Return true if rendering should happen this frame and false otherwise.
6873
*
@@ -130,7 +135,7 @@ internal class FpsManager(
130135
previousFrameTimeNs = frameTimeNs
131136
// we always increase choreographer tick by one + add number of skipped frames for consistent results
132137
choreographerTicks += skippedNow + 1
133-
if (skippedNow > 0 && LOG_STATISTICS) {
138+
if (LOG_STATISTICS && skippedNow > 0) {
134139
logW(
135140
TAG,
136141
"Skipped $skippedNow VSYNC pulses since last actual render," +
@@ -173,7 +178,7 @@ internal class FpsManager(
173178
if (LOG_STATISTICS) {
174179
logI(
175180
TAG,
176-
"Performing pacing, current index=$drawnFrameIndex," +
181+
"Performing pacing, current index=$drawnFrameIndex, choreographerTicks=$choreographerTicks," +
177182
" previous drawn=$previousDrawnFrameIndex, proceed with rendering=${drawnFrameIndex > previousDrawnFrameIndex}"
178183
)
179184
}

maps-sdk/src/test/java/com/mapbox/maps/renderer/FpsManagerTest.kt

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class FpsManagerTest {
4040
pauseHandler()
4141
idleHandler(vsyncCount = 10) {
4242
assert(fpsManager.preRender(it))
43+
fpsManager.postRender()
4344
}
4445
}
4546

@@ -50,6 +51,7 @@ class FpsManagerTest {
5051
val frameRenderedPattern = mutableListOf<Boolean>()
5152
idleHandler(vsyncCount = 10) {
5253
frameRenderedPattern.add(fpsManager.preRender(it))
54+
fpsManager.postRender()
5355
}
5456
Assert.assertArrayEquals(
5557
arrayOf(
@@ -68,6 +70,7 @@ class FpsManagerTest {
6870
val frameRenderedPattern = mutableListOf<Boolean>()
6971
idleHandler(vsyncCount = vsyncCount) {
7072
frameRenderedPattern.add(fpsManager.preRender(it))
73+
fpsManager.postRender()
7174
}
7275
Assert.assertArrayEquals(
7376
Array(vsyncCount) { it == 8 || it == 17 },
@@ -83,6 +86,7 @@ class FpsManagerTest {
8386
val frameRenderedPattern = mutableListOf<Boolean>()
8487
idleHandler(vsyncCount = vsyncCount) {
8588
frameRenderedPattern.add(fpsManager.preRender(it))
89+
fpsManager.postRender()
8690
}
8791
Assert.assertArrayEquals(
8892
// 6 frames should be rendered in total during 2 seconds with 3 FPS
@@ -99,6 +103,7 @@ class FpsManagerTest {
99103
val frameRenderedPattern = mutableListOf<Boolean>()
100104
idleHandler(vsyncCount = vsyncCount) {
101105
frameRenderedPattern.add(fpsManager.preRender(it))
106+
fpsManager.postRender()
102107
}
103108
Assert.assertArrayEquals(
104109
Array(vsyncCount) { it == 59 || it == 119 },
@@ -114,11 +119,97 @@ class FpsManagerTest {
114119
val frameRenderedPattern = mutableListOf<Boolean>()
115120
idleHandler(vsyncCount = vsyncCount) {
116121
frameRenderedPattern.add(fpsManager.preRender(it))
122+
fpsManager.postRender()
123+
}
124+
Assert.assertArrayEquals(
125+
Array(vsyncCount) { true },
126+
frameRenderedPattern.toTypedArray()
127+
)
128+
}
129+
130+
@Test
131+
fun userRefreshRateSameAsScreenRefreshRateTest() {
132+
fpsManager.setUserRefreshRate(SCREEN_FPS)
133+
pauseHandler()
134+
val frameRenderedPattern = mutableListOf<Boolean>()
135+
idleHandler(vsyncCount = 5 * SCREEN_FPS) {
136+
frameRenderedPattern.add(fpsManager.preRender(it))
137+
fpsManager.postRender()
138+
}
139+
Assert.assertArrayEquals(
140+
Array(5 * SCREEN_FPS) { true },
141+
frameRenderedPattern.toTypedArray()
142+
)
143+
}
144+
145+
@Test
146+
fun `user refresh rate changes mid render with listener`() {
147+
val fpsValueArray = mutableListOf<Double>()
148+
val fpsChangedListener = OnFpsChangedListener {
149+
fpsValueArray.add(it)
150+
}
151+
fpsManager.fpsChangedListener = fpsChangedListener
152+
pauseHandler()
153+
val frameRenderedPattern = mutableListOf<Boolean>()
154+
val vsyncCount = 90
155+
156+
// First set user refresh rate to be the same as screen refresh rate
157+
fpsManager.setUserRefreshRate(SCREEN_FPS)
158+
idleHandler(vsyncCount = vsyncCount) {
159+
frameRenderedPattern.add(fpsManager.preRender(it))
160+
fpsManager.postRender()
117161
}
118162
Assert.assertArrayEquals(
119163
Array(vsyncCount) { true },
120164
frameRenderedPattern.toTypedArray()
121165
)
166+
Assert.assertEquals(1, fpsValueArray.size)
167+
Assert.assertEquals(SCREEN_FPS.toDouble(), fpsValueArray.last(), EPS)
168+
169+
frameRenderedPattern.clear()
170+
171+
// Now change the user refresh ratio to be half of the screen refresh rate
172+
fpsManager.setUserRefreshRate(SCREEN_FPS / 2)
173+
// That triggers a new listener with the FPS value for the remaining 30 frames out of the 90 above
174+
Assert.assertEquals(2, fpsValueArray.size)
175+
Assert.assertEquals(SCREEN_FPS.toDouble(), fpsValueArray.last(), EPS)
176+
177+
// Now render vsyncCount frames with the new user refresh rate
178+
idleHandler(vsyncCount = vsyncCount) {
179+
frameRenderedPattern.add(fpsManager.preRender(it))
180+
fpsManager.postRender()
181+
}
182+
Assert.assertArrayEquals(
183+
// We expect every other frame to be rendered (user SCREEN_FPS/2 FPS)
184+
Array(vsyncCount) { it % 2 != 0 },
185+
frameRenderedPattern.toTypedArray()
186+
)
187+
Assert.assertEquals(3, fpsValueArray.size)
188+
Assert.assertEquals(SCREEN_FPS / 2.0, fpsValueArray.last(), EPS)
189+
190+
frameRenderedPattern.clear()
191+
192+
// Reset user refresh rate to be the same as screen one
193+
fpsManager.setUserRefreshRate(SCREEN_FPS)
194+
// That triggers a new listener with the FPS value for the remaining 30 frames out of the 90 above
195+
Assert.assertEquals(4, fpsValueArray.size)
196+
Assert.assertEquals(SCREEN_FPS / 2.0, fpsValueArray.last(), EPS)
197+
198+
idleHandler(vsyncCount = vsyncCount) {
199+
frameRenderedPattern.add(fpsManager.preRender(it))
200+
fpsManager.postRender()
201+
}
202+
Assert.assertArrayEquals(
203+
Array(vsyncCount) { true },
204+
frameRenderedPattern.toTypedArray()
205+
)
206+
Assert.assertEquals(5, fpsValueArray.size)
207+
Assert.assertEquals(SCREEN_FPS.toDouble(), fpsValueArray.last(), EPS)
208+
209+
fpsManager.onSurfaceDestroyed()
210+
// One last FPS value for the remaining 30 frames out of the 90 above
211+
Assert.assertEquals(6, fpsValueArray.size)
212+
Assert.assertEquals(SCREEN_FPS.toDouble(), fpsValueArray.last(), EPS)
122213
}
123214

124215
@Test
@@ -184,12 +275,11 @@ class FpsManagerTest {
184275
private fun pauseHandler() = Shadows.shadowOf(Looper.getMainLooper()).pause()
185276

186277
private fun idleHandler(vsyncCount: Int, actionOnVsync: ((Long) -> Unit)) {
187-
for (i in 0 until vsyncCount) {
188-
Choreographer.getInstance().postFrameCallback { frameTimeNs ->
189-
actionOnVsync.invoke(frameTimeNs)
190-
}
191-
Shadows.shadowOf(Looper.getMainLooper())
192-
.idleFor(CHOREOGRAPHER_POST_FRAME_CALLBACK_DELAY_MS.toLong(), TimeUnit.MILLISECONDS)
278+
val choreographer = Choreographer.getInstance()
279+
val looper = Shadows.shadowOf(Looper.getMainLooper())
280+
repeat(vsyncCount) {
281+
choreographer.postFrameCallback(actionOnVsync)
282+
looper.idleFor(CHOREOGRAPHER_POST_FRAME_CALLBACK_DELAY_MS.toLong(), TimeUnit.MILLISECONDS)
193283
}
194284
}
195285

0 commit comments

Comments
 (0)