@luma.gl/webgl 9.2.6 → 9.3.0-alpha.4

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 (84) hide show
  1. package/dist/adapter/helpers/get-shader-layout-from-glsl.js +11 -8
  2. package/dist/adapter/helpers/get-shader-layout-from-glsl.js.map +1 -1
  3. package/dist/adapter/helpers/parse-shader-compiler-log.d.ts +1 -1
  4. package/dist/adapter/helpers/parse-shader-compiler-log.d.ts.map +1 -1
  5. package/dist/adapter/helpers/parse-shader-compiler-log.js +20 -0
  6. package/dist/adapter/helpers/parse-shader-compiler-log.js.map +1 -1
  7. package/dist/adapter/resources/webgl-command-buffer.d.ts +2 -3
  8. package/dist/adapter/resources/webgl-command-buffer.d.ts.map +1 -1
  9. package/dist/adapter/resources/webgl-command-buffer.js +9 -5
  10. package/dist/adapter/resources/webgl-command-buffer.js.map +1 -1
  11. package/dist/adapter/resources/webgl-fence.d.ts +14 -0
  12. package/dist/adapter/resources/webgl-fence.d.ts.map +1 -0
  13. package/dist/adapter/resources/webgl-fence.js +49 -0
  14. package/dist/adapter/resources/webgl-fence.js.map +1 -0
  15. package/dist/adapter/resources/webgl-render-pass.d.ts.map +1 -1
  16. package/dist/adapter/resources/webgl-render-pass.js +9 -6
  17. package/dist/adapter/resources/webgl-render-pass.js.map +1 -1
  18. package/dist/adapter/resources/webgl-render-pipeline.d.ts.map +1 -1
  19. package/dist/adapter/resources/webgl-render-pipeline.js +1 -2
  20. package/dist/adapter/resources/webgl-render-pipeline.js.map +1 -1
  21. package/dist/adapter/resources/webgl-texture.d.ts +21 -4
  22. package/dist/adapter/resources/webgl-texture.d.ts.map +1 -1
  23. package/dist/adapter/resources/webgl-texture.js +148 -22
  24. package/dist/adapter/resources/webgl-texture.js.map +1 -1
  25. package/dist/adapter/resources/webgl-transform-feedback.js +5 -5
  26. package/dist/adapter/resources/webgl-transform-feedback.js.map +1 -1
  27. package/dist/adapter/webgl-adapter.d.ts.map +1 -1
  28. package/dist/adapter/webgl-adapter.js +22 -23
  29. package/dist/adapter/webgl-adapter.js.map +1 -1
  30. package/dist/adapter/webgl-canvas-context.d.ts +2 -2
  31. package/dist/adapter/webgl-canvas-context.d.ts.map +1 -1
  32. package/dist/adapter/webgl-canvas-context.js +16 -6
  33. package/dist/adapter/webgl-canvas-context.js.map +1 -1
  34. package/dist/adapter/webgl-device.d.ts +4 -2
  35. package/dist/adapter/webgl-device.d.ts.map +1 -1
  36. package/dist/adapter/webgl-device.js +39 -24
  37. package/dist/adapter/webgl-device.js.map +1 -1
  38. package/dist/context/debug/spector.d.ts.map +1 -1
  39. package/dist/context/debug/spector.js +4 -4
  40. package/dist/context/debug/spector.js.map +1 -1
  41. package/dist/context/debug/webgl-developer-tools.js +6 -6
  42. package/dist/context/debug/webgl-developer-tools.js.map +1 -1
  43. package/dist/context/helpers/create-browser-context.d.ts.map +1 -1
  44. package/dist/context/helpers/create-browser-context.js +46 -36
  45. package/dist/context/helpers/create-browser-context.js.map +1 -1
  46. package/dist/context/helpers/webgl-context-data.d.ts +5 -1
  47. package/dist/context/helpers/webgl-context-data.d.ts.map +1 -1
  48. package/dist/context/helpers/webgl-context-data.js +9 -10
  49. package/dist/context/helpers/webgl-context-data.js.map +1 -1
  50. package/dist/context/parameters/unified-parameter-api.d.ts +1 -1
  51. package/dist/context/parameters/unified-parameter-api.js +2 -2
  52. package/dist/context/parameters/unified-parameter-api.js.map +1 -1
  53. package/dist/context/state-tracker/webgl-state-tracker.js +2 -2
  54. package/dist/context/state-tracker/webgl-state-tracker.js.map +1 -1
  55. package/dist/dist.dev.js +567 -318
  56. package/dist/dist.min.js +2 -2
  57. package/dist/index.cjs +550 -314
  58. package/dist/index.cjs.map +4 -4
  59. package/dist/index.d.ts +1 -0
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1 -0
  62. package/dist/index.js.map +1 -1
  63. package/dist/utils/fill-array.js +1 -1
  64. package/dist/utils/fill-array.js.map +1 -1
  65. package/package.json +5 -5
  66. package/src/adapter/helpers/get-shader-layout-from-glsl.ts +11 -9
  67. package/src/adapter/helpers/parse-shader-compiler-log.ts +23 -1
  68. package/src/adapter/resources/webgl-command-buffer.ts +18 -22
  69. package/src/adapter/resources/webgl-fence.ts +55 -0
  70. package/src/adapter/resources/webgl-render-pass.ts +10 -6
  71. package/src/adapter/resources/webgl-render-pipeline.ts +1 -2
  72. package/src/adapter/resources/webgl-texture.ts +209 -37
  73. package/src/adapter/resources/webgl-transform-feedback.ts +5 -5
  74. package/src/adapter/webgl-adapter.ts +26 -24
  75. package/src/adapter/webgl-canvas-context.ts +19 -8
  76. package/src/adapter/webgl-device.ts +41 -29
  77. package/src/context/debug/spector.ts +4 -4
  78. package/src/context/debug/webgl-developer-tools.ts +15 -6
  79. package/src/context/helpers/create-browser-context.ts +54 -43
  80. package/src/context/helpers/webgl-context-data.ts +17 -11
  81. package/src/context/parameters/unified-parameter-api.ts +2 -2
  82. package/src/context/state-tracker/webgl-state-tracker.ts +2 -2
  83. package/src/index.ts +1 -0
  84. package/src/utils/fill-array.ts +1 -1
@@ -2,33 +2,45 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {
6
- Device,
7
- TextureProps,
8
- TextureViewProps,
9
- Sampler,
10
- SamplerProps,
11
- CopyExternalImageOptions,
12
- CopyImageDataOptions,
13
- TypedArray
5
+ // @ts-nocheck
6
+
7
+ import {
8
+ type Device,
9
+ type TextureProps,
10
+ type TextureViewProps,
11
+ type Sampler,
12
+ type SamplerProps,
13
+ type CopyExternalImageOptions,
14
+ type CopyImageDataOptions,
15
+ type TextureReadOptions,
16
+ type TextureWriteOptions,
17
+ type TextureFormat,
18
+ type TypedArray,
19
+ Buffer,
20
+ Texture,
21
+ log
14
22
  } from '@luma.gl/core';
15
- import {Texture, log} from '@luma.gl/core';
23
+
16
24
  import {
25
+ GLSamplerParameters,
26
+ GLValueParameters,
17
27
  GL,
18
28
  GLTextureTarget,
19
29
  GLTextureCubeMapTarget,
20
30
  GLTexelDataFormat,
21
- GLPixelType,
22
- // GLDataType,
23
- GLSamplerParameters,
24
- GLValueParameters
31
+ GLPixelType
25
32
  } from '@luma.gl/constants';
33
+
26
34
  import {getTextureFormatWebGL} from '../converters/webgl-texture-table';
27
35
  import {convertSamplerParametersToWebGL} from '../converters/sampler-parameters';
28
36
  import {withGLParameters} from '../../context/state-tracker/with-parameters';
29
37
  import {WebGLDevice} from '../webgl-device';
38
+ import {WEBGLFramebuffer} from './webgl-framebuffer';
30
39
  import {WEBGLSampler} from './webgl-sampler';
31
40
  import {WEBGLTextureView} from './webgl-texture-view';
41
+ import {convertDataTypeToGLDataType} from '../converters/webgl-shadertypes';
42
+ import {convertGLDataTypeToDataType} from '../converters/shader-formats';
43
+ import {getTypedArrayConstructor, getDataType} from '@luma.gl/core';
32
44
 
33
45
  /**
34
46
  * WebGL... the texture API from hell... hopefully made simpler
@@ -65,9 +77,12 @@ export class WEBGLTexture extends Texture {
65
77
  // state
66
78
  /** Texture binding slot - TODO - move to texture view? */
67
79
  _textureUnit: number = 0;
80
+ /** Chached framebuffer */
81
+ _framebuffer: WEBGLFramebuffer | null = null;
68
82
 
69
83
  constructor(device: Device, props: TextureProps) {
70
- super(device, props);
84
+ // const byteAlignment = this._getRowByteAlignment(props.format, props.width);
85
+ super(device, props, {byteAlignment: 1});
71
86
 
72
87
  this.device = device as WebGLDevice;
73
88
  this.gl = this.device.gl;
@@ -119,6 +134,10 @@ export class WEBGLTexture extends Texture {
119
134
 
120
135
  override destroy(): void {
121
136
  if (this.handle) {
137
+ // Destroy any cached framebuffer
138
+ this._framebuffer?.destroy();
139
+ this._framebuffer = null;
140
+
122
141
  this.gl.deleteTexture(this.handle);
123
142
  this.removeStats();
124
143
  this.trackDeallocatedMemory('Texture');
@@ -138,12 +157,51 @@ export class WEBGLTexture extends Texture {
138
157
  this._setSamplerParameters(parameters);
139
158
  }
140
159
 
160
+ copyExternalImage(options_: CopyExternalImageOptions): {width: number; height: number} {
161
+ const options = this._normalizeCopyExternalImageOptions(options_);
162
+
163
+ if (options.sourceX || options.sourceY) {
164
+ // requires copyTexSubImage2D from a framebuffer'
165
+ throw new Error('WebGL does not support sourceX/sourceY)');
166
+ }
167
+
168
+ const {glFormat, glType} = this;
169
+ const {image, depth, mipLevel, x, y, z, width, height} = options;
170
+
171
+ // WebGL cube maps specify faces by overriding target instead of using the z parameter
172
+ const glTarget = getWebGLCubeFaceTarget(this.glTarget, this.dimension, z);
173
+ const glParameters: GLValueParameters = options.flipY ? {[GL.UNPACK_FLIP_Y_WEBGL]: true} : {};
174
+
175
+ this.gl.bindTexture(this.glTarget, this.handle);
176
+
177
+ withGLParameters(this.gl, glParameters, () => {
178
+ switch (this.dimension) {
179
+ case '2d':
180
+ case 'cube':
181
+ // prettier-ignore
182
+ this.gl.texSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, glType, image);
183
+ break;
184
+ case '2d-array':
185
+ case '3d':
186
+ // prettier-ignore
187
+ this.gl.texSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, glType, image);
188
+ break;
189
+ default:
190
+ // Can never happen in WebGL
191
+ }
192
+ });
193
+
194
+ this.gl.bindTexture(this.glTarget, null);
195
+
196
+ return {width: options.width, height: options.height};
197
+ }
198
+
141
199
  copyImageData(options_: CopyImageDataOptions): void {
142
200
  const options = this._normalizeCopyImageDataOptions(options_);
143
201
 
144
202
  const typedArray = options.data as TypedArray;
145
- const {width, height, depth} = this;
146
- const {mipLevel = 0, byteOffset = 0, x = 0, y = 0, z = 0} = options;
203
+ const {width, height, depth, z = 0} = options;
204
+ const {mipLevel = 0, byteOffset = 0, x = 0, y = 0} = options;
147
205
  const {glFormat, glType, compressed} = this;
148
206
 
149
207
  // Target used for face updates, but not for binding
@@ -164,12 +222,13 @@ export class WEBGLTexture extends Texture {
164
222
 
165
223
  const glParameters: GLValueParameters = !this.compressed
166
224
  ? {
225
+ [GL.UNPACK_ALIGNMENT]: this.byteAlignment,
167
226
  ...(unpackRowLength !== undefined ? {[GL.UNPACK_ROW_LENGTH]: unpackRowLength} : {}),
168
227
  [GL.UNPACK_IMAGE_HEIGHT]: options.rowsPerImage
169
228
  }
170
229
  : {};
171
230
 
172
- this.gl.bindTexture(glTarget, this.handle);
231
+ this.gl.bindTexture(this.glTarget, this.handle);
173
232
 
174
233
  withGLParameters(this.gl, glParameters, () => {
175
234
  switch (this.dimension) {
@@ -198,51 +257,163 @@ export class WEBGLTexture extends Texture {
198
257
  }
199
258
  });
200
259
 
201
- this.gl.bindTexture(glTarget, null);
260
+ this.gl.bindTexture(this.glTarget, null);
202
261
  }
203
262
 
204
- copyExternalImage(options_: CopyExternalImageOptions): {width: number; height: number} {
205
- const options = this._normalizeCopyExternalImageOptions(options_);
263
+ readBuffer(options: TextureReadOptions = {}, buffer?: Buffer): Buffer {
264
+ throw new Error('readBuffer not implemented');
265
+ }
206
266
 
207
- if (options.sourceX || options.sourceY) {
208
- // requires copyTexSubImage2D from a framebuffer'
209
- throw new Error('WebGL does not support sourceX/sourceY)');
210
- }
267
+ async readDataAsync(options: TextureReadOptions = {}): Promise<ArrayBuffer> {
268
+ return this.readDataSyncWebGL(options);
269
+ }
211
270
 
212
- const {glFormat, glType} = this;
213
- const {image, depth, mipLevel, x, y, z, width, height} = options;
271
+ writeBuffer(buffer: Buffer, options_: TextureWriteOptions = {}) {}
214
272
 
215
- // WebGL cube maps specify faces by overriding target instead of using the depth parameter
273
+ writeData(data: ArrayBuffer | ArrayBufferView, options_: TextureWriteOptions = {}): void {
274
+ const options = this._normalizeTextureWriteOptions(options_);
275
+
276
+ const typedArray = ArrayBuffer.isView(data) ? data : new Uint8Array(data);
277
+ const {} = this;
278
+ const {width, height, mipLevel, x, y, z} = options;
279
+ const {glFormat, glType, compressed} = this;
280
+ const depth = 0; // TODO - fix
216
281
  const glTarget = getWebGLCubeFaceTarget(this.glTarget, this.dimension, depth);
217
- const glParameters: GLValueParameters = options.flipY ? {[GL.UNPACK_FLIP_Y_WEBGL]: true} : {};
218
282
 
219
- this.gl.bindTexture(this.glTarget, this.handle);
283
+ // const byteOffset = 0;
284
+ // const {bytesPerRow, rowsPerImage} = this.computeMemoryLayout(options);
285
+
286
+ const glParameters: GLValueParameters = !this.compressed
287
+ ? {
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
292
+ }
293
+ : {};
294
+
295
+ this.gl.bindTexture(glTarget, this.handle);
296
+ this.gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, null);
220
297
 
221
298
  withGLParameters(this.gl, glParameters, () => {
222
299
  switch (this.dimension) {
223
300
  case '2d':
224
301
  case 'cube':
225
- // prettier-ignore
226
- this.gl.texSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, glType, image);
302
+ if (compressed) {
303
+ // prettier-ignore
304
+ this.gl.compressedTexSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, typedArray);
305
+ } else {
306
+ // prettier-ignore
307
+ this.gl.texSubImage2D(glTarget, mipLevel, x, y, width, height, glFormat, glType, typedArray);
308
+ }
227
309
  break;
228
310
  case '2d-array':
229
311
  case '3d':
230
- // prettier-ignore
231
- this.gl.texSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, glType, image);
312
+ if (compressed) {
313
+ // prettier-ignore
314
+ this.gl.compressedTexSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, typedArray);
315
+ } else {
316
+ // prettier-ignore
317
+ this.gl.texSubImage3D(glTarget, mipLevel, x, y, z, width, height, depth, glFormat, glType, typedArray);
318
+ }
232
319
  break;
233
320
  default:
234
321
  // Can never happen in WebGL
235
322
  }
236
323
  });
237
324
 
238
- this.gl.bindTexture(this.glTarget, null);
325
+ this.gl.bindTexture(glTarget, null);
326
+ }
239
327
 
240
- return {width: options.width, height: options.height};
328
+ // IMPLEMENTATION SPECIFIC
329
+
330
+ /** @todo - for now we always use 1 for maximum compatibility, we can fine tune later */
331
+ private _getRowByteAlignment(format: TextureFormat, width: number): 1 | 2 | 4 | 8 {
332
+ // For best texture data read/write performance, calculate the biggest pack/unpack alignment
333
+ // that fits with the provided texture row byte length
334
+ // Note: Any RGBA or 32 bit type will be at least 4 bytes, which should result in good performance.
335
+ // const info = this.device.getTextureFormatInfo(format);
336
+ // const rowByteLength = width * info.bytesPerPixel;
337
+ // if (rowByteLength % 8 === 0) return 8;
338
+ // if (rowByteLength % 4 === 0) return 4;
339
+ // if (rowByteLength % 2 === 0) return 2;
340
+ return 1;
341
+ }
342
+
343
+ /**
344
+ * Wraps a given texture into a framebuffer object, that can be further used
345
+ * to read data from the texture object.
346
+ */
347
+ _getFramebuffer() {
348
+ this._framebuffer ||= this.device.createFramebuffer({
349
+ id: `framebuffer-for-${this.id}`,
350
+ width: this.width,
351
+ height: this.height,
352
+ colorAttachments: [this]
353
+ });
354
+ return this._framebuffer;
241
355
  }
242
356
 
243
357
  // WEBGL SPECIFIC
244
358
 
245
- generateMipmapsWebGL(options?: {force?: boolean}): void {
359
+ override readDataSyncWebGL(options_: TextureReadOptions = {}): ArrayBuffer {
360
+ const options = this._normalizeTextureReadOptions(options_);
361
+
362
+ const memoryLayout = this.computeMemoryLayout(options);
363
+
364
+ // const formatInfo = getTextureFormatInfo(format);
365
+ // Allocate pixel array if not already available, using supplied type
366
+ const shaderType = convertGLDataTypeToDataType(this.glType);
367
+
368
+ 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
373
+ | Uint8Array
374
+ | Uint16Array
375
+ | Float32Array;
376
+
377
+ // Pixel array available, if necessary, deduce type from it.
378
+ const signedType = getDataType(targetArray);
379
+ const sourceType = convertDataTypeToGLDataType(signedType);
380
+
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));
388
+
389
+ 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)
392
+ const prevHandle = this.gl.bindFramebuffer(
393
+ GL.FRAMEBUFFER,
394
+ framebuffer.handle
395
+ ) as unknown as WebGLFramebuffer | null;
396
+
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);
409
+
410
+ return targetArray.buffer as ArrayBuffer;
411
+ }
412
+
413
+ /**
414
+ * @note - this is used by the DynamicTexture class to generate mipmaps on WebGL
415
+ */
416
+ override generateMipmapsWebGL(options?: {force?: boolean}): void {
246
417
  const isFilterableAndRenderable =
247
418
  this.device.isTextureFormatRenderable(this.props.format) &&
248
419
  this.device.isTextureFormatFilterable(this.props.format);
@@ -312,6 +483,7 @@ export class WEBGLTexture extends Texture {
312
483
 
313
484
  this.gl.bindTexture(this.glTarget, null);
314
485
  }
486
+
315
487
  _getActiveUnit(): number {
316
488
  return this.gl.getParameter(GL.ACTIVE_TEXTURE) - GL.TEXTURE0;
317
489
  }
@@ -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');
@@ -78,40 +77,44 @@ export class WebGLAdapter extends Adapter {
78
77
  async create(props: DeviceProps = {}): Promise<WebGLDevice> {
79
78
  const {WebGLDevice} = await import('./webgl-device');
80
79
 
81
- log.groupCollapsed(LOG_LEVEL, 'WebGLDevice created')();
82
- try {
83
- const promises: Promise<unknown>[] = [];
80
+ const promises: Promise<unknown>[] = [];
84
81
 
85
- // Load webgl and spector debug scripts from CDN if requested
86
- if (props.debugWebGL || props.debug) {
87
- promises.push(loadWebGLDeveloperTools());
88
- }
82
+ // Load webgl and spector debug scripts from CDN if requested
83
+ if (props.debugWebGL || props.debug) {
84
+ promises.push(loadWebGLDeveloperTools());
85
+ }
89
86
 
90
- if (props.debugSpectorJS) {
91
- promises.push(loadSpectorJS(props));
92
- }
87
+ if (props.debugSpectorJS) {
88
+ promises.push(loadSpectorJS(props));
89
+ }
93
90
 
94
- // Wait for all the loads to settle before creating the context.
95
- // The Device.create() functions are async, so in contrast to the constructor, we can `await` here.
96
- const results = await Promise.allSettled(promises);
97
- for (const result of results) {
98
- if (result.status === 'rejected') {
99
- log.error(`Failed to initialize debug libraries ${result.reason}`)();
100
- }
91
+ // Wait for all the loads to settle before creating the context.
92
+ // The Device.create() functions are async, so in contrast to the constructor, we can `await` here.
93
+ const results = await Promise.allSettled(promises);
94
+ for (const result of results) {
95
+ if (result.status === 'rejected') {
96
+ log.error(`Failed to initialize debug libraries ${result.reason}`)();
101
97
  }
98
+ }
102
99
 
100
+ try {
103
101
  const device = new WebGLDevice(props);
104
102
 
103
+ log.groupCollapsed(LOG_LEVEL, `WebGLDevice ${device.id} created`)();
105
104
  // Log some debug info about the newly created context
106
105
  const message = `\
107
106
  ${device._reused ? 'Reusing' : 'Created'} device with WebGL2 ${device.props.debug ? 'debug ' : ''}context: \
108
107
  ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContext.id}`;
109
108
  log.probe(LOG_LEVEL, message)();
110
109
  log.table(LOG_LEVEL, device.info)();
111
-
112
110
  return device;
113
111
  } finally {
114
112
  log.groupEnd(LOG_LEVEL)();
113
+ log.info(
114
+ LOG_LEVEL,
115
+ `%cWebGL call tracing: luma.log.set('debug-webgl') `,
116
+ 'color: white; background: blue; padding: 2px 6px; border-radius: 3px;'
117
+ )();
115
118
  }
116
119
  }
117
120
  }
@@ -121,8 +124,7 @@ function isWebGL(gl: any): gl is WebGL2RenderingContext {
121
124
  if (typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext) {
122
125
  return true;
123
126
  }
124
- // Look for debug contexts, headless gl etc
125
- return Boolean(gl && Number.isFinite(gl._version));
127
+ return Boolean(gl && typeof gl.createVertexArray === 'function');
126
128
  }
127
129
 
128
130
  export const webgl2Adapter = new WebGLAdapter();
@@ -27,16 +27,27 @@ export class WebGLCanvasContext extends CanvasContext {
27
27
 
28
28
  // Base class constructor cannot access derived methods/fields, so we need to call these functions in the subclass constructor
29
29
  this._setAutoCreatedCanvasId(`${this.device.id}-canvas`);
30
- this._updateDevice();
31
- }
32
-
33
- getCurrentFramebuffer(): WEBGLFramebuffer {
34
- // Setting handle to null returns a reference to the default framebuffer
35
- this._framebuffer = this._framebuffer || new WEBGLFramebuffer(this.device, {handle: null});
36
- return this._framebuffer;
30
+ this._configureDevice();
37
31
  }
38
32
 
39
33
  // IMPLEMENTATION OF ABSTRACT METHODS
40
34
 
41
- _updateDevice(): void {}
35
+ _configureDevice(): void {
36
+ const shouldResize =
37
+ this.drawingBufferWidth !== this._framebuffer?.width ||
38
+ this.drawingBufferHeight !== this._framebuffer?.height;
39
+ if (shouldResize) {
40
+ this._framebuffer?.resize([this.drawingBufferWidth, this.drawingBufferHeight]);
41
+ }
42
+ }
43
+
44
+ _getCurrentFramebuffer(): WEBGLFramebuffer {
45
+ this._framebuffer ||= new WEBGLFramebuffer(this.device, {
46
+ id: 'canvas-context-framebuffer',
47
+ handle: null, // Setting handle to null returns a reference to the default WebGL framebuffer
48
+ width: this.drawingBufferWidth,
49
+ height: this.drawingBufferHeight
50
+ });
51
+ return this._framebuffer;
52
+ }
42
53
  }
@@ -36,6 +36,7 @@ import {Device, CanvasContext, log} from '@luma.gl/core';
36
36
  import type {GLExtensions} from '@luma.gl/constants';
37
37
  import {WebGLStateTracker} from '../context/state-tracker/webgl-state-tracker';
38
38
  import {createBrowserContext} from '../context/helpers/create-browser-context';
39
+ import {getWebGLContextData} from '../context/helpers/webgl-context-data';
39
40
  import {getDeviceInfo} from './device-helpers/webgl-device-info';
40
41
  import {WebGLDeviceFeatures} from './device-helpers/webgl-device-features';
41
42
  import {WebGLDeviceLimits} from './device-helpers/webgl-device-limits';
@@ -57,6 +58,7 @@ import {WEBGLCommandBuffer} from './resources/webgl-command-buffer';
57
58
  import {WEBGLVertexArray} from './resources/webgl-vertex-array';
58
59
  import {WEBGLTransformFeedback} from './resources/webgl-transform-feedback';
59
60
  import {WEBGLQuerySet} from './resources/webgl-query-set';
61
+ import {WEBGLFence} from './resources/webgl-fence';
60
62
 
61
63
  import {readPixelsToArray, readPixelsToBuffer} from './helpers/webgl-texture-utils';
62
64
  import {
@@ -69,6 +71,13 @@ import {getWebGLExtension} from '../context/helpers/webgl-extensions';
69
71
 
70
72
  /** WebGPU style Device API for a WebGL context */
71
73
  export class WebGLDevice extends Device {
74
+ static getDeviceFromContext(gl: WebGL2RenderingContext | null): WebGLDevice | null {
75
+ if (!gl) {
76
+ return null;
77
+ }
78
+ // @ts-expect-error Ingore WebGL2RenderingContext type
79
+ return gl.luma?.device ?? null;
80
+ }
72
81
  // Public `Device` API
73
82
 
74
83
  /** type of this device */
@@ -99,7 +108,7 @@ export class WebGLDevice extends Device {
99
108
  _constants: (TypedArray | null)[];
100
109
 
101
110
  /** State used by luma.gl classes - TODO - not used? */
102
- readonly _extensions: GLExtensions = {};
111
+ readonly extensions!: GLExtensions;
103
112
  _polyfilled: boolean = false;
104
113
 
105
114
  /** Instance of Spector.js (if initialized) */
@@ -140,7 +149,8 @@ export class WebGLDevice extends Device {
140
149
  // Note that this can be avoided in webgl2adapter.create() if
141
150
  // DeviceProps._reuseDevices is set.
142
151
  // @ts-expect-error device is attached to context
143
- let device: WebGLDevice | undefined = canvasContextProps.canvas?.gl?.device;
152
+ const existingContext = canvasContextProps.canvas?.gl ?? null;
153
+ let device: WebGLDevice | null = WebGLDevice.getDeviceFromContext(existingContext);
144
154
  if (device) {
145
155
  throw new Error(`WebGL context already attached to device ${device.id}`);
146
156
  }
@@ -160,6 +170,9 @@ export class WebGLDevice extends Device {
160
170
  if (props.powerPreference !== undefined) {
161
171
  webglContextAttributes.powerPreference = props.powerPreference;
162
172
  }
173
+ if (props.failIfMajorPerformanceCaveat !== undefined) {
174
+ webglContextAttributes.failIfMajorPerformanceCaveat = props.failIfMajorPerformanceCaveat;
175
+ }
163
176
 
164
177
  // Check if we should attach to an externally created context or create a new context
165
178
  const externalGLContext = this.props._handle as WebGL2RenderingContext | null;
@@ -186,8 +199,7 @@ export class WebGLDevice extends Device {
186
199
 
187
200
  // Note that the browser will only create one WebGL context per canvas.
188
201
  // This means that a newly created gl context may already have a device attached to it.
189
- // @ts-expect-error luma.gl stores a device reference on the context.
190
- device = gl.device;
202
+ device = WebGLDevice.getDeviceFromContext(gl);
191
203
  if (device) {
192
204
  if (props._reuseDevices) {
193
205
  log.log(
@@ -195,6 +207,9 @@ export class WebGLDevice extends Device {
195
207
  `Not creating a new Device, instead returning a reference to Device ${device.id} already attached to WebGL context`,
196
208
  device
197
209
  )();
210
+ // Destroy the orphaned canvas context that was created above (line 149)
211
+ // to prevent its ResizeObserver from firing callbacks with undefined device
212
+ this.canvasContext.destroy();
198
213
  device._reused = true;
199
214
  return device;
200
215
  }
@@ -210,18 +225,15 @@ export class WebGLDevice extends Device {
210
225
  this.spectorJS = initializeSpectorJS({...this.props, gl: this.handle});
211
226
 
212
227
  // Instrument context
213
- (this.gl as any).device = this; // Update GL context: Link webgl context back to device
214
- // TODO - remove, this is only used to detect debug contexts.
215
- (this.gl as any)._version = 2; // Update GL context: Store WebGL version field on gl context (HACK to identify debug contexts)
228
+ const contextData = getWebGLContextData(this.handle);
229
+ contextData.device = this; // Update GL context: Link webgl context back to device
230
+
231
+ this.extensions = contextData.extensions || (contextData.extensions = {});
216
232
 
217
233
  // initialize luma Device fields
218
- this.info = getDeviceInfo(this.gl, this._extensions);
234
+ this.info = getDeviceInfo(this.gl, this.extensions);
219
235
  this.limits = new WebGLDeviceLimits(this.gl);
220
- this.features = new WebGLDeviceFeatures(
221
- this.gl,
222
- this._extensions,
223
- this.props._disabledFeatures
224
- );
236
+ this.features = new WebGLDeviceFeatures(this.gl, this.extensions, this.props._disabledFeatures);
225
237
  if (this.props._initializeFeatures) {
226
238
  this.features.initializeFeatures();
227
239
  }
@@ -232,15 +244,14 @@ export class WebGLDevice extends Device {
232
244
  });
233
245
  glState.trackState(this.gl, {copyState: false});
234
246
 
235
- // DEBUG contexts: Add luma debug instrumentation to the context, force log level to at least 1
236
- const debugWebGL = props.debugWebGL || props.debug;
237
- const traceWebGL = props.debugWebGL;
238
- if (debugWebGL) {
239
- this.gl = makeDebugContext(this.gl, {debugWebGL, traceWebGL});
247
+ // props.debug - instrument the WebGL context with Khronos debug tools
248
+ // props.debugWebGL - activate WebGL context tracing, force log level to at least 1
249
+ if (props.debug || props.debugWebGL) {
250
+ this.gl = makeDebugContext(this.gl, {debugWebGL: true, traceWebGL: props.debugWebGL});
240
251
  log.warn('WebGL debug mode activated. Performance reduced.')();
241
- if (props.debugWebGL) {
242
- log.level = Math.max(log.level, 1);
243
- }
252
+ }
253
+ if (props.debugWebGL) {
254
+ log.level = Math.max(log.level, 1);
244
255
  }
245
256
 
246
257
  this.commandEncoder = new WEBGLCommandEncoder(this, {id: `${this}-command-encoder`});
@@ -264,7 +275,8 @@ export class WebGLDevice extends Device {
264
275
  // Therefore we must do nothing in destroy() if props._reuseDevices is true
265
276
  if (!this.props._reuseDevices && !this._reused) {
266
277
  // Delete the reference to the device that we store on the WebGL context
267
- delete (this.gl as any).device;
278
+ const contextData = getWebGLContextData(this.handle);
279
+ contextData.device = null;
268
280
  }
269
281
  }
270
282
 
@@ -274,10 +286,6 @@ export class WebGLDevice extends Device {
274
286
 
275
287
  // IMPLEMENTATION OF ABSTRACT DEVICE
276
288
 
277
- getTextureByteAlignment(): number {
278
- return 4;
279
- }
280
-
281
289
  createCanvasContext(props?: CanvasContextProps): CanvasContext {
282
290
  throw new Error('WebGL only supports a single canvas');
283
291
  }
@@ -319,6 +327,10 @@ export class WebGLDevice extends Device {
319
327
  return new WEBGLQuerySet(this, props);
320
328
  }
321
329
 
330
+ override createFence(): WEBGLFence {
331
+ return new WEBGLFence(this);
332
+ }
333
+
322
334
  createRenderPipeline(props: RenderPipelineProps): WEBGLRenderPipeline {
323
335
  return new WEBGLRenderPipeline(this, props);
324
336
  }
@@ -406,7 +418,7 @@ export class WebGLDevice extends Device {
406
418
  override _getDeviceSpecificTextureFormatCapabilities(
407
419
  capabilities: DeviceTextureFormatCapabilities
408
420
  ): DeviceTextureFormatCapabilities {
409
- return getTextureFormatCapabilitiesWebGL(this.gl, capabilities, this._extensions);
421
+ return getTextureFormatCapabilitiesWebGL(this.gl, capabilities, this.extensions);
410
422
  }
411
423
 
412
424
  //
@@ -509,8 +521,8 @@ export class WebGLDevice extends Device {
509
521
 
510
522
  /** Ensure extensions are only requested once */
511
523
  getExtension(name: keyof GLExtensions): GLExtensions {
512
- getWebGLExtension(this.gl, name, this._extensions);
513
- return this._extensions;
524
+ getWebGLExtension(this.gl, name, this.extensions);
525
+ return this.extensions;
514
526
  }
515
527
 
516
528
  // INTERNAL SUPPORT METHODS FOR WEBGL RESOURCES