• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / deck.gl / 13958303482

19 Mar 2025 10:59PM UTC coverage: 91.63% (+0.001%) from 91.629%
13958303482

Pull #9531

github

web-flow
Merge 730f98b88 into cb17e8241
Pull Request #9531: feat: Port point-cloud-layer to WebGPU

6705 of 7355 branches covered (91.16%)

Branch coverage included in aggregate %.

124 of 131 new or added lines in 4 files covered. (94.66%)

18 existing lines in 2 files now uncovered.

55147 of 60147 relevant lines covered (91.69%)

14595.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.8
/modules/layers/src/point-cloud-layer/point-cloud-layer.ts
1
// deck.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4

1✔
5
import {
1✔
6
  Layer,
1✔
7
  project32,
1✔
8
  picking,
1✔
9
  UNIT,
1✔
10
  LayerProps,
1✔
11
  LayerDataSource,
1✔
12
  UpdateParameters,
1✔
13
  Unit,
1✔
14
  AccessorFunction,
1✔
15
  Position,
1✔
16
  Accessor,
1✔
17
  Color,
1✔
18
  Material,
1✔
19
  DefaultProps
1✔
20
} from '@deck.gl/core';
1✔
21
import {Parameters} from '@luma.gl/core';
1✔
22
import {Model, Geometry} from '@luma.gl/engine';
1✔
23
import {gouraudMaterial} from '@luma.gl/shadertools';
1✔
24

1✔
25
import {pointCloudUniforms, PointCloudProps} from './point-cloud-layer-uniforms';
1✔
26
import vs from './point-cloud-layer-vertex.glsl';
1✔
27
import fs from './point-cloud-layer-fragment.glsl';
1✔
28
import source from './point-cloud-layer.wgsl';
1✔
29

1✔
30
const DEFAULT_COLOR: [number, number, number, number] = [0, 0, 0, 255];
1✔
31
const DEFAULT_NORMAL: [number, number, number] = [0, 0, 1];
1✔
32

1✔
33
const defaultProps: DefaultProps<PointCloudLayerProps> = {
1✔
34
  sizeUnits: 'pixels',
1✔
35
  pointSize: {type: 'number', min: 0, value: 10}, //  point radius in pixels
1✔
36

1✔
37
  getPosition: {type: 'accessor', value: (x: any) => x.position},
1✔
38
  getNormal: {type: 'accessor', value: DEFAULT_NORMAL},
1✔
39
  getColor: {type: 'accessor', value: DEFAULT_COLOR},
1✔
40

1✔
41
  material: true,
1✔
42

1✔
43
  // Depreated
1✔
44
  radiusPixels: {deprecatedFor: 'pointSize'}
1✔
45
};
1✔
46

1✔
47
// support loaders.gl point cloud format
1✔
48
function normalizeData(data) {
11✔
49
  const {header, attributes} = data;
11✔
50
  if (!header || !attributes) {
11✔
51
    return;
10✔
52
  }
10✔
53

1✔
54
  data.length = header.vertexCount;
1✔
55

1✔
56
  if (attributes.POSITION) {
1✔
57
    attributes.instancePositions = attributes.POSITION;
1✔
58
  }
1✔
59
  if (attributes.NORMAL) {
1✔
60
    attributes.instanceNormals = attributes.NORMAL;
1✔
61
  }
1✔
62
  if (attributes.COLOR_0) {
1✔
63
    const {size, value} = attributes.COLOR_0;
1✔
64
    attributes.instanceColors = {size, type: 'unorm8', value};
1✔
65
  }
1✔
66
}
11✔
67

1✔
68
/** All properties supported by PointCloudLayer. */
1✔
69
export type PointCloudLayerProps<DataT = unknown> = _PointCloudLayerProps<DataT> & LayerProps;
1✔
70

1✔
71
/** Properties added by PointCloudLayer. */
1✔
72
type _PointCloudLayerProps<DataT> = {
1✔
73
  data: LayerDataSource<DataT>;
1✔
74
  /**
1✔
75
   * The units of the point size, one of `'meters'`, `'common'`, and `'pixels'`.
1✔
76
   * @default 'pixels'
1✔
77
   */
1✔
78
  sizeUnits?: Unit;
1✔
79

1✔
80
  /**
1✔
81
   * Global radius of all points, in units specified by `sizeUnits`
1✔
82
   * @default 10
1✔
83
   */
1✔
84
  pointSize?: number;
1✔
85

1✔
86
  /**
1✔
87
   * @deprecated Use `pointSize` instead
1✔
88
   */
1✔
89
  radiusPixels?: number;
1✔
90

1✔
91
  /**
1✔
92
   * Material settings for lighting effect.
1✔
93
   *
1✔
94
   * @default true
1✔
95
   * @see https://deck.gl/docs/developer-guide/using-lighting
1✔
96
   */
1✔
97
  material?: Material;
1✔
98

1✔
99
  /**
1✔
100
   * Method called to retrieve the position of each object.
1✔
101
   * @default object => object.position
1✔
102
   */
1✔
103
  getPosition?: AccessorFunction<DataT, Position>;
1✔
104

1✔
105
  /**
1✔
106
   * The normal of each object, in `[nx, ny, nz]`.
1✔
107
   * @default [0, 0, 1]
1✔
108
   */
1✔
109
  getNormal?: Accessor<DataT, [number, number, number]>;
1✔
110

1✔
111
  /**
1✔
112
   * The rgba color is in the format of `[r, g, b, [a]]`
1✔
113
   * @default [0, 0, 0, 255]
1✔
114
   */
1✔
115
  getColor?: Accessor<DataT, Color>;
1✔
116
};
1✔
117

1✔
118
/** Render a point cloud with 3D positions, normals and colors. */
1✔
119
export default class PointCloudLayer<DataT = any, ExtraPropsT extends {} = {}> extends Layer<
1✔
120
  ExtraPropsT & Required<_PointCloudLayerProps<DataT>>
1✔
121
> {
1✔
122
  static layerName = 'PointCloudLayer';
1✔
123
  static defaultProps = defaultProps;
1✔
124

1✔
125
  state!: {
1✔
126
    model?: Model;
1✔
127
  };
1✔
128

1✔
129
  getShaders() {
1✔
130
    return super.getShaders({
5✔
131
      vs,
5✔
132
      fs,
5✔
133
      source,
5✔
134
      modules: [project32, gouraudMaterial, picking, pointCloudUniforms]
5✔
135
    });
5✔
136
  }
5✔
137

1✔
138
  initializeState() {
1✔
139
    this.getAttributeManager()!.addInstanced({
5✔
140
      instancePositions: {
5✔
141
        size: 3,
5✔
142
        type: 'float64',
5✔
143
        fp64: this.use64bitPositions(),
5✔
144
        transition: true,
5✔
145
        accessor: 'getPosition'
5✔
146
      },
5✔
147
      instanceNormals: {
5✔
148
        size: 3,
5✔
149
        transition: true,
5✔
150
        accessor: 'getNormal',
5✔
151
        defaultValue: DEFAULT_NORMAL
5✔
152
      },
5✔
153
      instanceColors: {
5✔
154
        size: this.props.colorFormat.length,
5✔
155
        type: 'unorm8',
5✔
156
        transition: true,
5✔
157
        accessor: 'getColor',
5✔
158
        defaultValue: DEFAULT_COLOR
5✔
159
      }
5✔
160
    });
5✔
161
  }
5✔
162

1✔
163
  updateState(params: UpdateParameters<this>): void {
1✔
164
    const {changeFlags, props} = params;
18✔
165
    super.updateState(params);
18✔
166
    if (changeFlags.extensionsChanged) {
18✔
167
      this.state.model?.destroy();
5!
168
      this.state.model = this._getModel();
5✔
169
      this.getAttributeManager()!.invalidateAll();
5✔
170
    }
5✔
171
    if (changeFlags.dataChanged) {
18✔
172
      normalizeData(props.data);
11✔
173
    }
11✔
174
  }
18✔
175

1✔
176
  draw({uniforms}) {
1✔
177
    const {pointSize, sizeUnits} = this.props;
19✔
178
    const model = this.state.model!;
19✔
179
    const pointCloudProps: PointCloudProps = {
19✔
180
      sizeUnits: UNIT[sizeUnits],
19✔
181
      radiusPixels: pointSize
19✔
182
    };
19✔
183
    model.shaderInputs.setProps({pointCloud: pointCloudProps});
19✔
184
    if (this.context.device.type === 'webgpu') {
19!
NEW
185
      // @ts-expect-error TODO - this line was needed during WebGPU port
×
NEW
186
      model.instanceCount = this.props.data.length;
×
NEW
187
    }
×
188
    model.draw(this.context.renderPass);
19✔
189
  }
19✔
190

1✔
191
  protected _getModel(): Model {
1✔
192
    // TODO(ibgreen): WebGPU complication: Matching attachment state of the renderpass requires including a depth buffer
5✔
193
    const parameters =
5✔
194
      this.context.device.type === 'webgpu'
5!
NEW
195
        ? ({
×
NEW
196
            depthWriteEnabled: true,
×
NEW
197
            depthCompare: 'less-equal'
×
NEW
198
          } satisfies Parameters)
×
199
        : undefined;
5✔
200

5✔
201
    // a triangle that minimally cover the unit circle
5✔
202
    const positions: number[] = [];
5✔
203
    for (let i = 0; i < 3; i++) {
5✔
204
      const angle = (i / 3) * Math.PI * 2;
15✔
205
      positions.push(Math.cos(angle) * 2, Math.sin(angle) * 2, 0);
15✔
206
    }
15✔
207

5✔
208
    return new Model(this.context.device, {
5✔
209
      ...this.getShaders(),
5✔
210
      id: this.props.id,
5✔
211
      bufferLayout: this.getAttributeManager()!.getBufferLayouts(),
5✔
212
      geometry: new Geometry({
5✔
213
        topology: 'triangle-list',
5✔
214
        attributes: {
5✔
215
          positions: new Float32Array(positions)
5✔
216
        }
5✔
217
      }),
5✔
218
      parameters,
5✔
219
      isInstanced: true
5✔
220
    });
5✔
221
  }
5✔
222
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc