@luma.gl/webgl 9.1.0-alpha.16 → 9.1.0-alpha.17

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 (55) hide show
  1. package/dist/adapter/converters/sampler-parameters.js +6 -4
  2. package/dist/adapter/converters/texture-formats.d.ts +49 -11
  3. package/dist/adapter/converters/texture-formats.d.ts.map +1 -1
  4. package/dist/adapter/converters/texture-formats.js +150 -160
  5. package/dist/adapter/helpers/format-utils.d.ts.map +1 -1
  6. package/dist/adapter/helpers/format-utils.js +6 -0
  7. package/dist/adapter/helpers/webgl-texture-utils.d.ts +10 -8
  8. package/dist/adapter/helpers/webgl-texture-utils.d.ts.map +1 -1
  9. package/dist/adapter/helpers/webgl-texture-utils.js +46 -32
  10. package/dist/adapter/resources/webgl-command-buffer.d.ts +59 -2
  11. package/dist/adapter/resources/webgl-command-buffer.d.ts.map +1 -1
  12. package/dist/adapter/resources/webgl-command-buffer.js +72 -16
  13. package/dist/adapter/resources/webgl-command-encoder.d.ts.map +1 -1
  14. package/dist/adapter/resources/webgl-command-encoder.js +3 -0
  15. package/dist/adapter/resources/webgl-external-texture.js +14 -0
  16. package/dist/adapter/resources/webgl-framebuffer.d.ts.map +1 -1
  17. package/dist/adapter/resources/webgl-framebuffer.js +1 -2
  18. package/dist/adapter/resources/webgl-render-pass.d.ts.map +1 -1
  19. package/dist/adapter/resources/webgl-render-pass.js +38 -20
  20. package/dist/adapter/resources/webgl-render-pipeline.d.ts.map +1 -1
  21. package/dist/adapter/resources/webgl-render-pipeline.js +6 -5
  22. package/dist/adapter/resources/webgl-shader.js +1 -1
  23. package/dist/adapter/resources/webgl-texture.d.ts +8 -14
  24. package/dist/adapter/resources/webgl-texture.d.ts.map +1 -1
  25. package/dist/adapter/resources/webgl-texture.js +119 -208
  26. package/dist/adapter/webgl-adapter.js +1 -1
  27. package/dist/adapter/webgl-device.d.ts +7 -1
  28. package/dist/adapter/webgl-device.d.ts.map +1 -1
  29. package/dist/adapter/webgl-device.js +22 -10
  30. package/dist/context/debug/webgl-developer-tools.d.ts +1 -0
  31. package/dist/context/debug/webgl-developer-tools.d.ts.map +1 -1
  32. package/dist/context/debug/webgl-developer-tools.js +4 -2
  33. package/dist/context/helpers/create-browser-context.d.ts.map +1 -1
  34. package/dist/context/helpers/create-browser-context.js +17 -3
  35. package/dist/dist.dev.js +226 -272
  36. package/dist/dist.min.js +2 -2
  37. package/dist/index.cjs +220 -269
  38. package/dist/index.cjs.map +3 -3
  39. package/package.json +4 -4
  40. package/src/adapter/converters/sampler-parameters.ts +6 -4
  41. package/src/adapter/converters/texture-formats.ts +171 -177
  42. package/src/adapter/helpers/format-utils.ts +6 -0
  43. package/src/adapter/helpers/webgl-texture-utils.ts +66 -45
  44. package/src/adapter/resources/webgl-command-buffer.ts +108 -24
  45. package/src/adapter/resources/webgl-command-encoder.ts +6 -0
  46. package/src/adapter/resources/webgl-external-texture.ts +14 -0
  47. package/src/adapter/resources/webgl-framebuffer.ts +1 -2
  48. package/src/adapter/resources/webgl-render-pass.ts +44 -23
  49. package/src/adapter/resources/webgl-render-pipeline.ts +6 -5
  50. package/src/adapter/resources/webgl-shader.ts +1 -1
  51. package/src/adapter/resources/webgl-texture.ts +126 -235
  52. package/src/adapter/webgl-adapter.ts +1 -1
  53. package/src/adapter/webgl-device.ts +23 -10
  54. package/src/context/debug/webgl-developer-tools.ts +5 -2
  55. package/src/context/helpers/create-browser-context.ts +18 -3
@@ -17,10 +17,8 @@ import type {
17
17
  Sampler,
18
18
  SamplerProps,
19
19
  SamplerParameters,
20
- // TextureFormat,
21
20
  TextureCubeFace,
22
21
  ExternalImage,
23
- TextureLevelData,
24
22
  Texture1DData,
25
23
  Texture2DData,
26
24
  Texture3DData,
@@ -58,42 +56,11 @@ import {
58
56
  getWebGLTextureTarget
59
57
  } from '../helpers/webgl-texture-utils';
60
58
 
61
- // PORTABLE HELPERS (Move to methods on Texture?)
62
-
63
- /**
64
- * Normalize TextureData to an array of TextureLevelData / ExternalImages
65
- * @param data
66
- * @param options
67
- * @returns array of TextureLevelData / ExternalImages
68
- */
69
- function normalizeTextureData(
70
- data: Texture2DData,
71
- options: {width: number; height: number; depth: number}
72
- ): (TextureLevelData | ExternalImage)[] {
73
- let lodArray: (TextureLevelData | ExternalImage)[];
74
- if (ArrayBuffer.isView(data)) {
75
- lodArray = [
76
- {
77
- // ts-expect-error does data really need to be Uint8ClampedArray?
78
- data,
79
- width: options.width,
80
- height: options.height
81
- // depth: options.depth
82
- }
83
- ];
84
- } else if (!Array.isArray(data)) {
85
- lodArray = [data];
86
- } else {
87
- lodArray = data;
88
- }
89
- return lodArray;
90
- }
91
-
92
59
  /**
93
60
  * WebGL... the texture API from hell... hopefully made simpler
94
61
  */
95
62
  export class WEBGLTexture extends Texture {
96
- readonly MAX_ATTRIBUTES: number;
63
+ // readonly MAX_ATTRIBUTES: number;
97
64
  readonly device: WebGLDevice;
98
65
  readonly gl: WebGL2RenderingContext;
99
66
  handle: WebGLTexture;
@@ -101,9 +68,14 @@ export class WEBGLTexture extends Texture {
101
68
  sampler: WEBGLSampler = undefined; // TODO - currently unused in WebGL. Create dummy sampler?
102
69
  view: WEBGLTextureView = undefined; // TODO - currently unused in WebGL. Create dummy view?
103
70
 
104
- mipmaps: boolean = false;
71
+ mipmaps: boolean;
72
+
73
+ // Texture type
74
+ /** Whether the internal format is compressed */
75
+ compressed: boolean;
105
76
 
106
77
  /**
78
+ * The WebGL target corresponding to the texture type
107
79
  * @note `target` cannot be modified by bind:
108
80
  * textures are special because when you first bind them to a target,
109
81
  * When you first bind a texture as a GL_TEXTURE_2D, you are saying that this texture is a 2D texture.
@@ -112,41 +84,23 @@ export class WEBGLTexture extends Texture {
112
84
  * attempting to bind it as GL_TEXTURE_3D will give rise to a run-time error
113
85
  */
114
86
  glTarget: GLTextureTarget;
115
-
116
- // Texture type
117
-
118
87
  /** The WebGL format - essentially channel structure */
119
88
  glFormat: GLTexelDataFormat;
120
89
  /** The WebGL data format - the type of each channel */
121
90
  glType: GLPixelType;
122
91
  /** The WebGL constant corresponding to the WebGPU style constant in format */
123
92
  glInternalFormat: GL;
124
- /** Whether the internal format is compressed */
125
- compressed: boolean;
126
-
127
- // data;
128
- // inherited props
129
- // dimension: ...
130
- // format: GLTextureTarget;
131
- // width: number = undefined;
132
- // height: number = undefined;
133
- // depth: number = undefined;
134
93
 
135
94
  // state
136
- /** Texture binding slot */
95
+ /** Texture binding slot - TODO - move to texture view? */
137
96
  textureUnit: number = 0;
138
- /** For automatically updating video */
139
- _video: {
140
- video: HTMLVideoElement;
141
- parameters: any;
142
- lastTime: number;
143
- } | null = null;
144
97
 
145
98
  constructor(device: Device, props: TextureProps) {
146
- props = Texture._fixProps(props);
99
+ super(device, props);
147
100
 
148
- // Note: Clear out `props.data` so that we don't hold a reference to any big memory chunks
149
- super(device, {...Texture.defaultProps, ...props, data: undefined!});
101
+ // Texture base class strips out the data prop, so we need to add it back in
102
+ const propsWithData = {...this.props};
103
+ propsWithData.data = props.data;
150
104
 
151
105
  this.device = device as WebGLDevice;
152
106
  this.gl = this.device.gl;
@@ -155,26 +109,14 @@ export class WEBGLTexture extends Texture {
155
109
  this.glTarget = getWebGLTextureTarget(this.props.dimension);
156
110
 
157
111
  // The target format of this texture
158
- const format = getTextureFormatWebGL(this.props.format);
159
- this.glInternalFormat = format.internalFormat;
160
- this.glFormat = format.format;
161
- this.glType = format.type;
162
- this.compressed = format.compressed;
112
+ const formatInfo = getTextureFormatWebGL(this.props.format);
113
+ this.glInternalFormat = formatInfo.internalFormat;
114
+ this.glFormat = formatInfo.format;
115
+ this.glType = formatInfo.type;
116
+ this.compressed = formatInfo.compressed;
117
+ this.mipmaps = Boolean(this.props.mipmaps);
163
118
 
164
- if (
165
- typeof HTMLVideoElement !== 'undefined' &&
166
- props.data instanceof HTMLVideoElement &&
167
- // @ts-expect-error
168
- props.data.readyState < HTMLVideoElement.HAVE_METADATA
169
- ) {
170
- const video = props.data;
171
- this._video = null; // Declare member before the object is sealed
172
- video.addEventListener('loadeddata', () => this.initialize(props));
173
- }
174
-
175
- // We removed data, we need to add it again.
176
- // @ts-expect-error
177
- this.initialize({...this.props, data: props.data});
119
+ this._initialize(propsWithData);
178
120
 
179
121
  Object.seal(this);
180
122
  }
@@ -183,18 +125,14 @@ export class WEBGLTexture extends Texture {
183
125
  * Initialize texture with supplied props
184
126
  */
185
127
  // eslint-disable-next-line max-statements
186
- initialize(props: TextureProps = {}): void {
128
+ _initialize(propsWithData: TextureProps): void {
187
129
  this.handle = this.props.handle || this.gl.createTexture();
188
- this.device.setSpectorMetadata(this.handle, {...this.props, data: typeof this.props.data});
189
-
190
- const data = props.data;
130
+ this.device.setSpectorMetadata(this.handle, {...this.props, data: propsWithData.data});
191
131
 
192
- // const {parameters = {} as Record<GL, any>} = props;
193
-
194
- let {width, height} = props;
132
+ let {width, height} = propsWithData;
195
133
 
196
134
  if (!width || !height) {
197
- const textureSize = Texture.getTextureDataSize(data);
135
+ const textureSize = Texture.getTextureDataSize(propsWithData.data);
198
136
  width = textureSize?.width || 1;
199
137
  height = textureSize?.height || 1;
200
138
  }
@@ -202,73 +140,35 @@ export class WEBGLTexture extends Texture {
202
140
  // Store opts for accessors
203
141
  this.width = width;
204
142
  this.height = height;
205
- this.depth = props.depth;
143
+ this.depth = propsWithData.depth;
206
144
 
207
145
  // Set texture sampler parameters
208
- this.setSampler(props.sampler);
209
- // @ts-ignore
146
+ this.setSampler(propsWithData.sampler);
147
+ // @ts-ignore TODO - fix types
210
148
  this.view = new WEBGLTextureView(this.device, {...this.props, texture: this});
211
149
 
212
150
  this.bind();
213
- if (!this.props.data) {
214
- initializeTextureStorage(this.gl, this.mipLevels, this);
215
- }
151
+ initializeTextureStorage(this.gl, this.mipLevels, this);
216
152
 
217
- if (props.data) {
153
+ if (propsWithData.data) {
218
154
  // prettier-ignore
219
- switch (props.dimension) {
220
- case '1d': this.setTexture1DData(props.data); break;
221
- case '2d': this.setTexture2DData(props.data); break;
222
- case '3d': this.setTexture3DData(props.data); break;
223
- case 'cube': this.setTextureCubeData(props.data); break;
224
- case '2d-array': this.setTextureArrayData(props.data); break;
225
- case 'cube-array': this.setTextureCubeArrayData(props.data); break;
155
+ switch (propsWithData.dimension) {
156
+ case '1d': this.setTexture1DData(propsWithData.data); break;
157
+ case '2d': this.setTexture2DData(propsWithData.data); break;
158
+ case '3d': this.setTexture3DData(propsWithData.data); break;
159
+ case 'cube': this.setTextureCubeData(propsWithData.data); break;
160
+ case '2d-array': this.setTextureArrayData(propsWithData.data); break;
161
+ case 'cube-array': this.setTextureCubeArrayData(propsWithData.data); break;
226
162
  // @ts-expect-error
227
- default: throw new Error(props.dimension);
163
+ default: throw new Error(propsWithData.dimension);
228
164
  }
229
165
  }
230
166
 
231
- this.mipmaps = Boolean(props.mipmaps);
232
-
233
167
  if (this.mipmaps) {
234
168
  this.generateMipmap();
235
169
  }
236
-
237
- // if (isVideo) {
238
- // this._video = {
239
- // video: data,
240
- // // TODO - should we be using the sampler parameters here?
241
- // parameters: {},
242
- // // @ts-expect-error HTMLVideoElement.HAVE_CURRENT_DATA is not declared
243
- // lastTime: data.readyState >= HTMLVideoElement.HAVE_CURRENT_DATA ? data.currentTime : -1
244
- // };
245
- // }
246
170
  }
247
171
 
248
- /*
249
- initializeCube(props?: TextureProps): void {
250
- const {mipmaps = true} = props; // , parameters = {} as Record<GL, any>} = props;
251
-
252
- // Store props for accessors
253
- // this.props = props;
254
-
255
- // @ts-expect-error
256
- this.setCubeMapData(props).then(() => {
257
- // TODO - should genMipmap() be called on the cubemap or on the faces?
258
- // TODO - without generateMipmap() cube textures do not work at all!!! Why?
259
- if (mipmaps) {
260
- this.generateMipmap(props);
261
- }
262
-
263
- this.setSampler(props.sampler);
264
-
265
- // v8 compatibility?
266
- // const {parameters = {} as Record<GL, any>} = props;
267
- // this._setSamplerParameters(parameters);
268
- });
269
- }
270
- */
271
-
272
172
  override destroy(): void {
273
173
  if (this.handle) {
274
174
  this.gl.deleteTexture(this.handle);
@@ -279,10 +179,6 @@ export class WEBGLTexture extends Texture {
279
179
  }
280
180
  }
281
181
 
282
- override toString(): string {
283
- return `Texture(${this.id},${this.width}x${this.height})`;
284
- }
285
-
286
182
  createView(props: TextureViewProps): WEBGLTextureView {
287
183
  return new WEBGLTextureView(this.device, {...props, texture: this});
288
184
  }
@@ -301,37 +197,25 @@ export class WEBGLTexture extends Texture {
301
197
  this._setSamplerParameters(parameters);
302
198
  }
303
199
 
304
- /** Update external texture (video frame or canvas) */
305
- update(): void {
306
- log.warn('Texture.update() not implemented');
307
- // if (this._video) {
308
- // const {video, parameters, lastTime} = this._video;
309
- // // @ts-expect-error
310
- // if (lastTime === video.currentTime || video.readyState < HTMLVideoElement.HAVE_CURRENT_DATA) {
311
- // return;
312
- // }
313
- // this.setSubImageData({
314
- // data: video,
315
- // parameters
316
- // });
317
- // if (this.mipmaps) {
318
- // this.generateMipmap();
319
- // }
320
- // this._video.lastTime = video.currentTime;
321
- // }
322
- }
323
-
324
200
  // Call to regenerate mipmaps after modifying texture(s)
325
201
  generateMipmap(params = {}): void {
326
- if (!this.props.data) {
327
- return;
202
+ if (
203
+ !this.device.isTextureFormatRenderable(this.props.format) ||
204
+ !this.device.isTextureFormatFilterable(this.props.format)
205
+ ) {
206
+ log.warn(`${this} is not renderable or filterable, may not be able to generate mipmaps`)();
207
+ }
208
+
209
+ try {
210
+ this.gl.bindTexture(this.glTarget, this.handle);
211
+ withGLParameters(this.gl, params, () => {
212
+ this.gl.generateMipmap(this.glTarget);
213
+ });
214
+ } catch (error) {
215
+ log.warn(`Error generating mipmap for ${this}: ${(error as Error).message}`)();
216
+ } finally {
217
+ this.gl.bindTexture(this.glTarget, null);
328
218
  }
329
- this.mipmaps = true;
330
- this.gl.bindTexture(this.glTarget, this.handle);
331
- withGLParameters(this.gl, params, () => {
332
- this.gl.generateMipmap(this.glTarget);
333
- });
334
- this.gl.bindTexture(this.glTarget, null);
335
219
  }
336
220
 
337
221
  // Image Data Setters
@@ -349,11 +233,12 @@ export class WEBGLTexture extends Texture {
349
233
  aspect?: 'all' | 'stencil-only' | 'depth-only';
350
234
  colorSpace?: 'srgb';
351
235
  premultipliedAlpha?: boolean;
236
+ flipY?: boolean;
352
237
  }): {width: number; height: number} {
353
238
  const size = Texture.getExternalImageSize(options.image);
354
239
  const opts = {...Texture.defaultCopyExternalImageOptions, ...size, ...options};
355
240
 
356
- const {image, depth, mipLevel, x, y, z} = opts;
241
+ const {image, depth, mipLevel, x, y, z, flipY} = opts;
357
242
  let {width, height} = opts;
358
243
  const {dimension, glTarget, glFormat, glInternalFormat, glType} = this;
359
244
 
@@ -361,12 +246,9 @@ export class WEBGLTexture extends Texture {
361
246
  width = Math.min(width, size.width - x);
362
247
  height = Math.min(height, size.height - y);
363
248
 
364
- // WebGL does not yet support sourceX/sourceY in copyExternalImage; requires copyTexSubImage2D from a framebuffer'
365
-
366
249
  if (options.sourceX || options.sourceY) {
367
- throw new Error(
368
- 'WebGL does not yet support sourceX/sourceY in copyExternalImage; requires copyTexSubImage2D from a framebuffer'
369
- );
250
+ // requires copyTexSubImage2D from a framebuffer'
251
+ throw new Error('WebGL does not support sourceX/sourceY)');
370
252
  }
371
253
 
372
254
  copyExternalImageToMipLevel(this.device.gl, this.handle, image, {
@@ -381,7 +263,8 @@ export class WEBGLTexture extends Texture {
381
263
  glFormat,
382
264
  glInternalFormat,
383
265
  glType,
384
- glTarget
266
+ glTarget,
267
+ flipY
385
268
  });
386
269
 
387
270
  return {width: opts.width, height: opts.height};
@@ -395,7 +278,7 @@ export class WEBGLTexture extends Texture {
395
278
  setTexture2DData(lodData: Texture2DData, depth = 0): void {
396
279
  this.bind();
397
280
 
398
- const lodArray = normalizeTextureData(lodData, this);
281
+ const lodArray = Texture.normalizeTextureData(lodData, this);
399
282
 
400
283
  // If the user provides multiple LODs, then automatic mipmap
401
284
  // generation generateMipmap() should be disabled to avoid overwriting them.
@@ -474,6 +357,13 @@ export class WEBGLTexture extends Texture {
474
357
  this.setTexture2DData(lodData, faceDepth);
475
358
  }
476
359
 
360
+ // DEPRECATED METHODS
361
+
362
+ /** Update external texture (video frame or canvas) @deprecated Use ExternalTexture */
363
+ update(): void {
364
+ throw new Error('Texture.update() not implemented. Use ExternalTexture');
365
+ }
366
+
477
367
  // INTERNAL METHODS
478
368
 
479
369
  /** @todo update this method to accept LODs */
@@ -528,7 +418,7 @@ export class WEBGLTexture extends Texture {
528
418
  * Sets sampler parameters on texture
529
419
  */
530
420
  _setSamplerParameters(parameters: GLSamplerParameters): void {
531
- log.log(1, 'texture sampler parameters', parameters)();
421
+ log.log(1, `${this.id} sampler parameters`, this.device.getGLKeys(parameters))();
532
422
 
533
423
  this.gl.bindTexture(this.glTarget, this.handle);
534
424
  for (const [pname, pvalue] of Object.entries(parameters)) {
@@ -566,63 +456,6 @@ export class WEBGLTexture extends Texture {
566
456
  this.gl.bindTexture(this.glTarget, null);
567
457
  }
568
458
 
569
- // CLASSIC
570
-
571
- /*
572
- setCubeMapData(options: {
573
- width: number;
574
- height: number;
575
- data: Record<GL, Texture2DData> | Record<TextureCubeFace, Texture2DData>;
576
- format?: any;
577
- type?: any;
578
- /** @deprecated Use .data *
579
- pixels: any;
580
- }): void {
581
- const {gl} = this;
582
-
583
- const {width, height, pixels, data, format = GL.RGBA, type = GL.UNSIGNED_BYTE} = options;
584
-
585
- // pixel data (imageDataMap) is an Object from Face to Image or Promise.
586
- // For example:
587
- // {
588
- // GL.TEXTURE_CUBE_MAP_POSITIVE_X : Image-or-Promise,
589
- // GL.TEXTURE_CUBE_MAP_NEGATIVE_X : Image-or-Promise,
590
- // ... }
591
- // To provide multiple level-of-details (LODs) this can be Face to Array
592
- // of Image or Promise, like this
593
- // {
594
- // GL.TEXTURE_CUBE_MAP_POSITIVE_X : [Image-or-Promise-LOD-0, Image-or-Promise-LOD-1],
595
- // GL.TEXTURE_CUBE_MAP_NEGATIVE_X : [Image-or-Promise-LOD-0, Image-or-Promise-LOD-1],
596
- // ... }
597
-
598
- const imageDataMap = this._getImageDataMap(pixels || data);
599
-
600
- const resolvedFaces = WEBGLTexture.FACES.map(face => {
601
- const facePixels = imageDataMap[face];
602
- return Array.isArray(facePixels) ? facePixels : [facePixels];
603
- });
604
- this.bind();
605
-
606
- WEBGLTexture.FACES.forEach((face, index) => {
607
- if (resolvedFaces[index].length > 1 && this.props.mipmaps !== false) {
608
- // If the user provides multiple LODs, then automatic mipmap
609
- // generation generateMipmap() should be disabled to avoid overwritting them.
610
- log.warn(`${this.id} has mipmap and multiple LODs.`)();
611
- }
612
- resolvedFaces[index].forEach((image, lodLevel) => {
613
- // TODO: adjust width & height for LOD!
614
- if (width && height) {
615
- gl.texImage2D(face, lodLevel, format, width, height, 0 /* border*, format, type, image);
616
- } else {
617
- gl.texImage2D(face, lodLevel, format, format, type, image);
618
- }
619
- });
620
- });
621
-
622
- this.unbind();
623
- }
624
- */
625
-
626
459
  // INTERNAL SETTERS
627
460
 
628
461
  /**
@@ -645,7 +478,8 @@ export class WEBGLTexture extends Texture {
645
478
  ...this,
646
479
  depth,
647
480
  mipLevel,
648
- glTarget
481
+ glTarget,
482
+ flipY: this.props.flipY
649
483
  });
650
484
  return;
651
485
  }
@@ -693,3 +527,60 @@ export class WEBGLTexture extends Texture {
693
527
  return textureUnit;
694
528
  }
695
529
  }
530
+
531
+ // TODO - Remove when texture refactor is complete
532
+
533
+ /*
534
+ setCubeMapData(options: {
535
+ width: number;
536
+ height: number;
537
+ data: Record<GL, Texture2DData> | Record<TextureCubeFace, Texture2DData>;
538
+ format?: any;
539
+ type?: any;
540
+ /** @deprecated Use .data *
541
+ pixels: any;
542
+ }): void {
543
+ const {gl} = this;
544
+
545
+ const {width, height, pixels, data, format = GL.RGBA, type = GL.UNSIGNED_BYTE} = options;
546
+
547
+ // pixel data (imageDataMap) is an Object from Face to Image or Promise.
548
+ // For example:
549
+ // {
550
+ // GL.TEXTURE_CUBE_MAP_POSITIVE_X : Image-or-Promise,
551
+ // GL.TEXTURE_CUBE_MAP_NEGATIVE_X : Image-or-Promise,
552
+ // ... }
553
+ // To provide multiple level-of-details (LODs) this can be Face to Array
554
+ // of Image or Promise, like this
555
+ // {
556
+ // GL.TEXTURE_CUBE_MAP_POSITIVE_X : [Image-or-Promise-LOD-0, Image-or-Promise-LOD-1],
557
+ // GL.TEXTURE_CUBE_MAP_NEGATIVE_X : [Image-or-Promise-LOD-0, Image-or-Promise-LOD-1],
558
+ // ... }
559
+
560
+ const imageDataMap = this._getImageDataMap(pixels || data);
561
+
562
+ const resolvedFaces = WEBGLTexture.FACES.map(face => {
563
+ const facePixels = imageDataMap[face];
564
+ return Array.isArray(facePixels) ? facePixels : [facePixels];
565
+ });
566
+ this.bind();
567
+
568
+ WEBGLTexture.FACES.forEach((face, index) => {
569
+ if (resolvedFaces[index].length > 1 && this.props.mipmaps !== false) {
570
+ // If the user provides multiple LODs, then automatic mipmap
571
+ // generation generateMipmap() should be disabled to avoid overwritting them.
572
+ log.warn(`${this.id} has mipmap and multiple LODs.`)();
573
+ }
574
+ resolvedFaces[index].forEach((image, lodLevel) => {
575
+ // TODO: adjust width & height for LOD!
576
+ if (width && height) {
577
+ gl.texImage2D(face, lodLevel, format, width, height, 0 /* border*, format, type, image);
578
+ } else {
579
+ gl.texImage2D(face, lodLevel, format, format, type, image);
580
+ }
581
+ });
582
+ });
583
+
584
+ this.unbind();
585
+ }
586
+ */
@@ -61,7 +61,7 @@ export class WebGLAdapter extends Adapter {
61
61
  const promises: Promise<unknown>[] = [];
62
62
 
63
63
  // Load webgl and spector debug scripts from CDN if requested
64
- if (props.debugWebGL) {
64
+ if (props.debugWebGL || props.debug) {
65
65
  promises.push(loadWebGLDeveloperTools());
66
66
  }
67
67
 
@@ -202,11 +202,14 @@ export class WebGLDevice extends Device {
202
202
  glState.trackState(this.gl, {copyState: false});
203
203
 
204
204
  // DEBUG contexts: Add luma debug instrumentation to the context, force log level to at least 1
205
- if (props.debugWebGL) {
206
- this.gl = makeDebugContext(this.gl, {...props});
207
- this.debug = true;
208
- log.level = Math.max(log.level, 1);
205
+ const debugWebGL = props.debugWebGL || props.debug;
206
+ const traceWebGL = props.debugWebGL;
207
+ if (debugWebGL) {
208
+ this.gl = makeDebugContext(this.gl, {debugWebGL, traceWebGL});
209
209
  log.warn('WebGL debug mode activated. Performance reduced.')();
210
+ if (props.debugWebGL) {
211
+ log.level = Math.max(log.level, 1);
212
+ }
210
213
  }
211
214
  }
212
215
 
@@ -425,18 +428,28 @@ export class WebGLDevice extends Device {
425
428
  * Be aware that there are some duplicates especially for constants that are 0,
426
429
  * so this isn't guaranteed to return the right key in all cases.
427
430
  */
428
- getGLKey(value: unknown, gl?: WebGL2RenderingContext): string {
429
- // @ts-ignore expect-error depends on settings
430
- gl = gl || this.gl2 || this.gl;
431
+ getGLKey(value: unknown, options?: {emptyIfUnknown?: boolean}): string {
431
432
  const number = Number(value);
432
- for (const key in gl) {
433
+ for (const key in this.gl) {
433
434
  // @ts-ignore expect-error depends on settings
434
- if (gl[key] === number) {
435
+ if (this.gl[key] === number) {
435
436
  return `GL.${key}`;
436
437
  }
437
438
  }
438
439
  // No constant found. Stringify the value and return it.
439
- return String(value);
440
+ return options?.emptyIfUnknown ? '' : String(value);
441
+ }
442
+
443
+ /**
444
+ * Returns a map with any GL.<KEY> constants mapped to strings, both for keys and values
445
+ */
446
+ getGLKeys(glParameters: Record<number, unknown>): Record<string, string> {
447
+ const opts = {emptyIfUnknown: true};
448
+ return Object.entries(glParameters).reduce<Record<string, string>>((keys, [key, value]) => {
449
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
450
+ keys[`${key}:${this.getGLKey(key, opts)}`] = `${value}:${this.getGLKey(value, opts)}`;
451
+ return keys;
452
+ }, {});
440
453
  }
441
454
 
442
455
  /** Store constants */
@@ -12,6 +12,7 @@ const WEBGL_DEBUG_CDN_URL = 'https://unpkg.com/webgl-debug@2.0.1/index.js';
12
12
 
13
13
  type DebugContextProps = {
14
14
  debugWebGL?: boolean;
15
+ traceWebGL?: boolean;
15
16
  };
16
17
 
17
18
  type ContextData = {
@@ -51,7 +52,7 @@ export function makeDebugContext(
51
52
  gl: WebGL2RenderingContext,
52
53
  props: DebugContextProps = {}
53
54
  ): WebGL2RenderingContext {
54
- return props.debugWebGL ? getDebugContext(gl, props) : getRealContext(gl);
55
+ return props.debugWebGL || props.traceWebGL ? getDebugContext(gl, props) : getRealContext(gl);
55
56
  }
56
57
 
57
58
  // Returns the real context from either of the real/debug contexts
@@ -139,7 +140,9 @@ function onValidateGLFunc(
139
140
  let functionString: string = '';
140
141
  if (log.level >= 1) {
141
142
  functionString = getFunctionString(functionName, functionArgs);
142
- log.log(1, functionString)();
143
+ if (props.traceWebGL) {
144
+ log.log(1, functionString)();
145
+ }
143
146
  }
144
147
 
145
148
  for (const arg of functionArgs) {
@@ -25,7 +25,7 @@ export function createBrowserContext(
25
25
  webglContextAttributes: WebGLContextAttributes
26
26
  ): WebGL2RenderingContext {
27
27
  // Try to extract any extra information about why context creation failed
28
- const errorMessage = null;
28
+ let errorMessage = '';
29
29
  // const onCreateError = error => (errorMessage = error.statusMessage || errorMessage);
30
30
 
31
31
  // Avoid multiple listeners?
@@ -43,11 +43,15 @@ export function createBrowserContext(
43
43
 
44
44
  // Create a webgl2 context
45
45
  gl ||= canvas.getContext('webgl2', webglProps);
46
+ if (webglProps.failIfMajorPerformanceCaveat) {
47
+ errorMessage ||=
48
+ 'Only software GPU is available. Set `failIfMajorPerformanceCaveat: false` to allow.';
49
+ }
46
50
 
47
51
  // Creation failed with failIfMajorPerformanceCaveat - Try a Software GPU
48
52
  if (!gl && !webglContextAttributes.failIfMajorPerformanceCaveat) {
49
53
  webglProps.failIfMajorPerformanceCaveat = false;
50
- gl = canvas.getContext('webgl', webglProps) as WebGL2RenderingContext;
54
+ gl = canvas.getContext('webgl2', webglProps);
51
55
  // @ts-expect-error
52
56
  gl.luma ||= {};
53
57
  // @ts-expect-error
@@ -55,7 +59,16 @@ export function createBrowserContext(
55
59
  }
56
60
 
57
61
  if (!gl) {
58
- throw new Error(`Failed to create WebGL context: ${errorMessage || 'Unknown error'}`);
62
+ gl = canvas.getContext('webgl', {}) as WebGL2RenderingContext;
63
+ if (gl) {
64
+ gl = null;
65
+ errorMessage ||= 'Your browser only supports WebGL1';
66
+ }
67
+ }
68
+
69
+ if (!gl) {
70
+ errorMessage ||= 'Your browser does not support WebGL';
71
+ throw new Error(`Failed to create WebGL context: ${errorMessage}`);
59
72
  }
60
73
 
61
74
  // Carefully extract and wrap callbacks to prevent addEventListener from rebinding them.
@@ -67,6 +80,8 @@ export function createBrowserContext(
67
80
  false
68
81
  );
69
82
 
83
+ // @ts-expect-error
84
+ gl.luma ||= {};
70
85
  return gl;
71
86
  }
72
87