@luma.gl/webgl 9.3.0-alpha.2 → 9.3.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/adapter/converters/webgl-texture-table.d.ts +7 -1
  2. package/dist/adapter/converters/webgl-texture-table.d.ts.map +1 -1
  3. package/dist/adapter/converters/webgl-texture-table.js +121 -43
  4. package/dist/adapter/converters/webgl-texture-table.js.map +1 -1
  5. package/dist/adapter/device-helpers/webgl-device-features.d.ts.map +1 -1
  6. package/dist/adapter/device-helpers/webgl-device-features.js +1 -2
  7. package/dist/adapter/device-helpers/webgl-device-features.js.map +1 -1
  8. package/dist/adapter/device-helpers/webgl-device-info.js +5 -0
  9. package/dist/adapter/device-helpers/webgl-device-info.js.map +1 -1
  10. package/dist/adapter/helpers/get-shader-layout-from-glsl.js +23 -21
  11. package/dist/adapter/helpers/get-shader-layout-from-glsl.js.map +1 -1
  12. package/dist/adapter/helpers/parse-shader-compiler-log.d.ts +1 -1
  13. package/dist/adapter/helpers/parse-shader-compiler-log.d.ts.map +1 -1
  14. package/dist/adapter/helpers/parse-shader-compiler-log.js +20 -0
  15. package/dist/adapter/helpers/parse-shader-compiler-log.js.map +1 -1
  16. package/dist/adapter/resources/webgl-buffer.d.ts.map +1 -1
  17. package/dist/adapter/resources/webgl-buffer.js +19 -4
  18. package/dist/adapter/resources/webgl-buffer.js.map +1 -1
  19. package/dist/adapter/resources/webgl-command-buffer.d.ts +3 -4
  20. package/dist/adapter/resources/webgl-command-buffer.d.ts.map +1 -1
  21. package/dist/adapter/resources/webgl-command-buffer.js +11 -7
  22. package/dist/adapter/resources/webgl-command-buffer.js.map +1 -1
  23. package/dist/adapter/resources/webgl-command-encoder.d.ts +5 -4
  24. package/dist/adapter/resources/webgl-command-encoder.d.ts.map +1 -1
  25. package/dist/adapter/resources/webgl-command-encoder.js +20 -7
  26. package/dist/adapter/resources/webgl-command-encoder.js.map +1 -1
  27. package/dist/adapter/resources/webgl-query-set.d.ts +29 -31
  28. package/dist/adapter/resources/webgl-query-set.d.ts.map +1 -1
  29. package/dist/adapter/resources/webgl-query-set.js +193 -97
  30. package/dist/adapter/resources/webgl-query-set.js.map +1 -1
  31. package/dist/adapter/resources/webgl-render-pass.d.ts.map +1 -1
  32. package/dist/adapter/resources/webgl-render-pass.js +17 -0
  33. package/dist/adapter/resources/webgl-render-pass.js.map +1 -1
  34. package/dist/adapter/resources/webgl-render-pipeline.d.ts +13 -19
  35. package/dist/adapter/resources/webgl-render-pipeline.d.ts.map +1 -1
  36. package/dist/adapter/resources/webgl-render-pipeline.js +36 -154
  37. package/dist/adapter/resources/webgl-render-pipeline.js.map +1 -1
  38. package/dist/adapter/resources/webgl-shared-render-pipeline.d.ts +24 -0
  39. package/dist/adapter/resources/webgl-shared-render-pipeline.d.ts.map +1 -0
  40. package/dist/adapter/resources/webgl-shared-render-pipeline.js +152 -0
  41. package/dist/adapter/resources/webgl-shared-render-pipeline.js.map +1 -0
  42. package/dist/adapter/resources/webgl-texture.d.ts +23 -4
  43. package/dist/adapter/resources/webgl-texture.d.ts.map +1 -1
  44. package/dist/adapter/resources/webgl-texture.js +203 -100
  45. package/dist/adapter/resources/webgl-texture.js.map +1 -1
  46. package/dist/adapter/resources/webgl-transform-feedback.js +5 -5
  47. package/dist/adapter/resources/webgl-transform-feedback.js.map +1 -1
  48. package/dist/adapter/webgl-adapter.d.ts.map +1 -1
  49. package/dist/adapter/webgl-adapter.js +3 -4
  50. package/dist/adapter/webgl-adapter.js.map +1 -1
  51. package/dist/adapter/webgl-device.d.ts +6 -3
  52. package/dist/adapter/webgl-device.d.ts.map +1 -1
  53. package/dist/adapter/webgl-device.js +56 -14
  54. package/dist/adapter/webgl-device.js.map +1 -1
  55. package/dist/adapter/webgl-presentation-context.d.ts +21 -0
  56. package/dist/adapter/webgl-presentation-context.d.ts.map +1 -0
  57. package/dist/adapter/webgl-presentation-context.js +64 -0
  58. package/dist/adapter/webgl-presentation-context.js.map +1 -0
  59. package/dist/context/debug/spector.d.ts.map +1 -1
  60. package/dist/context/debug/spector.js +4 -4
  61. package/dist/context/debug/spector.js.map +1 -1
  62. package/dist/context/debug/webgl-developer-tools.js +2 -0
  63. package/dist/context/debug/webgl-developer-tools.js.map +1 -1
  64. package/dist/context/helpers/create-browser-context.d.ts.map +1 -1
  65. package/dist/context/helpers/create-browser-context.js +6 -8
  66. package/dist/context/helpers/create-browser-context.js.map +1 -1
  67. package/dist/context/helpers/webgl-context-data.d.ts +5 -1
  68. package/dist/context/helpers/webgl-context-data.d.ts.map +1 -1
  69. package/dist/context/helpers/webgl-context-data.js +9 -10
  70. package/dist/context/helpers/webgl-context-data.js.map +1 -1
  71. package/dist/context/parameters/unified-parameter-api.d.ts +1 -1
  72. package/dist/context/parameters/unified-parameter-api.js +2 -2
  73. package/dist/context/parameters/unified-parameter-api.js.map +1 -1
  74. package/dist/context/state-tracker/webgl-state-tracker.js +2 -2
  75. package/dist/context/state-tracker/webgl-state-tracker.js.map +1 -1
  76. package/dist/dist.dev.js +1427 -828
  77. package/dist/dist.min.js +2 -2
  78. package/dist/index.cjs +1325 -811
  79. package/dist/index.cjs.map +4 -4
  80. package/dist/utils/fill-array.js +1 -1
  81. package/dist/utils/fill-array.js.map +1 -1
  82. package/package.json +4 -4
  83. package/src/adapter/converters/webgl-texture-table.ts +159 -47
  84. package/src/adapter/device-helpers/webgl-device-features.ts +1 -2
  85. package/src/adapter/device-helpers/webgl-device-info.ts +6 -0
  86. package/src/adapter/helpers/get-shader-layout-from-glsl.ts +25 -24
  87. package/src/adapter/helpers/parse-shader-compiler-log.ts +23 -1
  88. package/src/adapter/resources/webgl-buffer.ts +16 -4
  89. package/src/adapter/resources/webgl-command-buffer.ts +21 -24
  90. package/src/adapter/resources/webgl-command-encoder.ts +22 -7
  91. package/src/adapter/resources/webgl-query-set.ts +229 -102
  92. package/src/adapter/resources/webgl-render-pass.ts +19 -0
  93. package/src/adapter/resources/webgl-render-pipeline.ts +46 -181
  94. package/src/adapter/resources/webgl-shared-render-pipeline.ts +208 -0
  95. package/src/adapter/resources/webgl-texture.ts +326 -121
  96. package/src/adapter/resources/webgl-transform-feedback.ts +5 -5
  97. package/src/adapter/webgl-adapter.ts +3 -4
  98. package/src/adapter/webgl-device.ts +66 -19
  99. package/src/adapter/webgl-presentation-context.ts +93 -0
  100. package/src/context/debug/spector.ts +4 -4
  101. package/src/context/debug/webgl-developer-tools.ts +2 -0
  102. package/src/context/helpers/create-browser-context.ts +8 -8
  103. package/src/context/helpers/webgl-context-data.ts +17 -11
  104. package/src/context/parameters/unified-parameter-api.ts +2 -2
  105. package/src/context/state-tracker/webgl-state-tracker.ts +2 -2
  106. package/src/utils/fill-array.ts +1 -1
@@ -11,11 +11,9 @@ import {
11
11
  type Sampler,
12
12
  type SamplerProps,
13
13
  type CopyExternalImageOptions,
14
- type CopyImageDataOptions,
15
14
  type TextureReadOptions,
16
15
  type TextureWriteOptions,
17
16
  type TextureFormat,
18
- type TypedArray,
19
17
  Buffer,
20
18
  Texture,
21
19
  log
@@ -35,12 +33,12 @@ import {getTextureFormatWebGL} from '../converters/webgl-texture-table';
35
33
  import {convertSamplerParametersToWebGL} from '../converters/sampler-parameters';
36
34
  import {withGLParameters} from '../../context/state-tracker/with-parameters';
37
35
  import {WebGLDevice} from '../webgl-device';
36
+ import {WEBGLBuffer} from './webgl-buffer';
38
37
  import {WEBGLFramebuffer} from './webgl-framebuffer';
39
38
  import {WEBGLSampler} from './webgl-sampler';
40
39
  import {WEBGLTextureView} from './webgl-texture-view';
41
- import {convertDataTypeToGLDataType} from '../converters/webgl-shadertypes';
42
40
  import {convertGLDataTypeToDataType} from '../converters/shader-formats';
43
- import {getTypedArrayConstructor, getDataType} from '@luma.gl/core';
41
+ import {getTypedArrayConstructor} from '@luma.gl/core';
44
42
 
45
43
  /**
46
44
  * WebGL... the texture API from hell... hopefully made simpler
@@ -77,8 +75,10 @@ export class WEBGLTexture extends Texture {
77
75
  // state
78
76
  /** Texture binding slot - TODO - move to texture view? */
79
77
  _textureUnit: number = 0;
80
- /** Chached framebuffer */
78
+ /** Cached framebuffer reused for color texture readback. */
81
79
  _framebuffer: WEBGLFramebuffer | null = null;
80
+ /** Cache key for the currently attached readback subresource `${mipLevel}:${layer}`. */
81
+ _framebufferAttachmentKey: string | null = null;
82
82
 
83
83
  constructor(device: Device, props: TextureProps) {
84
84
  // const byteAlignment = this._getRowByteAlignment(props.format, props.width);
@@ -107,23 +107,31 @@ export class WEBGLTexture extends Texture {
107
107
  */
108
108
  this.gl.bindTexture(this.glTarget, this.handle);
109
109
  const {dimension, width, height, depth, mipLevels, glTarget, glInternalFormat} = this;
110
- switch (dimension) {
111
- case '2d':
112
- case 'cube':
113
- this.gl.texStorage2D(glTarget, mipLevels, glInternalFormat, width, height);
114
- break;
115
- case '2d-array':
116
- case '3d':
117
- this.gl.texStorage3D(glTarget, mipLevels, glInternalFormat, width, height, depth);
118
- break;
119
- default:
120
- throw new Error(dimension);
110
+ if (!this.compressed) {
111
+ switch (dimension) {
112
+ case '2d':
113
+ case 'cube':
114
+ this.gl.texStorage2D(glTarget, mipLevels, glInternalFormat, width, height);
115
+ break;
116
+ case '2d-array':
117
+ case '3d':
118
+ this.gl.texStorage3D(glTarget, mipLevels, glInternalFormat, width, height, depth);
119
+ break;
120
+ default:
121
+ throw new Error(dimension);
122
+ }
121
123
  }
122
124
  this.gl.bindTexture(this.glTarget, null);
123
125
 
124
126
  // Set data
125
127
  this._initializeData(props.data);
126
128
 
129
+ if (!this.props.handle) {
130
+ this.trackAllocatedMemory(this.getAllocatedByteLength(), 'Texture');
131
+ } else {
132
+ this.trackReferencedMemory(this.getAllocatedByteLength(), 'Texture');
133
+ }
134
+
127
135
  // Set texture sampler parameters
128
136
  this.setSampler(this.props.sampler);
129
137
  // @ts-ignore TODO - fix types
@@ -137,10 +145,15 @@ export class WEBGLTexture extends Texture {
137
145
  // Destroy any cached framebuffer
138
146
  this._framebuffer?.destroy();
139
147
  this._framebuffer = null;
148
+ this._framebufferAttachmentKey = null;
140
149
 
141
- this.gl.deleteTexture(this.handle);
142
150
  this.removeStats();
143
- this.trackDeallocatedMemory('Texture');
151
+ if (!this.props.handle) {
152
+ this.gl.deleteTexture(this.handle);
153
+ this.trackDeallocatedMemory('Texture');
154
+ } else {
155
+ this.trackDeallocatedReferencedMemory('Texture');
156
+ }
144
157
  // this.handle = null;
145
158
  this.destroyed = true;
146
159
  }
@@ -196,103 +209,159 @@ export class WEBGLTexture extends Texture {
196
209
  return {width: options.width, height: options.height};
197
210
  }
198
211
 
199
- copyImageData(options_: CopyImageDataOptions): void {
200
- const options = this._normalizeCopyImageDataOptions(options_);
212
+ override copyImageData(options_): void {
213
+ super.copyImageData(options_);
214
+ }
201
215
 
202
- const typedArray = options.data as TypedArray;
203
- const {width, height, depth, z = 0} = options;
204
- const {mipLevel = 0, byteOffset = 0, x = 0, y = 0} = options;
205
- const {glFormat, glType, compressed} = this;
216
+ /**
217
+ * Reads a color texture subresource into a GPU buffer using `PIXEL_PACK_BUFFER`.
218
+ *
219
+ * @note Only first-pass color readback is supported. Unsupported formats and aspects throw
220
+ * before any WebGL calls are issued.
221
+ */
222
+ readBuffer(options: TextureReadOptions = {}, buffer?: Buffer): Buffer {
223
+ const normalizedOptions = this._getSupportedColorReadOptions(options);
224
+ const memoryLayout = this.computeMemoryLayout(normalizedOptions);
225
+ const readBuffer =
226
+ buffer ||
227
+ this.device.createBuffer({
228
+ byteLength: memoryLayout.byteLength,
229
+ usage: Buffer.COPY_DST | Buffer.MAP_READ
230
+ });
231
+
232
+ if (readBuffer.byteLength < memoryLayout.byteLength) {
233
+ throw new Error(
234
+ `${this} readBuffer target is too small (${readBuffer.byteLength} < ${memoryLayout.byteLength})`
235
+ );
236
+ }
237
+
238
+ const webglBuffer = readBuffer as WEBGLBuffer;
239
+ this.gl.bindBuffer(GL.PIXEL_PACK_BUFFER, webglBuffer.handle);
240
+ try {
241
+ this._readColorTextureLayers(normalizedOptions, memoryLayout, destinationByteOffset => {
242
+ this.gl.readPixels(
243
+ normalizedOptions.x,
244
+ normalizedOptions.y,
245
+ normalizedOptions.width,
246
+ normalizedOptions.height,
247
+ this.glFormat,
248
+ this.glType,
249
+ destinationByteOffset
250
+ );
251
+ });
252
+ } finally {
253
+ this.gl.bindBuffer(GL.PIXEL_PACK_BUFFER, null);
254
+ }
255
+
256
+ return readBuffer;
257
+ }
258
+
259
+ async readDataAsync(options: TextureReadOptions = {}): Promise<ArrayBuffer> {
260
+ const buffer = this.readBuffer(options);
261
+ const data = await buffer.readAsync();
262
+ buffer.destroy();
263
+ return data.buffer as ArrayBuffer;
264
+ }
206
265
 
207
- // Target used for face updates, but not for binding
266
+ writeBuffer(buffer: Buffer, options_: TextureWriteOptions = {}) {
267
+ const options = this._normalizeTextureWriteOptions(options_);
268
+ const {width, height, depthOrArrayLayers, mipLevel, byteOffset, x, y, z} = options;
269
+ const {glFormat, glType, compressed} = this;
208
270
  const glTarget = getWebGLCubeFaceTarget(this.glTarget, this.dimension, z);
209
271
 
210
- let unpackRowLength: number | undefined;
211
- if (!this.compressed) {
212
- const {bytesPerPixel} = this.device.getTextureFormatInfo(this.format);
213
- if (bytesPerPixel) {
214
- if (options.bytesPerRow % bytesPerPixel !== 0) {
215
- throw new Error(
216
- `bytesPerRow (${options.bytesPerRow}) must be a multiple of bytesPerPixel (${bytesPerPixel}) for ${this.format}`
217
- );
218
- }
219
- unpackRowLength = options.bytesPerRow / bytesPerPixel;
220
- }
272
+ if (compressed) {
273
+ throw new Error('writeBuffer for compressed textures is not implemented in WebGL');
221
274
  }
222
275
 
223
- const glParameters: GLValueParameters = !this.compressed
224
- ? {
225
- [GL.UNPACK_ALIGNMENT]: this.byteAlignment,
226
- ...(unpackRowLength !== undefined ? {[GL.UNPACK_ROW_LENGTH]: unpackRowLength} : {}),
227
- [GL.UNPACK_IMAGE_HEIGHT]: options.rowsPerImage
228
- }
229
- : {};
276
+ const {bytesPerPixel} = this.device.getTextureFormatInfo(this.format);
277
+ const unpackRowLength = bytesPerPixel ? options.bytesPerRow / bytesPerPixel : undefined;
278
+ const glParameters: GLValueParameters = {
279
+ [GL.UNPACK_ALIGNMENT]: this.byteAlignment,
280
+ ...(unpackRowLength !== undefined ? {[GL.UNPACK_ROW_LENGTH]: unpackRowLength} : {}),
281
+ [GL.UNPACK_IMAGE_HEIGHT]: options.rowsPerImage
282
+ };
230
283
 
231
284
  this.gl.bindTexture(this.glTarget, this.handle);
285
+ this.gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, buffer.handle);
232
286
 
233
287
  withGLParameters(this.gl, glParameters, () => {
234
288
  switch (this.dimension) {
235
289
  case '2d':
236
290
  case 'cube':
237
- if (compressed) {
238
- // prettier-ignore
239
- this.gl.compressedTexSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, typedArray, byteOffset); // , byteLength
240
- } else {
241
- // prettier-ignore
242
- this.gl.texSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, glType, typedArray, byteOffset); // , byteLength
243
- }
291
+ this.gl.texSubImage2D(
292
+ glTarget,
293
+ mipLevel,
294
+ x,
295
+ y,
296
+ width,
297
+ height,
298
+ glFormat,
299
+ glType,
300
+ byteOffset
301
+ );
244
302
  break;
245
303
  case '2d-array':
246
304
  case '3d':
247
- if (compressed) {
248
- // prettier-ignore
249
- this.gl.compressedTexSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, typedArray, byteOffset); // , byteLength
250
- } else {
251
- // prettier-ignore
252
- this.gl.texSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, glType, typedArray, byteOffset); // , byteLength
253
- }
305
+ this.gl.texSubImage3D(
306
+ glTarget,
307
+ mipLevel,
308
+ x,
309
+ y,
310
+ z,
311
+ width,
312
+ height,
313
+ depthOrArrayLayers,
314
+ glFormat,
315
+ glType,
316
+ byteOffset
317
+ );
254
318
  break;
255
319
  default:
256
- // Can never happen in WebGL
257
320
  }
258
321
  });
259
322
 
323
+ this.gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, null);
260
324
  this.gl.bindTexture(this.glTarget, null);
261
325
  }
262
326
 
263
- readBuffer(options: TextureReadOptions = {}, buffer?: Buffer): Buffer {
264
- throw new Error('readBuffer not implemented');
265
- }
266
-
267
- async readDataAsync(options: TextureReadOptions = {}): Promise<ArrayBuffer> {
268
- return this.readDataSyncWebGL(options);
269
- }
270
-
271
- writeBuffer(buffer: Buffer, options_: TextureWriteOptions = {}) {}
272
-
273
- writeData(data: ArrayBuffer | ArrayBufferView, options_: TextureWriteOptions = {}): void {
327
+ writeData(
328
+ data: ArrayBuffer | SharedArrayBuffer | ArrayBufferView,
329
+ options_: TextureWriteOptions = {}
330
+ ): void {
274
331
  const options = this._normalizeTextureWriteOptions(options_);
275
332
 
276
333
  const typedArray = ArrayBuffer.isView(data) ? data : new Uint8Array(data);
277
- const {} = this;
278
- const {width, height, mipLevel, x, y, z} = options;
334
+ const {width, height, depthOrArrayLayers, mipLevel, x, y, z, byteOffset} = options;
279
335
  const {glFormat, glType, compressed} = this;
280
- const depth = 0; // TODO - fix
281
- const glTarget = getWebGLCubeFaceTarget(this.glTarget, this.dimension, depth);
336
+ const glTarget = getWebGLCubeFaceTarget(this.glTarget, this.dimension, z);
282
337
 
283
- // const byteOffset = 0;
284
- // const {bytesPerRow, rowsPerImage} = this.computeMemoryLayout(options);
338
+ let unpackRowLength: number | undefined;
339
+ if (!compressed) {
340
+ const {bytesPerPixel} = this.device.getTextureFormatInfo(this.format);
341
+ if (bytesPerPixel) {
342
+ unpackRowLength = options.bytesPerRow / bytesPerPixel;
343
+ }
344
+ }
285
345
 
286
346
  const glParameters: GLValueParameters = !this.compressed
287
347
  ? {
288
- // WebGL does not require byte alignment, but allows it to be specified
289
- [GL.UNPACK_ALIGNMENT]: this.byteAlignment
290
- // [GL.UNPACK_ROW_LENGTH]: bytesPerRow,
291
- // [GL.UNPACK_IMAGE_HEIGHT]: rowsPerImage
348
+ [GL.UNPACK_ALIGNMENT]: this.byteAlignment,
349
+ ...(unpackRowLength !== undefined ? {[GL.UNPACK_ROW_LENGTH]: unpackRowLength} : {}),
350
+ [GL.UNPACK_IMAGE_HEIGHT]: options.rowsPerImage
292
351
  }
293
352
  : {};
353
+ const sourceElementOffset = getWebGLTextureSourceElementOffset(typedArray, byteOffset);
354
+ const compressedData = compressed ? getArrayBufferView(typedArray, byteOffset) : typedArray;
355
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
356
+ const isFullMipUpload =
357
+ x === 0 &&
358
+ y === 0 &&
359
+ z === 0 &&
360
+ width === mipLevelSize.width &&
361
+ height === mipLevelSize.height &&
362
+ depthOrArrayLayers === mipLevelSize.depthOrArrayLayers;
294
363
 
295
- this.gl.bindTexture(glTarget, this.handle);
364
+ this.gl.bindTexture(this.glTarget, this.handle);
296
365
  this.gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, null);
297
366
 
298
367
  withGLParameters(this.gl, glParameters, () => {
@@ -300,21 +369,51 @@ export class WEBGLTexture extends Texture {
300
369
  case '2d':
301
370
  case 'cube':
302
371
  if (compressed) {
303
- // prettier-ignore
304
- this.gl.compressedTexSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, typedArray);
372
+ if (isFullMipUpload) {
373
+ // prettier-ignore
374
+ this.gl.compressedTexImage2D(glTarget, mipLevel, glFormat, width, height, 0, compressedData);
375
+ } else {
376
+ // prettier-ignore
377
+ this.gl.compressedTexSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, compressedData);
378
+ }
305
379
  } else {
306
380
  // prettier-ignore
307
- this.gl.texSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, glType, typedArray);
381
+ this.gl.texSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, glType, typedArray, sourceElementOffset);
308
382
  }
309
383
  break;
310
384
  case '2d-array':
311
385
  case '3d':
312
386
  if (compressed) {
313
- // prettier-ignore
314
- this.gl.compressedTexSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, typedArray);
387
+ if (isFullMipUpload) {
388
+ // prettier-ignore
389
+ this.gl.compressedTexImage3D(
390
+ glTarget,
391
+ mipLevel,
392
+ glFormat,
393
+ width,
394
+ height,
395
+ depthOrArrayLayers,
396
+ 0,
397
+ compressedData
398
+ );
399
+ } else {
400
+ // prettier-ignore
401
+ this.gl.compressedTexSubImage3D(
402
+ glTarget,
403
+ mipLevel,
404
+ x,
405
+ y,
406
+ z,
407
+ width,
408
+ height,
409
+ depthOrArrayLayers,
410
+ glFormat,
411
+ compressedData
412
+ );
413
+ }
315
414
  } else {
316
415
  // prettier-ignore
317
- this.gl.texSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, glType, typedArray);
416
+ this.gl.texSubImage3D(glTarget, mipLevel, x, y, z, width, height, depthOrArrayLayers, glFormat, glType, typedArray, sourceElementOffset);
318
417
  }
319
418
  break;
320
419
  default:
@@ -322,7 +421,7 @@ export class WEBGLTexture extends Texture {
322
421
  }
323
422
  });
324
423
 
325
- this.gl.bindTexture(glTarget, null);
424
+ this.gl.bindTexture(this.glTarget, null);
326
425
  }
327
426
 
328
427
  // IMPLEMENTATION SPECIFIC
@@ -357,57 +456,138 @@ export class WEBGLTexture extends Texture {
357
456
  // WEBGL SPECIFIC
358
457
 
359
458
  override readDataSyncWebGL(options_: TextureReadOptions = {}): ArrayBuffer {
360
- const options = this._normalizeTextureReadOptions(options_);
361
-
459
+ const options = this._getSupportedColorReadOptions(options_);
362
460
  const memoryLayout = this.computeMemoryLayout(options);
363
461
 
364
462
  // const formatInfo = getTextureFormatInfo(format);
365
463
  // Allocate pixel array if not already available, using supplied type
366
464
  const shaderType = convertGLDataTypeToDataType(this.glType);
367
-
368
465
  const ArrayType = getTypedArrayConstructor(shaderType);
369
- // const components = glFormatToComponents(this.glFormat);
370
- // TODO - check for composite type (components = 1).
371
-
372
- const targetArray = new ArrayType(memoryLayout.byteLength) as
466
+ const targetArray = new ArrayType(memoryLayout.byteLength / ArrayType.BYTES_PER_ELEMENT) as
373
467
  | Uint8Array
374
468
  | Uint16Array
375
- | Float32Array;
376
-
377
- // Pixel array available, if necessary, deduce type from it.
378
- const signedType = getDataType(targetArray);
379
- const sourceType = convertDataTypeToGLDataType(signedType);
469
+ | Float32Array
470
+ | Int8Array
471
+ | Int16Array
472
+ | Int32Array
473
+ | Uint32Array;
474
+
475
+ this._readColorTextureLayers(options, memoryLayout, destinationByteOffset => {
476
+ const layerView = new ArrayType(
477
+ targetArray.buffer,
478
+ targetArray.byteOffset + destinationByteOffset,
479
+ memoryLayout.bytesPerImage / ArrayType.BYTES_PER_ELEMENT
480
+ );
481
+ this.gl.readPixels(
482
+ options.x,
483
+ options.y,
484
+ options.width,
485
+ options.height,
486
+ this.glFormat,
487
+ this.glType,
488
+ layerView
489
+ );
490
+ });
380
491
 
381
- // There is a lot of hedging in the WebGL2 spec about what formats are guaranteed to be readable
382
- // (It should always be possible to read RGBA/UNSIGNED_BYTE, but most other combinations are not guaranteed)
383
- // Querying is possible but expensive:
384
- // const {device} = framebuffer;
385
- // texture.glReadFormat ||= gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
386
- // texture.glReadType ||= gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE);
387
- // console.log('params', device.getGLKey(texture.glReadFormat), device.getGLKey(texture.glReadType));
492
+ return targetArray.buffer as ArrayBuffer;
493
+ }
388
494
 
495
+ /**
496
+ * Iterates the requested mip/layer/slice range, reattaching the cached read framebuffer as
497
+ * needed before delegating the actual `readPixels()` call to the supplied callback.
498
+ */
499
+ private _readColorTextureLayers(
500
+ options: Required<TextureReadOptions>,
501
+ memoryLayout: ReturnType<Texture['computeMemoryLayout']>,
502
+ readLayer: (destinationByteOffset: number) => void
503
+ ): void {
389
504
  const framebuffer = this._getFramebuffer();
390
-
391
- // Note: luma.gl overrides bindFramebuffer so that we can reliably restore the previous framebuffer (this is the only function for which we do that)
505
+ const packRowLength = memoryLayout.bytesPerRow / memoryLayout.bytesPerPixel;
506
+ const glParameters: GLValueParameters = {
507
+ [GL.PACK_ALIGNMENT]: this.byteAlignment,
508
+ ...(packRowLength !== options.width ? {[GL.PACK_ROW_LENGTH]: packRowLength} : {})
509
+ };
510
+
511
+ // Note: luma.gl overrides bindFramebuffer so that we can reliably restore the previous framebuffer.
512
+ const prevReadBuffer = this.gl.getParameter(GL.READ_BUFFER) as GL;
392
513
  const prevHandle = this.gl.bindFramebuffer(
393
514
  GL.FRAMEBUFFER,
394
515
  framebuffer.handle
395
516
  ) as unknown as WebGLFramebuffer | null;
396
517
 
397
- // Select the color attachment to read from
398
- this.gl.readBuffer(GL.COLOR_ATTACHMENT0);
399
- this.gl.readPixels(
400
- options.x,
401
- options.y,
402
- options.width,
403
- options.height,
404
- this.glFormat,
405
- sourceType,
406
- targetArray
407
- );
408
- this.gl.bindFramebuffer(GL.FRAMEBUFFER, prevHandle || null);
518
+ try {
519
+ this.gl.readBuffer(GL.COLOR_ATTACHMENT0);
520
+ withGLParameters(this.gl, glParameters, () => {
521
+ for (let layerIndex = 0; layerIndex < options.depthOrArrayLayers; layerIndex++) {
522
+ this._attachReadSubresource(framebuffer, options.mipLevel, options.z + layerIndex);
523
+ readLayer(layerIndex * memoryLayout.bytesPerImage);
524
+ }
525
+ });
526
+ } finally {
527
+ this.gl.bindFramebuffer(GL.FRAMEBUFFER, prevHandle || null);
528
+ this.gl.readBuffer(prevReadBuffer);
529
+ }
530
+ }
409
531
 
410
- return targetArray.buffer as ArrayBuffer;
532
+ /**
533
+ * Attaches a single color subresource to the cached read framebuffer.
534
+ *
535
+ * @note Repeated attachments of the same `(mipLevel, layer)` tuple are skipped.
536
+ */
537
+ private _attachReadSubresource(
538
+ framebuffer: WEBGLFramebuffer,
539
+ mipLevel: number,
540
+ layer: number
541
+ ): void {
542
+ const attachmentKey = `${mipLevel}:${layer}`;
543
+ if (this._framebufferAttachmentKey === attachmentKey) {
544
+ return;
545
+ }
546
+
547
+ switch (this.dimension) {
548
+ case '2d':
549
+ this.gl.framebufferTexture2D(
550
+ GL.FRAMEBUFFER,
551
+ GL.COLOR_ATTACHMENT0,
552
+ GL.TEXTURE_2D,
553
+ this.handle,
554
+ mipLevel
555
+ );
556
+ break;
557
+
558
+ case 'cube':
559
+ this.gl.framebufferTexture2D(
560
+ GL.FRAMEBUFFER,
561
+ GL.COLOR_ATTACHMENT0,
562
+ getWebGLCubeFaceTarget(this.glTarget, this.dimension, layer),
563
+ this.handle,
564
+ mipLevel
565
+ );
566
+ break;
567
+
568
+ case '2d-array':
569
+ case '3d':
570
+ this.gl.framebufferTextureLayer(
571
+ GL.FRAMEBUFFER,
572
+ GL.COLOR_ATTACHMENT0,
573
+ this.handle,
574
+ mipLevel,
575
+ layer
576
+ );
577
+ break;
578
+
579
+ default:
580
+ throw new Error(`${this} color readback does not support ${this.dimension} textures`);
581
+ }
582
+
583
+ if (this.device.props.debug) {
584
+ const status = Number(this.gl.checkFramebufferStatus(GL.FRAMEBUFFER));
585
+ if (status !== Number(GL.FRAMEBUFFER_COMPLETE)) {
586
+ throw new Error(`${framebuffer} incomplete for ${this} readback (${status})`);
587
+ }
588
+ }
589
+
590
+ this._framebufferAttachmentKey = attachmentKey;
411
591
  }
412
592
 
413
593
  /**
@@ -514,6 +694,31 @@ export class WEBGLTexture extends Texture {
514
694
  }
515
695
  }
516
696
 
697
+ function getArrayBufferView(typedArray: ArrayBufferView, byteOffset = 0): ArrayBufferView {
698
+ if (!byteOffset) {
699
+ return typedArray;
700
+ }
701
+
702
+ return new typedArray.constructor(
703
+ typedArray.buffer,
704
+ typedArray.byteOffset + byteOffset,
705
+ (typedArray.byteLength - byteOffset) / typedArray.BYTES_PER_ELEMENT
706
+ ) as ArrayBufferView;
707
+ }
708
+
709
+ function getWebGLTextureSourceElementOffset(
710
+ typedArray: ArrayBufferView,
711
+ byteOffset: number
712
+ ): number {
713
+ if (byteOffset % typedArray.BYTES_PER_ELEMENT !== 0) {
714
+ throw new Error(
715
+ `Texture byteOffset ${byteOffset} must align to typed array element size ${typedArray.BYTES_PER_ELEMENT}`
716
+ );
717
+ }
718
+
719
+ return byteOffset / typedArray.BYTES_PER_ELEMENT;
720
+ }
721
+
517
722
  // INTERNAL HELPERS
518
723
 
519
724
  /** Convert a WebGPU style texture constant to a WebGL style texture constant */
@@ -69,8 +69,8 @@ export class WEBGLTransformFeedback extends TransformFeedback {
69
69
  this.unusedBuffers = {};
70
70
 
71
71
  this.bind(() => {
72
- for (const bufferName in buffers) {
73
- this.setBuffer(bufferName, buffers[bufferName]);
72
+ for (const [bufferName, buffer] of Object.entries(buffers)) {
73
+ this.setBuffer(bufferName, buffer);
74
74
  }
75
75
  });
76
76
  }
@@ -99,7 +99,7 @@ export class WEBGLTransformFeedback extends TransformFeedback {
99
99
  return this.buffers[locationOrName] || null;
100
100
  }
101
101
  const location = this._getVaryingIndex(locationOrName);
102
- return location >= 0 ? this.buffers[location] : null;
102
+ return this.buffers[location] ?? null;
103
103
  }
104
104
 
105
105
  bind(funcOrHandle: (() => void) | WebGLTransformFeedback | null = this.handle) {
@@ -162,8 +162,8 @@ export class WEBGLTransformFeedback extends TransformFeedback {
162
162
  * cannot be bound to 'TRANSFORM_FEEDBACK_BUFFER' target.
163
163
  */
164
164
  protected _bindBuffers(): void {
165
- for (const bufferIndex in this.buffers) {
166
- const {buffer, byteLength, byteOffset} = this._getBufferRange(this.buffers[bufferIndex]);
165
+ for (const [bufferIndex, bufferEntry] of Object.entries(this.buffers)) {
166
+ const {buffer, byteLength, byteOffset} = this._getBufferRange(bufferEntry);
167
167
  this._bindBuffer(Number(bufferIndex), buffer, byteOffset, byteLength);
168
168
  }
169
169
  }
@@ -55,10 +55,9 @@ export class WebGLAdapter extends Adapter {
55
55
  if (gl instanceof WebGLDevice) {
56
56
  return gl;
57
57
  }
58
- // @ts-expect-error
59
- if (gl?.device instanceof WebGLDevice) {
60
- // @ts-expect-error
61
- return gl.device as WebGLDevice;
58
+ const existingDevice = WebGLDevice.getDeviceFromContext(gl as WebGL2RenderingContext | null);
59
+ if (existingDevice) {
60
+ return existingDevice;
62
61
  }
63
62
  if (!isWebGL(gl)) {
64
63
  throw new Error('Invalid WebGL2RenderingContext');