• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

visgl / luma.gl / 23357312823

20 Mar 2026 06:35PM UTC coverage: 58.158% (+5.9%) from 52.213%
23357312823

Pull #2555

github

web-flow
Merge 71b78bbe2 into fc5791b65
Pull Request #2555: chore: Run tests on src instead of dist

3021 of 6029 branches covered (50.11%)

Branch coverage included in aggregate %.

7102 of 11377 relevant lines covered (62.42%)

243.33 hits per line

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

74.67
/modules/engine/src/geometry/gpu-geometry.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import type {PrimitiveTopology, BufferLayout} from '@luma.gl/core';
6
import {Device, Buffer, vertexFormatDecoder} from '@luma.gl/core';
7
import type {Geometry} from '../geometry/geometry';
8
import {uid} from '../utils/uid';
9

10
export type GPUGeometryProps = {
11
  id?: string;
12
  /** Determines how vertices are read from the 'vertex' attributes */
13
  topology: 'point-list' | 'line-list' | 'line-strip' | 'triangle-list' | 'triangle-strip';
14
  /** Auto calculated from attributes if not provided */
15
  vertexCount: number;
16
  bufferLayout: BufferLayout[];
17
  indices?: Buffer | null;
18
  attributes: Record<string, Buffer>;
19
};
20

21
export class GPUGeometry {
22
  readonly id: string;
23
  userData: Record<string, unknown> = {};
24✔
24

25
  /** Determines how vertices are read from the 'vertex' attributes */
26
  readonly topology?: PrimitiveTopology;
27
  readonly bufferLayout: BufferLayout[] = [];
24✔
28

29
  readonly vertexCount: number;
30
  readonly indices?: Buffer | null;
31
  readonly attributes: Record<string, Buffer>;
32

33
  constructor(props: GPUGeometryProps) {
34
    this.id = props.id || uid('geometry');
24✔
35
    this.topology = props.topology;
24✔
36
    this.indices = props.indices || null;
24✔
37
    this.attributes = props.attributes;
24✔
38

39
    this.vertexCount = props.vertexCount;
24✔
40

41
    this.bufferLayout = props.bufferLayout || [];
24!
42

43
    if (this.indices) {
24✔
44
      if (!(this.indices.usage & Buffer.INDEX)) {
19!
45
        throw new Error('Index buffer must have INDEX usage');
×
46
      }
47
    }
48
  }
49

50
  destroy(): void {
51
    this.indices?.destroy();
21✔
52
    for (const attribute of Object.values(this.attributes)) {
21✔
53
      attribute.destroy();
63✔
54
    }
55
  }
56

57
  getVertexCount(): number {
58
    return this.vertexCount;
×
59
  }
60

61
  getAttributes(): Record<string, Buffer> {
62
    return this.attributes;
×
63
  }
64

65
  getIndexes(): Buffer | null {
66
    return this.indices || null;
×
67
  }
68

69
  _calculateVertexCount(positions: Buffer): number {
70
    // Assume that positions is a fully packed float32x3 buffer
71
    const vertexCount = positions.byteLength / 12;
×
72
    return vertexCount;
×
73
  }
74
}
75

76
export function makeGPUGeometry(device: Device, geometry: Geometry | GPUGeometry): GPUGeometry {
77
  if (geometry instanceof GPUGeometry) {
24!
78
    return geometry;
×
79
  }
80

81
  const indices = getIndexBufferFromGeometry(device, geometry);
24✔
82
  const {attributes, bufferLayout} = getAttributeBuffersFromGeometry(device, geometry);
24✔
83
  return new GPUGeometry({
24✔
84
    topology: geometry.topology || 'triangle-list',
24!
85
    bufferLayout,
86
    vertexCount: geometry.vertexCount,
87
    indices,
88
    attributes
89
  });
90
}
91

92
export function getIndexBufferFromGeometry(device: Device, geometry: Geometry): Buffer | undefined {
93
  if (!geometry.indices) {
24✔
94
    return undefined;
5✔
95
  }
96
  const data = geometry.indices.value;
19✔
97
  return device.createBuffer({usage: Buffer.INDEX, data});
19✔
98
}
99

100
export function getAttributeBuffersFromGeometry(
101
  device: Device,
102
  geometry: Geometry
103
): {attributes: Record<string, Buffer>; bufferLayout: BufferLayout[]; vertexCount: number} {
104
  const bufferLayout: BufferLayout[] = [];
24✔
105

106
  const attributes: Record<string, Buffer> = {};
24✔
107
  for (const [attributeName, attribute] of Object.entries(geometry.attributes)) {
24✔
108
    let name: string = attributeName;
69✔
109
    // TODO Map some GLTF attribute names (is this still needed?)
110
    switch (attributeName) {
69!
111
      case 'POSITION':
112
        name = 'positions';
19✔
113
        break;
19✔
114
      case 'NORMAL':
115
        name = 'normals';
19✔
116
        break;
19✔
117
      case 'TEXCOORD_0':
118
        name = 'texCoords';
16✔
119
        break;
16✔
120
      case 'COLOR_0':
121
        name = 'colors';
×
122
        break;
×
123
    }
124
    if (attribute) {
69!
125
      attributes[name] = device.createBuffer({
69✔
126
        data: attribute.value,
127
        id: `${attributeName}-buffer`
128
      });
129
      const {value, size, normalized} = attribute;
69✔
130
      if (size === undefined) {
69!
131
        throw new Error(`Attribute ${attributeName} is missing a size`);
×
132
      }
133
      bufferLayout.push({
69✔
134
        name,
135
        format: vertexFormatDecoder.getVertexFormatFromAttribute(value, size, normalized)
136
      });
137
    }
138
  }
139

140
  const vertexCount = geometry._calculateVertexCount(geometry.attributes, geometry.indices);
24✔
141

142
  return {attributes, bufferLayout, vertexCount};
24✔
143
}
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