Skip to content

chore(core) Adopt luma CanvasContext#10228

Merged
ibgreen merged 21 commits into
masterfrom
ib/canvas-context-93
May 26, 2026
Merged

chore(core) Adopt luma CanvasContext#10228
ibgreen merged 21 commits into
masterfrom
ib/canvas-context-93

Conversation

@ibgreen
Copy link
Copy Markdown
Collaborator

@ibgreen ibgreen commented Apr 16, 2026

Summary

Compared to master, this PR moves deck.gl toward a single source of truth for canvas integration by treating luma.gl CanvasContext as the authority for:

  • canvas resize observation
  • CSS size
  • drawing buffer size
  • CSS-to-device pixel conversion / DPR

Deck still owns viewport updates, redraw invalidation, picking orchestration, and callback wiring, but it no longer duplicates canvas sizing logic that already lives in luma.

image

Why

The goal is to ensure deck.gl has one canvas integration authority: luma.gl CanvasContext.

On master, Deck still had places where it:

  • manually synchronizes drawing buffer size
  • infers canvas size from DOM/canvas reads
  • uses raw canvas dimensions where canvas-context state is the better source
  • makes picking buffer and pixel conversion decisions without explicit active-context plumbing

This PR makes the contract cleaner:

  • luma CanvasContext owns resize observation, DPR tracking, drawing-buffer sizing, and CSS/device pixel conversion
  • Deck owns viewport updates, redraw invalidation, picking orchestration, and Deck callbacks
  • basemap adapters own camera extraction and DOM placement only

What changed vs master

Core canvas ownership

  • Replaces Deck-side DOM/canvas-size reads with CanvasContext-backed size reads.
  • Adds explicit active CanvasContext plumbing inside Deck.
  • Routes Deck resize handling through _onCanvasContextResize(canvasContext).
  • Keeps DeckProps.onResize behavior, now driven by luma canvas state.
  • Preserves external-device correctness by polling the external CanvasContext during render frames, without mutating user-owned device.props.onResize.

Picking / buffer sizing

  • Passes the active CanvasContext into internal picking operations.
  • Sizes picking FBOs from canvasContext.getDrawingBufferSize().
  • Uses the active canvas context for picking CSS-to-device pixel conversion.
  • Adds a regression test proving picking FBOs follow drawing-buffer size rather than CSS/canvas element size.

Basemap integrations

  • Leaves Mapbox and Google Maps responsible for camera sync and DOM/layout glue only.
  • Clarifies that shared canvas sizing and DPR belong to the basemap + luma canvas context, not Deck.

Future multi-canvas posture

  • Does not add a public multi-canvas API.
  • Does not enable canvases, canvasId, CanvasManager, per-canvas event managers, or PresentationContext rendering.
  • Adds preparatory CanvasContext plumbing so future multi-canvas work can route the correct context through resize and picking paths.

Future Opportunity

The paths for external contexts is still separated. Adding installable resize listeners on luma.gl Device and CanvasContext could help here.

CanvasContext-Centric Resize Plumbing

Files changed vs master

  • modules/core/src/lib/deck.ts
  • modules/core/src/lib/deck-picker.ts
  • modules/mapbox/src/deck-utils.ts
  • modules/google-maps/src/utils.ts
  • test/modules/core/lib/deck.spec.ts
  • test/modules/core/lib/deck-picker.spec.ts

Note

Medium Risk
Moderate risk because it rewires core canvas resize/DPR/picking plumbing and changes onResize callback signature; regressions could affect rendering size, interleaved integrations, or pick accuracy across devices/contexts.

Overview
Deck now treats luma.gl CanvasContext as the authority for canvas CSS size, drawing-buffer size, DPR conversion, and resize events. Deck stores the active canvas context, routes resize handling through a new internal _onCanvasContextResize, updates width/height from canvasContext.getCSSSize(), and takes ownership of device.props.onResize while active (restoring it on finalize).

Picking is updated to be context-aware. DeckPicker accepts an optional canvasContext for pick operations, sizes picking FBOs from canvasContext.getDrawingBufferSize(), and uses the context’s CSS-to-device conversions to compute device coordinates.

Docs/tests/integrations are adjusted accordingly: DeckProps.onResize now also receives the CanvasContext; Google Maps/Mapbox notes and style plumbing are tweaked for shared-canvas scenarios; React utils tighten element detection/types; Layer picking color cache is regenerated when an invalid/zeroed cache is detected; and new tests cover drawing-buffer-based picking buffer sizing plus resize/DPR forwarding behavior.

Reviewed by Cursor Bugbot for commit fb12200. Bugbot is set up for automated code reviews on this repo. Configure here.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f0c4b8b8a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread modules/core/src/lib/deck.ts
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 16, 2026

Coverage Status

coverage: 83.386% (+0.02%) from 83.366% — ib/canvas-context-93 into master

Comment thread modules/core/src/lib/deck-picker.ts Outdated
Comment thread modules/core/src/lib/deck.ts Outdated
}

this._updateCanvasSize();
if (this._pollCanvasContextSize) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this primarily intended to be a performance optimization to remove unnecessary syntonization when the device is internal and deck owns the onResize wiring?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This separate path between internal and externally created devices is the main remaining wart.
My current understanding is that we need some changes in luma to get around this.
I updated the implementation to be more CanvasContext centric and added a second infographic to the PR description to illustrate the two flows.

Comment thread modules/core/src/lib/deck.ts Outdated
this._needsRedraw = 'Canvas resized';
// Attached contexts still emit resize through luma's CanvasContext.
// Deck only mirrors that state into viewport dimensions and redraw flags.
this._onCanvasContextResize(canvasContext);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The removal of setDrawingBufferSize here causes a resize regression in interleaved modes (MapLibre, Mapbox, etc).

webgl2Adapter.attach() sets autoResize: false on the canvas context (webgl-adapter.ts:73), which means luma's _updateDrawingBufferSize() in canvas-surface.ts skips updating the drawing buffer when the ResizeObserver fires. The CSS size tracking (cssWidth/cssHeight) updates correctly, but luma's internal drawingBufferWidth/drawingBufferHeight never sync to the new canvas dimensions.

So while CanvasContext is indeed the authority for observing the resize, it doesn't act on it for attached contexts - someone still has to call setDrawingBufferSize. On master that was Deck.. this PR removes that call assuming luma handles it, but luma explicitly doesn't for autoResize: false.

Possible paths forward:

  1. Restore the setDrawingBufferSize call here (what master does, see fix(core): restore drawing buffer sync for attached GL contexts on resize #10281)
  2. Change luma's attach() to not disable autoResize, or add a separate flag that skips only the canvas element resize but still syncs the drawing buffer tracking
  3. Add an "installable resize listener" on CanvasContext (as you mentioned in the "Future Opportunity" section) that handles this automatically

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@chrisgervang I tried to address this concern can you take another look?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good! Just did a full side-by-side comparison between this PR and the behavior on master, and they behave identically.

The only remaining issue I found was a "flashing" bug across environments in interleaved mode, but I observed this before and after this change, so it's not a regression.. something to follow up on separately.

flashing.short.mov

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I recall doing a fix for flashing during resize in the main canvas, might be worth reviewing:

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fb12200. Configure here.

this.widgetManager.addDefault(new TooltipWidget());

this.setProps(this.props);
this.setProps({});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

useDevicePixels never forwarded for attached GL contexts

Medium Severity

For the props.gl attach path (Mapbox/MapLibre interleaved mode), useDevicePixels is never forwarded to the luma canvas context. In the constructor, _canvasContext is still null when setProps(initialProps) runs because the device is a promise. Later, _setDevice sets _canvasContext but then calls setProps({}) — and since {}.useDevicePixels === undefined, the condition props.useDevicePixels !== undefined is false, so forwarding is skipped. Previously setProps(this.props) always had useDevicePixels defined (defaulting to true), so it was forwarded after device initialization. Users who set useDevicePixels: false or a numeric DPR override with attached GL contexts will have that preference silently ignored.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fb12200. Configure here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I've confirmed this regression by setting the ratio to 1.5 and noticing it not take affect. I have use cases that frequently rely on setting this property

Screenshot 2026-05-22 at 4 31 43 PM

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Actually, this regression was introduced sometime during 9.3 development..

  1. useDevicePixels on standalone Deck regressed in 9.3 — it works on 9.1 and 9.2 but not 9.3. This is a pre-existing regression on master, unrelated to this PR.

  2. useDevicePixels in interleaved mode never worked, and that's ok (9.1, 9.2, 9.3) — because MapLibre/Mapbox owns the canvas and its drawing buffer. The correct mechanism is MapLibre's pixelRatio constructor option, which works across all versions including Ib's branch.

So the Bugbot finding was technically accurate about the code path but mis-attributed the impact - the real issue is a 9.3 master regression on the standalone useDevicePixels, which is a separate bug

Copy link
Copy Markdown
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.

The DPR regression should be addressed before shipping

canvasContext.setDrawingBufferSize(width, height);

this._needsRedraw = 'Canvas resized';
userOnResize?.(canvasContext, info);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: if a user passes deviceProps.onResize, it gets spread onto the device here but is then unconditionally overwritten in _setDeviceResizeHandler (line 1082) without forwarding. The old code preserved the user callback. Probably not blocking since none of our integrations use it, but worth noting as an intentional public API change or adding a forward call back.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I believe I added documentation for this overwrite in deck docs.
My overall assessment was that it was not really worth the extra complexity to try to handle this.
We can add it back later if needed.

this.widgetManager.addDefault(new TooltipWidget());

this.setProps(this.props);
this.setProps({});
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I've confirmed this regression by setting the ratio to 1.5 and noticing it not take affect. I have use cases that frequently rely on setting this property

Screenshot 2026-05-22 at 4 31 43 PM

@ibgreen ibgreen merged commit 6795cc9 into master May 26, 2026
6 checks passed
@ibgreen ibgreen deleted the ib/canvas-context-93 branch May 26, 2026 19:01
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