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

visgl / luma.gl / 23501099075

24 Mar 2026 04:40PM UTC coverage: 73.509% (-0.2%) from 73.683%
23501099075

Pull #2566

github

web-flow
Merge 8cb9da117 into a31dc56b8
Pull Request #2566: feat(gltf): KHR_animation_pointer

4749 of 7351 branches covered (64.6%)

Branch coverage included in aggregate %.

214 of 300 new or added lines in 9 files covered. (71.33%)

1 existing line in 1 file now uncovered.

10776 of 13769 relevant lines covered (78.26%)

272.42 hits per line

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

71.79
/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> = {};
29✔
24

25
  /** Determines how vertices are read from the 'vertex' attributes */
26
  readonly topology?: PrimitiveTopology;
27
  readonly bufferLayout: BufferLayout[] = [];
29✔
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');
29✔
35
    this.topology = props.topology;
29✔
36
    this.indices = props.indices || null;
29✔
37
    this.attributes = props.attributes;
29✔
38

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

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

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

50
  destroy(): void {
51
    this.indices?.destroy();
25✔
52
    for (const attribute of Object.values(this.attributes)) {
25✔
53
      attribute.destroy();
74✔
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) {
29!
78
    return geometry;
×
79
  }
80

81
  const indices = getIndexBufferFromGeometry(device, geometry);
29✔
82
  const {attributes, bufferLayout} = getAttributeBuffersFromGeometry(device, geometry);
29✔
83
  return new GPUGeometry({
29✔
84
    topology: geometry.topology || 'triangle-list',
29!
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) {
29✔
94
    return undefined;
5✔
95
  }
96
  const data = geometry.indices.value;
24✔
97
  return device.createBuffer({usage: Buffer.INDEX, data});
24✔
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[] = [];
29✔
105

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

143
  const vertexCount = geometry._calculateVertexCount(geometry.attributes, geometry.indices);
29✔
144

145
  return {attributes, bufferLayout, vertexCount};
29✔
146
}
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