Skip to content

fix(core): render repeated map viewports correctly with WebGPU - #10536

Merged
Pessimistress merged 3 commits into
masterfrom
codex/webgpu-repeated-map-viewports
Aug 7, 2026
Merged

fix(core): render repeated map viewports correctly with WebGPU#10536
Pessimistress merged 3 commits into
masterfrom
codex/webgpu-repeated-map-viewports

Conversation

@ibgreen-openai

@ibgreen-openai ibgreen-openai commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Goal

Fix WebGPU rendering and picking of repeated Mercator world copies reported in #10534 (comment).

Root cause

Repeated viewports redraw the same model with different projection uniforms. luma.gl currently reuses one managed uniform buffer per shader module, and the implicit WebGPU Model.draw() path updates it with GPUQueue.writeBuffer(). When those draws share one submission, every encoded draw can observe the final viewport's projection.

Changes

  • Move repeated-view scheduling above LayersPass into a shared helper used by DeckRenderer and both synchronous/asynchronous DeckPicker paths.
  • Render each WebGPU world copy through an ordinary, independently submitted layer pass; leave WebGL and single-viewport rendering unchanged.
  • Keep the logical parent viewport, activate it once, preserve layer stacking and framebuffer/depth contents across continuation passes, and run effect pre/post processing once per frame.
  • Preserve one picking-color encoder across scheduled picking passes so every world copy decodes to its layer, object, and logical parent viewport.
  • Remove the previous in-pass projection-uniform serialization/staging workaround; LayersPass only receives a selected physical subviewport.
  • Add real-WebGPU framebuffer readback for all three rendered and picked worlds, renderer/effect lifecycle coverage, and a picking-color/framebuffer-continuation regression.

Possible luma.gl solution

A cleaner upstream fix would give each draw an immutable uniform snapshot. UniformStore/Model.draw() could allocate aligned per-draw slices from a frame-scoped uniform ring buffer and bind each slice through buffer ranges or WebGPU dynamic offsets; alternatively, each draw could receive its own uniform buffer/bind group. luma.gl already has buffer-range bindings, hasDynamicOffset, and minUniformBufferOffsetAlignment, but WebGPURenderPass.setBindings() does not yet pass dynamic offsets to setBindGroup(). Completing that support would let repeated views share one render pass/submission without deck.gl-specific scheduling.

Validation

  • yarn lint
  • tsc --noEmit --pretty false -p modules/core/tsconfig.json
  • yarn vitest run --project headless test/modules/core/passes/layers-pass.spec.ts test/modules/core/passes/pick-layers-pass.spec.ts test/modules/core/lib/deck-renderer.spec.ts test/modules/core/lib/deck-picker.spec.ts17 passed, including real-WebGPU rendering/picking of three world copies and shared picking-color decoding.
  • yarn vitest run --project render test/render/test-cases/views.spec.ts -t map-repeat1 golden-image test passed.
  • yarn vitest run --project node test/modules/imports.node.spec.ts test/modules/layers/core-layers.node.spec.ts15 passed.

Refs #10534.

Copy link
Copy Markdown
Collaborator Author

Possible luma.gl follow-up

The underlying issue is that a single mutable uniform buffer is reused across multiple encoded draws, while GPUQueue.writeBuffer() executes before the render-pass commands are submitted. An update → draw → update → draw sequence therefore becomes update → update → submit(draw, draw) from the GPU's perspective.

A general luma.gl fix would make each draw's uniforms immutable:

  1. Let UniformStore/Model.draw() allocate one per-draw slice from a frame-scoped uniform buffer, aligned to device.limits.minUniformBufferOffsetAlignment.
  2. Write each uniform snapshot to its own slice instead of overwriting the buffer used by earlier draws.
  3. Bind that slice using existing buffer-range bindings, or complete hasDynamicOffset support by forwarding dynamic offsets through WebGPURenderPass.setBindings() to setBindGroup().

That would fix repeated viewports and other same-model/multiple-uniform cases, including fill/stroke-style draws, while keeping a single render pass and a single submission. This PR is the focused deck.gl-side workaround until that upstream behavior exists.

@coveralls

coveralls commented Aug 7, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 83.91% (+0.1%) from 83.761% — codex/webgpu-repeated-map-viewports into master

Copy link
Copy Markdown
Collaborator Author

Updated the proposed architecture after discussion: repeated-view scheduling now lives above LayersPass, shared by DeckRenderer and both picking paths. Each world copy runs as an ordinary layer pass with its own WebGPU submission; picking retains one color encoder across the sequence. This removes the previous deck.gl-specific projection-uniform serialization/staging and keeps effect processing once per frame.

The luma.gl-level improvement would still be immutable per-draw uniform snapshots: allocate aligned slices from a frame-scoped uniform ring, bind the appropriate range/dynamic offset for every draw, and forward WebGPU dynamic offsets through setBindGroup(). With that in place, repeated world draws could safely share a render pass/submission without either the old uniform-copy workaround or the new multi-submit scheduling.

@Pessimistress
Pessimistress force-pushed the codex/webgpu-repeated-map-viewports branch from 5eea9c7 to bb9d56b Compare August 7, 2026 18:51
@Pessimistress
Pessimistress marked this pull request as ready for review August 7, 2026 18:52
@Pessimistress
Pessimistress merged commit b501b53 into master Aug 7, 2026
5 checks passed
@Pessimistress
Pessimistress deleted the codex/webgpu-repeated-map-viewports branch August 7, 2026 19:04
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