Skip to content

feat(layers) Port PathLayer to WebGPU#10113

Open
ibgreen wants to merge 5 commits intomasterfrom
ib/path-layer-webgpu
Open

feat(layers) Port PathLayer to WebGPU#10113
ibgreen wants to merge 5 commits intomasterfrom
ib/path-layer-webgpu

Conversation

@ibgreen
Copy link
Copy Markdown
Collaborator

@ibgreen ibgreen commented Mar 18, 2026

Background

  • Port of path layer to WebGPU
  • picking works
image

Summary

This PR ports PathLayer to WGSL/WebGPU and updates the website highway/path example to support device switching and device stats.

Changes

PathLayer WebGPU/WGSL support

  • Added a WGSL shader for PathLayer.
  • Added WGSL path uniforms and wired them into the existing shader module.
  • Enabled PathLayer to provide a WGSL shader source alongside the existing GLSL shaders.
  • Added WebGPU-specific model setup for PathLayer, including depth state configuration.

PathLayer attribute/model plumbing

  • Added a WebGPU-specific attribute path for PathLayer that avoids the WebGL packed vertexOffset pattern.
  • Interleaved neighboring path positions into explicit WebGPU-friendly buffers for:
    • left/start/end/right positions
    • left/start/end/right fp64 low parts
  • Disabled attribute transitions for PathLayer on WebGPU to avoid unsupported BufferTransform usage.
  • Changed segmentTypes storage from Uint8ClampedArray to Float32Array to satisfy WebGPU vertex/upload alignment requirements.
  • Skipped getBounds() on WebGPU for PathLayer, since the WebGPU attribute layout no longer exposes the original vertexPositions buffer used by the WebGL path.

WGSL shader parity fixes

  • Ported PathLayer join/cap extrusion logic to WGSL.
  • Added WGSL picking/highlight handling for PathLayer.
  • Used layer.opacity in WGSL, matching existing layer-module usage.
  • Corrected WebGPU position/projection handling so path geometry matches the WebGL rendering more closely.

Shared WGSL fix

  • Fixed project_get_orientation_matrix in project.wgsl.ts by correcting the select(...) branch order.

Website example updates

  • Enabled WebGL2/WebGPU device tabs for the highway/path example.
  • Passed the selected device through to the example app.
  • Added a StatsWidget (type: 'device') to the example.
  • Added WebGPU premultiplied-alpha blending parameters to the highway example so visible rendering matches the existing WebGPU examples.

Files changed

  • modules/layers/src/path-layer/path-layer.wgsl.ts
  • modules/layers/src/path-layer/path-layer-uniforms.ts
  • modules/layers/src/path-layer/path-layer.ts
  • modules/layers/src/path-layer/path-tesselator.ts
  • modules/core/src/shaderlib/project/project.wgsl.ts
  • website/src/examples/geojson-layer-paths.js
  • examples/website/highway/app.tsx

Validation

  • yarn lint
    • passes with warnings only
  • vitest run --project render
    • passes, including geojson-layer and path-layer render cases
  • yarn test
    • not fully clean in this environment due unrelated terrain fetch failures in headless tests

@coveralls
Copy link
Copy Markdown

coveralls commented Mar 22, 2026

Coverage Status

coverage: 80.299% (-0.1%) from 80.395%
when pulling a712ff3 on ib/path-layer-webgpu
into e784bae on master.

vertexPositions: {
size: 3,
// Start filling buffer from 1 vertex in
vertexOffset: 1,
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.

Has the vertexOffset: 1 been missed in the WebGPU port?

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.

On vertexOffset: 1

Good catch. It is preserved semantically in the WebGPU path.

WebGPU does not support the same packed vertexOffset layout we use in the WebGL path, so the port materializes the equivalent neighbor window explicitly in _calculateInterleavedInstancePositions() using offsets [-1, 0, 1, 2]. That reproduces the padded access pattern from vertexOffset: 1 rather than dropping it.

attributeManager!.addInstanced({
instancePositions: {
size: 12,
type: 'float32',
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 it expected we use float32? WebGL has float64.

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.

On float32 vs float64

This is still the fp64 path logically, not an intentional downgrade to plain fp32.

WGSL vertex attributes here are not using float64, so on WebGPU the layer uploads:

  • float32 high parts
  • float32 64Low residuals

and reconstructs through project_position_vec3_f64(...) in the shader. So the representation is different, but it is meant to preserve the same high/low position split as the existing fp64 path.


? this.getAttributeManager()!.getBufferLayouts()
: this.getAttributeManager()!
.getBufferLayouts()
.map(layout =>
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.

extract to helper?

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.

On extracting the buffer layout branch to a helper

Agreed. I extracted the _getModel() buffer-layout branching/filtering into a small helper to keep the model setup easier to read, without broadening the refactor beyond that.

type: opts.fp64 ? Float64Array : Float32Array
},
segmentTypes: {size: 1, type: Uint8ClampedArray}
segmentTypes: {size: 1, type: Float32Array}
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.

why this change? Can't we use Uint8Array?

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.

On Float32Array for segmentTypes

This change is to satisfy WebGPU alignment/layout requirements in the current shader path.

instanceTypes is consumed as f32 in WGSL, and WebGPU rejects the 1-byte stride / non-4-byte upload pattern that worked on the WebGL side. Keeping Uint8Array would require a separate padded representation (for example uint8x4/unorm8x4) plus extra packing and shader changes, which felt like more JS/shader churn than justified for this PR.

So Float32Array here is the minimal way to make the existing WebGPU path valid without reopening the data model further.

@ibgreen ibgreen marked this pull request as ready for review March 29, 2026 23:03
@chrisgervang chrisgervang added this to the v9.3 milestone Apr 1, 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