Skip to content

Conversation

@zbigg
Copy link
Collaborator

@zbigg zbigg commented Jan 19, 2026

Followup of #9939

Background

#9939 introduced experimental "batched" rendering mode forMapboxOverlay, but it's broken as we missed that we have second render hook that now renders everything second time.

Change List

  • Fix double rendering of layers in MapboxOverlay in interlaved mode when _renderLayersInGroups is set

Note

Eliminates duplicate rendering in interleaved/batched mode by refining external render behavior.

  • Simplifies afterRender to only redraw when non-Mapbox views exist and replaces the layer filter to exclude MAPBOX_VIEW_ID rather than checking mapboxLayers
  • Removes mapboxLayers-based non-Mapbox layer detection to avoid rendering Mapbox layers twice

Written by Cursor Bugbot for commit 2f4c199. This will update automatically on new commits. Configure here.

@zbigg zbigg requested a review from felixpalmer January 19, 2026 12:59
@coveralls
Copy link

coveralls commented Jan 19, 2026

Coverage Status

coverage: 91.114% (-0.001%) from 91.115%
when pulling 2f4c199 on zbigg/fix-mapboxoverlay-double-render-in-groups
into 83ac412 on master.

const hasNonMapboxLayers =
// In interleaved mode _all_ layers are rendered through custom mapbox layer API (either one by one or in groups,
// so assume we never have "non-mapbox-managed" layers.
!interleaved && deckLayers.some(layer => layer && !mapboxLayerIds.includes(layer.id));
Copy link
Collaborator Author

@zbigg zbigg Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like MapboxOverlay has many modes of operations and afterRender does following:

  • checks if there are layers that are not "rendered by MapboxLayer"
  • or some non-mapbox viewports

Now, since deprecation of MapboxLayer API (it's private since v9 as i understand) at least in interleaved mode (batched or not), all layers are always managed internally by resolveLayers and always rendered in "main render pass managed by maplibre", so actually feels like hasNonMapboxLayers is not necessary.

I looked very briefly and it looks like afterRender isn't used at all in overlaid mode.

Am i right, that whole hasNonMapboxLayers cannot be effectively true?

Copy link
Collaborator

@chrisgervang chrisgervang Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe.. what about a multi-view case where you have an minimap or an orthographic view on top of the main map with layers that only appear in those views?

Are those layer render passes still "managed" by this overlay?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all layers are in userData.mapboxLayerIds then I think this can be simplified like you've proposed.

From a user perspective, they are always providing all layers to the overlay's layers props.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe.. what about a multi-view case

I have no experience with multiview, but this patch should multiview behavior.
If there are other views, all layers will be rendered there.

I am only removing case for layers in mapbox view (the main, the default one embedded in mapbox/libre map) ...

If all layers are in userData.mapboxLayerIds ...

Yes, this is what i believe. We cannot add MapboxLayer to "deck" in any other way than by passing normal deck Layer instance through overlay.setProps({layers: ....}) - uncontrolled map.addLayer(new MapboxLayer()) i. impossible now.

So ultimately mapboxLayerIds is equal to all layers in deck instance.

So this PR clears flow, so it's basically two steps:

  • mapbox/libre renders its own layers and all deck layers (in groups or not)
  • afterRender renders other views

To complete description, this is how i understand current flow:

  • (1) mapbox/libre renders own layers and some deck layers added manually with map.add(new MapboxLayer({deck: someOtherDeckInstance}) (?)
  • mapbox/libre all viewports, but
    • for mapbox view, it skips layers rendered in (1) renders rest on top
    • for other views renders all deck layers

The main mystery for me is that i don't know if/how it was possible to (1) to skip some layers. It must be some legacy path that was impossible now in v9.

I believe current code has just many unusable branches that are not working at all. Possibly remnants from old incarnations of MapboxOverlay.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this PR clears flow, so it's basically two steps:

  • mapbox/libre renders its own layers and all deck layers (in groups or not)
  • afterRender renders other views

Very interesting - is this the order it always occurs in as well? Layers and maplibre first, then other deck views (which may draw a clear color for a background)? That would explain why adding a view with a clear cannot be done before maplibre draws. Or, view draw order respect the views prop order?

I was trying to add a background color to the framebuffer while using Maplibre's globe view. Maplibre has no API for setting the background color, but deck can draw a full frame view with a clear color. I couldn't figure out why the background always drew over everything - perhaps this is why.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main mystery for me is that i don't know if/how it was possible to (1) to skip some layers. It must be some legacy path that was impossible now in v9.

I believe current code has just many unusable branches that are not working at all. Possibly remnants from old incarnations of MapboxOverlay.

I can't tell you how much I appreciate your work here. There are many remnants of old MapboxLayer features in MapboxOverlay since we wanted to offer a full migration path. But I agree this seems like a feature that no longer is used and I'd like to remove it

@zbigg zbigg requested a review from chrisgervang January 19, 2026 13:17
@zbigg
Copy link
Collaborator Author

zbigg commented Jan 19, 2026

I just've found that MapboxLayer docs are still available online even if it's not supported in v9

@zbigg zbigg marked this pull request as ready for review January 19, 2026 14:22
@zbigg zbigg changed the title fix(mapbox): In interleaved, grouped mode don't render layers twice fix(mapbox): In interleaved, batched mode don't render layers twice Jan 19, 2026
@chrisgervang
Copy link
Collaborator

I just've found that MapboxLayer docs are still available online even if it's not supported in v9

Yes, we hid it from the table of contents but kept it as an internal doc since the class was still in use.

Copy link
Collaborator

@chrisgervang chrisgervang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Pessimistress can you please gut check our conclusion in the comments that it's fine to remove these layer filters since it's for a feature no longer supported?

@zbigg zbigg requested a review from Pessimistress January 20, 2026 09:33
@zbigg
Copy link
Collaborator Author

zbigg commented Jan 23, 2026

I looked at test apps created in #9946 and confirm that we're ok and we still support mutliple views.
I'll perform some more tests and merge, so #9955 can follow.

@zbigg zbigg merged commit 50df2f2 into master Jan 23, 2026
6 checks passed
@zbigg zbigg deleted the zbigg/fix-mapboxoverlay-double-render-in-groups branch January 23, 2026 09:44
chrisgervang added a commit that referenced this pull request Jan 27, 2026
Instead of tracking MapboxLayer instances in a Set, use map.getLayer()
to check if a deck layer has a corresponding MapboxLayer on the map.
This simplifies the code since resolve-layers already creates MapboxLayer
instances with the same ID as the deck layer.

Ref: #9944

Co-Authored-By: Claude Opus 4.5 <[email protected]>
chrisgervang added a commit that referenced this pull request Jan 27, 2026
Instead of tracking MapboxLayer instances in a Set, use map.getLayer()
to check if a deck layer has a corresponding MapboxLayer on the map.
This simplifies the code since resolve-layers already creates MapboxLayer
instances with the same ID as the deck layer.

Ref: #9944

Co-Authored-By: Claude Opus 4.5 <[email protected]>
chrisgervang added a commit that referenced this pull request Jan 27, 2026
Instead of tracking MapboxLayer instances in a Set, use map.getLayer()
to check if a deck layer has a corresponding MapboxLayer on the map.
This simplifies the code since resolve-layers already creates MapboxLayer
instances with the same ID as the deck layer.

Ref: #9944

Co-Authored-By: Claude Opus 4.5 <[email protected]>
chrisgervang added a commit that referenced this pull request Jan 27, 2026
* chore(mapbox): Remove internal deck mode from MapboxLayer

MapboxLayer is no longer a public API (only MapboxOverlay is exported),
so the internal deck mode code paths are no longer needed.

Changes:
- Remove `isExternal` flag from UserData (always true now)
- Remove internal deck creation branch in getDeckInstance
- Remove `updateLayers` function (only used for internal mode)
- Make `deck` prop required in getDeckInstance
- Simplify MapboxLayer and MapboxLayerGroup to read map.__deck directly
- Only MapboxOverlay calls getDeckInstance for initialization
- Update tests to simulate MapboxOverlay flow

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* refactor(mapbox): Use getter for deck in MapboxLayer and MapboxLayerGroup

Replace `this.deck` field with a getter that reads from `map.__deck`
directly. This avoids potential stale references and simplifies the
lifecycle - `onAdd` no longer needs to capture the deck instance.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(mapbox): Keep non-null assertions in render method

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* refactor(mapbox): Make __deck non-optional in MapWithDeck type

Since MapboxLayer and MapboxLayerGroup are internal classes only used
by MapboxOverlay, and MapboxOverlay always sets up map.__deck before
adding these layers, __deck is guaranteed to exist when the map is set.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* refactor(mapbox): Remove deck getter, access map.__deck directly

Since __deck is guaranteed on MapWithDeck, we can access it directly
through the map rather than through a getter.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* test(mapbox): Remove trivial deck instance assertions

These assertions just verified getDeckInstance set map.__deck, which
is obvious and doesn't test any MapboxLayer behavior.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(mapbox): Restore original test structure for external Deck

The first test should create Deck without gl option to test the case
where deck has its own gl context. This matches the original test
behavior where deck.props.gl !== map's gl.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(mapbox): Initialize map.__deck in tests before using MapboxLayer

The resolveLayers test now calls getDeckInstance to set up map.__deck
before resolving layers, since MapboxLayer.onAdd expects it to be set.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* refactor(mapbox): Consolidate gl context handling in getDeckInstance

- Remove `gl` parameter from getDeckInstance - now obtained internally from map.painter.context.gl
- Remove always-true `if (deck.props.gl === gl)` condition since deck is always created without gl and getDeckInstance sets it
- Move WebGL1 compatibility warning from MapboxOverlay to getDeckInstance
- Simplify MapboxOverlay._onAddInterleaved - just creates Deck with user props
- Update tests to not pass gl when creating Deck instances

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(mapbox): Pass gl at Deck construction time, not via setProps

The gl prop must be set when constructing Deck, not via setProps after
construction. Move gl retrieval and WebGL1 compatibility warning back to
mapbox-overlay.ts where the Deck is constructed, and remove redundant gl
handling from getDeckInstance.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* refactor(mapbox): Remove redundant parameters merge in getDeckInstance

Parameters are already merged when creating Deck in mapbox-overlay.ts,
so the merge in getDeckInstance is redundant.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* refactor(mapbox): Remove unused deck prop from MapboxLayerProps

The deck prop was vestigial from the old code where it was passed to
getDeckInstance. Now MapboxLayer accesses the deck via map.__deck,
so the prop is no longer needed.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* chore(mapbox): Remove unsupported mapbox-layers-react test app

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(mapbox): Remove deck prop from MapboxLayer in resolve-layers

Missed this usage when removing the deck prop from MapboxLayerProps.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* chore(mapbox): Remove unsupported mapbox-layers test apps

Remove mapbox-layers test app directory and update index.html links.
The mapbox-layers-react app was already deleted in a previous commit.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* docs(mapbox): Update MapboxLayer doc to reflect internal-only status

MapboxLayer is no longer part of the public API. Updated the doc to
add a deprecation notice and migration guide pointing to MapboxOverlay.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* docs(mapbox): Remove MapboxLayer doc and update references

MapboxLayer is internal-only and should not be documented as a public
API. Removed the doc file and updated references in whats-new.md and
test README.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* fix(mapbox): Update tests to create Deck with default parameters

Tests now create Deck with default parameters merged, matching how
MapboxOverlay._onAddInterleaved creates the Deck. This is needed
because getDeckInstance no longer merges default parameters.

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* refactor(mapbox): Remove mapboxLayers tracking from UserData

Instead of tracking MapboxLayer instances in a Set, use map.getLayer()
to check if a deck layer has a corresponding MapboxLayer on the map.
This simplifies the code since resolve-layers already creates MapboxLayer
instances with the same ID as the deck layer.

Ref: #9944

Co-Authored-By: Claude Opus 4.5 <[email protected]>

* refactor(mapbox): Add null guard and consistent signature to MapboxLayer

- Add gl parameter to onAdd for consistency with CustomLayerInterface
- Add null guard in render() for safety, matching MapboxLayerGroup

Co-Authored-By: Claude Opus 4.5 <[email protected]>

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants