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

visgl / luma.gl / 23357510199

20 Mar 2026 06:40PM UTC coverage: 58.158% (+5.9%) from 52.213%
23357510199

push

github

web-flow
chore: Run tests on src instead of dist (#2555)

3021 of 6029 branches covered (50.11%)

Branch coverage included in aggregate %.

7102 of 11377 relevant lines covered (62.42%)

243.33 hits per line

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

93.43
/modules/webgl/src/adapter/resources/webgl-buffer.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import type {BufferMapCallback, BufferProps} from '@luma.gl/core';
6
import {Buffer} from '@luma.gl/core';
7
import {GL} from '@luma.gl/constants';
8
import {WebGLDevice} from '../webgl-device';
9

10
/** WebGL Buffer interface */
11
export class WEBGLBuffer extends Buffer {
12
  readonly device: WebGLDevice;
13
  readonly gl: WebGL2RenderingContext;
14
  readonly handle: WebGLBuffer;
15

16
  /** Target in OpenGL defines the type of buffer */
17
  readonly glTarget: GL.ARRAY_BUFFER | GL.ELEMENT_ARRAY_BUFFER | GL.UNIFORM_BUFFER;
18
  /** Usage is a hint on how frequently the buffer will be updates */
19
  readonly glUsage: GL.STATIC_DRAW | GL.DYNAMIC_DRAW;
20
  /** Index type is needed when issuing draw calls, so we pre-compute it */
21
  readonly glIndexType: GL.UNSIGNED_SHORT | GL.UNSIGNED_INT = GL.UNSIGNED_SHORT;
397✔
22

23
  /** Number of bytes allocated on the GPU for this buffer */
24
  byteLength: number = 0;
397✔
25
  /** Number of bytes used */
26
  bytesUsed: number = 0;
397✔
27

28
  constructor(device: WebGLDevice, props: BufferProps = {}) {
397✔
29
    super(device, props);
397✔
30

31
    this.device = device;
397✔
32
    this.gl = this.device.gl;
397✔
33

34
    const handle = typeof props === 'object' ? props.handle : undefined;
397!
35
    this.handle = handle || this.gl.createBuffer();
397✔
36
    device._setWebGLDebugMetadata(this.handle, this, {
397✔
37
      spector: {...this.props, data: typeof this.props.data}
38
    });
39

40
    // - In WebGL1, need to make sure we use GL.ELEMENT_ARRAY_BUFFER when initializing element buffers
41
    //   otherwise buffer type will lock to generic (non-element) buffer
42
    // - In WebGL2, we can use GL.COPY_READ_BUFFER which avoids locking the type here
43
    this.glTarget = getWebGLTarget(this.props.usage);
397✔
44
    this.glUsage = getWebGLUsage(this.props.usage);
397✔
45
    // Note: uint8 indices are converted to uint16 during device normalization for WebGPU compatibility
46
    this.glIndexType = this.props.indexType === 'uint32' ? GL.UNSIGNED_INT : GL.UNSIGNED_SHORT;
397✔
47

48
    // Set data: (re)initializes the buffer
49
    if (props.data) {
397✔
50
      this._initWithData(props.data, props.byteOffset, props.byteLength);
230✔
51
    } else {
52
      this._initWithByteLength(props.byteLength || 0);
167✔
53
    }
54
  }
55

56
  override destroy(): void {
57
    if (!this.destroyed && this.handle) {
343✔
58
      this.removeStats();
342✔
59
      if (!this.props.handle) {
342!
60
        this.trackDeallocatedMemory();
342✔
61
        this.gl.deleteBuffer(this.handle);
342✔
62
      } else {
63
        this.trackDeallocatedReferencedMemory('Buffer');
×
64
      }
65
      this.destroyed = true;
342✔
66
      // @ts-expect-error
67
      this.handle = null;
342✔
68
    }
69
  }
70

71
  /** Allocate a new buffer and initialize to contents of typed array */
72
  _initWithData(
73
    data: ArrayBuffer | ArrayBufferView,
74
    byteOffset: number = 0,
230✔
75
    byteLength: number = data.byteLength + byteOffset
230✔
76
  ): void {
77
    // const glTarget = this.device.isWebGL2 ? GL.COPY_WRITE_BUFFER : this.glTarget;
78
    const glTarget = this.glTarget;
230✔
79
    this.gl.bindBuffer(glTarget, this.handle);
230✔
80
    this.gl.bufferData(glTarget, byteLength, this.glUsage);
230✔
81
    this.gl.bufferSubData(glTarget, byteOffset, data);
230✔
82
    this.gl.bindBuffer(glTarget, null);
230✔
83

84
    this.bytesUsed = byteLength;
230✔
85
    this.byteLength = byteLength;
230✔
86

87
    this._setDebugData(data, byteOffset, byteLength);
230✔
88
    if (!this.props.handle) {
230!
89
      this.trackAllocatedMemory(byteLength);
230✔
90
    } else {
91
      this.trackReferencedMemory(byteLength, 'Buffer');
×
92
    }
93
  }
94

95
  // Allocate a GPU buffer of specified size.
96
  _initWithByteLength(byteLength: number): this {
97
    // assert(byteLength >= 0);
98

99
    // Workaround needed for Safari (#291):
100
    // gl.bufferData with size equal to 0 crashes. Instead create zero sized array.
101
    let data = byteLength;
167✔
102
    if (byteLength === 0) {
167✔
103
      // @ts-expect-error
104
      data = new Float32Array(0);
1✔
105
    }
106

107
    // const glTarget = this.device.isWebGL2 ? GL.COPY_WRITE_BUFFER : this.glTarget;
108
    const glTarget = this.glTarget;
167✔
109

110
    this.gl.bindBuffer(glTarget, this.handle);
167✔
111
    this.gl.bufferData(glTarget, data, this.glUsage);
167✔
112
    this.gl.bindBuffer(glTarget, null);
167✔
113

114
    this.bytesUsed = byteLength;
167✔
115
    this.byteLength = byteLength;
167✔
116

117
    this._setDebugData(null, 0, byteLength);
167✔
118
    if (!this.props.handle) {
167!
119
      this.trackAllocatedMemory(byteLength);
167✔
120
    } else {
121
      this.trackReferencedMemory(byteLength, 'Buffer');
×
122
    }
123

124
    return this;
167✔
125
  }
126

127
  write(data: ArrayBufferLike | ArrayBufferView, byteOffset: number = 0): void {
22✔
128
    const dataView = ArrayBuffer.isView(data) ? data : new Uint8Array(data);
22✔
129
    const srcOffset = 0;
22✔
130
    const byteLength = undefined; // data.byteLength;
22✔
131

132
    // Create the buffer - binding it here for the first time locks the type
133
    // In WebGL2, use GL.COPY_WRITE_BUFFER to avoid locking the type
134
    const glTarget = GL.COPY_WRITE_BUFFER;
22✔
135
    this.gl.bindBuffer(glTarget, this.handle);
22✔
136
    // WebGL2: subData supports additional srcOffset and length parameters
137
    if (srcOffset !== 0 || byteLength !== undefined) {
22!
138
      this.gl.bufferSubData(glTarget, byteOffset, dataView, srcOffset, byteLength);
×
139
    } else {
140
      this.gl.bufferSubData(glTarget, byteOffset, dataView);
22✔
141
    }
142
    this.gl.bindBuffer(glTarget, null);
22✔
143

144
    this._setDebugData(data, byteOffset, data.byteLength);
22✔
145
  }
146

147
  async mapAndWriteAsync(
148
    callback: BufferMapCallback<void>,
149
    byteOffset: number = 0,
2✔
150
    byteLength: number = this.byteLength - byteOffset
2✔
151
  ): Promise<void> {
152
    const arrayBuffer = new ArrayBuffer(byteLength);
2✔
153
    // eslint-disable-next-line @typescript-eslint/await-thenable
154
    await callback(arrayBuffer, 'copied');
2✔
155
    this.write(arrayBuffer, byteOffset);
2✔
156
  }
157

158
  async readAsync(byteOffset = 0, byteLength?: number): Promise<Uint8Array<ArrayBuffer>> {
125✔
159
    return this.readSyncWebGL(byteOffset, byteLength);
125✔
160
  }
161

162
  async mapAndReadAsync<T>(
163
    callback: BufferMapCallback<T>,
164
    byteOffset = 0,
2✔
165
    byteLength?: number
166
  ): Promise<T> {
167
    const data = await this.readAsync(byteOffset, byteLength);
2✔
168
    // eslint-disable-next-line @typescript-eslint/await-thenable
169
    return await callback(data.buffer, 'copied');
2✔
170
  }
171

172
  readSyncWebGL(byteOffset = 0, byteLength?: number): Uint8Array<ArrayBuffer> {
125✔
173
    byteLength = byteLength ?? this.byteLength - byteOffset;
125✔
174
    const data = new Uint8Array(byteLength);
125✔
175
    const dstOffset = 0;
125✔
176

177
    // Use GL.COPY_READ_BUFFER to avoid disturbing other targets and locking type
178
    this.gl.bindBuffer(GL.COPY_READ_BUFFER, this.handle);
125✔
179
    this.gl.getBufferSubData(GL.COPY_READ_BUFFER, byteOffset, data, dstOffset, byteLength);
125✔
180
    this.gl.bindBuffer(GL.COPY_READ_BUFFER, null);
125✔
181

182
    // Update local `data` if offsets are 0
183
    this._setDebugData(data, byteOffset, byteLength);
125✔
184

185
    return data;
125✔
186
  }
187
}
188

189
/**
190
 * Returns a WebGL buffer target
191
 *
192
 * @param usage
193
 * static MAP_READ = 0x01;
194
 * static MAP_WRITE = 0x02;
195
 * static COPY_SRC = 0x0004;
196
 * static COPY_DST = 0x0008;
197
 * static INDEX = 0x0010;
198
 * static VERTEX = 0x0020;
199
 * static UNIFORM = 0x0040;
200
 * static STORAGE = 0x0080;
201
 * static INDIRECT = 0x0100;
202
 * static QUERY_RESOLVE = 0x0200;
203
 *
204
 * @returns WebGL buffer targe
205
 *
206
 * Buffer bind points in WebGL2
207
 * gl.COPY_READ_BUFFER: Buffer for copying from one buffer object to another.
208
 * gl.COPY_WRITE_BUFFER: Buffer for copying from one buffer object to another.
209
 * gl.TRANSFORM_FEEDBACK_BUFFER: Buffer for transform feedback operations.
210
 * gl.PIXEL_PACK_BUFFER: Buffer used for pixel transfer operations.
211
 * gl.PIXEL_UNPACK_BUFFER: Buffer used for pixel transfer operations.
212
 */
213
function getWebGLTarget(
214
  usage: number
215
): GL.ARRAY_BUFFER | GL.ELEMENT_ARRAY_BUFFER | GL.UNIFORM_BUFFER {
216
  if (usage & Buffer.INDEX) {
397✔
217
    return GL.ELEMENT_ARRAY_BUFFER;
21✔
218
  }
219
  if (usage & Buffer.VERTEX) {
376✔
220
    return GL.ARRAY_BUFFER;
6✔
221
  }
222
  if (usage & Buffer.UNIFORM) {
370✔
223
    return GL.UNIFORM_BUFFER;
46✔
224
  }
225

226
  // Binding a buffer for the first time locks the type
227
  // In WebGL2, we can use GL.COPY_WRITE_BUFFER to avoid locking the type
228
  return GL.ARRAY_BUFFER;
324✔
229
}
230

231
/** @todo usage is not passed correctly */
232
function getWebGLUsage(usage: number): GL.STATIC_DRAW | GL.DYNAMIC_DRAW {
233
  if (usage & Buffer.INDEX) {
397✔
234
    return GL.STATIC_DRAW;
21✔
235
  }
236
  if (usage & Buffer.VERTEX) {
376✔
237
    return GL.STATIC_DRAW;
6✔
238
  }
239
  if (usage & Buffer.UNIFORM) {
370✔
240
    return GL.DYNAMIC_DRAW;
46✔
241
  }
242
  return GL.STATIC_DRAW;
324✔
243
}
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