Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/website/highway/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {GeoJsonLayer} from '@deck.gl/layers';
import {scaleLinear, scaleThreshold} from 'd3-scale';
import {CSVLoader} from '@loaders.gl/csv';
import {load} from '@loaders.gl/core';
import {Device} from '@luma.gl/core';

import {Feature, LineString, MultiLineString} from 'geojson';
import type {Color, PickingInfo, MapViewState} from '@deck.gl/core';
Expand Down Expand Up @@ -130,11 +131,13 @@ function renderTooltip({
}

export default function App({
device,
roads = DATA_URL.ROADS,
year,
accidents,
mapStyle = MAP_STYLE
}: {
device?: Device;
roads?: string | Road[];
accidents?: Accident[];
year?: number;
Expand Down Expand Up @@ -183,6 +186,7 @@ export default function App({

return (
<DeckGL
device={device}
layers={layers}
pickingRadius={5}
initialViewState={INITIAL_VIEW_STATE}
Expand Down
23 changes: 20 additions & 3 deletions modules/layers/src/path-layer/path-layer-uniforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,23 @@

import type {ShaderModule} from '@luma.gl/shadertools';

const uniformBlock = `\
const uniformBlockWGSL = /* wgsl */ `\
struct PathUniforms {
widthScale: f32,
widthMinPixels: f32,
widthMaxPixels: f32,
jointType: f32,
capType: f32,
miterLimit: f32,
billboard: f32,
widthUnits: i32,
};

@group(0) @binding(1)
var<uniform> path: PathUniforms;
`;

const uniformBlockGLSL = `\
uniform pathUniforms {
float widthScale;
float widthMinPixels;
Expand All @@ -30,8 +46,9 @@ export type PathProps = {

export const pathUniforms = {
name: 'path',
vs: uniformBlock,
fs: uniformBlock,
source: uniformBlockWGSL,
vs: uniformBlockGLSL,
fs: uniformBlockGLSL,
uniformTypes: {
widthScale: 'f32',
widthMinPixels: 'f32',
Expand Down
240 changes: 187 additions & 53 deletions modules/layers/src/path-layer/path-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {Layer, project32, picking, UNIT} from '@deck.gl/core';
import {Layer, project32, color, picking, UNIT} from '@deck.gl/core';
import {Parameters} from '@luma.gl/core';
import {Geometry} from '@luma.gl/engine';
import {Model} from '@luma.gl/engine';
import PathTesselator from './path-tesselator';

import {pathUniforms, PathProps} from './path-layer-uniforms';
import source from './path-layer.wgsl';
import vs from './path-layer-vertex.glsl';
import fs from './path-layer-fragment.glsl';

Expand Down Expand Up @@ -135,75 +137,144 @@ export default class PathLayer<DataT = any, ExtraPropsT extends {} = {}> extends
};

getShaders() {
return super.getShaders({vs, fs, modules: [project32, picking, pathUniforms]}); // 'project' module added by default.
return super.getShaders({vs, fs, source, modules: [project32, color, picking, pathUniforms]}); // 'project' module added by default.
}

get wrapLongitude(): boolean {
return false;
}

getBounds(): [number[], number[]] | null {
if (this.context.device.type === 'webgpu') {
return null;
}
return this.getAttributeManager()?.getBounds(['vertexPositions']);
}

initializeState() {
const noAlloc = true;
const attributeManager = this.getAttributeManager();
const enableTransitions = this.context.device.type !== 'webgpu';
/* eslint-disable max-len */
attributeManager!.addInstanced({
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?

type: 'float64',
fp64: this.use64bitPositions(),
transition: ATTRIBUTE_TRANSITION,
accessor: 'getPath',
// eslint-disable-next-line @typescript-eslint/unbound-method
update: this.calculatePositions,
noAlloc,
shaderAttributes: {
instanceLeftPositions: {
vertexOffset: 0
},
instanceStartPositions: {
vertexOffset: 1
if (this.context.device.type === 'webgpu') {
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.

transition: false,
accessor: 'getPath',
// eslint-disable-next-line @typescript-eslint/unbound-method
update: this.calculateInstancePositions,
shaderAttributes: {
instanceLeftPositions: {size: 3, elementOffset: 0},
instanceStartPositions: {size: 3, elementOffset: 3},
instanceEndPositions: {size: 3, elementOffset: 6},
instanceRightPositions: {size: 3, elementOffset: 9}
},
instanceEndPositions: {
vertexOffset: 2
noAlloc
},
instancePositions64Low: {
size: 12,
type: 'float32',
transition: false,
accessor: 'getPath',
// eslint-disable-next-line @typescript-eslint/unbound-method
update: this.calculateInstancePositions64Low,
shaderAttributes: {
instanceLeftPositions64Low: {size: 3, elementOffset: 0},
instanceStartPositions64Low: {size: 3, elementOffset: 3},
instanceEndPositions64Low: {size: 3, elementOffset: 6},
instanceRightPositions64Low: {size: 3, elementOffset: 9}
},
instanceRightPositions: {
vertexOffset: 3
noAlloc
},
instanceTypes: {
size: 1,
// eslint-disable-next-line @typescript-eslint/unbound-method
update: this.calculateSegmentTypes,
noAlloc
},
instanceStrokeWidths: {
size: 1,
accessor: 'getWidth',
transition: false,
defaultValue: 1
},
instanceColors: {
size: this.props.colorFormat.length,
type: 'unorm8',
accessor: 'getColor',
transition: false,
defaultValue: DEFAULT_COLOR
},
instancePickingColors: {
size: 4,
type: 'uint8',
accessor: (object, {index, target: value}) =>
this.encodePickingColor(
object && object.__source ? object.__source.index : index,
value
)
}
});
} else {
attributeManager!.addInstanced({
vertexPositions: {
size: 3,
// Start filling buffer from 1 vertex in
vertexOffset: 1,
type: 'float64',
fp64: this.use64bitPositions(),
transition: enableTransitions ? ATTRIBUTE_TRANSITION : false,
accessor: 'getPath',
// eslint-disable-next-line @typescript-eslint/unbound-method
update: this.calculatePositions,
noAlloc,
shaderAttributes: {
instanceLeftPositions: {
vertexOffset: 0
},
instanceStartPositions: {
vertexOffset: 1
},
instanceEndPositions: {
vertexOffset: 2
},
instanceRightPositions: {
vertexOffset: 3
}
}
},
instanceTypes: {
size: 1,
// eslint-disable-next-line @typescript-eslint/unbound-method
update: this.calculateSegmentTypes,
noAlloc
},
instanceStrokeWidths: {
size: 1,
accessor: 'getWidth',
transition: enableTransitions ? ATTRIBUTE_TRANSITION : false,
defaultValue: 1
},
instanceColors: {
size: this.props.colorFormat.length,
type: 'unorm8',
accessor: 'getColor',
transition: enableTransitions ? ATTRIBUTE_TRANSITION : false,
defaultValue: DEFAULT_COLOR
},
instancePickingColors: {
size: 4,
type: 'uint8',
accessor: (object, {index, target: value}) =>
this.encodePickingColor(
object && object.__source ? object.__source.index : index,
value
)
}
},
instanceTypes: {
size: 1,
type: 'uint8',
// eslint-disable-next-line @typescript-eslint/unbound-method
update: this.calculateSegmentTypes,
noAlloc
},
instanceStrokeWidths: {
size: 1,
accessor: 'getWidth',
transition: ATTRIBUTE_TRANSITION,
defaultValue: 1
},
instanceColors: {
size: this.props.colorFormat.length,
type: 'unorm8',
accessor: 'getColor',
transition: ATTRIBUTE_TRANSITION,
defaultValue: DEFAULT_COLOR
},
instancePickingColors: {
size: 4,
type: 'uint8',
accessor: (object, {index, target: value}) =>
this.encodePickingColor(object && object.__source ? object.__source.index : index, value)
}
});
});
}
/* eslint-enable max-len */

this.setState({
Expand Down Expand Up @@ -317,6 +388,31 @@ export default class PathLayer<DataT = any, ExtraPropsT extends {} = {}> extends
}

protected _getModel(): Model {
const parameters =
this.context.device.type === 'webgpu'
? ({
depthWriteEnabled: true,
depthCompare: 'less-equal'
} satisfies Parameters)
: undefined;
const bufferLayout =
this.context.device.type === 'webgpu'
? 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?

layout.name === 'vertexPositions'
? {
...layout,
attributes: (layout.attributes || []).filter(
attribute =>
attribute.attribute !== 'vertexPositions' &&
attribute.attribute !== 'vertexPositions64Low'
)
}
: layout
);

/*
* _
* "-_ 1 3 5
Expand Down Expand Up @@ -364,14 +460,15 @@ export default class PathLayer<DataT = any, ExtraPropsT extends {} = {}> extends
return new Model(this.context.device, {
...this.getShaders(),
id: this.props.id,
bufferLayout: this.getAttributeManager()!.getBufferLayouts(),
bufferLayout,
geometry: new Geometry({
topology: 'triangle-list',
attributes: {
indices: new Uint16Array(SEGMENT_INDICES),
positions: {value: new Float32Array(SEGMENT_POSITIONS), size: 2}
}
}),
parameters,
isInstanced: true
});
}
Expand All @@ -383,10 +480,47 @@ export default class PathLayer<DataT = any, ExtraPropsT extends {} = {}> extends
attribute.value = pathTesselator.get('positions');
}

protected calculateInstancePositions(attribute) {
this._calculateInterleavedInstancePositions(attribute, false);
}

protected calculateInstancePositions64Low(attribute) {
this._calculateInterleavedInstancePositions(attribute, true);
}

protected calculateSegmentTypes(attribute) {
const {pathTesselator} = this.state;

attribute.startIndices = pathTesselator.vertexStarts;
attribute.value = pathTesselator.get('segmentTypes');
}

protected _calculateInterleavedInstancePositions(attribute, lowPart: boolean) {
const {pathTesselator} = this.state;
const value = pathTesselator.get('positions');

if (!value) {
attribute.value = null;
return;
}

const numInstances = pathTesselator.instanceCount;
const result = new Float32Array(numInstances * 12);

for (let i = 0; i < numInstances; i++) {
const sourceIndex = i * 3;
const targetIndex = i * 12;
for (let vertexOffset = 0; vertexOffset < 4; vertexOffset++) {
const sourceOffset = sourceIndex + vertexOffset * 3;
const targetOffset = targetIndex + vertexOffset * 3;
for (let j = 0; j < 3; j++) {
const position = value[sourceOffset + j];
result[targetOffset + j] = lowPart ? position - Math.fround(position) : position;
}
}
}

attribute.startIndices = pathTesselator.vertexStarts;
attribute.value = result;
}
}
Loading
Loading