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

visgl / luma.gl / 28680145858

03 Jul 2026 07:36PM UTC coverage: 72.427% (+0.001%) from 72.426%
28680145858

Pull #2729

github

web-flow
Merge a44407155 into 88b244911
Pull Request #2729: feat(tables,gpgpu): add strided GPUDataView inputs

10983 of 17051 branches covered (64.41%)

Branch coverage included in aggregate %.

41 of 59 new or added lines in 3 files covered. (69.49%)

8 existing lines in 1 file now uncovered.

21379 of 27631 relevant lines covered (77.37%)

5735.1 hits per line

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

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

5
import {
6
  vertexFormatDecoder,
7
  type Buffer,
8
  type BufferLayout,
9
  type VertexFormat
10
} from '@luma.gl/core';
11
import type {DynamicBuffer} from '@luma.gl/engine';
12

13
/** Minimal backing resource required by a {@link GPUDataView}. */
14
export type GPUDataViewBuffer = {
15
  /** Number of addressable bytes in the backing resource. */
16
  readonly byteLength: number;
17
};
18

19
/** Properties for one fixed-width strided view over a GPU buffer-like resource. */
20
export type GPUDataViewProps<
21
  Format extends VertexFormat = VertexFormat,
22
  BufferType extends GPUDataViewBuffer = Buffer | DynamicBuffer
23
> = {
24
  /** Buffer-like resource containing the values. */
25
  buffer: BufferType;
26
  /** Canonical fixed-width memory format for one value. */
27
  format: Format;
28
  /** Number of fixed-width values in the view. */
29
  length: number;
30
  /** Byte offset of the first value. Defaults to zero. */
31
  byteOffset?: number;
32
  /** Byte distance between consecutive values. Defaults to the format byte length. */
33
  byteStride?: number;
34
};
35

36
/** Properties for deriving a fixed-width view from one buffer-layout attribute. */
37
export type GPUDataViewFromAttributeProps<
38
  BufferType extends GPUDataViewBuffer = Buffer | DynamicBuffer
39
> = {
40
  /** Buffer-like resource described by `bufferLayout`. */
41
  buffer: BufferType;
42
  /** Layout containing the requested attribute. */
43
  bufferLayout: BufferLayout;
44
  /** Attribute name to expose as a view. */
45
  attributeName: string;
46
  /** Number of attribute values in the view. */
47
  length: number;
48
  /** Additional byte offset applied before the attribute offset. Defaults to zero. */
49
  byteOffset?: number;
50
};
51

52
/**
53
 * Borrowed fixed-width strided view over a GPU buffer-like resource.
54
 *
55
 * A data view describes physical values only. It does not own its backing resource and carries no
56
 * logical list topology, validity, or adapter metadata.
57
 */
58
export class GPUDataView<
59
  Format extends VertexFormat = VertexFormat,
60
  BufferType extends GPUDataViewBuffer = Buffer | DynamicBuffer
61
> {
62
  /** Buffer-like resource containing the values. */
63
  readonly buffer: BufferType;
64
  /** Canonical fixed-width memory format for one value. */
65
  readonly format: Format;
66
  /** Number of fixed-width values in the view. */
67
  readonly length: number;
68
  /** Byte offset of the first value. */
69
  readonly byteOffset: number;
70
  /** Byte distance between consecutive values. */
71
  readonly byteStride: number;
72

73
  constructor(props: GPUDataViewProps<Format, BufferType>) {
74
    const elementByteLength = vertexFormatDecoder.getVertexFormatInfo(props.format).byteLength;
40✔
75
    const byteOffset = props.byteOffset ?? 0;
40✔
76
    const byteStride = props.byteStride ?? elementByteLength;
40✔
77

78
    validateSafeNonNegativeInteger(props.length, 'GPUDataView length');
40✔
79
    validateSafeNonNegativeInteger(byteOffset, 'GPUDataView byteOffset');
40✔
80
    validateSafeNonNegativeInteger(byteStride, 'GPUDataView byteStride');
40✔
81
    if (byteStride < elementByteLength) {
40!
NEW
82
      throw new Error(
×
83
        `GPUDataView byteStride ${byteStride} is smaller than ${props.format} byte length ${elementByteLength}`
84
      );
85
    }
86

87
    const byteLength = props.length === 0 ? 0 : (props.length - 1) * byteStride + elementByteLength;
40!
88
    const endByteOffset = byteOffset + byteLength;
40✔
89
    if (!Number.isSafeInteger(byteLength) || !Number.isSafeInteger(endByteOffset)) {
40!
NEW
90
      throw new Error('GPUDataView byte range must use safe integers');
×
91
    }
92
    if (endByteOffset > props.buffer.byteLength) {
40!
NEW
93
      throw new Error('GPUDataView exceeds its backing buffer byte length');
×
94
    }
95

96
    this.buffer = props.buffer;
40✔
97
    this.format = props.format;
40✔
98
    this.length = props.length;
40✔
99
    this.byteOffset = byteOffset;
40✔
100
    this.byteStride = byteStride;
40✔
101
  }
102

103
  /** Number of bytes occupied by one fixed-width value. */
104
  get elementByteLength(): number {
NEW
105
    return vertexFormatDecoder.getVertexFormatInfo(this.format).byteLength;
×
106
  }
107

108
  /** Number of bytes from the first value through the final value payload. */
109
  get byteLength(): number {
NEW
110
    return this.length === 0 ? 0 : (this.length - 1) * this.byteStride + this.elementByteLength;
×
111
  }
112
}
113

114
/**
115
 * Creates a borrowed fixed-width view of one attribute in a buffer layout.
116
 *
117
 * The returned view combines the caller's base offset with the attribute offset and uses the
118
 * layout's row stride. No GPU storage is allocated or copied.
119
 */
120
export function makeGPUDataViewFromAttribute<BufferType extends GPUDataViewBuffer>({
121
  buffer,
122
  bufferLayout,
123
  attributeName,
124
  length,
125
  byteOffset = 0
8✔
126
}: GPUDataViewFromAttributeProps<BufferType>): GPUDataView<VertexFormat, BufferType> {
127
  const attribute = bufferLayout.attributes?.find(
8✔
128
    candidate => candidate.attribute === attributeName
12✔
129
  );
130
  if (!attribute) {
8!
NEW
131
    throw new Error(
×
132
      `Buffer layout "${bufferLayout.name}" does not contain attribute "${attributeName}"`
133
    );
134
  }
135
  if (!attribute.format) {
8!
NEW
136
    throw new Error(`Buffer layout attribute "${attributeName}" requires a format`);
×
137
  }
138
  if (bufferLayout.byteStride === undefined) {
8!
NEW
139
    throw new Error(`Buffer layout "${bufferLayout.name}" requires byteStride for attribute views`);
×
140
  }
141

142
  return new GPUDataView({
8✔
143
    buffer,
144
    format: attribute.format,
145
    length,
146
    byteOffset: byteOffset + attribute.byteOffset,
147
    byteStride: bufferLayout.byteStride
148
  });
149
}
150

151
function validateSafeNonNegativeInteger(value: number, name: string): void {
152
  if (!Number.isSafeInteger(value) || value < 0) {
120!
NEW
153
    throw new Error(`${name} must be a non-negative safe integer`);
×
154
  }
155
}
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