@luma.gl/webgl 9.1.0-alpha.1 → 9.1.0-alpha.12

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 (109) hide show
  1. package/dist/adapter/converters/device-parameters.d.ts.map +1 -1
  2. package/dist/adapter/converters/device-parameters.js +18 -11
  3. package/dist/adapter/converters/texture-formats.d.ts +1 -1
  4. package/dist/adapter/converters/texture-formats.d.ts.map +1 -1
  5. package/dist/adapter/converters/texture-formats.js +9 -16
  6. package/dist/adapter/device-helpers/webgl-device-features.d.ts.map +1 -1
  7. package/dist/adapter/device-helpers/webgl-device-features.js +1 -3
  8. package/dist/adapter/helpers/format-utils.d.ts.map +1 -0
  9. package/dist/adapter/helpers/get-shader-layout.d.ts.map +1 -1
  10. package/dist/adapter/helpers/get-shader-layout.js +1 -3
  11. package/dist/adapter/helpers/typed-array-utils.d.ts.map +1 -0
  12. package/dist/adapter/helpers/webgl-texture-utils.d.ts +89 -22
  13. package/dist/adapter/helpers/webgl-texture-utils.d.ts.map +1 -1
  14. package/dist/adapter/helpers/webgl-texture-utils.js +220 -26
  15. package/dist/adapter/resources/webgl-framebuffer.js +1 -1
  16. package/dist/adapter/resources/webgl-render-pass.d.ts.map +1 -1
  17. package/dist/adapter/resources/webgl-render-pass.js +17 -4
  18. package/dist/adapter/resources/webgl-render-pipeline.d.ts +1 -3
  19. package/dist/adapter/resources/webgl-render-pipeline.d.ts.map +1 -1
  20. package/dist/adapter/resources/webgl-render-pipeline.js +1 -1
  21. package/dist/adapter/resources/webgl-shader.js +1 -1
  22. package/dist/adapter/resources/webgl-texture.d.ts +21 -3
  23. package/dist/adapter/resources/webgl-texture.d.ts.map +1 -1
  24. package/dist/adapter/resources/webgl-texture.js +49 -30
  25. package/dist/adapter/resources/webgl-transform-feedback.js +1 -1
  26. package/dist/adapter/resources/webgl-vertex-array.d.ts.map +1 -1
  27. package/dist/adapter/resources/webgl-vertex-array.js +3 -0
  28. package/dist/adapter/webgl-adapter.d.ts +21 -0
  29. package/dist/adapter/webgl-adapter.d.ts.map +1 -0
  30. package/dist/adapter/webgl-adapter.js +91 -0
  31. package/dist/adapter/webgl-device.d.ts +16 -29
  32. package/dist/adapter/webgl-device.d.ts.map +1 -1
  33. package/dist/adapter/webgl-device.js +34 -114
  34. package/dist/context/debug/spector-types.d.ts +1108 -0
  35. package/dist/context/debug/spector-types.d.ts.map +1 -0
  36. package/dist/context/debug/spector-types.js +697 -0
  37. package/dist/context/debug/spector.d.ts +12 -8
  38. package/dist/context/debug/spector.d.ts.map +1 -1
  39. package/dist/context/debug/spector.js +23 -17
  40. package/dist/context/polyfills/polyfill-webgl1-extensions.d.ts +9 -0
  41. package/dist/context/polyfills/polyfill-webgl1-extensions.d.ts.map +1 -0
  42. package/dist/context/polyfills/polyfill-webgl1-extensions.js +181 -0
  43. package/dist/context/state-tracker/webgl-state-tracker.d.ts +43 -0
  44. package/dist/context/state-tracker/webgl-state-tracker.d.ts.map +1 -0
  45. package/dist/context/state-tracker/{track-context-state.js → webgl-state-tracker.js} +44 -74
  46. package/dist/context/state-tracker/with-parameters.d.ts.map +1 -1
  47. package/dist/context/state-tracker/with-parameters.js +5 -4
  48. package/dist/deprecated/accessor.d.ts.map +1 -0
  49. package/dist/{classic → deprecated}/accessor.js +36 -1
  50. package/dist/deprecated/clear.d.ts.map +1 -0
  51. package/dist/{classic → deprecated}/clear.js +2 -0
  52. package/dist/dist.dev.js +816 -642
  53. package/dist/dist.min.js +2 -2
  54. package/dist/index.cjs +815 -648
  55. package/dist/index.cjs.map +4 -4
  56. package/dist/index.d.ts +4 -2
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +4 -3
  59. package/dist/utils/fill-array.d.ts +4 -4
  60. package/dist/utils/fill-array.d.ts.map +1 -1
  61. package/dist/utils/split-uniforms-and-bindings.d.ts +1 -1
  62. package/dist/utils/split-uniforms-and-bindings.d.ts.map +1 -1
  63. package/dist/utils/uid.d.ts +7 -0
  64. package/dist/utils/uid.d.ts.map +1 -0
  65. package/dist/utils/uid.js +14 -0
  66. package/package.json +5 -5
  67. package/src/adapter/converters/device-parameters.ts +18 -12
  68. package/src/adapter/converters/texture-formats.ts +12 -20
  69. package/src/adapter/device-helpers/webgl-device-features.ts +5 -3
  70. package/src/adapter/helpers/get-shader-layout.ts +1 -3
  71. package/src/adapter/helpers/webgl-texture-utils.ts +366 -44
  72. package/src/adapter/resources/webgl-framebuffer.ts +1 -1
  73. package/src/adapter/resources/webgl-render-pass.ts +20 -7
  74. package/src/adapter/resources/webgl-render-pipeline.ts +12 -4
  75. package/src/adapter/resources/webgl-shader.ts +1 -1
  76. package/src/adapter/resources/webgl-texture.ts +76 -30
  77. package/src/adapter/resources/webgl-transform-feedback.ts +1 -1
  78. package/src/adapter/resources/webgl-vertex-array.ts +3 -0
  79. package/src/adapter/webgl-adapter.ts +113 -0
  80. package/src/adapter/webgl-device.ts +45 -139
  81. package/src/context/debug/spector-types.ts +1154 -0
  82. package/src/context/debug/spector.ts +38 -29
  83. package/src/context/polyfills/polyfill-webgl1-extensions.ts +202 -0
  84. package/src/context/state-tracker/{track-context-state.ts → webgl-state-tracker.ts} +55 -94
  85. package/src/context/state-tracker/with-parameters.ts +5 -4
  86. package/src/{classic → deprecated}/accessor.ts +44 -3
  87. package/src/{classic → deprecated}/clear.ts +3 -1
  88. package/src/index.ts +6 -8
  89. package/src/utils/fill-array.ts +4 -4
  90. package/src/utils/split-uniforms-and-bindings.ts +3 -3
  91. package/src/utils/uid.ts +16 -0
  92. package/dist/classic/accessor.d.ts.map +0 -1
  93. package/dist/classic/clear.d.ts.map +0 -1
  94. package/dist/classic/copy-and-blit.d.ts +0 -63
  95. package/dist/classic/copy-and-blit.d.ts.map +0 -1
  96. package/dist/classic/copy-and-blit.js +0 -193
  97. package/dist/classic/format-utils.d.ts.map +0 -1
  98. package/dist/classic/typed-array-utils.d.ts.map +0 -1
  99. package/dist/context/state-tracker/track-context-state.d.ts +0 -22
  100. package/dist/context/state-tracker/track-context-state.d.ts.map +0 -1
  101. package/src/classic/copy-and-blit.ts +0 -318
  102. /package/dist/{classic → adapter/helpers}/format-utils.d.ts +0 -0
  103. /package/dist/{classic → adapter/helpers}/format-utils.js +0 -0
  104. /package/dist/{classic → adapter/helpers}/typed-array-utils.d.ts +0 -0
  105. /package/dist/{classic → adapter/helpers}/typed-array-utils.js +0 -0
  106. /package/dist/{classic → deprecated}/accessor.d.ts +0 -0
  107. /package/dist/{classic → deprecated}/clear.d.ts +0 -0
  108. /package/src/{classic → adapter/helpers}/format-utils.ts +0 -0
  109. /package/src/{classic → adapter/helpers}/typed-array-utils.ts +0 -0
@@ -52,7 +52,7 @@ import {WEBGLTextureView} from './webgl-texture-view';
52
52
  import {
53
53
  initializeTextureStorage,
54
54
  // clearMipLevel,
55
- copyCPUImageToMipLevel,
55
+ copyExternalImageToMipLevel,
56
56
  copyCPUDataToMipLevel,
57
57
  // copyGPUBufferToMipLevel,
58
58
  getWebGLTextureTarget
@@ -192,7 +192,7 @@ export class WEBGLTexture extends Texture {
192
192
  let {width, height} = props;
193
193
 
194
194
  if (!width || !height) {
195
- const textureSize = this.getTextureDataSize(data);
195
+ const textureSize = Texture.getTextureDataSize(data);
196
196
  width = textureSize?.width || 1;
197
197
  height = textureSize?.height || 1;
198
198
  }
@@ -333,13 +333,64 @@ export class WEBGLTexture extends Texture {
333
333
  }
334
334
 
335
335
  // Image Data Setters
336
+ copyExternalImage(options: {
337
+ image: ExternalImage;
338
+ sourceX?: number;
339
+ sourceY?: number;
340
+ width?: number;
341
+ height?: number;
342
+ depth?: number;
343
+ mipLevel?: number;
344
+ x?: number;
345
+ y?: number;
346
+ z?: number;
347
+ aspect?: 'all' | 'stencil-only' | 'depth-only';
348
+ colorSpace?: 'srgb';
349
+ premultipliedAlpha?: boolean;
350
+ }): {width: number; height: number} {
351
+ const size = Texture.getExternalImageSize(options.image);
352
+ const opts = {...Texture.defaultCopyExternalImageOptions, ...size, ...options};
353
+
354
+ const {image, depth, mipLevel, x, y, z} = opts;
355
+ let {width, height} = opts;
356
+ const {dimension, glTarget, glFormat, glInternalFormat, glType} = this;
357
+
358
+ // WebGL will error if we try to copy outside the bounds of the texture
359
+ width = Math.min(width, size.width - x);
360
+ height = Math.min(height, size.height - y);
361
+
362
+ // WebGL does not yet support sourceX/sourceY in copyExternalImage; requires copyTexSubImage2D from a framebuffer'
363
+
364
+ if (options.sourceX || options.sourceY) {
365
+ throw new Error(
366
+ 'WebGL does not yet support sourceX/sourceY in copyExternalImage; requires copyTexSubImage2D from a framebuffer'
367
+ );
368
+ }
369
+
370
+ copyExternalImageToMipLevel(this.device.gl, this.handle, image, {
371
+ dimension,
372
+ mipLevel,
373
+ x,
374
+ y,
375
+ z,
376
+ width,
377
+ height,
378
+ depth,
379
+ glFormat,
380
+ glInternalFormat,
381
+ glType,
382
+ glTarget
383
+ });
384
+
385
+ return {width: opts.width, height: opts.height};
386
+ }
336
387
 
337
388
  setTexture1DData(data: Texture1DData): void {
338
389
  throw new Error('setTexture1DData not supported in WebGL.');
339
390
  }
340
391
 
341
392
  /** Set a simple texture */
342
- setTexture2DData(lodData: Texture2DData, depth = 0, glTarget = this.glTarget): void {
393
+ setTexture2DData(lodData: Texture2DData, depth = 0): void {
343
394
  this.bind();
344
395
 
345
396
  const lodArray = normalizeTextureData(lodData, this);
@@ -367,7 +418,9 @@ export class WEBGLTexture extends Texture {
367
418
  throw new Error(this.id);
368
419
  }
369
420
  if (ArrayBuffer.isView(data)) {
421
+ this.bind();
370
422
  copyCPUDataToMipLevel(this.device.gl, data, this);
423
+ this.unbind();
371
424
  }
372
425
  }
373
426
 
@@ -381,9 +434,9 @@ export class WEBGLTexture extends Texture {
381
434
  if (this.props.dimension !== 'cube') {
382
435
  throw new Error(this.id);
383
436
  }
384
- // for (const face of Texture.CubeFaces) {
385
- // // this.setTextureCubeFaceData(face, data[face]);
386
- // }
437
+ for (const face of Texture.CubeFaces) {
438
+ this.setTextureCubeFaceData(data[face], face);
439
+ }
387
440
  }
388
441
 
389
442
  /**
@@ -414,27 +467,9 @@ export class WEBGLTexture extends Texture {
414
467
  log.warn(`${this.id} has mipmap and multiple LODs.`)();
415
468
  }
416
469
 
417
- // const glFace = GL.TEXTURE_CUBE_MAP_POSITIVE_X + Texture.CubeFaces.indexOf(face);
418
- // const glType = GL.UNSIGNED_BYTE;
419
- // const {width, height, format = GL.RGBA, type = GL.UNSIGNED_BYTE} = this;
420
- // const {width, height, format = GL.RGBA, type = GL.UNSIGNED_BYTE} = this;
470
+ const faceDepth = Texture.CubeFaces.indexOf(face);
421
471
 
422
- this.bind();
423
- // for (let lodLevel = 0; lodLevel < lodData.length; lodLevel++) {
424
- // const imageData = lodData[lodLevel];
425
- // if (imageData instanceof ArrayBuffer) {
426
- // // const imageData = image instanceof ArrayBuffer ? new ImageData(new Uint8ClampedArray(image), this.width) : image;
427
- // this.device.gl.texImage2D?.(
428
- // glFace,
429
- // lodLevel,
430
- // this.glInternalFormat,
431
- // this.glInternalFormat,
432
- // glType,
433
- // imageData
434
- // );
435
- // }
436
- // }
437
- this.unbind();
472
+ this.setTexture2DData(lodData, faceDepth);
438
473
  }
439
474
 
440
475
  // INTERNAL METHODS
@@ -592,23 +627,34 @@ export class WEBGLTexture extends Texture {
592
627
  * Copy a region of data from a CPU memory buffer into this texture.
593
628
  * @todo - GLUnpackParameters parameters
594
629
  */
595
- protected _setMipLevel(depth: number, level: number, textureData: Texture2DData, offset = 0) {
630
+ protected _setMipLevel(
631
+ depth: number,
632
+ mipLevel: number,
633
+ textureData: Texture2DData,
634
+ glTarget: GL = this.glTarget
635
+ ) {
596
636
  // if (!textureData) {
597
637
  // clearMipLevel(this.device.gl, {...this, depth, level});
598
638
  // return;
599
639
  // }
600
640
 
601
641
  if (Texture.isExternalImage(textureData)) {
602
- copyCPUImageToMipLevel(this.device.gl, textureData, {...this, depth, level});
642
+ copyExternalImageToMipLevel(this.device.gl, this.handle, textureData, {
643
+ ...this,
644
+ depth,
645
+ mipLevel,
646
+ glTarget
647
+ });
603
648
  return;
604
649
  }
605
650
 
606
651
  // @ts-expect-error
607
- if (this.isTextureLevelData(textureData)) {
652
+ if (Texture.isTextureLevelData(textureData)) {
608
653
  copyCPUDataToMipLevel(this.device.gl, textureData.data, {
609
654
  ...this,
610
655
  depth,
611
- level
656
+ mipLevel,
657
+ glTarget
612
658
  });
613
659
  return;
614
660
  }
@@ -56,7 +56,7 @@ export class WEBGLTransformFeedback extends TransformFeedback {
56
56
 
57
57
  end(): void {
58
58
  this.gl.endTransformFeedback();
59
- if (!this.bindOnUse) {
59
+ if (this.bindOnUse) {
60
60
  this._unbindBuffers();
61
61
  }
62
62
  this.gl.bindTransformFeedback(GL.TRANSFORM_FEEDBACK, null);
@@ -97,6 +97,9 @@ export class WEBGLVertexArray extends VertexArray {
97
97
  // Attaches ARRAY_BUFFER with specified buffer format to location
98
98
  this.device.gl.vertexAttribPointer(location, size, type, normalized, stride, offset);
99
99
  }
100
+ // Clear binding - keeping it may cause [.WebGL-0x12804417100]
101
+ // GL_INVALID_OPERATION: A transform feedback buffer that would be written to is also bound to a non-transform-feedback target
102
+ this.device.gl.bindBuffer(GL.ARRAY_BUFFER, null);
100
103
 
101
104
  // Mark as non-constant
102
105
  this.device.gl.enableVertexAttribArray(location);
@@ -0,0 +1,113 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Adapter, Device, DeviceProps, CanvasContext, log} from '@luma.gl/core';
6
+ import {WebGLDevice} from './webgl-device';
7
+ import {enforceWebGL2} from '../context/polyfills/polyfill-webgl1-extensions';
8
+ import {loadSpectorJS, DEFAULT_SPECTOR_PROPS} from '../context/debug/spector';
9
+ import {loadWebGLDeveloperTools} from '../context/debug/webgl-developer-tools';
10
+
11
+ const LOG_LEVEL = 1;
12
+
13
+ export class WebGLAdapter extends Adapter {
14
+ /** type of device's created by this adapter */
15
+ readonly type: Device['type'] = 'webgl';
16
+
17
+ constructor() {
18
+ super();
19
+
20
+ // Add spector default props to device default props, so that runtime settings are observed
21
+ Device.defaultProps = {...Device.defaultProps, ...DEFAULT_SPECTOR_PROPS};
22
+
23
+ // @ts-ignore DEPRECATED For backwards compatibility luma.registerDevices
24
+ WebGLDevice.adapter = this;
25
+ }
26
+
27
+ /** Check if WebGL 2 is available */
28
+ isSupported(): boolean {
29
+ return typeof WebGL2RenderingContext !== 'undefined';
30
+ }
31
+
32
+ /** Force any created WebGL contexts to be WebGL2 contexts, polyfilled with WebGL1 extensions */
33
+ enforceWebGL2(enable: boolean): void {
34
+ enforceWebGL2(enable);
35
+ }
36
+
37
+ /**
38
+ * Get a device instance from a GL context
39
+ * Creates and instruments the device if not already created
40
+ * @param gl
41
+ * @returns
42
+ */
43
+ async attach(gl: Device | WebGL2RenderingContext): Promise<WebGLDevice> {
44
+ if (gl instanceof WebGLDevice) {
45
+ return gl;
46
+ }
47
+ // @ts-expect-error
48
+ if (gl?.device instanceof Device) {
49
+ // @ts-expect-error
50
+ return gl.device as WebGLDevice;
51
+ }
52
+ if (!isWebGL(gl)) {
53
+ throw new Error('Invalid WebGL2RenderingContext');
54
+ }
55
+ return new WebGLDevice({gl: gl as WebGL2RenderingContext});
56
+ }
57
+
58
+ async create(props: DeviceProps = {}): Promise<WebGLDevice> {
59
+ log.groupCollapsed(LOG_LEVEL, 'WebGLDevice created')();
60
+
61
+ const promises: Promise<unknown>[] = [];
62
+
63
+ // Load webgl and spector debug scripts from CDN if requested
64
+ if (props.debug) {
65
+ promises.push(loadWebGLDeveloperTools());
66
+ }
67
+
68
+ if (props.debugWithSpectorJS) {
69
+ promises.push(loadSpectorJS(props));
70
+ }
71
+
72
+ // Wait for page to load: if canvas is a string we need to query the DOM for the canvas element.
73
+ // We only wait when props.canvas is string to avoids setting the global page onload callback unless necessary.
74
+ if (typeof props.canvas === 'string') {
75
+ promises.push(CanvasContext.pageLoaded);
76
+ }
77
+
78
+ // Wait for all the loads to settle before creating the context.
79
+ // The Device.create() functions are async, so in contrast to the constructor, we can `await` here.
80
+ const results = await Promise.allSettled(promises);
81
+ for (const result of results) {
82
+ if (result.status === 'rejected') {
83
+ log.error(`Failed to initialize debug libraries ${result.reason}`)();
84
+ }
85
+ }
86
+
87
+ log.probe(LOG_LEVEL + 1, 'DOM is loaded')();
88
+
89
+ const device = new WebGLDevice(props);
90
+
91
+ // Log some debug info about the newly created context
92
+ const message = `\
93
+ Created ${device.type}${device.debug ? ' debug' : ''} context: \
94
+ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContext.id}`;
95
+ log.probe(LOG_LEVEL, message)();
96
+ log.table(LOG_LEVEL, device.info)();
97
+
98
+ log.groupEnd(LOG_LEVEL)();
99
+
100
+ return device;
101
+ }
102
+ }
103
+
104
+ /** Check if supplied parameter is a WebGL2RenderingContext */
105
+ function isWebGL(gl: any): boolean {
106
+ if (typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext) {
107
+ return true;
108
+ }
109
+ // Look for debug contexts, headless gl etc
110
+ return Boolean(gl && Number.isFinite(gl._version));
111
+ }
112
+
113
+ export const webgl2Adapter = new WebGLAdapter();
@@ -3,27 +3,34 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import type {TypedArray} from '@math.gl/types';
6
- import type {DeviceProps, DeviceInfo, CanvasContextProps, TextureFormat} from '@luma.gl/core';
7
- import type {Buffer, Texture, Framebuffer, VertexArray, VertexArrayProps} from '@luma.gl/core';
6
+ import type {
7
+ DeviceProps,
8
+ DeviceInfo,
9
+ CanvasContextProps,
10
+ TextureFormat,
11
+ Buffer,
12
+ Texture,
13
+ Framebuffer,
14
+ VertexArray,
15
+ VertexArrayProps
16
+ } from '@luma.gl/core';
8
17
  import {Device, CanvasContext, log} from '@luma.gl/core';
9
18
  import type {GLExtensions} from '@luma.gl/constants';
10
- import {
11
- popContextState,
12
- pushContextState,
13
- trackContextState
14
- } from '../context/state-tracker/track-context-state';
19
+ import {WebGLStateTracker} from '../context/state-tracker/webgl-state-tracker';
15
20
  import {createBrowserContext} from '../context/helpers/create-browser-context';
16
21
  import {getDeviceInfo} from './device-helpers/webgl-device-info';
17
22
  import {WebGLDeviceFeatures} from './device-helpers/webgl-device-features';
18
23
  import {WebGLDeviceLimits} from './device-helpers/webgl-device-limits';
19
24
  import {WebGLCanvasContext} from './webgl-canvas-context';
20
- import {loadSpectorJS, initializeSpectorJS} from '../context/debug/spector';
21
- import {loadWebGLDeveloperTools, makeDebugContext} from '../context/debug/webgl-developer-tools';
25
+ import type {Spector} from '../context/debug/spector-types';
26
+ import {initializeSpectorJS} from '../context/debug/spector';
27
+ import {makeDebugContext} from '../context/debug/webgl-developer-tools';
22
28
  import {
23
29
  isTextureFormatSupported,
24
30
  isTextureFormatRenderable,
25
31
  isTextureFormatFilterable
26
32
  } from './converters/texture-formats';
33
+ import {uid} from '../utils/uid';
27
34
 
28
35
  // WebGL classes
29
36
  import type {
@@ -61,27 +68,22 @@ import {WEBGLVertexArray} from './resources/webgl-vertex-array';
61
68
  import {WEBGLTransformFeedback} from './resources/webgl-transform-feedback';
62
69
  import {WEBGLQuerySet} from './resources/webgl-query-set';
63
70
 
64
- import {readPixelsToArray, readPixelsToBuffer} from '../classic/copy-and-blit';
71
+ import {readPixelsToArray, readPixelsToBuffer} from './helpers/webgl-texture-utils';
65
72
  import {
66
73
  setGLParameters,
67
74
  getGLParameters,
68
75
  resetGLParameters
69
76
  } from '../context/parameters/unified-parameter-api';
70
77
  import {withGLParameters} from '../context/state-tracker/with-parameters';
71
- import {clear} from '../classic/clear';
78
+ import {clear} from '../deprecated/clear';
72
79
  import {getWebGLExtension} from '../context/helpers/webgl-extensions';
73
80
 
74
- const LOG_LEVEL = 1;
75
-
76
81
  /** WebGPU style Device API for a WebGL context */
77
82
  export class WebGLDevice extends Device {
78
83
  //
79
84
  // Public `Device` API
80
85
  //
81
86
 
82
- /** type of this device */
83
- static readonly type: string = 'webgl';
84
-
85
87
  /** type of this device */
86
88
  readonly type = 'webgl';
87
89
 
@@ -97,93 +99,26 @@ export class WebGLDevice extends Device {
97
99
 
98
100
  private _resolveContextLost?: (value: {reason: 'destroyed'; message: string}) => void;
99
101
 
100
- //
101
- // Static methods, expected to be present by `luma.createDevice()`
102
- //
103
-
104
- /** Check if WebGL 2 is available */
105
- static isSupported(): boolean {
106
- return typeof WebGL2RenderingContext !== 'undefined';
107
- }
108
-
109
- /**
110
- * Get a device instance from a GL context
111
- * Creates and instruments the device if not already created
112
- * @param gl
113
- * @returns
114
- */
115
- static attach(gl: Device | WebGL2RenderingContext): WebGLDevice {
116
- if (gl instanceof WebGLDevice) {
117
- return gl;
118
- }
119
- // @ts-expect-error
120
- if (gl?.device instanceof Device) {
121
- // @ts-expect-error
122
- return gl.device as WebGLDevice;
123
- }
124
- if (!isWebGL(gl)) {
125
- throw new Error('Invalid WebGL2RenderingContext');
126
- }
127
- return new WebGLDevice({gl: gl as WebGL2RenderingContext});
128
- }
129
-
130
- static async create(props: DeviceProps = {}): Promise<WebGLDevice> {
131
- log.groupCollapsed(LOG_LEVEL, 'WebGLDevice created')();
132
-
133
- const promises: Promise<unknown>[] = [];
134
-
135
- // Load webgl and spector debug scripts from CDN if requested
136
- if (props.debug) {
137
- promises.push(loadWebGLDeveloperTools());
138
- }
139
-
140
- if (props.spector) {
141
- promises.push(loadSpectorJS());
142
- }
143
-
144
- // Wait for page to load: if canvas is a string we need to query the DOM for the canvas element.
145
- // We only wait when props.canvas is string to avoids setting the global page onload callback unless necessary.
146
- if (typeof props.canvas === 'string') {
147
- promises.push(CanvasContext.pageLoaded);
148
- }
149
-
150
- // Wait for all the loads to settle before creating the context.
151
- // The Device.create() functions are async, so in contrast to the constructor, we can `await` here.
152
- const results = await Promise.allSettled(promises);
153
- for (const result of results) {
154
- if (result.status === 'rejected') {
155
- log.error(`Failed to initialize debug libraries ${result.reason}`)();
156
- }
157
- }
158
-
159
- log.probe(LOG_LEVEL + 1, 'DOM is loaded')();
160
-
161
- // @ts-expect-error
162
- if (props.gl?.device) {
163
- log.warn('reattaching existing device')();
164
- return WebGLDevice.attach(props.gl);
165
- }
166
-
167
- const device = new WebGLDevice(props);
102
+ /** WebGL2 context. */
103
+ readonly gl: WebGL2RenderingContext;
104
+ readonly debug: boolean = false;
168
105
 
169
- // Log some debug info about the newly created context
170
- const message = `\
171
- Created ${device.type}${device.debug ? ' debug' : ''} context: \
172
- ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContext.id}`;
173
- log.probe(LOG_LEVEL, message)();
174
- log.table(LOG_LEVEL, device.info)();
106
+ /** State used by luma.gl classes: TODO - move to canvasContext*/
107
+ readonly _canvasSizeInfo = {clientWidth: 0, clientHeight: 0, devicePixelRatio: 1};
175
108
 
176
- log.groupEnd(LOG_LEVEL)();
109
+ /** State used by luma.gl classes - TODO - not used? */
110
+ readonly _extensions: GLExtensions = {};
111
+ _polyfilled: boolean = false;
177
112
 
178
- return device;
179
- }
113
+ /** Instance of Spector.js (if initialized) */
114
+ spectorJS: Spector;
180
115
 
181
116
  //
182
117
  // Public API
183
118
  //
184
119
 
185
120
  constructor(props: DeviceProps) {
186
- super({...props, id: props.id || 'webgl-device'});
121
+ super({...props, id: props.id || uid('webgl-device')});
187
122
 
188
123
  // If attaching to an already attached context, return the attached device
189
124
  // @ts-expect-error device is attached to context
@@ -200,8 +135,7 @@ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContex
200
135
  this._resolveContextLost = resolve;
201
136
  });
202
137
 
203
- let gl: WebGL2RenderingContext | null = props.gl || null;
204
- gl ||= createBrowserContext(this.canvasContext.canvas, {
138
+ this.handle = createBrowserContext(this.canvasContext.canvas, {
205
139
  ...props,
206
140
  onContextLost: (event: Event) =>
207
141
  this._resolveContextLost?.({
@@ -209,18 +143,22 @@ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContex
209
143
  message: 'Entered sleep mode, or too many apps or browser tabs are using the GPU.'
210
144
  })
211
145
  });
146
+ this.gl = this.handle;
212
147
 
213
- if (!gl) {
148
+ if (!this.handle) {
214
149
  throw new Error('WebGL context creation failed');
215
150
  }
216
151
 
217
- this.handle = gl;
218
- this.gl = gl;
152
+ // Add spector debug instrumentation to context
153
+ // We need to trust spector integration to decide if spector should be initialized
154
+ // We also run spector instrumentation first, otherwise spector can clobber luma instrumentation.
155
+ this.spectorJS = initializeSpectorJS({...this.props, gl: this.handle});
219
156
 
157
+ // Instrument context
220
158
  (this.gl as any).device = this; // Update GL context: Link webgl context back to device
221
159
  (this.gl as any)._version = 2; // Update GL context: Store WebGL version field on gl context (HACK to identify debug contexts)
222
160
 
223
- // luma Device fields
161
+ // initialize luma Device fields
224
162
  this.info = getDeviceInfo(this.gl, this._extensions);
225
163
  this.limits = new WebGLDeviceLimits(this.gl);
226
164
  this.features = new WebGLDeviceFeatures(this.gl, this._extensions, this.props.disabledFeatures);
@@ -231,25 +169,18 @@ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContex
231
169
  this.canvasContext.resize();
232
170
 
233
171
  // Install context state tracking
234
- // @ts-expect-error - hidden parameters
235
- const {enable = true, copyState = false} = props;
236
- trackContextState(this.gl, {
237
- enable,
238
- copyState,
172
+ const glState = new WebGLStateTracker(this.gl, {
239
173
  log: (...args: any[]) => log.log(1, ...args)()
240
174
  });
175
+ glState.trackState(this.gl, {copyState: false});
241
176
 
242
- // DEBUG contexts: Add debug instrumentation to the context, force log level to at least 1
177
+ // DEBUG contexts: Add luma debug instrumentation to the context, force log level to at least 1
243
178
  if (props.debug) {
244
179
  this.gl = makeDebugContext(this.gl, {...props, throwOnError: true});
245
180
  this.debug = true;
246
181
  log.level = Math.max(log.level, 1);
247
182
  log.warn('WebGL debug mode activated. Performance reduced.')();
248
183
  }
249
-
250
- if (props.spector) {
251
- this.spectorJS = initializeSpectorJS({...this.props, canvas: this.handle.canvas});
252
- }
253
184
  }
254
185
 
255
186
  /**
@@ -262,10 +193,6 @@ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContex
262
193
  return this.gl.isContextLost();
263
194
  }
264
195
 
265
- getSize(): [number, number] {
266
- return [this.gl.drawingBufferWidth, this.gl.drawingBufferHeight];
267
- }
268
-
269
196
  isTextureFormatSupported(format: TextureFormat): boolean {
270
197
  return isTextureFormatSupported(this.gl, format, this._extensions);
271
198
  }
@@ -425,20 +352,6 @@ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContex
425
352
  // WebGL-only API (not part of `Device` API)
426
353
  //
427
354
 
428
- /** WebGL2 context. */
429
- readonly gl: WebGL2RenderingContext;
430
- readonly debug: boolean = false;
431
-
432
- /** State used by luma.gl classes: TODO - move to canvasContext*/
433
- readonly _canvasSizeInfo = {clientWidth: 0, clientHeight: 0, devicePixelRatio: 1};
434
-
435
- /** State used by luma.gl classes - TODO - not used? */
436
- readonly _extensions: GLExtensions = {};
437
- _polyfilled: boolean = false;
438
-
439
- /** Instance of Spector.js (if initialized) */
440
- spectorJS: unknown;
441
-
442
355
  /**
443
356
  * Triggers device (or WebGL context) loss.
444
357
  * @note primarily intended for testing how application reacts to device loss
@@ -461,12 +374,14 @@ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContex
461
374
 
462
375
  /** Save current WebGL context state onto an internal stack */
463
376
  pushState(): void {
464
- pushContextState(this.gl);
377
+ const webglState = WebGLStateTracker.get(this.gl);
378
+ webglState.push();
465
379
  }
466
380
 
467
381
  /** Restores previously saved context state */
468
382
  popState(): void {
469
- popContextState(this.gl);
383
+ const webglState = WebGLStateTracker.get(this.gl);
384
+ webglState.pop();
470
385
  }
471
386
 
472
387
  /**
@@ -541,15 +456,6 @@ ${device.info.vendor}, ${device.info.renderer} for canvas: ${device.canvasContex
541
456
  }
542
457
  }
543
458
 
544
- /** Check if supplied parameter is a WebGL2RenderingContext */
545
- function isWebGL(gl: any): boolean {
546
- if (typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext) {
547
- return true;
548
- }
549
- // Look for debug contexts, headless gl etc
550
- return Boolean(gl && Number.isFinite(gl._version));
551
- }
552
-
553
459
  /** Set constant float array attribute */
554
460
  function setConstantFloatArray(device: WebGLDevice, location: number, array: Float32Array): void {
555
461
  switch (array.length) {