Skip to content

Fix DPR changes in interleaved mode by leveraging onResize callback#9887

Merged
chrisgervang merged 6 commits intomasterfrom
chr/dpr-fix-clean
Dec 2, 2025
Merged

Fix DPR changes in interleaved mode by leveraging onResize callback#9887
chrisgervang merged 6 commits intomasterfrom
chr/dpr-fix-clean

Conversation

@chrisgervang
Copy link
Collaborator

@chrisgervang chrisgervang commented Dec 2, 2025

Summary

This PR fixes device pixel ratio (DPR) changes when moving windows between displays with different pixel ratios, building on top of #9886.

Key Discovery: The onResize callback is sufficient for handling both window resize AND DPR changes. The onDevicePixelRatioChange callback is redundant because luma.gl's ResizeObserver fires onResize whenever DPR changes.

Changes

modules/core/src/lib/deck.ts

  • Added onResize callback to attach path (interleaved mode) that:
    • Manually syncs drawing buffer dimensions from canvas
    • Sets redraw flag when resize is detected
    • Preserves user-provided callbacks
  • Kept existing onResize callback in create path (standalone mode)
  • Note: Manual drawing buffer sync needed because luma 9.2's setDrawingBufferSize() immediately modifies the canvas, but in attach mode the external library (Mapbox/Maplibre/Google Maps) manages canvas size

modules/mapbox/src/mapbox-overlay.ts

  • Removed incorrect autoResize: true override
  • This was fighting with luma's attach behavior which forces autoResize: false
  • Added explanatory comment

Test Matrix Results ✅

Tested all scenarios with window resize, DPR changes (moving between displays), and where applicable, fullscreen mode.

Google Maps (4/4) ✅

  • ✅ pure-js interleaved: true (default) - resize, DPR work
  • ✅ pure-js interleaved: false - resize, DPR work (⚠️ fullscreen blank - pre-existing)
  • ✅ react interleaved: true (default) - resize, DPR work
  • ✅ react interleaved: false - resize, DPR work (⚠️ fullscreen blank - pre-existing)

Maplibre (4/4) ✅

  • ✅ pure-js interleaved: false (default) - resize, DPR work
  • ✅ pure-js interleaved: true - resize, DPR work
  • ✅ react interleaved: false (default) - resize, DPR work
  • ✅ react interleaved: true - resize, DPR work

Mapbox (4/4) ✅

  • ✅ pure-js interleaved: false (default) - resize, DPR work
  • ✅ pure-js interleaved: true - resize, DPR work
  • ✅ react interleaved: false (default) - resize, DPR work
  • ✅ react interleaved: true - resize, DPR work

Maplibre Globe (2/2) ✅

  • ✅ pure-js interleaved: false (default) - resize, DPR work
  • ✅ pure-js interleaved: true - resize, DPR work

Test Apps (2/2) ✅

  • ✅ test/apps/widgets-infovis - resize, DPR work
  • ✅ test/apps/widgets-9.2 - resize, DPR work

Total: 15/15 scenarios passed

Known Issues

⚠️ Google Maps fullscreen mode shows blank canvas in overlaid mode (interleaved: false). This is a pre-existing issue unrelated to the DPR fix and should be tracked separately.

Technical Details

Why does onResize handle DPR changes?

Luma.gl's CanvasContext uses a ResizeObserver to monitor the canvas. When DPR changes (e.g., moving window to a display with different pixel ratio), the browser fires the ResizeObserver because the device pixel dimensions of the canvas change. This triggers the onResize callback with the new dimensions, making a separate onDevicePixelRatioChange callback redundant.

Why manual drawing buffer sync in attach mode?

In attach mode (interleaved rendering), the external mapping library (Mapbox/Maplibre/Google Maps) manages the canvas element and its size. When luma detects a resize, we need to manually sync the drawing buffer dimensions to match the canvas, because:

  1. autoResize is forced to false in attach mode (the external library controls canvas size)
  2. In luma 9.2, we can't call setDrawingBufferSize() as it would fight with the external library
  3. Direct property assignment ensures deck.gl's internal state matches the actual canvas size

Future Improvements

When upgrading to luma 9.3+, we can use canvasContext.setDrawingBufferSize(width, height) instead of direct property assignment (see TODO comments in code).


Note

Sync drawing buffer on onResize for attached WebGL contexts and drop Mapbox interleaved autoResize override to ensure correct DPR/resize behavior.

  • Core (modules/core/src/lib/deck.ts):
    • Attach path: wrap deviceProps.onResize to sync canvasContext.drawingBufferWidth/Height from canvas, set redraw, and preserve user callback.
    • Remove manual drawing buffer resize in setProps when autoResize === false.
  • Mapbox (modules/mapbox/src/mapbox-overlay.ts):
    • Interleaved mode: stop forcing createCanvasContext: {autoResize: true}; only set parameters alongside gl.

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

felixpalmer and others added 5 commits December 1, 2025 15:24
…eate paths

- Add onResize and onDevicePixelRatioChange callbacks to attach path
- Manually sync drawing buffer dimensions when callbacks fire
- Add onDevicePixelRatioChange callback to create path
- Remove incorrect autoResize override in MapboxOverlay
- Add console logs for debugging resize events
- Remove onDevicePixelRatioChange from attach path
- Remove onDevicePixelRatioChange from create path
- Remove debug console.logs
- Remove unused variable declarations

Testing confirmed that onResize callback alone handles both window
resize and DPR changes, making the separate DPR callback redundant.
@chrisgervang
Copy link
Collaborator Author

Ran this on noodles.gl as well and had great results when changing resolution with it's UI controls.

@akre54
Copy link
Collaborator

akre54 commented Dec 2, 2025

Nice fix!

Base automatically changed from felix/canvas-resize-bug-9.2 to master December 2, 2025 08:52
Copy link
Collaborator

@felixpalmer felixpalmer left a comment

Choose a reason for hiding this comment

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

Looks great :) Be sure to also give it a spin on the website - I just did and appears to work without issue

@coveralls
Copy link

Coverage Status

coverage: 91.136% (-0.009%) from 91.145%
when pulling 73ee11d on chr/dpr-fix-clean
into 233be18 on master.

@chrisgervang
Copy link
Collaborator Author

Tested the website - works without issues there as well.

I'd like to make this basemap test matrix faster to get through for future changes.. I'm generating a "basemap-browser" similar to layer-browser and am interested in setting up an automatic staging environment for the website, similar to what we're doing in noodles

@chrisgervang chrisgervang merged commit e716a4c into master Dec 2, 2025
6 checks passed
@chrisgervang chrisgervang deleted the chr/dpr-fix-clean branch December 2, 2025 17:15
felixpalmer added a commit that referenced this pull request Dec 3, 2025
…9887)

* fix(core): Correctly resize drawing buffer on resize

* fix(core,mapbox): Handle resize and DPR changes in both attach and create paths

- Add onResize and onDevicePixelRatioChange callbacks to attach path
- Manually sync drawing buffer dimensions when callbacks fire
- Add onDevicePixelRatioChange callback to create path
- Remove incorrect autoResize override in MapboxOverlay
- Add console logs for debugging resize events

* Remove manual drawing buffer sync from setProps

* Clean up: Remove redundant onDevicePixelRatioChange callbacks

- Remove onDevicePixelRatioChange from attach path
- Remove onDevicePixelRatioChange from create path
- Remove debug console.logs
- Remove unused variable declarations

Testing confirmed that onResize callback alone handles both window
resize and DPR changes, making the separate DPR callback redundant.

---------

Co-authored-by: Felix Palmer <felixpalmer@gmail.com>
@chrisgervang chrisgervang added this to the v9.2 patch releases milestone Feb 3, 2026
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