Skip to content

Commit 29aac72

Browse files
authored
Fix artifacts when ContextMode.SHARED is used (#2299)
* Fix artifacts when ContextMode.SHARED is used * Fix crash on surface recreation, add test * Handle surface resize more properly * Fix incorrect widget position and scale when resizing the drawing surface.
1 parent e967379 commit 29aac72

File tree

13 files changed

+126
-61
lines changed

13 files changed

+126
-61
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Mapbox welcomes participation and contributions from everyone.
44

5+
# 10.16.7
6+
## Bug fixes 🐞
7+
* Fix map being pixelated on some devices when `ContextMode.SHARED` is used (e.g. in AndroidAuto extension).
8+
* Fix incorrect widget position and scale when resizing the drawing surface.
59

610
# 10.16.6 March 04, 2024
711
## Bug fixes 🐞
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.mapbox.maps.testapp.integration.surface
2+
3+
import android.view.KeyEvent
4+
import androidx.test.ext.junit.rules.ActivityScenarioRule
5+
import androidx.test.ext.junit.runners.AndroidJUnit4
6+
import androidx.test.filters.LargeTest
7+
import com.mapbox.maps.testapp.examples.SurfaceActivity
8+
import com.mapbox.maps.testapp.integration.BaseIntegrationTest
9+
import com.mapbox.maps.testapp.integration.launchActivity
10+
import org.junit.Rule
11+
import org.junit.Test
12+
import org.junit.runner.RunWith
13+
14+
/**
15+
* Regression test that validates reopening an Activity with a surface using ContextMode.SHARED
16+
*/
17+
@RunWith(AndroidJUnit4::class)
18+
class SurfaceReopenTest : BaseIntegrationTest() {
19+
20+
@get:Rule
21+
var activityRule: ActivityScenarioRule<SurfaceActivity> = ActivityScenarioRule(
22+
SurfaceActivity::class.java
23+
)
24+
25+
@Test
26+
@LargeTest
27+
fun reopenSurfaceActivity() {
28+
activityRule.scenario.onActivity {
29+
device.launchActivity(it, SurfaceActivity::class.java)
30+
device.waitForIdle()
31+
}
32+
for (i in 0 until testRepeatCount) {
33+
device.waitForIdle()
34+
device.pressHome()
35+
device.waitForIdle()
36+
// double click restores last opened application
37+
device.pressKeyCode(KeyEvent.KEYCODE_APP_SWITCH)
38+
device.pressKeyCode(KeyEvent.KEYCODE_APP_SWITCH)
39+
// some time to set things up
40+
Thread.sleep(2_000L)
41+
}
42+
}
43+
}

sdk/src/main/java/com/mapbox/maps/MapSurface.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ class MapSurface : MapPluginProviderDelegate, MapControllable {
4949
this.context = context
5050
this.surface = surface
5151
this.mapInitOptions = mapInitOptions
52-
this.renderer = MapboxSurfaceRenderer(mapInitOptions.antialiasingSampleCount)
52+
this.renderer = MapboxSurfaceRenderer(
53+
mapInitOptions.antialiasingSampleCount,
54+
mapInitOptions.mapOptions.contextMode ?: ContextMode.UNIQUE
55+
)
5356
this.mapController = MapController(renderer, mapInitOptions).apply {
5457
initializePlugins(mapInitOptions)
5558
}

sdk/src/main/java/com/mapbox/maps/MapView.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,18 @@ open class MapView : FrameLayout, MapPluginProviderDelegate, MapControllable {
105105
} else {
106106
SurfaceView(context, attrs)
107107
}
108+
val contextMode = resolvedMapInitOptions.mapOptions.contextMode ?: ContextMode.UNIQUE
108109
mapController = MapController(
109110
when (view) {
110111
is SurfaceView -> MapboxSurfaceHolderRenderer(
111112
view.holder,
112-
resolvedMapInitOptions.antialiasingSampleCount
113+
resolvedMapInitOptions.antialiasingSampleCount,
114+
contextMode
113115
)
114116
is TextureView -> MapboxTextureViewRenderer(
115117
view,
116-
resolvedMapInitOptions.antialiasingSampleCount
118+
resolvedMapInitOptions.antialiasingSampleCount,
119+
contextMode
117120
)
118121
else -> throw IllegalArgumentException("Provided view has to be a texture or a surface.")
119122
},

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.view.Choreographer
99
import android.view.Surface
1010
import androidx.annotation.*
1111
import com.mapbox.common.MapboxSDKCommon
12+
import com.mapbox.maps.ContextMode
1213
import com.mapbox.maps.logE
1314
import com.mapbox.maps.logI
1415
import com.mapbox.maps.logW
@@ -55,6 +56,7 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
5556
private val widgetRenderer: MapboxWidgetRenderer
5657
private var widgetRenderCreated = false
5758
private val widgetTextureRenderer: TextureRenderer
59+
private val contextMode: ContextMode
5860

5961
@Volatile
6062
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@@ -133,13 +135,15 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
133135
mapboxWidgetRenderer: MapboxWidgetRenderer,
134136
translucentSurface: Boolean,
135137
antialiasingSampleCount: Int,
138+
contextMode: ContextMode,
136139
) {
137140
this.translucentSurface = translucentSurface
138141
this.mapboxRenderer = mapboxRenderer
139142
this.widgetRenderer = mapboxWidgetRenderer
140143
this.eglCore = EGLCore(translucentSurface, antialiasingSampleCount)
141144
this.eglSurface = eglCore.eglNoSurface
142145
this.widgetTextureRenderer = TextureRenderer()
146+
this.contextMode = contextMode
143147
renderHandlerThread = RenderHandlerThread()
144148
val handler = renderHandlerThread.start()
145149
fpsManager = FpsManager(handler)
@@ -162,6 +166,7 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
162166
this.fpsManager = fpsManager
163167
this.widgetTextureRenderer = widgetTextureRenderer
164168
this.eglSurface = eglCore.eglNoSurface
169+
this.contextMode = ContextMode.UNIQUE
165170
}
166171

167172
private fun postPrepareRenderFrame(delayMillis: Long = 0L) {
@@ -315,9 +320,12 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
315320
postPrepareRenderFrame()
316321
return
317322
}
323+
if (contextMode == ContextMode.SHARED) {
324+
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT or GLES20.GL_STENCIL_BUFFER_BIT)
325+
}
318326
if (widgetRenderer.hasWidgets()) {
319-
if (widgetRenderer.needTextureUpdate) {
320-
widgetRenderer.updateTexture()
327+
if (widgetRenderer.needRender) {
328+
widgetRenderer.renderToFrameBuffer()
321329
eglCore.makeCurrent(eglSurface)
322330
}
323331

sdk/src/main/java/com/mapbox/maps/renderer/MapboxSurfaceHolderRenderer.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package com.mapbox.maps.renderer
22

33
import android.view.SurfaceHolder
44
import androidx.annotation.VisibleForTesting
5+
import com.mapbox.maps.ContextMode
56

67
internal class MapboxSurfaceHolderRenderer : MapboxSurfaceRenderer, SurfaceHolder.Callback {
78

8-
constructor(surfaceHolder: SurfaceHolder, antialiasingSampleCount: Int) : super(antialiasingSampleCount) {
9+
constructor(surfaceHolder: SurfaceHolder, antialiasingSampleCount: Int, contextMode: ContextMode) : super(antialiasingSampleCount, contextMode) {
910
surfaceHolder.addCallback(this)
1011
}
1112

sdk/src/main/java/com/mapbox/maps/renderer/MapboxSurfaceRenderer.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package com.mapbox.maps.renderer
22

33
import android.view.Surface
44
import androidx.annotation.VisibleForTesting
5+
import com.mapbox.maps.ContextMode
56

67
internal open class MapboxSurfaceRenderer : MapboxRenderer {
78

89
private var createSurface = false
910

1011
override val widgetRenderer: MapboxWidgetRenderer
1112

12-
constructor(antialiasingSampleCount: Int) {
13+
constructor(antialiasingSampleCount: Int, contextMode: ContextMode) {
1314
widgetRenderer = MapboxWidgetRenderer(
1415
antialiasingSampleCount = antialiasingSampleCount,
1516
)
@@ -18,6 +19,7 @@ internal open class MapboxSurfaceRenderer : MapboxRenderer {
1819
mapboxWidgetRenderer = widgetRenderer,
1920
translucentSurface = false,
2021
antialiasingSampleCount = antialiasingSampleCount,
22+
contextMode = contextMode
2123
)
2224
}
2325

sdk/src/main/java/com/mapbox/maps/renderer/MapboxTextureViewRenderer.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import android.graphics.SurfaceTexture
44
import android.view.Surface
55
import android.view.TextureView
66
import androidx.annotation.VisibleForTesting
7+
import com.mapbox.maps.ContextMode
78

89
internal class MapboxTextureViewRenderer : MapboxRenderer, TextureView.SurfaceTextureListener {
910

1011
override val widgetRenderer: MapboxWidgetRenderer
1112

12-
constructor(textureView: TextureView, antialiasingSampleCount: Int) {
13+
constructor(textureView: TextureView, antialiasingSampleCount: Int, contextMode: ContextMode) {
1314
val widgetRenderer = MapboxWidgetRenderer(
1415
antialiasingSampleCount = antialiasingSampleCount,
1516
)
@@ -19,6 +20,7 @@ internal class MapboxTextureViewRenderer : MapboxRenderer, TextureView.SurfaceTe
1920
mapboxWidgetRenderer = widgetRenderer,
2021
translucentSurface = true,
2122
antialiasingSampleCount = antialiasingSampleCount,
23+
contextMode = contextMode,
2224
)
2325
textureView.let {
2426
it.isOpaque = false

sdk/src/main/java/com/mapbox/maps/renderer/MapboxWidgetRenderer.kt

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package com.mapbox.maps.renderer
22

33
import android.opengl.GLES20
4+
import com.mapbox.maps.MapboxExperimental
45
import com.mapbox.maps.logE
56
import com.mapbox.maps.renderer.egl.EGLCore
67
import com.mapbox.maps.renderer.widget.Widget
78
import java.util.concurrent.CopyOnWriteArraySet
89
import javax.microedition.khronos.egl.EGLContext
910
import javax.microedition.khronos.egl.EGLSurface
1011

12+
@OptIn(MapboxExperimental::class)
1113
internal class MapboxWidgetRenderer(
1214
private val antialiasingSampleCount: Int,
1315
) {
@@ -24,7 +26,7 @@ internal class MapboxWidgetRenderer(
2426
private var width = 0
2527
private var height = 0
2628

27-
val needTextureUpdate: Boolean
29+
val needRender: Boolean
2830
get() = widgets.any { it.renderer.needRender }
2931

3032
fun hasWidgets() = widgets.isNotEmpty()
@@ -51,12 +53,9 @@ internal class MapboxWidgetRenderer(
5153
widgets.forEach { it.renderer.onSurfaceChanged(width, height) }
5254
}
5355

54-
private fun attachTexture() {
55-
if (textures[0] != 0) {
56-
GLES20.glDeleteTextures(textures.size, textures, 0)
57-
}
56+
private fun prepareFrameBufferWithTexture() {
57+
GLES20.glGenFramebuffers(1, framebuffers, 0)
5858
GLES20.glGenTextures(1, textures, 0)
59-
6059
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0])
6160
GLES20.glTexParameterf(
6261
GLES20.GL_TEXTURE_2D,
@@ -89,23 +88,26 @@ internal class MapboxWidgetRenderer(
8988
GLES20.GL_UNSIGNED_BYTE,
9089
null
9190
)
91+
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
92+
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffers[0])
9293
GLES20.glFramebufferTexture2D(
9394
GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
9495
GLES20.GL_TEXTURE_2D, textures[0], 0
9596
)
96-
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
97+
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)
9798
}
9899

99100
fun release() {
101+
releaseAll(resizing = false)
102+
}
103+
104+
private fun releaseAll(resizing: Boolean) {
100105
val eglCore = this.eglCore
101106
val eglSurface = this.eglSurface
102107
if (eglCore != null) {
103108
if (eglSurface != null && eglSurface != eglCore.eglNoSurface) {
104109
eglCore.makeCurrent(eglSurface)
105-
106-
GLES20.glDeleteFramebuffers(framebuffers.size, framebuffers, 0)
107-
GLES20.glDeleteTextures(textures.size, textures, 0)
108-
110+
deleteFrameBufferWithTexture()
109111
// Do not clear widgets here, or they will be lost if Activity is paused/resumed.
110112
widgets.forEach {
111113
it.renderer.release()
@@ -117,52 +119,50 @@ internal class MapboxWidgetRenderer(
117119
eglCore.release()
118120
}
119121
this.eglSurface = null
120-
this.eglCore = null
122+
if (!resizing) {
123+
this.eglCore = null
124+
}
121125
this.eglContextCreated = false
122126
}
123127

124-
fun updateTexture() {
125-
if (needTextureUpdate) {
126-
checkSizeChanged()
127-
checkEgl()
128-
val eglCore = this.eglCore
129-
val eglSurface = this.eglSurface
130-
if (eglCore != null && eglSurface != null && eglSurface != eglCore.eglNoSurface) {
131-
eglCore.makeCurrent(eglSurface)
132-
bindFramebuffer()
133-
attachTexture()
134-
widgets.forEach {
135-
it.renderer.render()
136-
}
137-
unbindFramebuffer()
138-
}
128+
fun renderToFrameBuffer() {
129+
checkSizeChanged()
130+
checkEgl()
131+
val eglCore = this.eglCore
132+
val eglSurface = this.eglSurface
133+
if (eglCore == null || eglSurface == null || eglSurface == eglCore.eglNoSurface) {
134+
return
139135
}
136+
eglCore.makeCurrent(eglSurface)
137+
if (!hasTexture()) {
138+
prepareFrameBufferWithTexture()
139+
}
140+
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffers[0])
141+
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT or GLES20.GL_STENCIL_BUFFER_BIT)
142+
widgets.forEach {
143+
it.renderer.render()
144+
}
145+
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)
146+
}
147+
148+
private fun deleteFrameBufferWithTexture() {
149+
GLES20.glDeleteFramebuffers(framebuffers.size, framebuffers, 0)
150+
GLES20.glDeleteTextures(textures.size, textures, 0)
151+
framebuffers[0] = 0
152+
textures[0] = 0
140153
}
141154

142155
private fun checkSizeChanged() {
143156
if (sizeChanged) {
144157
val eglCore = this.eglCore
145158
val eglSurface = this.eglSurface
146159
if (eglCore != null && eglSurface != null && eglSurface != eglCore.eglNoSurface) {
147-
eglCore.releaseSurface(eglSurface)
148-
this.eglSurface = eglCore.eglNoSurface
160+
releaseAll(resizing = true)
149161
}
150-
151162
sizeChanged = false
152163
}
153164
}
154165

155-
private fun unbindFramebuffer() {
156-
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)
157-
}
158-
159-
private fun bindFramebuffer() {
160-
if (framebuffers[0] == 0) {
161-
GLES20.glGenFramebuffers(1, framebuffers, 0)
162-
}
163-
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffers[0])
164-
}
165-
166166
private fun checkEgl() {
167167
val eglSurface = this.eglSurface
168168
val eglCore = this.eglCore
@@ -185,7 +185,7 @@ internal class MapboxWidgetRenderer(
185185

186186
if (eglSurface == null || eglSurface == eglCore.eglNoSurface) {
187187
this.eglSurface = eglCore.createOffscreenSurface(width = width, height = height)
188-
if (eglSurface == eglCore.eglNoSurface) {
188+
if (this.eglSurface == eglCore.eglNoSurface) {
189189
logE(TAG, "Widget offscreen surface was not configured, please check logs above.")
190190
return
191191
}

sdk/src/main/java/com/mapbox/maps/renderer/egl/EGLCore.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ internal class EGLCore(
158158
if (eglCreatePbufferSurfaceError != null || eglSurface == null) {
159159
return eglNoSurface
160160
}
161+
logI(TAG, "Created PBuffer, w = $width, h = $height")
161162
return eglSurface
162163
}
163164

0 commit comments

Comments
 (0)