FillBucket.uploadPending() is always true: `this.uploaded` is never set, so every fill bucket re-uploads on every frame
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 82/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- performance
Research direction
Read the FillBucket.upload() and uploadPending() implementations, then compare them with LineBucket and CircleBucket as the issue describes. Reproduce by checking whether a loaded fill bucket remains pending after upload; confirm the fix by verifying it becomes non-pending when no re-upload is needed and that rendered colors remain unchanged.
Written by the indexing model from the issue text.
Description
mapbox-gl-js version
3.33.0 and 3.34.2 (latest on npm; same code in both)
Browser
Chrome (desktop)
What happens
FillBucket.uploadPending() reads this.uploaded, but FillBucket.upload() never sets it. Only the inner FillBufferData sets its own uploaded flag. The bucket therefore reports "pending" forever.
From dist/mapbox-gl-dev.js (3.33.0):
// class FillBucket
uploadPending() {
return !this.uploaded || this.bufferData.needsUpload() || this.hdExt != null && this.hdExt.needsUpload();
}
upload(context) {
this.bufferData.upload(context); // sets bufferData.uploaded, not this.uploaded
if (this.hdExt) {
this.hdExt.upload(context);
}
}
Compare LineBucket and CircleBucket, whose upload() ends with this.uploaded = true.
Because the bucket is always pending, Tile.upload() takes its slow branch for every fill bucket on every call:
// Tile.upload
if (bucket.uploadPending()) {
...
featureStates = sourceCache._state.getState(sourceLayerId, void 0);
...
bucket.upload(context, this.tileID.canonical, featureStates, availableImages, globalProperties);
}
getState(sourceLayer, undefined) builds a new object with Object.assign({}, base, changes) over every feature that has state in that source layer. So the cost per frame is (visible fill tiles) x (features with feature-state), even when nothing changed.
Impact
On a map with a vector fill layer and many features carrying feature-state, the main thread spends most of a pan inside Tile.upload. Measured on a phone-size map (390x789, zoom 16) with roughly 5,000 to 7,000 features holding state, 7 pans over the same ground with the tile cache cleared first:
Tile.upload main thread over 7 pans |
long frames (> 34 ms) in the worst pan | |
|---|---|---|
| unpatched | 3,545 ms | 129 ms |
| patched (below) | 118 to 125 ms | 0 to 40 ms |
The cost grows with the number of features holding state. With few of them the bug is still there but cheap.
Steps to reproduce
- Add a vector source and a
filllayer on it. - Call
map.setFeatureStatefor a few thousand features of that source layer. - After the map is idle, take a fill bucket from a loaded tile and call
bucket.uploadPending(). It returnstrue, andbucket.uploadedis never set. - Pan, and profile
Tile.prototype.upload:getStateruns for every fill tile on every call.
Expected
uploadPending() returns false once the bucket has uploaded and nothing needs a re-upload.
Suggested fix
Set the flag in FillBucket.upload(), as the other buckets do:
upload(context) {
this.bufferData.upload(context);
if (this.hdExt) {
this.hdExt.upload(context);
}
this.uploaded = true;
}
Or have uploadPending() read this.bufferData.uploaded.
Workaround
Wrap FillBucket.prototype.upload at runtime so it sets this.uploaded = true after the original call. Rendered colours were unchanged with the wrap in place.
- Dominant language
- TypeScript
- Stars
- 12.5k
- Forks
- 2.4k
- PR merge metrics
- No merged PRs in 30d
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from mapbox/mapbox-gl-js
-
interpolate-lab gives the same result as RGB interpolate (rgb2xyz/xyz2rgb scale 0-1 channels by 255)Openauto-triaged bug :lady_beetle:
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
mapbox/mapbox-gl-js#13751 ·
-
TypeError: Cannot read properties of undefined (reading 'get') when a map is removed while a color-theme LUT is decodingPossibly taken @Piyush495 claimed this 36 days ago. Openauto-triaged bug :lady_beetle:
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
mapbox/mapbox-gl-js#13737 · 1 comment ·
-
auto-triaged bug :lady_beetle:
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
mapbox/mapbox-gl-js#13723 · 2 comments ·
-
auto-triaged bug :lady_beetle:
Difficulty 1/5 Under an hour Newbie friendliness 86/100
mapbox/mapbox-gl-js#13717 ·
-
>= v3.21 Symbol layer without feature-state expression crash the mapPossibly taken @liueric-dev claimed this 77 days ago. Openauto-triaged bug :lady_beetle:
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
mapbox/mapbox-gl-js#13714 · 1 reaction ·
All issues in mapbox/mapbox-gl-js
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
Add "append" and "prepend" slots for vue-vuetify extended renderersPossibly taken @simeyc claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
eclipsesource/jsonforms#2668 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
cobot/zapier#520 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
README の通知条件を実際の判定処理に合わせるPossibly taken @unhappychoice claimed this today. Openfactory:in-progress priority:medium
Difficulty 1/5 Under an hour Newbie friendliness 90/100
unhappychoice/oss-issue-opener#395 · 1 comment ·
Maintainers usually reply within 1 day