@luma.gl/effects 9.2.6 → 9.3.0-alpha.11

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 (101) hide show
  1. package/dist/dist.dev.js +2734 -644
  2. package/dist/dist.min.js +10 -9
  3. package/dist/index.cjs +753 -302
  4. package/dist/index.cjs.map +3 -3
  5. package/dist/passes/postprocessing/fxaa/fxaa.d.ts +1 -0
  6. package/dist/passes/postprocessing/fxaa/fxaa.d.ts.map +1 -1
  7. package/dist/passes/postprocessing/fxaa/fxaa.js +287 -0
  8. package/dist/passes/postprocessing/fxaa/fxaa.js.map +1 -1
  9. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.d.ts +2 -2
  10. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.d.ts.map +1 -1
  11. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.js +6 -7
  12. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.js.map +1 -1
  13. package/dist/passes/postprocessing/image-adjust-filters/denoise.d.ts +2 -2
  14. package/dist/passes/postprocessing/image-adjust-filters/denoise.d.ts.map +1 -1
  15. package/dist/passes/postprocessing/image-adjust-filters/denoise.js +32 -24
  16. package/dist/passes/postprocessing/image-adjust-filters/denoise.js.map +1 -1
  17. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.d.ts +2 -2
  18. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.d.ts.map +1 -1
  19. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.js +26 -33
  20. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.js.map +1 -1
  21. package/dist/passes/postprocessing/image-adjust-filters/noise.d.ts +2 -2
  22. package/dist/passes/postprocessing/image-adjust-filters/noise.d.ts.map +1 -1
  23. package/dist/passes/postprocessing/image-adjust-filters/noise.js +10 -9
  24. package/dist/passes/postprocessing/image-adjust-filters/noise.js.map +1 -1
  25. package/dist/passes/postprocessing/image-adjust-filters/sepia.d.ts +2 -2
  26. package/dist/passes/postprocessing/image-adjust-filters/sepia.d.ts.map +1 -1
  27. package/dist/passes/postprocessing/image-adjust-filters/sepia.js +12 -11
  28. package/dist/passes/postprocessing/image-adjust-filters/sepia.js.map +1 -1
  29. package/dist/passes/postprocessing/image-adjust-filters/vibrance.d.ts +2 -2
  30. package/dist/passes/postprocessing/image-adjust-filters/vibrance.js +10 -10
  31. package/dist/passes/postprocessing/image-adjust-filters/vignette.d.ts +2 -2
  32. package/dist/passes/postprocessing/image-adjust-filters/vignette.d.ts.map +1 -1
  33. package/dist/passes/postprocessing/image-adjust-filters/vignette.js +11 -15
  34. package/dist/passes/postprocessing/image-adjust-filters/vignette.js.map +1 -1
  35. package/dist/passes/postprocessing/image-blur-filters/tiltshift.d.ts +3 -3
  36. package/dist/passes/postprocessing/image-blur-filters/tiltshift.d.ts.map +1 -1
  37. package/dist/passes/postprocessing/image-blur-filters/tiltshift.js +36 -18
  38. package/dist/passes/postprocessing/image-blur-filters/tiltshift.js.map +1 -1
  39. package/dist/passes/postprocessing/image-blur-filters/triangleblur.d.ts +3 -3
  40. package/dist/passes/postprocessing/image-blur-filters/triangleblur.d.ts.map +1 -1
  41. package/dist/passes/postprocessing/image-blur-filters/triangleblur.js +27 -18
  42. package/dist/passes/postprocessing/image-blur-filters/triangleblur.js.map +1 -1
  43. package/dist/passes/postprocessing/image-blur-filters/zoomblur.d.ts +3 -3
  44. package/dist/passes/postprocessing/image-blur-filters/zoomblur.d.ts.map +1 -1
  45. package/dist/passes/postprocessing/image-blur-filters/zoomblur.js +22 -13
  46. package/dist/passes/postprocessing/image-blur-filters/zoomblur.js.map +1 -1
  47. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.d.ts +2 -2
  48. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.d.ts.map +1 -1
  49. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.js +20 -18
  50. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.js.map +1 -1
  51. package/dist/passes/postprocessing/image-fun-filters/dotscreen.d.ts +2 -2
  52. package/dist/passes/postprocessing/image-fun-filters/dotscreen.js +12 -12
  53. package/dist/passes/postprocessing/image-fun-filters/edgework.d.ts +3 -3
  54. package/dist/passes/postprocessing/image-fun-filters/edgework.d.ts.map +1 -1
  55. package/dist/passes/postprocessing/image-fun-filters/edgework.js +85 -14
  56. package/dist/passes/postprocessing/image-fun-filters/edgework.js.map +1 -1
  57. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.d.ts +2 -2
  58. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.d.ts.map +1 -1
  59. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.js +35 -23
  60. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.js.map +1 -1
  61. package/dist/passes/postprocessing/image-fun-filters/ink.d.ts +2 -2
  62. package/dist/passes/postprocessing/image-fun-filters/ink.d.ts.map +1 -1
  63. package/dist/passes/postprocessing/image-fun-filters/ink.js +26 -17
  64. package/dist/passes/postprocessing/image-fun-filters/ink.js.map +1 -1
  65. package/dist/passes/postprocessing/image-fun-filters/magnify.d.ts +2 -2
  66. package/dist/passes/postprocessing/image-fun-filters/magnify.d.ts.map +1 -1
  67. package/dist/passes/postprocessing/image-fun-filters/magnify.js +23 -13
  68. package/dist/passes/postprocessing/image-fun-filters/magnify.js.map +1 -1
  69. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.d.ts +3 -3
  70. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.d.ts.map +1 -1
  71. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.js +28 -14
  72. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.js.map +1 -1
  73. package/dist/passes/postprocessing/image-warp-filters/swirl.d.ts +3 -3
  74. package/dist/passes/postprocessing/image-warp-filters/swirl.d.ts.map +1 -1
  75. package/dist/passes/postprocessing/image-warp-filters/swirl.js +21 -16
  76. package/dist/passes/postprocessing/image-warp-filters/swirl.js.map +1 -1
  77. package/dist/passes/postprocessing/image-warp-filters/warp.d.ts +1 -1
  78. package/dist/passes/postprocessing/image-warp-filters/warp.d.ts.map +1 -1
  79. package/dist/passes/postprocessing/image-warp-filters/warp.js +9 -4
  80. package/dist/passes/postprocessing/image-warp-filters/warp.js.map +1 -1
  81. package/package.json +4 -5
  82. package/src/passes/postprocessing/fxaa/fxaa.ts +288 -0
  83. package/src/passes/postprocessing/image-adjust-filters/brightnesscontrast.ts +6 -7
  84. package/src/passes/postprocessing/image-adjust-filters/denoise.ts +34 -26
  85. package/src/passes/postprocessing/image-adjust-filters/huesaturation.ts +28 -35
  86. package/src/passes/postprocessing/image-adjust-filters/noise.ts +10 -9
  87. package/src/passes/postprocessing/image-adjust-filters/sepia.ts +12 -11
  88. package/src/passes/postprocessing/image-adjust-filters/vibrance.ts +10 -10
  89. package/src/passes/postprocessing/image-adjust-filters/vignette.ts +11 -15
  90. package/src/passes/postprocessing/image-blur-filters/tiltshift.ts +38 -20
  91. package/src/passes/postprocessing/image-blur-filters/triangleblur.ts +27 -18
  92. package/src/passes/postprocessing/image-blur-filters/zoomblur.ts +23 -14
  93. package/src/passes/postprocessing/image-fun-filters/colorhalftone.ts +20 -18
  94. package/src/passes/postprocessing/image-fun-filters/dotscreen.ts +12 -12
  95. package/src/passes/postprocessing/image-fun-filters/edgework.ts +86 -15
  96. package/src/passes/postprocessing/image-fun-filters/hexagonalpixelate.ts +39 -27
  97. package/src/passes/postprocessing/image-fun-filters/ink.ts +26 -17
  98. package/src/passes/postprocessing/image-fun-filters/magnify.ts +23 -13
  99. package/src/passes/postprocessing/image-warp-filters/bulgepinch.ts +28 -14
  100. package/src/passes/postprocessing/image-warp-filters/swirl.ts +21 -16
  101. package/src/passes/postprocessing/image-warp-filters/warp.ts +9 -4
package/dist/dist.dev.js CHANGED
@@ -64,39 +64,51 @@ var __exports__ = (() => {
64
64
  DeviceFeatures: () => DeviceFeatures,
65
65
  DeviceLimits: () => DeviceLimits,
66
66
  ExternalTexture: () => ExternalTexture,
67
+ Fence: () => Fence,
67
68
  Framebuffer: () => Framebuffer,
69
+ PipelineFactory: () => PipelineFactory,
68
70
  PipelineLayout: () => PipelineLayout,
71
+ PresentationContext: () => PresentationContext,
69
72
  QuerySet: () => QuerySet,
70
73
  RenderPass: () => RenderPass,
71
74
  RenderPipeline: () => RenderPipeline,
72
75
  Resource: () => Resource,
73
76
  Sampler: () => Sampler,
74
77
  Shader: () => Shader,
78
+ ShaderBlockWriter: () => ShaderBlockWriter,
79
+ ShaderFactory: () => ShaderFactory,
80
+ SharedRenderPipeline: () => SharedRenderPipeline,
75
81
  Texture: () => Texture,
76
- TextureFormatDecoder: () => TextureFormatDecoder,
77
82
  TextureView: () => TextureView,
78
83
  TransformFeedback: () => TransformFeedback,
79
84
  UniformBlock: () => UniformBlock,
80
- UniformBufferLayout: () => UniformBufferLayout,
81
85
  UniformStore: () => UniformStore,
82
86
  VertexArray: () => VertexArray,
87
+ _getDefaultBindGroupFactory: () => _getDefaultBindGroupFactory,
83
88
  _getTextureFormatDefinition: () => getTextureFormatDefinition,
84
89
  _getTextureFormatTable: () => getTextureFormatTable,
90
+ assert: () => assert2,
91
+ assertDefined: () => assertDefined,
92
+ dataTypeDecoder: () => dataTypeDecoder,
93
+ flattenBindingsByGroup: () => flattenBindingsByGroup,
85
94
  getAttributeInfosFromLayouts: () => getAttributeInfosFromLayouts,
86
95
  getAttributeShaderTypeInfo: () => getAttributeShaderTypeInfo,
87
- getDataType: () => getDataType,
88
- getDataTypeInfo: () => getDataTypeInfo,
89
- getNormalizedDataType: () => getNormalizedDataType,
96
+ getExternalImageSize: () => getExternalImageSize,
90
97
  getScratchArray: () => getScratchArray,
98
+ getShaderLayoutBinding: () => getShaderLayoutBinding,
99
+ getTextureImageView: () => getTextureImageView,
91
100
  getTypedArrayConstructor: () => getTypedArrayConstructor,
92
101
  getVariableShaderTypeInfo: () => getVariableShaderTypeInfo,
93
- getVertexFormatFromAttribute: () => getVertexFormatFromAttribute,
94
- getVertexFormatInfo: () => getVertexFormatInfo,
102
+ isExternalImage: () => isExternalImage,
95
103
  log: () => log,
96
104
  luma: () => luma,
97
- makeVertexFormat: () => makeVertexFormat,
105
+ makeShaderBlockLayout: () => makeShaderBlockLayout,
106
+ normalizeBindingsByGroup: () => normalizeBindingsByGroup,
98
107
  readPixel: () => readPixel,
108
+ setTextureImageData: () => setTextureImageData,
109
+ shaderTypeDecoder: () => shaderTypeDecoder,
99
110
  textureFormatDecoder: () => textureFormatDecoder,
111
+ vertexFormatDecoder: () => vertexFormatDecoder,
100
112
  writePixel: () => writePixel
101
113
  });
102
114
 
@@ -294,6 +306,24 @@ var __exports__ = (() => {
294
306
  };
295
307
 
296
308
  // ../core/src/utils/stats-manager.ts
309
+ var GPU_TIME_AND_MEMORY_STATS = "GPU Time and Memory";
310
+ var GPU_TIME_AND_MEMORY_STAT_ORDER = [
311
+ "Adapter",
312
+ "GPU",
313
+ "GPU Type",
314
+ "GPU Backend",
315
+ "Frame Rate",
316
+ "CPU Time",
317
+ "GPU Time",
318
+ "GPU Memory",
319
+ "Buffer Memory",
320
+ "Texture Memory",
321
+ "Referenced Buffer Memory",
322
+ "Referenced Texture Memory",
323
+ "Swap Chain Texture"
324
+ ];
325
+ var ORDERED_STATS_CACHE = /* @__PURE__ */ new WeakMap();
326
+ var ORDERED_STAT_NAME_SET_CACHE = /* @__PURE__ */ new WeakMap();
297
327
  var StatsManager = class {
298
328
  stats = /* @__PURE__ */ new Map();
299
329
  getStats(name2) {
@@ -303,10 +333,50 @@ var __exports__ = (() => {
303
333
  if (!this.stats.has(name2)) {
304
334
  this.stats.set(name2, new Stats({ id: name2 }));
305
335
  }
306
- return this.stats.get(name2);
336
+ const stats = this.stats.get(name2);
337
+ if (name2 === GPU_TIME_AND_MEMORY_STATS) {
338
+ initializeStats(stats, GPU_TIME_AND_MEMORY_STAT_ORDER);
339
+ }
340
+ return stats;
307
341
  }
308
342
  };
309
343
  var lumaStats = new StatsManager();
344
+ function initializeStats(stats, orderedStatNames) {
345
+ const statsMap = stats.stats;
346
+ let addedOrderedStat = false;
347
+ for (const statName of orderedStatNames) {
348
+ if (!statsMap[statName]) {
349
+ stats.get(statName);
350
+ addedOrderedStat = true;
351
+ }
352
+ }
353
+ const statCount = Object.keys(statsMap).length;
354
+ const cachedStats = ORDERED_STATS_CACHE.get(stats);
355
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
356
+ return;
357
+ }
358
+ const reorderedStats = {};
359
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE.get(orderedStatNames);
360
+ if (!orderedStatNamesSet) {
361
+ orderedStatNamesSet = new Set(orderedStatNames);
362
+ ORDERED_STAT_NAME_SET_CACHE.set(orderedStatNames, orderedStatNamesSet);
363
+ }
364
+ for (const statName of orderedStatNames) {
365
+ if (statsMap[statName]) {
366
+ reorderedStats[statName] = statsMap[statName];
367
+ }
368
+ }
369
+ for (const [statName, stat] of Object.entries(statsMap)) {
370
+ if (!orderedStatNamesSet.has(statName)) {
371
+ reorderedStats[statName] = stat;
372
+ }
373
+ }
374
+ for (const statName of Object.keys(statsMap)) {
375
+ delete statsMap[statName];
376
+ }
377
+ Object.assign(statsMap, reorderedStats);
378
+ ORDERED_STATS_CACHE.set(stats, { orderedStatNames, statCount });
379
+ }
310
380
 
311
381
  // ../../node_modules/@probe.gl/env/dist/lib/globals.js
312
382
  var window_ = globalThis;
@@ -338,7 +408,139 @@ var __exports__ = (() => {
338
408
  }
339
409
 
340
410
  // ../../node_modules/@probe.gl/env/dist/index.js
341
- var VERSION = true ? "4.1.0" : "untranspiled source";
411
+ var VERSION = true ? "4.1.1" : "untranspiled source";
412
+
413
+ // ../../node_modules/@probe.gl/log/dist/utils/assert.js
414
+ function assert(condition, message) {
415
+ if (!condition) {
416
+ throw new Error(message || "Assertion failed");
417
+ }
418
+ }
419
+
420
+ // ../../node_modules/@probe.gl/log/dist/loggers/log-utils.js
421
+ function normalizeLogLevel(logLevel) {
422
+ if (!logLevel) {
423
+ return 0;
424
+ }
425
+ let resolvedLevel;
426
+ switch (typeof logLevel) {
427
+ case "number":
428
+ resolvedLevel = logLevel;
429
+ break;
430
+ case "object":
431
+ resolvedLevel = logLevel.logLevel || logLevel.priority || 0;
432
+ break;
433
+ default:
434
+ return 0;
435
+ }
436
+ assert(Number.isFinite(resolvedLevel) && resolvedLevel >= 0);
437
+ return resolvedLevel;
438
+ }
439
+ function normalizeArguments(opts) {
440
+ const { logLevel, message } = opts;
441
+ opts.logLevel = normalizeLogLevel(logLevel);
442
+ const args = opts.args ? Array.from(opts.args) : [];
443
+ while (args.length && args.shift() !== message) {
444
+ }
445
+ switch (typeof logLevel) {
446
+ case "string":
447
+ case "function":
448
+ if (message !== void 0) {
449
+ args.unshift(message);
450
+ }
451
+ opts.message = logLevel;
452
+ break;
453
+ case "object":
454
+ Object.assign(opts, logLevel);
455
+ break;
456
+ default:
457
+ }
458
+ if (typeof opts.message === "function") {
459
+ opts.message = opts.message();
460
+ }
461
+ const messageType = typeof opts.message;
462
+ assert(messageType === "string" || messageType === "object");
463
+ return Object.assign(opts, { args }, opts.opts);
464
+ }
465
+
466
+ // ../../node_modules/@probe.gl/log/dist/loggers/base-log.js
467
+ var noop = () => {
468
+ };
469
+ var BaseLog = class {
470
+ constructor({ level = 0 } = {}) {
471
+ this.userData = {};
472
+ this._onceCache = /* @__PURE__ */ new Set();
473
+ this._level = level;
474
+ }
475
+ set level(newLevel) {
476
+ this.setLevel(newLevel);
477
+ }
478
+ get level() {
479
+ return this.getLevel();
480
+ }
481
+ setLevel(level) {
482
+ this._level = level;
483
+ return this;
484
+ }
485
+ getLevel() {
486
+ return this._level;
487
+ }
488
+ // Unconditional logging
489
+ warn(message, ...args) {
490
+ return this._log("warn", 0, message, args, { once: true });
491
+ }
492
+ error(message, ...args) {
493
+ return this._log("error", 0, message, args);
494
+ }
495
+ // Conditional logging
496
+ log(logLevel, message, ...args) {
497
+ return this._log("log", logLevel, message, args);
498
+ }
499
+ info(logLevel, message, ...args) {
500
+ return this._log("info", logLevel, message, args);
501
+ }
502
+ once(logLevel, message, ...args) {
503
+ return this._log("once", logLevel, message, args, { once: true });
504
+ }
505
+ _log(type, logLevel, message, args, options = {}) {
506
+ const normalized = normalizeArguments({
507
+ logLevel,
508
+ message,
509
+ args: this._buildArgs(logLevel, message, args),
510
+ opts: options
511
+ });
512
+ return this._createLogFunction(type, normalized, options);
513
+ }
514
+ _buildArgs(logLevel, message, args) {
515
+ return [logLevel, message, ...args];
516
+ }
517
+ _createLogFunction(type, normalized, options) {
518
+ if (!this._shouldLog(normalized.logLevel)) {
519
+ return noop;
520
+ }
521
+ const tag = this._getOnceTag(options.tag ?? normalized.tag ?? normalized.message);
522
+ if ((options.once || normalized.once) && tag !== void 0) {
523
+ if (this._onceCache.has(tag)) {
524
+ return noop;
525
+ }
526
+ this._onceCache.add(tag);
527
+ }
528
+ return this._emit(type, normalized);
529
+ }
530
+ _shouldLog(logLevel) {
531
+ return this.getLevel() >= normalizeLogLevel(logLevel);
532
+ }
533
+ _getOnceTag(tag) {
534
+ if (tag === void 0) {
535
+ return void 0;
536
+ }
537
+ try {
538
+ return typeof tag === "string" ? tag : String(tag);
539
+ } catch {
540
+ return void 0;
541
+ }
542
+ }
543
+ };
342
544
 
343
545
  // ../../node_modules/@probe.gl/log/dist/utils/local-storage.js
344
546
  function getStorage(type) {
@@ -457,13 +659,6 @@ var __exports__ = (() => {
457
659
  }
458
660
  }
459
661
 
460
- // ../../node_modules/@probe.gl/log/dist/utils/assert.js
461
- function assert(condition, message) {
462
- if (!condition) {
463
- throw new Error(message || "Assertion failed");
464
- }
465
- }
466
-
467
662
  // ../../node_modules/@probe.gl/log/dist/utils/hi-res-timestamp.js
468
663
  function getHiResTimestamp2() {
469
664
  let timestamp;
@@ -478,7 +673,7 @@ var __exports__ = (() => {
478
673
  return timestamp;
479
674
  }
480
675
 
481
- // ../../node_modules/@probe.gl/log/dist/log.js
676
+ // ../../node_modules/@probe.gl/log/dist/loggers/probe-log.js
482
677
  var originalConsole = {
483
678
  debug: isBrowser() ? console.debug || console.log : console.log,
484
679
  log: console.log,
@@ -490,12 +685,9 @@ var __exports__ = (() => {
490
685
  enabled: true,
491
686
  level: 0
492
687
  };
493
- function noop() {
494
- }
495
- var cache = {};
496
- var ONCE = { once: true };
497
- var Log = class {
688
+ var ProbeLog = class extends BaseLog {
498
689
  constructor({ id } = { id: "" }) {
690
+ super({ level: 0 });
499
691
  this.VERSION = VERSION;
500
692
  this._startTs = getHiResTimestamp2();
501
693
  this._deltaTs = getHiResTimestamp2();
@@ -503,22 +695,16 @@ var __exports__ = (() => {
503
695
  this.LOG_THROTTLE_TIMEOUT = 0;
504
696
  this.id = id;
505
697
  this.userData = {};
506
- this._storage = new LocalStorage(`__probe-${this.id}__`, DEFAULT_LOG_CONFIGURATION);
698
+ this._storage = new LocalStorage(`__probe-${this.id}__`, { [this.id]: DEFAULT_LOG_CONFIGURATION });
507
699
  this.timeStamp(`${this.id} started`);
508
700
  autobind(this);
509
701
  Object.seal(this);
510
702
  }
511
- set level(newLevel) {
512
- this.setLevel(newLevel);
513
- }
514
- get level() {
515
- return this.getLevel();
516
- }
517
703
  isEnabled() {
518
- return this._storage.config.enabled;
704
+ return this._getConfiguration().enabled;
519
705
  }
520
706
  getLevel() {
521
- return this._storage.config.level;
707
+ return this._getConfiguration().level;
522
708
  }
523
709
  /** @return milliseconds, with fractions */
524
710
  getTotal() {
@@ -542,20 +728,20 @@ var __exports__ = (() => {
542
728
  }
543
729
  // Configure
544
730
  enable(enabled = true) {
545
- this._storage.setConfiguration({ enabled });
731
+ this._updateConfiguration({ enabled });
546
732
  return this;
547
733
  }
548
734
  setLevel(level) {
549
- this._storage.setConfiguration({ level });
735
+ this._updateConfiguration({ level });
550
736
  return this;
551
737
  }
552
738
  /** return the current status of the setting */
553
739
  get(setting) {
554
- return this._storage.config[setting];
740
+ return this._getConfiguration()[setting];
555
741
  }
556
742
  // update the status of the setting
557
743
  set(setting, value) {
558
- this._storage.setConfiguration({ [setting]: value });
744
+ this._updateConfiguration({ [setting]: value });
559
745
  }
560
746
  /** Logs the current settings as a table */
561
747
  settings() {
@@ -571,11 +757,16 @@ var __exports__ = (() => {
571
757
  throw new Error(message || "Assertion failed");
572
758
  }
573
759
  }
574
- warn(message) {
575
- return this._getLogFunction(0, message, originalConsole.warn, arguments, ONCE);
760
+ warn(message, ...args) {
761
+ return this._log("warn", 0, message, args, {
762
+ method: originalConsole.warn,
763
+ once: true
764
+ });
576
765
  }
577
- error(message) {
578
- return this._getLogFunction(0, message, originalConsole.error, arguments);
766
+ error(message, ...args) {
767
+ return this._log("error", 0, message, args, {
768
+ method: originalConsole.error
769
+ });
579
770
  }
580
771
  /** Print a deprecation warning */
581
772
  deprecated(oldUsage, newUsage) {
@@ -585,50 +776,63 @@ var __exports__ = (() => {
585
776
  removed(oldUsage, newUsage) {
586
777
  return this.error(`\`${oldUsage}\` has been removed. Use \`${newUsage}\` instead`);
587
778
  }
588
- probe(logLevel, message) {
589
- return this._getLogFunction(logLevel, message, originalConsole.log, arguments, {
779
+ probe(logLevel, message, ...args) {
780
+ return this._log("log", logLevel, message, args, {
781
+ method: originalConsole.log,
590
782
  time: true,
591
783
  once: true
592
784
  });
593
785
  }
594
- log(logLevel, message) {
595
- return this._getLogFunction(logLevel, message, originalConsole.debug, arguments);
786
+ log(logLevel, message, ...args) {
787
+ return this._log("log", logLevel, message, args, {
788
+ method: originalConsole.debug
789
+ });
596
790
  }
597
- info(logLevel, message) {
598
- return this._getLogFunction(logLevel, message, console.info, arguments);
791
+ info(logLevel, message, ...args) {
792
+ return this._log("info", logLevel, message, args, { method: console.info });
599
793
  }
600
- once(logLevel, message) {
601
- return this._getLogFunction(logLevel, message, originalConsole.debug || originalConsole.info, arguments, ONCE);
794
+ once(logLevel, message, ...args) {
795
+ return this._log("once", logLevel, message, args, {
796
+ method: originalConsole.debug || originalConsole.info,
797
+ once: true
798
+ });
602
799
  }
603
800
  /** Logs an object as a table */
604
801
  table(logLevel, table, columns) {
605
802
  if (table) {
606
- return this._getLogFunction(logLevel, table, console.table || noop, columns && [columns], {
803
+ return this._log("table", logLevel, table, columns && [columns] || [], {
804
+ method: console.table || noop,
607
805
  tag: getTableHeader(table)
608
806
  });
609
807
  }
610
808
  return noop;
611
809
  }
612
810
  time(logLevel, message) {
613
- return this._getLogFunction(logLevel, message, console.time ? console.time : console.info);
811
+ return this._log("time", logLevel, message, [], {
812
+ method: console.time ? console.time : console.info
813
+ });
614
814
  }
615
815
  timeEnd(logLevel, message) {
616
- return this._getLogFunction(logLevel, message, console.timeEnd ? console.timeEnd : console.info);
816
+ return this._log("time", logLevel, message, [], {
817
+ method: console.timeEnd ? console.timeEnd : console.info
818
+ });
617
819
  }
618
820
  timeStamp(logLevel, message) {
619
- return this._getLogFunction(logLevel, message, console.timeStamp || noop);
821
+ return this._log("time", logLevel, message, [], {
822
+ method: console.timeStamp || noop
823
+ });
620
824
  }
621
825
  group(logLevel, message, opts = { collapsed: false }) {
622
- const options = normalizeArguments({ logLevel, message, opts });
623
- const { collapsed } = opts;
624
- options.method = (collapsed ? console.groupCollapsed : console.group) || console.info;
625
- return this._getLogFunction(options);
826
+ const method = (opts.collapsed ? console.groupCollapsed : console.group) || console.info;
827
+ return this._log("group", logLevel, message, [], { method });
626
828
  }
627
829
  groupCollapsed(logLevel, message, opts = {}) {
628
830
  return this.group(logLevel, message, Object.assign({}, opts, { collapsed: true }));
629
831
  }
630
832
  groupEnd(logLevel) {
631
- return this._getLogFunction(logLevel, "", console.groupEnd || noop);
833
+ return this._log("groupEnd", logLevel, "", [], {
834
+ method: console.groupEnd || noop
835
+ });
632
836
  }
633
837
  // EXPERIMENTAL
634
838
  withGroup(logLevel, message, func) {
@@ -644,78 +848,34 @@ var __exports__ = (() => {
644
848
  console.trace();
645
849
  }
646
850
  }
647
- // PRIVATE METHODS
648
- /** Deduces log level from a variety of arguments */
649
851
  _shouldLog(logLevel) {
650
- return this.isEnabled() && this.getLevel() >= normalizeLogLevel(logLevel);
651
- }
652
- _getLogFunction(logLevel, message, method, args, opts) {
653
- if (this._shouldLog(logLevel)) {
654
- opts = normalizeArguments({ logLevel, message, args, opts });
655
- method = method || opts.method;
656
- assert(method);
657
- opts.total = this.getTotal();
658
- opts.delta = this.getDelta();
659
- this._deltaTs = getHiResTimestamp2();
660
- const tag = opts.tag || opts.message;
661
- if (opts.once && tag) {
662
- if (!cache[tag]) {
663
- cache[tag] = getHiResTimestamp2();
664
- } else {
665
- return noop;
666
- }
667
- }
668
- message = decorateMessage(this.id, opts.message, opts);
669
- return method.bind(console, message, ...opts.args);
670
- }
671
- return noop;
852
+ return this.isEnabled() && super._shouldLog(logLevel);
672
853
  }
673
- };
674
- Log.VERSION = VERSION;
675
- function normalizeLogLevel(logLevel) {
676
- if (!logLevel) {
677
- return 0;
678
- }
679
- let resolvedLevel;
680
- switch (typeof logLevel) {
681
- case "number":
682
- resolvedLevel = logLevel;
683
- break;
684
- case "object":
685
- resolvedLevel = logLevel.logLevel || logLevel.priority || 0;
686
- break;
687
- default:
688
- return 0;
689
- }
690
- assert(Number.isFinite(resolvedLevel) && resolvedLevel >= 0);
691
- return resolvedLevel;
692
- }
693
- function normalizeArguments(opts) {
694
- const { logLevel, message } = opts;
695
- opts.logLevel = normalizeLogLevel(logLevel);
696
- const args = opts.args ? Array.from(opts.args) : [];
697
- while (args.length && args.shift() !== message) {
854
+ _emit(_type, normalized) {
855
+ const method = normalized.method;
856
+ assert(method);
857
+ normalized.total = this.getTotal();
858
+ normalized.delta = this.getDelta();
859
+ this._deltaTs = getHiResTimestamp2();
860
+ const message = decorateMessage(this.id, normalized.message, normalized);
861
+ return method.bind(console, message, ...normalized.args);
698
862
  }
699
- switch (typeof logLevel) {
700
- case "string":
701
- case "function":
702
- if (message !== void 0) {
703
- args.unshift(message);
704
- }
705
- opts.message = logLevel;
706
- break;
707
- case "object":
708
- Object.assign(opts, logLevel);
709
- break;
710
- default:
863
+ _getConfiguration() {
864
+ if (!this._storage.config[this.id]) {
865
+ this._updateConfiguration(DEFAULT_LOG_CONFIGURATION);
866
+ }
867
+ return this._storage.config[this.id];
711
868
  }
712
- if (typeof opts.message === "function") {
713
- opts.message = opts.message();
869
+ _updateConfiguration(configuration) {
870
+ const currentConfiguration = this._storage.config[this.id] || {
871
+ ...DEFAULT_LOG_CONFIGURATION
872
+ };
873
+ this._storage.setConfiguration({
874
+ [this.id]: { ...currentConfiguration, ...configuration }
875
+ });
714
876
  }
715
- const messageType = typeof opts.message;
716
- assert(messageType === "string" || messageType === "object");
717
- return Object.assign(opts, { args }, opts.opts);
718
- }
877
+ };
878
+ ProbeLog.VERSION = VERSION;
719
879
  function decorateMessage(id, message, opts) {
720
880
  if (typeof message === "string") {
721
881
  const time = opts.time ? leftPad(formatTime(opts.total)) : "";
@@ -737,10 +897,10 @@ var __exports__ = (() => {
737
897
  globalThis.probe = {};
738
898
 
739
899
  // ../../node_modules/@probe.gl/log/dist/index.js
740
- var dist_default = new Log({ id: "@probe.gl/log" });
900
+ var dist_default = new ProbeLog({ id: "@probe.gl/log" });
741
901
 
742
902
  // ../core/src/utils/log.ts
743
- var log = new Log({ id: "luma.gl" });
903
+ var log = new ProbeLog({ id: "luma.gl" });
744
904
 
745
905
  // ../core/src/utils/uid.ts
746
906
  var uidCounters = {};
@@ -751,19 +911,75 @@ var __exports__ = (() => {
751
911
  }
752
912
 
753
913
  // ../core/src/adapter/resources/resource.ts
914
+ var CPU_HOTSPOT_PROFILER_MODULE = "cpu-hotspot-profiler";
915
+ var RESOURCE_COUNTS_STATS = "GPU Resource Counts";
916
+ var LEGACY_RESOURCE_COUNTS_STATS = "Resource Counts";
917
+ var GPU_TIME_AND_MEMORY_STATS2 = "GPU Time and Memory";
918
+ var BASE_RESOURCE_COUNT_ORDER = [
919
+ "Resources",
920
+ "Buffers",
921
+ "Textures",
922
+ "Samplers",
923
+ "TextureViews",
924
+ "Framebuffers",
925
+ "QuerySets",
926
+ "Shaders",
927
+ "RenderPipelines",
928
+ "ComputePipelines",
929
+ "PipelineLayouts",
930
+ "VertexArrays",
931
+ "RenderPasss",
932
+ "ComputePasss",
933
+ "CommandEncoders",
934
+ "CommandBuffers"
935
+ ];
936
+ var WEBGL_RESOURCE_COUNT_ORDER = [
937
+ "Resources",
938
+ "Buffers",
939
+ "Textures",
940
+ "Samplers",
941
+ "TextureViews",
942
+ "Framebuffers",
943
+ "QuerySets",
944
+ "Shaders",
945
+ "RenderPipelines",
946
+ "SharedRenderPipelines",
947
+ "ComputePipelines",
948
+ "PipelineLayouts",
949
+ "VertexArrays",
950
+ "RenderPasss",
951
+ "ComputePasss",
952
+ "CommandEncoders",
953
+ "CommandBuffers"
954
+ ];
955
+ var BASE_RESOURCE_COUNT_STAT_ORDER = BASE_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
956
+ `${resourceType} Created`,
957
+ `${resourceType} Active`
958
+ ]);
959
+ var WEBGL_RESOURCE_COUNT_STAT_ORDER = WEBGL_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
960
+ `${resourceType} Created`,
961
+ `${resourceType} Active`
962
+ ]);
963
+ var ORDERED_STATS_CACHE2 = /* @__PURE__ */ new WeakMap();
964
+ var ORDERED_STAT_NAME_SET_CACHE2 = /* @__PURE__ */ new WeakMap();
754
965
  var Resource = class {
755
966
  toString() {
756
967
  return `${this[Symbol.toStringTag] || this.constructor.name}:"${this.id}"`;
757
968
  }
758
969
  /** props.id, for debugging. */
759
970
  id;
971
+ /** The props that this resource was created with */
760
972
  props;
973
+ /** User data object, reserved for the application */
761
974
  userData = {};
975
+ /** The device that this resource is associated with - TODO can we remove this dup? */
762
976
  _device;
763
977
  /** Whether this resource has been destroyed */
764
978
  destroyed = false;
765
979
  /** For resources that allocate GPU memory */
766
980
  allocatedBytes = 0;
981
+ /** Stats bucket currently holding the tracked allocation */
982
+ allocatedBytesName = null;
767
983
  /** Attached resources will be destroyed when this resource is destroyed. Tracks auto-created "sub" resources. */
768
984
  _attachedResources = /* @__PURE__ */ new Set();
769
985
  /**
@@ -785,6 +1001,9 @@ var __exports__ = (() => {
785
1001
  * destroy can be called on any resource to release it before it is garbage collected.
786
1002
  */
787
1003
  destroy() {
1004
+ if (this.destroyed) {
1005
+ return;
1006
+ }
788
1007
  this.destroyResource();
789
1008
  }
790
1009
  /** @deprecated Use destroy() */
@@ -823,7 +1042,7 @@ var __exports__ = (() => {
823
1042
  }
824
1043
  /** Destroy all owned resources. Make sure the resources are no longer needed before calling. */
825
1044
  destroyAttachedResources() {
826
- for (const resource of Object.values(this._attachedResources)) {
1045
+ for (const resource of this._attachedResources) {
827
1046
  resource.destroy();
828
1047
  }
829
1048
  this._attachedResources = /* @__PURE__ */ new Set();
@@ -831,37 +1050,107 @@ var __exports__ = (() => {
831
1050
  // PROTECTED METHODS
832
1051
  /** Perform all destroy steps. Can be called by derived resources when overriding destroy() */
833
1052
  destroyResource() {
1053
+ if (this.destroyed) {
1054
+ return;
1055
+ }
834
1056
  this.destroyAttachedResources();
835
1057
  this.removeStats();
836
1058
  this.destroyed = true;
837
1059
  }
838
1060
  /** Called by .destroy() to track object destruction. Subclass must call if overriding destroy() */
839
1061
  removeStats() {
840
- const stats = this._device.statsManager.getStats("Resource Counts");
841
- const name2 = this[Symbol.toStringTag];
842
- stats.get(`${name2}s Active`).decrementCount();
1062
+ const profiler = getCpuHotspotProfiler(this._device);
1063
+ const startTime = profiler ? getTimestamp() : 0;
1064
+ const statsObjects = [
1065
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1066
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1067
+ ];
1068
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1069
+ for (const stats of statsObjects) {
1070
+ initializeStats2(stats, orderedStatNames);
1071
+ }
1072
+ const name2 = this.getStatsName();
1073
+ for (const stats of statsObjects) {
1074
+ stats.get("Resources Active").decrementCount();
1075
+ stats.get(`${name2}s Active`).decrementCount();
1076
+ }
1077
+ if (profiler) {
1078
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1079
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1080
+ }
843
1081
  }
844
1082
  /** Called by subclass to track memory allocations */
845
- trackAllocatedMemory(bytes, name2 = this[Symbol.toStringTag]) {
846
- const stats = this._device.statsManager.getStats("Resource Counts");
1083
+ trackAllocatedMemory(bytes, name2 = this.getStatsName()) {
1084
+ const profiler = getCpuHotspotProfiler(this._device);
1085
+ const startTime = profiler ? getTimestamp() : 0;
1086
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
1087
+ if (this.allocatedBytes > 0 && this.allocatedBytesName) {
1088
+ stats.get("GPU Memory").subtractCount(this.allocatedBytes);
1089
+ stats.get(`${this.allocatedBytesName} Memory`).subtractCount(this.allocatedBytes);
1090
+ }
847
1091
  stats.get("GPU Memory").addCount(bytes);
848
1092
  stats.get(`${name2} Memory`).addCount(bytes);
1093
+ if (profiler) {
1094
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1095
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1096
+ }
849
1097
  this.allocatedBytes = bytes;
1098
+ this.allocatedBytesName = name2;
1099
+ }
1100
+ /** Called by subclass to track handle-backed memory allocations separately from owned allocations */
1101
+ trackReferencedMemory(bytes, name2 = this.getStatsName()) {
1102
+ this.trackAllocatedMemory(bytes, `Referenced ${name2}`);
850
1103
  }
851
1104
  /** Called by subclass to track memory deallocations */
852
- trackDeallocatedMemory(name2 = this[Symbol.toStringTag]) {
853
- const stats = this._device.statsManager.getStats("Resource Counts");
1105
+ trackDeallocatedMemory(name2 = this.getStatsName()) {
1106
+ if (this.allocatedBytes === 0) {
1107
+ this.allocatedBytesName = null;
1108
+ return;
1109
+ }
1110
+ const profiler = getCpuHotspotProfiler(this._device);
1111
+ const startTime = profiler ? getTimestamp() : 0;
1112
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
854
1113
  stats.get("GPU Memory").subtractCount(this.allocatedBytes);
855
- stats.get(`${name2} Memory`).subtractCount(this.allocatedBytes);
1114
+ stats.get(`${this.allocatedBytesName || name2} Memory`).subtractCount(this.allocatedBytes);
1115
+ if (profiler) {
1116
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1117
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1118
+ }
856
1119
  this.allocatedBytes = 0;
1120
+ this.allocatedBytesName = null;
1121
+ }
1122
+ /** Called by subclass to deallocate handle-backed memory tracked via trackReferencedMemory() */
1123
+ trackDeallocatedReferencedMemory(name2 = this.getStatsName()) {
1124
+ this.trackDeallocatedMemory(`Referenced ${name2}`);
857
1125
  }
858
1126
  /** Called by resource constructor to track object creation */
859
1127
  addStats() {
860
- const stats = this._device.statsManager.getStats("Resource Counts");
861
- const name2 = this[Symbol.toStringTag];
862
- stats.get("Resources Created").incrementCount();
863
- stats.get(`${name2}s Created`).incrementCount();
864
- stats.get(`${name2}s Active`).incrementCount();
1128
+ const name2 = this.getStatsName();
1129
+ const profiler = getCpuHotspotProfiler(this._device);
1130
+ const startTime = profiler ? getTimestamp() : 0;
1131
+ const statsObjects = [
1132
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1133
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1134
+ ];
1135
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1136
+ for (const stats of statsObjects) {
1137
+ initializeStats2(stats, orderedStatNames);
1138
+ }
1139
+ for (const stats of statsObjects) {
1140
+ stats.get("Resources Created").incrementCount();
1141
+ stats.get("Resources Active").incrementCount();
1142
+ stats.get(`${name2}s Created`).incrementCount();
1143
+ stats.get(`${name2}s Active`).incrementCount();
1144
+ }
1145
+ if (profiler) {
1146
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1147
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1148
+ }
1149
+ recordTransientCanvasResourceCreate(this._device, name2);
1150
+ }
1151
+ /** Canonical resource name used for stats buckets. */
1152
+ getStatsName() {
1153
+ return getCanonicalResourceName(this);
865
1154
  }
866
1155
  };
867
1156
  /** Default properties for resource */
@@ -879,6 +1168,96 @@ var __exports__ = (() => {
879
1168
  }
880
1169
  return mergedProps;
881
1170
  }
1171
+ function initializeStats2(stats, orderedStatNames) {
1172
+ const statsMap = stats.stats;
1173
+ let addedOrderedStat = false;
1174
+ for (const statName of orderedStatNames) {
1175
+ if (!statsMap[statName]) {
1176
+ stats.get(statName);
1177
+ addedOrderedStat = true;
1178
+ }
1179
+ }
1180
+ const statCount = Object.keys(statsMap).length;
1181
+ const cachedStats = ORDERED_STATS_CACHE2.get(stats);
1182
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
1183
+ return;
1184
+ }
1185
+ const reorderedStats = {};
1186
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE2.get(orderedStatNames);
1187
+ if (!orderedStatNamesSet) {
1188
+ orderedStatNamesSet = new Set(orderedStatNames);
1189
+ ORDERED_STAT_NAME_SET_CACHE2.set(orderedStatNames, orderedStatNamesSet);
1190
+ }
1191
+ for (const statName of orderedStatNames) {
1192
+ if (statsMap[statName]) {
1193
+ reorderedStats[statName] = statsMap[statName];
1194
+ }
1195
+ }
1196
+ for (const [statName, stat] of Object.entries(statsMap)) {
1197
+ if (!orderedStatNamesSet.has(statName)) {
1198
+ reorderedStats[statName] = stat;
1199
+ }
1200
+ }
1201
+ for (const statName of Object.keys(statsMap)) {
1202
+ delete statsMap[statName];
1203
+ }
1204
+ Object.assign(statsMap, reorderedStats);
1205
+ ORDERED_STATS_CACHE2.set(stats, { orderedStatNames, statCount });
1206
+ }
1207
+ function getResourceCountStatOrder(device) {
1208
+ return device.type === "webgl" ? WEBGL_RESOURCE_COUNT_STAT_ORDER : BASE_RESOURCE_COUNT_STAT_ORDER;
1209
+ }
1210
+ function getCpuHotspotProfiler(device) {
1211
+ const profiler = device.userData[CPU_HOTSPOT_PROFILER_MODULE];
1212
+ return profiler?.enabled ? profiler : null;
1213
+ }
1214
+ function getTimestamp() {
1215
+ return globalThis.performance?.now?.() ?? Date.now();
1216
+ }
1217
+ function recordTransientCanvasResourceCreate(device, name2) {
1218
+ const profiler = getCpuHotspotProfiler(device);
1219
+ if (!profiler || !profiler.activeDefaultFramebufferAcquireDepth) {
1220
+ return;
1221
+ }
1222
+ profiler.transientCanvasResourceCreates = (profiler.transientCanvasResourceCreates || 0) + 1;
1223
+ switch (name2) {
1224
+ case "Texture":
1225
+ profiler.transientCanvasTextureCreates = (profiler.transientCanvasTextureCreates || 0) + 1;
1226
+ break;
1227
+ case "TextureView":
1228
+ profiler.transientCanvasTextureViewCreates = (profiler.transientCanvasTextureViewCreates || 0) + 1;
1229
+ break;
1230
+ case "Sampler":
1231
+ profiler.transientCanvasSamplerCreates = (profiler.transientCanvasSamplerCreates || 0) + 1;
1232
+ break;
1233
+ case "Framebuffer":
1234
+ profiler.transientCanvasFramebufferCreates = (profiler.transientCanvasFramebufferCreates || 0) + 1;
1235
+ break;
1236
+ default:
1237
+ break;
1238
+ }
1239
+ }
1240
+ function getCanonicalResourceName(resource) {
1241
+ let prototype = Object.getPrototypeOf(resource);
1242
+ while (prototype) {
1243
+ const parentPrototype = Object.getPrototypeOf(prototype);
1244
+ if (!parentPrototype || parentPrototype === Resource.prototype) {
1245
+ return getPrototypeToStringTag(prototype) || resource[Symbol.toStringTag] || resource.constructor.name;
1246
+ }
1247
+ prototype = parentPrototype;
1248
+ }
1249
+ return resource[Symbol.toStringTag] || resource.constructor.name;
1250
+ }
1251
+ function getPrototypeToStringTag(prototype) {
1252
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, Symbol.toStringTag);
1253
+ if (typeof descriptor?.get === "function") {
1254
+ return descriptor.get.call(prototype);
1255
+ }
1256
+ if (typeof descriptor?.value === "string") {
1257
+ return descriptor.value;
1258
+ }
1259
+ return null;
1260
+ }
882
1261
 
883
1262
  // ../core/src/adapter/resources/buffer.ts
884
1263
  var _Buffer = class extends Resource {
@@ -918,18 +1297,26 @@ var __exports__ = (() => {
918
1297
  /** A partial CPU-side copy of the data in this buffer, for debugging purposes */
919
1298
  debugData = new ArrayBuffer(0);
920
1299
  /** This doesn't handle partial non-zero offset updates correctly */
921
- _setDebugData(data, byteOffset, byteLength) {
922
- const arrayBuffer2 = ArrayBuffer.isView(data) ? data.buffer : data;
1300
+ _setDebugData(data, _byteOffset, byteLength) {
1301
+ let arrayBufferView = null;
1302
+ let arrayBuffer2;
1303
+ if (ArrayBuffer.isView(data)) {
1304
+ arrayBufferView = data;
1305
+ arrayBuffer2 = data.buffer;
1306
+ } else {
1307
+ arrayBuffer2 = data;
1308
+ }
923
1309
  const debugDataLength = Math.min(
924
1310
  data ? data.byteLength : byteLength,
925
1311
  _Buffer.DEBUG_DATA_MAX_LENGTH
926
1312
  );
927
1313
  if (arrayBuffer2 === null) {
928
1314
  this.debugData = new ArrayBuffer(debugDataLength);
929
- } else if (byteOffset === 0 && byteLength === arrayBuffer2.byteLength) {
930
- this.debugData = arrayBuffer2.slice(0, debugDataLength);
931
1315
  } else {
932
- this.debugData = arrayBuffer2.slice(byteOffset, byteOffset + debugDataLength);
1316
+ const sourceByteOffset = Math.min(arrayBufferView?.byteOffset || 0, arrayBuffer2.byteLength);
1317
+ const availableByteLength = Math.max(0, arrayBuffer2.byteLength - sourceByteOffset);
1318
+ const copyByteLength = Math.min(debugDataLength, availableByteLength);
1319
+ this.debugData = new Uint8Array(arrayBuffer2, sourceByteOffset, copyByteLength).slice().buffer;
933
1320
  }
934
1321
  }
935
1322
  };
@@ -963,61 +1350,73 @@ var __exports__ = (() => {
963
1350
  onMapped: void 0
964
1351
  });
965
1352
 
966
- // ../core/src/shadertypes/data-types/decode-data-types.ts
967
- function getDataTypeInfo(type) {
968
- const [signedType, primitiveType, byteLength] = NORMALIZED_TYPE_MAP[type];
969
- const normalized = type.includes("norm");
970
- const integer = !normalized && !type.startsWith("float");
971
- const signed = type.startsWith("s");
972
- return {
973
- signedType,
974
- primitiveType,
975
- byteLength,
976
- normalized,
977
- integer,
978
- signed
979
- };
980
- }
981
- function getNormalizedDataType(signedDataType) {
982
- const dataType = signedDataType;
983
- switch (dataType) {
984
- case "uint8":
985
- return "unorm8";
986
- case "sint8":
987
- return "snorm8";
988
- case "uint16":
989
- return "unorm16";
990
- case "sint16":
991
- return "snorm16";
992
- default:
993
- return dataType;
1353
+ // ../core/src/shadertypes/data-types/data-type-decoder.ts
1354
+ var DataTypeDecoder = class {
1355
+ /**
1356
+ * Gets info about a data type constant (signed or normalized)
1357
+ * @returns underlying primitive / signed types, byte length, normalization, integer, signed flags
1358
+ */
1359
+ getDataTypeInfo(type) {
1360
+ const [signedType, primitiveType, byteLength] = NORMALIZED_TYPE_MAP[type];
1361
+ const normalized = type.includes("norm");
1362
+ const integer = !normalized && !type.startsWith("float");
1363
+ const signed = type.startsWith("s");
1364
+ return {
1365
+ signedType,
1366
+ primitiveType,
1367
+ byteLength,
1368
+ normalized,
1369
+ integer,
1370
+ signed
1371
+ // TODO - add webglOnly flag
1372
+ };
994
1373
  }
995
- }
996
- function alignTo(size, count) {
997
- switch (count) {
998
- case 1:
999
- return size;
1000
- case 2:
1001
- return size + size % 2;
1002
- default:
1003
- return size + (4 - size % 4) % 4;
1374
+ /** Build a vertex format from a signed data type and a component */
1375
+ getNormalizedDataType(signedDataType) {
1376
+ const dataType = signedDataType;
1377
+ switch (dataType) {
1378
+ case "uint8":
1379
+ return "unorm8";
1380
+ case "sint8":
1381
+ return "snorm8";
1382
+ case "uint16":
1383
+ return "unorm16";
1384
+ case "sint16":
1385
+ return "snorm16";
1386
+ default:
1387
+ return dataType;
1388
+ }
1004
1389
  }
1005
- }
1006
- function getDataType(arrayOrType) {
1007
- const Constructor = ArrayBuffer.isView(arrayOrType) ? arrayOrType.constructor : arrayOrType;
1008
- if (Constructor === Uint8ClampedArray) {
1009
- return "uint8";
1390
+ /** Align offset to 1, 2 or 4 elements (4, 8 or 16 bytes) */
1391
+ alignTo(size, count) {
1392
+ switch (count) {
1393
+ case 1:
1394
+ return size;
1395
+ case 2:
1396
+ return size + size % 2;
1397
+ default:
1398
+ return size + (4 - size % 4) % 4;
1399
+ }
1010
1400
  }
1011
- const info = Object.values(NORMALIZED_TYPE_MAP).find((entry) => Constructor === entry[4]);
1012
- if (!info) {
1013
- throw new Error(Constructor.name);
1401
+ /** Returns the VariableShaderType that corresponds to a typed array */
1402
+ getDataType(arrayOrType) {
1403
+ const Constructor = ArrayBuffer.isView(arrayOrType) ? arrayOrType.constructor : arrayOrType;
1404
+ if (Constructor === Uint8ClampedArray) {
1405
+ return "uint8";
1406
+ }
1407
+ const info = Object.values(NORMALIZED_TYPE_MAP).find((entry) => Constructor === entry[4]);
1408
+ if (!info) {
1409
+ throw new Error(Constructor.name);
1410
+ }
1411
+ return info[0];
1014
1412
  }
1015
- return info[0];
1016
- }
1017
- function getTypedArrayConstructor(type) {
1018
- const [, , , , Constructor] = NORMALIZED_TYPE_MAP[type];
1019
- return Constructor;
1020
- }
1413
+ /** Returns the TypedArray that corresponds to a shader data type */
1414
+ getTypedArrayConstructor(type) {
1415
+ const [, , , , Constructor] = NORMALIZED_TYPE_MAP[type];
1416
+ return Constructor;
1417
+ }
1418
+ };
1419
+ var dataTypeDecoder = new DataTypeDecoder();
1021
1420
  var NORMALIZED_TYPE_MAP = {
1022
1421
  uint8: ["uint8", "u32", 1, false, Uint8Array],
1023
1422
  sint8: ["sint8", "i32", 1, false, Int8Array],
@@ -1033,87 +1432,138 @@ var __exports__ = (() => {
1033
1432
  sint32: ["sint32", "i32", 4, false, Int32Array]
1034
1433
  };
1035
1434
 
1036
- // ../core/src/shadertypes/vertex-arrays/decode-vertex-format.ts
1037
- function getVertexFormatInfo(format) {
1038
- let webglOnly;
1039
- if (format.endsWith("-webgl")) {
1040
- format.replace("-webgl", "");
1041
- webglOnly = true;
1042
- }
1043
- const [type_, count] = format.split("x");
1044
- const type = type_;
1045
- const components = count ? parseInt(count) : 1;
1046
- const decodedType = getDataTypeInfo(type);
1047
- const result = {
1048
- type,
1049
- components,
1050
- byteLength: decodedType.byteLength * components,
1051
- integer: decodedType.integer,
1052
- signed: decodedType.signed,
1053
- normalized: decodedType.normalized
1054
- };
1055
- if (webglOnly) {
1056
- result.webglOnly = true;
1057
- }
1058
- return result;
1059
- }
1060
- function makeVertexFormat(signedDataType, components, normalized) {
1061
- const dataType = normalized ? getNormalizedDataType(signedDataType) : signedDataType;
1062
- switch (dataType) {
1063
- case "unorm8":
1064
- if (components === 1) {
1065
- return "unorm8";
1066
- }
1067
- if (components === 3) {
1068
- return "unorm8x3-webgl";
1069
- }
1070
- return `${dataType}x${components}`;
1071
- case "snorm8":
1072
- case "uint8":
1073
- case "sint8":
1074
- case "uint16":
1075
- case "sint16":
1076
- case "unorm16":
1077
- case "snorm16":
1078
- case "float16":
1079
- if (components === 1 || components === 3) {
1080
- throw new Error(`size: ${components}`);
1081
- }
1082
- return `${dataType}x${components}`;
1083
- default:
1084
- return components === 1 ? dataType : `${dataType}x${components}`;
1435
+ // ../core/src/shadertypes/vertex-types/vertex-format-decoder.ts
1436
+ var VertexFormatDecoder = class {
1437
+ /**
1438
+ * Decodes a vertex format, returning type, components, byte length and flags (integer, signed, normalized)
1439
+ */
1440
+ getVertexFormatInfo(format) {
1441
+ let webglOnly;
1442
+ if (format.endsWith("-webgl")) {
1443
+ format.replace("-webgl", "");
1444
+ webglOnly = true;
1445
+ }
1446
+ const [type_, count] = format.split("x");
1447
+ const type = type_;
1448
+ const components = count ? parseInt(count) : 1;
1449
+ const decodedType = dataTypeDecoder.getDataTypeInfo(type);
1450
+ const result = {
1451
+ type,
1452
+ components,
1453
+ byteLength: decodedType.byteLength * components,
1454
+ integer: decodedType.integer,
1455
+ signed: decodedType.signed,
1456
+ normalized: decodedType.normalized
1457
+ };
1458
+ if (webglOnly) {
1459
+ result.webglOnly = true;
1460
+ }
1461
+ return result;
1085
1462
  }
1086
- }
1087
- function getVertexFormatFromAttribute(typedArray, size, normalized) {
1088
- if (!size || size > 4) {
1089
- throw new Error(`size ${size}`);
1463
+ /** Build a vertex format from a signed data type and a component */
1464
+ makeVertexFormat(signedDataType, components, normalized) {
1465
+ const dataType = normalized ? dataTypeDecoder.getNormalizedDataType(signedDataType) : signedDataType;
1466
+ switch (dataType) {
1467
+ case "unorm8":
1468
+ if (components === 1) {
1469
+ return "unorm8";
1470
+ }
1471
+ if (components === 3) {
1472
+ return "unorm8x3-webgl";
1473
+ }
1474
+ return `${dataType}x${components}`;
1475
+ case "snorm8":
1476
+ if (components === 1) {
1477
+ return "snorm8";
1478
+ }
1479
+ if (components === 3) {
1480
+ return "snorm8x3-webgl";
1481
+ }
1482
+ return `${dataType}x${components}`;
1483
+ case "uint8":
1484
+ case "sint8":
1485
+ if (components === 1 || components === 3) {
1486
+ throw new Error(`size: ${components}`);
1487
+ }
1488
+ return `${dataType}x${components}`;
1489
+ case "uint16":
1490
+ if (components === 1) {
1491
+ return "uint16";
1492
+ }
1493
+ if (components === 3) {
1494
+ return "uint16x3-webgl";
1495
+ }
1496
+ return `${dataType}x${components}`;
1497
+ case "sint16":
1498
+ if (components === 1) {
1499
+ return "sint16";
1500
+ }
1501
+ if (components === 3) {
1502
+ return "sint16x3-webgl";
1503
+ }
1504
+ return `${dataType}x${components}`;
1505
+ case "unorm16":
1506
+ if (components === 1) {
1507
+ return "unorm16";
1508
+ }
1509
+ if (components === 3) {
1510
+ return "unorm16x3-webgl";
1511
+ }
1512
+ return `${dataType}x${components}`;
1513
+ case "snorm16":
1514
+ if (components === 1) {
1515
+ return "snorm16";
1516
+ }
1517
+ if (components === 3) {
1518
+ return "snorm16x3-webgl";
1519
+ }
1520
+ return `${dataType}x${components}`;
1521
+ case "float16":
1522
+ if (components === 1 || components === 3) {
1523
+ throw new Error(`size: ${components}`);
1524
+ }
1525
+ return `${dataType}x${components}`;
1526
+ default:
1527
+ return components === 1 ? dataType : `${dataType}x${components}`;
1528
+ }
1090
1529
  }
1091
- const components = size;
1092
- const signedDataType = getDataType(typedArray);
1093
- return makeVertexFormat(signedDataType, components, normalized);
1094
- }
1095
- function getCompatibleVertexFormat(opts) {
1096
- let vertexType;
1097
- switch (opts.primitiveType) {
1098
- case "f32":
1099
- vertexType = "float32";
1100
- break;
1101
- case "i32":
1102
- vertexType = "sint32";
1103
- break;
1104
- case "u32":
1105
- vertexType = "uint32";
1106
- break;
1107
- case "f16":
1108
- return opts.components <= 2 ? "float16x2" : "float16x4";
1530
+ /** Get the vertex format for an attribute with TypedArray and size */
1531
+ getVertexFormatFromAttribute(typedArray, size, normalized) {
1532
+ if (!size || size > 4) {
1533
+ throw new Error(`size ${size}`);
1534
+ }
1535
+ const components = size;
1536
+ const signedDataType = dataTypeDecoder.getDataType(typedArray);
1537
+ return this.makeVertexFormat(signedDataType, components, normalized);
1109
1538
  }
1110
- if (opts.components === 1) {
1111
- return vertexType;
1539
+ /**
1540
+ * Return a "default" vertex format for a certain shader data type
1541
+ * The simplest vertex format that matches the shader attribute's data type
1542
+ */
1543
+ getCompatibleVertexFormat(opts) {
1544
+ let vertexType;
1545
+ switch (opts.primitiveType) {
1546
+ case "f32":
1547
+ vertexType = "float32";
1548
+ break;
1549
+ case "i32":
1550
+ vertexType = "sint32";
1551
+ break;
1552
+ case "u32":
1553
+ vertexType = "uint32";
1554
+ break;
1555
+ case "f16":
1556
+ return opts.components <= 2 ? "float16x2" : "float16x4";
1557
+ }
1558
+ if (opts.components === 1) {
1559
+ return vertexType;
1560
+ }
1561
+ return `${vertexType}x${opts.components}`;
1112
1562
  }
1113
- return `${vertexType}x${opts.components}`;
1114
- }
1563
+ };
1564
+ var vertexFormatDecoder = new VertexFormatDecoder();
1115
1565
 
1116
- // ../core/src/shadertypes/textures/texture-format-table.ts
1566
+ // ../core/src/shadertypes/texture-types/texture-format-table.ts
1117
1567
  var texture_compression_bc = "texture-compression-bc";
1118
1568
  var texture_compression_astc = "texture-compression-astc";
1119
1569
  var texture_compression_etc2 = "texture-compression-etc2";
@@ -1124,6 +1574,7 @@ var __exports__ = (() => {
1124
1574
  var float16_renderable = "float16-renderable-webgl";
1125
1575
  var rgb9e5ufloat_renderable = "rgb9e5ufloat-renderable-webgl";
1126
1576
  var snorm8_renderable = "snorm8-renderable-webgl";
1577
+ var norm16_webgl = "norm16-webgl";
1127
1578
  var norm16_renderable = "norm16-renderable-webgl";
1128
1579
  var snorm16_renderable = "snorm16-renderable-webgl";
1129
1580
  var float32_filterable = "float32-filterable";
@@ -1157,16 +1608,16 @@ var __exports__ = (() => {
1157
1608
  "rgba8sint": {},
1158
1609
  "bgra8unorm": {},
1159
1610
  "bgra8unorm-srgb": {},
1160
- "r16unorm": { f: norm16_renderable },
1161
- "rg16unorm": { render: norm16_renderable },
1162
- "rgb16unorm-webgl": { f: norm16_renderable },
1611
+ "r16unorm": { f: norm16_webgl, render: norm16_renderable },
1612
+ "rg16unorm": { f: norm16_webgl, render: norm16_renderable },
1613
+ "rgb16unorm-webgl": { f: norm16_webgl, render: false },
1163
1614
  // rgb not renderable
1164
- "rgba16unorm": { render: norm16_renderable },
1165
- "r16snorm": { f: snorm16_renderable },
1166
- "rg16snorm": { render: snorm16_renderable },
1167
- "rgb16snorm-webgl": { f: norm16_renderable },
1615
+ "rgba16unorm": { f: norm16_webgl, render: norm16_renderable },
1616
+ "r16snorm": { f: norm16_webgl, render: snorm16_renderable },
1617
+ "rg16snorm": { f: norm16_webgl, render: snorm16_renderable },
1618
+ "rgb16snorm-webgl": { f: norm16_webgl, render: false },
1168
1619
  // rgb not renderable
1169
- "rgba16snorm": { render: snorm16_renderable },
1620
+ "rgba16snorm": { f: norm16_webgl, render: snorm16_renderable },
1170
1621
  "r16uint": {},
1171
1622
  "rg16uint": {},
1172
1623
  "rgba16uint": {},
@@ -1269,7 +1720,7 @@ var __exports__ = (() => {
1269
1720
  // WEBGL_compressed_texture_pvrtc
1270
1721
  "pvrtc-rgb4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1271
1722
  "pvrtc-rgba4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1272
- "pvrtc-rbg2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1723
+ "pvrtc-rgb2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1273
1724
  "pvrtc-rgba2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1274
1725
  // WEBGL_compressed_texture_etc1
1275
1726
  "etc1-rbg-unorm-webgl": { f: texture_compression_etc1_webgl },
@@ -1283,7 +1734,10 @@ var __exports__ = (() => {
1283
1734
  ...TEXTURE_FORMAT_COMPRESSED_TABLE
1284
1735
  };
1285
1736
 
1286
- // ../core/src/shadertypes/textures/texture-format-decoder.ts
1737
+ // ../core/src/shadertypes/texture-types/texture-format-decoder.ts
1738
+ var RGB_FORMAT_REGEX = /^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/;
1739
+ var COLOR_FORMAT_PREFIXES = ["rgb", "rgba", "bgra"];
1740
+ var DEPTH_FORMAT_PREFIXES = ["depth", "stencil"];
1287
1741
  var COMPRESSED_TEXTURE_FORMAT_PREFIXES = [
1288
1742
  "bc1",
1289
1743
  "bc2",
@@ -1299,49 +1753,83 @@ var __exports__ = (() => {
1299
1753
  "astc",
1300
1754
  "pvrtc"
1301
1755
  ];
1302
- var RGB_FORMAT_REGEX = /^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/;
1303
1756
  var TextureFormatDecoder = class {
1304
- /** Returns information about a texture format, e.g. attatchment type, components, byte length and flags (integer, signed, normalized) */
1305
- getInfo(format) {
1306
- return getTextureFormatInfo(format);
1307
- }
1308
1757
  /** Checks if a texture format is color */
1309
1758
  isColor(format) {
1310
- return format.startsWith("rgba") || format.startsWith("bgra") || format.startsWith("rgb");
1759
+ return COLOR_FORMAT_PREFIXES.some((prefix) => format.startsWith(prefix));
1311
1760
  }
1312
1761
  /** Checks if a texture format is depth or stencil */
1313
1762
  isDepthStencil(format) {
1314
- return format.startsWith("depth") || format.startsWith("stencil");
1763
+ return DEPTH_FORMAT_PREFIXES.some((prefix) => format.startsWith(prefix));
1315
1764
  }
1316
1765
  /** Checks if a texture format is compressed */
1317
1766
  isCompressed(format) {
1318
1767
  return COMPRESSED_TEXTURE_FORMAT_PREFIXES.some((prefix) => format.startsWith(prefix));
1319
1768
  }
1320
- /**
1321
- * Returns the "static" capabilities of a texture format.
1322
- * @note Needs to be checked against current device
1323
- */
1769
+ /** Returns information about a texture format, e.g. attachment type, components, byte length and flags (integer, signed, normalized) */
1770
+ getInfo(format) {
1771
+ return getTextureFormatInfo(format);
1772
+ }
1773
+ /** "static" capabilities of a texture format. @note Needs to be adjusted against current device */
1324
1774
  getCapabilities(format) {
1325
- const info = getTextureFormatDefinition(format);
1326
- const formatCapabilities = {
1327
- format,
1328
- create: info.f ?? true,
1329
- render: info.render ?? true,
1330
- filter: info.filter ?? true,
1331
- blend: info.blend ?? true,
1332
- store: info.store ?? true
1333
- };
1334
- const formatInfo = getTextureFormatInfo(format);
1335
- const isDepthStencil = format.startsWith("depth") || format.startsWith("stencil");
1336
- const isSigned = formatInfo?.signed;
1337
- const isInteger = formatInfo?.integer;
1338
- const isWebGLSpecific = formatInfo?.webgl;
1339
- formatCapabilities.render &&= !isSigned;
1340
- formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1341
- return formatCapabilities;
1775
+ return getTextureFormatCapabilities(format);
1776
+ }
1777
+ /** Computes the memory layout for a texture, in particular including row byte alignment */
1778
+ computeMemoryLayout(opts) {
1779
+ return computeTextureMemoryLayout(opts);
1342
1780
  }
1343
1781
  };
1344
1782
  var textureFormatDecoder = new TextureFormatDecoder();
1783
+ function computeTextureMemoryLayout({
1784
+ format,
1785
+ width,
1786
+ height,
1787
+ depth,
1788
+ byteAlignment
1789
+ }) {
1790
+ const formatInfo = textureFormatDecoder.getInfo(format);
1791
+ const {
1792
+ bytesPerPixel,
1793
+ bytesPerBlock = bytesPerPixel,
1794
+ blockWidth = 1,
1795
+ blockHeight = 1,
1796
+ compressed = false
1797
+ } = formatInfo;
1798
+ const blockColumns = compressed ? Math.ceil(width / blockWidth) : width;
1799
+ const blockRows = compressed ? Math.ceil(height / blockHeight) : height;
1800
+ const unpaddedBytesPerRow = blockColumns * bytesPerBlock;
1801
+ const bytesPerRow = Math.ceil(unpaddedBytesPerRow / byteAlignment) * byteAlignment;
1802
+ const rowsPerImage = blockRows;
1803
+ const byteLength = bytesPerRow * rowsPerImage * depth;
1804
+ return {
1805
+ bytesPerPixel,
1806
+ bytesPerRow,
1807
+ rowsPerImage,
1808
+ depthOrArrayLayers: depth,
1809
+ bytesPerImage: bytesPerRow * rowsPerImage,
1810
+ byteLength
1811
+ };
1812
+ }
1813
+ function getTextureFormatCapabilities(format) {
1814
+ const info = getTextureFormatDefinition(format);
1815
+ const formatCapabilities = {
1816
+ format,
1817
+ create: info.f ?? true,
1818
+ render: info.render ?? true,
1819
+ filter: info.filter ?? true,
1820
+ blend: info.blend ?? true,
1821
+ store: info.store ?? true
1822
+ };
1823
+ const formatInfo = getTextureFormatInfo(format);
1824
+ const isDepthStencil = format.startsWith("depth") || format.startsWith("stencil");
1825
+ const isSigned = formatInfo?.signed;
1826
+ const isInteger = formatInfo?.integer;
1827
+ const isWebGLSpecific = formatInfo?.webgl;
1828
+ const isCompressed = Boolean(formatInfo?.compressed);
1829
+ formatCapabilities.render &&= !isDepthStencil && !isCompressed;
1830
+ formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1831
+ return formatCapabilities;
1832
+ }
1345
1833
  function getTextureFormatInfo(format) {
1346
1834
  let formatInfo = getTextureFormatInfoUsingTable(format);
1347
1835
  if (textureFormatDecoder.isCompressed(format)) {
@@ -1350,19 +1838,20 @@ var __exports__ = (() => {
1350
1838
  formatInfo.bytesPerPixel = 1;
1351
1839
  formatInfo.srgb = false;
1352
1840
  formatInfo.compressed = true;
1841
+ formatInfo.bytesPerBlock = getCompressedTextureBlockByteLength(format);
1353
1842
  const blockSize = getCompressedTextureBlockSize(format);
1354
1843
  if (blockSize) {
1355
1844
  formatInfo.blockWidth = blockSize.blockWidth;
1356
1845
  formatInfo.blockHeight = blockSize.blockHeight;
1357
1846
  }
1358
1847
  }
1359
- const matches = RGB_FORMAT_REGEX.exec(format);
1848
+ const matches = !formatInfo.packed ? RGB_FORMAT_REGEX.exec(format) : null;
1360
1849
  if (matches) {
1361
1850
  const [, channels, length, type, srgb, suffix] = matches;
1362
1851
  const dataType = `${type}${length}`;
1363
- const decodedType = getDataTypeInfo(dataType);
1852
+ const decodedType = dataTypeDecoder.getDataTypeInfo(dataType);
1364
1853
  const bits = decodedType.byteLength * 8;
1365
- const components = channels.length;
1854
+ const components = channels?.length ?? 1;
1366
1855
  const bitsPerChannel = [
1367
1856
  bits,
1368
1857
  components >= 2 ? bits : 0,
@@ -1379,7 +1868,7 @@ var __exports__ = (() => {
1379
1868
  signed: decodedType.signed,
1380
1869
  normalized: decodedType.normalized,
1381
1870
  bitsPerChannel,
1382
- bytesPerPixel: decodedType.byteLength * channels.length,
1871
+ bytesPerPixel: decodedType.byteLength * components,
1383
1872
  packed: formatInfo.packed,
1384
1873
  srgb: formatInfo.srgb
1385
1874
  };
@@ -1435,10 +1924,31 @@ var __exports__ = (() => {
1435
1924
  const [, blockWidth, blockHeight] = matches;
1436
1925
  return { blockWidth: Number(blockWidth), blockHeight: Number(blockHeight) };
1437
1926
  }
1927
+ if (format.startsWith("bc") || format.startsWith("etc1") || format.startsWith("etc2") || format.startsWith("eac") || format.startsWith("atc")) {
1928
+ return { blockWidth: 4, blockHeight: 4 };
1929
+ }
1930
+ if (format.startsWith("pvrtc-rgb4") || format.startsWith("pvrtc-rgba4")) {
1931
+ return { blockWidth: 4, blockHeight: 4 };
1932
+ }
1933
+ if (format.startsWith("pvrtc-rgb2") || format.startsWith("pvrtc-rgba2")) {
1934
+ return { blockWidth: 8, blockHeight: 4 };
1935
+ }
1438
1936
  return null;
1439
1937
  }
1938
+ function getCompressedTextureBlockByteLength(format) {
1939
+ 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") {
1940
+ return 8;
1941
+ }
1942
+ 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") {
1943
+ return 16;
1944
+ }
1945
+ if (format.startsWith("pvrtc")) {
1946
+ return 8;
1947
+ }
1948
+ return 16;
1949
+ }
1440
1950
 
1441
- // ../core/src/image-utils/image-types.ts
1951
+ // ../core/src/shadertypes/image-types/image-types.ts
1442
1952
  function isExternalImage(data) {
1443
1953
  return typeof ImageData !== "undefined" && data instanceof ImageData || typeof ImageBitmap !== "undefined" && data instanceof ImageBitmap || typeof HTMLImageElement !== "undefined" && data instanceof HTMLImageElement || typeof HTMLVideoElement !== "undefined" && data instanceof HTMLVideoElement || typeof VideoFrame !== "undefined" && data instanceof VideoFrame || typeof HTMLCanvasElement !== "undefined" && data instanceof HTMLCanvasElement || typeof OffscreenCanvas !== "undefined" && data instanceof OffscreenCanvas;
1444
1954
  }
@@ -1461,6 +1971,52 @@ var __exports__ = (() => {
1461
1971
  // ../core/src/adapter/device.ts
1462
1972
  var DeviceLimits = class {
1463
1973
  };
1974
+ function formatErrorLogArguments(context, args) {
1975
+ const formattedContext = formatErrorLogValue(context);
1976
+ const formattedArgs = args.map(formatErrorLogValue).filter((arg) => arg !== void 0);
1977
+ return [formattedContext, ...formattedArgs].filter((arg) => arg !== void 0);
1978
+ }
1979
+ function formatErrorLogValue(value) {
1980
+ if (value === void 0) {
1981
+ return void 0;
1982
+ }
1983
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1984
+ return value;
1985
+ }
1986
+ if (value instanceof Error) {
1987
+ return value.message;
1988
+ }
1989
+ if (Array.isArray(value)) {
1990
+ return value.map(formatErrorLogValue);
1991
+ }
1992
+ if (typeof value === "object") {
1993
+ if (hasCustomToString(value)) {
1994
+ const stringValue = String(value);
1995
+ if (stringValue !== "[object Object]") {
1996
+ return stringValue;
1997
+ }
1998
+ }
1999
+ if (looksLikeGPUCompilationMessage(value)) {
2000
+ return formatGPUCompilationMessage(value);
2001
+ }
2002
+ return value.constructor?.name || "Object";
2003
+ }
2004
+ return String(value);
2005
+ }
2006
+ function hasCustomToString(value) {
2007
+ return "toString" in value && typeof value.toString === "function" && value.toString !== Object.prototype.toString;
2008
+ }
2009
+ function looksLikeGPUCompilationMessage(value) {
2010
+ return "message" in value && "type" in value;
2011
+ }
2012
+ function formatGPUCompilationMessage(value) {
2013
+ const type = typeof value.type === "string" ? value.type : "message";
2014
+ const message = typeof value.message === "string" ? value.message : "";
2015
+ const lineNum = typeof value.lineNum === "number" ? value.lineNum : null;
2016
+ const linePos = typeof value.linePos === "number" ? value.linePos : null;
2017
+ const location = lineNum !== null && linePos !== null ? ` @ ${lineNum}:${linePos}` : lineNum !== null ? ` @ ${lineNum}` : "";
2018
+ return `${type}${location}: ${message}`.trim();
2019
+ }
1464
2020
  var DeviceFeatures = class {
1465
2021
  features;
1466
2022
  disabledFeatures;
@@ -1490,19 +2046,24 @@ var __exports__ = (() => {
1490
2046
  userData = {};
1491
2047
  /** stats */
1492
2048
  statsManager = lumaStats;
2049
+ /** Internal per-device factory storage */
2050
+ _factories = {};
1493
2051
  /** An abstract timestamp used for change tracking */
1494
2052
  timestamp = 0;
1495
2053
  /** True if this device has been reused during device creation (app has multiple references) */
1496
2054
  _reused = false;
1497
2055
  /** Used by other luma.gl modules to store data on the device */
1498
- _lumaData = {};
2056
+ _moduleData = {};
1499
2057
  _textureCaps = {};
2058
+ /** Internal timestamp query set used when GPU timing collection is enabled for this device. */
2059
+ _debugGPUTimeQuery = null;
1500
2060
  constructor(props) {
1501
2061
  this.props = { ..._Device.defaultProps, ...props };
1502
2062
  this.id = this.props.id || uid(this[Symbol.toStringTag].toLowerCase());
1503
2063
  }
2064
+ // TODO - just expose the shadertypes decoders?
1504
2065
  getVertexFormatInfo(format) {
1505
- return getVertexFormatInfo(format);
2066
+ return vertexFormatDecoder.getVertexFormatInfo(format);
1506
2067
  }
1507
2068
  isVertexFormatSupported(format) {
1508
2069
  return true;
@@ -1550,6 +2111,16 @@ var __exports__ = (() => {
1550
2111
  isTextureFormatCompressed(format) {
1551
2112
  return textureFormatDecoder.isCompressed(format);
1552
2113
  }
2114
+ /** Returns the compressed texture formats that can be created and sampled on this device */
2115
+ getSupportedCompressedTextureFormats() {
2116
+ const supportedFormats = [];
2117
+ for (const format of Object.keys(getTextureFormatTable())) {
2118
+ if (this.isTextureFormatCompressed(format) && this.isTextureFormatSupported(format)) {
2119
+ supportedFormats.push(format);
2120
+ }
2121
+ }
2122
+ return supportedFormats;
2123
+ }
1553
2124
  // DEBUG METHODS
1554
2125
  pushDebugGroup(groupLabel) {
1555
2126
  this.commandEncoder.pushDebugGroup(groupLabel);
@@ -1592,7 +2163,13 @@ var __exports__ = (() => {
1592
2163
  reportError(error, context, ...args) {
1593
2164
  const isHandled = this.props.onError(error, context);
1594
2165
  if (!isHandled) {
1595
- return log.error(error.message, context, ...args);
2166
+ const logArguments = formatErrorLogArguments(context, args);
2167
+ return log.error(
2168
+ this.type === "webgl" ? "%cWebGL" : "%cWebGPU",
2169
+ "color: white; background: red; padding: 2px 6px; border-radius: 3px;",
2170
+ error.message,
2171
+ ...logArguments
2172
+ );
1596
2173
  }
1597
2174
  return () => {
1598
2175
  };
@@ -1614,6 +2191,10 @@ or create a device with the 'debug: true' prop.`;
1614
2191
  }
1615
2192
  return this.canvasContext;
1616
2193
  }
2194
+ /** Create a fence sync object */
2195
+ createFence() {
2196
+ throw new Error("createFence() not implemented");
2197
+ }
1617
2198
  /** Create a RenderPass using the default CommandEncoder */
1618
2199
  beginRenderPass(props) {
1619
2200
  return this.commandEncoder.beginRenderPass(props);
@@ -1622,6 +2203,78 @@ or create a device with the 'debug: true' prop.`;
1622
2203
  beginComputePass(props) {
1623
2204
  return this.commandEncoder.beginComputePass(props);
1624
2205
  }
2206
+ /**
2207
+ * Generate mipmaps for a WebGPU texture.
2208
+ * WebGPU textures must be created up front with the required mip count, usage flags, and a format that supports the chosen generation path.
2209
+ * WebGL uses `Texture.generateMipmapsWebGL()` directly because the backend manages mip generation on the texture object itself.
2210
+ */
2211
+ generateMipmapsWebGPU(_texture) {
2212
+ throw new Error("not implemented");
2213
+ }
2214
+ /** Internal helper for creating a shareable WebGL render-pipeline implementation. */
2215
+ _createSharedRenderPipelineWebGL(_props) {
2216
+ throw new Error("_createSharedRenderPipelineWebGL() not implemented");
2217
+ }
2218
+ /** Internal WebGPU-only helper for retrieving the native bind-group layout for a pipeline group. */
2219
+ _createBindGroupLayoutWebGPU(_pipeline, _group) {
2220
+ throw new Error("_createBindGroupLayoutWebGPU() not implemented");
2221
+ }
2222
+ /** Internal WebGPU-only helper for creating a native bind group. */
2223
+ _createBindGroupWebGPU(_bindGroupLayout, _shaderLayout, _bindings, _group, _label) {
2224
+ throw new Error("_createBindGroupWebGPU() not implemented");
2225
+ }
2226
+ /**
2227
+ * Internal helper that returns `true` when timestamp-query GPU timing should be
2228
+ * collected for this device.
2229
+ */
2230
+ _supportsDebugGPUTime() {
2231
+ return this.features.has("timestamp-query") && Boolean(this.props.debug || this.props.debugGPUTime);
2232
+ }
2233
+ /**
2234
+ * Internal helper that enables device-managed GPU timing collection on the
2235
+ * default command encoder. Reuses the existing query set if timing is already enabled.
2236
+ *
2237
+ * @param queryCount - Number of timestamp slots reserved for profiled passes.
2238
+ * @returns The device-managed timestamp QuerySet, or `null` when timing is not supported or could not be enabled.
2239
+ */
2240
+ _enableDebugGPUTime(queryCount = 256) {
2241
+ if (!this._supportsDebugGPUTime()) {
2242
+ return null;
2243
+ }
2244
+ if (this._debugGPUTimeQuery) {
2245
+ return this._debugGPUTimeQuery;
2246
+ }
2247
+ try {
2248
+ this._debugGPUTimeQuery = this.createQuerySet({ type: "timestamp", count: queryCount });
2249
+ this.commandEncoder = this.createCommandEncoder({
2250
+ id: this.commandEncoder.props.id,
2251
+ timeProfilingQuerySet: this._debugGPUTimeQuery
2252
+ });
2253
+ } catch {
2254
+ this._debugGPUTimeQuery = null;
2255
+ }
2256
+ return this._debugGPUTimeQuery;
2257
+ }
2258
+ /**
2259
+ * Internal helper that disables device-managed GPU timing collection and restores
2260
+ * the default command encoder to an unprofiled state.
2261
+ */
2262
+ _disableDebugGPUTime() {
2263
+ if (!this._debugGPUTimeQuery) {
2264
+ return;
2265
+ }
2266
+ if (this.commandEncoder.getTimeProfilingQuerySet() === this._debugGPUTimeQuery) {
2267
+ this.commandEncoder = this.createCommandEncoder({
2268
+ id: this.commandEncoder.props.id
2269
+ });
2270
+ }
2271
+ this._debugGPUTimeQuery.destroy();
2272
+ this._debugGPUTimeQuery = null;
2273
+ }
2274
+ /** Internal helper that returns `true` when device-managed GPU timing is currently active. */
2275
+ _isDebugGPUTimeEnabled() {
2276
+ return this._debugGPUTimeQuery !== null;
2277
+ }
1625
2278
  // DEPRECATED METHODS
1626
2279
  /** @deprecated Use getDefaultCanvasContext() */
1627
2280
  getCanvasContext() {
@@ -1657,6 +2310,12 @@ or create a device with the 'debug: true' prop.`;
1657
2310
  resetWebGL() {
1658
2311
  throw new Error("not implemented");
1659
2312
  }
2313
+ // INTERNAL LUMA.GL METHODS
2314
+ getModuleData(moduleName) {
2315
+ this._moduleData[moduleName] ||= {};
2316
+ return this._moduleData[moduleName];
2317
+ }
2318
+ // INTERNAL HELPERS
1660
2319
  // IMPLEMENTATION
1661
2320
  /** Helper to get the canvas context props */
1662
2321
  static _getCanvasContextProps(props) {
@@ -1723,7 +2382,8 @@ or create a device with the 'debug: true' prop.`;
1723
2382
  onVisibilityChange: (context) => log.log(1, `${context} Visibility changed ${context.isVisible}`)(),
1724
2383
  onDevicePixelRatioChange: (context, info) => log.log(1, `${context} DPR changed ${info.oldRatio} => ${context.devicePixelRatio}`)(),
1725
2384
  // Debug flags
1726
- debug: log.get("debug") || void 0,
2385
+ debug: getDefaultDebugValue(),
2386
+ debugGPUTime: false,
1727
2387
  debugShaders: log.get("debug-shaders") || void 0,
1728
2388
  debugFramebuffers: Boolean(log.get("debug-framebuffers")),
1729
2389
  debugFactories: Boolean(log.get("debug-factories")),
@@ -1734,9 +2394,11 @@ or create a device with the 'debug: true' prop.`;
1734
2394
  // Experimental
1735
2395
  _reuseDevices: false,
1736
2396
  _requestMaxLimits: true,
1737
- _cacheShaders: false,
1738
- _cachePipelines: false,
1739
- _cacheDestroyPolicy: "unused",
2397
+ _cacheShaders: true,
2398
+ _destroyShaders: false,
2399
+ _cachePipelines: true,
2400
+ _sharePipelines: true,
2401
+ _destroyPipelines: false,
1740
2402
  // TODO - Change these after confirming things work as expected
1741
2403
  _initializeFeatures: true,
1742
2404
  _disabledFeatures: {
@@ -1745,6 +2407,25 @@ or create a device with the 'debug: true' prop.`;
1745
2407
  // INTERNAL
1746
2408
  _handle: void 0
1747
2409
  });
2410
+ function _getDefaultDebugValue(logDebugValue, nodeEnv) {
2411
+ if (logDebugValue !== void 0 && logDebugValue !== null) {
2412
+ return Boolean(logDebugValue);
2413
+ }
2414
+ if (nodeEnv !== void 0) {
2415
+ return nodeEnv !== "production";
2416
+ }
2417
+ return false;
2418
+ }
2419
+ function getDefaultDebugValue() {
2420
+ return _getDefaultDebugValue(log.get("debug"), getNodeEnv());
2421
+ }
2422
+ function getNodeEnv() {
2423
+ const processObject = globalThis.process;
2424
+ if (!processObject?.env) {
2425
+ return void 0;
2426
+ }
2427
+ return processObject.env["NODE_ENV"];
2428
+ }
1748
2429
 
1749
2430
  // ../core/src/adapter/luma.ts
1750
2431
  var STARTUP_MESSAGE = "set luma.log.level=1 (or higher) to trace rendering";
@@ -1922,6 +2603,100 @@ or create a device with the 'debug: true' prop.`;
1922
2603
  return pageLoadPromise;
1923
2604
  }
1924
2605
 
2606
+ // ../core/src/adapter/canvas-observer.ts
2607
+ var CanvasObserver = class {
2608
+ props;
2609
+ _resizeObserver;
2610
+ _intersectionObserver;
2611
+ _observeDevicePixelRatioTimeout = null;
2612
+ _observeDevicePixelRatioMediaQuery = null;
2613
+ _handleDevicePixelRatioChange = () => this._refreshDevicePixelRatio();
2614
+ _trackPositionInterval = null;
2615
+ _started = false;
2616
+ get started() {
2617
+ return this._started;
2618
+ }
2619
+ constructor(props) {
2620
+ this.props = props;
2621
+ }
2622
+ start() {
2623
+ if (this._started || !this.props.canvas) {
2624
+ return;
2625
+ }
2626
+ this._started = true;
2627
+ this._intersectionObserver ||= new IntersectionObserver(
2628
+ (entries) => this.props.onIntersection(entries)
2629
+ );
2630
+ this._resizeObserver ||= new ResizeObserver((entries) => this.props.onResize(entries));
2631
+ this._intersectionObserver.observe(this.props.canvas);
2632
+ try {
2633
+ this._resizeObserver.observe(this.props.canvas, { box: "device-pixel-content-box" });
2634
+ } catch {
2635
+ this._resizeObserver.observe(this.props.canvas, { box: "content-box" });
2636
+ }
2637
+ this._observeDevicePixelRatioTimeout = setTimeout(() => this._refreshDevicePixelRatio(), 0);
2638
+ if (this.props.trackPosition) {
2639
+ this._trackPosition();
2640
+ }
2641
+ }
2642
+ stop() {
2643
+ if (!this._started) {
2644
+ return;
2645
+ }
2646
+ this._started = false;
2647
+ if (this._observeDevicePixelRatioTimeout) {
2648
+ clearTimeout(this._observeDevicePixelRatioTimeout);
2649
+ this._observeDevicePixelRatioTimeout = null;
2650
+ }
2651
+ if (this._observeDevicePixelRatioMediaQuery) {
2652
+ this._observeDevicePixelRatioMediaQuery.removeEventListener(
2653
+ "change",
2654
+ this._handleDevicePixelRatioChange
2655
+ );
2656
+ this._observeDevicePixelRatioMediaQuery = null;
2657
+ }
2658
+ if (this._trackPositionInterval) {
2659
+ clearInterval(this._trackPositionInterval);
2660
+ this._trackPositionInterval = null;
2661
+ }
2662
+ this._resizeObserver?.disconnect();
2663
+ this._intersectionObserver?.disconnect();
2664
+ }
2665
+ _refreshDevicePixelRatio() {
2666
+ if (!this._started) {
2667
+ return;
2668
+ }
2669
+ this.props.onDevicePixelRatioChange();
2670
+ this._observeDevicePixelRatioMediaQuery?.removeEventListener(
2671
+ "change",
2672
+ this._handleDevicePixelRatioChange
2673
+ );
2674
+ this._observeDevicePixelRatioMediaQuery = matchMedia(
2675
+ `(resolution: ${window.devicePixelRatio}dppx)`
2676
+ );
2677
+ this._observeDevicePixelRatioMediaQuery.addEventListener(
2678
+ "change",
2679
+ this._handleDevicePixelRatioChange,
2680
+ { once: true }
2681
+ );
2682
+ }
2683
+ _trackPosition(intervalMs = 100) {
2684
+ if (this._trackPositionInterval) {
2685
+ return;
2686
+ }
2687
+ this._trackPositionInterval = setInterval(() => {
2688
+ if (!this._started) {
2689
+ if (this._trackPositionInterval) {
2690
+ clearInterval(this._trackPositionInterval);
2691
+ this._trackPositionInterval = null;
2692
+ }
2693
+ } else {
2694
+ this.props.onPositionChange();
2695
+ }
2696
+ }, intervalMs);
2697
+ }
2698
+ };
2699
+
1925
2700
  // ../core/src/utils/promise-utils.ts
1926
2701
  function withResolvers() {
1927
2702
  let resolve;
@@ -1933,8 +2708,21 @@ or create a device with the 'debug: true' prop.`;
1933
2708
  return { promise, resolve, reject };
1934
2709
  }
1935
2710
 
1936
- // ../core/src/adapter/canvas-context.ts
1937
- var _CanvasContext = class {
2711
+ // ../core/src/utils/assert.ts
2712
+ function assert2(condition, message) {
2713
+ if (!condition) {
2714
+ const error = new Error(message ?? "luma.gl assertion failed.");
2715
+ Error.captureStackTrace?.(error, assert2);
2716
+ throw error;
2717
+ }
2718
+ }
2719
+ function assertDefined(value, message) {
2720
+ assert2(value, message);
2721
+ return value;
2722
+ }
2723
+
2724
+ // ../core/src/adapter/canvas-surface.ts
2725
+ var _CanvasSurface = class {
1938
2726
  static isHTMLCanvas(canvas) {
1939
2727
  return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
1940
2728
  }
@@ -1968,16 +2756,20 @@ or create a device with the 'debug: true' prop.`;
1968
2756
  drawingBufferWidth;
1969
2757
  /** Height of drawing buffer: automatically tracks this.pixelHeight if props.autoResize is true */
1970
2758
  drawingBufferHeight;
2759
+ /** Resolves when the canvas is initialized, i.e. when the ResizeObserver has updated the pixel size */
1971
2760
  _initializedResolvers = withResolvers();
1972
- _resizeObserver;
1973
- _intersectionObserver;
1974
- _position;
2761
+ _canvasObserver;
2762
+ /** Position of the canvas in the document, updated by a timer */
2763
+ _position = [0, 0];
2764
+ /** Whether this canvas context has been destroyed */
1975
2765
  destroyed = false;
2766
+ /** Whether the drawing buffer size needs to be resized (deferred resizing to avoid flicker) */
2767
+ _needsDrawingBufferResize = true;
1976
2768
  toString() {
1977
2769
  return `${this[Symbol.toStringTag]}(${this.id})`;
1978
2770
  }
1979
2771
  constructor(props) {
1980
- this.props = { ..._CanvasContext.defaultProps, ...props };
2772
+ this.props = { ..._CanvasSurface.defaultProps, ...props };
1981
2773
  props = this.props;
1982
2774
  this.initialized = this._initializedResolvers.promise;
1983
2775
  if (!isBrowser()) {
@@ -1989,11 +2781,11 @@ or create a device with the 'debug: true' prop.`;
1989
2781
  } else {
1990
2782
  this.canvas = props.canvas;
1991
2783
  }
1992
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2784
+ if (_CanvasSurface.isHTMLCanvas(this.canvas)) {
1993
2785
  this.id = props.id || this.canvas.id;
1994
2786
  this.type = "html-canvas";
1995
2787
  this.htmlCanvas = this.canvas;
1996
- } else if (_CanvasContext.isOffscreenCanvas(this.canvas)) {
2788
+ } else if (_CanvasSurface.isOffscreenCanvas(this.canvas)) {
1997
2789
  this.id = props.id || "offscreen-canvas";
1998
2790
  this.type = "offscreen-canvas";
1999
2791
  this.offscreenCanvas = this.canvas;
@@ -2009,25 +2801,21 @@ or create a device with the 'debug: true' prop.`;
2009
2801
  this.drawingBufferHeight = this.canvas.height;
2010
2802
  this.devicePixelRatio = globalThis.devicePixelRatio || 1;
2011
2803
  this._position = [0, 0];
2012
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2013
- this._intersectionObserver = new IntersectionObserver(
2014
- (entries) => this._handleIntersection(entries)
2015
- );
2016
- this._intersectionObserver.observe(this.canvas);
2017
- this._resizeObserver = new ResizeObserver((entries) => this._handleResize(entries));
2018
- try {
2019
- this._resizeObserver.observe(this.canvas, { box: "device-pixel-content-box" });
2020
- } catch {
2021
- this._resizeObserver.observe(this.canvas, { box: "content-box" });
2022
- }
2023
- setTimeout(() => this._observeDevicePixelRatio(), 0);
2024
- if (this.props.trackPosition) {
2025
- this._trackPosition();
2026
- }
2027
- }
2804
+ this._canvasObserver = new CanvasObserver({
2805
+ canvas: this.htmlCanvas,
2806
+ trackPosition: this.props.trackPosition,
2807
+ onResize: (entries) => this._handleResize(entries),
2808
+ onIntersection: (entries) => this._handleIntersection(entries),
2809
+ onDevicePixelRatioChange: () => this._observeDevicePixelRatio(),
2810
+ onPositionChange: () => this.updatePosition()
2811
+ });
2028
2812
  }
2029
2813
  destroy() {
2030
- this.destroyed = true;
2814
+ if (!this.destroyed) {
2815
+ this.destroyed = true;
2816
+ this._stopObservers();
2817
+ this.device = null;
2818
+ }
2031
2819
  }
2032
2820
  setProps(props) {
2033
2821
  if ("useDevicePixels" in props) {
@@ -2036,55 +2824,41 @@ or create a device with the 'debug: true' prop.`;
2036
2824
  }
2037
2825
  return this;
2038
2826
  }
2039
- // SIZE METHODS
2040
- /**
2041
- * Returns the size covered by the canvas in CSS pixels
2042
- * @note This can be different from the actual device pixel size of a canvas due to DPR scaling, and rounding to integer pixels
2043
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2044
- */
2827
+ /** Returns a framebuffer with properly resized current 'swap chain' textures */
2828
+ getCurrentFramebuffer(options) {
2829
+ this._resizeDrawingBufferIfNeeded();
2830
+ return this._getCurrentFramebuffer(options);
2831
+ }
2045
2832
  getCSSSize() {
2046
2833
  return [this.cssWidth, this.cssHeight];
2047
2834
  }
2048
2835
  getPosition() {
2049
2836
  return this._position;
2050
2837
  }
2051
- /**
2052
- * Returns the size covered by the canvas in actual device pixels.
2053
- * @note This can be different from the 'CSS' size of a canvas due to DPR scaling, and rounding to integer pixels
2054
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2055
- */
2056
2838
  getDevicePixelSize() {
2057
2839
  return [this.devicePixelWidth, this.devicePixelHeight];
2058
2840
  }
2059
- /** Get the drawing buffer size (number of pixels GPU is rendering into, can be different from CSS size) */
2060
2841
  getDrawingBufferSize() {
2061
2842
  return [this.drawingBufferWidth, this.drawingBufferHeight];
2062
2843
  }
2063
- /** Returns the biggest allowed framebuffer size. @todo Allow the application to limit this? */
2064
2844
  getMaxDrawingBufferSize() {
2065
2845
  const maxTextureDimension = this.device.limits.maxTextureDimension2D;
2066
2846
  return [maxTextureDimension, maxTextureDimension];
2067
2847
  }
2068
- /** Update the canvas drawing buffer size. Called automatically if props.autoResize is true. */
2069
2848
  setDrawingBufferSize(width, height) {
2070
- this.canvas.width = width;
2071
- this.canvas.height = height;
2849
+ width = Math.floor(width);
2850
+ height = Math.floor(height);
2851
+ if (this.drawingBufferWidth === width && this.drawingBufferHeight === height) {
2852
+ return;
2853
+ }
2072
2854
  this.drawingBufferWidth = width;
2073
2855
  this.drawingBufferHeight = height;
2856
+ this._needsDrawingBufferResize = true;
2074
2857
  }
2075
- /**
2076
- * Returns the current DPR (number of physical pixels per CSS pixel), if props.useDevicePixels is true
2077
- * @note This can be a fractional (non-integer) number, e.g. when the user zooms in the browser.
2078
- * @note This function handles the non-HTML canvas cases
2079
- */
2080
2858
  getDevicePixelRatio() {
2081
- const dpr = typeof window !== "undefined" && window.devicePixelRatio;
2082
- return dpr || 1;
2859
+ const devicePixelRatio2 = typeof window !== "undefined" && window.devicePixelRatio;
2860
+ return devicePixelRatio2 || 1;
2083
2861
  }
2084
- // DEPRECATED METHODS
2085
- /**
2086
- * Maps CSS pixel position to device pixel position
2087
- */
2088
2862
  cssToDevicePixels(cssPixel, yInvert = true) {
2089
2863
  const ratio = this.cssToDeviceRatio();
2090
2864
  const [width, height] = this.getDrawingBufferSize();
@@ -2094,10 +2868,10 @@ or create a device with the 'debug: true' prop.`;
2094
2868
  getPixelSize() {
2095
2869
  return this.getDevicePixelSize();
2096
2870
  }
2097
- /** @deprecated - TODO which values should we use for aspect */
2871
+ /** @deprecated Use the current drawing buffer size for projection setup. */
2098
2872
  getAspect() {
2099
- const [width, height] = this.getDevicePixelSize();
2100
- return width / height;
2873
+ const [width, height] = this.getDrawingBufferSize();
2874
+ return width > 0 && height > 0 ? width / height : 1;
2101
2875
  }
2102
2876
  /** @deprecated Returns multiplier need to convert CSS size to Device size */
2103
2877
  cssToDeviceRatio() {
@@ -2113,18 +2887,40 @@ or create a device with the 'debug: true' prop.`;
2113
2887
  resize(size) {
2114
2888
  this.setDrawingBufferSize(size.width, size.height);
2115
2889
  }
2116
- // IMPLEMENTATION
2117
- /**
2118
- * Allows subclass constructor to override the canvas id for auto created canvases.
2119
- * This can really help when debugging DOM in apps that create multiple devices
2120
- */
2121
2890
  _setAutoCreatedCanvasId(id) {
2122
2891
  if (this.htmlCanvas?.id === "lumagl-auto-created-canvas") {
2123
2892
  this.htmlCanvas.id = id;
2124
2893
  }
2125
2894
  }
2126
- /** reacts to an observed intersection */
2895
+ /**
2896
+ * Starts DOM observation after the derived context and its device are fully initialized.
2897
+ *
2898
+ * `CanvasSurface` construction runs before subclasses can assign `this.device`, and the
2899
+ * default WebGL canvas context is created before `WebGLDevice` has initialized `limits`,
2900
+ * `features`, and the rest of its runtime state. Deferring observer startup avoids early
2901
+ * `ResizeObserver` and DPR callbacks running against a partially initialized device.
2902
+ */
2903
+ _startObservers() {
2904
+ if (this.destroyed) {
2905
+ return;
2906
+ }
2907
+ this._canvasObserver.start();
2908
+ }
2909
+ /**
2910
+ * Stops all DOM observation and timers associated with a canvas surface.
2911
+ *
2912
+ * This pairs with `_startObservers()` so teardown uses the same lifecycle whether a context is
2913
+ * explicitly destroyed, abandoned during device reuse, or temporarily has not started observing
2914
+ * yet. Centralizing shutdown here keeps resize/DPR/position watchers from surviving past the
2915
+ * lifetime of the owning device.
2916
+ */
2917
+ _stopObservers() {
2918
+ this._canvasObserver.stop();
2919
+ }
2127
2920
  _handleIntersection(entries) {
2921
+ if (this.destroyed) {
2922
+ return;
2923
+ }
2128
2924
  const entry = entries.find((entry_) => entry_.target === this.canvas);
2129
2925
  if (!entry) {
2130
2926
  return;
@@ -2135,21 +2931,20 @@ or create a device with the 'debug: true' prop.`;
2135
2931
  this.device.props.onVisibilityChange(this);
2136
2932
  }
2137
2933
  }
2138
- /**
2139
- * Reacts to an observed resize by using the most accurate pixel size information the browser can provide
2140
- * @see https://web.dev/articles/device-pixel-content-box
2141
- * @see https://webgpufundamentals.org/webgpu/lessons/webgpu-resizing-the-canvas.html
2142
- */
2143
2934
  _handleResize(entries) {
2935
+ if (this.destroyed) {
2936
+ return;
2937
+ }
2144
2938
  const entry = entries.find((entry_) => entry_.target === this.canvas);
2145
2939
  if (!entry) {
2146
2940
  return;
2147
2941
  }
2148
- this.cssWidth = entry.contentBoxSize[0].inlineSize;
2149
- this.cssHeight = entry.contentBoxSize[0].blockSize;
2942
+ const contentBoxSize = assertDefined(entry.contentBoxSize?.[0]);
2943
+ this.cssWidth = contentBoxSize.inlineSize;
2944
+ this.cssHeight = contentBoxSize.blockSize;
2150
2945
  const oldPixelSize = this.getDevicePixelSize();
2151
- const devicePixelWidth = entry.devicePixelContentBoxSize?.[0].inlineSize || entry.contentBoxSize[0].inlineSize * devicePixelRatio;
2152
- const devicePixelHeight = entry.devicePixelContentBoxSize?.[0].blockSize || entry.contentBoxSize[0].blockSize * devicePixelRatio;
2946
+ const devicePixelWidth = entry.devicePixelContentBoxSize?.[0]?.inlineSize || contentBoxSize.inlineSize * devicePixelRatio;
2947
+ const devicePixelHeight = entry.devicePixelContentBoxSize?.[0]?.blockSize || contentBoxSize.blockSize * devicePixelRatio;
2153
2948
  const [maxDevicePixelWidth, maxDevicePixelHeight] = this.getMaxDrawingBufferSize();
2154
2949
  this.devicePixelWidth = Math.max(1, Math.min(devicePixelWidth, maxDevicePixelWidth));
2155
2950
  this.devicePixelHeight = Math.max(1, Math.min(devicePixelHeight, maxDevicePixelHeight));
@@ -2159,47 +2954,47 @@ or create a device with the 'debug: true' prop.`;
2159
2954
  _updateDrawingBufferSize() {
2160
2955
  if (this.props.autoResize) {
2161
2956
  if (typeof this.props.useDevicePixels === "number") {
2162
- const dpr = this.props.useDevicePixels;
2163
- this.setDrawingBufferSize(this.cssWidth * dpr, this.cssHeight * dpr);
2957
+ const devicePixelRatio2 = this.props.useDevicePixels;
2958
+ this.setDrawingBufferSize(
2959
+ this.cssWidth * devicePixelRatio2,
2960
+ this.cssHeight * devicePixelRatio2
2961
+ );
2164
2962
  } else if (this.props.useDevicePixels) {
2165
2963
  this.setDrawingBufferSize(this.devicePixelWidth, this.devicePixelHeight);
2166
2964
  } else {
2167
2965
  this.setDrawingBufferSize(this.cssWidth, this.cssHeight);
2168
2966
  }
2169
- this._updateDevice();
2170
2967
  }
2171
2968
  this._initializedResolvers.resolve();
2172
2969
  this.isInitialized = true;
2173
2970
  this.updatePosition();
2174
2971
  }
2175
- /** Monitor DPR changes */
2972
+ _resizeDrawingBufferIfNeeded() {
2973
+ if (this._needsDrawingBufferResize) {
2974
+ this._needsDrawingBufferResize = false;
2975
+ const sizeChanged = this.drawingBufferWidth !== this.canvas.width || this.drawingBufferHeight !== this.canvas.height;
2976
+ if (sizeChanged) {
2977
+ this.canvas.width = this.drawingBufferWidth;
2978
+ this.canvas.height = this.drawingBufferHeight;
2979
+ this._configureDevice();
2980
+ }
2981
+ }
2982
+ }
2176
2983
  _observeDevicePixelRatio() {
2984
+ if (this.destroyed || !this._canvasObserver.started) {
2985
+ return;
2986
+ }
2177
2987
  const oldRatio = this.devicePixelRatio;
2178
2988
  this.devicePixelRatio = window.devicePixelRatio;
2179
2989
  this.updatePosition();
2180
- this.device.props.onDevicePixelRatioChange(this, { oldRatio });
2181
- matchMedia(`(resolution: ${this.devicePixelRatio}dppx)`).addEventListener(
2182
- "change",
2183
- () => this._observeDevicePixelRatio(),
2184
- { once: true }
2185
- );
2186
- }
2187
- /** Start tracking positions with a timer */
2188
- _trackPosition(intervalMs = 100) {
2189
- const intervalId = setInterval(() => {
2190
- if (this.destroyed) {
2191
- clearInterval(intervalId);
2192
- } else {
2193
- this.updatePosition();
2194
- }
2195
- }, intervalMs);
2990
+ this.device.props.onDevicePixelRatioChange?.(this, {
2991
+ oldRatio
2992
+ });
2196
2993
  }
2197
- /**
2198
- * Calculated the absolute position of the canvas
2199
- * @note - getBoundingClientRect() is normally cheap but can be expensive
2200
- * if called before browser has finished a reflow. Should not be the case here.
2201
- */
2202
2994
  updatePosition() {
2995
+ if (this.destroyed) {
2996
+ return;
2997
+ }
2203
2998
  const newRect = this.htmlCanvas?.getBoundingClientRect();
2204
2999
  if (newRect) {
2205
3000
  const position = [newRect.left, newRect.top];
@@ -2208,13 +3003,15 @@ or create a device with the 'debug: true' prop.`;
2208
3003
  if (positionChanged) {
2209
3004
  const oldPosition = this._position;
2210
3005
  this._position = position;
2211
- this.device.props.onPositionChange?.(this, { oldPosition });
3006
+ this.device.props.onPositionChange?.(this, {
3007
+ oldPosition
3008
+ });
2212
3009
  }
2213
3010
  }
2214
3011
  }
2215
3012
  };
2216
- var CanvasContext = _CanvasContext;
2217
- __publicField(CanvasContext, "defaultProps", {
3013
+ var CanvasSurface = _CanvasSurface;
3014
+ __publicField(CanvasSurface, "defaultProps", {
2218
3015
  id: void 0,
2219
3016
  canvas: null,
2220
3017
  width: 800,
@@ -2242,7 +3039,7 @@ or create a device with the 'debug: true' prop.`;
2242
3039
  }
2243
3040
  function getCanvasFromDOM(canvasId) {
2244
3041
  const canvas = document.getElementById(canvasId);
2245
- if (!CanvasContext.isHTMLCanvas(canvas)) {
3042
+ if (!CanvasSurface.isHTMLCanvas(canvas)) {
2246
3043
  throw new Error("Object is not a canvas element");
2247
3044
  }
2248
3045
  return canvas;
@@ -2266,33 +3063,40 @@ or create a device with the 'debug: true' prop.`;
2266
3063
  const point = pixel;
2267
3064
  const x = scaleX(point[0], ratio, width);
2268
3065
  let y = scaleY(point[1], ratio, height, yInvert);
2269
- let t = scaleX(point[0] + 1, ratio, width);
2270
- const xHigh = t === width - 1 ? t : t - 1;
2271
- t = scaleY(point[1] + 1, ratio, height, yInvert);
3066
+ let temporary = scaleX(point[0] + 1, ratio, width);
3067
+ const xHigh = temporary === width - 1 ? temporary : temporary - 1;
3068
+ temporary = scaleY(point[1] + 1, ratio, height, yInvert);
2272
3069
  let yHigh;
2273
3070
  if (yInvert) {
2274
- t = t === 0 ? t : t + 1;
3071
+ temporary = temporary === 0 ? temporary : temporary + 1;
2275
3072
  yHigh = y;
2276
- y = t;
3073
+ y = temporary;
2277
3074
  } else {
2278
- yHigh = t === height - 1 ? t : t - 1;
3075
+ yHigh = temporary === height - 1 ? temporary : temporary - 1;
2279
3076
  }
2280
3077
  return {
2281
3078
  x,
2282
3079
  y,
2283
- // when ratio < 1, current css pixel and next css pixel may point to same device pixel, set width/height to 1 in those cases.
2284
3080
  width: Math.max(xHigh - x + 1, 1),
2285
3081
  height: Math.max(yHigh - y + 1, 1)
2286
3082
  };
2287
3083
  }
2288
3084
  function scaleX(x, ratio, width) {
2289
- const r = Math.min(Math.round(x * ratio), width - 1);
2290
- return r;
3085
+ return Math.min(Math.round(x * ratio), width - 1);
2291
3086
  }
2292
3087
  function scaleY(y, ratio, height, yInvert) {
2293
3088
  return yInvert ? Math.max(0, height - 1 - Math.round(y * ratio)) : Math.min(Math.round(y * ratio), height - 1);
2294
3089
  }
2295
3090
 
3091
+ // ../core/src/adapter/canvas-context.ts
3092
+ var CanvasContext = class extends CanvasSurface {
3093
+ };
3094
+ __publicField(CanvasContext, "defaultProps", CanvasSurface.defaultProps);
3095
+
3096
+ // ../core/src/adapter/presentation-context.ts
3097
+ var PresentationContext = class extends CanvasSurface {
3098
+ };
3099
+
2296
3100
  // ../core/src/adapter/resources/sampler.ts
2297
3101
  var _Sampler = class extends Resource {
2298
3102
  get [Symbol.toStringTag]() {
@@ -2347,7 +3151,15 @@ or create a device with the 'debug: true' prop.`;
2347
3151
  depth;
2348
3152
  /** mip levels in this texture */
2349
3153
  mipLevels;
2350
- /** "Time" of last update. Monotonically increasing timestamp. TODO move to AsyncTexture? */
3154
+ /** sample count */
3155
+ samples;
3156
+ /** Rows are multiples of this length, padded with extra bytes if needed */
3157
+ byteAlignment;
3158
+ /** The ready promise is always resolved. It is provided for type compatibility with DynamicTexture. */
3159
+ ready = Promise.resolve(this);
3160
+ /** isReady is always true. It is provided for type compatibility with DynamicTexture. */
3161
+ isReady = true;
3162
+ /** "Time" of last update. Monotonically increasing timestamp. TODO move to DynamicTexture? */
2351
3163
  updateTimestamp;
2352
3164
  get [Symbol.toStringTag]() {
2353
3165
  return "Texture";
@@ -2356,7 +3168,7 @@ or create a device with the 'debug: true' prop.`;
2356
3168
  return `Texture(${this.id},${this.format},${this.width}x${this.height})`;
2357
3169
  }
2358
3170
  /** Do not use directly. Create with device.createTexture() */
2359
- constructor(device, props) {
3171
+ constructor(device, props, backendProps) {
2360
3172
  props = _Texture.normalizeProps(device, props);
2361
3173
  super(device, props, _Texture.defaultProps);
2362
3174
  this.dimension = this.props.dimension;
@@ -2366,6 +3178,10 @@ or create a device with the 'debug: true' prop.`;
2366
3178
  this.height = this.props.height;
2367
3179
  this.depth = this.props.depth;
2368
3180
  this.mipLevels = this.props.mipLevels;
3181
+ this.samples = this.props.samples || 1;
3182
+ if (this.dimension === "cube") {
3183
+ this.depth = 6;
3184
+ }
2369
3185
  if (this.props.width === void 0 || this.props.height === void 0) {
2370
3186
  if (device.isExternalImage(props.data)) {
2371
3187
  const size = device.getExternalImageSize(props.data);
@@ -2376,17 +3192,14 @@ or create a device with the 'debug: true' prop.`;
2376
3192
  this.height = 1;
2377
3193
  if (this.props.width === void 0 || this.props.height === void 0) {
2378
3194
  log.warn(
2379
- `${this} created with undefined width or height. This is deprecated. Use AsyncTexture instead.`
3195
+ `${this} created with undefined width or height. This is deprecated. Use DynamicTexture instead.`
2380
3196
  )();
2381
3197
  }
2382
3198
  }
2383
3199
  }
3200
+ this.byteAlignment = backendProps?.byteAlignment || 1;
2384
3201
  this.updateTimestamp = device.incrementTimestamp();
2385
3202
  }
2386
- /** Set sampler props associated with this texture */
2387
- setSampler(sampler) {
2388
- this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
2389
- }
2390
3203
  /**
2391
3204
  * Create a new texture with the same parameters and optionally a different size
2392
3205
  * @note Textures are immutable and cannot be resized after creation, but we can create a similar texture with the same parameters but a new size.
@@ -2395,6 +3208,105 @@ or create a device with the 'debug: true' prop.`;
2395
3208
  clone(size) {
2396
3209
  return this.device.createTexture({ ...this.props, ...size });
2397
3210
  }
3211
+ /** Set sampler props associated with this texture */
3212
+ setSampler(sampler) {
3213
+ this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
3214
+ }
3215
+ /**
3216
+ * Copy raw image data (bytes) into the texture.
3217
+ *
3218
+ * @note Deprecated compatibility wrapper over {@link writeData}.
3219
+ * @note Uses the same layout defaults and alignment rules as {@link writeData}.
3220
+ * @note Tightly packed CPU uploads can omit `bytesPerRow` and `rowsPerImage`.
3221
+ * @note If the CPU source rows are padded, pass explicit `bytesPerRow` and `rowsPerImage`.
3222
+ * @deprecated Use writeData()
3223
+ */
3224
+ copyImageData(options) {
3225
+ const { data, depth, ...writeOptions } = options;
3226
+ this.writeData(data, {
3227
+ ...writeOptions,
3228
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3229
+ });
3230
+ }
3231
+ /**
3232
+ * Calculates the memory layout of the texture, required when reading and writing data.
3233
+ * @return the backend-aligned linear layout, in particular bytesPerRow which includes any required padding for buffer copy/read paths
3234
+ */
3235
+ computeMemoryLayout(options_ = {}) {
3236
+ const options = this._normalizeTextureReadOptions(options_);
3237
+ const { width = this.width, height = this.height, depthOrArrayLayers = this.depth } = options;
3238
+ const { format, byteAlignment } = this;
3239
+ return textureFormatDecoder.computeMemoryLayout({
3240
+ format,
3241
+ width,
3242
+ height,
3243
+ depth: depthOrArrayLayers,
3244
+ byteAlignment
3245
+ });
3246
+ }
3247
+ /**
3248
+ * Read the contents of a texture into a GPU Buffer.
3249
+ * @returns A Buffer containing the texture data.
3250
+ *
3251
+ * @note The memory layout of the texture data is determined by the texture format and dimensions.
3252
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3253
+ * @note The application can call Buffer.readAsync() to read the returned buffer on the CPU.
3254
+ * @note The destination buffer must be supplied by the caller and must be large enough for the requested region.
3255
+ * @note On WebGPU this corresponds to a texture-to-buffer copy and uses buffer-copy alignment rules.
3256
+ * @note On WebGL, luma.gl emulates the same logical readback behavior.
3257
+ */
3258
+ readBuffer(options, buffer) {
3259
+ throw new Error("readBuffer not implemented");
3260
+ }
3261
+ /**
3262
+ * Reads data from a texture into an ArrayBuffer.
3263
+ * @returns An ArrayBuffer containing the texture data.
3264
+ *
3265
+ * @note The memory layout of the texture data is determined by the texture format and dimensions.
3266
+ * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3267
+ * @deprecated Use Texture.readBuffer() with an explicit destination buffer, or DynamicTexture.readAsync() for convenience readback.
3268
+ */
3269
+ readDataAsync(options) {
3270
+ throw new Error("readBuffer not implemented");
3271
+ }
3272
+ /**
3273
+ * Writes a GPU Buffer into a texture.
3274
+ *
3275
+ * @param buffer - Source GPU buffer.
3276
+ * @param options - Destination subresource, extent, and source layout options.
3277
+ * @note The memory layout of the texture data is determined by the texture format and dimensions.
3278
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3279
+ * @note On WebGPU this corresponds to a buffer-to-texture copy and uses buffer-copy alignment rules.
3280
+ * @note On WebGL, luma.gl emulates the same destination and layout semantics.
3281
+ */
3282
+ writeBuffer(buffer, options) {
3283
+ throw new Error("readBuffer not implemented");
3284
+ }
3285
+ /**
3286
+ * Writes an array buffer into a texture.
3287
+ *
3288
+ * @param data - Source texel data.
3289
+ * @param options - Destination subresource, extent, and source layout options.
3290
+ * @note If `bytesPerRow` and `rowsPerImage` are omitted, luma.gl computes a tightly packed CPU-memory layout for the requested region.
3291
+ * @note On WebGPU this corresponds to `GPUQueue.writeTexture()` and does not implicitly pad rows to 256 bytes.
3292
+ * @note On WebGL, padded CPU data is supported via the same `bytesPerRow` and `rowsPerImage` options.
3293
+ */
3294
+ writeData(data, options) {
3295
+ throw new Error("readBuffer not implemented");
3296
+ }
3297
+ // IMPLEMENTATION SPECIFIC
3298
+ /**
3299
+ * WebGL can read data synchronously.
3300
+ * @note While it is convenient, the performance penalty is very significant
3301
+ */
3302
+ readDataSyncWebGL(options) {
3303
+ throw new Error("readDataSyncWebGL not available");
3304
+ }
3305
+ /** Generate mipmaps (WebGL only) */
3306
+ generateMipmapsWebGL() {
3307
+ throw new Error("generateMipmapsWebGL not available");
3308
+ }
3309
+ // HELPERS
2398
3310
  /** Ensure we have integer coordinates */
2399
3311
  static normalizeProps(device, props) {
2400
3312
  const newProps = { ...props };
@@ -2407,7 +3319,6 @@ or create a device with the 'debug: true' prop.`;
2407
3319
  }
2408
3320
  return newProps;
2409
3321
  }
2410
- // HELPERS
2411
3322
  /** Initialize texture with supplied props */
2412
3323
  // eslint-disable-next-line max-statements
2413
3324
  _initializeData(data) {
@@ -2441,23 +3352,166 @@ or create a device with the 'debug: true' prop.`;
2441
3352
  }
2442
3353
  }
2443
3354
  _normalizeCopyImageDataOptions(options_) {
2444
- const { width, height, depth } = this;
2445
- const options = { ..._Texture.defaultCopyDataOptions, width, height, depth, ...options_ };
2446
- const info = this.device.getTextureFormatInfo(this.format);
2447
- if (!options_.bytesPerRow && !info.bytesPerPixel) {
2448
- throw new Error(`bytesPerRow must be provided for texture format ${this.format}`);
2449
- }
2450
- options.bytesPerRow = options_.bytesPerRow || width * (info.bytesPerPixel || 4);
2451
- options.rowsPerImage = options_.rowsPerImage || height;
2452
- return options;
3355
+ const { data, depth, ...writeOptions } = options_;
3356
+ const options = this._normalizeTextureWriteOptions({
3357
+ ...writeOptions,
3358
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3359
+ });
3360
+ return { data, depth: options.depthOrArrayLayers, ...options };
2453
3361
  }
2454
3362
  _normalizeCopyExternalImageOptions(options_) {
3363
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3364
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3365
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
2455
3366
  const size = this.device.getExternalImageSize(options_.image);
2456
- const options = { ..._Texture.defaultCopyExternalImageOptions, ...size, ...options_ };
2457
- options.width = Math.min(options.width, this.width - options.x);
2458
- options.height = Math.min(options.height, this.height - options.y);
3367
+ const options = {
3368
+ ..._Texture.defaultCopyExternalImageOptions,
3369
+ ...mipLevelSize,
3370
+ ...size,
3371
+ ...optionsWithoutUndefined
3372
+ };
3373
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3374
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3375
+ options.depth = Math.min(options.depth, mipLevelSize.depthOrArrayLayers - options.z);
3376
+ return options;
3377
+ }
3378
+ _normalizeTextureReadOptions(options_) {
3379
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3380
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3381
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3382
+ const options = {
3383
+ ..._Texture.defaultTextureReadOptions,
3384
+ ...mipLevelSize,
3385
+ ...optionsWithoutUndefined
3386
+ };
3387
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3388
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3389
+ options.depthOrArrayLayers = Math.min(
3390
+ options.depthOrArrayLayers,
3391
+ mipLevelSize.depthOrArrayLayers - options.z
3392
+ );
2459
3393
  return options;
2460
3394
  }
3395
+ /**
3396
+ * Normalizes a texture read request and validates the color-only readback contract used by the
3397
+ * current texture read APIs. Supported dimensions are `2d`, `cube`, `cube-array`,
3398
+ * `2d-array`, and `3d`.
3399
+ *
3400
+ * @throws if the texture format, aspect, or dimension is not supported by the first-pass
3401
+ * color-read implementation.
3402
+ */
3403
+ _getSupportedColorReadOptions(options_) {
3404
+ const options = this._normalizeTextureReadOptions(options_);
3405
+ const formatInfo = textureFormatDecoder.getInfo(this.format);
3406
+ this._validateColorReadAspect(options);
3407
+ this._validateColorReadFormat(formatInfo);
3408
+ switch (this.dimension) {
3409
+ case "2d":
3410
+ case "cube":
3411
+ case "cube-array":
3412
+ case "2d-array":
3413
+ case "3d":
3414
+ return options;
3415
+ default:
3416
+ throw new Error(`${this} color readback does not support ${this.dimension} textures`);
3417
+ }
3418
+ }
3419
+ /** Validates that a read request targets the full color aspect of the texture. */
3420
+ _validateColorReadAspect(options) {
3421
+ if (options.aspect !== "all") {
3422
+ throw new Error(`${this} color readback only supports aspect 'all'`);
3423
+ }
3424
+ }
3425
+ /** Validates that a read request targets an uncompressed color-renderable texture format. */
3426
+ _validateColorReadFormat(formatInfo) {
3427
+ if (formatInfo.compressed) {
3428
+ throw new Error(
3429
+ `${this} color readback does not support compressed formats (${this.format})`
3430
+ );
3431
+ }
3432
+ switch (formatInfo.attachment) {
3433
+ case "color":
3434
+ return;
3435
+ case "depth":
3436
+ throw new Error(`${this} color readback does not support depth formats (${this.format})`);
3437
+ case "stencil":
3438
+ throw new Error(`${this} color readback does not support stencil formats (${this.format})`);
3439
+ case "depth-stencil":
3440
+ throw new Error(
3441
+ `${this} color readback does not support depth-stencil formats (${this.format})`
3442
+ );
3443
+ default:
3444
+ throw new Error(`${this} color readback does not support format ${this.format}`);
3445
+ }
3446
+ }
3447
+ _normalizeTextureWriteOptions(options_) {
3448
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3449
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3450
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3451
+ const options = {
3452
+ ..._Texture.defaultTextureWriteOptions,
3453
+ ...mipLevelSize,
3454
+ ...optionsWithoutUndefined
3455
+ };
3456
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3457
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3458
+ options.depthOrArrayLayers = Math.min(
3459
+ options.depthOrArrayLayers,
3460
+ mipLevelSize.depthOrArrayLayers - options.z
3461
+ );
3462
+ const layout = textureFormatDecoder.computeMemoryLayout({
3463
+ format: this.format,
3464
+ width: options.width,
3465
+ height: options.height,
3466
+ depth: options.depthOrArrayLayers,
3467
+ byteAlignment: this.byteAlignment
3468
+ });
3469
+ const minimumBytesPerRow = layout.bytesPerPixel * options.width;
3470
+ options.bytesPerRow = optionsWithoutUndefined.bytesPerRow ?? layout.bytesPerRow;
3471
+ options.rowsPerImage = optionsWithoutUndefined.rowsPerImage ?? options.height;
3472
+ if (options.bytesPerRow < minimumBytesPerRow) {
3473
+ throw new Error(
3474
+ `bytesPerRow (${options.bytesPerRow}) must be at least ${minimumBytesPerRow} for ${this.format}`
3475
+ );
3476
+ }
3477
+ if (options.rowsPerImage < options.height) {
3478
+ throw new Error(
3479
+ `rowsPerImage (${options.rowsPerImage}) must be at least ${options.height} for ${this.format}`
3480
+ );
3481
+ }
3482
+ const bytesPerPixel = this.device.getTextureFormatInfo(this.format).bytesPerPixel;
3483
+ if (bytesPerPixel && options.bytesPerRow % bytesPerPixel !== 0) {
3484
+ throw new Error(
3485
+ `bytesPerRow (${options.bytesPerRow}) must be a multiple of bytesPerPixel (${bytesPerPixel}) for ${this.format}`
3486
+ );
3487
+ }
3488
+ return options;
3489
+ }
3490
+ _getMipLevelSize(mipLevel) {
3491
+ const width = Math.max(1, this.width >> mipLevel);
3492
+ const height = this.baseDimension === "1d" ? 1 : Math.max(1, this.height >> mipLevel);
3493
+ const depthOrArrayLayers = this.dimension === "3d" ? Math.max(1, this.depth >> mipLevel) : this.depth;
3494
+ return { width, height, depthOrArrayLayers };
3495
+ }
3496
+ getAllocatedByteLength() {
3497
+ let allocatedByteLength = 0;
3498
+ for (let mipLevel = 0; mipLevel < this.mipLevels; mipLevel++) {
3499
+ const { width, height, depthOrArrayLayers } = this._getMipLevelSize(mipLevel);
3500
+ allocatedByteLength += textureFormatDecoder.computeMemoryLayout({
3501
+ format: this.format,
3502
+ width,
3503
+ height,
3504
+ depth: depthOrArrayLayers,
3505
+ byteAlignment: 1
3506
+ }).byteLength;
3507
+ }
3508
+ return allocatedByteLength * this.samples;
3509
+ }
3510
+ static _omitUndefined(options) {
3511
+ return Object.fromEntries(
3512
+ Object.entries(options).filter(([, value]) => value !== void 0)
3513
+ );
3514
+ }
2461
3515
  };
2462
3516
  var Texture = _Texture;
2463
3517
  /** The texture can be bound for use as a sampled texture in a shader */
@@ -2474,13 +3528,12 @@ or create a device with the 'debug: true' prop.`;
2474
3528
  __publicField(Texture, "TEXTURE", 4);
2475
3529
  /** @deprecated Use Texture.RENDER */
2476
3530
  __publicField(Texture, "RENDER_ATTACHMENT", 16);
2477
- /** Default options */
2478
3531
  __publicField(Texture, "defaultProps", {
2479
3532
  ...Resource.defaultProps,
2480
3533
  data: null,
2481
3534
  dimension: "2d",
2482
3535
  format: "rgba8unorm",
2483
- usage: _Texture.TEXTURE | _Texture.RENDER_ATTACHMENT | _Texture.COPY_DST,
3536
+ usage: _Texture.SAMPLE | _Texture.RENDER | _Texture.COPY_DST,
2484
3537
  width: void 0,
2485
3538
  height: void 0,
2486
3539
  depth: 1,
@@ -2494,6 +3547,10 @@ or create a device with the 'debug: true' prop.`;
2494
3547
  byteOffset: 0,
2495
3548
  bytesPerRow: void 0,
2496
3549
  rowsPerImage: void 0,
3550
+ width: void 0,
3551
+ height: void 0,
3552
+ depthOrArrayLayers: void 0,
3553
+ depth: 1,
2497
3554
  mipLevel: 0,
2498
3555
  x: 0,
2499
3556
  y: 0,
@@ -2517,6 +3574,29 @@ or create a device with the 'debug: true' prop.`;
2517
3574
  premultipliedAlpha: false,
2518
3575
  flipY: false
2519
3576
  });
3577
+ __publicField(Texture, "defaultTextureReadOptions", {
3578
+ x: 0,
3579
+ y: 0,
3580
+ z: 0,
3581
+ width: void 0,
3582
+ height: void 0,
3583
+ depthOrArrayLayers: 1,
3584
+ mipLevel: 0,
3585
+ aspect: "all"
3586
+ });
3587
+ __publicField(Texture, "defaultTextureWriteOptions", {
3588
+ byteOffset: 0,
3589
+ bytesPerRow: void 0,
3590
+ rowsPerImage: void 0,
3591
+ x: 0,
3592
+ y: 0,
3593
+ z: 0,
3594
+ width: void 0,
3595
+ height: void 0,
3596
+ depthOrArrayLayers: 1,
3597
+ mipLevel: 0,
3598
+ aspect: "all"
3599
+ });
2520
3600
 
2521
3601
  // ../core/src/adapter/resources/texture-view.ts
2522
3602
  var _TextureView = class extends Resource {
@@ -2563,24 +3643,32 @@ or create a device with the 'debug: true' prop.`;
2563
3643
  const log2 = shaderLog.slice().sort((a, b) => a.lineNum - b.lineNum);
2564
3644
  switch (options?.showSourceCode || "no") {
2565
3645
  case "all":
2566
- let currentMessage = 0;
3646
+ let currentMessageIndex = 0;
2567
3647
  for (let lineNum = 1; lineNum <= lines.length; lineNum++) {
2568
- formattedLog += getNumberedLine(lines[lineNum - 1], lineNum, options);
2569
- while (log2.length > currentMessage && log2[currentMessage].lineNum === lineNum) {
2570
- const message = log2[currentMessage++];
2571
- formattedLog += formatCompilerMessage(message, lines, message.lineNum, {
3648
+ const line = lines[lineNum - 1];
3649
+ const currentMessage = log2[currentMessageIndex];
3650
+ if (line && currentMessage) {
3651
+ formattedLog += getNumberedLine(line, lineNum, options);
3652
+ }
3653
+ while (log2.length > currentMessageIndex && currentMessage.lineNum === lineNum) {
3654
+ const message = log2[currentMessageIndex++];
3655
+ if (message) {
3656
+ formattedLog += formatCompilerMessage(message, lines, message.lineNum, {
3657
+ ...options,
3658
+ inlineSource: false
3659
+ });
3660
+ }
3661
+ }
3662
+ }
3663
+ while (log2.length > currentMessageIndex) {
3664
+ const message = log2[currentMessageIndex++];
3665
+ if (message) {
3666
+ formattedLog += formatCompilerMessage(message, [], 0, {
2572
3667
  ...options,
2573
3668
  inlineSource: false
2574
3669
  });
2575
3670
  }
2576
3671
  }
2577
- while (log2.length > currentMessage) {
2578
- const message = log2[currentMessage++];
2579
- formattedLog += formatCompilerMessage(message, [], 0, {
2580
- ...options,
2581
- inlineSource: false
2582
- });
2583
- }
2584
3672
  return formattedLog;
2585
3673
  case "issues":
2586
3674
  case "no":
@@ -2602,8 +3690,8 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
2602
3690
 
2603
3691
  `;
2604
3692
  }
2605
- const color = message.type === "error" ? "red" : "#8B4000";
2606
- 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}`;
3693
+ const color = message.type === "error" ? "red" : "orange";
3694
+ 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}`;
2607
3695
  }
2608
3696
  function getNumberedLines(lines, lineNum, options) {
2609
3697
  let numberedLines = "";
@@ -2689,29 +3777,34 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
2689
3777
  }
2690
3778
  const shaderName = shaderId;
2691
3779
  const shaderTitle = `${this.stage} shader "${shaderName}"`;
2692
- let htmlLog = formatCompilerLog(messages, this.source, { showSourceCode: "all", html: true });
3780
+ const htmlLog = formatCompilerLog(messages, this.source, { showSourceCode: "all", html: true });
2693
3781
  const translatedSource = this.getTranslatedSource();
3782
+ const container = document.createElement("div");
3783
+ container.innerHTML = `<h1>Compilation error in ${shaderTitle}</h1>
3784
+ <div style="display:flex;position:fixed;top:10px;right:20px;gap:2px;">
3785
+ <button id="copy">Copy source</button><br/>
3786
+ <button id="close">Close</button>
3787
+ </div>
3788
+ <code><pre>${htmlLog}</pre></code>`;
2694
3789
  if (translatedSource) {
2695
- htmlLog += `<br /><br /><h1>Translated Source</h1><br /><br /><code style="user-select:text;"><pre>${translatedSource}</pre></code>`;
2696
- }
2697
- const button = document.createElement("Button");
2698
- button.innerHTML = `
2699
- <h1>Compilation error in ${shaderTitle}</h1><br /><br />
2700
- <code style="user-select:text;"><pre>
2701
- ${htmlLog}
2702
- </pre></code>`;
2703
- button.style.top = "10px";
2704
- button.style.left = "10px";
2705
- button.style.position = "absolute";
2706
- button.style.zIndex = "9999";
2707
- button.style.width = "100%";
2708
- button.style.textAlign = "left";
2709
- document.body.appendChild(button);
2710
- const errors = document.getElementsByClassName("luma-compiler-log-error");
2711
- errors[0]?.scrollIntoView();
2712
- button.onclick = () => {
2713
- const dataURI = `data:text/plain,${encodeURIComponent(this.source)}`;
2714
- navigator.clipboard.writeText(dataURI);
3790
+ container.innerHTML += `<br /><h1>Translated Source</h1><br /><br /><code><pre>${translatedSource}</pre></code>`;
3791
+ }
3792
+ container.style.top = "0";
3793
+ container.style.left = "0";
3794
+ container.style.background = "white";
3795
+ container.style.position = "fixed";
3796
+ container.style.zIndex = "9999";
3797
+ container.style.maxWidth = "100vw";
3798
+ container.style.maxHeight = "100vh";
3799
+ container.style.overflowY = "auto";
3800
+ document.body.appendChild(container);
3801
+ const error = container.querySelector(".luma-compiler-log-error");
3802
+ error?.scrollIntoView();
3803
+ container.querySelector("button#close").onclick = () => {
3804
+ container.remove();
3805
+ };
3806
+ container.querySelector("button#copy").onclick = () => {
3807
+ navigator.clipboard.writeText(this.source);
2715
3808
  };
2716
3809
  }
2717
3810
  };
@@ -2731,7 +3824,7 @@ ${htmlLog}
2731
3824
  function getShaderName(shader, defaultName = "unnamed") {
2732
3825
  const SHADER_NAME_REGEXP = /#define[\s*]SHADER_NAME[\s*]([A-Za-z0-9_-]+)[\s*]/;
2733
3826
  const match = SHADER_NAME_REGEXP.exec(shader);
2734
- return match ? match[1] : defaultName;
3827
+ return match?.[1] ?? defaultName;
2735
3828
  }
2736
3829
 
2737
3830
  // ../core/src/adapter/resources/framebuffer.ts
@@ -2757,7 +3850,12 @@ ${htmlLog}
2757
3850
  (colorAttachment) => colorAttachment.texture.clone(size)
2758
3851
  );
2759
3852
  const depthStencilAttachment = this.depthStencilAttachment && this.depthStencilAttachment.texture.clone(size);
2760
- return this.device.createFramebuffer({ ...this.props, colorAttachments, depthStencilAttachment });
3853
+ return this.device.createFramebuffer({
3854
+ ...this.props,
3855
+ ...size,
3856
+ colorAttachments,
3857
+ depthStencilAttachment
3858
+ });
2761
3859
  }
2762
3860
  resize(size) {
2763
3861
  let updateSize = !size;
@@ -2832,17 +3930,15 @@ ${htmlLog}
2832
3930
  * and destroys existing textures if owned
2833
3931
  */
2834
3932
  resizeAttachments(width, height) {
2835
- for (let i = 0; i < this.colorAttachments.length; ++i) {
2836
- if (this.colorAttachments[i]) {
2837
- const resizedTexture = this.colorAttachments[i].texture.clone({
2838
- width,
2839
- height
2840
- });
2841
- this.destroyAttachedResource(this.colorAttachments[i]);
2842
- this.colorAttachments[i] = resizedTexture.view;
2843
- this.attachResource(resizedTexture.view);
2844
- }
2845
- }
3933
+ this.colorAttachments.forEach((colorAttachment, i) => {
3934
+ const resizedTexture = colorAttachment.texture.clone({
3935
+ width,
3936
+ height
3937
+ });
3938
+ this.destroyAttachedResource(colorAttachment);
3939
+ this.colorAttachments[i] = resizedTexture.view;
3940
+ this.attachResource(resizedTexture.view);
3941
+ });
2846
3942
  if (this.depthStencilAttachment) {
2847
3943
  const resizedTexture = this.depthStencilAttachment.texture.clone({
2848
3944
  width,
@@ -2879,30 +3975,547 @@ ${htmlLog}
2879
3975
  linkStatus = "pending";
2880
3976
  /** The hash of the pipeline */
2881
3977
  hash = "";
3978
+ /** Optional shared backend implementation */
3979
+ sharedRenderPipeline = null;
3980
+ /** Whether shader or pipeline compilation/linking is still in progress */
3981
+ get isPending() {
3982
+ return this.linkStatus === "pending" || this.vs.compilationStatus === "pending" || this.fs?.compilationStatus === "pending";
3983
+ }
3984
+ /** Whether shader or pipeline compilation/linking has failed */
3985
+ get isErrored() {
3986
+ return this.linkStatus === "error" || this.vs.compilationStatus === "error" || this.fs?.compilationStatus === "error";
3987
+ }
2882
3988
  constructor(device, props) {
2883
3989
  super(device, props, _RenderPipeline.defaultProps);
2884
3990
  this.shaderLayout = this.props.shaderLayout;
2885
3991
  this.bufferLayout = this.props.bufferLayout || [];
3992
+ this.sharedRenderPipeline = this.props._sharedRenderPipeline || null;
3993
+ }
3994
+ };
3995
+ var RenderPipeline = _RenderPipeline;
3996
+ __publicField(RenderPipeline, "defaultProps", {
3997
+ ...Resource.defaultProps,
3998
+ vs: null,
3999
+ vertexEntryPoint: "vertexMain",
4000
+ vsConstants: {},
4001
+ fs: null,
4002
+ fragmentEntryPoint: "fragmentMain",
4003
+ fsConstants: {},
4004
+ shaderLayout: null,
4005
+ bufferLayout: [],
4006
+ topology: "triangle-list",
4007
+ colorAttachmentFormats: void 0,
4008
+ depthStencilAttachmentFormat: void 0,
4009
+ parameters: {},
4010
+ varyings: void 0,
4011
+ bufferMode: void 0,
4012
+ disableWarnings: false,
4013
+ _sharedRenderPipeline: void 0,
4014
+ bindings: void 0,
4015
+ bindGroups: void 0
4016
+ });
4017
+
4018
+ // ../core/src/adapter/resources/shared-render-pipeline.ts
4019
+ var SharedRenderPipeline = class extends Resource {
4020
+ get [Symbol.toStringTag]() {
4021
+ return "SharedRenderPipeline";
4022
+ }
4023
+ constructor(device, props) {
4024
+ super(device, props, {
4025
+ ...Resource.defaultProps,
4026
+ handle: void 0,
4027
+ vs: void 0,
4028
+ fs: void 0,
4029
+ varyings: void 0,
4030
+ bufferMode: void 0
4031
+ });
4032
+ }
4033
+ };
4034
+
4035
+ // ../core/src/adapter/resources/compute-pipeline.ts
4036
+ var _ComputePipeline = class extends Resource {
4037
+ get [Symbol.toStringTag]() {
4038
+ return "ComputePipeline";
4039
+ }
4040
+ hash = "";
4041
+ /** The merged shader layout */
4042
+ shaderLayout;
4043
+ constructor(device, props) {
4044
+ super(device, props, _ComputePipeline.defaultProps);
4045
+ this.shaderLayout = props.shaderLayout;
4046
+ }
4047
+ };
4048
+ var ComputePipeline = _ComputePipeline;
4049
+ __publicField(ComputePipeline, "defaultProps", {
4050
+ ...Resource.defaultProps,
4051
+ shader: void 0,
4052
+ entryPoint: void 0,
4053
+ constants: {},
4054
+ shaderLayout: void 0
4055
+ });
4056
+
4057
+ // ../core/src/factories/pipeline-factory.ts
4058
+ var _PipelineFactory = class {
4059
+ /** Get the singleton default pipeline factory for the specified device */
4060
+ static getDefaultPipelineFactory(device) {
4061
+ const moduleData = device.getModuleData("@luma.gl/core");
4062
+ moduleData.defaultPipelineFactory ||= new _PipelineFactory(device);
4063
+ return moduleData.defaultPipelineFactory;
4064
+ }
4065
+ device;
4066
+ _hashCounter = 0;
4067
+ _hashes = {};
4068
+ _renderPipelineCache = {};
4069
+ _computePipelineCache = {};
4070
+ _sharedRenderPipelineCache = {};
4071
+ get [Symbol.toStringTag]() {
4072
+ return "PipelineFactory";
4073
+ }
4074
+ toString() {
4075
+ return `PipelineFactory(${this.device.id})`;
4076
+ }
4077
+ constructor(device) {
4078
+ this.device = device;
4079
+ }
4080
+ /**
4081
+ * WebGL has two cache layers with different priorities:
4082
+ * - `_sharedRenderPipelineCache` owns `WEBGLSharedRenderPipeline` / `WebGLProgram` reuse.
4083
+ * - `_renderPipelineCache` owns `RenderPipeline` wrapper reuse.
4084
+ *
4085
+ * Shared WebGL program reuse is the hard requirement. Wrapper reuse is beneficial,
4086
+ * but wrapper cache misses are acceptable if that keeps the cache logic simple and
4087
+ * prevents incorrect cache hits.
4088
+ *
4089
+ * In particular, wrapper hash logic must never force program creation or linked-program
4090
+ * introspection just to decide whether a shared WebGL program can be reused.
4091
+ */
4092
+ /** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */
4093
+ createRenderPipeline(props) {
4094
+ if (!this.device.props._cachePipelines) {
4095
+ return this.device.createRenderPipeline(props);
4096
+ }
4097
+ const allProps = { ...RenderPipeline.defaultProps, ...props };
4098
+ const cache = this._renderPipelineCache;
4099
+ const hash = this._hashRenderPipeline(allProps);
4100
+ let pipeline = cache[hash]?.resource;
4101
+ if (!pipeline) {
4102
+ const sharedRenderPipeline = this.device.type === "webgl" && this.device.props._sharePipelines ? this.createSharedRenderPipeline(allProps) : void 0;
4103
+ pipeline = this.device.createRenderPipeline({
4104
+ ...allProps,
4105
+ id: allProps.id ? `${allProps.id}-cached` : uid("unnamed-cached"),
4106
+ _sharedRenderPipeline: sharedRenderPipeline
4107
+ });
4108
+ pipeline.hash = hash;
4109
+ cache[hash] = { resource: pipeline, useCount: 1 };
4110
+ if (this.device.props.debugFactories) {
4111
+ log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
4112
+ }
4113
+ } else {
4114
+ cache[hash].useCount++;
4115
+ if (this.device.props.debugFactories) {
4116
+ log.log(
4117
+ 3,
4118
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
4119
+ )();
4120
+ }
4121
+ }
4122
+ return pipeline;
4123
+ }
4124
+ /** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */
4125
+ createComputePipeline(props) {
4126
+ if (!this.device.props._cachePipelines) {
4127
+ return this.device.createComputePipeline(props);
4128
+ }
4129
+ const allProps = { ...ComputePipeline.defaultProps, ...props };
4130
+ const cache = this._computePipelineCache;
4131
+ const hash = this._hashComputePipeline(allProps);
4132
+ let pipeline = cache[hash]?.resource;
4133
+ if (!pipeline) {
4134
+ pipeline = this.device.createComputePipeline({
4135
+ ...allProps,
4136
+ id: allProps.id ? `${allProps.id}-cached` : void 0
4137
+ });
4138
+ pipeline.hash = hash;
4139
+ cache[hash] = { resource: pipeline, useCount: 1 };
4140
+ if (this.device.props.debugFactories) {
4141
+ log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
4142
+ }
4143
+ } else {
4144
+ cache[hash].useCount++;
4145
+ if (this.device.props.debugFactories) {
4146
+ log.log(
4147
+ 3,
4148
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
4149
+ )();
4150
+ }
4151
+ }
4152
+ return pipeline;
4153
+ }
4154
+ release(pipeline) {
4155
+ if (!this.device.props._cachePipelines) {
4156
+ pipeline.destroy();
4157
+ return;
4158
+ }
4159
+ const cache = this._getCache(pipeline);
4160
+ const hash = pipeline.hash;
4161
+ cache[hash].useCount--;
4162
+ if (cache[hash].useCount === 0) {
4163
+ this._destroyPipeline(pipeline);
4164
+ if (this.device.props.debugFactories) {
4165
+ log.log(3, `${this}: ${pipeline} released and destroyed`)();
4166
+ }
4167
+ } else if (cache[hash].useCount < 0) {
4168
+ log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)();
4169
+ cache[hash].useCount = 0;
4170
+ } else if (this.device.props.debugFactories) {
4171
+ log.log(3, `${this}: ${pipeline} released, count=${cache[hash].useCount}`)();
4172
+ }
4173
+ }
4174
+ createSharedRenderPipeline(props) {
4175
+ const sharedPipelineHash = this._hashSharedRenderPipeline(props);
4176
+ let sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
4177
+ if (!sharedCacheItem) {
4178
+ const sharedRenderPipeline = this.device._createSharedRenderPipelineWebGL(props);
4179
+ sharedCacheItem = { resource: sharedRenderPipeline, useCount: 0 };
4180
+ this._sharedRenderPipelineCache[sharedPipelineHash] = sharedCacheItem;
4181
+ }
4182
+ sharedCacheItem.useCount++;
4183
+ return sharedCacheItem.resource;
4184
+ }
4185
+ releaseSharedRenderPipeline(pipeline) {
4186
+ if (!pipeline.sharedRenderPipeline) {
4187
+ return;
4188
+ }
4189
+ const sharedPipelineHash = this._hashSharedRenderPipeline(pipeline.sharedRenderPipeline.props);
4190
+ const sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
4191
+ if (!sharedCacheItem) {
4192
+ return;
4193
+ }
4194
+ sharedCacheItem.useCount--;
4195
+ if (sharedCacheItem.useCount === 0) {
4196
+ sharedCacheItem.resource.destroy();
4197
+ delete this._sharedRenderPipelineCache[sharedPipelineHash];
4198
+ }
4199
+ }
4200
+ // PRIVATE
4201
+ /** Destroy a cached pipeline, removing it from the cache if configured to do so. */
4202
+ _destroyPipeline(pipeline) {
4203
+ const cache = this._getCache(pipeline);
4204
+ if (!this.device.props._destroyPipelines) {
4205
+ return false;
4206
+ }
4207
+ delete cache[pipeline.hash];
4208
+ pipeline.destroy();
4209
+ if (pipeline instanceof RenderPipeline) {
4210
+ this.releaseSharedRenderPipeline(pipeline);
4211
+ }
4212
+ return true;
4213
+ }
4214
+ /** Get the appropriate cache for the type of pipeline */
4215
+ _getCache(pipeline) {
4216
+ let cache;
4217
+ if (pipeline instanceof ComputePipeline) {
4218
+ cache = this._computePipelineCache;
4219
+ }
4220
+ if (pipeline instanceof RenderPipeline) {
4221
+ cache = this._renderPipelineCache;
4222
+ }
4223
+ if (!cache) {
4224
+ throw new Error(`${this}`);
4225
+ }
4226
+ if (!cache[pipeline.hash]) {
4227
+ throw new Error(`${this}: ${pipeline} matched incorrect entry`);
4228
+ }
4229
+ return cache;
4230
+ }
4231
+ /** Calculate a hash based on all the inputs for a compute pipeline */
4232
+ _hashComputePipeline(props) {
4233
+ const { type } = this.device;
4234
+ const shaderHash = this._getHash(props.shader.source);
4235
+ const shaderLayoutHash = this._getHash(JSON.stringify(props.shaderLayout));
4236
+ return `${type}/C/${shaderHash}SL${shaderLayoutHash}`;
4237
+ }
4238
+ /** Calculate a hash based on all the inputs for a render pipeline */
4239
+ _hashRenderPipeline(props) {
4240
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
4241
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
4242
+ const varyingHash = this._getWebGLVaryingHash(props);
4243
+ const shaderLayoutHash = this._getHash(JSON.stringify(props.shaderLayout));
4244
+ const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
4245
+ const { type } = this.device;
4246
+ switch (type) {
4247
+ case "webgl":
4248
+ const webglParameterHash = this._getHash(JSON.stringify(props.parameters));
4249
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${webglParameterHash}SL${shaderLayoutHash}BL${bufferLayoutHash}`;
4250
+ case "webgpu":
4251
+ default:
4252
+ const entryPointHash = this._getHash(
4253
+ JSON.stringify({
4254
+ vertexEntryPoint: props.vertexEntryPoint,
4255
+ fragmentEntryPoint: props.fragmentEntryPoint
4256
+ })
4257
+ );
4258
+ const parameterHash = this._getHash(JSON.stringify(props.parameters));
4259
+ const attachmentHash = this._getWebGPUAttachmentHash(props);
4260
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}EP${entryPointHash}P${parameterHash}SL${shaderLayoutHash}BL${bufferLayoutHash}A${attachmentHash}`;
4261
+ }
4262
+ }
4263
+ // This is the only gate for shared `WebGLProgram` reuse.
4264
+ // Only include inputs that affect program linking or transform-feedback linkage.
4265
+ // Wrapper-only concerns such as topology, parameters, attachment formats and layout
4266
+ // overrides must not be added here.
4267
+ _hashSharedRenderPipeline(props) {
4268
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
4269
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
4270
+ const varyingHash = this._getWebGLVaryingHash(props);
4271
+ return `webgl/S/${vsHash}/${fsHash}V${varyingHash}`;
4272
+ }
4273
+ _getHash(key) {
4274
+ if (this._hashes[key] === void 0) {
4275
+ this._hashes[key] = this._hashCounter++;
4276
+ }
4277
+ return this._hashes[key];
4278
+ }
4279
+ _getWebGLVaryingHash(props) {
4280
+ const { varyings = [], bufferMode = null } = props;
4281
+ return this._getHash(JSON.stringify({ varyings, bufferMode }));
4282
+ }
4283
+ _getWebGPUAttachmentHash(props) {
4284
+ const colorAttachmentFormats = props.colorAttachmentFormats ?? [
4285
+ this.device.preferredColorFormat
4286
+ ];
4287
+ const depthStencilAttachmentFormat = props.parameters?.depthWriteEnabled ? props.depthStencilAttachmentFormat || this.device.preferredDepthFormat : null;
4288
+ return this._getHash(
4289
+ JSON.stringify({
4290
+ colorAttachmentFormats,
4291
+ depthStencilAttachmentFormat
4292
+ })
4293
+ );
4294
+ }
4295
+ };
4296
+ var PipelineFactory = _PipelineFactory;
4297
+ __publicField(PipelineFactory, "defaultProps", { ...RenderPipeline.defaultProps });
4298
+
4299
+ // ../core/src/factories/shader-factory.ts
4300
+ var _ShaderFactory = class {
4301
+ /** Returns the default ShaderFactory for the given {@link Device}, creating one if necessary. */
4302
+ static getDefaultShaderFactory(device) {
4303
+ const moduleData = device.getModuleData("@luma.gl/core");
4304
+ moduleData.defaultShaderFactory ||= new _ShaderFactory(device);
4305
+ return moduleData.defaultShaderFactory;
4306
+ }
4307
+ device;
4308
+ _cache = {};
4309
+ get [Symbol.toStringTag]() {
4310
+ return "ShaderFactory";
4311
+ }
4312
+ toString() {
4313
+ return `${this[Symbol.toStringTag]}(${this.device.id})`;
4314
+ }
4315
+ /** @internal */
4316
+ constructor(device) {
4317
+ this.device = device;
4318
+ }
4319
+ /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */
4320
+ createShader(props) {
4321
+ if (!this.device.props._cacheShaders) {
4322
+ return this.device.createShader(props);
4323
+ }
4324
+ const key = this._hashShader(props);
4325
+ let cacheEntry = this._cache[key];
4326
+ if (!cacheEntry) {
4327
+ const resource = this.device.createShader({
4328
+ ...props,
4329
+ id: props.id ? `${props.id}-cached` : void 0
4330
+ });
4331
+ this._cache[key] = cacheEntry = { resource, useCount: 1 };
4332
+ if (this.device.props.debugFactories) {
4333
+ log.log(3, `${this}: Created new shader ${resource.id}`)();
4334
+ }
4335
+ } else {
4336
+ cacheEntry.useCount++;
4337
+ if (this.device.props.debugFactories) {
4338
+ log.log(
4339
+ 3,
4340
+ `${this}: Reusing shader ${cacheEntry.resource.id} count=${cacheEntry.useCount}`
4341
+ )();
4342
+ }
4343
+ }
4344
+ return cacheEntry.resource;
4345
+ }
4346
+ /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */
4347
+ release(shader) {
4348
+ if (!this.device.props._cacheShaders) {
4349
+ shader.destroy();
4350
+ return;
4351
+ }
4352
+ const key = this._hashShader(shader);
4353
+ const cacheEntry = this._cache[key];
4354
+ if (cacheEntry) {
4355
+ cacheEntry.useCount--;
4356
+ if (cacheEntry.useCount === 0) {
4357
+ if (this.device.props._destroyShaders) {
4358
+ delete this._cache[key];
4359
+ cacheEntry.resource.destroy();
4360
+ if (this.device.props.debugFactories) {
4361
+ log.log(3, `${this}: Releasing shader ${shader.id}, destroyed`)();
4362
+ }
4363
+ }
4364
+ } else if (cacheEntry.useCount < 0) {
4365
+ throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`);
4366
+ } else if (this.device.props.debugFactories) {
4367
+ log.log(3, `${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)();
4368
+ }
4369
+ }
4370
+ }
4371
+ // PRIVATE
4372
+ _hashShader(value) {
4373
+ return `${value.stage}:${value.source}`;
4374
+ }
4375
+ };
4376
+ var ShaderFactory = _ShaderFactory;
4377
+ __publicField(ShaderFactory, "defaultProps", { ...Shader.defaultProps });
4378
+
4379
+ // ../core/src/adapter-utils/bind-groups.ts
4380
+ function getShaderLayoutBinding(shaderLayout, bindingName, options) {
4381
+ const bindingLayout = shaderLayout.bindings.find(
4382
+ (binding) => binding.name === bindingName || `${binding.name.toLocaleLowerCase()}uniforms` === bindingName.toLocaleLowerCase()
4383
+ );
4384
+ if (!bindingLayout && !options?.ignoreWarnings) {
4385
+ log.warn(`Binding ${bindingName} not set: Not found in shader layout.`)();
4386
+ }
4387
+ return bindingLayout || null;
4388
+ }
4389
+ function normalizeBindingsByGroup(shaderLayout, bindingsOrBindGroups) {
4390
+ if (!bindingsOrBindGroups) {
4391
+ return {};
4392
+ }
4393
+ if (areBindingsGrouped(bindingsOrBindGroups)) {
4394
+ const bindGroups2 = bindingsOrBindGroups;
4395
+ return Object.fromEntries(
4396
+ Object.entries(bindGroups2).map(([group, bindings]) => [Number(group), { ...bindings }])
4397
+ );
4398
+ }
4399
+ const bindGroups = {};
4400
+ for (const [bindingName, binding] of Object.entries(bindingsOrBindGroups)) {
4401
+ const bindingLayout = getShaderLayoutBinding(shaderLayout, bindingName);
4402
+ const group = bindingLayout?.group ?? 0;
4403
+ bindGroups[group] ||= {};
4404
+ bindGroups[group][bindingName] = binding;
4405
+ }
4406
+ return bindGroups;
4407
+ }
4408
+ function flattenBindingsByGroup(bindGroups) {
4409
+ const bindings = {};
4410
+ for (const groupBindings of Object.values(bindGroups)) {
4411
+ Object.assign(bindings, groupBindings);
4412
+ }
4413
+ return bindings;
4414
+ }
4415
+ function areBindingsGrouped(bindingsOrBindGroups) {
4416
+ const keys = Object.keys(bindingsOrBindGroups);
4417
+ return keys.length > 0 && keys.every((key) => /^\d+$/.test(key));
4418
+ }
4419
+
4420
+ // ../core/src/factories/bind-group-factory.ts
4421
+ var BindGroupFactory = class {
4422
+ device;
4423
+ _layoutCacheByPipeline = /* @__PURE__ */ new WeakMap();
4424
+ _bindGroupCacheByLayout = /* @__PURE__ */ new WeakMap();
4425
+ constructor(device) {
4426
+ this.device = device;
4427
+ }
4428
+ getBindGroups(pipeline, bindings, bindGroupCacheKeys) {
4429
+ if (this.device.type !== "webgpu" || pipeline.shaderLayout.bindings.length === 0) {
4430
+ return {};
4431
+ }
4432
+ const bindingsByGroup = normalizeBindingsByGroup(pipeline.shaderLayout, bindings);
4433
+ const resolvedBindGroups = {};
4434
+ for (const group of getBindGroupIndicesUpToMax(pipeline.shaderLayout.bindings)) {
4435
+ const groupBindings = bindingsByGroup[group];
4436
+ const bindGroupLayout = this._getBindGroupLayout(pipeline, group);
4437
+ const bindGroupLabel = getBindGroupLabel(pipeline, pipeline.shaderLayout, group);
4438
+ if (!groupBindings || Object.keys(groupBindings).length === 0) {
4439
+ if (!hasBindingsInGroup(pipeline.shaderLayout.bindings, group)) {
4440
+ resolvedBindGroups[group] = this._getEmptyBindGroup(
4441
+ bindGroupLayout,
4442
+ pipeline.shaderLayout,
4443
+ group,
4444
+ bindGroupLabel
4445
+ );
4446
+ }
4447
+ continue;
4448
+ }
4449
+ const bindGroupCacheKey = bindGroupCacheKeys?.[group];
4450
+ if (bindGroupCacheKey) {
4451
+ const layoutCache = this._getLayoutBindGroupCache(bindGroupLayout);
4452
+ if (layoutCache.bindGroupsBySource.has(bindGroupCacheKey)) {
4453
+ resolvedBindGroups[group] = layoutCache.bindGroupsBySource.get(bindGroupCacheKey) || null;
4454
+ continue;
4455
+ }
4456
+ const bindGroup = this.device._createBindGroupWebGPU(
4457
+ bindGroupLayout,
4458
+ pipeline.shaderLayout,
4459
+ groupBindings,
4460
+ group,
4461
+ bindGroupLabel
4462
+ );
4463
+ layoutCache.bindGroupsBySource.set(bindGroupCacheKey, bindGroup);
4464
+ resolvedBindGroups[group] = bindGroup;
4465
+ } else {
4466
+ resolvedBindGroups[group] = this.device._createBindGroupWebGPU(
4467
+ bindGroupLayout,
4468
+ pipeline.shaderLayout,
4469
+ groupBindings,
4470
+ group,
4471
+ bindGroupLabel
4472
+ );
4473
+ }
4474
+ }
4475
+ return resolvedBindGroups;
4476
+ }
4477
+ _getBindGroupLayout(pipeline, group) {
4478
+ let layoutCache = this._layoutCacheByPipeline.get(pipeline);
4479
+ if (!layoutCache) {
4480
+ layoutCache = {};
4481
+ this._layoutCacheByPipeline.set(pipeline, layoutCache);
4482
+ }
4483
+ layoutCache[group] ||= this.device._createBindGroupLayoutWebGPU(pipeline, group);
4484
+ return layoutCache[group];
4485
+ }
4486
+ _getEmptyBindGroup(bindGroupLayout, shaderLayout, group, label) {
4487
+ const layoutCache = this._getLayoutBindGroupCache(bindGroupLayout);
4488
+ layoutCache.emptyBindGroup ||= this.device._createBindGroupWebGPU(bindGroupLayout, shaderLayout, {}, group, label) || null;
4489
+ return layoutCache.emptyBindGroup;
4490
+ }
4491
+ _getLayoutBindGroupCache(bindGroupLayout) {
4492
+ let layoutCache = this._bindGroupCacheByLayout.get(bindGroupLayout);
4493
+ if (!layoutCache) {
4494
+ layoutCache = { bindGroupsBySource: /* @__PURE__ */ new WeakMap() };
4495
+ this._bindGroupCacheByLayout.set(bindGroupLayout, layoutCache);
4496
+ }
4497
+ return layoutCache;
2886
4498
  }
2887
4499
  };
2888
- var RenderPipeline = _RenderPipeline;
2889
- __publicField(RenderPipeline, "defaultProps", {
2890
- ...Resource.defaultProps,
2891
- vs: null,
2892
- vertexEntryPoint: "vertexMain",
2893
- vsConstants: {},
2894
- fs: null,
2895
- fragmentEntryPoint: "fragmentMain",
2896
- fsConstants: {},
2897
- shaderLayout: null,
2898
- bufferLayout: [],
2899
- topology: "triangle-list",
2900
- colorAttachmentFormats: void 0,
2901
- depthStencilAttachmentFormat: void 0,
2902
- parameters: {},
2903
- bindings: {},
2904
- uniforms: {}
2905
- });
4500
+ function _getDefaultBindGroupFactory(device) {
4501
+ device._factories.bindGroupFactory ||= new BindGroupFactory(device);
4502
+ return device._factories.bindGroupFactory;
4503
+ }
4504
+ function getBindGroupIndicesUpToMax(bindings) {
4505
+ const maxGroup = bindings.reduce(
4506
+ (highestGroup, binding) => Math.max(highestGroup, binding.group),
4507
+ -1
4508
+ );
4509
+ return Array.from({ length: maxGroup + 1 }, (_, group) => group);
4510
+ }
4511
+ function hasBindingsInGroup(bindings, group) {
4512
+ return bindings.some((binding) => binding.group === group);
4513
+ }
4514
+ function getBindGroupLabel(pipeline, shaderLayout, group) {
4515
+ const bindingNames = shaderLayout.bindings.filter((binding) => binding.group === group).sort((left, right) => left.location - right.location).map((binding) => binding.name);
4516
+ const bindingSuffix = bindingNames.length > 0 ? bindingNames.join(",") : "empty";
4517
+ return `${pipeline.id}/group${group}[${bindingSuffix}]`;
4518
+ }
2906
4519
 
2907
4520
  // ../core/src/adapter/resources/render-pass.ts
2908
4521
  var _RenderPass = class extends Resource {
@@ -2942,28 +4555,6 @@ ${htmlLog}
2942
4555
  endTimestampIndex: void 0
2943
4556
  });
2944
4557
 
2945
- // ../core/src/adapter/resources/compute-pipeline.ts
2946
- var _ComputePipeline = class extends Resource {
2947
- get [Symbol.toStringTag]() {
2948
- return "ComputePipeline";
2949
- }
2950
- hash = "";
2951
- /** The merged shader layout */
2952
- shaderLayout;
2953
- constructor(device, props) {
2954
- super(device, props, _ComputePipeline.defaultProps);
2955
- this.shaderLayout = props.shaderLayout;
2956
- }
2957
- };
2958
- var ComputePipeline = _ComputePipeline;
2959
- __publicField(ComputePipeline, "defaultProps", {
2960
- ...Resource.defaultProps,
2961
- shader: void 0,
2962
- entryPoint: void 0,
2963
- constants: {},
2964
- shaderLayout: void 0
2965
- });
2966
-
2967
4558
  // ../core/src/adapter/resources/compute-pass.ts
2968
4559
  var _ComputePass = class extends Resource {
2969
4560
  constructor(device, props) {
@@ -2986,8 +4577,69 @@ ${htmlLog}
2986
4577
  get [Symbol.toStringTag]() {
2987
4578
  return "CommandEncoder";
2988
4579
  }
4580
+ _timeProfilingQuerySet = null;
4581
+ _timeProfilingSlotCount = 0;
4582
+ _gpuTimeMs;
2989
4583
  constructor(device, props) {
2990
4584
  super(device, props, _CommandEncoder.defaultProps);
4585
+ this._timeProfilingQuerySet = props.timeProfilingQuerySet ?? null;
4586
+ this._timeProfilingSlotCount = 0;
4587
+ this._gpuTimeMs = void 0;
4588
+ }
4589
+ /**
4590
+ * Reads all resolved timestamp pairs on the current profiler query set and caches the sum
4591
+ * as milliseconds on this encoder.
4592
+ */
4593
+ async resolveTimeProfilingQuerySet() {
4594
+ this._gpuTimeMs = void 0;
4595
+ if (!this._timeProfilingQuerySet) {
4596
+ return;
4597
+ }
4598
+ const pairCount = Math.floor(this._timeProfilingSlotCount / 2);
4599
+ if (pairCount <= 0) {
4600
+ return;
4601
+ }
4602
+ const queryCount = pairCount * 2;
4603
+ const results = await this._timeProfilingQuerySet.readResults({
4604
+ firstQuery: 0,
4605
+ queryCount
4606
+ });
4607
+ let totalDurationNanoseconds = 0n;
4608
+ for (let queryIndex = 0; queryIndex < queryCount; queryIndex += 2) {
4609
+ totalDurationNanoseconds += results[queryIndex + 1] - results[queryIndex];
4610
+ }
4611
+ this._gpuTimeMs = Number(totalDurationNanoseconds) / 1e6;
4612
+ }
4613
+ /** Returns the number of query slots consumed by automatic pass profiling on this encoder. */
4614
+ getTimeProfilingSlotCount() {
4615
+ return this._timeProfilingSlotCount;
4616
+ }
4617
+ getTimeProfilingQuerySet() {
4618
+ return this._timeProfilingQuerySet;
4619
+ }
4620
+ /** Internal helper for auto-assigning timestamp slots to render/compute passes on this encoder. */
4621
+ _applyTimeProfilingToPassProps(props) {
4622
+ const passProps = props || {};
4623
+ if (!this._supportsTimestampQueries() || !this._timeProfilingQuerySet) {
4624
+ return passProps;
4625
+ }
4626
+ if (passProps.timestampQuerySet !== void 0 || passProps.beginTimestampIndex !== void 0 || passProps.endTimestampIndex !== void 0) {
4627
+ return passProps;
4628
+ }
4629
+ const beginTimestampIndex = this._timeProfilingSlotCount;
4630
+ if (beginTimestampIndex + 1 >= this._timeProfilingQuerySet.props.count) {
4631
+ return passProps;
4632
+ }
4633
+ this._timeProfilingSlotCount += 2;
4634
+ return {
4635
+ ...passProps,
4636
+ timestampQuerySet: this._timeProfilingQuerySet,
4637
+ beginTimestampIndex,
4638
+ endTimestampIndex: beginTimestampIndex + 1
4639
+ };
4640
+ }
4641
+ _supportsTimestampQueries() {
4642
+ return this.device.features.has("timestamp-query");
2991
4643
  }
2992
4644
  };
2993
4645
  var CommandEncoder = _CommandEncoder;
@@ -2996,7 +4648,8 @@ ${htmlLog}
2996
4648
  // beginComputePass(optional GPUComputePassDescriptor descriptor = {}): GPUComputePassEncoder;
2997
4649
  __publicField(CommandEncoder, "defaultProps", {
2998
4650
  ...Resource.defaultProps,
2999
- measureExecutionTime: void 0
4651
+ measureExecutionTime: void 0,
4652
+ timeProfilingQuerySet: void 0
3000
4653
  });
3001
4654
 
3002
4655
  // ../core/src/adapter/resources/command-buffer.ts
@@ -3013,13 +4666,22 @@ ${htmlLog}
3013
4666
  ...Resource.defaultProps
3014
4667
  });
3015
4668
 
3016
- // ../core/src/shadertypes/data-types/decode-shader-types.ts
4669
+ // ../core/src/shadertypes/shader-types/shader-type-decoder.ts
3017
4670
  function getVariableShaderTypeInfo(format) {
3018
- const decoded = UNIFORM_FORMATS[format];
4671
+ const resolvedFormat = resolveVariableShaderTypeAlias(format);
4672
+ const decoded = UNIFORM_FORMATS[resolvedFormat];
4673
+ if (!decoded) {
4674
+ throw new Error(`Unsupported variable shader type: ${format}`);
4675
+ }
3019
4676
  return decoded;
3020
4677
  }
3021
4678
  function getAttributeShaderTypeInfo(attributeType) {
3022
- const [primitiveType, components] = TYPE_INFO[attributeType];
4679
+ const resolvedAttributeType = resolveAttributeShaderTypeAlias(attributeType);
4680
+ const decoded = TYPE_INFO[resolvedAttributeType];
4681
+ if (!decoded) {
4682
+ throw new Error(`Unsupported attribute shader type: ${attributeType}`);
4683
+ }
4684
+ const [primitiveType, components] = decoded;
3023
4685
  const integer = primitiveType === "i32" || primitiveType === "u32";
3024
4686
  const signed = primitiveType !== "u32";
3025
4687
  const byteLength = PRIMITIVE_TYPE_SIZES[primitiveType] * components;
@@ -3031,6 +4693,33 @@ ${htmlLog}
3031
4693
  signed
3032
4694
  };
3033
4695
  }
4696
+ var ShaderTypeDecoder = class {
4697
+ getVariableShaderTypeInfo(format) {
4698
+ return getVariableShaderTypeInfo(format);
4699
+ }
4700
+ getAttributeShaderTypeInfo(attributeType) {
4701
+ return getAttributeShaderTypeInfo(attributeType);
4702
+ }
4703
+ makeShaderAttributeType(primitiveType, components) {
4704
+ return makeShaderAttributeType(primitiveType, components);
4705
+ }
4706
+ resolveAttributeShaderTypeAlias(alias) {
4707
+ return resolveAttributeShaderTypeAlias(alias);
4708
+ }
4709
+ resolveVariableShaderTypeAlias(alias) {
4710
+ return resolveVariableShaderTypeAlias(alias);
4711
+ }
4712
+ };
4713
+ function makeShaderAttributeType(primitiveType, components) {
4714
+ return components === 1 ? primitiveType : `vec${components}<${primitiveType}>`;
4715
+ }
4716
+ function resolveAttributeShaderTypeAlias(alias) {
4717
+ return WGSL_ATTRIBUTE_TYPE_ALIAS_MAP[alias] || alias;
4718
+ }
4719
+ function resolveVariableShaderTypeAlias(alias) {
4720
+ return WGSL_VARIABLE_TYPE_ALIAS_MAP[alias] || alias;
4721
+ }
4722
+ var shaderTypeDecoder = new ShaderTypeDecoder();
3034
4723
  var PRIMITIVE_TYPE_SIZES = {
3035
4724
  f32: 4,
3036
4725
  f16: 2,
@@ -3127,7 +4816,18 @@ ${htmlLog}
3127
4816
  vec4h: "vec4<f16>"
3128
4817
  };
3129
4818
  var WGSL_VARIABLE_TYPE_ALIAS_MAP = {
3130
- ...WGSL_ATTRIBUTE_TYPE_ALIAS_MAP,
4819
+ vec2i: "vec2<i32>",
4820
+ vec3i: "vec3<i32>",
4821
+ vec4i: "vec4<i32>",
4822
+ vec2u: "vec2<u32>",
4823
+ vec3u: "vec3<u32>",
4824
+ vec4u: "vec4<u32>",
4825
+ vec2f: "vec2<f32>",
4826
+ vec3f: "vec3<f32>",
4827
+ vec4f: "vec4<f32>",
4828
+ vec2h: "vec2<f16>",
4829
+ vec3h: "vec3<f16>",
4830
+ vec4h: "vec4<f16>",
3131
4831
  mat2x2f: "mat2x2<f32>",
3132
4832
  mat2x3f: "mat2x3<f32>",
3133
4833
  mat2x4f: "mat2x4<f32>",
@@ -3194,10 +4894,10 @@ ${htmlLog}
3194
4894
  if (!shaderDeclaration) {
3195
4895
  return null;
3196
4896
  }
3197
- const attributeTypeInfo = getAttributeShaderTypeInfo(shaderDeclaration.type);
3198
- const defaultVertexFormat = getCompatibleVertexFormat(attributeTypeInfo);
4897
+ const attributeTypeInfo = shaderTypeDecoder.getAttributeShaderTypeInfo(shaderDeclaration.type);
4898
+ const defaultVertexFormat = vertexFormatDecoder.getCompatibleVertexFormat(attributeTypeInfo);
3199
4899
  const vertexFormat = bufferMapping?.vertexFormat || defaultVertexFormat;
3200
- const vertexFormatInfo = getVertexFormatInfo(vertexFormat);
4900
+ const vertexFormatInfo = vertexFormatDecoder.getVertexFormatInfo(vertexFormat);
3201
4901
  return {
3202
4902
  attributeName: bufferMapping?.attributeName || shaderDeclaration.name,
3203
4903
  bufferName: bufferMapping?.bufferName || shaderDeclaration.name,
@@ -3265,7 +4965,7 @@ ${htmlLog}
3265
4965
  let byteStride = bufferLayout.byteStride;
3266
4966
  if (typeof bufferLayout.byteStride !== "number") {
3267
4967
  for (const attributeMapping2 of bufferLayout.attributes || []) {
3268
- const info = getVertexFormatInfo(attributeMapping2.format);
4968
+ const info = vertexFormatDecoder.getVertexFormatInfo(attributeMapping2.format);
3269
4969
  byteStride += info.byteLength;
3270
4970
  }
3271
4971
  }
@@ -3353,6 +5053,20 @@ ${htmlLog}
3353
5053
  count: void 0
3354
5054
  });
3355
5055
 
5056
+ // ../core/src/adapter/resources/fence.ts
5057
+ var _Fence = class extends Resource {
5058
+ get [Symbol.toStringTag]() {
5059
+ return "Fence";
5060
+ }
5061
+ constructor(device, props = {}) {
5062
+ super(device, props, _Fence.defaultProps);
5063
+ }
5064
+ };
5065
+ var Fence = _Fence;
5066
+ __publicField(Fence, "defaultProps", {
5067
+ ...Resource.defaultProps
5068
+ });
5069
+
3356
5070
  // ../core/src/adapter/resources/pipeline-layout.ts
3357
5071
  var _PipelineLayout = class extends Resource {
3358
5072
  get [Symbol.toStringTag]() {
@@ -3371,6 +5085,200 @@ ${htmlLog}
3371
5085
  }
3372
5086
  });
3373
5087
 
5088
+ // ../core/src/shadertypes/data-types/decode-data-types.ts
5089
+ function alignTo(size, count) {
5090
+ switch (count) {
5091
+ case 1:
5092
+ return size;
5093
+ case 2:
5094
+ return size + size % 2;
5095
+ default:
5096
+ return size + (4 - size % 4) % 4;
5097
+ }
5098
+ }
5099
+ function getTypedArrayConstructor(type) {
5100
+ const [, , , , Constructor] = NORMALIZED_TYPE_MAP2[type];
5101
+ return Constructor;
5102
+ }
5103
+ var NORMALIZED_TYPE_MAP2 = {
5104
+ uint8: ["uint8", "u32", 1, false, Uint8Array],
5105
+ sint8: ["sint8", "i32", 1, false, Int8Array],
5106
+ unorm8: ["uint8", "f32", 1, true, Uint8Array],
5107
+ snorm8: ["sint8", "f32", 1, true, Int8Array],
5108
+ uint16: ["uint16", "u32", 2, false, Uint16Array],
5109
+ sint16: ["sint16", "i32", 2, false, Int16Array],
5110
+ unorm16: ["uint16", "u32", 2, true, Uint16Array],
5111
+ snorm16: ["sint16", "i32", 2, true, Int16Array],
5112
+ float16: ["float16", "f16", 2, false, Uint16Array],
5113
+ float32: ["float32", "f32", 4, false, Float32Array],
5114
+ uint32: ["uint32", "u32", 4, false, Uint32Array],
5115
+ sint32: ["sint32", "i32", 4, false, Int32Array]
5116
+ };
5117
+
5118
+ // ../core/src/shadertypes/shader-types/shader-block-layout.ts
5119
+ function makeShaderBlockLayout(uniformTypes, options = {}) {
5120
+ const copiedUniformTypes = { ...uniformTypes };
5121
+ const layout = options.layout ?? "std140";
5122
+ const fields = {};
5123
+ let size = 0;
5124
+ for (const [key, uniformType] of Object.entries(copiedUniformTypes)) {
5125
+ size = addToLayout(fields, key, uniformType, size, layout);
5126
+ }
5127
+ size = alignTo(size, getTypeAlignment(copiedUniformTypes, layout));
5128
+ return {
5129
+ layout,
5130
+ byteLength: size * 4,
5131
+ uniformTypes: copiedUniformTypes,
5132
+ fields
5133
+ };
5134
+ }
5135
+ function getLeafLayoutInfo(type, layout) {
5136
+ const resolvedType = resolveVariableShaderTypeAlias(type);
5137
+ const decodedType = getVariableShaderTypeInfo(resolvedType);
5138
+ const matrixMatch = /^mat(\d)x(\d)<.+>$/.exec(resolvedType);
5139
+ if (matrixMatch) {
5140
+ const columns = Number(matrixMatch[1]);
5141
+ const rows = Number(matrixMatch[2]);
5142
+ const columnInfo = getVectorLayoutInfo(
5143
+ rows,
5144
+ resolvedType,
5145
+ decodedType.type,
5146
+ layout
5147
+ );
5148
+ const columnStride = getMatrixColumnStride(columnInfo.size, columnInfo.alignment, layout);
5149
+ return {
5150
+ alignment: columnInfo.alignment,
5151
+ size: columns * columnStride,
5152
+ components: columns * rows,
5153
+ columns,
5154
+ rows,
5155
+ columnStride,
5156
+ shaderType: resolvedType,
5157
+ type: decodedType.type
5158
+ };
5159
+ }
5160
+ const vectorMatch = /^vec(\d)<.+>$/.exec(resolvedType);
5161
+ if (vectorMatch) {
5162
+ return getVectorLayoutInfo(
5163
+ Number(vectorMatch[1]),
5164
+ resolvedType,
5165
+ decodedType.type,
5166
+ layout
5167
+ );
5168
+ }
5169
+ return {
5170
+ alignment: 1,
5171
+ size: 1,
5172
+ components: 1,
5173
+ columns: 1,
5174
+ rows: 1,
5175
+ columnStride: 1,
5176
+ shaderType: resolvedType,
5177
+ type: decodedType.type
5178
+ };
5179
+ }
5180
+ function isCompositeShaderTypeStruct(value) {
5181
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5182
+ }
5183
+ function addToLayout(fields, name2, type, offset, layout) {
5184
+ if (typeof type === "string") {
5185
+ const info = getLeafLayoutInfo(type, layout);
5186
+ const alignedOffset = alignTo(offset, info.alignment);
5187
+ fields[name2] = {
5188
+ offset: alignedOffset,
5189
+ ...info
5190
+ };
5191
+ return alignedOffset + info.size;
5192
+ }
5193
+ if (Array.isArray(type)) {
5194
+ if (Array.isArray(type[0])) {
5195
+ throw new Error(`Nested arrays are not supported for ${name2}`);
5196
+ }
5197
+ const elementType = type[0];
5198
+ const length = type[1];
5199
+ const stride = getArrayStride(elementType, layout);
5200
+ const arrayOffset = alignTo(offset, getTypeAlignment(type, layout));
5201
+ for (let i = 0; i < length; i++) {
5202
+ addToLayout(fields, `${name2}[${i}]`, elementType, arrayOffset + i * stride, layout);
5203
+ }
5204
+ return arrayOffset + stride * length;
5205
+ }
5206
+ if (isCompositeShaderTypeStruct(type)) {
5207
+ const structAlignment = getTypeAlignment(type, layout);
5208
+ let structOffset = alignTo(offset, structAlignment);
5209
+ for (const [memberName, memberType] of Object.entries(type)) {
5210
+ structOffset = addToLayout(fields, `${name2}.${memberName}`, memberType, structOffset, layout);
5211
+ }
5212
+ return alignTo(structOffset, structAlignment);
5213
+ }
5214
+ throw new Error(`Unsupported CompositeShaderType for ${name2}`);
5215
+ }
5216
+ function getTypeSize(type, layout) {
5217
+ if (typeof type === "string") {
5218
+ return getLeafLayoutInfo(type, layout).size;
5219
+ }
5220
+ if (Array.isArray(type)) {
5221
+ const elementType = type[0];
5222
+ const length = type[1];
5223
+ if (Array.isArray(elementType)) {
5224
+ throw new Error("Nested arrays are not supported");
5225
+ }
5226
+ return getArrayStride(elementType, layout) * length;
5227
+ }
5228
+ let size = 0;
5229
+ for (const memberType of Object.values(type)) {
5230
+ const compositeMemberType = memberType;
5231
+ size = alignTo(size, getTypeAlignment(compositeMemberType, layout));
5232
+ size += getTypeSize(compositeMemberType, layout);
5233
+ }
5234
+ return alignTo(size, getTypeAlignment(type, layout));
5235
+ }
5236
+ function getTypeAlignment(type, layout) {
5237
+ if (typeof type === "string") {
5238
+ return getLeafLayoutInfo(type, layout).alignment;
5239
+ }
5240
+ if (Array.isArray(type)) {
5241
+ const elementType = type[0];
5242
+ const elementAlignment = getTypeAlignment(elementType, layout);
5243
+ return uses16ByteArrayAlignment(layout) ? Math.max(elementAlignment, 4) : elementAlignment;
5244
+ }
5245
+ let maxAlignment = 1;
5246
+ for (const memberType of Object.values(type)) {
5247
+ const memberAlignment = getTypeAlignment(memberType, layout);
5248
+ maxAlignment = Math.max(maxAlignment, memberAlignment);
5249
+ }
5250
+ return uses16ByteStructAlignment(layout) ? Math.max(maxAlignment, 4) : maxAlignment;
5251
+ }
5252
+ function getVectorLayoutInfo(components, shaderType, type, layout) {
5253
+ return {
5254
+ alignment: components === 2 ? 2 : 4,
5255
+ size: components === 3 ? 3 : components,
5256
+ components,
5257
+ columns: 1,
5258
+ rows: components,
5259
+ columnStride: components === 3 ? 3 : components,
5260
+ shaderType,
5261
+ type
5262
+ };
5263
+ }
5264
+ function getArrayStride(elementType, layout) {
5265
+ const elementSize = getTypeSize(elementType, layout);
5266
+ const elementAlignment = getTypeAlignment(elementType, layout);
5267
+ return getArrayLikeStride(elementSize, elementAlignment, layout);
5268
+ }
5269
+ function getArrayLikeStride(size, alignment, layout) {
5270
+ return alignTo(size, uses16ByteArrayAlignment(layout) ? 4 : alignment);
5271
+ }
5272
+ function getMatrixColumnStride(size, alignment, layout) {
5273
+ return layout === "std140" ? 4 : alignTo(size, alignment);
5274
+ }
5275
+ function uses16ByteArrayAlignment(layout) {
5276
+ return layout === "std140" || layout === "wgsl-uniform";
5277
+ }
5278
+ function uses16ByteStructAlignment(layout) {
5279
+ return layout === "std140" || layout === "wgsl-uniform";
5280
+ }
5281
+
3374
5282
  // ../core/src/utils/array-utils-flat.ts
3375
5283
  var arrayBuffer;
3376
5284
  function getScratchArrayBuffer(byteLength) {
@@ -3395,92 +5303,201 @@ ${htmlLog}
3395
5303
  return isTypedArray(value);
3396
5304
  }
3397
5305
 
3398
- // ../core/src/portable/uniform-buffer-layout.ts
3399
- var minBufferSize = 1024;
3400
- var UniformBufferLayout = class {
3401
- layout = {};
3402
- /** number of bytes needed for buffer allocation */
3403
- byteLength;
3404
- /** Create a new UniformBufferLayout given a map of attributes. */
3405
- constructor(uniformTypes, uniformSizes = {}) {
3406
- let size = 0;
3407
- for (const [key, uniformType] of Object.entries(uniformTypes)) {
3408
- const typeAndComponents = getVariableShaderTypeInfo(uniformType);
3409
- const { type, components } = typeAndComponents;
3410
- const count = components * (uniformSizes?.[key] ?? 1);
3411
- size = alignTo(size, count);
3412
- const offset = size;
3413
- size += count;
3414
- this.layout[key] = { type, size: count, offset };
3415
- }
3416
- size += (4 - size % 4) % 4;
3417
- const actualByteLength = size * 4;
3418
- this.byteLength = Math.max(actualByteLength, minBufferSize);
3419
- }
3420
- /** Get the data for the complete buffer */
5306
+ // ../core/src/portable/shader-block-writer.ts
5307
+ var ShaderBlockWriter = class {
5308
+ /** Layout metadata used to flatten and serialize values. */
5309
+ layout;
5310
+ /**
5311
+ * Creates a writer for a precomputed shader-block layout.
5312
+ */
5313
+ constructor(layout) {
5314
+ this.layout = layout;
5315
+ }
5316
+ /**
5317
+ * Returns `true` if the flattened layout contains the given field.
5318
+ */
5319
+ has(name2) {
5320
+ return Boolean(this.layout.fields[name2]);
5321
+ }
5322
+ /**
5323
+ * Returns offset and size metadata for a flattened field.
5324
+ */
5325
+ get(name2) {
5326
+ const entry = this.layout.fields[name2];
5327
+ return entry ? { offset: entry.offset, size: entry.size } : void 0;
5328
+ }
5329
+ /**
5330
+ * Flattens nested composite values into leaf-path values understood by {@link UniformBlock}.
5331
+ *
5332
+ * Top-level values may be supplied either in nested object form matching the
5333
+ * declared composite shader types or as already-flattened leaf-path values.
5334
+ */
5335
+ getFlatUniformValues(uniformValues) {
5336
+ const flattenedUniformValues = {};
5337
+ for (const [name2, value] of Object.entries(uniformValues)) {
5338
+ const uniformType = this.layout.uniformTypes[name2];
5339
+ if (uniformType) {
5340
+ this._flattenCompositeValue(flattenedUniformValues, name2, uniformType, value);
5341
+ } else if (this.layout.fields[name2]) {
5342
+ flattenedUniformValues[name2] = value;
5343
+ }
5344
+ }
5345
+ return flattenedUniformValues;
5346
+ }
5347
+ /**
5348
+ * Serializes the supplied values into buffer-backed binary data.
5349
+ *
5350
+ * The returned view length matches {@link ShaderBlockLayout.byteLength}, which
5351
+ * is the exact packed size of the block.
5352
+ */
3421
5353
  getData(uniformValues) {
3422
- const arrayBuffer2 = getScratchArrayBuffer(this.byteLength);
5354
+ const buffer = getScratchArrayBuffer(this.layout.byteLength);
5355
+ new Uint8Array(buffer, 0, this.layout.byteLength).fill(0);
3423
5356
  const typedArrays = {
3424
- i32: new Int32Array(arrayBuffer2),
3425
- u32: new Uint32Array(arrayBuffer2),
3426
- f32: new Float32Array(arrayBuffer2),
3427
- // TODO not implemented
3428
- f16: new Uint16Array(arrayBuffer2)
5357
+ i32: new Int32Array(buffer),
5358
+ u32: new Uint32Array(buffer),
5359
+ f32: new Float32Array(buffer),
5360
+ f16: new Uint16Array(buffer)
3429
5361
  };
3430
- for (const [name2, value] of Object.entries(uniformValues)) {
3431
- const uniformLayout = this.layout[name2];
3432
- if (!uniformLayout) {
3433
- log.warn(`Supplied uniform value ${name2} not present in uniform block layout`)();
3434
- continue;
5362
+ const flattenedUniformValues = this.getFlatUniformValues(uniformValues);
5363
+ for (const [name2, value] of Object.entries(flattenedUniformValues)) {
5364
+ this._writeLeafValue(typedArrays, name2, value);
5365
+ }
5366
+ return new Uint8Array(buffer, 0, this.layout.byteLength);
5367
+ }
5368
+ /**
5369
+ * Recursively flattens nested values using the declared composite shader type.
5370
+ */
5371
+ _flattenCompositeValue(flattenedUniformValues, baseName, uniformType, value) {
5372
+ if (value === void 0) {
5373
+ return;
5374
+ }
5375
+ if (typeof uniformType === "string" || this.layout.fields[baseName]) {
5376
+ flattenedUniformValues[baseName] = value;
5377
+ return;
5378
+ }
5379
+ if (Array.isArray(uniformType)) {
5380
+ const elementType = uniformType[0];
5381
+ const length = uniformType[1];
5382
+ if (Array.isArray(elementType)) {
5383
+ throw new Error(`Nested arrays are not supported for ${baseName}`);
3435
5384
  }
3436
- const { type, size, offset } = uniformLayout;
3437
- const typedArray = typedArrays[type];
3438
- if (size === 1) {
3439
- if (typeof value !== "number" && typeof value !== "boolean") {
3440
- log.warn(
3441
- `Supplied value for single component uniform ${name2} is not a number: ${value}`
3442
- )();
5385
+ if (typeof elementType === "string" && isNumberArray(value)) {
5386
+ this._flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value);
5387
+ return;
5388
+ }
5389
+ if (!Array.isArray(value)) {
5390
+ log.warn(`Unsupported uniform array value for ${baseName}:`, value)();
5391
+ return;
5392
+ }
5393
+ for (let index = 0; index < Math.min(value.length, length); index++) {
5394
+ const elementValue = value[index];
5395
+ if (elementValue === void 0) {
3443
5396
  continue;
3444
5397
  }
3445
- typedArray[offset] = Number(value);
3446
- } else {
3447
- if (!isNumberArray(value)) {
3448
- log.warn(
3449
- `Supplied value for multi component / array uniform ${name2} is not a numeric array: ${value}`
3450
- )();
5398
+ this._flattenCompositeValue(
5399
+ flattenedUniformValues,
5400
+ `${baseName}[${index}]`,
5401
+ elementType,
5402
+ elementValue
5403
+ );
5404
+ }
5405
+ return;
5406
+ }
5407
+ if (isCompositeShaderTypeStruct(uniformType) && isCompositeUniformObject(value)) {
5408
+ for (const [key, subValue] of Object.entries(value)) {
5409
+ if (subValue === void 0) {
3451
5410
  continue;
3452
5411
  }
3453
- typedArray.set(value, offset);
5412
+ const nestedName = `${baseName}.${key}`;
5413
+ this._flattenCompositeValue(flattenedUniformValues, nestedName, uniformType[key], subValue);
3454
5414
  }
5415
+ return;
3455
5416
  }
3456
- return new Uint8Array(arrayBuffer2, 0, this.byteLength);
5417
+ log.warn(`Unsupported uniform value for ${baseName}:`, value)();
3457
5418
  }
3458
- /** Does this layout have a field with specified name */
3459
- has(name2) {
3460
- return Boolean(this.layout[name2]);
5419
+ /**
5420
+ * Expands tightly packed numeric arrays into per-element leaf fields.
5421
+ */
5422
+ _flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value) {
5423
+ const numericValue = value;
5424
+ const elementLayout = getLeafLayoutInfo(elementType, this.layout.layout);
5425
+ const packedElementLength = elementLayout.components;
5426
+ for (let index = 0; index < length; index++) {
5427
+ const start = index * packedElementLength;
5428
+ if (start >= numericValue.length) {
5429
+ break;
5430
+ }
5431
+ if (packedElementLength === 1) {
5432
+ flattenedUniformValues[`${baseName}[${index}]`] = Number(numericValue[start]);
5433
+ } else {
5434
+ flattenedUniformValues[`${baseName}[${index}]`] = sliceNumericArray(
5435
+ value,
5436
+ start,
5437
+ start + packedElementLength
5438
+ );
5439
+ }
5440
+ }
3461
5441
  }
3462
- /** Get offset and size for a field with specified name */
3463
- get(name2) {
3464
- const layout = this.layout[name2];
3465
- return layout;
5442
+ /**
5443
+ * Writes one flattened leaf value into its typed-array view.
5444
+ */
5445
+ _writeLeafValue(typedArrays, name2, value) {
5446
+ const entry = this.layout.fields[name2];
5447
+ if (!entry) {
5448
+ log.warn(`Uniform ${name2} not found in layout`)();
5449
+ return;
5450
+ }
5451
+ const { type, components, columns, rows, offset, columnStride } = entry;
5452
+ const array = typedArrays[type];
5453
+ if (components === 1) {
5454
+ array[offset] = Number(value);
5455
+ return;
5456
+ }
5457
+ const sourceValue = value;
5458
+ if (columns === 1) {
5459
+ for (let componentIndex = 0; componentIndex < components; componentIndex++) {
5460
+ array[offset + componentIndex] = Number(sourceValue[componentIndex] ?? 0);
5461
+ }
5462
+ return;
5463
+ }
5464
+ let sourceIndex = 0;
5465
+ for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
5466
+ const columnOffset = offset + columnIndex * columnStride;
5467
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
5468
+ array[columnOffset + rowIndex] = Number(sourceValue[sourceIndex++] ?? 0);
5469
+ }
5470
+ }
3466
5471
  }
3467
5472
  };
5473
+ function isCompositeUniformObject(value) {
5474
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && !ArrayBuffer.isView(value);
5475
+ }
5476
+ function sliceNumericArray(value, start, end) {
5477
+ return Array.prototype.slice.call(value, start, end);
5478
+ }
3468
5479
 
3469
5480
  // ../core/src/utils/array-equal.ts
5481
+ var MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH = 128;
3470
5482
  function arrayEqual(a, b, limit = 16) {
3471
- if (a !== b) {
3472
- return false;
5483
+ if (a === b) {
5484
+ return true;
3473
5485
  }
3474
5486
  const arrayA = a;
3475
5487
  const arrayB = b;
3476
- if (!isNumberArray(arrayA)) {
5488
+ if (!isNumberArray(arrayA) || !isNumberArray(arrayB)) {
3477
5489
  return false;
3478
5490
  }
3479
- if (isNumberArray(arrayB) && arrayA.length === arrayB.length) {
3480
- for (let i = 0; i < arrayA.length; ++i) {
3481
- if (arrayB[i] !== arrayA[i]) {
3482
- return false;
3483
- }
5491
+ if (arrayA.length !== arrayB.length) {
5492
+ return false;
5493
+ }
5494
+ const maxCompareLength = Math.min(limit, MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH);
5495
+ if (arrayA.length > maxCompareLength) {
5496
+ return false;
5497
+ }
5498
+ for (let i = 0; i < arrayA.length; ++i) {
5499
+ if (arrayB[i] !== arrayA[i]) {
5500
+ return false;
3484
5501
  }
3485
5502
  }
3486
5503
  return true;
@@ -3545,27 +5562,33 @@ ${htmlLog}
3545
5562
  };
3546
5563
 
3547
5564
  // ../core/src/portable/uniform-store.ts
5565
+ var minUniformBufferSize = 1024;
3548
5566
  var UniformStore = class {
5567
+ /** Device used to infer layout and allocate buffers. */
5568
+ device;
3549
5569
  /** Stores the uniform values for each uniform block */
3550
5570
  uniformBlocks = /* @__PURE__ */ new Map();
3551
- /** Can generate data for a uniform buffer for each block from data */
3552
- uniformBufferLayouts = /* @__PURE__ */ new Map();
5571
+ /** Flattened layout metadata for each block. */
5572
+ shaderBlockLayouts = /* @__PURE__ */ new Map();
5573
+ /** Serializers for block-backed uniform data. */
5574
+ shaderBlockWriters = /* @__PURE__ */ new Map();
3553
5575
  /** Actual buffer for the blocks */
3554
5576
  uniformBuffers = /* @__PURE__ */ new Map();
3555
5577
  /**
3556
- * Create a new UniformStore instance
3557
- * @param blocks
5578
+ * Creates a new {@link UniformStore} for the supplied device and block definitions.
3558
5579
  */
3559
- constructor(blocks) {
5580
+ constructor(device, blocks) {
5581
+ this.device = device;
3560
5582
  for (const [bufferName, block] of Object.entries(blocks)) {
3561
5583
  const uniformBufferName = bufferName;
3562
- const uniformBufferLayout = new UniformBufferLayout(
3563
- block.uniformTypes ?? {},
3564
- block.uniformSizes ?? {}
3565
- );
3566
- this.uniformBufferLayouts.set(uniformBufferName, uniformBufferLayout);
5584
+ const shaderBlockLayout = makeShaderBlockLayout(block.uniformTypes ?? {}, {
5585
+ layout: block.layout ?? getDefaultUniformBufferLayout(device)
5586
+ });
5587
+ const shaderBlockWriter = new ShaderBlockWriter(shaderBlockLayout);
5588
+ this.shaderBlockLayouts.set(uniformBufferName, shaderBlockLayout);
5589
+ this.shaderBlockWriters.set(uniformBufferName, shaderBlockWriter);
3567
5590
  const uniformBlock = new UniformBlock({ name: bufferName });
3568
- uniformBlock.setUniforms(block.defaultUniforms || {});
5591
+ uniformBlock.setUniforms(shaderBlockWriter.getFlatUniformValues(block.defaultUniforms || {}));
3569
5592
  this.uniformBlocks.set(uniformBufferName, uniformBlock);
3570
5593
  }
3571
5594
  }
@@ -3577,33 +5600,51 @@ ${htmlLog}
3577
5600
  }
3578
5601
  /**
3579
5602
  * Set uniforms
3580
- * Makes all properties partial
5603
+ *
5604
+ * Makes all group properties partial and eagerly propagates changes to any
5605
+ * managed GPU buffers.
3581
5606
  */
3582
5607
  setUniforms(uniforms) {
3583
5608
  for (const [blockName, uniformValues] of Object.entries(uniforms)) {
3584
- this.uniformBlocks.get(blockName)?.setUniforms(uniformValues);
5609
+ const uniformBufferName = blockName;
5610
+ const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
5611
+ const flattenedUniforms = shaderBlockWriter?.getFlatUniformValues(
5612
+ uniformValues || {}
5613
+ );
5614
+ this.uniformBlocks.get(uniformBufferName)?.setUniforms(flattenedUniforms || {});
3585
5615
  }
3586
5616
  this.updateUniformBuffers();
3587
5617
  }
3588
- /** Get the required minimum length of the uniform buffer */
5618
+ /**
5619
+ * Returns the allocation size for the named uniform buffer.
5620
+ *
5621
+ * This may exceed the packed layout size because minimum buffer-size policy is
5622
+ * applied at the store layer.
5623
+ */
3589
5624
  getUniformBufferByteLength(uniformBufferName) {
3590
- return this.uniformBufferLayouts.get(uniformBufferName)?.byteLength || 0;
5625
+ const packedByteLength = this.shaderBlockLayouts.get(uniformBufferName)?.byteLength || 0;
5626
+ return Math.max(packedByteLength, minUniformBufferSize);
3591
5627
  }
3592
- /** Get formatted binary memory that can be uploaded to a buffer */
5628
+ /**
5629
+ * Returns packed binary data that can be uploaded to the named uniform buffer.
5630
+ *
5631
+ * The returned view length matches the packed block size and is not padded to
5632
+ * the store's minimum allocation size.
5633
+ */
3593
5634
  getUniformBufferData(uniformBufferName) {
3594
5635
  const uniformValues = this.uniformBlocks.get(uniformBufferName)?.getAllUniforms() || {};
3595
- return this.uniformBufferLayouts.get(uniformBufferName)?.getData(uniformValues);
5636
+ const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
5637
+ return shaderBlockWriter?.getData(uniformValues) || new Uint8Array(0);
3596
5638
  }
3597
5639
  /**
3598
- * Creates an unmanaged uniform buffer (umnanaged means that application is responsible for destroying it)
3599
- * The new buffer is initialized with current / supplied values
5640
+ * Creates an unmanaged uniform buffer initialized with the current or supplied values.
3600
5641
  */
3601
- createUniformBuffer(device, uniformBufferName, uniforms) {
5642
+ createUniformBuffer(uniformBufferName, uniforms) {
3602
5643
  if (uniforms) {
3603
5644
  this.setUniforms(uniforms);
3604
5645
  }
3605
5646
  const byteLength = this.getUniformBufferByteLength(uniformBufferName);
3606
- const uniformBuffer = device.createBuffer({
5647
+ const uniformBuffer = this.device.createBuffer({
3607
5648
  usage: Buffer2.UNIFORM | Buffer2.COPY_DST,
3608
5649
  byteLength
3609
5650
  });
@@ -3611,11 +5652,11 @@ ${htmlLog}
3611
5652
  uniformBuffer.write(uniformBufferData);
3612
5653
  return uniformBuffer;
3613
5654
  }
3614
- /** Get the managed uniform buffer. "managed" resources are destroyed when the uniformStore is destroyed. */
3615
- getManagedUniformBuffer(device, uniformBufferName) {
5655
+ /** Returns the managed uniform buffer for the named block. */
5656
+ getManagedUniformBuffer(uniformBufferName) {
3616
5657
  if (!this.uniformBuffers.get(uniformBufferName)) {
3617
5658
  const byteLength = this.getUniformBufferByteLength(uniformBufferName);
3618
- const uniformBuffer = device.createBuffer({
5659
+ const uniformBuffer = this.device.createBuffer({
3619
5660
  usage: Buffer2.UNIFORM | Buffer2.COPY_DST,
3620
5661
  byteLength
3621
5662
  });
@@ -3623,7 +5664,11 @@ ${htmlLog}
3623
5664
  }
3624
5665
  return this.uniformBuffers.get(uniformBufferName);
3625
5666
  }
3626
- /** Updates all uniform buffers where values have changed */
5667
+ /**
5668
+ * Updates every managed uniform buffer whose source uniforms have changed.
5669
+ *
5670
+ * @returns The first redraw reason encountered, or `false` if nothing changed.
5671
+ */
3627
5672
  updateUniformBuffers() {
3628
5673
  let reason = false;
3629
5674
  for (const uniformBufferName of this.uniformBlocks.keys()) {
@@ -3635,7 +5680,11 @@ ${htmlLog}
3635
5680
  }
3636
5681
  return reason;
3637
5682
  }
3638
- /** Update one uniform buffer. Only updates if values have changed */
5683
+ /**
5684
+ * Updates one managed uniform buffer if its corresponding block is dirty.
5685
+ *
5686
+ * @returns The redraw reason for the update, or `false` if no write occurred.
5687
+ */
3639
5688
  updateUniformBuffer(uniformBufferName) {
3640
5689
  const uniformBlock = this.uniformBlocks.get(uniformBufferName);
3641
5690
  let uniformBuffer = this.uniformBuffers.get(uniformBufferName);
@@ -3656,8 +5705,49 @@ ${htmlLog}
3656
5705
  return reason;
3657
5706
  }
3658
5707
  };
5708
+ function getDefaultUniformBufferLayout(device) {
5709
+ return device.type === "webgpu" ? "wgsl-uniform" : "std140";
5710
+ }
5711
+
5712
+ // ../core/src/shadertypes/texture-types/texture-layout.ts
5713
+ function getTextureImageView(arrayBuffer2, memoryLayout, format, image = 0) {
5714
+ const formatInfo = textureFormatDecoder.getInfo(format);
5715
+ const bytesPerComponent = formatInfo.bytesPerPixel / formatInfo.components;
5716
+ const { bytesPerImage } = memoryLayout;
5717
+ const offset = bytesPerImage * image;
5718
+ const totalPixels = memoryLayout.bytesPerImage / bytesPerComponent;
5719
+ switch (format) {
5720
+ case "rgba8unorm":
5721
+ case "bgra8unorm":
5722
+ case "rgba8uint":
5723
+ return new Uint8Array(arrayBuffer2, offset, totalPixels);
5724
+ case "r8unorm":
5725
+ return new Uint8Array(arrayBuffer2, offset, totalPixels);
5726
+ case "r16uint":
5727
+ case "rgba16uint":
5728
+ return new Uint16Array(arrayBuffer2, offset, totalPixels);
5729
+ case "r32uint":
5730
+ case "rgba32uint":
5731
+ return new Uint32Array(arrayBuffer2, offset, totalPixels);
5732
+ case "r32float":
5733
+ return new Float32Array(arrayBuffer2, offset, totalPixels);
5734
+ case "rgba16float":
5735
+ return new Uint16Array(arrayBuffer2, offset, totalPixels);
5736
+ case "rgba32float":
5737
+ return new Float32Array(arrayBuffer2, offset, totalPixels);
5738
+ default:
5739
+ throw new Error(`Unsupported format: ${format}`);
5740
+ }
5741
+ }
5742
+ function setTextureImageData(arrayBuffer2, memoryLayout, format, data, image = 0) {
5743
+ const offset = 0;
5744
+ const totalPixels = memoryLayout.bytesPerImage / memoryLayout.bytesPerPixel;
5745
+ const subArray = data.subarray(0, totalPixels);
5746
+ const typedArray = getTextureImageView(arrayBuffer2, memoryLayout, format, image);
5747
+ typedArray.set(subArray, offset);
5748
+ }
3659
5749
 
3660
- // ../core/src/shadertypes/textures/pixel-utils.ts
5750
+ // ../core/src/shadertypes/texture-types/pixel-utils.ts
3661
5751
  function readPixel(pixelData, x, y, bitsPerChannel) {
3662
5752
  if (x < 0 || x >= pixelData.width || y < 0 || y >= pixelData.height) {
3663
5753
  throw new Error("Coordinates out of bounds.");
@@ -3667,7 +5757,7 @@ ${htmlLog}
3667
5757
  let bitOffsetWithinPixel = 0;
3668
5758
  const channels = [];
3669
5759
  for (let i = 0; i < 4; i++) {
3670
- const bits = bitsPerChannel[i];
5760
+ const bits = bitsPerChannel[i] ?? 0;
3671
5761
  if (bits <= 0) {
3672
5762
  channels.push(0);
3673
5763
  } else {
@@ -3676,14 +5766,14 @@ ${htmlLog}
3676
5766
  bitOffsetWithinPixel += bits;
3677
5767
  }
3678
5768
  }
3679
- return [channels[0], channels[1], channels[2], channels[3]];
5769
+ return [channels[0] ?? 0, channels[1] ?? 0, channels[2] ?? 0, channels[3] ?? 0];
3680
5770
  }
3681
5771
  function writePixel(dataView, bitOffset, bitsPerChannel, pixel) {
3682
5772
  let currentBitOffset = bitOffset;
3683
5773
  for (let channel = 0; channel < 4; channel++) {
3684
- const bits = bitsPerChannel[channel];
5774
+ const bits = bitsPerChannel[channel] ?? 0;
3685
5775
  const maxValue = (1 << bits) - 1;
3686
- const channelValue = pixel[channel] & maxValue;
5776
+ const channelValue = (pixel[channel] ?? 0) & maxValue;
3687
5777
  writeBitsToDataView(dataView, currentBitOffset, bits, channelValue);
3688
5778
  currentBitOffset += bits;
3689
5779
  }