Commit f64fb09
Reset stale texture ID in BitmapWidgetRenderer after glDeleteTextures (#10780)
## Summary
Fixes https://mapbox.atlassian.net/browse/MAPSAND-2215
- Stale texture ID reuse in `BitmapWidgetRenderer` causing rendering
artifacts on Android Auto after surface recreation
- Adds `textures[0] = 0` in `release()` after `glDeleteTextures` to
prevent the old ID from being reused on re-attach
- New unit tests in `BitmapWidgetRendererTest.kt` verifying texture
reallocation and `needRender` state after release
## Problem
Customers report intermittent rendering artifacts and reversed/large
bitmaps on Android Auto when using `BitmapWidget` (e.g., speedometer).
Reported on multiple devices (OnePlus Nord 4, Samsung Galaxy S25, Pixel
7, Fairphone 5) all running Android 15, Maps SDK 11.6.0.
### Root Cause
`BitmapWidgetRenderer.release()` deletes the GL texture but does **not**
reset `textures[0] = 0`:
```kotlin
// BEFORE fix
override fun release() {
lock.withLock {
if (program != 0) {
GLES20.glDeleteTextures(textures.size, textures, 0)
// BUG: textures[0] still holds the old (now freed) ID!
GLES20.glDeleteProgram(program)
program = 0
}
needRender = false
}
}
```
Meanwhile, `textureFromBitmapIfChanged()` only allocates a new texture
when `textures[0] == 0`:
```kotlin
if (textures[0] == 0) { // skipped when textures[0] still holds stale ID
GLES20.glGenTextures(1, textures, 0)
}
```
### What Happens on Android Auto
The widget renderer and native map renderer **share a texture
namespace** (via a shared EGL context), so texture IDs can collide
between them.
```mermaid
sequenceDiagram
participant AA as Android Auto
participant W as BitmapWidgetRenderer
participant GPU as GPU Driver
participant Map as Native Map Renderer
Note over AA: Car head unit connected
AA->>W: prepare() + render()
W->>GPU: glGenTextures → ID 42
W->>GPU: texImage2D(42, speedometer bitmap)
Note over W: textures[0] = 42
Note over AA: Surface destroyed (disconnect/reconnect)
AA->>W: release()
W->>GPU: glDeleteTextures(42)
Note over W,GPU: ID 42 freed, but textures[0] still holds 42!
Note over Map: Native renderer allocates textures...
Map->>GPU: glGenTextures → ID 42 (recycled!)
Map->>GPU: texImage2D(42, map tile bitmap)
Note over AA: New surface created
AA->>W: prepare() + render()
Note over W: textures[0] = 42 ≠ 0, skip glGenTextures
W->>GPU: glBindTexture(42)
W->>GPU: texImage2D(42, speedometer bitmap)
Note over GPU: OVERWRITES the map tile texture!
Note over GPU: Frame composited: map tile shows<br/>speedometer bitmap = giant reversed image
```
The artifact appears as a **large reversed bitmap** because:
- **Large**: The speedometer bitmap overwrites a map tile texture, which
is drawn at map-tile scale (covering a large screen area)
- **Reversed**: Map tile UV coordinates differ from widget UV
coordinates, so the bitmap appears flipped
### Why Android Auto Specifically?
Android Auto has significantly more surface lifecycle churn than a
normal `MapView`:
- Connecting/disconnecting from the car head unit destroys and recreates
the surface
- Google's `SurfaceContainer` can call `onSurfaceAvailable` without a
prior `onSurfaceDestroyed` (acknowledged in `CarMapSurfaceOwner.kt`
referencing [Google issue
#235121269](https://issuetracker.google.com/issues/235121269))
- Each cycle triggers `release()` → re-`prepare()`, hitting this bug
A regular `MapView` in an Activity rarely goes through this cycle, which
is why the bug was only reported on Android Auto.
## Fix
One line — reset `textures[0]` after deletion so the next render
allocates a fresh ID:
```kotlin
GLES20.glDeleteTextures(textures.size, textures, 0)
textures[0] = 0 // ← ensures glGenTextures is called on next render
```
This matches the pattern already used correctly in
`MapboxWidgetRenderer.deleteFrameBufferWithTexture()`.
## Key Changes
- **`BitmapWidgetRenderer.kt`**: Add `textures[0] = 0` after
`glDeleteTextures` in `release()`
- **`BitmapWidgetRendererTest.kt`** (new): Robolectric tests — `release
then re-render allocates fresh texture` and `release sets needRender to
false`
## Test plan
- [x] Unit tests added and passing
- [x] Manual verification on Android Auto (surface destroy/recreate
cycle)
cc @mapbox/maps-android
cc @mapbox/sdk-platform
GitOrigin-RevId: 457b011818610755601dd0c12b5f1a9cf56d5e4b1 parent c4de168 commit f64fb09
3 files changed
Lines changed: 117 additions & 0 deletions
File tree
- maps-sdk/src
- main/java/com/mapbox/maps/renderer/widget
- test/java/com/mapbox/maps/renderer/widget
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
235 | 235 | | |
236 | 236 | | |
237 | 237 | | |
| 238 | + | |
238 | 239 | | |
239 | 240 | | |
240 | 241 | | |
| |||
Lines changed: 115 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
0 commit comments