Skip to content

Conversation

@chrisgervang
Copy link
Collaborator

@chrisgervang chrisgervang commented Nov 20, 2025

Summary

This is a POC to demonstrate what the problems were and how they can be fixed without any API changes. I focused testing on Maplibre in PureJS, since the issues are always reproducible there. I still need to test the react environment, mapbox, and prune out unnecessary code.

And, once the dust has settled, I'd like to discuss if there are sensible changes to make in luma or deck's API, or if this integration complexity is just unavoidable, and our best choice is to just isolate it to the mapbox module with integration test coverage.

This comment was a big clue #9856 (comment) and it may be that GoogleMapsOverlay needs a similar resize handler... consider both fixes when considering API changes.

(Potentially) Closes #9666

Problem

When using MapboxOverlay with interleaved: true, deck.gl layers would get out of sync with the underlying Mapbox/Maplibre map during:

  • Browser window resizing
  • Moving windows between screens with different device pixel ratios (DPR)

This occurred because deck.gl wasn't properly tracking canvas size changes when the map controlled the canvas.

Solution

Modified modules/mapbox/src/mapbox-overlay.ts to synchronize dimensions between the map and deck.gl.

Key Changes:

  1. Disabled autoResize (line 147): Set autoResize: false in deviceProps since the map controls canvas size, not deck.gl

  2. Added ResizeObserver (lines 161-169): Watches for canvas size changes including DPR changes that don't trigger map resize events

  3. Implemented _handleInterleavedResize() (lines 269-337): Core resize handler that:

    • Waits for deck initialization before processing
    • Reads actual canvas.width/height as the drawing buffer size (lines 286-287)
    • Directly syncs luma.gl's internal drawingBufferWidth/Height tracking (lines 301-306)
    • Updates CSS dimensions for correct cssToDeviceRatio calculation (lines 309-316)
    • Sets deck's width/height to CSS pixels (lines 321-323)
    • Updates viewManager and layerManager (lines 327-329)
    • Triggers redraws (lines 333-336)
  4. Fixed event handler cleanup (lines 155-156, 209-213): Properly stores and removes map resize handler reference

  5. Added private fields (lines 48-50): Track resize observer, last canvas size, and map resize handler

Key Insight

In interleaved mode, Mapbox/Maplibre controls the canvas and may use either CSS pixels or device pixels depending on configuration. The fix works by:

  • Reading the actual canvas.width/height (the true drawing buffer size)
  • Syncing all of deck.gl's internal dimension tracking to match
  • Using CSS pixels for deck's logical dimensions while the drawing buffer may be in device pixels
  • Letting cssToDeviceRatio() handle the scaling factor

Testing

Verified working for:

  • ✅ Window resizing on single screen
  • ✅ Moving between DPR 1 and DPR 2 screens
  • ✅ Maplibre
  • ✅ PureJS
  • ✅ Chrome / MacOS
  • 🚧 React
  • 🚧 Mapbox
  • 🚧 Safari/Firefox

Files Modified

  • modules/mapbox/src/mapbox-overlay.ts - Core resize handling fix

The issue was that in interleaved mode, deck.gl shares the WebGL context with Mapbox/Maplibre, but the drawing buffer sizes were getting out of sync
  during resize events.

  The solution involved three changes:

  1. Set autoResize: true and useDevicePixels: false (mapbox-overlay.ts:143-146) - This allows luma.gl to handle resizing, but prevents DPR scaling
  issues.
  2. Listen to map resize events (mapbox-overlay.ts:152) - Added map.on('resize', this._handleInterleavedResize) to handle when the map canvas resizes.
  3. Manually synchronize drawing buffer dimensions (mapbox-overlay.ts:246-267) - In the resize handler:
    - Set device.canvasContext.drawingBufferWidth/Height to match the map's canvas dimensions
    - Call _updateCanvasSize() to update deck's internal size tracking
    - Call redraw() to render with the new size
Replaces manual assignment of drawing buffer dimensions with a call to syncDrawingBufferSize for luma.gl compatibility. Removes explicit redraw call, relying on map's render event to trigger deck redraw.
Adds a ResizeObserver to track canvas size changes, including device pixel ratio changes that do not trigger Mapbox's 'resize' event. Synchronizes luma.gl and Deck.gl internal size tracking with the actual canvas dimensions, ensuring correct rendering and fixing issues with white-out and redraws in interleaved mode.
Replaces the call to syncDrawingBufferSize with direct updates to drawingBufferWidth and drawingBufferHeight on the canvasContext. This ensures luma.gl's internal state matches the externally managed canvas size in interleaved mode.
Introduces a canvas element to the Map mock for testing purposes, including getCanvas and getPixelRatio methods. This enhances the mock's compatibility with code expecting these Mapbox GL JS API features.
Adds a check to ensure that a viewport exists before attempting to activate it in MapboxOverlay. Prevents potential errors when the viewports array is empty.
@ibgreen
Copy link
Collaborator

ibgreen commented Nov 20, 2025

, I'd like to discuss if there are sensible changes to make in luma or deck's API, or if this integration complexity is just unavoidable, and our best choice is to just isolate it to the mapbox module with integration test coverage.

@ibgreen
Copy link
Collaborator

ibgreen commented Nov 20, 2025

This is an example of where we have significant duplication
https://github.com/visgl/deck.gl/pull/9870/files

Actually, maybe the problem isn't with activateViewport but something else. Maybe calling activateViewport is wrong in the first place during resize.
  Looking back at deck.gl's internal _updateCanvasSize, it only calls activateViewport but deck.gl controls its own canvas, whereas in interleaved mode
  we don't. Maybe we shouldn't be calling activateViewport at all?
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.

[Bug] Maplibre and deck.gl go out-of-sync when resizing the map container

3 participants