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

visgl / luma.gl / 28707386205

04 Jul 2026 01:15PM UTC coverage: 72.095% (-0.1%) from 72.227%
28707386205

Pull #2733

github

web-flow
Merge 0a36c7b37 into 86ddcd30b
Pull Request #2733: feat(tables) Declare physical GPU data struct formats inline

11252 of 17588 branches covered (63.98%)

Branch coverage included in aggregate %.

25 of 83 new or added lines in 3 files covered. (30.12%)

21826 of 28293 relevant lines covered (77.14%)

5601.61 hits per line

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

55.79
/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 GPUDataStructFormat,
15
  type GPUDataStructLayout
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
/**
23
 * Private scalar specialization marker. Unlike `null` or `undefined`, a unique symbol remains
24
 * disjoint from string layouts when downstream projects disable strict null checks.
25
 */
26
declare const SCALAR_GPU_DATA_LAYOUT: unique symbol;
27
/** Type of the private scalar specialization marker. */
28
type ScalarGPUDataLayout = typeof SCALAR_GPU_DATA_LAYOUT;
29
/** Internal discriminator between scalar/list and struct `GPUData` specializations. */
30
type GPUDataLayout = GPUDataStructLayout | ScalarGPUDataLayout;
31
/** Maps constructor declarations to the canonical metadata retained by `GPUData.format`. */
32
type GPUDataFormatT<
33
  Format extends GPUDataFormatDeclaration,
34
  Layout extends GPUDataLayout
35
> = Layout extends GPUDataStructLayout
36
  ? GPUDataStructFormat<Extract<Format, GPUDataStructFields>, Layout>
37
  : Extract<Format, GPUVectorFormat>;
38

39
/** Constructor props that wrap one existing GPU data buffer. */
40
type GPUDataFromBufferBaseProps = {
41
  /** Stable dynamic GPU buffer wrapper for this data range. */
42
  buffer: Buffer | DynamicBuffer;
43
  /** Number of logical rows in the data range. */
44
  length: number;
45
  /** Number of fixed rows or flattened vertex-list values in the data range. */
46
  valueLength?: number;
47
  /** Number of scalar values represented by one fixed row or flattened element. */
48
  stride?: number;
49
  /** Byte offset of the first logical row. */
50
  byteOffset?: number;
51
  /** Bytes between adjacent fixed rows or flattened elements. */
52
  byteStride?: number;
53
  /** Number of bytes occupied by the fixed row or flattened element payload. */
54
  rowByteLength?: number;
55
  /** Whether this data view should destroy the buffer. */
56
  ownsBuffer?: boolean;
57
  /** Optional metadata owned by the producer, such as Arrow readback descriptors. */
58
  readbackMetadata?: GPUDataReadbackMetadata;
59
  /** Optional row offsets for variable-length values normalized to this data chunk. */
60
  valueOffsets?: Int32Array;
61
  /** Optional row validity bitmap normalized to this data chunk. */
62
  nullBitmap?: Uint8Array;
63
  /** Optional number of uploaded value bytes referenced by this data chunk. */
64
  valueByteLength?: number;
65
  /** Optional adapter-owned metadata; core tables do not inspect this value. */
66
  dataType?: unknown;
67
};
68

69
/**
70
 * Constructor props that wrap one existing GPU data buffer.
71
 * Scalar/list declarations accept a format string and reject `layout`; struct declarations require
72
 * named fixed-width fields and optionally select physical alignment rules.
73
 *
74
 * @typeParam Format - Scalar/list format string or inline struct field declaration.
75
 * @typeParam Layout - Struct alignment rules, defaulting to `wgsl-storage`.
76
 */
77
export type GPUDataFromBufferProps<
78
  Format extends GPUDataFormatDeclaration = GPUVectorFormat,
79
  Layout extends GPUDataStructLayout = 'wgsl-storage'
80
> = GPUDataFromBufferBaseProps &
81
  (
82
    | {
83
        /** Canonical scalar or list memory-layout descriptor for this data range. */
84
        format?: Extract<Format, GPUVectorFormat>;
85
        /** Struct layout rules are only valid with a struct format declaration. */
86
        layout?: never;
87
      }
88
    | ([Extract<Format, GPUDataStructFields>] extends [never]
89
        ? never
90
        : {
91
            /** Named fixed-width formats stored in each interleaved row. */
92
            format: Extract<Format, GPUDataStructFields>;
93
            /** Physical struct packing rules. Defaults to `wgsl-storage`. */
94
            layout?: Layout;
95
          })
96
  );
97

98
/** Internal constructor props for scalar and list formats. */
99
type GPUDataScalarFromBufferProps<Format extends GPUVectorFormat> = GPUDataFromBufferBaseProps & {
100
  format?: Format;
101
  layout?: never;
102
};
103

104
/** Internal constructor props for inline struct field declarations. */
105
type GPUDataStructFromBufferProps<
106
  Fields extends GPUDataStructFields,
107
  Layout extends GPUDataStructLayout
108
> = GPUDataFromBufferBaseProps & {
109
  format: Fields;
110
  layout?: Layout;
111
};
112

113
/** Implements the single-buffer ownership protocol shared by all typed `GPUData` variants. */
114
class GPUDataBufferOwner {
115
  /** GPU buffer containing this chunk's bytes. */
116
  readonly buffer: Buffer | DynamicBuffer;
117
  private ownsDataBuffer: boolean;
118

119
  /** Creates an owner or borrower for one backing buffer. */
120
  constructor(buffer: Buffer | DynamicBuffer, ownsBuffer: boolean) {
121
    this.buffer = buffer;
1,167✔
122
    this.ownsDataBuffer = ownsBuffer;
1,167✔
123
  }
124

125
  /** Whether this data range is responsible for destroying its backing buffer. */
126
  get ownsBuffer(): boolean {
127
    return this.ownsDataBuffer;
4✔
128
  }
129

130
  /** Transfers backing-buffer ownership to another GPUData chunk over the same buffer. */
131
  transferBufferOwnership(target: GPUDataBufferOwner): void {
132
    if (target.buffer !== this.buffer) {
1!
NEW
133
      throw new Error('GPUData ownership can only be transferred to the same buffer');
×
134
    }
135
    target.ownsDataBuffer = this.ownsDataBuffer;
1✔
136
    this.ownsDataBuffer = false;
1✔
137
  }
138

139
  /** Releases the backing buffer when this data range owns it. */
140
  destroy(): void {
141
    if (this.ownsDataBuffer) {
502✔
142
      this.buffer.destroy();
490✔
143
      this.ownsDataBuffer = false;
490✔
144
    }
145
  }
146
}
147

148
/** Runtime implementation behind the typed GPUData constructor interface. */
149
class GPUDataImpl extends GPUDataBufferOwner {
150
  /** Optional adapter-owned metadata; core tables do not inspect this value. */
151
  readonly dataType?: unknown;
152
  /** Canonical memory-layout descriptor for this data range. */
153
  readonly format?: GPUDataFormat;
154
  /** Number of logical rows in this chunk. */
155
  readonly length: number;
156
  /** Number of fixed rows or flattened vertex-list values in this chunk. */
157
  readonly valueLength: number;
158
  /** Number of scalar values represented by one fixed row or flattened element. */
159
  readonly stride: number;
160
  /** Byte offset of the first logical row in {@link buffer}. */
161
  readonly byteOffset: number;
162
  /** Bytes between adjacent fixed rows or flattened elements in {@link buffer}. */
163
  readonly byteStride: number;
164
  /** Bytes occupied by one fixed row or flattened element payload. */
165
  readonly rowByteLength: number;
166
  /** Optional producer-owned metadata retained for adapter-level readback. */
167
  readonly readbackMetadata?: GPUDataReadbackMetadata;
168
  /** Optional row offsets for variable-length values normalized to this data chunk. */
169
  readonly valueOffsets?: Int32Array;
170
  /** Optional row validity bitmap normalized to this data chunk. */
171
  readonly nullBitmap?: Uint8Array;
172
  /** Optional number of uploaded value bytes referenced by this data chunk. */
173
  readonly valueByteLength?: number;
174
  /** Normalizes constructor declarations and derives missing row metadata. */
175
  constructor(
176
    props:
177
      | GPUDataFromBufferProps<GPUVectorFormat>
178
      | GPUDataFromBufferProps<GPUDataStructFields, GPUDataStructLayout>
179
  ) {
180
    const {
181
      buffer,
182
      format,
183
      length,
184
      valueLength,
185
      stride,
186
      byteOffset = 0,
513✔
187
      byteStride,
188
      rowByteLength,
189
      ownsBuffer = false,
3✔
190
      readbackMetadata,
191
      valueOffsets,
192
      nullBitmap,
193
      valueByteLength,
194
      dataType
195
    } = props;
1,167✔
196
    super(buffer, ownsBuffer);
1,167✔
197

198
    // Store only canonical metadata; inline field declarations are constructor input syntax.
199
    let canonicalFormat: GPUDataFormat | undefined;
200
    if (!format) {
1,167✔
201
      canonicalFormat = undefined;
69✔
202
    } else if (typeof format === 'string') {
1,098!
203
      canonicalFormat = format;
1,098✔
204
    } else {
NEW
205
      canonicalFormat = normalizeGPUDataStructFormat(format, props.layout ?? 'wgsl-storage');
×
206
    }
207
    const structFormat = isGPUDataStructFormat(canonicalFormat) ? canonicalFormat : undefined;
1,167!
208
    const formatInfo =
209
      typeof canonicalFormat === 'string' ? getGPUVectorFormatInfo(canonicalFormat) : undefined;
1,167✔
210
    this.dataType = dataType;
1,167✔
211
    this.format = canonicalFormat;
1,167✔
212
    this.length = length;
1,167✔
213
    this.valueLength = valueLength ?? length;
1,167✔
214
    this.stride =
1,167✔
215
      stride ??
1,214!
216
      formatInfo?.components ??
217
      structFormat?.components ??
218
      byteStride ??
219
      rowByteLength ??
220
      1;
221
    this.byteOffset = byteOffset;
1,167✔
222
    this.rowByteLength =
1,167✔
223
      rowByteLength ??
1,259!
224
      structFormat?.rowByteLength ??
225
      formatInfo?.byteLength ??
226
      byteStride ??
227
      this.stride;
228
    this.byteStride = byteStride ?? structFormat?.byteStride ?? this.rowByteLength;
1,167✔
229

230
    // Explicit row overrides may add padding but cannot truncate the computed struct layout.
231
    if (structFormat) {
1,167!
NEW
232
      if (this.rowByteLength < structFormat.rowByteLength) {
×
NEW
233
        throw new Error(
×
234
          `GPUData rowByteLength ${this.rowByteLength} is smaller than struct format row byte length ${structFormat.rowByteLength}`
235
        );
236
      }
NEW
237
      if (this.byteStride < Math.max(structFormat.byteStride, this.rowByteLength)) {
×
NEW
238
        throw new Error(
×
239
          `GPUData byteStride ${this.byteStride} is smaller than its struct row layout`
240
        );
241
      }
242
    }
243
    this.readbackMetadata = readbackMetadata;
1,167✔
244
    this.valueOffsets = valueOffsets;
1,167✔
245
    this.nullBitmap = nullBitmap;
1,167✔
246
    this.valueByteLength = valueByteLength;
1,167✔
247
  }
248

249
  /** Returns a borrowed zero-copy view of one named struct field, or null for non-struct data. */
250
  getChild(name: string): GPUDataView | null {
NEW
251
    if (!isGPUDataStructFormat(this.format)) {
×
NEW
252
      return null;
×
253
    }
NEW
254
    const field = this.format.fields[name];
×
NEW
255
    if (!field) {
×
NEW
256
      return null;
×
257
    }
NEW
258
    return new GPUDataView({
×
259
      buffer: this.buffer,
260
      format: field.format,
261
      length: this.length,
262
      byteOffset: this.byteOffset + field.byteOffset,
263
      byteStride: this.byteStride
264
    });
265
  }
266

267
  /** Returns a borrowed zero-copy view of one struct field by declaration order. */
268
  getChildAt(index: number): GPUDataView | null {
NEW
269
    if (!isGPUDataStructFormat(this.format)) {
×
NEW
270
      return null;
×
271
    }
NEW
272
    const field = Object.values(this.format.fields)[index];
×
NEW
273
    if (!field) {
×
NEW
274
      return null;
×
275
    }
NEW
276
    return new GPUDataView({
×
277
      buffer: this.buffer,
278
      format: field.format,
279
      length: this.length,
280
      byteOffset: this.byteOffset + field.byteOffset,
281
      byteStride: this.byteStride
282
    });
283
  }
284
}
285

286
/**
287
 * GPU memory and format metadata for one contiguous data chunk.
288
 *
289
 * `GPUData` is Arrow-agnostic. Format-specific adapters upload bytes and retain
290
 * any extra schema/readback metadata outside table core.
291
 */
292
interface GPUDataBase<Format extends GPUDataFormat = GPUDataFormat> {
293
  /** GPU buffer containing this chunk's bytes. */
294
  readonly buffer: Buffer | DynamicBuffer;
295
  /** Optional adapter-owned metadata; core tables do not inspect this value. */
296
  readonly dataType?: unknown;
297
  /** Canonical memory-layout descriptor for this data range. */
298
  readonly format?: Format;
299
  /** Number of logical rows in this chunk. */
300
  readonly length: number;
301
  /** Number of fixed rows or flattened vertex-list values in this chunk. */
302
  readonly valueLength: number;
303
  /** Number of scalar values represented by one fixed row or flattened element. */
304
  readonly stride: number;
305
  /** Byte offset of the first logical row in {@link buffer}. */
306
  readonly byteOffset: number;
307
  /** Bytes between adjacent fixed rows or flattened elements in {@link buffer}. */
308
  readonly byteStride: number;
309
  /** Bytes occupied by one fixed row or flattened element payload. */
310
  readonly rowByteLength: number;
311
  /** Optional producer-owned metadata retained for adapter-level readback. */
312
  readonly readbackMetadata?: GPUDataReadbackMetadata;
313
  /** Optional row offsets for variable-length values normalized to this data chunk. */
314
  readonly valueOffsets?: Int32Array;
315
  /** Optional row validity bitmap normalized to this data chunk. */
316
  readonly nullBitmap?: Uint8Array;
317
  /** Optional number of uploaded value bytes referenced by this data chunk. */
318
  readonly valueByteLength?: number;
319
  /** Whether this data range is responsible for destroying its backing buffer. */
320
  readonly ownsBuffer: boolean;
321

322
  /** Returns a borrowed zero-copy view of one named struct field, or null for non-struct data. */
323
  getChild(name: string): GPUDataView | null;
324
  /** Returns a borrowed zero-copy view of one struct field by declaration order. */
325
  getChildAt(index: number): GPUDataView | null;
326
  /** Transfers backing-buffer ownership to another GPUData chunk over the same buffer. */
327
  transferBufferOwnership(target: GPUDataBase): void;
328
  /** Releases the backing buffer when this data range owns it. */
329
  destroy(): void;
330
}
331

332
/**
333
 * GPU memory and canonical format metadata for one contiguous data chunk.
334
 * Inline struct field declarations retain their field names in the type and expose typed child
335
 * views, while scalar and list formats preserve the existing `GPUVectorFormat` specialization.
336
 *
337
 * @typeParam Format - Scalar/list format string or inline struct field declaration.
338
 * @typeParam Layout - Struct alignment rules; omitted for scalar and list formats.
339
 */
340
export type GPUData<
341
  Format extends GPUDataFormatDeclaration = GPUVectorFormat,
342
  Layout extends GPUDataLayout = ScalarGPUDataLayout
343
> = Omit<GPUDataBase<GPUDataFormatT<Format, Layout>>, 'getChild' | 'getChildAt'> &
344
  GPUDataChildMethods<Format, Layout>;
345

346
/** Selects broad scalar child methods or field-aware struct child methods. */
347
type GPUDataChildMethods<
348
  Format extends GPUDataFormatDeclaration,
349
  Layout extends GPUDataLayout
350
> = Layout extends GPUDataStructLayout
351
  ? Extract<Format, GPUDataStructFields> extends infer Fields extends GPUDataStructFields
352
    ? {
353
        getChild<Name extends string>(name: Name): GPUDataChild<Fields, Name> | null;
354
        getChildAt(index: number): GPUDataChildAt<Fields> | null;
355
      }
356
    : {}
357
  : {
358
      getChild(name: string): GPUDataView | null;
359
      getChildAt(index: number): GPUDataView | null;
360
    };
361

362
/** Constructor overloads that infer scalar formats, default storage structs, and explicit layouts. */
363
type GPUDataConstructor = {
364
  /** Constructs scalar or list data while preserving its literal format. */
365
  new <const Format extends GPUVectorFormat = GPUVectorFormat>(
366
    props: GPUDataScalarFromBufferProps<Format>
367
  ): GPUData<Format>;
368
  /** Constructs struct data using the default `wgsl-storage` layout. */
369
  new <const Fields extends GPUDataStructFields>(
370
    props: GPUDataStructFromBufferProps<Fields, 'wgsl-storage'>
371
  ): GPUData<Fields, 'wgsl-storage'>;
372
  /** Constructs struct data using an explicitly selected layout. */
373
  new <const Fields extends GPUDataStructFields, const Layout extends GPUDataStructLayout>(
374
    props: GPUDataStructFromBufferProps<Fields, Layout> & {layout: Layout}
375
  ): GPUData<Fields, Layout>;
376
  /** Broad runtime signature used when callers do not retain literal format information. */
377
  new (
378
    props:
379
      | GPUDataScalarFromBufferProps<GPUVectorFormat>
380
      | GPUDataStructFromBufferProps<GPUDataStructFields, GPUDataStructLayout>
381
  ): GPUData;
382
};
383

384
/**
385
 * Typed constructor for scalar, list, and inline struct `GPUData` declarations.
386
 * Inline struct fields are normalized into canonical `GPUDataStructFormat` metadata at runtime.
387
 */
388
export const GPUData = GPUDataImpl as unknown as GPUDataConstructor;
61✔
389

390
/**
391
 * Borrowed view type returned for one named GPU data struct field.
392
 * Literal names resolve to one precise view type, while a runtime `string` resolves to the union
393
 * of all field view types.
394
 *
395
 * @typeParam Format - Inline struct field declaration.
396
 * @typeParam Name - Requested field name.
397
 */
398
export type GPUDataChild<Format extends GPUDataFormatDeclaration, Name extends string> = [
399
  Extract<Format, GPUDataStructFields>
400
] extends [never]
401
  ? never
402
  : Extract<Format, GPUDataStructFields> extends infer Fields extends GPUDataStructFields
403
    ? string extends Name
404
      ? GPUDataChildAt<Fields>
405
      : Name extends keyof Fields
406
        ? GPUDataView<Fields[Name]>
407
        : never
408
    : never;
409

410
/**
411
 * Union of borrowed view types returned when selecting a struct field by numeric index.
412
 *
413
 * @typeParam Format - Inline struct field declaration.
414
 */
415
export type GPUDataChildAt<Format extends GPUDataFormatDeclaration> = [
416
  Extract<Format, GPUDataStructFields>
417
] extends [never]
418
  ? never
419
  : Extract<Format, GPUDataStructFields> extends infer Fields extends GPUDataStructFields
420
    ? {[Name in keyof Fields]: GPUDataView<Fields[Name]>}[keyof Fields]
421
    : 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