Skip to content

Commit bdb365b

Browse files
jushgithub-actions[bot]
authored andcommitted
Fix rare scenario where map render surface size was wrong (#9293)
There was a rare situation where if the surface changed like this: 1. First set it to width: 100, height: 200 2. Resize it to width: 300, height: 400 3. At this point if the render thread was busy the resize was not processed 4. Immediately set again width: 100, height: 200 5. At this point onSurfaceSizeChanged did not schedule a post to render thread because the size is still 100,200 6. Render thread was freed and could process the resize in step 2 but there's no task for step 4 Expected result would be that both resizes in step 2 and 4 should run. cc @mapbox/maps-android cc @mapbox/sdk-ci GitOrigin-RevId: 1a940b42e0b49ece1d86b6e4146532688ae7454c
1 parent 4490986 commit bdb365b

4 files changed

Lines changed: 63 additions & 25 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ Mapbox welcomes participation and contributions from everyone.
77
# main
88

99
## Features ✨ and improvements 🏁
10-
* Introduce experimental `queryRenderedRasterValues` API for querying the rendered raster array value at a point on the map.
10+
* Introduce experimental `queryRenderedRasterValues` API for querying the rendered raster array value at a point on the map.
11+
12+
## Bug fixes 🐞
13+
* Fix rare scenario where map render surface size was wrong.
1114

1215
# 11.18.0
1316

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

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
6969
internal var surface: Surface? = null
7070
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
7171
internal var eglSurface: EGLSurface
72+
73+
@RenderThread
7274
private var width: Int = 0
75+
@RenderThread
7376
private var height: Int = 0
7477

7578
private val widgetRenderer: MapboxWidgetRenderer
@@ -80,7 +83,6 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
8083
@Volatile
8184
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
8285
internal var awaitingNextVsync = false
83-
private var sizeChanged = false
8486
@Volatile
8587
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
8688
internal var paused = false
@@ -119,9 +121,10 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
119121
* Render thread should be treated as valid (prepared to render a map) when both flags are true.
120122
* Getter is thread-safe as this flag could be accessed from any thread.
121123
*/
122-
private val renderThreadPrepared get() = renderThreadPreparedLock.withLock {
123-
eglContextMadeCurrent && nativeRenderCreated
124-
}
124+
private val renderThreadPrepared
125+
get() = renderThreadPreparedLock.withLock {
126+
eglContextMadeCurrent && nativeRenderCreated
127+
}
125128
private var eglContextCreated = false
126129
private var renderNotSupported = false
127130

@@ -223,7 +226,7 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
223226
* Keep a runnable to [prepareRenderFrame] to avoid creating a new one on every frame.
224227
*/
225228
private val prepareRenderFrameRunnable: Runnable = Runnable {
226-
prepareRenderFrame()
229+
prepareRenderFrame(width = null, height = null, creatingSurface = false)
227230
}
228231
private fun postPrepareRenderFrame(delayMillis: Long = 0L) {
229232
renderHandlerThread.postDelayed(
@@ -265,10 +268,6 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
265268
nativeRenderCreated = true
266269
mapboxRenderer.createRenderer()
267270
logI(TAG, "Native renderer created.")
268-
mapboxRenderer.onSurfaceChanged(
269-
width = width,
270-
height = height
271-
)
272271
}
273272
return true
274273
}
@@ -336,12 +335,10 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
336335
return true
337336
}
338337

339-
private fun checkSurfaceSizeChanged() {
340-
if (sizeChanged) {
338+
@RenderThread
339+
private fun notifyRenderersSizeChanged(width: Int, height: Int) {
341340
mapboxRenderer.onSurfaceChanged(width = width, height = height)
342341
widgetRenderer.onSurfaceChanged(width = width, height = height)
343-
sizeChanged = false
344-
}
345342
}
346343

347344
private fun checkWidgetRender() {
@@ -470,18 +467,34 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
470467
}
471468
eglContextCreated = false
472469
if (tryRecreate) {
473-
setUpRenderThread(creatingSurface = true)
470+
if (setUpRenderThread(creatingSurface = true)) {
471+
notifyRenderersSizeChanged(width, height)
472+
}
474473
} else {
475474
surface?.release()
476475
}
477476
}
478477
}
479478

480-
private fun prepareRenderFrame(creatingSurface: Boolean = false) {
481-
// no need to do anything if we're waiting for next VSYNC already;
479+
/**
480+
* Responsible to prepare for rendering and schedule a new frame render (synchronized with the
481+
* display's refresh rate using [Choreographer]).
482+
* There are 3 main reasons to request a new frame:
483+
* 1. A new surface is available ([creatingSurface] flag is true and [width] and [height] are given).
484+
* 2. The surface was resized (new [width] and [height] are given).
485+
* 3. Normal render pass ([creatingSurface] flag is false and no sizes are given).
486+
*/
487+
@RenderThread
488+
private fun prepareRenderFrame(
489+
width: Int?,
490+
height: Int?,
491+
creatingSurface: Boolean
492+
) {
493+
// no need to do anything if we're already waiting for next VSYNC (`doFrame`);
482494
// however if Android has sent us new surface - we must proceed up to `setUpRenderThread` or
483495
// otherwise main thread will end up having deadlock
484-
if (awaitingNextVsync && !creatingSurface) {
496+
// We also should go through if we've new sizes to set
497+
if (awaitingNextVsync && !creatingSurface && width == null && height == null) {
485498
return
486499
}
487500
// Check first if we have to stop rendering at all (even if there was no EGL config) and cleanup EGL.
@@ -515,19 +528,21 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
515528
}
516529
}
517530
checkWidgetRender()
518-
checkSurfaceSizeChanged()
531+
if (width != null && height != null && renderThreadPrepared) {
532+
notifyRenderersSizeChanged(width, height)
533+
}
519534
Choreographer.getInstance().postFrameCallback(this)
520535
awaitingNextVsync = true
521536
}
522537

523538
@UiThread
524539
fun onSurfaceSizeChanged(width: Int, height: Int) {
525-
if (this.width != width || this.height != height) {
526-
renderHandlerThread.post {
540+
renderHandlerThread.post {
541+
if (this.width != width || this.height != height) {
527542
this@MapboxRenderThread.width = width
528543
this@MapboxRenderThread.height = height
529-
sizeChanged = true
530-
prepareRenderFrame()
544+
// Schedule a new frame to draw map with the new size
545+
prepareRenderFrame(width = width, height = height, creatingSurface = false)
531546
}
532547
}
533548
}
@@ -586,8 +601,8 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
586601
}
587602
this.width = width
588603
this.height = height
589-
widgetRenderer.onSurfaceChanged(width = width, height = height)
590-
prepareRenderFrame(creatingSurface = true)
604+
// Make sure we initialize renderer and schedule next frame to draw map.
605+
prepareRenderFrame(width, height, creatingSurface = true)
591606
}
592607

593608
@UiThread
@@ -604,6 +619,8 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
604619
surfaceProcessingLock.withLock {
605620
if (renderHandlerThread.isRunning) {
606621
renderHandlerThread.post {
622+
this.width = width
623+
this.height = height
607624
processAndroidSurface(surface, width, height)
608625
}
609626
logI(TAG, "onSurfaceCreated: waiting Android surface to be processed...")

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package com.mapbox.maps.renderer
1111
AnnotationTarget.FUNCTION,
1212
AnnotationTarget.PROPERTY_GETTER,
1313
AnnotationTarget.PROPERTY_SETTER,
14+
AnnotationTarget.PROPERTY,
1415
AnnotationTarget.CONSTRUCTOR,
1516
AnnotationTarget.CLASS,
1617
)

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,23 @@ class MapboxRenderThreadTest {
200200
}
201201
}
202202

203+
@Test
204+
fun onSurfaceSizeChangedWhileBusyTest() {
205+
val mapboxRenderer: MapboxRenderer = mockk(relaxUnitFun = true)
206+
initRenderThread(mapboxRenderer)
207+
provideValidSurface()
208+
pauseHandler()
209+
mapboxRenderThread.onSurfaceSizeChanged(2, 2)
210+
mapboxRenderThread.onSurfaceSizeChanged(1, 1)
211+
idleHandler()
212+
verifyOrder {
213+
mapboxRenderer.createRenderer()
214+
mapboxRenderer.onSurfaceChanged(1, 1)
215+
mapboxRenderer.onSurfaceChanged(2, 2)
216+
mapboxRenderer.onSurfaceChanged(1, 1)
217+
}
218+
}
219+
203220
@Test
204221
fun onSurfaceSizeChangedSameSizeTest() {
205222
initRenderThread()

0 commit comments

Comments
 (0)