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

visgl / luma.gl / 28705267989

04 Jul 2026 11:49AM UTC coverage: 72.054% (-0.1%) from 72.186%
28705267989

Pull #2733

github

web-flow
Merge 8c7886bfa into 30d8c7ace
Pull Request #2733: [codex] Declare physical GPU data struct formats inline

11275 of 17606 branches covered (64.04%)

Branch coverage included in aggregate %.

22 of 79 new or added lines in 3 files covered. (27.85%)

10 existing lines in 3 files now uncovered.

21723 of 28190 relevant lines covered (77.06%)

5622.03 hits per line

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

54.95
/modules/tables/src/table/gpu-data.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import {Buffer} from '@luma.gl/core';
6
import {DynamicBuffer} from '@luma.gl/engine';
7
import {GPUDataView} from './gpu-data-view';
8
import {
9
  isGPUDataStructFormat,
10
  normalizeGPUDataStructFormat,
11
  type GPUDataFormat,
12
  type GPUDataFormatDeclaration,
13
  type GPUDataStructFields,
14
  type GPUDataStructLayout,
15
  type GPUDataFormatT
16
} from './gpu-data-format';
17
import {getGPUVectorFormatInfo, type GPUVectorFormat} from './gpu-vector-format';
18

19
/** Optional caller-owned metadata retained on a GPU data range. */
20
export type GPUDataReadbackMetadata = any;
21

22
/** Constructor props that wrap one existing GPU data buffer. */
23
type GPUDataFromBufferBaseProps = {
24
  /** Stable dynamic GPU buffer wrapper for this data range. */
25
  buffer: Buffer | DynamicBuffer;
26
  /** Number of logical rows in the data range. */
27
  length: number;
28
  /** Number of fixed rows or flattened vertex-list values in the data range. */
29
  valueLength?: number;
30
  /** Number of scalar values represented by one fixed row or flattened element. */
31
  stride?: number;
32
  /** Byte offset of the first logical row. */
33
  byteOffset?: number;
34
  /** Bytes between adjacent fixed rows or flattened elements. */
35
  byteStride?: number;
36
  /** Number of bytes occupied by the fixed row or flattened element payload. */
37
  rowByteLength?: number;
38
  /** Whether this data view should destroy the buffer. */
39
  ownsBuffer?: boolean;
40
  /** Optional metadata owned by the producer, such as Arrow readback descriptors. */
41
  readbackMetadata?: GPUDataReadbackMetadata;
42
  /** Optional row offsets for variable-length values normalized to this data chunk. */
43
  valueOffsets?: Int32Array;
44
  /** Optional row validity bitmap normalized to this data chunk. */
45
  nullBitmap?: Uint8Array;
46
  /** Optional number of uploaded value bytes referenced by this data chunk. */
47
  valueByteLength?: number;
48
  /** Optional adapter-owned metadata; core tables do not inspect this value. */
49
  dataType?: unknown;
50
};
51

52
/** Constructor props that wrap one existing GPU data buffer. */
53
export type GPUDataFromBufferProps<
54
  Format extends GPUDataFormatDeclaration = GPUVectorFormat,
55
  Layout extends GPUDataStructLayout = 'wgsl-storage'
56
> = GPUDataFromBufferBaseProps &
57
  (
58
    | {
59
        /** Canonical scalar or list memory-layout descriptor for this data range. */
60
        format?: Extract<Format, GPUVectorFormat>;
61
        /** Struct layout rules are only valid with a struct format declaration. */
62
        layout?: never;
63
      }
64
    | ([Extract<Format, GPUDataStructFields>] extends [never]
65
        ? never
66
        : {
67
            /** Named fixed-width formats stored in each interleaved row. */
68
            format: Extract<Format, GPUDataStructFields>;
69
            /** Physical struct packing rules. Defaults to `wgsl-storage`. */
70
            layout?: Layout;
71
          })
72
  );
73

74
type GPUDataScalarFromBufferProps<Format extends GPUVectorFormat> = GPUDataFromBufferBaseProps & {
75
  format?: Format;
76
  layout?: never;
77
};
78

79
type GPUDataStructFromBufferProps<
80
  Fields extends GPUDataStructFields,
81
  Layout extends GPUDataStructLayout
82
> = GPUDataFromBufferBaseProps & {
83
  format: Fields;
84
  layout?: Layout;
85
};
86

87
class GPUDataBufferOwner {
88
  /** GPU buffer containing this chunk's bytes. */
89
  readonly buffer: Buffer | DynamicBuffer;
90
  private ownsDataBuffer: boolean;
91

92
  constructor(buffer: Buffer | DynamicBuffer, ownsBuffer: boolean) {
93
    this.buffer = buffer;
1,157✔
94
    this.ownsDataBuffer = ownsBuffer;
1,157✔
95
  }
96

97
  /** Whether this data range is responsible for destroying its backing buffer. */
98
  get ownsBuffer(): boolean {
99
    return this.ownsDataBuffer;
4✔
100
  }
101

102
  /** Transfers backing-buffer ownership to another GPUData chunk over the same buffer. */
103
  transferBufferOwnership(target: GPUDataBufferOwner): void {
104
    if (target.buffer !== this.buffer) {
1!
NEW
105
      throw new Error('GPUData ownership can only be transferred to the same buffer');
×
106
    }
107
    target.ownsDataBuffer = this.ownsDataBuffer;
1✔
108
    this.ownsDataBuffer = false;
1✔
109
  }
110

111
  /** Releases the backing buffer when this data range owns it. */
112
  destroy(): void {
113
    if (this.ownsDataBuffer) {
576✔
114
      this.buffer.destroy();
564✔
115
      this.ownsDataBuffer = false;
564✔
116
    }
117
  }
118
}
119

120
/** Runtime implementation behind the typed GPUData constructor interface. */
121
class GPUDataImpl extends GPUDataBufferOwner {
122
  /** Optional adapter-owned metadata; core tables do not inspect this value. */
123
  readonly dataType?: unknown;
124
  /** Canonical memory-layout descriptor for this data range. */
125
  readonly format?: GPUDataFormat;
126
  /** Number of logical rows in this chunk. */
127
  readonly length: number;
128
  /** Number of fixed rows or flattened vertex-list values in this chunk. */
129
  readonly valueLength: number;
130
  /** Number of scalar values represented by one fixed row or flattened element. */
131
  readonly stride: number;
132
  /** Byte offset of the first logical row in {@link buffer}. */
133
  readonly byteOffset: number;
134
  /** Bytes between adjacent fixed rows or flattened elements in {@link buffer}. */
135
  readonly byteStride: number;
136
  /** Bytes occupied by one fixed row or flattened element payload. */
137
  readonly rowByteLength: number;
138
  /** Optional producer-owned metadata retained for adapter-level readback. */
139
  readonly readbackMetadata?: GPUDataReadbackMetadata;
140
  /** Optional row offsets for variable-length values normalized to this data chunk. */
141
  readonly valueOffsets?: Int32Array;
142
  /** Optional row validity bitmap normalized to this data chunk. */
143
  readonly nullBitmap?: Uint8Array;
144
  /** Optional number of uploaded value bytes referenced by this data chunk. */
145
  readonly valueByteLength?: number;
146
  constructor(
147
    props:
148
      | GPUDataFromBufferProps<GPUVectorFormat>
149
      | GPUDataFromBufferProps<GPUDataStructFields, GPUDataStructLayout>
150
  ) {
151
    const {
152
      buffer,
153
      format,
154
      length,
155
      valueLength,
156
      stride,
157
      byteOffset = 0,
503✔
158
      byteStride,
159
      rowByteLength,
160
      ownsBuffer = false,
3✔
161
      readbackMetadata,
162
      valueOffsets,
163
      nullBitmap,
164
      valueByteLength,
165
      dataType
166
    } = props;
1,157✔
167
    super(buffer, ownsBuffer);
1,157✔
168
    const canonicalFormat: GPUDataFormat | undefined =
169
      format && typeof format === 'object'
1,157!
170
        ? normalizeGPUDataStructFormat(format, props.layout ?? 'wgsl-storage')
×
171
        : format;
172
    const structFormat = isGPUDataStructFormat(canonicalFormat) ? canonicalFormat : undefined;
1,157!
173
    const formatInfo =
174
      typeof canonicalFormat === 'string' ? getGPUVectorFormatInfo(canonicalFormat) : undefined;
1,157✔
175
    this.dataType = dataType;
1,157✔
176
    this.format = canonicalFormat;
1,157✔
177
    this.length = length;
1,157✔
178
    this.valueLength = valueLength ?? length;
1,157✔
179
    this.stride =
1,157✔
180
      stride ??
1,204!
181
      formatInfo?.components ??
182
      structFormat?.components ??
183
      byteStride ??
184
      rowByteLength ??
185
      1;
186
    this.byteOffset = byteOffset;
1,157✔
187
    this.rowByteLength =
1,157✔
188
      rowByteLength ??
1,249!
189
      structFormat?.rowByteLength ??
190
      formatInfo?.byteLength ??
191
      byteStride ??
192
      this.stride;
193
    this.byteStride = byteStride ?? structFormat?.byteStride ?? this.rowByteLength;
1,157✔
194
    if (structFormat) {
1,157!
NEW
195
      if (this.rowByteLength < structFormat.rowByteLength) {
×
NEW
196
        throw new Error(
×
197
          `GPUData rowByteLength ${this.rowByteLength} is smaller than struct format row byte length ${structFormat.rowByteLength}`
198
        );
199
      }
NEW
200
      if (this.byteStride < Math.max(structFormat.byteStride, this.rowByteLength)) {
×
NEW
201
        throw new Error(
×
202
          `GPUData byteStride ${this.byteStride} is smaller than its struct row layout`
203
        );
204
      }
205
    }
206
    this.readbackMetadata = readbackMetadata;
1,157✔
207
    this.valueOffsets = valueOffsets;
1,157✔
208
    this.nullBitmap = nullBitmap;
1,157✔
209
    this.valueByteLength = valueByteLength;
1,157✔
210
  }
211

212
  /** Returns a borrowed zero-copy view of one named struct field, or null for non-struct data. */
213
  getChild(name: string): GPUDataView | null {
NEW
214
    if (!isGPUDataStructFormat(this.format)) {
×
NEW
215
      return null;
×
216
    }
NEW
217
    const field = this.format.fields[name];
×
NEW
218
    if (!field) {
×
NEW
219
      return null;
×
220
    }
NEW
UNCOV
221
    return new GPUDataView({
×
222
      buffer: this.buffer,
223
      format: field.format,
224
      length: this.length,
225
      byteOffset: this.byteOffset + field.byteOffset,
226
      byteStride: this.byteStride
227
    });
228
  }
229

230
  /** Returns a borrowed zero-copy view of one struct field by declaration order. */
231
  getChildAt(index: number): GPUDataView | null {
NEW
232
    if (!isGPUDataStructFormat(this.format)) {
×
NEW
233
      return null;
×
234
    }
NEW
235
    const field = Object.values(this.format.fields)[index];
×
NEW
236
    if (!field) {
×
NEW
237
      return null;
×
238
    }
NEW
239
    return new GPUDataView({
×
240
      buffer: this.buffer,
241
      format: field.format,
242
      length: this.length,
243
      byteOffset: this.byteOffset + field.byteOffset,
244
      byteStride: this.byteStride
245
    });
246
  }
247
}
248

249
/**
250
 * GPU memory and format metadata for one contiguous data chunk.
251
 *
252
 * `GPUData` is Arrow-agnostic. Format-specific adapters upload bytes and retain
253
 * any extra schema/readback metadata outside table core.
254
 */
255
interface GPUDataBase<Format extends GPUDataFormat = GPUDataFormat> {
256
  /** GPU buffer containing this chunk's bytes. */
257
  readonly buffer: Buffer | DynamicBuffer;
258
  /** Optional adapter-owned metadata; core tables do not inspect this value. */
259
  readonly dataType?: unknown;
260
  /** Canonical memory-layout descriptor for this data range. */
261
  readonly format?: Format;
262
  /** Number of logical rows in this chunk. */
263
  readonly length: number;
264
  /** Number of fixed rows or flattened vertex-list values in this chunk. */
265
  readonly valueLength: number;
266
  /** Number of scalar values represented by one fixed row or flattened element. */
267
  readonly stride: number;
268
  /** Byte offset of the first logical row in {@link buffer}. */
269
  readonly byteOffset: number;
270
  /** Bytes between adjacent fixed rows or flattened elements in {@link buffer}. */
271
  readonly byteStride: number;
272
  /** Bytes occupied by one fixed row or flattened element payload. */
273
  readonly rowByteLength: number;
274
  /** Optional producer-owned metadata retained for adapter-level readback. */
275
  readonly readbackMetadata?: GPUDataReadbackMetadata;
276
  /** Optional row offsets for variable-length values normalized to this data chunk. */
277
  readonly valueOffsets?: Int32Array;
278
  /** Optional row validity bitmap normalized to this data chunk. */
279
  readonly nullBitmap?: Uint8Array;
280
  /** Optional number of uploaded value bytes referenced by this data chunk. */
281
  readonly valueByteLength?: number;
282
  /** Whether this data range is responsible for destroying its backing buffer. */
283
  readonly ownsBuffer: boolean;
284

285
  /** Returns a borrowed zero-copy view of one named struct field, or null for non-struct data. */
286
  getChild(name: string): GPUDataView | null;
287
  /** Returns a borrowed zero-copy view of one struct field by declaration order. */
288
  getChildAt(index: number): GPUDataView | null;
289
  /** Transfers backing-buffer ownership to another GPUData chunk over the same buffer. */
290
  transferBufferOwnership(target: GPUDataBase): void;
291
  /** Releases the backing buffer when this data range owns it. */
292
  destroy(): void;
293
}
294

295
/** Typed GPU data object for scalar, list, or inline struct format declarations. */
296
export type GPUData<
297
  Format extends GPUDataFormatDeclaration = GPUVectorFormat,
298
  Layout extends GPUDataStructLayout | null = null
299
> = Omit<GPUDataBase<GPUDataFormatT<Format, Layout>>, 'getChild' | 'getChildAt'> &
300
  GPUDataChildMethods<Format, Layout>;
301

302
type GPUDataChildMethods<
303
  Format extends GPUDataFormatDeclaration,
304
  Layout extends GPUDataStructLayout | null
305
> = Layout extends null
306
  ? {
307
      getChild(name: string): GPUDataView | null;
308
      getChildAt(index: number): GPUDataView | null;
309
    }
310
  : Extract<Format, GPUDataStructFields> extends infer Fields extends GPUDataStructFields
311
    ? {
312
        getChild<Name extends string>(name: Name): GPUDataChild<Fields, Name> | null;
313
        getChildAt(index: number): GPUDataChildAt<Fields> | null;
314
      }
315
    : {};
316

317
type GPUDataConstructor = {
318
  new <const Format extends GPUVectorFormat = GPUVectorFormat>(
319
    props: GPUDataScalarFromBufferProps<Format>
320
  ): GPUData<Format>;
321
  new <const Fields extends GPUDataStructFields>(
322
    props: GPUDataStructFromBufferProps<Fields, 'wgsl-storage'>
323
  ): GPUData<Fields, 'wgsl-storage'>;
324
  new <const Fields extends GPUDataStructFields, const Layout extends GPUDataStructLayout>(
325
    props: GPUDataStructFromBufferProps<Fields, Layout> & {layout: Layout}
326
  ): GPUData<Fields, Layout>;
327
  new (
328
    props:
329
      | GPUDataScalarFromBufferProps<GPUVectorFormat>
330
      | GPUDataStructFromBufferProps<GPUDataStructFields, GPUDataStructLayout>
331
  ): GPUData;
332
};
333

334
/** Typed constructor for scalar, list, and inline struct GPUData declarations. */
335
export const GPUData = GPUDataImpl as unknown as GPUDataConstructor;
61✔
336

337
/** Borrowed view type returned for one named GPU data struct field. */
338
export type GPUDataChild<Format extends GPUDataFormatDeclaration, Name extends string> = [
339
  Extract<Format, GPUDataStructFields>
340
] extends [never]
341
  ? never
342
  : Extract<Format, GPUDataStructFields> extends infer Fields extends GPUDataStructFields
343
    ? Name extends keyof Fields
344
      ? GPUDataView<Fields[Name]>
345
      : never
346
    : never;
347

348
/** Borrowed view type returned for a GPU data struct field selected by index. */
349
export type GPUDataChildAt<Format extends GPUDataFormatDeclaration> = [
350
  Extract<Format, GPUDataStructFields>
351
] extends [never]
352
  ? never
353
  : Extract<Format, GPUDataStructFields> extends infer Fields extends GPUDataStructFields
354
    ? GPUDataView<Fields[keyof Fields]>
355
    : never;
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