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

visgl / luma.gl / 25879749619

14 May 2026 07:05PM UTC coverage: 74.881% (-0.2%) from 75.089%
25879749619

push

github

web-flow
feat(text) TextArrowModel (#2615)

6380 of 9600 branches covered (66.46%)

Branch coverage included in aggregate %.

462 of 672 new or added lines in 6 files covered. (68.75%)

123 existing lines in 9 files now uncovered.

13975 of 17583 relevant lines covered (79.48%)

782.31 hits per line

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

76.15
/modules/gpgpu/src/operation/gpu-table.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4
import {getDataTypeFromTypedArray, getTypedArrayFromDataType} from '../utils/vertex-data-types';
5
import {Device, Buffer, SignedDataType} from '@luma.gl/core';
6
import type {TypedArray, TypedArrayConstructor} from '@math.gl/types';
7
import {bufferPool} from '../utils/buffer-pool';
8
import type {Operation} from './operation';
9

10
/** Properties used to construct a {@link GPUTableEvaluator}. */
11
export type GPUTableEvaluatorProps = {
12
  /** Optional debug name used by {@link GPUTableEvaluator.toString}. */
13
  id?: string;
14
  /** Scalar element type for every stored value. */
15
  type: SignedDataType;
16
  /** Number of scalar elements in each logical row. */
17
  size: number;
18
  /** Number of bytes to skip before reading the first element.
19
   * @default 0
20
   */
21
  offset?: number;
22
  /** Number of bytes between the starts of adjacent rows.
23
   * @default ValueType.BYTES_PER_ELEMENT * size
24
   */
25
  stride?: number;
26
  /** CPU buffer that initializes the table, required unless `source` is supplied. */
27
  value?: TypedArray;
28
  /** Lazy operation or table whose output initializes this table, required unless `value` is supplied. */
29
  source?: Operation | GPUTableEvaluator | null;
30
  /** Whether every row should read the same value. */
31
  isConstant?: boolean;
32
  /** Number of logical rows, inferred for constants and CPU-backed tables when omitted. */
33
  length?: number;
34
};
35

36
/**
37
 * Device-agnostic, immutable 2D numeric table used as input and output for lazy GPGPU operations.
38
 *
39
 * A table describes row layout and a data source, but does not allocate or run GPU work until
40
 * {@link GPUTableEvaluator.evaluate} is called. Operation functions such as `add()` return new tables whose
41
 * `source` points at the deferred operation.
42
 */
43
export class GPUTableEvaluator {
44
  /** Scalar element type for each stored value. */
45
  readonly type: SignedDataType;
46
  /** Number of scalar elements in each logical row. */
47
  readonly size: number;
48
  /** Number of bytes to skip before reading the first element. */
49
  readonly offset: number;
50
  /** Number of bytes between the starts of adjacent rows. */
51
  readonly stride: number;
52
  /** Whether all rows share the same value. */
53
  readonly isConstant: boolean;
54
  /** Number of logical rows. */
55
  readonly length: number;
56
  /** Total bytes needed for the table storage. */
57
  readonly byteLength: number;
58
  /** TypedArray constructor for CPU representation, derived from {@link GPUTableEvaluator.type}. */
59
  readonly ValueType: TypedArrayConstructor;
60

61
  /** User-assigned id for easy debugging */
62
  protected _id?: string;
63
  /** destroy() has been called and no more resources should be created */
64
  protected _destroyed: boolean = false;
78✔
65

66
  /** CPU buffer, either provided by the user or read back from the GPU */
67
  protected _value?: TypedArray;
68
  /** Operation whose output is used to fill the vector, required unless `value` is supplied */
69
  protected _source: Operation | GPUTableEvaluator | null = null;
78✔
70
  /** GPU buffer */
71
  private _buffer?: Buffer;
72

73
  /**
74
   * Constructs a table from a CPU array.
75
   *
76
   * Plain JavaScript arrays are converted to typed arrays using `type` or `float32` by default.
77
   * `Float64Array` inputs are reinterpreted as `uint32` pairs so they can be consumed by GPU
78
   * operations such as {@link fround}.
79
   */
80
  static fromArray(
81
    value: TypedArray | number[],
82
    {
83
      type,
84
      size = 1,
39✔
85
      offset = 0,
39✔
86
      stride = 0
39✔
87
    }: Partial<Pick<GPUTableEvaluatorProps, 'type' | 'size' | 'offset' | 'stride'>>
88
  ): GPUTableEvaluator {
89
    if (Array.isArray(value)) {
39✔
90
      type = type || 'float32';
33✔
91
      const ArrayType = getTypedArrayFromDataType(type);
33✔
92
      value = new ArrayType(value);
33✔
93
    } else if (value instanceof Float64Array) {
6!
94
      // This is not really supported by GPU buffer, treat it as 2 uints
95
      type = 'uint32';
6✔
96
      size *= 2;
6✔
97
      offset *= 2;
6✔
98
      stride *= 2;
6✔
99
      value = new Uint32Array(value.buffer);
6✔
100
    } else {
101
      type = type || getDataTypeFromTypedArray(value);
×
102
    }
103
    return new GPUTableEvaluator({
39✔
104
      type,
105
      size,
106
      offset,
107
      stride,
108
      value
109
    });
110
  }
111

112
  /**
113
   * Constructs a constant table whose single row is broadcast across non-constant inputs.
114
   *
115
   * @param value - Scalar or row value.
116
   * @param type - Scalar element type used for the CPU representation.
117
   */
118
  static fromConstant(
119
    value: number | number[],
120
    type: SignedDataType = 'float32'
10✔
121
  ): GPUTableEvaluator {
122
    const ArrayType = getTypedArrayFromDataType(type);
10✔
123
    if (!Array.isArray(value)) {
10✔
124
      value = [value];
2✔
125
    }
126
    return new GPUTableEvaluator({
10✔
127
      isConstant: true,
128
      type,
129
      size: value.length,
130
      value: new ArrayType(value)
131
    });
132
  }
133

134
  /** TODO - Construct a new GPUTableEvaluator from a loaders.gl Table/BatchedTable. */
135
  // static from(table: Table, columnName: string | number): GPUTableEvaluator
136

137
  /**
138
   * Creates a table from explicit row layout and source information.
139
   *
140
   * Prefer {@link GPUTableEvaluator.fromArray} or {@link GPUTableEvaluator.fromConstant} for CPU-backed tables.
141
   */
142
  constructor(props: GPUTableEvaluatorProps) {
143
    const {
144
      id,
145
      type,
146
      size = 1,
78✔
147
      offset = 0,
78✔
148
      stride,
149
      value,
150
      source = null,
78✔
151
      isConstant = false
78✔
152
    } = props;
78✔
153
    if (!source && !value) {
78!
UNCOV
154
      throw new Error('OperationResource must have a value source');
×
155
    }
156

157
    this._id = id;
78✔
158
    this.type = type;
78✔
159
    this.size = size;
78✔
160
    this.ValueType = getTypedArrayFromDataType(this.type);
78✔
161
    this.offset = offset;
78✔
162
    this.stride = stride || this.ValueType.BYTES_PER_ELEMENT * size;
78✔
163
    this._value = value;
78✔
164
    this._source = source;
78✔
165

166
    let {length} = props;
78✔
167
    if (length === undefined) {
78✔
168
      if (isConstant) {
49✔
169
        length = 1;
10✔
170
      } else {
171
        if (!value) {
39!
UNCOV
172
          throw new Error('GPUTableEvaluator: length not defined');
×
173
        }
174
        length = Math.ceil(value.byteLength / this.stride);
39✔
175
      }
176
    }
177
    this.isConstant = isConstant;
78✔
178
    this.length = length;
78✔
179
    this.byteLength = this.stride * length;
78✔
180
  }
181

182
  /** CPU-side typed array, when available. */
183
  get value(): TypedArray | undefined {
184
    return this._value;
51✔
185
  }
186

187
  /** GPU buffer for the table. Only available after {@link GPUTableEvaluator.evaluate} resolves. */
188
  get buffer(): Buffer {
189
    if (!this._buffer) {
69!
UNCOV
190
      throw new Error(`${this} not evaluated`);
×
191
    }
192
    return this._buffer;
69✔
193
  }
194

195
  /**
196
   * Materializes the table on a device.
197
   *
198
   * If the table is operation-backed, dependencies are evaluated first and then the backend
199
   * operation writes into a cached GPU buffer.
200
   */
201
  async evaluate(device: Device): Promise<void> {
202
    if (this._destroyed) {
78!
UNCOV
203
      throw new Error(`GPUTableEvaluator ${this} already destroyed`);
×
204
    }
205
    if (!this._buffer) {
78!
206
      let buffer: Buffer;
207
      if (this._source instanceof GPUTableEvaluator) {
78!
UNCOV
208
        await this._source.evaluate(device);
×
UNCOV
209
        buffer = this._source.buffer;
×
210
      } else {
211
        buffer = bufferPool.createOrReuse(device, this.byteLength);
78✔
212
        if (this._value) {
78✔
213
          buffer.write(this._value);
49✔
214
        } else {
215
          await this._source!.execute(device, buffer);
29✔
216
        }
217
      }
218
      // cache the result when successful
219
      this._buffer = buffer;
78✔
220
    }
221
  }
222

223
  /**
224
   * Reads table data back to the CPU.
225
   *
226
   * This is intended for debugging and validation. Tightly packed rows return a typed-array view;
227
   * strided rows are copied into a compact typed array.
228
   */
229
  async readValue(startRow: number = 0, endRow?: number): Promise<TypedArray> {
26✔
230
    const {ValueType} = this;
26✔
231
    if (!this._value) {
26!
232
      const bytes = await this.buffer.readAsync(this.offset, this.byteLength);
26✔
233
      this._value = new ValueType(bytes.buffer as ArrayBuffer);
26✔
234
    }
235

236
    const {size, offset, stride, length} = this;
26✔
237
    const width = ValueType.BYTES_PER_ELEMENT * size;
26✔
238
    endRow = endRow ?? length;
26✔
239

240
    if (stride === width) {
26!
241
      const buffer = this._value!.buffer as ArrayBuffer;
26✔
242
      return new ValueType(buffer, offset + stride * startRow, (endRow - startRow) * size);
26✔
243
    }
244

245
    const bytes = new Uint8Array(width * (endRow - startRow));
×
246
    let i0 = offset + startRow * stride,
×
247
      i1 = 0;
×
UNCOV
248
    for (let y = startRow; y < endRow; y++) {
×
249
      for (let x = 0; x < width; x++) {
×
UNCOV
250
        bytes[i1++] = bytes[i0 + x];
×
251
      }
UNCOV
252
      i0 += stride;
×
253
    }
UNCOV
254
    return new ValueType(bytes.buffer);
×
255
  }
256

257
  /** Returns the debug id, source description, or class name. */
258
  toString(): string {
UNCOV
259
    return this._id ?? this._source?.toString() ?? this.constructor.name;
×
260
  }
261

262
  /** Releases cached GPU storage and prevents future evaluation. */
263
  destroy() {
264
    if (this._buffer) {
75!
265
      bufferPool.recycle(this._buffer);
75✔
266
      this._buffer = undefined;
75✔
267
    }
268
    this._destroyed = true;
75✔
269
    this._source = null;
75✔
270
  }
271
}
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