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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/dist.dev.js +1265 -362
  2. package/dist/dist.min.js +10 -9
  3. package/dist/index.cjs +419 -258
  4. package/dist/index.cjs.map +2 -2
  5. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.d.ts +1 -1
  6. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.d.ts.map +1 -1
  7. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.js +5 -4
  8. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.js.map +1 -1
  9. package/dist/passes/postprocessing/image-adjust-filters/denoise.d.ts +1 -1
  10. package/dist/passes/postprocessing/image-adjust-filters/denoise.d.ts.map +1 -1
  11. package/dist/passes/postprocessing/image-adjust-filters/denoise.js +29 -21
  12. package/dist/passes/postprocessing/image-adjust-filters/denoise.js.map +1 -1
  13. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.d.ts +1 -1
  14. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.d.ts.map +1 -1
  15. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.js +26 -32
  16. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.js.map +1 -1
  17. package/dist/passes/postprocessing/image-adjust-filters/noise.d.ts +1 -1
  18. package/dist/passes/postprocessing/image-adjust-filters/noise.d.ts.map +1 -1
  19. package/dist/passes/postprocessing/image-adjust-filters/noise.js +8 -7
  20. package/dist/passes/postprocessing/image-adjust-filters/noise.js.map +1 -1
  21. package/dist/passes/postprocessing/image-adjust-filters/sepia.d.ts +1 -1
  22. package/dist/passes/postprocessing/image-adjust-filters/sepia.d.ts.map +1 -1
  23. package/dist/passes/postprocessing/image-adjust-filters/sepia.js +10 -9
  24. package/dist/passes/postprocessing/image-adjust-filters/sepia.js.map +1 -1
  25. package/dist/passes/postprocessing/image-adjust-filters/vibrance.d.ts +1 -1
  26. package/dist/passes/postprocessing/image-adjust-filters/vibrance.d.ts.map +1 -1
  27. package/dist/passes/postprocessing/image-adjust-filters/vibrance.js +9 -8
  28. package/dist/passes/postprocessing/image-adjust-filters/vibrance.js.map +1 -1
  29. package/dist/passes/postprocessing/image-adjust-filters/vignette.d.ts +1 -1
  30. package/dist/passes/postprocessing/image-adjust-filters/vignette.d.ts.map +1 -1
  31. package/dist/passes/postprocessing/image-adjust-filters/vignette.js +9 -13
  32. package/dist/passes/postprocessing/image-adjust-filters/vignette.js.map +1 -1
  33. package/dist/passes/postprocessing/image-blur-filters/tiltshift.d.ts +2 -2
  34. package/dist/passes/postprocessing/image-blur-filters/tiltshift.d.ts.map +1 -1
  35. package/dist/passes/postprocessing/image-blur-filters/tiltshift.js +34 -16
  36. package/dist/passes/postprocessing/image-blur-filters/tiltshift.js.map +1 -1
  37. package/dist/passes/postprocessing/image-blur-filters/triangleblur.d.ts +2 -2
  38. package/dist/passes/postprocessing/image-blur-filters/triangleblur.d.ts.map +1 -1
  39. package/dist/passes/postprocessing/image-blur-filters/triangleblur.js +21 -12
  40. package/dist/passes/postprocessing/image-blur-filters/triangleblur.js.map +1 -1
  41. package/dist/passes/postprocessing/image-blur-filters/zoomblur.d.ts +2 -2
  42. package/dist/passes/postprocessing/image-blur-filters/zoomblur.d.ts.map +1 -1
  43. package/dist/passes/postprocessing/image-blur-filters/zoomblur.js +20 -11
  44. package/dist/passes/postprocessing/image-blur-filters/zoomblur.js.map +1 -1
  45. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.d.ts +1 -1
  46. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.d.ts.map +1 -1
  47. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.js +18 -16
  48. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.js.map +1 -1
  49. package/dist/passes/postprocessing/image-fun-filters/dotscreen.d.ts +1 -1
  50. package/dist/passes/postprocessing/image-fun-filters/dotscreen.js +11 -11
  51. package/dist/passes/postprocessing/image-fun-filters/edgework.d.ts +2 -2
  52. package/dist/passes/postprocessing/image-fun-filters/edgework.d.ts.map +1 -1
  53. package/dist/passes/postprocessing/image-fun-filters/edgework.js +83 -12
  54. package/dist/passes/postprocessing/image-fun-filters/edgework.js.map +1 -1
  55. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.d.ts +1 -1
  56. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.d.ts.map +1 -1
  57. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.js +33 -21
  58. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.js.map +1 -1
  59. package/dist/passes/postprocessing/image-fun-filters/ink.d.ts +1 -1
  60. package/dist/passes/postprocessing/image-fun-filters/ink.d.ts.map +1 -1
  61. package/dist/passes/postprocessing/image-fun-filters/ink.js +24 -15
  62. package/dist/passes/postprocessing/image-fun-filters/ink.js.map +1 -1
  63. package/dist/passes/postprocessing/image-fun-filters/magnify.d.ts +1 -1
  64. package/dist/passes/postprocessing/image-fun-filters/magnify.d.ts.map +1 -1
  65. package/dist/passes/postprocessing/image-fun-filters/magnify.js +16 -11
  66. package/dist/passes/postprocessing/image-fun-filters/magnify.js.map +1 -1
  67. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.d.ts +2 -2
  68. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.d.ts.map +1 -1
  69. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.js +24 -10
  70. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.js.map +1 -1
  71. package/dist/passes/postprocessing/image-warp-filters/swirl.d.ts +2 -2
  72. package/dist/passes/postprocessing/image-warp-filters/swirl.d.ts.map +1 -1
  73. package/dist/passes/postprocessing/image-warp-filters/swirl.js +18 -13
  74. package/dist/passes/postprocessing/image-warp-filters/swirl.js.map +1 -1
  75. package/dist/passes/postprocessing/image-warp-filters/warp.d.ts +1 -1
  76. package/dist/passes/postprocessing/image-warp-filters/warp.d.ts.map +1 -1
  77. package/dist/passes/postprocessing/image-warp-filters/warp.js +9 -4
  78. package/dist/passes/postprocessing/image-warp-filters/warp.js.map +1 -1
  79. package/package.json +2 -2
  80. package/src/passes/postprocessing/image-adjust-filters/brightnesscontrast.ts +5 -4
  81. package/src/passes/postprocessing/image-adjust-filters/denoise.ts +31 -23
  82. package/src/passes/postprocessing/image-adjust-filters/huesaturation.ts +28 -34
  83. package/src/passes/postprocessing/image-adjust-filters/noise.ts +8 -7
  84. package/src/passes/postprocessing/image-adjust-filters/sepia.ts +10 -9
  85. package/src/passes/postprocessing/image-adjust-filters/vibrance.ts +9 -8
  86. package/src/passes/postprocessing/image-adjust-filters/vignette.ts +9 -13
  87. package/src/passes/postprocessing/image-blur-filters/tiltshift.ts +36 -18
  88. package/src/passes/postprocessing/image-blur-filters/triangleblur.ts +21 -12
  89. package/src/passes/postprocessing/image-blur-filters/zoomblur.ts +21 -12
  90. package/src/passes/postprocessing/image-fun-filters/colorhalftone.ts +18 -16
  91. package/src/passes/postprocessing/image-fun-filters/dotscreen.ts +11 -11
  92. package/src/passes/postprocessing/image-fun-filters/edgework.ts +84 -13
  93. package/src/passes/postprocessing/image-fun-filters/hexagonalpixelate.ts +36 -24
  94. package/src/passes/postprocessing/image-fun-filters/ink.ts +24 -15
  95. package/src/passes/postprocessing/image-fun-filters/magnify.ts +16 -11
  96. package/src/passes/postprocessing/image-warp-filters/bulgepinch.ts +24 -10
  97. package/src/passes/postprocessing/image-warp-filters/swirl.ts +18 -13
  98. package/src/passes/postprocessing/image-warp-filters/warp.ts +9 -4
package/dist/dist.dev.js CHANGED
@@ -67,12 +67,14 @@ var __exports__ = (() => {
67
67
  Fence: () => Fence,
68
68
  Framebuffer: () => Framebuffer,
69
69
  PipelineLayout: () => PipelineLayout,
70
+ PresentationContext: () => PresentationContext,
70
71
  QuerySet: () => QuerySet,
71
72
  RenderPass: () => RenderPass,
72
73
  RenderPipeline: () => RenderPipeline,
73
74
  Resource: () => Resource,
74
75
  Sampler: () => Sampler,
75
76
  Shader: () => Shader,
77
+ SharedRenderPipeline: () => SharedRenderPipeline,
76
78
  Texture: () => Texture,
77
79
  TextureFormatDecoder: () => TextureFormatDecoder,
78
80
  TextureView: () => TextureView,
@@ -83,6 +85,8 @@ var __exports__ = (() => {
83
85
  VertexArray: () => VertexArray,
84
86
  _getTextureFormatDefinition: () => getTextureFormatDefinition,
85
87
  _getTextureFormatTable: () => getTextureFormatTable,
88
+ assert: () => assert2,
89
+ assertDefined: () => assertDefined,
86
90
  getAttributeInfosFromLayouts: () => getAttributeInfosFromLayouts,
87
91
  getAttributeShaderTypeInfo: () => getAttributeShaderTypeInfo,
88
92
  getDataType: () => getDataType,
@@ -299,6 +303,24 @@ var __exports__ = (() => {
299
303
  };
300
304
 
301
305
  // ../core/src/utils/stats-manager.ts
306
+ var GPU_TIME_AND_MEMORY_STATS = "GPU Time and Memory";
307
+ var GPU_TIME_AND_MEMORY_STAT_ORDER = [
308
+ "Adapter",
309
+ "GPU",
310
+ "GPU Type",
311
+ "GPU Backend",
312
+ "Frame Rate",
313
+ "CPU Time",
314
+ "GPU Time",
315
+ "GPU Memory",
316
+ "Buffer Memory",
317
+ "Texture Memory",
318
+ "Referenced Buffer Memory",
319
+ "Referenced Texture Memory",
320
+ "Swap Chain Texture"
321
+ ];
322
+ var ORDERED_STATS_CACHE = /* @__PURE__ */ new WeakMap();
323
+ var ORDERED_STAT_NAME_SET_CACHE = /* @__PURE__ */ new WeakMap();
302
324
  var StatsManager = class {
303
325
  stats = /* @__PURE__ */ new Map();
304
326
  getStats(name2) {
@@ -308,10 +330,50 @@ var __exports__ = (() => {
308
330
  if (!this.stats.has(name2)) {
309
331
  this.stats.set(name2, new Stats({ id: name2 }));
310
332
  }
311
- return this.stats.get(name2);
333
+ const stats = this.stats.get(name2);
334
+ if (name2 === GPU_TIME_AND_MEMORY_STATS) {
335
+ initializeStats(stats, GPU_TIME_AND_MEMORY_STAT_ORDER);
336
+ }
337
+ return stats;
312
338
  }
313
339
  };
314
340
  var lumaStats = new StatsManager();
341
+ function initializeStats(stats, orderedStatNames) {
342
+ const statsMap = stats.stats;
343
+ let addedOrderedStat = false;
344
+ for (const statName of orderedStatNames) {
345
+ if (!statsMap[statName]) {
346
+ stats.get(statName);
347
+ addedOrderedStat = true;
348
+ }
349
+ }
350
+ const statCount = Object.keys(statsMap).length;
351
+ const cachedStats = ORDERED_STATS_CACHE.get(stats);
352
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
353
+ return;
354
+ }
355
+ const reorderedStats = {};
356
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE.get(orderedStatNames);
357
+ if (!orderedStatNamesSet) {
358
+ orderedStatNamesSet = new Set(orderedStatNames);
359
+ ORDERED_STAT_NAME_SET_CACHE.set(orderedStatNames, orderedStatNamesSet);
360
+ }
361
+ for (const statName of orderedStatNames) {
362
+ if (statsMap[statName]) {
363
+ reorderedStats[statName] = statsMap[statName];
364
+ }
365
+ }
366
+ for (const [statName, stat] of Object.entries(statsMap)) {
367
+ if (!orderedStatNamesSet.has(statName)) {
368
+ reorderedStats[statName] = stat;
369
+ }
370
+ }
371
+ for (const statName of Object.keys(statsMap)) {
372
+ delete statsMap[statName];
373
+ }
374
+ Object.assign(statsMap, reorderedStats);
375
+ ORDERED_STATS_CACHE.set(stats, { orderedStatNames, statCount });
376
+ }
315
377
 
316
378
  // ../../node_modules/@probe.gl/env/dist/lib/globals.js
317
379
  var window_ = globalThis;
@@ -343,7 +405,139 @@ var __exports__ = (() => {
343
405
  }
344
406
 
345
407
  // ../../node_modules/@probe.gl/env/dist/index.js
346
- var VERSION = true ? "4.1.0" : "untranspiled source";
408
+ var VERSION = true ? "4.1.1" : "untranspiled source";
409
+
410
+ // ../../node_modules/@probe.gl/log/dist/utils/assert.js
411
+ function assert(condition, message) {
412
+ if (!condition) {
413
+ throw new Error(message || "Assertion failed");
414
+ }
415
+ }
416
+
417
+ // ../../node_modules/@probe.gl/log/dist/loggers/log-utils.js
418
+ function normalizeLogLevel(logLevel) {
419
+ if (!logLevel) {
420
+ return 0;
421
+ }
422
+ let resolvedLevel;
423
+ switch (typeof logLevel) {
424
+ case "number":
425
+ resolvedLevel = logLevel;
426
+ break;
427
+ case "object":
428
+ resolvedLevel = logLevel.logLevel || logLevel.priority || 0;
429
+ break;
430
+ default:
431
+ return 0;
432
+ }
433
+ assert(Number.isFinite(resolvedLevel) && resolvedLevel >= 0);
434
+ return resolvedLevel;
435
+ }
436
+ function normalizeArguments(opts) {
437
+ const { logLevel, message } = opts;
438
+ opts.logLevel = normalizeLogLevel(logLevel);
439
+ const args = opts.args ? Array.from(opts.args) : [];
440
+ while (args.length && args.shift() !== message) {
441
+ }
442
+ switch (typeof logLevel) {
443
+ case "string":
444
+ case "function":
445
+ if (message !== void 0) {
446
+ args.unshift(message);
447
+ }
448
+ opts.message = logLevel;
449
+ break;
450
+ case "object":
451
+ Object.assign(opts, logLevel);
452
+ break;
453
+ default:
454
+ }
455
+ if (typeof opts.message === "function") {
456
+ opts.message = opts.message();
457
+ }
458
+ const messageType = typeof opts.message;
459
+ assert(messageType === "string" || messageType === "object");
460
+ return Object.assign(opts, { args }, opts.opts);
461
+ }
462
+
463
+ // ../../node_modules/@probe.gl/log/dist/loggers/base-log.js
464
+ var noop = () => {
465
+ };
466
+ var BaseLog = class {
467
+ constructor({ level = 0 } = {}) {
468
+ this.userData = {};
469
+ this._onceCache = /* @__PURE__ */ new Set();
470
+ this._level = level;
471
+ }
472
+ set level(newLevel) {
473
+ this.setLevel(newLevel);
474
+ }
475
+ get level() {
476
+ return this.getLevel();
477
+ }
478
+ setLevel(level) {
479
+ this._level = level;
480
+ return this;
481
+ }
482
+ getLevel() {
483
+ return this._level;
484
+ }
485
+ // Unconditional logging
486
+ warn(message, ...args) {
487
+ return this._log("warn", 0, message, args, { once: true });
488
+ }
489
+ error(message, ...args) {
490
+ return this._log("error", 0, message, args);
491
+ }
492
+ // Conditional logging
493
+ log(logLevel, message, ...args) {
494
+ return this._log("log", logLevel, message, args);
495
+ }
496
+ info(logLevel, message, ...args) {
497
+ return this._log("info", logLevel, message, args);
498
+ }
499
+ once(logLevel, message, ...args) {
500
+ return this._log("once", logLevel, message, args, { once: true });
501
+ }
502
+ _log(type, logLevel, message, args, options = {}) {
503
+ const normalized = normalizeArguments({
504
+ logLevel,
505
+ message,
506
+ args: this._buildArgs(logLevel, message, args),
507
+ opts: options
508
+ });
509
+ return this._createLogFunction(type, normalized, options);
510
+ }
511
+ _buildArgs(logLevel, message, args) {
512
+ return [logLevel, message, ...args];
513
+ }
514
+ _createLogFunction(type, normalized, options) {
515
+ if (!this._shouldLog(normalized.logLevel)) {
516
+ return noop;
517
+ }
518
+ const tag = this._getOnceTag(options.tag ?? normalized.tag ?? normalized.message);
519
+ if ((options.once || normalized.once) && tag !== void 0) {
520
+ if (this._onceCache.has(tag)) {
521
+ return noop;
522
+ }
523
+ this._onceCache.add(tag);
524
+ }
525
+ return this._emit(type, normalized);
526
+ }
527
+ _shouldLog(logLevel) {
528
+ return this.getLevel() >= normalizeLogLevel(logLevel);
529
+ }
530
+ _getOnceTag(tag) {
531
+ if (tag === void 0) {
532
+ return void 0;
533
+ }
534
+ try {
535
+ return typeof tag === "string" ? tag : String(tag);
536
+ } catch {
537
+ return void 0;
538
+ }
539
+ }
540
+ };
347
541
 
348
542
  // ../../node_modules/@probe.gl/log/dist/utils/local-storage.js
349
543
  function getStorage(type) {
@@ -462,13 +656,6 @@ var __exports__ = (() => {
462
656
  }
463
657
  }
464
658
 
465
- // ../../node_modules/@probe.gl/log/dist/utils/assert.js
466
- function assert(condition, message) {
467
- if (!condition) {
468
- throw new Error(message || "Assertion failed");
469
- }
470
- }
471
-
472
659
  // ../../node_modules/@probe.gl/log/dist/utils/hi-res-timestamp.js
473
660
  function getHiResTimestamp2() {
474
661
  let timestamp;
@@ -483,7 +670,7 @@ var __exports__ = (() => {
483
670
  return timestamp;
484
671
  }
485
672
 
486
- // ../../node_modules/@probe.gl/log/dist/log.js
673
+ // ../../node_modules/@probe.gl/log/dist/loggers/probe-log.js
487
674
  var originalConsole = {
488
675
  debug: isBrowser() ? console.debug || console.log : console.log,
489
676
  log: console.log,
@@ -495,12 +682,9 @@ var __exports__ = (() => {
495
682
  enabled: true,
496
683
  level: 0
497
684
  };
498
- function noop() {
499
- }
500
- var cache = {};
501
- var ONCE = { once: true };
502
- var Log = class {
685
+ var ProbeLog = class extends BaseLog {
503
686
  constructor({ id } = { id: "" }) {
687
+ super({ level: 0 });
504
688
  this.VERSION = VERSION;
505
689
  this._startTs = getHiResTimestamp2();
506
690
  this._deltaTs = getHiResTimestamp2();
@@ -508,22 +692,16 @@ var __exports__ = (() => {
508
692
  this.LOG_THROTTLE_TIMEOUT = 0;
509
693
  this.id = id;
510
694
  this.userData = {};
511
- this._storage = new LocalStorage(`__probe-${this.id}__`, DEFAULT_LOG_CONFIGURATION);
695
+ this._storage = new LocalStorage(`__probe-${this.id}__`, { [this.id]: DEFAULT_LOG_CONFIGURATION });
512
696
  this.timeStamp(`${this.id} started`);
513
697
  autobind(this);
514
698
  Object.seal(this);
515
699
  }
516
- set level(newLevel) {
517
- this.setLevel(newLevel);
518
- }
519
- get level() {
520
- return this.getLevel();
521
- }
522
700
  isEnabled() {
523
- return this._storage.config.enabled;
701
+ return this._getConfiguration().enabled;
524
702
  }
525
703
  getLevel() {
526
- return this._storage.config.level;
704
+ return this._getConfiguration().level;
527
705
  }
528
706
  /** @return milliseconds, with fractions */
529
707
  getTotal() {
@@ -547,20 +725,20 @@ var __exports__ = (() => {
547
725
  }
548
726
  // Configure
549
727
  enable(enabled = true) {
550
- this._storage.setConfiguration({ enabled });
728
+ this._updateConfiguration({ enabled });
551
729
  return this;
552
730
  }
553
731
  setLevel(level) {
554
- this._storage.setConfiguration({ level });
732
+ this._updateConfiguration({ level });
555
733
  return this;
556
734
  }
557
735
  /** return the current status of the setting */
558
736
  get(setting) {
559
- return this._storage.config[setting];
737
+ return this._getConfiguration()[setting];
560
738
  }
561
739
  // update the status of the setting
562
740
  set(setting, value) {
563
- this._storage.setConfiguration({ [setting]: value });
741
+ this._updateConfiguration({ [setting]: value });
564
742
  }
565
743
  /** Logs the current settings as a table */
566
744
  settings() {
@@ -576,11 +754,16 @@ var __exports__ = (() => {
576
754
  throw new Error(message || "Assertion failed");
577
755
  }
578
756
  }
579
- warn(message) {
580
- return this._getLogFunction(0, message, originalConsole.warn, arguments, ONCE);
757
+ warn(message, ...args) {
758
+ return this._log("warn", 0, message, args, {
759
+ method: originalConsole.warn,
760
+ once: true
761
+ });
581
762
  }
582
- error(message) {
583
- return this._getLogFunction(0, message, originalConsole.error, arguments);
763
+ error(message, ...args) {
764
+ return this._log("error", 0, message, args, {
765
+ method: originalConsole.error
766
+ });
584
767
  }
585
768
  /** Print a deprecation warning */
586
769
  deprecated(oldUsage, newUsage) {
@@ -590,50 +773,63 @@ var __exports__ = (() => {
590
773
  removed(oldUsage, newUsage) {
591
774
  return this.error(`\`${oldUsage}\` has been removed. Use \`${newUsage}\` instead`);
592
775
  }
593
- probe(logLevel, message) {
594
- return this._getLogFunction(logLevel, message, originalConsole.log, arguments, {
776
+ probe(logLevel, message, ...args) {
777
+ return this._log("log", logLevel, message, args, {
778
+ method: originalConsole.log,
595
779
  time: true,
596
780
  once: true
597
781
  });
598
782
  }
599
- log(logLevel, message) {
600
- return this._getLogFunction(logLevel, message, originalConsole.debug, arguments);
783
+ log(logLevel, message, ...args) {
784
+ return this._log("log", logLevel, message, args, {
785
+ method: originalConsole.debug
786
+ });
601
787
  }
602
- info(logLevel, message) {
603
- return this._getLogFunction(logLevel, message, console.info, arguments);
788
+ info(logLevel, message, ...args) {
789
+ return this._log("info", logLevel, message, args, { method: console.info });
604
790
  }
605
- once(logLevel, message) {
606
- return this._getLogFunction(logLevel, message, originalConsole.debug || originalConsole.info, arguments, ONCE);
791
+ once(logLevel, message, ...args) {
792
+ return this._log("once", logLevel, message, args, {
793
+ method: originalConsole.debug || originalConsole.info,
794
+ once: true
795
+ });
607
796
  }
608
797
  /** Logs an object as a table */
609
798
  table(logLevel, table, columns) {
610
799
  if (table) {
611
- return this._getLogFunction(logLevel, table, console.table || noop, columns && [columns], {
800
+ return this._log("table", logLevel, table, columns && [columns] || [], {
801
+ method: console.table || noop,
612
802
  tag: getTableHeader(table)
613
803
  });
614
804
  }
615
805
  return noop;
616
806
  }
617
807
  time(logLevel, message) {
618
- return this._getLogFunction(logLevel, message, console.time ? console.time : console.info);
808
+ return this._log("time", logLevel, message, [], {
809
+ method: console.time ? console.time : console.info
810
+ });
619
811
  }
620
812
  timeEnd(logLevel, message) {
621
- return this._getLogFunction(logLevel, message, console.timeEnd ? console.timeEnd : console.info);
813
+ return this._log("time", logLevel, message, [], {
814
+ method: console.timeEnd ? console.timeEnd : console.info
815
+ });
622
816
  }
623
817
  timeStamp(logLevel, message) {
624
- return this._getLogFunction(logLevel, message, console.timeStamp || noop);
818
+ return this._log("time", logLevel, message, [], {
819
+ method: console.timeStamp || noop
820
+ });
625
821
  }
626
822
  group(logLevel, message, opts = { collapsed: false }) {
627
- const options = normalizeArguments({ logLevel, message, opts });
628
- const { collapsed } = opts;
629
- options.method = (collapsed ? console.groupCollapsed : console.group) || console.info;
630
- return this._getLogFunction(options);
823
+ const method = (opts.collapsed ? console.groupCollapsed : console.group) || console.info;
824
+ return this._log("group", logLevel, message, [], { method });
631
825
  }
632
826
  groupCollapsed(logLevel, message, opts = {}) {
633
827
  return this.group(logLevel, message, Object.assign({}, opts, { collapsed: true }));
634
828
  }
635
829
  groupEnd(logLevel) {
636
- return this._getLogFunction(logLevel, "", console.groupEnd || noop);
830
+ return this._log("groupEnd", logLevel, "", [], {
831
+ method: console.groupEnd || noop
832
+ });
637
833
  }
638
834
  // EXPERIMENTAL
639
835
  withGroup(logLevel, message, func) {
@@ -649,78 +845,34 @@ var __exports__ = (() => {
649
845
  console.trace();
650
846
  }
651
847
  }
652
- // PRIVATE METHODS
653
- /** Deduces log level from a variety of arguments */
654
848
  _shouldLog(logLevel) {
655
- return this.isEnabled() && this.getLevel() >= normalizeLogLevel(logLevel);
656
- }
657
- _getLogFunction(logLevel, message, method, args, opts) {
658
- if (this._shouldLog(logLevel)) {
659
- opts = normalizeArguments({ logLevel, message, args, opts });
660
- method = method || opts.method;
661
- assert(method);
662
- opts.total = this.getTotal();
663
- opts.delta = this.getDelta();
664
- this._deltaTs = getHiResTimestamp2();
665
- const tag = opts.tag || opts.message;
666
- if (opts.once && tag) {
667
- if (!cache[tag]) {
668
- cache[tag] = getHiResTimestamp2();
669
- } else {
670
- return noop;
671
- }
672
- }
673
- message = decorateMessage(this.id, opts.message, opts);
674
- return method.bind(console, message, ...opts.args);
675
- }
676
- return noop;
677
- }
678
- };
679
- Log.VERSION = VERSION;
680
- function normalizeLogLevel(logLevel) {
681
- if (!logLevel) {
682
- return 0;
849
+ return this.isEnabled() && super._shouldLog(logLevel);
683
850
  }
684
- let resolvedLevel;
685
- switch (typeof logLevel) {
686
- case "number":
687
- resolvedLevel = logLevel;
688
- break;
689
- case "object":
690
- resolvedLevel = logLevel.logLevel || logLevel.priority || 0;
691
- break;
692
- default:
693
- return 0;
694
- }
695
- assert(Number.isFinite(resolvedLevel) && resolvedLevel >= 0);
696
- return resolvedLevel;
697
- }
698
- function normalizeArguments(opts) {
699
- const { logLevel, message } = opts;
700
- opts.logLevel = normalizeLogLevel(logLevel);
701
- const args = opts.args ? Array.from(opts.args) : [];
702
- while (args.length && args.shift() !== message) {
851
+ _emit(_type, normalized) {
852
+ const method = normalized.method;
853
+ assert(method);
854
+ normalized.total = this.getTotal();
855
+ normalized.delta = this.getDelta();
856
+ this._deltaTs = getHiResTimestamp2();
857
+ const message = decorateMessage(this.id, normalized.message, normalized);
858
+ return method.bind(console, message, ...normalized.args);
703
859
  }
704
- switch (typeof logLevel) {
705
- case "string":
706
- case "function":
707
- if (message !== void 0) {
708
- args.unshift(message);
709
- }
710
- opts.message = logLevel;
711
- break;
712
- case "object":
713
- Object.assign(opts, logLevel);
714
- break;
715
- default:
860
+ _getConfiguration() {
861
+ if (!this._storage.config[this.id]) {
862
+ this._updateConfiguration(DEFAULT_LOG_CONFIGURATION);
863
+ }
864
+ return this._storage.config[this.id];
716
865
  }
717
- if (typeof opts.message === "function") {
718
- opts.message = opts.message();
866
+ _updateConfiguration(configuration) {
867
+ const currentConfiguration = this._storage.config[this.id] || {
868
+ ...DEFAULT_LOG_CONFIGURATION
869
+ };
870
+ this._storage.setConfiguration({
871
+ [this.id]: { ...currentConfiguration, ...configuration }
872
+ });
719
873
  }
720
- const messageType = typeof opts.message;
721
- assert(messageType === "string" || messageType === "object");
722
- return Object.assign(opts, { args }, opts.opts);
723
- }
874
+ };
875
+ ProbeLog.VERSION = VERSION;
724
876
  function decorateMessage(id, message, opts) {
725
877
  if (typeof message === "string") {
726
878
  const time = opts.time ? leftPad(formatTime(opts.total)) : "";
@@ -742,10 +894,10 @@ var __exports__ = (() => {
742
894
  globalThis.probe = {};
743
895
 
744
896
  // ../../node_modules/@probe.gl/log/dist/index.js
745
- var dist_default = new Log({ id: "@probe.gl/log" });
897
+ var dist_default = new ProbeLog({ id: "@probe.gl/log" });
746
898
 
747
899
  // ../core/src/utils/log.ts
748
- var log = new Log({ id: "luma.gl" });
900
+ var log = new ProbeLog({ id: "luma.gl" });
749
901
 
750
902
  // ../core/src/utils/uid.ts
751
903
  var uidCounters = {};
@@ -756,6 +908,57 @@ var __exports__ = (() => {
756
908
  }
757
909
 
758
910
  // ../core/src/adapter/resources/resource.ts
911
+ var CPU_HOTSPOT_PROFILER_MODULE = "cpu-hotspot-profiler";
912
+ var RESOURCE_COUNTS_STATS = "GPU Resource Counts";
913
+ var LEGACY_RESOURCE_COUNTS_STATS = "Resource Counts";
914
+ var GPU_TIME_AND_MEMORY_STATS2 = "GPU Time and Memory";
915
+ var BASE_RESOURCE_COUNT_ORDER = [
916
+ "Resources",
917
+ "Buffers",
918
+ "Textures",
919
+ "Samplers",
920
+ "TextureViews",
921
+ "Framebuffers",
922
+ "QuerySets",
923
+ "Shaders",
924
+ "RenderPipelines",
925
+ "ComputePipelines",
926
+ "PipelineLayouts",
927
+ "VertexArrays",
928
+ "RenderPasss",
929
+ "ComputePasss",
930
+ "CommandEncoders",
931
+ "CommandBuffers"
932
+ ];
933
+ var WEBGL_RESOURCE_COUNT_ORDER = [
934
+ "Resources",
935
+ "Buffers",
936
+ "Textures",
937
+ "Samplers",
938
+ "TextureViews",
939
+ "Framebuffers",
940
+ "QuerySets",
941
+ "Shaders",
942
+ "RenderPipelines",
943
+ "SharedRenderPipelines",
944
+ "ComputePipelines",
945
+ "PipelineLayouts",
946
+ "VertexArrays",
947
+ "RenderPasss",
948
+ "ComputePasss",
949
+ "CommandEncoders",
950
+ "CommandBuffers"
951
+ ];
952
+ var BASE_RESOURCE_COUNT_STAT_ORDER = BASE_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
953
+ `${resourceType} Created`,
954
+ `${resourceType} Active`
955
+ ]);
956
+ var WEBGL_RESOURCE_COUNT_STAT_ORDER = WEBGL_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
957
+ `${resourceType} Created`,
958
+ `${resourceType} Active`
959
+ ]);
960
+ var ORDERED_STATS_CACHE2 = /* @__PURE__ */ new WeakMap();
961
+ var ORDERED_STAT_NAME_SET_CACHE2 = /* @__PURE__ */ new WeakMap();
759
962
  var Resource = class {
760
963
  toString() {
761
964
  return `${this[Symbol.toStringTag] || this.constructor.name}:"${this.id}"`;
@@ -772,6 +975,8 @@ var __exports__ = (() => {
772
975
  destroyed = false;
773
976
  /** For resources that allocate GPU memory */
774
977
  allocatedBytes = 0;
978
+ /** Stats bucket currently holding the tracked allocation */
979
+ allocatedBytesName = null;
775
980
  /** Attached resources will be destroyed when this resource is destroyed. Tracks auto-created "sub" resources. */
776
981
  _attachedResources = /* @__PURE__ */ new Set();
777
982
  /**
@@ -793,6 +998,9 @@ var __exports__ = (() => {
793
998
  * destroy can be called on any resource to release it before it is garbage collected.
794
999
  */
795
1000
  destroy() {
1001
+ if (this.destroyed) {
1002
+ return;
1003
+ }
796
1004
  this.destroyResource();
797
1005
  }
798
1006
  /** @deprecated Use destroy() */
@@ -831,7 +1039,7 @@ var __exports__ = (() => {
831
1039
  }
832
1040
  /** Destroy all owned resources. Make sure the resources are no longer needed before calling. */
833
1041
  destroyAttachedResources() {
834
- for (const resource of Object.values(this._attachedResources)) {
1042
+ for (const resource of this._attachedResources) {
835
1043
  resource.destroy();
836
1044
  }
837
1045
  this._attachedResources = /* @__PURE__ */ new Set();
@@ -839,37 +1047,107 @@ var __exports__ = (() => {
839
1047
  // PROTECTED METHODS
840
1048
  /** Perform all destroy steps. Can be called by derived resources when overriding destroy() */
841
1049
  destroyResource() {
1050
+ if (this.destroyed) {
1051
+ return;
1052
+ }
842
1053
  this.destroyAttachedResources();
843
1054
  this.removeStats();
844
1055
  this.destroyed = true;
845
1056
  }
846
1057
  /** Called by .destroy() to track object destruction. Subclass must call if overriding destroy() */
847
1058
  removeStats() {
848
- const stats = this._device.statsManager.getStats("Resource Counts");
849
- const name2 = this[Symbol.toStringTag];
850
- stats.get(`${name2}s Active`).decrementCount();
1059
+ const profiler = getCpuHotspotProfiler(this._device);
1060
+ const startTime = profiler ? getTimestamp() : 0;
1061
+ const statsObjects = [
1062
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1063
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1064
+ ];
1065
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1066
+ for (const stats of statsObjects) {
1067
+ initializeStats2(stats, orderedStatNames);
1068
+ }
1069
+ const name2 = this.getStatsName();
1070
+ for (const stats of statsObjects) {
1071
+ stats.get("Resources Active").decrementCount();
1072
+ stats.get(`${name2}s Active`).decrementCount();
1073
+ }
1074
+ if (profiler) {
1075
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1076
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1077
+ }
851
1078
  }
852
1079
  /** Called by subclass to track memory allocations */
853
- trackAllocatedMemory(bytes, name2 = this[Symbol.toStringTag]) {
854
- const stats = this._device.statsManager.getStats("Resource Counts");
1080
+ trackAllocatedMemory(bytes, name2 = this.getStatsName()) {
1081
+ const profiler = getCpuHotspotProfiler(this._device);
1082
+ const startTime = profiler ? getTimestamp() : 0;
1083
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
1084
+ if (this.allocatedBytes > 0 && this.allocatedBytesName) {
1085
+ stats.get("GPU Memory").subtractCount(this.allocatedBytes);
1086
+ stats.get(`${this.allocatedBytesName} Memory`).subtractCount(this.allocatedBytes);
1087
+ }
855
1088
  stats.get("GPU Memory").addCount(bytes);
856
1089
  stats.get(`${name2} Memory`).addCount(bytes);
1090
+ if (profiler) {
1091
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1092
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1093
+ }
857
1094
  this.allocatedBytes = bytes;
1095
+ this.allocatedBytesName = name2;
1096
+ }
1097
+ /** Called by subclass to track handle-backed memory allocations separately from owned allocations */
1098
+ trackReferencedMemory(bytes, name2 = this.getStatsName()) {
1099
+ this.trackAllocatedMemory(bytes, `Referenced ${name2}`);
858
1100
  }
859
1101
  /** Called by subclass to track memory deallocations */
860
- trackDeallocatedMemory(name2 = this[Symbol.toStringTag]) {
861
- const stats = this._device.statsManager.getStats("Resource Counts");
1102
+ trackDeallocatedMemory(name2 = this.getStatsName()) {
1103
+ if (this.allocatedBytes === 0) {
1104
+ this.allocatedBytesName = null;
1105
+ return;
1106
+ }
1107
+ const profiler = getCpuHotspotProfiler(this._device);
1108
+ const startTime = profiler ? getTimestamp() : 0;
1109
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
862
1110
  stats.get("GPU Memory").subtractCount(this.allocatedBytes);
863
- stats.get(`${name2} Memory`).subtractCount(this.allocatedBytes);
1111
+ stats.get(`${this.allocatedBytesName || name2} Memory`).subtractCount(this.allocatedBytes);
1112
+ if (profiler) {
1113
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1114
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1115
+ }
864
1116
  this.allocatedBytes = 0;
1117
+ this.allocatedBytesName = null;
1118
+ }
1119
+ /** Called by subclass to deallocate handle-backed memory tracked via trackReferencedMemory() */
1120
+ trackDeallocatedReferencedMemory(name2 = this.getStatsName()) {
1121
+ this.trackDeallocatedMemory(`Referenced ${name2}`);
865
1122
  }
866
1123
  /** Called by resource constructor to track object creation */
867
1124
  addStats() {
868
- const stats = this._device.statsManager.getStats("Resource Counts");
869
- const name2 = this[Symbol.toStringTag];
870
- stats.get("Resources Created").incrementCount();
871
- stats.get(`${name2}s Created`).incrementCount();
872
- stats.get(`${name2}s Active`).incrementCount();
1125
+ const name2 = this.getStatsName();
1126
+ const profiler = getCpuHotspotProfiler(this._device);
1127
+ const startTime = profiler ? getTimestamp() : 0;
1128
+ const statsObjects = [
1129
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1130
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1131
+ ];
1132
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1133
+ for (const stats of statsObjects) {
1134
+ initializeStats2(stats, orderedStatNames);
1135
+ }
1136
+ for (const stats of statsObjects) {
1137
+ stats.get("Resources Created").incrementCount();
1138
+ stats.get("Resources Active").incrementCount();
1139
+ stats.get(`${name2}s Created`).incrementCount();
1140
+ stats.get(`${name2}s Active`).incrementCount();
1141
+ }
1142
+ if (profiler) {
1143
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1144
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1145
+ }
1146
+ recordTransientCanvasResourceCreate(this._device, name2);
1147
+ }
1148
+ /** Canonical resource name used for stats buckets. */
1149
+ getStatsName() {
1150
+ return getCanonicalResourceName(this);
873
1151
  }
874
1152
  };
875
1153
  /** Default properties for resource */
@@ -887,6 +1165,96 @@ var __exports__ = (() => {
887
1165
  }
888
1166
  return mergedProps;
889
1167
  }
1168
+ function initializeStats2(stats, orderedStatNames) {
1169
+ const statsMap = stats.stats;
1170
+ let addedOrderedStat = false;
1171
+ for (const statName of orderedStatNames) {
1172
+ if (!statsMap[statName]) {
1173
+ stats.get(statName);
1174
+ addedOrderedStat = true;
1175
+ }
1176
+ }
1177
+ const statCount = Object.keys(statsMap).length;
1178
+ const cachedStats = ORDERED_STATS_CACHE2.get(stats);
1179
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
1180
+ return;
1181
+ }
1182
+ const reorderedStats = {};
1183
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE2.get(orderedStatNames);
1184
+ if (!orderedStatNamesSet) {
1185
+ orderedStatNamesSet = new Set(orderedStatNames);
1186
+ ORDERED_STAT_NAME_SET_CACHE2.set(orderedStatNames, orderedStatNamesSet);
1187
+ }
1188
+ for (const statName of orderedStatNames) {
1189
+ if (statsMap[statName]) {
1190
+ reorderedStats[statName] = statsMap[statName];
1191
+ }
1192
+ }
1193
+ for (const [statName, stat] of Object.entries(statsMap)) {
1194
+ if (!orderedStatNamesSet.has(statName)) {
1195
+ reorderedStats[statName] = stat;
1196
+ }
1197
+ }
1198
+ for (const statName of Object.keys(statsMap)) {
1199
+ delete statsMap[statName];
1200
+ }
1201
+ Object.assign(statsMap, reorderedStats);
1202
+ ORDERED_STATS_CACHE2.set(stats, { orderedStatNames, statCount });
1203
+ }
1204
+ function getResourceCountStatOrder(device) {
1205
+ return device.type === "webgl" ? WEBGL_RESOURCE_COUNT_STAT_ORDER : BASE_RESOURCE_COUNT_STAT_ORDER;
1206
+ }
1207
+ function getCpuHotspotProfiler(device) {
1208
+ const profiler = device.userData[CPU_HOTSPOT_PROFILER_MODULE];
1209
+ return profiler?.enabled ? profiler : null;
1210
+ }
1211
+ function getTimestamp() {
1212
+ return globalThis.performance?.now?.() ?? Date.now();
1213
+ }
1214
+ function recordTransientCanvasResourceCreate(device, name2) {
1215
+ const profiler = getCpuHotspotProfiler(device);
1216
+ if (!profiler || !profiler.activeDefaultFramebufferAcquireDepth) {
1217
+ return;
1218
+ }
1219
+ profiler.transientCanvasResourceCreates = (profiler.transientCanvasResourceCreates || 0) + 1;
1220
+ switch (name2) {
1221
+ case "Texture":
1222
+ profiler.transientCanvasTextureCreates = (profiler.transientCanvasTextureCreates || 0) + 1;
1223
+ break;
1224
+ case "TextureView":
1225
+ profiler.transientCanvasTextureViewCreates = (profiler.transientCanvasTextureViewCreates || 0) + 1;
1226
+ break;
1227
+ case "Sampler":
1228
+ profiler.transientCanvasSamplerCreates = (profiler.transientCanvasSamplerCreates || 0) + 1;
1229
+ break;
1230
+ case "Framebuffer":
1231
+ profiler.transientCanvasFramebufferCreates = (profiler.transientCanvasFramebufferCreates || 0) + 1;
1232
+ break;
1233
+ default:
1234
+ break;
1235
+ }
1236
+ }
1237
+ function getCanonicalResourceName(resource) {
1238
+ let prototype = Object.getPrototypeOf(resource);
1239
+ while (prototype) {
1240
+ const parentPrototype = Object.getPrototypeOf(prototype);
1241
+ if (!parentPrototype || parentPrototype === Resource.prototype) {
1242
+ return getPrototypeToStringTag(prototype) || resource[Symbol.toStringTag] || resource.constructor.name;
1243
+ }
1244
+ prototype = parentPrototype;
1245
+ }
1246
+ return resource[Symbol.toStringTag] || resource.constructor.name;
1247
+ }
1248
+ function getPrototypeToStringTag(prototype) {
1249
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, Symbol.toStringTag);
1250
+ if (typeof descriptor?.get === "function") {
1251
+ return descriptor.get.call(prototype);
1252
+ }
1253
+ if (typeof descriptor?.value === "string") {
1254
+ return descriptor.value;
1255
+ }
1256
+ return null;
1257
+ }
890
1258
 
891
1259
  // ../core/src/adapter/resources/buffer.ts
892
1260
  var _Buffer = class extends Resource {
@@ -926,18 +1294,26 @@ var __exports__ = (() => {
926
1294
  /** A partial CPU-side copy of the data in this buffer, for debugging purposes */
927
1295
  debugData = new ArrayBuffer(0);
928
1296
  /** This doesn't handle partial non-zero offset updates correctly */
929
- _setDebugData(data, byteOffset, byteLength) {
930
- const arrayBuffer2 = ArrayBuffer.isView(data) ? data.buffer : data;
1297
+ _setDebugData(data, _byteOffset, byteLength) {
1298
+ let arrayBufferView = null;
1299
+ let arrayBuffer2;
1300
+ if (ArrayBuffer.isView(data)) {
1301
+ arrayBufferView = data;
1302
+ arrayBuffer2 = data.buffer;
1303
+ } else {
1304
+ arrayBuffer2 = data;
1305
+ }
931
1306
  const debugDataLength = Math.min(
932
1307
  data ? data.byteLength : byteLength,
933
1308
  _Buffer.DEBUG_DATA_MAX_LENGTH
934
1309
  );
935
1310
  if (arrayBuffer2 === null) {
936
1311
  this.debugData = new ArrayBuffer(debugDataLength);
937
- } else if (byteOffset === 0 && byteLength === arrayBuffer2.byteLength) {
938
- this.debugData = arrayBuffer2.slice(0, debugDataLength);
939
1312
  } else {
940
- this.debugData = arrayBuffer2.slice(byteOffset, byteOffset + debugDataLength);
1313
+ const sourceByteOffset = Math.min(arrayBufferView?.byteOffset || 0, arrayBuffer2.byteLength);
1314
+ const availableByteLength = Math.max(0, arrayBuffer2.byteLength - sourceByteOffset);
1315
+ const copyByteLength = Math.min(debugDataLength, availableByteLength);
1316
+ this.debugData = new Uint8Array(arrayBuffer2, sourceByteOffset, copyByteLength).slice().buffer;
941
1317
  }
942
1318
  }
943
1319
  };
@@ -1133,6 +1509,7 @@ var __exports__ = (() => {
1133
1509
  var float16_renderable = "float16-renderable-webgl";
1134
1510
  var rgb9e5ufloat_renderable = "rgb9e5ufloat-renderable-webgl";
1135
1511
  var snorm8_renderable = "snorm8-renderable-webgl";
1512
+ var norm16_webgl = "norm16-webgl";
1136
1513
  var norm16_renderable = "norm16-renderable-webgl";
1137
1514
  var snorm16_renderable = "snorm16-renderable-webgl";
1138
1515
  var float32_filterable = "float32-filterable";
@@ -1166,16 +1543,16 @@ var __exports__ = (() => {
1166
1543
  "rgba8sint": {},
1167
1544
  "bgra8unorm": {},
1168
1545
  "bgra8unorm-srgb": {},
1169
- "r16unorm": { f: norm16_renderable },
1170
- "rg16unorm": { render: norm16_renderable },
1171
- "rgb16unorm-webgl": { f: norm16_renderable },
1546
+ "r16unorm": { f: norm16_webgl, render: norm16_renderable },
1547
+ "rg16unorm": { f: norm16_webgl, render: norm16_renderable },
1548
+ "rgb16unorm-webgl": { f: norm16_webgl, render: false },
1172
1549
  // rgb not renderable
1173
- "rgba16unorm": { render: norm16_renderable },
1174
- "r16snorm": { f: snorm16_renderable },
1175
- "rg16snorm": { render: snorm16_renderable },
1176
- "rgb16snorm-webgl": { f: norm16_renderable },
1550
+ "rgba16unorm": { f: norm16_webgl, render: norm16_renderable },
1551
+ "r16snorm": { f: norm16_webgl, render: snorm16_renderable },
1552
+ "rg16snorm": { f: norm16_webgl, render: snorm16_renderable },
1553
+ "rgb16snorm-webgl": { f: norm16_webgl, render: false },
1177
1554
  // rgb not renderable
1178
- "rgba16snorm": { render: snorm16_renderable },
1555
+ "rgba16snorm": { f: norm16_webgl, render: snorm16_renderable },
1179
1556
  "r16uint": {},
1180
1557
  "rg16uint": {},
1181
1558
  "rgba16uint": {},
@@ -1278,7 +1655,7 @@ var __exports__ = (() => {
1278
1655
  // WEBGL_compressed_texture_pvrtc
1279
1656
  "pvrtc-rgb4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1280
1657
  "pvrtc-rgba4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1281
- "pvrtc-rbg2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1658
+ "pvrtc-rgb2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1282
1659
  "pvrtc-rgba2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1283
1660
  // WEBGL_compressed_texture_etc1
1284
1661
  "etc1-rbg-unorm-webgl": { f: texture_compression_etc1_webgl },
@@ -1345,10 +1722,19 @@ var __exports__ = (() => {
1345
1722
  depth,
1346
1723
  byteAlignment
1347
1724
  }) {
1348
- const { bytesPerPixel } = textureFormatDecoder.getInfo(format);
1349
- const unpaddedBytesPerRow = width * bytesPerPixel;
1725
+ const formatInfo = textureFormatDecoder.getInfo(format);
1726
+ const {
1727
+ bytesPerPixel,
1728
+ bytesPerBlock = bytesPerPixel,
1729
+ blockWidth = 1,
1730
+ blockHeight = 1,
1731
+ compressed = false
1732
+ } = formatInfo;
1733
+ const blockColumns = compressed ? Math.ceil(width / blockWidth) : width;
1734
+ const blockRows = compressed ? Math.ceil(height / blockHeight) : height;
1735
+ const unpaddedBytesPerRow = blockColumns * bytesPerBlock;
1350
1736
  const bytesPerRow = Math.ceil(unpaddedBytesPerRow / byteAlignment) * byteAlignment;
1351
- const rowsPerImage = height;
1737
+ const rowsPerImage = blockRows;
1352
1738
  const byteLength = bytesPerRow * rowsPerImage * depth;
1353
1739
  return {
1354
1740
  bytesPerPixel,
@@ -1374,7 +1760,8 @@ var __exports__ = (() => {
1374
1760
  const isSigned = formatInfo?.signed;
1375
1761
  const isInteger = formatInfo?.integer;
1376
1762
  const isWebGLSpecific = formatInfo?.webgl;
1377
- formatCapabilities.render &&= !isSigned;
1763
+ const isCompressed = Boolean(formatInfo?.compressed);
1764
+ formatCapabilities.render &&= !isDepthStencil && !isCompressed;
1378
1765
  formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1379
1766
  return formatCapabilities;
1380
1767
  }
@@ -1386,6 +1773,7 @@ var __exports__ = (() => {
1386
1773
  formatInfo.bytesPerPixel = 1;
1387
1774
  formatInfo.srgb = false;
1388
1775
  formatInfo.compressed = true;
1776
+ formatInfo.bytesPerBlock = getCompressedTextureBlockByteLength(format);
1389
1777
  const blockSize = getCompressedTextureBlockSize(format);
1390
1778
  if (blockSize) {
1391
1779
  formatInfo.blockWidth = blockSize.blockWidth;
@@ -1398,7 +1786,7 @@ var __exports__ = (() => {
1398
1786
  const dataType = `${type}${length}`;
1399
1787
  const decodedType = getDataTypeInfo(dataType);
1400
1788
  const bits = decodedType.byteLength * 8;
1401
- const components = channels.length;
1789
+ const components = channels?.length ?? 1;
1402
1790
  const bitsPerChannel = [
1403
1791
  bits,
1404
1792
  components >= 2 ? bits : 0,
@@ -1415,7 +1803,7 @@ var __exports__ = (() => {
1415
1803
  signed: decodedType.signed,
1416
1804
  normalized: decodedType.normalized,
1417
1805
  bitsPerChannel,
1418
- bytesPerPixel: decodedType.byteLength * channels.length,
1806
+ bytesPerPixel: decodedType.byteLength * components,
1419
1807
  packed: formatInfo.packed,
1420
1808
  srgb: formatInfo.srgb
1421
1809
  };
@@ -1471,8 +1859,29 @@ var __exports__ = (() => {
1471
1859
  const [, blockWidth, blockHeight] = matches;
1472
1860
  return { blockWidth: Number(blockWidth), blockHeight: Number(blockHeight) };
1473
1861
  }
1862
+ if (format.startsWith("bc") || format.startsWith("etc1") || format.startsWith("etc2") || format.startsWith("eac") || format.startsWith("atc")) {
1863
+ return { blockWidth: 4, blockHeight: 4 };
1864
+ }
1865
+ if (format.startsWith("pvrtc-rgb4") || format.startsWith("pvrtc-rgba4")) {
1866
+ return { blockWidth: 4, blockHeight: 4 };
1867
+ }
1868
+ if (format.startsWith("pvrtc-rgb2") || format.startsWith("pvrtc-rgba2")) {
1869
+ return { blockWidth: 8, blockHeight: 4 };
1870
+ }
1474
1871
  return null;
1475
1872
  }
1873
+ function getCompressedTextureBlockByteLength(format) {
1874
+ if (format.startsWith("bc1") || format.startsWith("bc4") || format.startsWith("etc1") || format.startsWith("etc2-rgb8") || format.startsWith("etc2-rgb8a1") || format.startsWith("eac-r11") || format === "atc-rgb-unorm-webgl") {
1875
+ return 8;
1876
+ }
1877
+ if (format.startsWith("bc2") || format.startsWith("bc3") || format.startsWith("bc5") || format.startsWith("bc6h") || format.startsWith("bc7") || format.startsWith("etc2-rgba8") || format.startsWith("eac-rg11") || format.startsWith("astc") || format === "atc-rgba-unorm-webgl" || format === "atc-rgbai-unorm-webgl") {
1878
+ return 16;
1879
+ }
1880
+ if (format.startsWith("pvrtc")) {
1881
+ return 8;
1882
+ }
1883
+ return 16;
1884
+ }
1476
1885
 
1477
1886
  // ../core/src/image-utils/image-types.ts
1478
1887
  function isExternalImage(data) {
@@ -1533,6 +1942,8 @@ var __exports__ = (() => {
1533
1942
  /** Used by other luma.gl modules to store data on the device */
1534
1943
  _moduleData = {};
1535
1944
  _textureCaps = {};
1945
+ /** Internal timestamp query set used when GPU timing collection is enabled for this device. */
1946
+ _debugGPUTimeQuery = null;
1536
1947
  constructor(props) {
1537
1948
  this.props = { ..._Device.defaultProps, ...props };
1538
1949
  this.id = this.props.id || uid(this[Symbol.toStringTag].toLowerCase());
@@ -1586,6 +1997,16 @@ var __exports__ = (() => {
1586
1997
  isTextureFormatCompressed(format) {
1587
1998
  return textureFormatDecoder.isCompressed(format);
1588
1999
  }
2000
+ /** Returns the compressed texture formats that can be created and sampled on this device */
2001
+ getSupportedCompressedTextureFormats() {
2002
+ const supportedFormats = [];
2003
+ for (const format of Object.keys(getTextureFormatTable())) {
2004
+ if (this.isTextureFormatCompressed(format) && this.isTextureFormatSupported(format)) {
2005
+ supportedFormats.push(format);
2006
+ }
2007
+ }
2008
+ return supportedFormats;
2009
+ }
1589
2010
  // DEBUG METHODS
1590
2011
  pushDebugGroup(groupLabel) {
1591
2012
  this.commandEncoder.pushDebugGroup(groupLabel);
@@ -1668,6 +2089,70 @@ or create a device with the 'debug: true' prop.`;
1668
2089
  beginComputePass(props) {
1669
2090
  return this.commandEncoder.beginComputePass(props);
1670
2091
  }
2092
+ /**
2093
+ * Generate mipmaps for a WebGPU texture.
2094
+ * WebGPU textures must be created up front with the required mip count, usage flags, and a format that supports the chosen generation path.
2095
+ * WebGL uses `Texture.generateMipmapsWebGL()` directly because the backend manages mip generation on the texture object itself.
2096
+ */
2097
+ generateMipmapsWebGPU(_texture) {
2098
+ throw new Error("not implemented");
2099
+ }
2100
+ /** Internal helper for creating a shareable WebGL render-pipeline implementation. */
2101
+ _createSharedRenderPipelineWebGL(_props) {
2102
+ throw new Error("_createSharedRenderPipelineWebGL() not implemented");
2103
+ }
2104
+ /**
2105
+ * Internal helper that returns `true` when timestamp-query GPU timing should be
2106
+ * collected for this device.
2107
+ */
2108
+ _supportsDebugGPUTime() {
2109
+ return this.features.has("timestamp-query") && Boolean(this.props.debug || this.props.debugGPUTime);
2110
+ }
2111
+ /**
2112
+ * Internal helper that enables device-managed GPU timing collection on the
2113
+ * default command encoder. Reuses the existing query set if timing is already enabled.
2114
+ *
2115
+ * @param queryCount - Number of timestamp slots reserved for profiled passes.
2116
+ * @returns The device-managed timestamp QuerySet, or `null` when timing is not supported or could not be enabled.
2117
+ */
2118
+ _enableDebugGPUTime(queryCount = 256) {
2119
+ if (!this._supportsDebugGPUTime()) {
2120
+ return null;
2121
+ }
2122
+ if (this._debugGPUTimeQuery) {
2123
+ return this._debugGPUTimeQuery;
2124
+ }
2125
+ try {
2126
+ this._debugGPUTimeQuery = this.createQuerySet({ type: "timestamp", count: queryCount });
2127
+ this.commandEncoder = this.createCommandEncoder({
2128
+ id: this.commandEncoder.props.id,
2129
+ timeProfilingQuerySet: this._debugGPUTimeQuery
2130
+ });
2131
+ } catch {
2132
+ this._debugGPUTimeQuery = null;
2133
+ }
2134
+ return this._debugGPUTimeQuery;
2135
+ }
2136
+ /**
2137
+ * Internal helper that disables device-managed GPU timing collection and restores
2138
+ * the default command encoder to an unprofiled state.
2139
+ */
2140
+ _disableDebugGPUTime() {
2141
+ if (!this._debugGPUTimeQuery) {
2142
+ return;
2143
+ }
2144
+ if (this.commandEncoder.getTimeProfilingQuerySet() === this._debugGPUTimeQuery) {
2145
+ this.commandEncoder = this.createCommandEncoder({
2146
+ id: this.commandEncoder.props.id
2147
+ });
2148
+ }
2149
+ this._debugGPUTimeQuery.destroy();
2150
+ this._debugGPUTimeQuery = null;
2151
+ }
2152
+ /** Internal helper that returns `true` when device-managed GPU timing is currently active. */
2153
+ _isDebugGPUTimeEnabled() {
2154
+ return this._debugGPUTimeQuery !== null;
2155
+ }
1671
2156
  // DEPRECATED METHODS
1672
2157
  /** @deprecated Use getDefaultCanvasContext() */
1673
2158
  getCanvasContext() {
@@ -1775,7 +2260,8 @@ or create a device with the 'debug: true' prop.`;
1775
2260
  onVisibilityChange: (context) => log.log(1, `${context} Visibility changed ${context.isVisible}`)(),
1776
2261
  onDevicePixelRatioChange: (context, info) => log.log(1, `${context} DPR changed ${info.oldRatio} => ${context.devicePixelRatio}`)(),
1777
2262
  // Debug flags
1778
- debug: log.get("debug") || void 0,
2263
+ debug: getDefaultDebugValue(),
2264
+ debugGPUTime: false,
1779
2265
  debugShaders: log.get("debug-shaders") || void 0,
1780
2266
  debugFramebuffers: Boolean(log.get("debug-framebuffers")),
1781
2267
  debugFactories: Boolean(log.get("debug-factories")),
@@ -1786,9 +2272,11 @@ or create a device with the 'debug: true' prop.`;
1786
2272
  // Experimental
1787
2273
  _reuseDevices: false,
1788
2274
  _requestMaxLimits: true,
1789
- _cacheShaders: false,
1790
- _cachePipelines: false,
1791
- _cacheDestroyPolicy: "unused",
2275
+ _cacheShaders: true,
2276
+ _destroyShaders: false,
2277
+ _cachePipelines: true,
2278
+ _sharePipelines: true,
2279
+ _destroyPipelines: false,
1792
2280
  // TODO - Change these after confirming things work as expected
1793
2281
  _initializeFeatures: true,
1794
2282
  _disabledFeatures: {
@@ -1797,6 +2285,25 @@ or create a device with the 'debug: true' prop.`;
1797
2285
  // INTERNAL
1798
2286
  _handle: void 0
1799
2287
  });
2288
+ function _getDefaultDebugValue(logDebugValue, nodeEnv) {
2289
+ if (logDebugValue !== void 0 && logDebugValue !== null) {
2290
+ return Boolean(logDebugValue);
2291
+ }
2292
+ if (nodeEnv !== void 0) {
2293
+ return nodeEnv !== "production";
2294
+ }
2295
+ return false;
2296
+ }
2297
+ function getDefaultDebugValue() {
2298
+ return _getDefaultDebugValue(log.get("debug"), getNodeEnv());
2299
+ }
2300
+ function getNodeEnv() {
2301
+ const processObject = globalThis.process;
2302
+ if (!processObject?.env) {
2303
+ return void 0;
2304
+ }
2305
+ return processObject.env["NODE_ENV"];
2306
+ }
1800
2307
 
1801
2308
  // ../core/src/adapter/luma.ts
1802
2309
  var STARTUP_MESSAGE = "set luma.log.level=1 (or higher) to trace rendering";
@@ -1974,6 +2481,100 @@ or create a device with the 'debug: true' prop.`;
1974
2481
  return pageLoadPromise;
1975
2482
  }
1976
2483
 
2484
+ // ../core/src/adapter/canvas-observer.ts
2485
+ var CanvasObserver = class {
2486
+ props;
2487
+ _resizeObserver;
2488
+ _intersectionObserver;
2489
+ _observeDevicePixelRatioTimeout = null;
2490
+ _observeDevicePixelRatioMediaQuery = null;
2491
+ _handleDevicePixelRatioChange = () => this._refreshDevicePixelRatio();
2492
+ _trackPositionInterval = null;
2493
+ _started = false;
2494
+ get started() {
2495
+ return this._started;
2496
+ }
2497
+ constructor(props) {
2498
+ this.props = props;
2499
+ }
2500
+ start() {
2501
+ if (this._started || !this.props.canvas) {
2502
+ return;
2503
+ }
2504
+ this._started = true;
2505
+ this._intersectionObserver ||= new IntersectionObserver(
2506
+ (entries) => this.props.onIntersection(entries)
2507
+ );
2508
+ this._resizeObserver ||= new ResizeObserver((entries) => this.props.onResize(entries));
2509
+ this._intersectionObserver.observe(this.props.canvas);
2510
+ try {
2511
+ this._resizeObserver.observe(this.props.canvas, { box: "device-pixel-content-box" });
2512
+ } catch {
2513
+ this._resizeObserver.observe(this.props.canvas, { box: "content-box" });
2514
+ }
2515
+ this._observeDevicePixelRatioTimeout = setTimeout(() => this._refreshDevicePixelRatio(), 0);
2516
+ if (this.props.trackPosition) {
2517
+ this._trackPosition();
2518
+ }
2519
+ }
2520
+ stop() {
2521
+ if (!this._started) {
2522
+ return;
2523
+ }
2524
+ this._started = false;
2525
+ if (this._observeDevicePixelRatioTimeout) {
2526
+ clearTimeout(this._observeDevicePixelRatioTimeout);
2527
+ this._observeDevicePixelRatioTimeout = null;
2528
+ }
2529
+ if (this._observeDevicePixelRatioMediaQuery) {
2530
+ this._observeDevicePixelRatioMediaQuery.removeEventListener(
2531
+ "change",
2532
+ this._handleDevicePixelRatioChange
2533
+ );
2534
+ this._observeDevicePixelRatioMediaQuery = null;
2535
+ }
2536
+ if (this._trackPositionInterval) {
2537
+ clearInterval(this._trackPositionInterval);
2538
+ this._trackPositionInterval = null;
2539
+ }
2540
+ this._resizeObserver?.disconnect();
2541
+ this._intersectionObserver?.disconnect();
2542
+ }
2543
+ _refreshDevicePixelRatio() {
2544
+ if (!this._started) {
2545
+ return;
2546
+ }
2547
+ this.props.onDevicePixelRatioChange();
2548
+ this._observeDevicePixelRatioMediaQuery?.removeEventListener(
2549
+ "change",
2550
+ this._handleDevicePixelRatioChange
2551
+ );
2552
+ this._observeDevicePixelRatioMediaQuery = matchMedia(
2553
+ `(resolution: ${window.devicePixelRatio}dppx)`
2554
+ );
2555
+ this._observeDevicePixelRatioMediaQuery.addEventListener(
2556
+ "change",
2557
+ this._handleDevicePixelRatioChange,
2558
+ { once: true }
2559
+ );
2560
+ }
2561
+ _trackPosition(intervalMs = 100) {
2562
+ if (this._trackPositionInterval) {
2563
+ return;
2564
+ }
2565
+ this._trackPositionInterval = setInterval(() => {
2566
+ if (!this._started) {
2567
+ if (this._trackPositionInterval) {
2568
+ clearInterval(this._trackPositionInterval);
2569
+ this._trackPositionInterval = null;
2570
+ }
2571
+ } else {
2572
+ this.props.onPositionChange();
2573
+ }
2574
+ }, intervalMs);
2575
+ }
2576
+ };
2577
+
1977
2578
  // ../core/src/utils/promise-utils.ts
1978
2579
  function withResolvers() {
1979
2580
  let resolve;
@@ -1985,8 +2586,21 @@ or create a device with the 'debug: true' prop.`;
1985
2586
  return { promise, resolve, reject };
1986
2587
  }
1987
2588
 
1988
- // ../core/src/adapter/canvas-context.ts
1989
- var _CanvasContext = class {
2589
+ // ../core/src/utils/assert.ts
2590
+ function assert2(condition, message) {
2591
+ if (!condition) {
2592
+ const error = new Error(message ?? "luma.gl assertion failed.");
2593
+ Error.captureStackTrace?.(error, assert2);
2594
+ throw error;
2595
+ }
2596
+ }
2597
+ function assertDefined(value, message) {
2598
+ assert2(value, message);
2599
+ return value;
2600
+ }
2601
+
2602
+ // ../core/src/adapter/canvas-surface.ts
2603
+ var _CanvasSurface = class {
1990
2604
  static isHTMLCanvas(canvas) {
1991
2605
  return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
1992
2606
  }
@@ -2022,10 +2636,7 @@ or create a device with the 'debug: true' prop.`;
2022
2636
  drawingBufferHeight;
2023
2637
  /** Resolves when the canvas is initialized, i.e. when the ResizeObserver has updated the pixel size */
2024
2638
  _initializedResolvers = withResolvers();
2025
- /** ResizeObserver to track canvas size changes */
2026
- _resizeObserver;
2027
- /** IntersectionObserver to track canvas visibility changes */
2028
- _intersectionObserver;
2639
+ _canvasObserver;
2029
2640
  /** Position of the canvas in the document, updated by a timer */
2030
2641
  _position = [0, 0];
2031
2642
  /** Whether this canvas context has been destroyed */
@@ -2036,7 +2647,7 @@ or create a device with the 'debug: true' prop.`;
2036
2647
  return `${this[Symbol.toStringTag]}(${this.id})`;
2037
2648
  }
2038
2649
  constructor(props) {
2039
- this.props = { ..._CanvasContext.defaultProps, ...props };
2650
+ this.props = { ..._CanvasSurface.defaultProps, ...props };
2040
2651
  props = this.props;
2041
2652
  this.initialized = this._initializedResolvers.promise;
2042
2653
  if (!isBrowser()) {
@@ -2048,11 +2659,11 @@ or create a device with the 'debug: true' prop.`;
2048
2659
  } else {
2049
2660
  this.canvas = props.canvas;
2050
2661
  }
2051
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2662
+ if (_CanvasSurface.isHTMLCanvas(this.canvas)) {
2052
2663
  this.id = props.id || this.canvas.id;
2053
2664
  this.type = "html-canvas";
2054
2665
  this.htmlCanvas = this.canvas;
2055
- } else if (_CanvasContext.isOffscreenCanvas(this.canvas)) {
2666
+ } else if (_CanvasSurface.isOffscreenCanvas(this.canvas)) {
2056
2667
  this.id = props.id || "offscreen-canvas";
2057
2668
  this.type = "offscreen-canvas";
2058
2669
  this.offscreenCanvas = this.canvas;
@@ -2068,25 +2679,21 @@ or create a device with the 'debug: true' prop.`;
2068
2679
  this.drawingBufferHeight = this.canvas.height;
2069
2680
  this.devicePixelRatio = globalThis.devicePixelRatio || 1;
2070
2681
  this._position = [0, 0];
2071
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2072
- this._intersectionObserver = new IntersectionObserver(
2073
- (entries) => this._handleIntersection(entries)
2074
- );
2075
- this._intersectionObserver.observe(this.canvas);
2076
- this._resizeObserver = new ResizeObserver((entries) => this._handleResize(entries));
2077
- try {
2078
- this._resizeObserver.observe(this.canvas, { box: "device-pixel-content-box" });
2079
- } catch {
2080
- this._resizeObserver.observe(this.canvas, { box: "content-box" });
2081
- }
2082
- setTimeout(() => this._observeDevicePixelRatio(), 0);
2083
- if (this.props.trackPosition) {
2084
- this._trackPosition();
2085
- }
2086
- }
2682
+ this._canvasObserver = new CanvasObserver({
2683
+ canvas: this.htmlCanvas,
2684
+ trackPosition: this.props.trackPosition,
2685
+ onResize: (entries) => this._handleResize(entries),
2686
+ onIntersection: (entries) => this._handleIntersection(entries),
2687
+ onDevicePixelRatioChange: () => this._observeDevicePixelRatio(),
2688
+ onPositionChange: () => this.updatePosition()
2689
+ });
2087
2690
  }
2088
2691
  destroy() {
2089
- this.destroyed = true;
2692
+ if (!this.destroyed) {
2693
+ this.destroyed = true;
2694
+ this._stopObservers();
2695
+ this.device = null;
2696
+ }
2090
2697
  }
2091
2698
  setProps(props) {
2092
2699
  if ("useDevicePixels" in props) {
@@ -2100,59 +2707,36 @@ or create a device with the 'debug: true' prop.`;
2100
2707
  this._resizeDrawingBufferIfNeeded();
2101
2708
  return this._getCurrentFramebuffer(options);
2102
2709
  }
2103
- // SIZE METHODS
2104
- /**
2105
- * Returns the size covered by the canvas in CSS pixels
2106
- * @note This can be different from the actual device pixel size of a canvas due to DPR scaling, and rounding to integer pixels
2107
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2108
- */
2109
2710
  getCSSSize() {
2110
2711
  return [this.cssWidth, this.cssHeight];
2111
2712
  }
2112
2713
  getPosition() {
2113
2714
  return this._position;
2114
2715
  }
2115
- /**
2116
- * Returns the size covered by the canvas in actual device pixels.
2117
- * @note This can be different from the 'CSS' size of a canvas due to DPR scaling, and rounding to integer pixels
2118
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2119
- */
2120
2716
  getDevicePixelSize() {
2121
2717
  return [this.devicePixelWidth, this.devicePixelHeight];
2122
2718
  }
2123
- /** Get the drawing buffer size (number of pixels GPU is rendering into, can be different from CSS size) */
2124
2719
  getDrawingBufferSize() {
2125
2720
  return [this.drawingBufferWidth, this.drawingBufferHeight];
2126
2721
  }
2127
- /** Returns the biggest allowed framebuffer size. @todo Allow the application to limit this? */
2128
2722
  getMaxDrawingBufferSize() {
2129
2723
  const maxTextureDimension = this.device.limits.maxTextureDimension2D;
2130
2724
  return [maxTextureDimension, maxTextureDimension];
2131
2725
  }
2132
- /**
2133
- * Update the canvas drawing buffer size.
2134
- * @note - Called automatically if props.autoResize is true.
2135
- * @note - Defers update of drawing buffer size until framebuffer is requested to avoid flicker
2136
- * (resizing clears the drawing buffer)!
2137
- */
2138
2726
  setDrawingBufferSize(width, height) {
2139
- this.drawingBufferWidth = Math.floor(width);
2140
- this.drawingBufferHeight = Math.floor(height);
2727
+ width = Math.floor(width);
2728
+ height = Math.floor(height);
2729
+ if (this.drawingBufferWidth === width && this.drawingBufferHeight === height) {
2730
+ return;
2731
+ }
2732
+ this.drawingBufferWidth = width;
2733
+ this.drawingBufferHeight = height;
2141
2734
  this._needsDrawingBufferResize = true;
2142
2735
  }
2143
- /**
2144
- * Returns the current DPR (number of physical pixels per CSS pixel), if props.useDevicePixels is true
2145
- * @note This can be a fractional (non-integer) number, e.g. when the user zooms in the browser.
2146
- * @note This function handles the non-HTML canvas cases
2147
- */
2148
2736
  getDevicePixelRatio() {
2149
- const dpr = typeof window !== "undefined" && window.devicePixelRatio;
2150
- return dpr || 1;
2737
+ const devicePixelRatio2 = typeof window !== "undefined" && window.devicePixelRatio;
2738
+ return devicePixelRatio2 || 1;
2151
2739
  }
2152
- // DEPRECATED METHODS
2153
- /**
2154
- * Maps CSS pixel position to device pixel position
2155
- */
2156
2740
  cssToDevicePixels(cssPixel, yInvert = true) {
2157
2741
  const ratio = this.cssToDeviceRatio();
2158
2742
  const [width, height] = this.getDrawingBufferSize();
@@ -2162,10 +2746,10 @@ or create a device with the 'debug: true' prop.`;
2162
2746
  getPixelSize() {
2163
2747
  return this.getDevicePixelSize();
2164
2748
  }
2165
- /** @deprecated - TODO which values should we use for aspect */
2749
+ /** @deprecated Use the current drawing buffer size for projection setup. */
2166
2750
  getAspect() {
2167
- const [width, height] = this.getDevicePixelSize();
2168
- return width / height;
2751
+ const [width, height] = this.getDrawingBufferSize();
2752
+ return width > 0 && height > 0 ? width / height : 1;
2169
2753
  }
2170
2754
  /** @deprecated Returns multiplier need to convert CSS size to Device size */
2171
2755
  cssToDeviceRatio() {
@@ -2181,18 +2765,40 @@ or create a device with the 'debug: true' prop.`;
2181
2765
  resize(size) {
2182
2766
  this.setDrawingBufferSize(size.width, size.height);
2183
2767
  }
2184
- // IMPLEMENTATION
2185
- /**
2186
- * Allows subclass constructor to override the canvas id for auto created canvases.
2187
- * This can really help when debugging DOM in apps that create multiple devices
2188
- */
2189
2768
  _setAutoCreatedCanvasId(id) {
2190
2769
  if (this.htmlCanvas?.id === "lumagl-auto-created-canvas") {
2191
2770
  this.htmlCanvas.id = id;
2192
2771
  }
2193
2772
  }
2194
- /** reacts to an observed intersection */
2773
+ /**
2774
+ * Starts DOM observation after the derived context and its device are fully initialized.
2775
+ *
2776
+ * `CanvasSurface` construction runs before subclasses can assign `this.device`, and the
2777
+ * default WebGL canvas context is created before `WebGLDevice` has initialized `limits`,
2778
+ * `features`, and the rest of its runtime state. Deferring observer startup avoids early
2779
+ * `ResizeObserver` and DPR callbacks running against a partially initialized device.
2780
+ */
2781
+ _startObservers() {
2782
+ if (this.destroyed) {
2783
+ return;
2784
+ }
2785
+ this._canvasObserver.start();
2786
+ }
2787
+ /**
2788
+ * Stops all DOM observation and timers associated with a canvas surface.
2789
+ *
2790
+ * This pairs with `_startObservers()` so teardown uses the same lifecycle whether a context is
2791
+ * explicitly destroyed, abandoned during device reuse, or temporarily has not started observing
2792
+ * yet. Centralizing shutdown here keeps resize/DPR/position watchers from surviving past the
2793
+ * lifetime of the owning device.
2794
+ */
2795
+ _stopObservers() {
2796
+ this._canvasObserver.stop();
2797
+ }
2195
2798
  _handleIntersection(entries) {
2799
+ if (this.destroyed) {
2800
+ return;
2801
+ }
2196
2802
  const entry = entries.find((entry_) => entry_.target === this.canvas);
2197
2803
  if (!entry) {
2198
2804
  return;
@@ -2203,33 +2809,34 @@ or create a device with the 'debug: true' prop.`;
2203
2809
  this.device.props.onVisibilityChange(this);
2204
2810
  }
2205
2811
  }
2206
- /**
2207
- * Reacts to an observed resize by using the most accurate pixel size information the browser can provide
2208
- * @see https://web.dev/articles/device-pixel-content-box
2209
- * @see https://webgpufundamentals.org/webgpu/lessons/webgpu-resizing-the-canvas.html
2210
- */
2211
2812
  _handleResize(entries) {
2813
+ if (this.destroyed) {
2814
+ return;
2815
+ }
2212
2816
  const entry = entries.find((entry_) => entry_.target === this.canvas);
2213
2817
  if (!entry) {
2214
2818
  return;
2215
2819
  }
2216
- this.cssWidth = entry.contentBoxSize[0].inlineSize;
2217
- this.cssHeight = entry.contentBoxSize[0].blockSize;
2820
+ const contentBoxSize = assertDefined(entry.contentBoxSize?.[0]);
2821
+ this.cssWidth = contentBoxSize.inlineSize;
2822
+ this.cssHeight = contentBoxSize.blockSize;
2218
2823
  const oldPixelSize = this.getDevicePixelSize();
2219
- const devicePixelWidth = entry.devicePixelContentBoxSize?.[0].inlineSize || entry.contentBoxSize[0].inlineSize * devicePixelRatio;
2220
- const devicePixelHeight = entry.devicePixelContentBoxSize?.[0].blockSize || entry.contentBoxSize[0].blockSize * devicePixelRatio;
2824
+ const devicePixelWidth = entry.devicePixelContentBoxSize?.[0]?.inlineSize || contentBoxSize.inlineSize * devicePixelRatio;
2825
+ const devicePixelHeight = entry.devicePixelContentBoxSize?.[0]?.blockSize || contentBoxSize.blockSize * devicePixelRatio;
2221
2826
  const [maxDevicePixelWidth, maxDevicePixelHeight] = this.getMaxDrawingBufferSize();
2222
2827
  this.devicePixelWidth = Math.max(1, Math.min(devicePixelWidth, maxDevicePixelWidth));
2223
2828
  this.devicePixelHeight = Math.max(1, Math.min(devicePixelHeight, maxDevicePixelHeight));
2224
2829
  this._updateDrawingBufferSize();
2225
2830
  this.device.props.onResize(this, { oldPixelSize });
2226
2831
  }
2227
- /** Initiate a deferred update for the canvas drawing buffer size */
2228
2832
  _updateDrawingBufferSize() {
2229
2833
  if (this.props.autoResize) {
2230
2834
  if (typeof this.props.useDevicePixels === "number") {
2231
- const dpr = this.props.useDevicePixels;
2232
- this.setDrawingBufferSize(this.cssWidth * dpr, this.cssHeight * dpr);
2835
+ const devicePixelRatio2 = this.props.useDevicePixels;
2836
+ this.setDrawingBufferSize(
2837
+ this.cssWidth * devicePixelRatio2,
2838
+ this.cssHeight * devicePixelRatio2
2839
+ );
2233
2840
  } else if (this.props.useDevicePixels) {
2234
2841
  this.setDrawingBufferSize(this.devicePixelWidth, this.devicePixelHeight);
2235
2842
  } else {
@@ -2240,7 +2847,6 @@ or create a device with the 'debug: true' prop.`;
2240
2847
  this.isInitialized = true;
2241
2848
  this.updatePosition();
2242
2849
  }
2243
- /** Perform a deferred resize of the drawing buffer if needed */
2244
2850
  _resizeDrawingBufferIfNeeded() {
2245
2851
  if (this._needsDrawingBufferResize) {
2246
2852
  this._needsDrawingBufferResize = false;
@@ -2252,34 +2858,21 @@ or create a device with the 'debug: true' prop.`;
2252
2858
  }
2253
2859
  }
2254
2860
  }
2255
- /** Monitor DPR changes */
2256
2861
  _observeDevicePixelRatio() {
2862
+ if (this.destroyed || !this._canvasObserver.started) {
2863
+ return;
2864
+ }
2257
2865
  const oldRatio = this.devicePixelRatio;
2258
2866
  this.devicePixelRatio = window.devicePixelRatio;
2259
2867
  this.updatePosition();
2260
- this.device.props.onDevicePixelRatioChange(this, { oldRatio });
2261
- matchMedia(`(resolution: ${this.devicePixelRatio}dppx)`).addEventListener(
2262
- "change",
2263
- () => this._observeDevicePixelRatio(),
2264
- { once: true }
2265
- );
2266
- }
2267
- /** Start tracking positions with a timer */
2268
- _trackPosition(intervalMs = 100) {
2269
- const intervalId = setInterval(() => {
2270
- if (this.destroyed) {
2271
- clearInterval(intervalId);
2272
- } else {
2273
- this.updatePosition();
2274
- }
2275
- }, intervalMs);
2868
+ this.device.props.onDevicePixelRatioChange?.(this, {
2869
+ oldRatio
2870
+ });
2276
2871
  }
2277
- /**
2278
- * Calculated the absolute position of the canvas
2279
- * @note - getBoundingClientRect() is normally cheap but can be expensive
2280
- * if called before browser has finished a reflow. Should not be the case here.
2281
- */
2282
2872
  updatePosition() {
2873
+ if (this.destroyed) {
2874
+ return;
2875
+ }
2283
2876
  const newRect = this.htmlCanvas?.getBoundingClientRect();
2284
2877
  if (newRect) {
2285
2878
  const position = [newRect.left, newRect.top];
@@ -2288,13 +2881,15 @@ or create a device with the 'debug: true' prop.`;
2288
2881
  if (positionChanged) {
2289
2882
  const oldPosition = this._position;
2290
2883
  this._position = position;
2291
- this.device.props.onPositionChange?.(this, { oldPosition });
2884
+ this.device.props.onPositionChange?.(this, {
2885
+ oldPosition
2886
+ });
2292
2887
  }
2293
2888
  }
2294
2889
  }
2295
2890
  };
2296
- var CanvasContext = _CanvasContext;
2297
- __publicField(CanvasContext, "defaultProps", {
2891
+ var CanvasSurface = _CanvasSurface;
2892
+ __publicField(CanvasSurface, "defaultProps", {
2298
2893
  id: void 0,
2299
2894
  canvas: null,
2300
2895
  width: 800,
@@ -2322,7 +2917,7 @@ or create a device with the 'debug: true' prop.`;
2322
2917
  }
2323
2918
  function getCanvasFromDOM(canvasId) {
2324
2919
  const canvas = document.getElementById(canvasId);
2325
- if (!CanvasContext.isHTMLCanvas(canvas)) {
2920
+ if (!CanvasSurface.isHTMLCanvas(canvas)) {
2326
2921
  throw new Error("Object is not a canvas element");
2327
2922
  }
2328
2923
  return canvas;
@@ -2346,33 +2941,40 @@ or create a device with the 'debug: true' prop.`;
2346
2941
  const point = pixel;
2347
2942
  const x = scaleX(point[0], ratio, width);
2348
2943
  let y = scaleY(point[1], ratio, height, yInvert);
2349
- let t = scaleX(point[0] + 1, ratio, width);
2350
- const xHigh = t === width - 1 ? t : t - 1;
2351
- t = scaleY(point[1] + 1, ratio, height, yInvert);
2944
+ let temporary = scaleX(point[0] + 1, ratio, width);
2945
+ const xHigh = temporary === width - 1 ? temporary : temporary - 1;
2946
+ temporary = scaleY(point[1] + 1, ratio, height, yInvert);
2352
2947
  let yHigh;
2353
2948
  if (yInvert) {
2354
- t = t === 0 ? t : t + 1;
2949
+ temporary = temporary === 0 ? temporary : temporary + 1;
2355
2950
  yHigh = y;
2356
- y = t;
2951
+ y = temporary;
2357
2952
  } else {
2358
- yHigh = t === height - 1 ? t : t - 1;
2953
+ yHigh = temporary === height - 1 ? temporary : temporary - 1;
2359
2954
  }
2360
2955
  return {
2361
2956
  x,
2362
2957
  y,
2363
- // when ratio < 1, current css pixel and next css pixel may point to same device pixel, set width/height to 1 in those cases.
2364
2958
  width: Math.max(xHigh - x + 1, 1),
2365
2959
  height: Math.max(yHigh - y + 1, 1)
2366
2960
  };
2367
2961
  }
2368
2962
  function scaleX(x, ratio, width) {
2369
- const r = Math.min(Math.round(x * ratio), width - 1);
2370
- return r;
2963
+ return Math.min(Math.round(x * ratio), width - 1);
2371
2964
  }
2372
2965
  function scaleY(y, ratio, height, yInvert) {
2373
2966
  return yInvert ? Math.max(0, height - 1 - Math.round(y * ratio)) : Math.min(Math.round(y * ratio), height - 1);
2374
2967
  }
2375
2968
 
2969
+ // ../core/src/adapter/canvas-context.ts
2970
+ var CanvasContext = class extends CanvasSurface {
2971
+ };
2972
+ __publicField(CanvasContext, "defaultProps", CanvasSurface.defaultProps);
2973
+
2974
+ // ../core/src/adapter/presentation-context.ts
2975
+ var PresentationContext = class extends CanvasSurface {
2976
+ };
2977
+
2376
2978
  // ../core/src/adapter/resources/sampler.ts
2377
2979
  var _Sampler = class extends Resource {
2378
2980
  get [Symbol.toStringTag]() {
@@ -2427,6 +3029,8 @@ or create a device with the 'debug: true' prop.`;
2427
3029
  depth;
2428
3030
  /** mip levels in this texture */
2429
3031
  mipLevels;
3032
+ /** sample count */
3033
+ samples;
2430
3034
  /** Rows are multiples of this length, padded with extra bytes if needed */
2431
3035
  byteAlignment;
2432
3036
  /** The ready promise is always resolved. It is provided for type compatibility with DynamicTexture. */
@@ -2452,6 +3056,7 @@ or create a device with the 'debug: true' prop.`;
2452
3056
  this.height = this.props.height;
2453
3057
  this.depth = this.props.depth;
2454
3058
  this.mipLevels = this.props.mipLevels;
3059
+ this.samples = this.props.samples || 1;
2455
3060
  if (this.dimension === "cube") {
2456
3061
  this.depth = 6;
2457
3062
  }
@@ -2485,9 +3090,25 @@ or create a device with the 'debug: true' prop.`;
2485
3090
  setSampler(sampler) {
2486
3091
  this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
2487
3092
  }
3093
+ /**
3094
+ * Copy raw image data (bytes) into the texture.
3095
+ *
3096
+ * @note Deprecated compatibility wrapper over {@link writeData}.
3097
+ * @note Uses the same layout defaults and alignment rules as {@link writeData}.
3098
+ * @note Tightly packed CPU uploads can omit `bytesPerRow` and `rowsPerImage`.
3099
+ * @note If the CPU source rows are padded, pass explicit `bytesPerRow` and `rowsPerImage`.
3100
+ * @deprecated Use writeData()
3101
+ */
3102
+ copyImageData(options) {
3103
+ const { data, depth, ...writeOptions } = options;
3104
+ this.writeData(data, {
3105
+ ...writeOptions,
3106
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3107
+ });
3108
+ }
2488
3109
  /**
2489
3110
  * Calculates the memory layout of the texture, required when reading and writing data.
2490
- * @return the memory layout of the texture, in particular bytesPerRow which includes required padding
3111
+ * @return the backend-aligned linear layout, in particular bytesPerRow which includes any required padding for buffer copy/read paths
2491
3112
  */
2492
3113
  computeMemoryLayout(options_ = {}) {
2493
3114
  const options = this._normalizeTextureReadOptions(options_);
@@ -2506,9 +3127,11 @@ or create a device with the 'debug: true' prop.`;
2506
3127
  * @returns A Buffer containing the texture data.
2507
3128
  *
2508
3129
  * @note The memory layout of the texture data is determined by the texture format and dimensions.
2509
- * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3130
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
2510
3131
  * @note The application can call Buffer.readAsync()
2511
3132
  * @note If not supplied a buffer will be created and the application needs to call Buffer.destroy
3133
+ * @note On WebGPU this corresponds to a texture-to-buffer copy and uses buffer-copy alignment rules.
3134
+ * @note On WebGL, luma.gl emulates the same logical readback behavior.
2512
3135
  */
2513
3136
  readBuffer(options, buffer) {
2514
3137
  throw new Error("readBuffer not implemented");
@@ -2524,10 +3147,14 @@ or create a device with the 'debug: true' prop.`;
2524
3147
  throw new Error("readBuffer not implemented");
2525
3148
  }
2526
3149
  /**
2527
- * Writes an GPU Buffer into a texture.
3150
+ * Writes a GPU Buffer into a texture.
2528
3151
  *
3152
+ * @param buffer - Source GPU buffer.
3153
+ * @param options - Destination subresource, extent, and source layout options.
2529
3154
  * @note The memory layout of the texture data is determined by the texture format and dimensions.
2530
- * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3155
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3156
+ * @note On WebGPU this corresponds to a buffer-to-texture copy and uses buffer-copy alignment rules.
3157
+ * @note On WebGL, luma.gl emulates the same destination and layout semantics.
2531
3158
  */
2532
3159
  writeBuffer(buffer, options) {
2533
3160
  throw new Error("readBuffer not implemented");
@@ -2535,8 +3162,11 @@ or create a device with the 'debug: true' prop.`;
2535
3162
  /**
2536
3163
  * Writes an array buffer into a texture.
2537
3164
  *
2538
- * @note The memory layout of the texture data is determined by the texture format and dimensions.
2539
- * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3165
+ * @param data - Source texel data.
3166
+ * @param options - Destination subresource, extent, and source layout options.
3167
+ * @note If `bytesPerRow` and `rowsPerImage` are omitted, luma.gl computes a tightly packed CPU-memory layout for the requested region.
3168
+ * @note On WebGPU this corresponds to `GPUQueue.writeTexture()` and does not implicitly pad rows to 256 bytes.
3169
+ * @note On WebGL, padded CPU data is supported via the same `bytesPerRow` and `rowsPerImage` options.
2540
3170
  */
2541
3171
  writeData(data, options) {
2542
3172
  throw new Error("readBuffer not implemented");
@@ -2599,37 +3229,166 @@ or create a device with the 'debug: true' prop.`;
2599
3229
  }
2600
3230
  }
2601
3231
  _normalizeCopyImageDataOptions(options_) {
2602
- const { width, height, depth } = this;
2603
- const options = { ..._Texture.defaultCopyDataOptions, width, height, depth, ...options_ };
2604
- const info = this.device.getTextureFormatInfo(this.format);
2605
- if (!options_.bytesPerRow && !info.bytesPerPixel) {
2606
- throw new Error(`bytesPerRow must be provided for texture format ${this.format}`);
2607
- }
2608
- options.bytesPerRow = options_.bytesPerRow || width * (info.bytesPerPixel || 4);
2609
- options.rowsPerImage = options_.rowsPerImage || height;
2610
- return options;
3232
+ const { data, depth, ...writeOptions } = options_;
3233
+ const options = this._normalizeTextureWriteOptions({
3234
+ ...writeOptions,
3235
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3236
+ });
3237
+ return { data, depth: options.depthOrArrayLayers, ...options };
2611
3238
  }
2612
3239
  _normalizeCopyExternalImageOptions(options_) {
3240
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3241
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3242
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
2613
3243
  const size = this.device.getExternalImageSize(options_.image);
2614
- const options = { ..._Texture.defaultCopyExternalImageOptions, ...size, ...options_ };
2615
- options.width = Math.min(options.width, this.width - options.x);
2616
- options.height = Math.min(options.height, this.height - options.y);
3244
+ const options = {
3245
+ ..._Texture.defaultCopyExternalImageOptions,
3246
+ ...mipLevelSize,
3247
+ ...size,
3248
+ ...optionsWithoutUndefined
3249
+ };
3250
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3251
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3252
+ options.depth = Math.min(options.depth, mipLevelSize.depthOrArrayLayers - options.z);
2617
3253
  return options;
2618
3254
  }
2619
3255
  _normalizeTextureReadOptions(options_) {
2620
- const { width, height } = this;
2621
- const options = { ..._Texture.defaultTextureReadOptions, width, height, ...options_ };
2622
- options.width = Math.min(options.width, this.width - options.x);
2623
- options.height = Math.min(options.height, this.height - options.y);
3256
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3257
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3258
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3259
+ const options = {
3260
+ ..._Texture.defaultTextureReadOptions,
3261
+ ...mipLevelSize,
3262
+ ...optionsWithoutUndefined
3263
+ };
3264
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3265
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3266
+ options.depthOrArrayLayers = Math.min(
3267
+ options.depthOrArrayLayers,
3268
+ mipLevelSize.depthOrArrayLayers - options.z
3269
+ );
2624
3270
  return options;
2625
3271
  }
3272
+ /**
3273
+ * Normalizes a texture read request and validates the color-only readback contract used by the
3274
+ * current texture read APIs. Supported dimensions are `2d`, `cube`, `cube-array`,
3275
+ * `2d-array`, and `3d`.
3276
+ *
3277
+ * @throws if the texture format, aspect, or dimension is not supported by the first-pass
3278
+ * color-read implementation.
3279
+ */
3280
+ _getSupportedColorReadOptions(options_) {
3281
+ const options = this._normalizeTextureReadOptions(options_);
3282
+ const formatInfo = textureFormatDecoder.getInfo(this.format);
3283
+ this._validateColorReadAspect(options);
3284
+ this._validateColorReadFormat(formatInfo);
3285
+ switch (this.dimension) {
3286
+ case "2d":
3287
+ case "cube":
3288
+ case "cube-array":
3289
+ case "2d-array":
3290
+ case "3d":
3291
+ return options;
3292
+ default:
3293
+ throw new Error(`${this} color readback does not support ${this.dimension} textures`);
3294
+ }
3295
+ }
3296
+ /** Validates that a read request targets the full color aspect of the texture. */
3297
+ _validateColorReadAspect(options) {
3298
+ if (options.aspect !== "all") {
3299
+ throw new Error(`${this} color readback only supports aspect 'all'`);
3300
+ }
3301
+ }
3302
+ /** Validates that a read request targets an uncompressed color-renderable texture format. */
3303
+ _validateColorReadFormat(formatInfo) {
3304
+ if (formatInfo.compressed) {
3305
+ throw new Error(
3306
+ `${this} color readback does not support compressed formats (${this.format})`
3307
+ );
3308
+ }
3309
+ switch (formatInfo.attachment) {
3310
+ case "color":
3311
+ return;
3312
+ case "depth":
3313
+ throw new Error(`${this} color readback does not support depth formats (${this.format})`);
3314
+ case "stencil":
3315
+ throw new Error(`${this} color readback does not support stencil formats (${this.format})`);
3316
+ case "depth-stencil":
3317
+ throw new Error(
3318
+ `${this} color readback does not support depth-stencil formats (${this.format})`
3319
+ );
3320
+ default:
3321
+ throw new Error(`${this} color readback does not support format ${this.format}`);
3322
+ }
3323
+ }
2626
3324
  _normalizeTextureWriteOptions(options_) {
2627
- const { width, height } = this;
2628
- const options = { ..._Texture.defaultTextureReadOptions, width, height, ...options_ };
2629
- options.width = Math.min(options.width, this.width - options.x);
2630
- options.height = Math.min(options.height, this.height - options.y);
3325
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3326
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3327
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3328
+ const options = {
3329
+ ..._Texture.defaultTextureWriteOptions,
3330
+ ...mipLevelSize,
3331
+ ...optionsWithoutUndefined
3332
+ };
3333
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3334
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3335
+ options.depthOrArrayLayers = Math.min(
3336
+ options.depthOrArrayLayers,
3337
+ mipLevelSize.depthOrArrayLayers - options.z
3338
+ );
3339
+ const layout = textureFormatDecoder.computeMemoryLayout({
3340
+ format: this.format,
3341
+ width: options.width,
3342
+ height: options.height,
3343
+ depth: options.depthOrArrayLayers,
3344
+ byteAlignment: this.byteAlignment
3345
+ });
3346
+ const minimumBytesPerRow = layout.bytesPerPixel * options.width;
3347
+ options.bytesPerRow = optionsWithoutUndefined.bytesPerRow ?? layout.bytesPerRow;
3348
+ options.rowsPerImage = optionsWithoutUndefined.rowsPerImage ?? options.height;
3349
+ if (options.bytesPerRow < minimumBytesPerRow) {
3350
+ throw new Error(
3351
+ `bytesPerRow (${options.bytesPerRow}) must be at least ${minimumBytesPerRow} for ${this.format}`
3352
+ );
3353
+ }
3354
+ if (options.rowsPerImage < options.height) {
3355
+ throw new Error(
3356
+ `rowsPerImage (${options.rowsPerImage}) must be at least ${options.height} for ${this.format}`
3357
+ );
3358
+ }
3359
+ const bytesPerPixel = this.device.getTextureFormatInfo(this.format).bytesPerPixel;
3360
+ if (bytesPerPixel && options.bytesPerRow % bytesPerPixel !== 0) {
3361
+ throw new Error(
3362
+ `bytesPerRow (${options.bytesPerRow}) must be a multiple of bytesPerPixel (${bytesPerPixel}) for ${this.format}`
3363
+ );
3364
+ }
2631
3365
  return options;
2632
3366
  }
3367
+ _getMipLevelSize(mipLevel) {
3368
+ const width = Math.max(1, this.width >> mipLevel);
3369
+ const height = this.baseDimension === "1d" ? 1 : Math.max(1, this.height >> mipLevel);
3370
+ const depthOrArrayLayers = this.dimension === "3d" ? Math.max(1, this.depth >> mipLevel) : this.depth;
3371
+ return { width, height, depthOrArrayLayers };
3372
+ }
3373
+ getAllocatedByteLength() {
3374
+ let allocatedByteLength = 0;
3375
+ for (let mipLevel = 0; mipLevel < this.mipLevels; mipLevel++) {
3376
+ const { width, height, depthOrArrayLayers } = this._getMipLevelSize(mipLevel);
3377
+ allocatedByteLength += textureFormatDecoder.computeMemoryLayout({
3378
+ format: this.format,
3379
+ width,
3380
+ height,
3381
+ depth: depthOrArrayLayers,
3382
+ byteAlignment: 1
3383
+ }).byteLength;
3384
+ }
3385
+ return allocatedByteLength * this.samples;
3386
+ }
3387
+ static _omitUndefined(options) {
3388
+ return Object.fromEntries(
3389
+ Object.entries(options).filter(([, value]) => value !== void 0)
3390
+ );
3391
+ }
2633
3392
  };
2634
3393
  var Texture = _Texture;
2635
3394
  /** The texture can be bound for use as a sampled texture in a shader */
@@ -2665,6 +3424,10 @@ or create a device with the 'debug: true' prop.`;
2665
3424
  byteOffset: 0,
2666
3425
  bytesPerRow: void 0,
2667
3426
  rowsPerImage: void 0,
3427
+ width: void 0,
3428
+ height: void 0,
3429
+ depthOrArrayLayers: void 0,
3430
+ depth: 1,
2668
3431
  mipLevel: 0,
2669
3432
  x: 0,
2670
3433
  y: 0,
@@ -2698,6 +3461,19 @@ or create a device with the 'debug: true' prop.`;
2698
3461
  mipLevel: 0,
2699
3462
  aspect: "all"
2700
3463
  });
3464
+ __publicField(Texture, "defaultTextureWriteOptions", {
3465
+ byteOffset: 0,
3466
+ bytesPerRow: void 0,
3467
+ rowsPerImage: void 0,
3468
+ x: 0,
3469
+ y: 0,
3470
+ z: 0,
3471
+ width: void 0,
3472
+ height: void 0,
3473
+ depthOrArrayLayers: 1,
3474
+ mipLevel: 0,
3475
+ aspect: "all"
3476
+ });
2701
3477
 
2702
3478
  // ../core/src/adapter/resources/texture-view.ts
2703
3479
  var _TextureView = class extends Resource {
@@ -2744,24 +3520,32 @@ or create a device with the 'debug: true' prop.`;
2744
3520
  const log2 = shaderLog.slice().sort((a, b) => a.lineNum - b.lineNum);
2745
3521
  switch (options?.showSourceCode || "no") {
2746
3522
  case "all":
2747
- let currentMessage = 0;
3523
+ let currentMessageIndex = 0;
2748
3524
  for (let lineNum = 1; lineNum <= lines.length; lineNum++) {
2749
- formattedLog += getNumberedLine(lines[lineNum - 1], lineNum, options);
2750
- while (log2.length > currentMessage && log2[currentMessage].lineNum === lineNum) {
2751
- const message = log2[currentMessage++];
2752
- formattedLog += formatCompilerMessage(message, lines, message.lineNum, {
3525
+ const line = lines[lineNum - 1];
3526
+ const currentMessage = log2[currentMessageIndex];
3527
+ if (line && currentMessage) {
3528
+ formattedLog += getNumberedLine(line, lineNum, options);
3529
+ }
3530
+ while (log2.length > currentMessageIndex && currentMessage.lineNum === lineNum) {
3531
+ const message = log2[currentMessageIndex++];
3532
+ if (message) {
3533
+ formattedLog += formatCompilerMessage(message, lines, message.lineNum, {
3534
+ ...options,
3535
+ inlineSource: false
3536
+ });
3537
+ }
3538
+ }
3539
+ }
3540
+ while (log2.length > currentMessageIndex) {
3541
+ const message = log2[currentMessageIndex++];
3542
+ if (message) {
3543
+ formattedLog += formatCompilerMessage(message, [], 0, {
2753
3544
  ...options,
2754
3545
  inlineSource: false
2755
3546
  });
2756
3547
  }
2757
3548
  }
2758
- while (log2.length > currentMessage) {
2759
- const message = log2[currentMessage++];
2760
- formattedLog += formatCompilerMessage(message, [], 0, {
2761
- ...options,
2762
- inlineSource: false
2763
- });
2764
- }
2765
3549
  return formattedLog;
2766
3550
  case "issues":
2767
3551
  case "no":
@@ -2783,8 +3567,8 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
2783
3567
 
2784
3568
  `;
2785
3569
  }
2786
- const color = message.type === "error" ? "red" : "#8B4000";
2787
- return options?.html ? `<div class='luma-compiler-log-error' style="color:${color};"><b> ${message.type.toUpperCase()}: ${message.message}</b></div>` : `${message.type.toUpperCase()}: ${message.message}`;
3570
+ const color = message.type === "error" ? "red" : "orange";
3571
+ return options?.html ? `<div class='luma-compiler-log-${message.type}' style="color:${color};"><b> ${message.type.toUpperCase()}: ${message.message}</b></div>` : `${message.type.toUpperCase()}: ${message.message}`;
2788
3572
  }
2789
3573
  function getNumberedLines(lines, lineNum, options) {
2790
3574
  let numberedLines = "";
@@ -2870,29 +3654,34 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
2870
3654
  }
2871
3655
  const shaderName = shaderId;
2872
3656
  const shaderTitle = `${this.stage} shader "${shaderName}"`;
2873
- let htmlLog = formatCompilerLog(messages, this.source, { showSourceCode: "all", html: true });
3657
+ const htmlLog = formatCompilerLog(messages, this.source, { showSourceCode: "all", html: true });
2874
3658
  const translatedSource = this.getTranslatedSource();
3659
+ const container = document.createElement("div");
3660
+ container.innerHTML = `<h1>Compilation error in ${shaderTitle}</h1>
3661
+ <div style="display:flex;position:fixed;top:10px;right:20px;gap:2px;">
3662
+ <button id="copy">Copy source</button><br/>
3663
+ <button id="close">Close</button>
3664
+ </div>
3665
+ <code><pre>${htmlLog}</pre></code>`;
2875
3666
  if (translatedSource) {
2876
- htmlLog += `<br /><br /><h1>Translated Source</h1><br /><br /><code style="user-select:text;"><pre>${translatedSource}</pre></code>`;
2877
- }
2878
- const button = document.createElement("Button");
2879
- button.innerHTML = `
2880
- <h1>Compilation error in ${shaderTitle}</h1><br /><br />
2881
- <code style="user-select:text;"><pre>
2882
- ${htmlLog}
2883
- </pre></code>`;
2884
- button.style.top = "10px";
2885
- button.style.left = "10px";
2886
- button.style.position = "absolute";
2887
- button.style.zIndex = "9999";
2888
- button.style.width = "100%";
2889
- button.style.textAlign = "left";
2890
- document.body.appendChild(button);
2891
- const errors = document.getElementsByClassName("luma-compiler-log-error");
2892
- errors[0]?.scrollIntoView();
2893
- button.onclick = () => {
2894
- const dataURI = `data:text/plain,${encodeURIComponent(this.source)}`;
2895
- navigator.clipboard.writeText(dataURI);
3667
+ container.innerHTML += `<br /><h1>Translated Source</h1><br /><br /><code><pre>${translatedSource}</pre></code>`;
3668
+ }
3669
+ container.style.top = "0";
3670
+ container.style.left = "0";
3671
+ container.style.background = "white";
3672
+ container.style.position = "fixed";
3673
+ container.style.zIndex = "9999";
3674
+ container.style.maxWidth = "100vw";
3675
+ container.style.maxHeight = "100vh";
3676
+ container.style.overflowY = "auto";
3677
+ document.body.appendChild(container);
3678
+ const error = container.querySelector(".luma-compiler-log-error");
3679
+ error?.scrollIntoView();
3680
+ container.querySelector("button#close").onclick = () => {
3681
+ container.remove();
3682
+ };
3683
+ container.querySelector("button#copy").onclick = () => {
3684
+ navigator.clipboard.writeText(this.source);
2896
3685
  };
2897
3686
  }
2898
3687
  };
@@ -2912,7 +3701,7 @@ ${htmlLog}
2912
3701
  function getShaderName(shader, defaultName = "unnamed") {
2913
3702
  const SHADER_NAME_REGEXP = /#define[\s*]SHADER_NAME[\s*]([A-Za-z0-9_-]+)[\s*]/;
2914
3703
  const match = SHADER_NAME_REGEXP.exec(shader);
2915
- return match ? match[1] : defaultName;
3704
+ return match?.[1] ?? defaultName;
2916
3705
  }
2917
3706
 
2918
3707
  // ../core/src/adapter/resources/framebuffer.ts
@@ -3018,17 +3807,15 @@ ${htmlLog}
3018
3807
  * and destroys existing textures if owned
3019
3808
  */
3020
3809
  resizeAttachments(width, height) {
3021
- for (let i = 0; i < this.colorAttachments.length; ++i) {
3022
- if (this.colorAttachments[i]) {
3023
- const resizedTexture = this.colorAttachments[i].texture.clone({
3024
- width,
3025
- height
3026
- });
3027
- this.destroyAttachedResource(this.colorAttachments[i]);
3028
- this.colorAttachments[i] = resizedTexture.view;
3029
- this.attachResource(resizedTexture.view);
3030
- }
3031
- }
3810
+ this.colorAttachments.forEach((colorAttachment, i) => {
3811
+ const resizedTexture = colorAttachment.texture.clone({
3812
+ width,
3813
+ height
3814
+ });
3815
+ this.destroyAttachedResource(colorAttachment);
3816
+ this.colorAttachments[i] = resizedTexture.view;
3817
+ this.attachResource(resizedTexture.view);
3818
+ });
3032
3819
  if (this.depthStencilAttachment) {
3033
3820
  const resizedTexture = this.depthStencilAttachment.texture.clone({
3034
3821
  width,
@@ -3065,10 +3852,21 @@ ${htmlLog}
3065
3852
  linkStatus = "pending";
3066
3853
  /** The hash of the pipeline */
3067
3854
  hash = "";
3855
+ /** Optional shared backend implementation */
3856
+ sharedRenderPipeline = null;
3857
+ /** Whether shader or pipeline compilation/linking is still in progress */
3858
+ get isPending() {
3859
+ return this.linkStatus === "pending" || this.vs.compilationStatus === "pending" || this.fs?.compilationStatus === "pending";
3860
+ }
3861
+ /** Whether shader or pipeline compilation/linking has failed */
3862
+ get isErrored() {
3863
+ return this.linkStatus === "error" || this.vs.compilationStatus === "error" || this.fs?.compilationStatus === "error";
3864
+ }
3068
3865
  constructor(device, props) {
3069
3866
  super(device, props, _RenderPipeline.defaultProps);
3070
3867
  this.shaderLayout = this.props.shaderLayout;
3071
3868
  this.bufferLayout = this.props.bufferLayout || [];
3869
+ this.sharedRenderPipeline = this.props._sharedRenderPipeline || null;
3072
3870
  }
3073
3871
  };
3074
3872
  var RenderPipeline = _RenderPipeline;
@@ -3086,10 +3884,30 @@ ${htmlLog}
3086
3884
  colorAttachmentFormats: void 0,
3087
3885
  depthStencilAttachmentFormat: void 0,
3088
3886
  parameters: {},
3089
- bindings: {},
3090
- uniforms: {}
3887
+ varyings: void 0,
3888
+ bufferMode: void 0,
3889
+ disableWarnings: false,
3890
+ _sharedRenderPipeline: void 0,
3891
+ bindings: void 0
3091
3892
  });
3092
3893
 
3894
+ // ../core/src/adapter/resources/shared-render-pipeline.ts
3895
+ var SharedRenderPipeline = class extends Resource {
3896
+ get [Symbol.toStringTag]() {
3897
+ return "SharedRenderPipeline";
3898
+ }
3899
+ constructor(device, props) {
3900
+ super(device, props, {
3901
+ ...Resource.defaultProps,
3902
+ handle: void 0,
3903
+ vs: void 0,
3904
+ fs: void 0,
3905
+ varyings: void 0,
3906
+ bufferMode: void 0
3907
+ });
3908
+ }
3909
+ };
3910
+
3093
3911
  // ../core/src/adapter/resources/render-pass.ts
3094
3912
  var _RenderPass = class extends Resource {
3095
3913
  get [Symbol.toStringTag]() {
@@ -3172,8 +3990,69 @@ ${htmlLog}
3172
3990
  get [Symbol.toStringTag]() {
3173
3991
  return "CommandEncoder";
3174
3992
  }
3993
+ _timeProfilingQuerySet = null;
3994
+ _timeProfilingSlotCount = 0;
3995
+ _gpuTimeMs;
3175
3996
  constructor(device, props) {
3176
3997
  super(device, props, _CommandEncoder.defaultProps);
3998
+ this._timeProfilingQuerySet = props.timeProfilingQuerySet ?? null;
3999
+ this._timeProfilingSlotCount = 0;
4000
+ this._gpuTimeMs = void 0;
4001
+ }
4002
+ /**
4003
+ * Reads all resolved timestamp pairs on the current profiler query set and caches the sum
4004
+ * as milliseconds on this encoder.
4005
+ */
4006
+ async resolveTimeProfilingQuerySet() {
4007
+ this._gpuTimeMs = void 0;
4008
+ if (!this._timeProfilingQuerySet) {
4009
+ return;
4010
+ }
4011
+ const pairCount = Math.floor(this._timeProfilingSlotCount / 2);
4012
+ if (pairCount <= 0) {
4013
+ return;
4014
+ }
4015
+ const queryCount = pairCount * 2;
4016
+ const results = await this._timeProfilingQuerySet.readResults({
4017
+ firstQuery: 0,
4018
+ queryCount
4019
+ });
4020
+ let totalDurationNanoseconds = 0n;
4021
+ for (let queryIndex = 0; queryIndex < queryCount; queryIndex += 2) {
4022
+ totalDurationNanoseconds += results[queryIndex + 1] - results[queryIndex];
4023
+ }
4024
+ this._gpuTimeMs = Number(totalDurationNanoseconds) / 1e6;
4025
+ }
4026
+ /** Returns the number of query slots consumed by automatic pass profiling on this encoder. */
4027
+ getTimeProfilingSlotCount() {
4028
+ return this._timeProfilingSlotCount;
4029
+ }
4030
+ getTimeProfilingQuerySet() {
4031
+ return this._timeProfilingQuerySet;
4032
+ }
4033
+ /** Internal helper for auto-assigning timestamp slots to render/compute passes on this encoder. */
4034
+ _applyTimeProfilingToPassProps(props) {
4035
+ const passProps = props || {};
4036
+ if (!this._supportsTimestampQueries() || !this._timeProfilingQuerySet) {
4037
+ return passProps;
4038
+ }
4039
+ if (passProps.timestampQuerySet !== void 0 || passProps.beginTimestampIndex !== void 0 || passProps.endTimestampIndex !== void 0) {
4040
+ return passProps;
4041
+ }
4042
+ const beginTimestampIndex = this._timeProfilingSlotCount;
4043
+ if (beginTimestampIndex + 1 >= this._timeProfilingQuerySet.props.count) {
4044
+ return passProps;
4045
+ }
4046
+ this._timeProfilingSlotCount += 2;
4047
+ return {
4048
+ ...passProps,
4049
+ timestampQuerySet: this._timeProfilingQuerySet,
4050
+ beginTimestampIndex,
4051
+ endTimestampIndex: beginTimestampIndex + 1
4052
+ };
4053
+ }
4054
+ _supportsTimestampQueries() {
4055
+ return this.device.features.has("timestamp-query");
3177
4056
  }
3178
4057
  };
3179
4058
  var CommandEncoder = _CommandEncoder;
@@ -3182,7 +4061,8 @@ ${htmlLog}
3182
4061
  // beginComputePass(optional GPUComputePassDescriptor descriptor = {}): GPUComputePassEncoder;
3183
4062
  __publicField(CommandEncoder, "defaultProps", {
3184
4063
  ...Resource.defaultProps,
3185
- measureExecutionTime: void 0
4064
+ measureExecutionTime: void 0,
4065
+ timeProfilingQuerySet: void 0
3186
4066
  });
3187
4067
 
3188
4068
  // ../core/src/adapter/resources/command-buffer.ts
@@ -3201,11 +4081,20 @@ ${htmlLog}
3201
4081
 
3202
4082
  // ../core/src/shadertypes/data-types/decode-shader-types.ts
3203
4083
  function getVariableShaderTypeInfo(format) {
3204
- const decoded = UNIFORM_FORMATS[format];
4084
+ const resolvedFormat = resolveVariableShaderTypeAlias(format);
4085
+ const decoded = UNIFORM_FORMATS[resolvedFormat];
4086
+ if (!decoded) {
4087
+ throw new Error(`Unsupported variable shader type: ${format}`);
4088
+ }
3205
4089
  return decoded;
3206
4090
  }
3207
4091
  function getAttributeShaderTypeInfo(attributeType) {
3208
- const [primitiveType, components] = TYPE_INFO[attributeType];
4092
+ const resolvedAttributeType = resolveAttributeShaderTypeAlias(attributeType);
4093
+ const decoded = TYPE_INFO[resolvedAttributeType];
4094
+ if (!decoded) {
4095
+ throw new Error(`Unsupported attribute shader type: ${attributeType}`);
4096
+ }
4097
+ const [primitiveType, components] = decoded;
3209
4098
  const integer = primitiveType === "i32" || primitiveType === "u32";
3210
4099
  const signed = primitiveType !== "u32";
3211
4100
  const byteLength = PRIMITIVE_TYPE_SIZES[primitiveType] * components;
@@ -3217,6 +4106,12 @@ ${htmlLog}
3217
4106
  signed
3218
4107
  };
3219
4108
  }
4109
+ function resolveAttributeShaderTypeAlias(alias) {
4110
+ return WGSL_ATTRIBUTE_TYPE_ALIAS_MAP[alias] || alias;
4111
+ }
4112
+ function resolveVariableShaderTypeAlias(alias) {
4113
+ return WGSL_VARIABLE_TYPE_ALIAS_MAP[alias] || alias;
4114
+ }
3220
4115
  var PRIMITIVE_TYPE_SIZES = {
3221
4116
  f32: 4,
3222
4117
  f16: 2,
@@ -3541,7 +4436,9 @@ ${htmlLog}
3541
4436
 
3542
4437
  // ../core/src/adapter/resources/fence.ts
3543
4438
  var _Fence = class extends Resource {
3544
- [Symbol.toStringTag] = "WEBGLFence";
4439
+ get [Symbol.toStringTag]() {
4440
+ return "Fence";
4441
+ }
3545
4442
  constructor(device, props = {}) {
3546
4443
  super(device, props, _Fence.defaultProps);
3547
4444
  }
@@ -3701,20 +4598,26 @@ ${htmlLog}
3701
4598
  }
3702
4599
 
3703
4600
  // ../core/src/utils/array-equal.ts
4601
+ var MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH = 128;
3704
4602
  function arrayEqual(a, b, limit = 16) {
3705
- if (a !== b) {
3706
- return false;
4603
+ if (a === b) {
4604
+ return true;
3707
4605
  }
3708
4606
  const arrayA = a;
3709
4607
  const arrayB = b;
3710
- if (!isNumberArray(arrayA)) {
4608
+ if (!isNumberArray(arrayA) || !isNumberArray(arrayB)) {
3711
4609
  return false;
3712
4610
  }
3713
- if (isNumberArray(arrayB) && arrayA.length === arrayB.length) {
3714
- for (let i = 0; i < arrayA.length; ++i) {
3715
- if (arrayB[i] !== arrayA[i]) {
3716
- return false;
3717
- }
4611
+ if (arrayA.length !== arrayB.length) {
4612
+ return false;
4613
+ }
4614
+ const maxCompareLength = Math.min(limit, MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH);
4615
+ if (arrayA.length > maxCompareLength) {
4616
+ return false;
4617
+ }
4618
+ for (let i = 0; i < arrayA.length; ++i) {
4619
+ if (arrayB[i] !== arrayA[i]) {
4620
+ return false;
3718
4621
  }
3719
4622
  }
3720
4623
  return true;
@@ -3939,7 +4842,7 @@ ${htmlLog}
3939
4842
  let bitOffsetWithinPixel = 0;
3940
4843
  const channels = [];
3941
4844
  for (let i = 0; i < 4; i++) {
3942
- const bits = bitsPerChannel[i];
4845
+ const bits = bitsPerChannel[i] ?? 0;
3943
4846
  if (bits <= 0) {
3944
4847
  channels.push(0);
3945
4848
  } else {
@@ -3948,14 +4851,14 @@ ${htmlLog}
3948
4851
  bitOffsetWithinPixel += bits;
3949
4852
  }
3950
4853
  }
3951
- return [channels[0], channels[1], channels[2], channels[3]];
4854
+ return [channels[0] ?? 0, channels[1] ?? 0, channels[2] ?? 0, channels[3] ?? 0];
3952
4855
  }
3953
4856
  function writePixel(dataView, bitOffset, bitsPerChannel, pixel) {
3954
4857
  let currentBitOffset = bitOffset;
3955
4858
  for (let channel = 0; channel < 4; channel++) {
3956
- const bits = bitsPerChannel[channel];
4859
+ const bits = bitsPerChannel[channel] ?? 0;
3957
4860
  const maxValue = (1 << bits) - 1;
3958
- const channelValue = pixel[channel] & maxValue;
4861
+ const channelValue = (pixel[channel] ?? 0) & maxValue;
3959
4862
  writeBitsToDataView(dataView, currentBitOffset, bits, channelValue);
3960
4863
  currentBitOffset += bits;
3961
4864
  }