Skip to content

fix(extensions): align billboard, 3D, justified, and offset dashes - #10580

Merged
chrisgervang merged 67 commits into
masterfrom
claude/deck-gl-path-dashing-2jnq8i-2
Aug 31, 2026
Merged

fix(extensions): align billboard, 3D, justified, and offset dashes#10580
chrisgervang merged 67 commits into
masterfrom
claude/deck-gl-path-dashing-2jnq8i-2

Conversation

@chrisgervang

@chrisgervang chrisgervang commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Stack position: Part 2 of 6; base master after #10579 merged. The test-only clipped-composition baseline #10625, option-typing cleanup #10616, and general Viewport#equals prerequisite #10611 are merged into master and synchronized here.

Goal

Align dash coordinates and phase across flat, billboarded, elevated, normalized, justified, and offset paths without changing the public API.

How to read the image diffs

This change improves how the renderer measures distance along a dashed line.

  • Think of the dash pattern as marks on a ruler laid along the line.
  • Before, that ruler could be stretched or squeezed when a line tilted through 3D, faced the camera, was widened by an offset, or crossed behind the camera.
  • Now the ruler follows the same path that is actually rendered, so the dashes keep their intended size and spacing.
  • Expected image changes look like dashes and gaps moving into more even positions, especially on elevated, billboarded, clipped, and offset paths.
  • The line's color and overall width should generally stay the same.
  • When part of a line passes behind the camera, the hidden part still counts toward the pattern. The visible dashes therefore do not restart or get squeezed into the remaining piece, and the cut end is shaped like a real visible boundary.

Changes

  • Normalize dash period through a half-width-in-pixels coordinate shared by both PathLayer extrusion branches.
  • Advance elevated and billboarded paths with rendered 3D arclength while compiling the additional GLSL/WGSL work only under DASH_ENABLED.
  • Derive continuous phase from normalized PathTessellator geometry, including Globe subdivision, antimeridian cuts, closed paths, binary paths, and partial updates.
  • Preserve the scalar public getDashOffsets; use private rendered-path metrics internally and reject unreadable GPU-only geometry unless explicit offsets are supplied.
  • Track shader-equivalent AUTO_OFFSET projection inputs and refresh only path metrics when needed, without retessellating ordinary paths at projection boundaries or during same-projection pan/zoom.
  • Isolate and document the layer projection props that invalidate CPU dash metrics; data, tessellation, and viewport-scale invalidation remain with their existing owners.
  • Preserve master WGSL clipping, geometry-position, and billboard hooks.
  • Preserve dash phase and dash justification when camera clipping shortens a billboard segment, while retaining the clipped visible interval for cap and miter coverage in GLSL and WGSL and keeping those bounds in the same coordinate after offset widening.
  • Guard short justified runs and preserve phase through offset widening.
  • Clarify that getDashArray values are relative to half the effective path width; for example, [4, 5] on a 10 px path produces 20 px dashes and 25 px gaps.
  • Supply the viewport fixture required by the production CPU benchmark.
  • Refresh test(render): baseline clipped PathStyle composition #10625's clipped billboard + offset + rounded-AA golden without changing its fixture, exposing this layer's arclength, phase, and visible-bound improvements as a direct image diff.

Benchmark results

GPU measurements use the same Apple M1 Max/ANGLE Metal/WebGL2, 3840×2160, 20-sample method as the merged baseline #10579. Production timing was measured at 3b6a81d40e. Direct commit 6affdb4d2b only supplies the CPU benchmark viewport fixture; direct commits ae0924f452, 85f58ab05f, and 50f3053053 add phase-preserving clipping, visible cap/joint bounds, and offset-coordinate consistency. These benchmark variants do not execute billboard clipping or combine offset with dashing. The bounds fix does add one dash-only varying to the flat dash variants, so the historical table is the closest measured reference rather than a post-fix A/B.

Workload Plain Segment dash Continuous path Offset only
100K sparse 1 px strokes 2.388 2.762 2.768 2.448
256 overlapping 64 px strokes 0.459 0.560 0.565 0.480

Plain and offset-only timing stayed effectively unchanged from part 1, confirming that DASH_ENABLED keeps the new arclength work out of those shaders. Correct rendered 3D arclength increased the sparse dash pass by about 0.16 ms; the overdraw-heavy result remained within 0.005 ms of the prior layer.

For 100,000 segments, normalized rendered-path metrics took about 2.9 ms at this head; the compatibility helper took about 1.5 ms for nested XYZ and 3.1 ms for flat XYZ. Path mode is the only mode that performs this CPU phase pass.

Correctness measurements improved at the same time: elevated-spacing relative standard deviation fell from 0.134 to 0.012 (about 91%), billboard and flat periods both became 34.8 px, and offsets 0/2/4/8 all retained the same 43.4 px period.

Validation

  • The standalone CPU benchmark now executes its production normalized-geometry row.
  • Camera-clipping, PathStyle, and analytic-AA tests passed 25/25; yarn lint passed, and the affected-test pre-commit gate passed 15/15.
  • Focused WebGL dash render suite: 25 passed, one intentional backend-selection skip.
  • The isolated no-MSAA clipped-composition render passed; its output matches the pre-fix test(render): baseline clipped PathStyle composition #10625 golden by 98.44%, making the intended churn directly reviewable.
  • Required GitHub checks and automated reviews are tracked on the current head.

Note

Medium Risk
Changes core PathLayer vertex shaders and PathStyleExtension attribute updates used by many map visualizations; behavior shifts are intentional but will alter existing dash rendering and golden images.

Overview
PathStyleExtension and PathLayer now measure dash length and phase in a single coordinate system so patterns stay consistent when geometry is tessellated, elevated, billboarded, offset, or clipped.

High-precision dashes no longer derive phase from a per-path getPath transform. A CPU calculateDashMetrics pass walks normalized tessellated segments (via new getPathSegmentIndices), projects positions with shader-matching logic (projectRenderedPathPosition), and fills instanceDashOffsets. PathLayer tracks projection/tessellation inputs and invalidates only those metrics when the viewport or modelMatrix changes, without retessellating on every pan/zoom. GPU-only binary paths must supply data.attributes.instanceDashOffsets or the layer throws a clear error.

Shaders (GLSL/WGSL, gated by DASH_ENABLED) scale dash periods to half-width in pixels, correct billboard vs flat extrusion, advance along true 3D arclength, preserve phase when clipLine shortens billboard segments (vPathBounds), fix short justified segments, and keep offset widening from drifting dash phase. Docs clarify that getDashArray is relative to half stroke width.

Tests and render-suite comments/goldens are updated to assert the corrected behavior (continuous phase on dense polylines, billboard parity, 3D elevation, clipping composition, offsets).

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

@coveralls

coveralls commented Aug 21, 2026

Copy link
Copy Markdown

Coverage Status

No base build to compare — claude/deck-gl-path-dashing-2jnq8i-2 into master

@chrisgervang
chrisgervang marked this pull request as ready for review August 21, 2026 23:15
Comment thread modules/layers/src/path-layer/path-layer-vertex.glsl.ts
@greptile-apps

greptile-apps Bot commented Aug 21, 2026

Copy link
Copy Markdown

Greptile Summary

The PR aligns dash coordinates and phase across PathLayer rendering modes without changing the public API.

  • Computes continuous dash metrics from normalized tessellated geometry and refreshes them when projection inputs change.
  • Applies consistent 3D, billboard, clipping, justification, and offset behavior across GLSL and WGSL.
  • Updates documentation, focused tests, benchmarks, and render-test goldens.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
modules/extensions/src/path-style/path-style-extension.ts Adds projection-aware CPU dash metrics and targeted invalidation for high-precision path dashes.
modules/extensions/src/path-style/shaders.glsl.ts Normalizes dash units, guards short justified runs, and preserves phase through offset widening.
modules/layers/src/path-layer/path-layer-vertex.glsl.ts Applies rendered 3D arclength and clipping-aware dash coordinates across the GLSL extrusion paths.
modules/layers/src/path-layer/path-layer.wgsl.ts Mirrors the GLSL arclength, clipping-bound, and dash-coordinate behavior for WebGPU.
modules/layers/src/path-layer/path-layer.ts Tracks tessellation and projection inputs so dash metrics are invalidated without unnecessary retessellation.
modules/layers/src/path-layer/path-tesselator.ts Exposes rendered segment traversal order for continuous CPU phase calculation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Path geometry] --> B[PathTessellator]
  B --> C[CPU dash metrics]
  C --> D[instanceDashOffsets]
  B --> E[PathLayer segment attributes]
  D --> F[Dash shaders]
  E --> F
  F --> G[3D and billboard arclength]
  G --> H[Clipping and offset correction]
  H --> I[Consistent rendered dash phase]
Loading

Reviews (34): Last reviewed commit: "test(render): document PathStyle dash ex..." | Re-trigger Greptile

@chrisgervang
chrisgervang force-pushed the claude/deck-gl-path-dashing-2jnq8i-2 branch 2 times, most recently from 1d25bdf to 6720bf7 Compare August 22, 2026 01:09
@chrisgervang
chrisgervang force-pushed the claude/deck-gl-path-dashing-2jnq8i-2 branch 3 times, most recently from a62c717 to b123295 Compare August 22, 2026 22:15
claude and others added 4 commits August 22, 2026 15:28
…ension

Adds a render-test matrix and an interactive app for PathStyleExtension dashing,
covering segment density, dash arrays, corners, zoom levels, device pixel ratio,
3D elevation and billboard/flat parity.

The golden images record current behavior, defects included, so that subsequent
work shows up as a direct image diff. Measured from these goldens with
getDashArray [4, 5]:

- Six strips drawing the identical straight line diverge purely by vertex count.
  1, 2 and 4 segments dash correctly at a 43.4px period, 12 segments drifts to
  55.4px, and 40 and 120 segments render fully solid. Dash phase restarts at
  every vertex, so once a segment is shorter than one dash period nothing is
  discarded.
- dashJustified collapses the same way; justification is also per segment.
- highPrecisionDash renders all six strips identically and correctly, confirming
  the continuous-arclength mechanism works and is merely opt-in.
- billboard: true combined with highPrecisionDash is broken: the same geometry
  renders solid when dense and at a 52.0px period, 1.5x too long, when sparse,
  against 34.8px for the flat copy.
- Dash length grows along paths that climb in Z, because the CPU accumulates 3D
  distance while the shader coordinate measures 2D.

No source changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GipN1niYRgRuLVat2wujw
@chrisgervang
chrisgervang force-pushed the claude/deck-gl-path-dashing-2jnq8i-2 branch 2 times, most recently from 2d981ba to 0b63723 Compare August 23, 2026 19:44
@chrisgervang
chrisgervang force-pushed the claude/deck-gl-path-dashing-2jnq8i-2 branch from 0b63723 to 340db32 Compare August 24, 2026 00:05
@chrisgervang
chrisgervang force-pushed the claude/deck-gl-path-dashing-2jnq8i-2 branch from 340db32 to d73780d Compare August 24, 2026 00:12
chrisgervang and others added 4 commits August 26, 2026 15:23
Dashing measured its along-path coordinate in units that differ between the two
extrusion branches of the path layer, so the same geometry dashed differently
depending on the billboard prop.

At the point the dash shaders run, `width` is in common units for a flat path
but already in pixels for a billboarded one, because the clip-space conversion
happens inside getLineJoinOffset. That conversion also folds in
project.focalDistance, which scales the half-width the segment delta is divided
by but not the delta itself. Two consequences, both visible in the render tests:
dividing the CPU-side dash offsets by width.x dropped a whole factor of
project.scale when billboarded, and the dash period came out focalDistance times
too long. On a MapView at z14 a billboarded path rendered solid when dense and at
a 52.0px period when sparse, against 34.8px for the flat copy of the same line.
Both now measure 34.8px.

Everything is normalized through one half-width-in-pixels quantity that is
correct in either branch. getDashArray is documented relative to the stroke,
which flat paths already honored, so the billboard case is corrected onto the
flat one rather than the other way round.

Also:

- The shader now scales its along-segment coordinate by the 3D-to-2D arclength
  ratio, so it agrees with the CPU-side offsets, which have always accumulated
  3D distance. Previously the two advanced at different rates and the pattern
  broke up at every joint: on a path descending 900 units over a 720 unit run,
  dash spacing had a relative standard deviation of 0.134, now 0.012. Both
  vPathLength and vPathPosition.y are scaled alike, so the fragment shader joint
  tests are unaffected. Billboard mode is excluded, having already resolved the
  segment to its screen projection. Mirrored in path-layer.wgsl.ts.
- dashJustified guarded against segments shorter than half a dash period, which
  rounded to zero periods, made unitLength infinite and rendered the segment
  solid.
- The offset shaders now rescale vDashOffset alongside vPathPosition.y and
  vPathLength, so a dashed offset line stays in phase with an unoffset one. The
  render case that covers this used 40 segments, where every strip collapsed to
  solid via the segment-density defect and the image proved nothing; it now uses
  2 segments and all four offsets share one dash phase.
- getDashArray is documented relative to half the stroke width, which is what it
  has always measured.

Golden images updated for the billboard, 3D and offset cases. Flat, non-
billboarded dashing is byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GipN1niYRgRuLVat2wujw
@chrisgervang
chrisgervang force-pushed the claude/deck-gl-path-dashing-2jnq8i-2 branch from d73780d to 3a72563 Compare August 26, 2026 22:56
Comment thread modules/layers/src/path-layer/path-layer-vertex.glsl.ts
Base automatically changed from claude/deck-gl-path-dashing-2jnq8i to master August 30, 2026 23:18
chrisgervang and others added 8 commits August 30, 2026 18:36
# Conflicts:
#	test/bench/path-style-extension.bench.js
#	test/render/golden-images/path-dash-3d-billboard-pitched-rounded.png
#	test/render/golden-images/path-dash-3d-billboard.png
#	test/render/golden-images/path-dash-3d-flat-antialiasing.png
#	test/render/golden-images/path-dash-3d-flat.png
#	test/render/golden-images/path-dash-billboard-map-z10.png
#	test/render/golden-images/path-dash-billboard-map-z14.png
#	test/render/golden-images/path-dash-billboard-map-z18.png
#	test/render/golden-images/path-dash-billboard-pitched.png
#	test/render/golden-images/path-dash-offset-mode-path.png
#	test/render/golden-images/path-dash-offset.png
#	test/render/test-cases/path-dash.spec.ts
Comment thread modules/layers/src/path-layer/path-layer-vertex.glsl.ts

@chrisgervang chrisgervang left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comments for a follow up refactor. Merging as-is for a baseline fix

Comment thread modules/extensions/src/path-style/path-style-extension.ts Outdated
Comment thread modules/extensions/src/path-style/path-style-extension.ts
Comment thread modules/extensions/src/path-style/shaders.glsl.ts Outdated
Comment thread modules/extensions/src/path-style/shaders.glsl.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 c38672a. Configure here.

Comment thread modules/extensions/src/path-style/shaders.glsl.ts
@chrisgervang chrisgervang added this to the v9.4 milestone Aug 31, 2026
@chrisgervang
chrisgervang merged commit 0779ac4 into master Aug 31, 2026
8 checks passed
@chrisgervang
chrisgervang deleted the claude/deck-gl-path-dashing-2jnq8i-2 branch August 31, 2026 22:54
chrisgervang added a commit that referenced this pull request Aug 31, 2026
# Conflicts:
#	modules/extensions/src/path-style/shaders.glsl.ts
#	test/modules/extensions/path.spec.ts
#	test/modules/layers/path-antialiasing.spec.ts
#	test/render/golden-images/path-dash-3d-billboard-pitched-rounded.png
#	test/render/golden-images/path-dash-3d-billboard.png
#	test/render/golden-images/path-dash-3d-flat-antialiasing.png
#	test/render/golden-images/path-dash-3d-flat.png
#	test/render/golden-images/path-dash-billboard-map-z10.png
#	test/render/golden-images/path-dash-billboard-map-z14.png
#	test/render/golden-images/path-dash-billboard-map-z18.png
#	test/render/golden-images/path-dash-billboard-pitched.png
#	test/render/golden-images/path-dash-clipped-composition.png
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