Skip to content

fix(google-maps): initialize deck in onDraw when onContextRestored is skipped#10179

Open
officialasishkumar wants to merge 3 commits intovisgl:masterfrom
officialasishkumar:fix/google-maps-overlay-interleaved-readd
Open

fix(google-maps): initialize deck in onDraw when onContextRestored is skipped#10179
officialasishkumar wants to merge 3 commits intovisgl:masterfrom
officialasishkumar:fix/google-maps-overlay-interleaved-readd

Conversation

@officialasishkumar
Copy link
Copy Markdown
Contributor

Closes #10059

Change List

  • Add defensive deck instance initialization in _onDrawVector for interleaved mode when onContextRestored was not fired
  • When a WebGLOverlayView is removed from a map and a new one is added, Google Maps may not fire onContextRestored because the WebGL context was never actually lost — only the overlay was detached
  • This left _deck as null, causing _onDrawVector to return early without rendering, making the second overlay permanently invisible
  • The fix calls _onContextRestored({gl}) from _onDrawVector if the deck instance hasn't been created yet, using the GL context provided by the onDraw callback
  • The initialization guard (!this._deck && this.props.interleaved) ensures this only triggers when needed and has no effect on the normal lifecycle where onContextRestored fires before onDraw

… skipped

When a GoogleMapsOverlay with interleaved=true is removed from a map
and a new overlay is subsequently added, Google Maps may not fire the
onContextRestored callback for the new WebGLOverlayView because the
WebGL context was never actually lost — only the overlay was removed.
This leaves _deck as null, causing _onDrawVector to return early
without ever rendering.

The fix adds a defensive initialization check in _onDrawVector: if the
deck instance has not been created yet and we are in interleaved mode,
call _onContextRestored with the GL context provided by the onDraw
callback. This ensures the deck instance is properly initialized
regardless of whether Google Maps fires onContextRestored.

The check only triggers when _deck is null, so it has no effect on the
normal lifecycle where onContextRestored fires before onDraw.

Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
@Pessimistress
Copy link
Copy Markdown
Collaborator

When a WebGLOverlayView is removed from a map and a new one is added

Can you elaborate on this scenario? How does it bypass onAdd in this case? onDraw is the wrong place to create resources.

@officialasishkumar
Copy link
Copy Markdown
Contributor Author

@Pessimistress The case I was aiming at is not that onAdd is skipped, it is that the interleaved WebGL overlay can reach onDraw without a fresh onContextRestored after the overlay is detached and re-added. That said, you are right that onDraw is a poor place to create resources. I need to rework this around the lifecycle rather than force initialization there.

@officialasishkumar
Copy link
Copy Markdown
Contributor Author

@Pessimistress Thanks for the question. The scenario is:

  1. You create a GoogleMapsOverlay with interleaved: true and call setMap(map). This creates a WebGLOverlayView, which goes through the normal lifecycle: onAdd -> onContextRestored (where _deck is created) -> onDraw.

  2. You call overlay.setMap(null) to remove it. This triggers _onRemove, but critically, the underlying WebGL context on the map is not lost since the map itself is still alive.

  3. You create a new GoogleMapsOverlay and call setMap(map) on the same map. A new WebGLOverlayView is created and attached. Google Maps fires onAdd, but it skips onContextRestored because from its perspective the WebGL context was never lost (it was active the entire time on the map). So _deck stays null.

  4. When onDraw fires, the old code checked if (!this._deck) and returned early, so the new overlay never rendered.

The fix does not create GL resources in the wrong place. It delegates to the existing _onContextRestored({gl}) method which handles all the deck instance creation. The guard !this._deck && this.props.interleaved ensures this only fires once, as a fallback for the missed lifecycle event. After that first call, the normal onDraw path runs as before.

@Pessimistress
Copy link
Copy Markdown
Collaborator

You create a new GoogleMapsOverlay and call setMap(map) on the same map. A new WebGLOverlayView is created and attached. Google Maps fires onAdd, but it skips onContextRestored because from its perspective the WebGL context was never lost (it was active the entire time on the map). So _deck stays null.

This has nothing to do with onContextRestored. It is important that operations are performed in a callback that matches the current stage. If the bug is that onAdd is not creating the deck instance, then onAdd needs to be fixed, not onDraw.

…nterleaved mode

When a WebGLOverlayView is removed and re-added to the same map,
onContextRestored may be skipped because the WebGL context was never
lost. Previously the fallback was in onDraw, but resource creation
belongs in onAdd. The new _onAddInterleaved callback retrieves the
existing GL context from the map canvas and delegates to
_onContextRestored, keeping initialization in the correct lifecycle
stage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@officialasishkumar
Copy link
Copy Markdown
Contributor Author

@Pessimistress You're right — initializing resources in onDraw was wrong. I've reworked the fix so it stays in the onAdd lifecycle stage.

The new approach adds an _onAddInterleaved callback that runs as the WebGL overlay's onAdd (previously noop). It retrieves the existing GL context from the map's canvas via getContext('webgl2') and delegates to _onContextRestored to create the deck instance. This way:

  • On first add: onAdd finds no canvas/context yet, so it's a no-op. onContextRestored fires normally and creates the deck as before.
  • On re-add (after remove + new overlay on same map): onAdd finds the existing GL context from the map's canvas and initializes the deck. If onContextRestored still fires, createDeckInstance detects the same map and returns the existing instance.

The onDraw fallback has been removed — _onDrawVector is back to its original form. All existing tests pass.

Copy link
Copy Markdown
Collaborator

@Pessimistress Pessimistress left a comment

Choose a reason for hiding this comment

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

I want to challenge your report that this._deck is null on re-add.
As you can see, onRemove does not delete this._deck, only hides it.
Only places where this._deck is set to null is in onContextLost and finalize.
So either you are calling finalize in your own app, after which you should not call setMap again; or Google Maps is invoking onContextLost but not onContextRestored. Neither of these scenarios are for this component to handle.

}
const mapCanvas = this._map.getDiv().querySelector('canvas');
if (mapCanvas) {
const gl = mapCanvas.getContext('webgl2');
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 is very suspicious... If the underlying context has not been created for any reason, this will return a new WebGL context AND break the base map because getContext is not called with any expected options.

Copy link
Copy Markdown
Contributor Author

@officialasishkumar officialasishkumar Apr 12, 2026

Choose a reason for hiding this comment

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

Fair concern. HTMLCanvasElement.getContext('webgl2') is supposed to return the existing context if one was already created on that canvas (which Google Maps does before onAdd runs). But the moment the invariant breaks, we'd spuriously create a fresh context without the options Google Maps needs, which is exactly what you're flagging.

Two options I can take:

  1. Drop this path entirely and instead trigger this._overlay.requestRedraw() right after the re-add, letting the normal onContextRestored lifecycle handle it.
  2. Keep the canvas lookup but guard with a check that the canvas already has an active gl context before calling getContext (e.g. inspect canvas attributes or skip if any unexpected state), and only fire once via the _overlayInitialized flag I mentioned on the other thread.

(1) feels safer to me if requestRedraw reliably triggers onContextRestored on a re-add.

// Create WebGL overlay for camera data (and GL context if interleaved)
const overlay = new google.maps.WebGLOverlayView();
overlay.onAdd = noop;
overlay.onAdd = interleaved ? this._onAddInterleaved.bind(this) : noop;
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.

If onContextRestored is also invoked on first add, doesn't this call _onContextRestored twice?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. On first add Google Maps does fire onContextRestored separately, so this would indeed double-init. The intent here was to cover the re-add case where the context was never lost and onContextRestored is skipped.

Thinking about the cleanest fix: track an _overlayInitialized flag per overlay instance that _onContextRestored sets (and _onContextLost/_onRemove clears), then defer the fallback via queueMicrotask so Google Maps' own onContextRestored can fire first. If it does, the flag gates out the duplicate. If it doesn't (the re-add case), we fetch the existing context.

Does that approach sound right to you before I push?

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 am still not convinced. As I commented before:

onRemove does not delete this._deck, only hides it.
Only places where this._deck is set to null is in onContextLost and finalize.
So either you are calling finalize in your own app, after which you should not call setMap again; or Google Maps is invoking onContextLost but not onContextRestored. Neither of these scenarios are for this component to handle.

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] Subsequent GoogleMapsOverlays fail to render after an overlay has been added then removed when interleaved=true

2 participants