@luma.gl/effects 9.2.5 → 9.3.0-alpha.10

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 +2692 -645
  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 {
@@ -887,7 +1266,7 @@ var __exports__ = (() => {
887
1266
  }
888
1267
  /** The usage with which this buffer was created */
889
1268
  usage;
890
- /** For index buffers, whether indices are 16 or 32 bit */
1269
+ /** For index buffers, whether indices are 8, 16 or 32 bit. Note: uint8 indices are automatically converted to uint16 for WebGPU compatibility */
891
1270
  indexType;
892
1271
  /** "Time" of last update, can be used to check if redraw is needed */
893
1272
  updateTimestamp;
@@ -898,6 +1277,8 @@ var __exports__ = (() => {
898
1277
  deducedProps.indexType = "uint32";
899
1278
  } else if (props.data instanceof Uint16Array) {
900
1279
  deducedProps.indexType = "uint16";
1280
+ } else if (props.data instanceof Uint8Array) {
1281
+ deducedProps.indexType = "uint8";
901
1282
  }
902
1283
  }
903
1284
  delete deducedProps.data;
@@ -916,18 +1297,26 @@ var __exports__ = (() => {
916
1297
  /** A partial CPU-side copy of the data in this buffer, for debugging purposes */
917
1298
  debugData = new ArrayBuffer(0);
918
1299
  /** This doesn't handle partial non-zero offset updates correctly */
919
- _setDebugData(data, byteOffset, byteLength) {
920
- 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
+ }
921
1309
  const debugDataLength = Math.min(
922
1310
  data ? data.byteLength : byteLength,
923
1311
  _Buffer.DEBUG_DATA_MAX_LENGTH
924
1312
  );
925
1313
  if (arrayBuffer2 === null) {
926
1314
  this.debugData = new ArrayBuffer(debugDataLength);
927
- } else if (byteOffset === 0 && byteLength === arrayBuffer2.byteLength) {
928
- this.debugData = arrayBuffer2.slice(0, debugDataLength);
929
1315
  } else {
930
- 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;
931
1320
  }
932
1321
  }
933
1322
  };
@@ -961,61 +1350,73 @@ var __exports__ = (() => {
961
1350
  onMapped: void 0
962
1351
  });
963
1352
 
964
- // ../core/src/shadertypes/data-types/decode-data-types.ts
965
- function getDataTypeInfo(type) {
966
- const [signedType, primitiveType, byteLength] = NORMALIZED_TYPE_MAP[type];
967
- const normalized = type.includes("norm");
968
- const integer = !normalized && !type.startsWith("float");
969
- const signed = type.startsWith("s");
970
- return {
971
- signedType,
972
- primitiveType,
973
- byteLength,
974
- normalized,
975
- integer,
976
- signed
977
- };
978
- }
979
- function getNormalizedDataType(signedDataType) {
980
- const dataType = signedDataType;
981
- switch (dataType) {
982
- case "uint8":
983
- return "unorm8";
984
- case "sint8":
985
- return "snorm8";
986
- case "uint16":
987
- return "unorm16";
988
- case "sint16":
989
- return "snorm16";
990
- default:
991
- 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
+ };
992
1373
  }
993
- }
994
- function alignTo(size, count) {
995
- switch (count) {
996
- case 1:
997
- return size;
998
- case 2:
999
- return size + size % 2;
1000
- default:
1001
- 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
+ }
1002
1389
  }
1003
- }
1004
- function getDataType(arrayOrType) {
1005
- const Constructor = ArrayBuffer.isView(arrayOrType) ? arrayOrType.constructor : arrayOrType;
1006
- if (Constructor === Uint8ClampedArray) {
1007
- 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
+ }
1008
1400
  }
1009
- const info = Object.values(NORMALIZED_TYPE_MAP).find((entry) => Constructor === entry[4]);
1010
- if (!info) {
1011
- 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];
1012
1412
  }
1013
- return info[0];
1014
- }
1015
- function getTypedArrayConstructor(type) {
1016
- const [, , , , Constructor] = NORMALIZED_TYPE_MAP[type];
1017
- return Constructor;
1018
- }
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();
1019
1420
  var NORMALIZED_TYPE_MAP = {
1020
1421
  uint8: ["uint8", "u32", 1, false, Uint8Array],
1021
1422
  sint8: ["sint8", "i32", 1, false, Int8Array],
@@ -1031,87 +1432,99 @@ var __exports__ = (() => {
1031
1432
  sint32: ["sint32", "i32", 4, false, Int32Array]
1032
1433
  };
1033
1434
 
1034
- // ../core/src/shadertypes/vertex-arrays/decode-vertex-format.ts
1035
- function getVertexFormatInfo(format) {
1036
- let webglOnly;
1037
- if (format.endsWith("-webgl")) {
1038
- format.replace("-webgl", "");
1039
- webglOnly = true;
1040
- }
1041
- const [type_, count] = format.split("x");
1042
- const type = type_;
1043
- const components = count ? parseInt(count) : 1;
1044
- const decodedType = getDataTypeInfo(type);
1045
- const result = {
1046
- type,
1047
- components,
1048
- byteLength: decodedType.byteLength * components,
1049
- integer: decodedType.integer,
1050
- signed: decodedType.signed,
1051
- normalized: decodedType.normalized
1052
- };
1053
- if (webglOnly) {
1054
- result.webglOnly = true;
1055
- }
1056
- return result;
1057
- }
1058
- function makeVertexFormat(signedDataType, components, normalized) {
1059
- const dataType = normalized ? getNormalizedDataType(signedDataType) : signedDataType;
1060
- switch (dataType) {
1061
- case "unorm8":
1062
- if (components === 1) {
1063
- return "unorm8";
1064
- }
1065
- if (components === 3) {
1066
- return "unorm8x3-webgl";
1067
- }
1068
- return `${dataType}x${components}`;
1069
- case "snorm8":
1070
- case "uint8":
1071
- case "sint8":
1072
- case "uint16":
1073
- case "sint16":
1074
- case "unorm16":
1075
- case "snorm16":
1076
- case "float16":
1077
- if (components === 1 || components === 3) {
1078
- throw new Error(`size: ${components}`);
1079
- }
1080
- return `${dataType}x${components}`;
1081
- default:
1082
- 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;
1083
1462
  }
1084
- }
1085
- function getVertexFormatFromAttribute(typedArray, size, normalized) {
1086
- if (!size || size > 4) {
1087
- 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
+ case "uint8":
1477
+ case "sint8":
1478
+ case "uint16":
1479
+ case "sint16":
1480
+ case "unorm16":
1481
+ case "snorm16":
1482
+ case "float16":
1483
+ if (components === 1 || components === 3) {
1484
+ throw new Error(`size: ${components}`);
1485
+ }
1486
+ return `${dataType}x${components}`;
1487
+ default:
1488
+ return components === 1 ? dataType : `${dataType}x${components}`;
1489
+ }
1088
1490
  }
1089
- const components = size;
1090
- const signedDataType = getDataType(typedArray);
1091
- return makeVertexFormat(signedDataType, components, normalized);
1092
- }
1093
- function getCompatibleVertexFormat(opts) {
1094
- let vertexType;
1095
- switch (opts.primitiveType) {
1096
- case "f32":
1097
- vertexType = "float32";
1098
- break;
1099
- case "i32":
1100
- vertexType = "sint32";
1101
- break;
1102
- case "u32":
1103
- vertexType = "uint32";
1104
- break;
1105
- case "f16":
1106
- return opts.components <= 2 ? "float16x2" : "float16x4";
1491
+ /** Get the vertex format for an attribute with TypedArray and size */
1492
+ getVertexFormatFromAttribute(typedArray, size, normalized) {
1493
+ if (!size || size > 4) {
1494
+ throw new Error(`size ${size}`);
1495
+ }
1496
+ const components = size;
1497
+ const signedDataType = dataTypeDecoder.getDataType(typedArray);
1498
+ return this.makeVertexFormat(signedDataType, components, normalized);
1107
1499
  }
1108
- if (opts.components === 1) {
1109
- return vertexType;
1500
+ /**
1501
+ * Return a "default" vertex format for a certain shader data type
1502
+ * The simplest vertex format that matches the shader attribute's data type
1503
+ */
1504
+ getCompatibleVertexFormat(opts) {
1505
+ let vertexType;
1506
+ switch (opts.primitiveType) {
1507
+ case "f32":
1508
+ vertexType = "float32";
1509
+ break;
1510
+ case "i32":
1511
+ vertexType = "sint32";
1512
+ break;
1513
+ case "u32":
1514
+ vertexType = "uint32";
1515
+ break;
1516
+ case "f16":
1517
+ return opts.components <= 2 ? "float16x2" : "float16x4";
1518
+ }
1519
+ if (opts.components === 1) {
1520
+ return vertexType;
1521
+ }
1522
+ return `${vertexType}x${opts.components}`;
1110
1523
  }
1111
- return `${vertexType}x${opts.components}`;
1112
- }
1524
+ };
1525
+ var vertexFormatDecoder = new VertexFormatDecoder();
1113
1526
 
1114
- // ../core/src/shadertypes/textures/texture-format-table.ts
1527
+ // ../core/src/shadertypes/texture-types/texture-format-table.ts
1115
1528
  var texture_compression_bc = "texture-compression-bc";
1116
1529
  var texture_compression_astc = "texture-compression-astc";
1117
1530
  var texture_compression_etc2 = "texture-compression-etc2";
@@ -1122,6 +1535,7 @@ var __exports__ = (() => {
1122
1535
  var float16_renderable = "float16-renderable-webgl";
1123
1536
  var rgb9e5ufloat_renderable = "rgb9e5ufloat-renderable-webgl";
1124
1537
  var snorm8_renderable = "snorm8-renderable-webgl";
1538
+ var norm16_webgl = "norm16-webgl";
1125
1539
  var norm16_renderable = "norm16-renderable-webgl";
1126
1540
  var snorm16_renderable = "snorm16-renderable-webgl";
1127
1541
  var float32_filterable = "float32-filterable";
@@ -1155,16 +1569,16 @@ var __exports__ = (() => {
1155
1569
  "rgba8sint": {},
1156
1570
  "bgra8unorm": {},
1157
1571
  "bgra8unorm-srgb": {},
1158
- "r16unorm": { f: norm16_renderable },
1159
- "rg16unorm": { render: norm16_renderable },
1160
- "rgb16unorm-webgl": { f: norm16_renderable },
1572
+ "r16unorm": { f: norm16_webgl, render: norm16_renderable },
1573
+ "rg16unorm": { f: norm16_webgl, render: norm16_renderable },
1574
+ "rgb16unorm-webgl": { f: norm16_webgl, render: false },
1161
1575
  // rgb not renderable
1162
- "rgba16unorm": { render: norm16_renderable },
1163
- "r16snorm": { f: snorm16_renderable },
1164
- "rg16snorm": { render: snorm16_renderable },
1165
- "rgb16snorm-webgl": { f: norm16_renderable },
1576
+ "rgba16unorm": { f: norm16_webgl, render: norm16_renderable },
1577
+ "r16snorm": { f: norm16_webgl, render: snorm16_renderable },
1578
+ "rg16snorm": { f: norm16_webgl, render: snorm16_renderable },
1579
+ "rgb16snorm-webgl": { f: norm16_webgl, render: false },
1166
1580
  // rgb not renderable
1167
- "rgba16snorm": { render: snorm16_renderable },
1581
+ "rgba16snorm": { f: norm16_webgl, render: snorm16_renderable },
1168
1582
  "r16uint": {},
1169
1583
  "rg16uint": {},
1170
1584
  "rgba16uint": {},
@@ -1267,7 +1681,7 @@ var __exports__ = (() => {
1267
1681
  // WEBGL_compressed_texture_pvrtc
1268
1682
  "pvrtc-rgb4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1269
1683
  "pvrtc-rgba4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1270
- "pvrtc-rbg2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1684
+ "pvrtc-rgb2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1271
1685
  "pvrtc-rgba2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1272
1686
  // WEBGL_compressed_texture_etc1
1273
1687
  "etc1-rbg-unorm-webgl": { f: texture_compression_etc1_webgl },
@@ -1281,7 +1695,10 @@ var __exports__ = (() => {
1281
1695
  ...TEXTURE_FORMAT_COMPRESSED_TABLE
1282
1696
  };
1283
1697
 
1284
- // ../core/src/shadertypes/textures/texture-format-decoder.ts
1698
+ // ../core/src/shadertypes/texture-types/texture-format-decoder.ts
1699
+ var RGB_FORMAT_REGEX = /^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/;
1700
+ var COLOR_FORMAT_PREFIXES = ["rgb", "rgba", "bgra"];
1701
+ var DEPTH_FORMAT_PREFIXES = ["depth", "stencil"];
1285
1702
  var COMPRESSED_TEXTURE_FORMAT_PREFIXES = [
1286
1703
  "bc1",
1287
1704
  "bc2",
@@ -1297,49 +1714,83 @@ var __exports__ = (() => {
1297
1714
  "astc",
1298
1715
  "pvrtc"
1299
1716
  ];
1300
- var RGB_FORMAT_REGEX = /^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/;
1301
1717
  var TextureFormatDecoder = class {
1302
- /** Returns information about a texture format, e.g. attatchment type, components, byte length and flags (integer, signed, normalized) */
1303
- getInfo(format) {
1304
- return getTextureFormatInfo(format);
1305
- }
1306
1718
  /** Checks if a texture format is color */
1307
1719
  isColor(format) {
1308
- return format.startsWith("rgba") || format.startsWith("bgra") || format.startsWith("rgb");
1720
+ return COLOR_FORMAT_PREFIXES.some((prefix) => format.startsWith(prefix));
1309
1721
  }
1310
1722
  /** Checks if a texture format is depth or stencil */
1311
1723
  isDepthStencil(format) {
1312
- return format.startsWith("depth") || format.startsWith("stencil");
1724
+ return DEPTH_FORMAT_PREFIXES.some((prefix) => format.startsWith(prefix));
1313
1725
  }
1314
1726
  /** Checks if a texture format is compressed */
1315
1727
  isCompressed(format) {
1316
1728
  return COMPRESSED_TEXTURE_FORMAT_PREFIXES.some((prefix) => format.startsWith(prefix));
1317
1729
  }
1318
- /**
1319
- * Returns the "static" capabilities of a texture format.
1320
- * @note Needs to be checked against current device
1321
- */
1730
+ /** Returns information about a texture format, e.g. attachment type, components, byte length and flags (integer, signed, normalized) */
1731
+ getInfo(format) {
1732
+ return getTextureFormatInfo(format);
1733
+ }
1734
+ /** "static" capabilities of a texture format. @note Needs to be adjusted against current device */
1322
1735
  getCapabilities(format) {
1323
- const info = getTextureFormatDefinition(format);
1324
- const formatCapabilities = {
1325
- format,
1326
- create: info.f ?? true,
1327
- render: info.render ?? true,
1328
- filter: info.filter ?? true,
1329
- blend: info.blend ?? true,
1330
- store: info.store ?? true
1331
- };
1332
- const formatInfo = getTextureFormatInfo(format);
1333
- const isDepthStencil = format.startsWith("depth") || format.startsWith("stencil");
1334
- const isSigned = formatInfo?.signed;
1335
- const isInteger = formatInfo?.integer;
1336
- const isWebGLSpecific = formatInfo?.webgl;
1337
- formatCapabilities.render &&= !isSigned;
1338
- formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1339
- return formatCapabilities;
1736
+ return getTextureFormatCapabilities(format);
1737
+ }
1738
+ /** Computes the memory layout for a texture, in particular including row byte alignment */
1739
+ computeMemoryLayout(opts) {
1740
+ return computeTextureMemoryLayout(opts);
1340
1741
  }
1341
1742
  };
1342
1743
  var textureFormatDecoder = new TextureFormatDecoder();
1744
+ function computeTextureMemoryLayout({
1745
+ format,
1746
+ width,
1747
+ height,
1748
+ depth,
1749
+ byteAlignment
1750
+ }) {
1751
+ const formatInfo = textureFormatDecoder.getInfo(format);
1752
+ const {
1753
+ bytesPerPixel,
1754
+ bytesPerBlock = bytesPerPixel,
1755
+ blockWidth = 1,
1756
+ blockHeight = 1,
1757
+ compressed = false
1758
+ } = formatInfo;
1759
+ const blockColumns = compressed ? Math.ceil(width / blockWidth) : width;
1760
+ const blockRows = compressed ? Math.ceil(height / blockHeight) : height;
1761
+ const unpaddedBytesPerRow = blockColumns * bytesPerBlock;
1762
+ const bytesPerRow = Math.ceil(unpaddedBytesPerRow / byteAlignment) * byteAlignment;
1763
+ const rowsPerImage = blockRows;
1764
+ const byteLength = bytesPerRow * rowsPerImage * depth;
1765
+ return {
1766
+ bytesPerPixel,
1767
+ bytesPerRow,
1768
+ rowsPerImage,
1769
+ depthOrArrayLayers: depth,
1770
+ bytesPerImage: bytesPerRow * rowsPerImage,
1771
+ byteLength
1772
+ };
1773
+ }
1774
+ function getTextureFormatCapabilities(format) {
1775
+ const info = getTextureFormatDefinition(format);
1776
+ const formatCapabilities = {
1777
+ format,
1778
+ create: info.f ?? true,
1779
+ render: info.render ?? true,
1780
+ filter: info.filter ?? true,
1781
+ blend: info.blend ?? true,
1782
+ store: info.store ?? true
1783
+ };
1784
+ const formatInfo = getTextureFormatInfo(format);
1785
+ const isDepthStencil = format.startsWith("depth") || format.startsWith("stencil");
1786
+ const isSigned = formatInfo?.signed;
1787
+ const isInteger = formatInfo?.integer;
1788
+ const isWebGLSpecific = formatInfo?.webgl;
1789
+ const isCompressed = Boolean(formatInfo?.compressed);
1790
+ formatCapabilities.render &&= !isDepthStencil && !isCompressed;
1791
+ formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1792
+ return formatCapabilities;
1793
+ }
1343
1794
  function getTextureFormatInfo(format) {
1344
1795
  let formatInfo = getTextureFormatInfoUsingTable(format);
1345
1796
  if (textureFormatDecoder.isCompressed(format)) {
@@ -1348,19 +1799,20 @@ var __exports__ = (() => {
1348
1799
  formatInfo.bytesPerPixel = 1;
1349
1800
  formatInfo.srgb = false;
1350
1801
  formatInfo.compressed = true;
1802
+ formatInfo.bytesPerBlock = getCompressedTextureBlockByteLength(format);
1351
1803
  const blockSize = getCompressedTextureBlockSize(format);
1352
1804
  if (blockSize) {
1353
1805
  formatInfo.blockWidth = blockSize.blockWidth;
1354
1806
  formatInfo.blockHeight = blockSize.blockHeight;
1355
1807
  }
1356
1808
  }
1357
- const matches = RGB_FORMAT_REGEX.exec(format);
1809
+ const matches = !formatInfo.packed ? RGB_FORMAT_REGEX.exec(format) : null;
1358
1810
  if (matches) {
1359
1811
  const [, channels, length, type, srgb, suffix] = matches;
1360
1812
  const dataType = `${type}${length}`;
1361
- const decodedType = getDataTypeInfo(dataType);
1813
+ const decodedType = dataTypeDecoder.getDataTypeInfo(dataType);
1362
1814
  const bits = decodedType.byteLength * 8;
1363
- const components = channels.length;
1815
+ const components = channels?.length ?? 1;
1364
1816
  const bitsPerChannel = [
1365
1817
  bits,
1366
1818
  components >= 2 ? bits : 0,
@@ -1377,7 +1829,7 @@ var __exports__ = (() => {
1377
1829
  signed: decodedType.signed,
1378
1830
  normalized: decodedType.normalized,
1379
1831
  bitsPerChannel,
1380
- bytesPerPixel: decodedType.byteLength * channels.length,
1832
+ bytesPerPixel: decodedType.byteLength * components,
1381
1833
  packed: formatInfo.packed,
1382
1834
  srgb: formatInfo.srgb
1383
1835
  };
@@ -1433,10 +1885,31 @@ var __exports__ = (() => {
1433
1885
  const [, blockWidth, blockHeight] = matches;
1434
1886
  return { blockWidth: Number(blockWidth), blockHeight: Number(blockHeight) };
1435
1887
  }
1888
+ if (format.startsWith("bc") || format.startsWith("etc1") || format.startsWith("etc2") || format.startsWith("eac") || format.startsWith("atc")) {
1889
+ return { blockWidth: 4, blockHeight: 4 };
1890
+ }
1891
+ if (format.startsWith("pvrtc-rgb4") || format.startsWith("pvrtc-rgba4")) {
1892
+ return { blockWidth: 4, blockHeight: 4 };
1893
+ }
1894
+ if (format.startsWith("pvrtc-rgb2") || format.startsWith("pvrtc-rgba2")) {
1895
+ return { blockWidth: 8, blockHeight: 4 };
1896
+ }
1436
1897
  return null;
1437
1898
  }
1899
+ function getCompressedTextureBlockByteLength(format) {
1900
+ 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") {
1901
+ return 8;
1902
+ }
1903
+ 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") {
1904
+ return 16;
1905
+ }
1906
+ if (format.startsWith("pvrtc")) {
1907
+ return 8;
1908
+ }
1909
+ return 16;
1910
+ }
1438
1911
 
1439
- // ../core/src/image-utils/image-types.ts
1912
+ // ../core/src/shadertypes/image-types/image-types.ts
1440
1913
  function isExternalImage(data) {
1441
1914
  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;
1442
1915
  }
@@ -1459,6 +1932,52 @@ var __exports__ = (() => {
1459
1932
  // ../core/src/adapter/device.ts
1460
1933
  var DeviceLimits = class {
1461
1934
  };
1935
+ function formatErrorLogArguments(context, args) {
1936
+ const formattedContext = formatErrorLogValue(context);
1937
+ const formattedArgs = args.map(formatErrorLogValue).filter((arg) => arg !== void 0);
1938
+ return [formattedContext, ...formattedArgs].filter((arg) => arg !== void 0);
1939
+ }
1940
+ function formatErrorLogValue(value) {
1941
+ if (value === void 0) {
1942
+ return void 0;
1943
+ }
1944
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1945
+ return value;
1946
+ }
1947
+ if (value instanceof Error) {
1948
+ return value.message;
1949
+ }
1950
+ if (Array.isArray(value)) {
1951
+ return value.map(formatErrorLogValue);
1952
+ }
1953
+ if (typeof value === "object") {
1954
+ if (hasCustomToString(value)) {
1955
+ const stringValue = String(value);
1956
+ if (stringValue !== "[object Object]") {
1957
+ return stringValue;
1958
+ }
1959
+ }
1960
+ if (looksLikeGPUCompilationMessage(value)) {
1961
+ return formatGPUCompilationMessage(value);
1962
+ }
1963
+ return value.constructor?.name || "Object";
1964
+ }
1965
+ return String(value);
1966
+ }
1967
+ function hasCustomToString(value) {
1968
+ return "toString" in value && typeof value.toString === "function" && value.toString !== Object.prototype.toString;
1969
+ }
1970
+ function looksLikeGPUCompilationMessage(value) {
1971
+ return "message" in value && "type" in value;
1972
+ }
1973
+ function formatGPUCompilationMessage(value) {
1974
+ const type = typeof value.type === "string" ? value.type : "message";
1975
+ const message = typeof value.message === "string" ? value.message : "";
1976
+ const lineNum = typeof value.lineNum === "number" ? value.lineNum : null;
1977
+ const linePos = typeof value.linePos === "number" ? value.linePos : null;
1978
+ const location = lineNum !== null && linePos !== null ? ` @ ${lineNum}:${linePos}` : lineNum !== null ? ` @ ${lineNum}` : "";
1979
+ return `${type}${location}: ${message}`.trim();
1980
+ }
1462
1981
  var DeviceFeatures = class {
1463
1982
  features;
1464
1983
  disabledFeatures;
@@ -1488,19 +2007,24 @@ var __exports__ = (() => {
1488
2007
  userData = {};
1489
2008
  /** stats */
1490
2009
  statsManager = lumaStats;
2010
+ /** Internal per-device factory storage */
2011
+ _factories = {};
1491
2012
  /** An abstract timestamp used for change tracking */
1492
2013
  timestamp = 0;
1493
2014
  /** True if this device has been reused during device creation (app has multiple references) */
1494
2015
  _reused = false;
1495
2016
  /** Used by other luma.gl modules to store data on the device */
1496
- _lumaData = {};
2017
+ _moduleData = {};
1497
2018
  _textureCaps = {};
2019
+ /** Internal timestamp query set used when GPU timing collection is enabled for this device. */
2020
+ _debugGPUTimeQuery = null;
1498
2021
  constructor(props) {
1499
2022
  this.props = { ..._Device.defaultProps, ...props };
1500
2023
  this.id = this.props.id || uid(this[Symbol.toStringTag].toLowerCase());
1501
2024
  }
2025
+ // TODO - just expose the shadertypes decoders?
1502
2026
  getVertexFormatInfo(format) {
1503
- return getVertexFormatInfo(format);
2027
+ return vertexFormatDecoder.getVertexFormatInfo(format);
1504
2028
  }
1505
2029
  isVertexFormatSupported(format) {
1506
2030
  return true;
@@ -1548,6 +2072,16 @@ var __exports__ = (() => {
1548
2072
  isTextureFormatCompressed(format) {
1549
2073
  return textureFormatDecoder.isCompressed(format);
1550
2074
  }
2075
+ /** Returns the compressed texture formats that can be created and sampled on this device */
2076
+ getSupportedCompressedTextureFormats() {
2077
+ const supportedFormats = [];
2078
+ for (const format of Object.keys(getTextureFormatTable())) {
2079
+ if (this.isTextureFormatCompressed(format) && this.isTextureFormatSupported(format)) {
2080
+ supportedFormats.push(format);
2081
+ }
2082
+ }
2083
+ return supportedFormats;
2084
+ }
1551
2085
  // DEBUG METHODS
1552
2086
  pushDebugGroup(groupLabel) {
1553
2087
  this.commandEncoder.pushDebugGroup(groupLabel);
@@ -1590,7 +2124,13 @@ var __exports__ = (() => {
1590
2124
  reportError(error, context, ...args) {
1591
2125
  const isHandled = this.props.onError(error, context);
1592
2126
  if (!isHandled) {
1593
- return log.error(error.message, context, ...args);
2127
+ const logArguments = formatErrorLogArguments(context, args);
2128
+ return log.error(
2129
+ this.type === "webgl" ? "%cWebGL" : "%cWebGPU",
2130
+ "color: white; background: red; padding: 2px 6px; border-radius: 3px;",
2131
+ error.message,
2132
+ ...logArguments
2133
+ );
1594
2134
  }
1595
2135
  return () => {
1596
2136
  };
@@ -1612,6 +2152,10 @@ or create a device with the 'debug: true' prop.`;
1612
2152
  }
1613
2153
  return this.canvasContext;
1614
2154
  }
2155
+ /** Create a fence sync object */
2156
+ createFence() {
2157
+ throw new Error("createFence() not implemented");
2158
+ }
1615
2159
  /** Create a RenderPass using the default CommandEncoder */
1616
2160
  beginRenderPass(props) {
1617
2161
  return this.commandEncoder.beginRenderPass(props);
@@ -1620,6 +2164,78 @@ or create a device with the 'debug: true' prop.`;
1620
2164
  beginComputePass(props) {
1621
2165
  return this.commandEncoder.beginComputePass(props);
1622
2166
  }
2167
+ /**
2168
+ * Generate mipmaps for a WebGPU texture.
2169
+ * WebGPU textures must be created up front with the required mip count, usage flags, and a format that supports the chosen generation path.
2170
+ * WebGL uses `Texture.generateMipmapsWebGL()` directly because the backend manages mip generation on the texture object itself.
2171
+ */
2172
+ generateMipmapsWebGPU(_texture) {
2173
+ throw new Error("not implemented");
2174
+ }
2175
+ /** Internal helper for creating a shareable WebGL render-pipeline implementation. */
2176
+ _createSharedRenderPipelineWebGL(_props) {
2177
+ throw new Error("_createSharedRenderPipelineWebGL() not implemented");
2178
+ }
2179
+ /** Internal WebGPU-only helper for retrieving the native bind-group layout for a pipeline group. */
2180
+ _createBindGroupLayoutWebGPU(_pipeline, _group) {
2181
+ throw new Error("_createBindGroupLayoutWebGPU() not implemented");
2182
+ }
2183
+ /** Internal WebGPU-only helper for creating a native bind group. */
2184
+ _createBindGroupWebGPU(_bindGroupLayout, _shaderLayout, _bindings, _group) {
2185
+ throw new Error("_createBindGroupWebGPU() not implemented");
2186
+ }
2187
+ /**
2188
+ * Internal helper that returns `true` when timestamp-query GPU timing should be
2189
+ * collected for this device.
2190
+ */
2191
+ _supportsDebugGPUTime() {
2192
+ return this.features.has("timestamp-query") && Boolean(this.props.debug || this.props.debugGPUTime);
2193
+ }
2194
+ /**
2195
+ * Internal helper that enables device-managed GPU timing collection on the
2196
+ * default command encoder. Reuses the existing query set if timing is already enabled.
2197
+ *
2198
+ * @param queryCount - Number of timestamp slots reserved for profiled passes.
2199
+ * @returns The device-managed timestamp QuerySet, or `null` when timing is not supported or could not be enabled.
2200
+ */
2201
+ _enableDebugGPUTime(queryCount = 256) {
2202
+ if (!this._supportsDebugGPUTime()) {
2203
+ return null;
2204
+ }
2205
+ if (this._debugGPUTimeQuery) {
2206
+ return this._debugGPUTimeQuery;
2207
+ }
2208
+ try {
2209
+ this._debugGPUTimeQuery = this.createQuerySet({ type: "timestamp", count: queryCount });
2210
+ this.commandEncoder = this.createCommandEncoder({
2211
+ id: this.commandEncoder.props.id,
2212
+ timeProfilingQuerySet: this._debugGPUTimeQuery
2213
+ });
2214
+ } catch {
2215
+ this._debugGPUTimeQuery = null;
2216
+ }
2217
+ return this._debugGPUTimeQuery;
2218
+ }
2219
+ /**
2220
+ * Internal helper that disables device-managed GPU timing collection and restores
2221
+ * the default command encoder to an unprofiled state.
2222
+ */
2223
+ _disableDebugGPUTime() {
2224
+ if (!this._debugGPUTimeQuery) {
2225
+ return;
2226
+ }
2227
+ if (this.commandEncoder.getTimeProfilingQuerySet() === this._debugGPUTimeQuery) {
2228
+ this.commandEncoder = this.createCommandEncoder({
2229
+ id: this.commandEncoder.props.id
2230
+ });
2231
+ }
2232
+ this._debugGPUTimeQuery.destroy();
2233
+ this._debugGPUTimeQuery = null;
2234
+ }
2235
+ /** Internal helper that returns `true` when device-managed GPU timing is currently active. */
2236
+ _isDebugGPUTimeEnabled() {
2237
+ return this._debugGPUTimeQuery !== null;
2238
+ }
1623
2239
  // DEPRECATED METHODS
1624
2240
  /** @deprecated Use getDefaultCanvasContext() */
1625
2241
  getCanvasContext() {
@@ -1655,6 +2271,12 @@ or create a device with the 'debug: true' prop.`;
1655
2271
  resetWebGL() {
1656
2272
  throw new Error("not implemented");
1657
2273
  }
2274
+ // INTERNAL LUMA.GL METHODS
2275
+ getModuleData(moduleName) {
2276
+ this._moduleData[moduleName] ||= {};
2277
+ return this._moduleData[moduleName];
2278
+ }
2279
+ // INTERNAL HELPERS
1658
2280
  // IMPLEMENTATION
1659
2281
  /** Helper to get the canvas context props */
1660
2282
  static _getCanvasContextProps(props) {
@@ -1686,6 +2308,9 @@ or create a device with the 'debug: true' prop.`;
1686
2308
  newProps.indexType = "uint32";
1687
2309
  } else if (props.data instanceof Uint16Array) {
1688
2310
  newProps.indexType = "uint16";
2311
+ } else if (props.data instanceof Uint8Array) {
2312
+ newProps.data = new Uint16Array(props.data);
2313
+ newProps.indexType = "uint16";
1689
2314
  }
1690
2315
  }
1691
2316
  if (!newProps.indexType) {
@@ -1718,7 +2343,8 @@ or create a device with the 'debug: true' prop.`;
1718
2343
  onVisibilityChange: (context) => log.log(1, `${context} Visibility changed ${context.isVisible}`)(),
1719
2344
  onDevicePixelRatioChange: (context, info) => log.log(1, `${context} DPR changed ${info.oldRatio} => ${context.devicePixelRatio}`)(),
1720
2345
  // Debug flags
1721
- debug: log.get("debug") || void 0,
2346
+ debug: getDefaultDebugValue(),
2347
+ debugGPUTime: false,
1722
2348
  debugShaders: log.get("debug-shaders") || void 0,
1723
2349
  debugFramebuffers: Boolean(log.get("debug-framebuffers")),
1724
2350
  debugFactories: Boolean(log.get("debug-factories")),
@@ -1729,9 +2355,11 @@ or create a device with the 'debug: true' prop.`;
1729
2355
  // Experimental
1730
2356
  _reuseDevices: false,
1731
2357
  _requestMaxLimits: true,
1732
- _cacheShaders: false,
1733
- _cachePipelines: false,
1734
- _cacheDestroyPolicy: "unused",
2358
+ _cacheShaders: true,
2359
+ _destroyShaders: false,
2360
+ _cachePipelines: true,
2361
+ _sharePipelines: true,
2362
+ _destroyPipelines: false,
1735
2363
  // TODO - Change these after confirming things work as expected
1736
2364
  _initializeFeatures: true,
1737
2365
  _disabledFeatures: {
@@ -1740,6 +2368,25 @@ or create a device with the 'debug: true' prop.`;
1740
2368
  // INTERNAL
1741
2369
  _handle: void 0
1742
2370
  });
2371
+ function _getDefaultDebugValue(logDebugValue, nodeEnv) {
2372
+ if (logDebugValue !== void 0 && logDebugValue !== null) {
2373
+ return Boolean(logDebugValue);
2374
+ }
2375
+ if (nodeEnv !== void 0) {
2376
+ return nodeEnv !== "production";
2377
+ }
2378
+ return false;
2379
+ }
2380
+ function getDefaultDebugValue() {
2381
+ return _getDefaultDebugValue(log.get("debug"), getNodeEnv());
2382
+ }
2383
+ function getNodeEnv() {
2384
+ const processObject = globalThis.process;
2385
+ if (!processObject?.env) {
2386
+ return void 0;
2387
+ }
2388
+ return processObject.env["NODE_ENV"];
2389
+ }
1743
2390
 
1744
2391
  // ../core/src/adapter/luma.ts
1745
2392
  var STARTUP_MESSAGE = "set luma.log.level=1 (or higher) to trace rendering";
@@ -1917,6 +2564,100 @@ or create a device with the 'debug: true' prop.`;
1917
2564
  return pageLoadPromise;
1918
2565
  }
1919
2566
 
2567
+ // ../core/src/adapter/canvas-observer.ts
2568
+ var CanvasObserver = class {
2569
+ props;
2570
+ _resizeObserver;
2571
+ _intersectionObserver;
2572
+ _observeDevicePixelRatioTimeout = null;
2573
+ _observeDevicePixelRatioMediaQuery = null;
2574
+ _handleDevicePixelRatioChange = () => this._refreshDevicePixelRatio();
2575
+ _trackPositionInterval = null;
2576
+ _started = false;
2577
+ get started() {
2578
+ return this._started;
2579
+ }
2580
+ constructor(props) {
2581
+ this.props = props;
2582
+ }
2583
+ start() {
2584
+ if (this._started || !this.props.canvas) {
2585
+ return;
2586
+ }
2587
+ this._started = true;
2588
+ this._intersectionObserver ||= new IntersectionObserver(
2589
+ (entries) => this.props.onIntersection(entries)
2590
+ );
2591
+ this._resizeObserver ||= new ResizeObserver((entries) => this.props.onResize(entries));
2592
+ this._intersectionObserver.observe(this.props.canvas);
2593
+ try {
2594
+ this._resizeObserver.observe(this.props.canvas, { box: "device-pixel-content-box" });
2595
+ } catch {
2596
+ this._resizeObserver.observe(this.props.canvas, { box: "content-box" });
2597
+ }
2598
+ this._observeDevicePixelRatioTimeout = setTimeout(() => this._refreshDevicePixelRatio(), 0);
2599
+ if (this.props.trackPosition) {
2600
+ this._trackPosition();
2601
+ }
2602
+ }
2603
+ stop() {
2604
+ if (!this._started) {
2605
+ return;
2606
+ }
2607
+ this._started = false;
2608
+ if (this._observeDevicePixelRatioTimeout) {
2609
+ clearTimeout(this._observeDevicePixelRatioTimeout);
2610
+ this._observeDevicePixelRatioTimeout = null;
2611
+ }
2612
+ if (this._observeDevicePixelRatioMediaQuery) {
2613
+ this._observeDevicePixelRatioMediaQuery.removeEventListener(
2614
+ "change",
2615
+ this._handleDevicePixelRatioChange
2616
+ );
2617
+ this._observeDevicePixelRatioMediaQuery = null;
2618
+ }
2619
+ if (this._trackPositionInterval) {
2620
+ clearInterval(this._trackPositionInterval);
2621
+ this._trackPositionInterval = null;
2622
+ }
2623
+ this._resizeObserver?.disconnect();
2624
+ this._intersectionObserver?.disconnect();
2625
+ }
2626
+ _refreshDevicePixelRatio() {
2627
+ if (!this._started) {
2628
+ return;
2629
+ }
2630
+ this.props.onDevicePixelRatioChange();
2631
+ this._observeDevicePixelRatioMediaQuery?.removeEventListener(
2632
+ "change",
2633
+ this._handleDevicePixelRatioChange
2634
+ );
2635
+ this._observeDevicePixelRatioMediaQuery = matchMedia(
2636
+ `(resolution: ${window.devicePixelRatio}dppx)`
2637
+ );
2638
+ this._observeDevicePixelRatioMediaQuery.addEventListener(
2639
+ "change",
2640
+ this._handleDevicePixelRatioChange,
2641
+ { once: true }
2642
+ );
2643
+ }
2644
+ _trackPosition(intervalMs = 100) {
2645
+ if (this._trackPositionInterval) {
2646
+ return;
2647
+ }
2648
+ this._trackPositionInterval = setInterval(() => {
2649
+ if (!this._started) {
2650
+ if (this._trackPositionInterval) {
2651
+ clearInterval(this._trackPositionInterval);
2652
+ this._trackPositionInterval = null;
2653
+ }
2654
+ } else {
2655
+ this.props.onPositionChange();
2656
+ }
2657
+ }, intervalMs);
2658
+ }
2659
+ };
2660
+
1920
2661
  // ../core/src/utils/promise-utils.ts
1921
2662
  function withResolvers() {
1922
2663
  let resolve;
@@ -1928,8 +2669,21 @@ or create a device with the 'debug: true' prop.`;
1928
2669
  return { promise, resolve, reject };
1929
2670
  }
1930
2671
 
1931
- // ../core/src/adapter/canvas-context.ts
1932
- var _CanvasContext = class {
2672
+ // ../core/src/utils/assert.ts
2673
+ function assert2(condition, message) {
2674
+ if (!condition) {
2675
+ const error = new Error(message ?? "luma.gl assertion failed.");
2676
+ Error.captureStackTrace?.(error, assert2);
2677
+ throw error;
2678
+ }
2679
+ }
2680
+ function assertDefined(value, message) {
2681
+ assert2(value, message);
2682
+ return value;
2683
+ }
2684
+
2685
+ // ../core/src/adapter/canvas-surface.ts
2686
+ var _CanvasSurface = class {
1933
2687
  static isHTMLCanvas(canvas) {
1934
2688
  return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
1935
2689
  }
@@ -1963,16 +2717,20 @@ or create a device with the 'debug: true' prop.`;
1963
2717
  drawingBufferWidth;
1964
2718
  /** Height of drawing buffer: automatically tracks this.pixelHeight if props.autoResize is true */
1965
2719
  drawingBufferHeight;
2720
+ /** Resolves when the canvas is initialized, i.e. when the ResizeObserver has updated the pixel size */
1966
2721
  _initializedResolvers = withResolvers();
1967
- _resizeObserver;
1968
- _intersectionObserver;
1969
- _position;
2722
+ _canvasObserver;
2723
+ /** Position of the canvas in the document, updated by a timer */
2724
+ _position = [0, 0];
2725
+ /** Whether this canvas context has been destroyed */
1970
2726
  destroyed = false;
2727
+ /** Whether the drawing buffer size needs to be resized (deferred resizing to avoid flicker) */
2728
+ _needsDrawingBufferResize = true;
1971
2729
  toString() {
1972
2730
  return `${this[Symbol.toStringTag]}(${this.id})`;
1973
2731
  }
1974
2732
  constructor(props) {
1975
- this.props = { ..._CanvasContext.defaultProps, ...props };
2733
+ this.props = { ..._CanvasSurface.defaultProps, ...props };
1976
2734
  props = this.props;
1977
2735
  this.initialized = this._initializedResolvers.promise;
1978
2736
  if (!isBrowser()) {
@@ -1984,11 +2742,11 @@ or create a device with the 'debug: true' prop.`;
1984
2742
  } else {
1985
2743
  this.canvas = props.canvas;
1986
2744
  }
1987
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2745
+ if (_CanvasSurface.isHTMLCanvas(this.canvas)) {
1988
2746
  this.id = props.id || this.canvas.id;
1989
2747
  this.type = "html-canvas";
1990
2748
  this.htmlCanvas = this.canvas;
1991
- } else if (_CanvasContext.isOffscreenCanvas(this.canvas)) {
2749
+ } else if (_CanvasSurface.isOffscreenCanvas(this.canvas)) {
1992
2750
  this.id = props.id || "offscreen-canvas";
1993
2751
  this.type = "offscreen-canvas";
1994
2752
  this.offscreenCanvas = this.canvas;
@@ -2004,25 +2762,21 @@ or create a device with the 'debug: true' prop.`;
2004
2762
  this.drawingBufferHeight = this.canvas.height;
2005
2763
  this.devicePixelRatio = globalThis.devicePixelRatio || 1;
2006
2764
  this._position = [0, 0];
2007
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2008
- this._intersectionObserver = new IntersectionObserver(
2009
- (entries) => this._handleIntersection(entries)
2010
- );
2011
- this._intersectionObserver.observe(this.canvas);
2012
- this._resizeObserver = new ResizeObserver((entries) => this._handleResize(entries));
2013
- try {
2014
- this._resizeObserver.observe(this.canvas, { box: "device-pixel-content-box" });
2015
- } catch {
2016
- this._resizeObserver.observe(this.canvas, { box: "content-box" });
2017
- }
2018
- setTimeout(() => this._observeDevicePixelRatio(), 0);
2019
- if (this.props.trackPosition) {
2020
- this._trackPosition();
2021
- }
2022
- }
2765
+ this._canvasObserver = new CanvasObserver({
2766
+ canvas: this.htmlCanvas,
2767
+ trackPosition: this.props.trackPosition,
2768
+ onResize: (entries) => this._handleResize(entries),
2769
+ onIntersection: (entries) => this._handleIntersection(entries),
2770
+ onDevicePixelRatioChange: () => this._observeDevicePixelRatio(),
2771
+ onPositionChange: () => this.updatePosition()
2772
+ });
2023
2773
  }
2024
2774
  destroy() {
2025
- this.destroyed = true;
2775
+ if (!this.destroyed) {
2776
+ this.destroyed = true;
2777
+ this._stopObservers();
2778
+ this.device = null;
2779
+ }
2026
2780
  }
2027
2781
  setProps(props) {
2028
2782
  if ("useDevicePixels" in props) {
@@ -2031,55 +2785,41 @@ or create a device with the 'debug: true' prop.`;
2031
2785
  }
2032
2786
  return this;
2033
2787
  }
2034
- // SIZE METHODS
2035
- /**
2036
- * Returns the size covered by the canvas in CSS pixels
2037
- * @note This can be different from the actual device pixel size of a canvas due to DPR scaling, and rounding to integer pixels
2038
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2039
- */
2788
+ /** Returns a framebuffer with properly resized current 'swap chain' textures */
2789
+ getCurrentFramebuffer(options) {
2790
+ this._resizeDrawingBufferIfNeeded();
2791
+ return this._getCurrentFramebuffer(options);
2792
+ }
2040
2793
  getCSSSize() {
2041
2794
  return [this.cssWidth, this.cssHeight];
2042
2795
  }
2043
2796
  getPosition() {
2044
2797
  return this._position;
2045
2798
  }
2046
- /**
2047
- * Returns the size covered by the canvas in actual device pixels.
2048
- * @note This can be different from the 'CSS' size of a canvas due to DPR scaling, and rounding to integer pixels
2049
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2050
- */
2051
2799
  getDevicePixelSize() {
2052
2800
  return [this.devicePixelWidth, this.devicePixelHeight];
2053
2801
  }
2054
- /** Get the drawing buffer size (number of pixels GPU is rendering into, can be different from CSS size) */
2055
2802
  getDrawingBufferSize() {
2056
2803
  return [this.drawingBufferWidth, this.drawingBufferHeight];
2057
2804
  }
2058
- /** Returns the biggest allowed framebuffer size. @todo Allow the application to limit this? */
2059
2805
  getMaxDrawingBufferSize() {
2060
2806
  const maxTextureDimension = this.device.limits.maxTextureDimension2D;
2061
2807
  return [maxTextureDimension, maxTextureDimension];
2062
2808
  }
2063
- /** Update the canvas drawing buffer size. Called automatically if props.autoResize is true. */
2064
2809
  setDrawingBufferSize(width, height) {
2065
- this.canvas.width = width;
2066
- this.canvas.height = height;
2810
+ width = Math.floor(width);
2811
+ height = Math.floor(height);
2812
+ if (this.drawingBufferWidth === width && this.drawingBufferHeight === height) {
2813
+ return;
2814
+ }
2067
2815
  this.drawingBufferWidth = width;
2068
2816
  this.drawingBufferHeight = height;
2817
+ this._needsDrawingBufferResize = true;
2069
2818
  }
2070
- /**
2071
- * Returns the current DPR (number of physical pixels per CSS pixel), if props.useDevicePixels is true
2072
- * @note This can be a fractional (non-integer) number, e.g. when the user zooms in the browser.
2073
- * @note This function handles the non-HTML canvas cases
2074
- */
2075
2819
  getDevicePixelRatio() {
2076
- const dpr = typeof window !== "undefined" && window.devicePixelRatio;
2077
- return dpr || 1;
2820
+ const devicePixelRatio2 = typeof window !== "undefined" && window.devicePixelRatio;
2821
+ return devicePixelRatio2 || 1;
2078
2822
  }
2079
- // DEPRECATED METHODS
2080
- /**
2081
- * Maps CSS pixel position to device pixel position
2082
- */
2083
2823
  cssToDevicePixels(cssPixel, yInvert = true) {
2084
2824
  const ratio = this.cssToDeviceRatio();
2085
2825
  const [width, height] = this.getDrawingBufferSize();
@@ -2089,10 +2829,10 @@ or create a device with the 'debug: true' prop.`;
2089
2829
  getPixelSize() {
2090
2830
  return this.getDevicePixelSize();
2091
2831
  }
2092
- /** @deprecated - TODO which values should we use for aspect */
2832
+ /** @deprecated Use the current drawing buffer size for projection setup. */
2093
2833
  getAspect() {
2094
- const [width, height] = this.getDevicePixelSize();
2095
- return width / height;
2834
+ const [width, height] = this.getDrawingBufferSize();
2835
+ return width > 0 && height > 0 ? width / height : 1;
2096
2836
  }
2097
2837
  /** @deprecated Returns multiplier need to convert CSS size to Device size */
2098
2838
  cssToDeviceRatio() {
@@ -2108,18 +2848,40 @@ or create a device with the 'debug: true' prop.`;
2108
2848
  resize(size) {
2109
2849
  this.setDrawingBufferSize(size.width, size.height);
2110
2850
  }
2111
- // IMPLEMENTATION
2112
- /**
2113
- * Allows subclass constructor to override the canvas id for auto created canvases.
2114
- * This can really help when debugging DOM in apps that create multiple devices
2115
- */
2116
2851
  _setAutoCreatedCanvasId(id) {
2117
2852
  if (this.htmlCanvas?.id === "lumagl-auto-created-canvas") {
2118
2853
  this.htmlCanvas.id = id;
2119
2854
  }
2120
2855
  }
2121
- /** reacts to an observed intersection */
2856
+ /**
2857
+ * Starts DOM observation after the derived context and its device are fully initialized.
2858
+ *
2859
+ * `CanvasSurface` construction runs before subclasses can assign `this.device`, and the
2860
+ * default WebGL canvas context is created before `WebGLDevice` has initialized `limits`,
2861
+ * `features`, and the rest of its runtime state. Deferring observer startup avoids early
2862
+ * `ResizeObserver` and DPR callbacks running against a partially initialized device.
2863
+ */
2864
+ _startObservers() {
2865
+ if (this.destroyed) {
2866
+ return;
2867
+ }
2868
+ this._canvasObserver.start();
2869
+ }
2870
+ /**
2871
+ * Stops all DOM observation and timers associated with a canvas surface.
2872
+ *
2873
+ * This pairs with `_startObservers()` so teardown uses the same lifecycle whether a context is
2874
+ * explicitly destroyed, abandoned during device reuse, or temporarily has not started observing
2875
+ * yet. Centralizing shutdown here keeps resize/DPR/position watchers from surviving past the
2876
+ * lifetime of the owning device.
2877
+ */
2878
+ _stopObservers() {
2879
+ this._canvasObserver.stop();
2880
+ }
2122
2881
  _handleIntersection(entries) {
2882
+ if (this.destroyed) {
2883
+ return;
2884
+ }
2123
2885
  const entry = entries.find((entry_) => entry_.target === this.canvas);
2124
2886
  if (!entry) {
2125
2887
  return;
@@ -2130,21 +2892,20 @@ or create a device with the 'debug: true' prop.`;
2130
2892
  this.device.props.onVisibilityChange(this);
2131
2893
  }
2132
2894
  }
2133
- /**
2134
- * Reacts to an observed resize by using the most accurate pixel size information the browser can provide
2135
- * @see https://web.dev/articles/device-pixel-content-box
2136
- * @see https://webgpufundamentals.org/webgpu/lessons/webgpu-resizing-the-canvas.html
2137
- */
2138
2895
  _handleResize(entries) {
2896
+ if (this.destroyed) {
2897
+ return;
2898
+ }
2139
2899
  const entry = entries.find((entry_) => entry_.target === this.canvas);
2140
2900
  if (!entry) {
2141
2901
  return;
2142
2902
  }
2143
- this.cssWidth = entry.contentBoxSize[0].inlineSize;
2144
- this.cssHeight = entry.contentBoxSize[0].blockSize;
2903
+ const contentBoxSize = assertDefined(entry.contentBoxSize?.[0]);
2904
+ this.cssWidth = contentBoxSize.inlineSize;
2905
+ this.cssHeight = contentBoxSize.blockSize;
2145
2906
  const oldPixelSize = this.getDevicePixelSize();
2146
- const devicePixelWidth = entry.devicePixelContentBoxSize?.[0].inlineSize || entry.contentBoxSize[0].inlineSize * devicePixelRatio;
2147
- const devicePixelHeight = entry.devicePixelContentBoxSize?.[0].blockSize || entry.contentBoxSize[0].blockSize * devicePixelRatio;
2907
+ const devicePixelWidth = entry.devicePixelContentBoxSize?.[0]?.inlineSize || contentBoxSize.inlineSize * devicePixelRatio;
2908
+ const devicePixelHeight = entry.devicePixelContentBoxSize?.[0]?.blockSize || contentBoxSize.blockSize * devicePixelRatio;
2148
2909
  const [maxDevicePixelWidth, maxDevicePixelHeight] = this.getMaxDrawingBufferSize();
2149
2910
  this.devicePixelWidth = Math.max(1, Math.min(devicePixelWidth, maxDevicePixelWidth));
2150
2911
  this.devicePixelHeight = Math.max(1, Math.min(devicePixelHeight, maxDevicePixelHeight));
@@ -2154,47 +2915,47 @@ or create a device with the 'debug: true' prop.`;
2154
2915
  _updateDrawingBufferSize() {
2155
2916
  if (this.props.autoResize) {
2156
2917
  if (typeof this.props.useDevicePixels === "number") {
2157
- const dpr = this.props.useDevicePixels;
2158
- this.setDrawingBufferSize(this.cssWidth * dpr, this.cssHeight * dpr);
2918
+ const devicePixelRatio2 = this.props.useDevicePixels;
2919
+ this.setDrawingBufferSize(
2920
+ this.cssWidth * devicePixelRatio2,
2921
+ this.cssHeight * devicePixelRatio2
2922
+ );
2159
2923
  } else if (this.props.useDevicePixels) {
2160
2924
  this.setDrawingBufferSize(this.devicePixelWidth, this.devicePixelHeight);
2161
2925
  } else {
2162
2926
  this.setDrawingBufferSize(this.cssWidth, this.cssHeight);
2163
2927
  }
2164
- this._updateDevice();
2165
2928
  }
2166
2929
  this._initializedResolvers.resolve();
2167
2930
  this.isInitialized = true;
2168
2931
  this.updatePosition();
2169
2932
  }
2170
- /** Monitor DPR changes */
2933
+ _resizeDrawingBufferIfNeeded() {
2934
+ if (this._needsDrawingBufferResize) {
2935
+ this._needsDrawingBufferResize = false;
2936
+ const sizeChanged = this.drawingBufferWidth !== this.canvas.width || this.drawingBufferHeight !== this.canvas.height;
2937
+ if (sizeChanged) {
2938
+ this.canvas.width = this.drawingBufferWidth;
2939
+ this.canvas.height = this.drawingBufferHeight;
2940
+ this._configureDevice();
2941
+ }
2942
+ }
2943
+ }
2171
2944
  _observeDevicePixelRatio() {
2945
+ if (this.destroyed || !this._canvasObserver.started) {
2946
+ return;
2947
+ }
2172
2948
  const oldRatio = this.devicePixelRatio;
2173
2949
  this.devicePixelRatio = window.devicePixelRatio;
2174
2950
  this.updatePosition();
2175
- this.device.props.onDevicePixelRatioChange(this, { oldRatio });
2176
- matchMedia(`(resolution: ${this.devicePixelRatio}dppx)`).addEventListener(
2177
- "change",
2178
- () => this._observeDevicePixelRatio(),
2179
- { once: true }
2180
- );
2181
- }
2182
- /** Start tracking positions with a timer */
2183
- _trackPosition(intervalMs = 100) {
2184
- const intervalId = setInterval(() => {
2185
- if (this.destroyed) {
2186
- clearInterval(intervalId);
2187
- } else {
2188
- this.updatePosition();
2189
- }
2190
- }, intervalMs);
2951
+ this.device.props.onDevicePixelRatioChange?.(this, {
2952
+ oldRatio
2953
+ });
2191
2954
  }
2192
- /**
2193
- * Calculated the absolute position of the canvas
2194
- * @note - getBoundingClientRect() is normally cheap but can be expensive
2195
- * if called before browser has finished a reflow. Should not be the case here.
2196
- */
2197
2955
  updatePosition() {
2956
+ if (this.destroyed) {
2957
+ return;
2958
+ }
2198
2959
  const newRect = this.htmlCanvas?.getBoundingClientRect();
2199
2960
  if (newRect) {
2200
2961
  const position = [newRect.left, newRect.top];
@@ -2203,13 +2964,15 @@ or create a device with the 'debug: true' prop.`;
2203
2964
  if (positionChanged) {
2204
2965
  const oldPosition = this._position;
2205
2966
  this._position = position;
2206
- this.device.props.onPositionChange?.(this, { oldPosition });
2967
+ this.device.props.onPositionChange?.(this, {
2968
+ oldPosition
2969
+ });
2207
2970
  }
2208
2971
  }
2209
2972
  }
2210
2973
  };
2211
- var CanvasContext = _CanvasContext;
2212
- __publicField(CanvasContext, "defaultProps", {
2974
+ var CanvasSurface = _CanvasSurface;
2975
+ __publicField(CanvasSurface, "defaultProps", {
2213
2976
  id: void 0,
2214
2977
  canvas: null,
2215
2978
  width: 800,
@@ -2237,7 +3000,7 @@ or create a device with the 'debug: true' prop.`;
2237
3000
  }
2238
3001
  function getCanvasFromDOM(canvasId) {
2239
3002
  const canvas = document.getElementById(canvasId);
2240
- if (!CanvasContext.isHTMLCanvas(canvas)) {
3003
+ if (!CanvasSurface.isHTMLCanvas(canvas)) {
2241
3004
  throw new Error("Object is not a canvas element");
2242
3005
  }
2243
3006
  return canvas;
@@ -2261,33 +3024,40 @@ or create a device with the 'debug: true' prop.`;
2261
3024
  const point = pixel;
2262
3025
  const x = scaleX(point[0], ratio, width);
2263
3026
  let y = scaleY(point[1], ratio, height, yInvert);
2264
- let t = scaleX(point[0] + 1, ratio, width);
2265
- const xHigh = t === width - 1 ? t : t - 1;
2266
- t = scaleY(point[1] + 1, ratio, height, yInvert);
3027
+ let temporary = scaleX(point[0] + 1, ratio, width);
3028
+ const xHigh = temporary === width - 1 ? temporary : temporary - 1;
3029
+ temporary = scaleY(point[1] + 1, ratio, height, yInvert);
2267
3030
  let yHigh;
2268
3031
  if (yInvert) {
2269
- t = t === 0 ? t : t + 1;
3032
+ temporary = temporary === 0 ? temporary : temporary + 1;
2270
3033
  yHigh = y;
2271
- y = t;
3034
+ y = temporary;
2272
3035
  } else {
2273
- yHigh = t === height - 1 ? t : t - 1;
3036
+ yHigh = temporary === height - 1 ? temporary : temporary - 1;
2274
3037
  }
2275
3038
  return {
2276
3039
  x,
2277
3040
  y,
2278
- // when ratio < 1, current css pixel and next css pixel may point to same device pixel, set width/height to 1 in those cases.
2279
3041
  width: Math.max(xHigh - x + 1, 1),
2280
3042
  height: Math.max(yHigh - y + 1, 1)
2281
3043
  };
2282
3044
  }
2283
3045
  function scaleX(x, ratio, width) {
2284
- const r = Math.min(Math.round(x * ratio), width - 1);
2285
- return r;
3046
+ return Math.min(Math.round(x * ratio), width - 1);
2286
3047
  }
2287
3048
  function scaleY(y, ratio, height, yInvert) {
2288
3049
  return yInvert ? Math.max(0, height - 1 - Math.round(y * ratio)) : Math.min(Math.round(y * ratio), height - 1);
2289
3050
  }
2290
3051
 
3052
+ // ../core/src/adapter/canvas-context.ts
3053
+ var CanvasContext = class extends CanvasSurface {
3054
+ };
3055
+ __publicField(CanvasContext, "defaultProps", CanvasSurface.defaultProps);
3056
+
3057
+ // ../core/src/adapter/presentation-context.ts
3058
+ var PresentationContext = class extends CanvasSurface {
3059
+ };
3060
+
2291
3061
  // ../core/src/adapter/resources/sampler.ts
2292
3062
  var _Sampler = class extends Resource {
2293
3063
  get [Symbol.toStringTag]() {
@@ -2342,7 +3112,15 @@ or create a device with the 'debug: true' prop.`;
2342
3112
  depth;
2343
3113
  /** mip levels in this texture */
2344
3114
  mipLevels;
2345
- /** "Time" of last update. Monotonically increasing timestamp. TODO move to AsyncTexture? */
3115
+ /** sample count */
3116
+ samples;
3117
+ /** Rows are multiples of this length, padded with extra bytes if needed */
3118
+ byteAlignment;
3119
+ /** The ready promise is always resolved. It is provided for type compatibility with DynamicTexture. */
3120
+ ready = Promise.resolve(this);
3121
+ /** isReady is always true. It is provided for type compatibility with DynamicTexture. */
3122
+ isReady = true;
3123
+ /** "Time" of last update. Monotonically increasing timestamp. TODO move to DynamicTexture? */
2346
3124
  updateTimestamp;
2347
3125
  get [Symbol.toStringTag]() {
2348
3126
  return "Texture";
@@ -2351,7 +3129,7 @@ or create a device with the 'debug: true' prop.`;
2351
3129
  return `Texture(${this.id},${this.format},${this.width}x${this.height})`;
2352
3130
  }
2353
3131
  /** Do not use directly. Create with device.createTexture() */
2354
- constructor(device, props) {
3132
+ constructor(device, props, backendProps) {
2355
3133
  props = _Texture.normalizeProps(device, props);
2356
3134
  super(device, props, _Texture.defaultProps);
2357
3135
  this.dimension = this.props.dimension;
@@ -2361,6 +3139,10 @@ or create a device with the 'debug: true' prop.`;
2361
3139
  this.height = this.props.height;
2362
3140
  this.depth = this.props.depth;
2363
3141
  this.mipLevels = this.props.mipLevels;
3142
+ this.samples = this.props.samples || 1;
3143
+ if (this.dimension === "cube") {
3144
+ this.depth = 6;
3145
+ }
2364
3146
  if (this.props.width === void 0 || this.props.height === void 0) {
2365
3147
  if (device.isExternalImage(props.data)) {
2366
3148
  const size = device.getExternalImageSize(props.data);
@@ -2371,17 +3153,14 @@ or create a device with the 'debug: true' prop.`;
2371
3153
  this.height = 1;
2372
3154
  if (this.props.width === void 0 || this.props.height === void 0) {
2373
3155
  log.warn(
2374
- `${this} created with undefined width or height. This is deprecated. Use AsyncTexture instead.`
3156
+ `${this} created with undefined width or height. This is deprecated. Use DynamicTexture instead.`
2375
3157
  )();
2376
3158
  }
2377
3159
  }
2378
3160
  }
3161
+ this.byteAlignment = backendProps?.byteAlignment || 1;
2379
3162
  this.updateTimestamp = device.incrementTimestamp();
2380
3163
  }
2381
- /** Set sampler props associated with this texture */
2382
- setSampler(sampler) {
2383
- this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
2384
- }
2385
3164
  /**
2386
3165
  * Create a new texture with the same parameters and optionally a different size
2387
3166
  * @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.
@@ -2390,6 +3169,105 @@ or create a device with the 'debug: true' prop.`;
2390
3169
  clone(size) {
2391
3170
  return this.device.createTexture({ ...this.props, ...size });
2392
3171
  }
3172
+ /** Set sampler props associated with this texture */
3173
+ setSampler(sampler) {
3174
+ this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
3175
+ }
3176
+ /**
3177
+ * Copy raw image data (bytes) into the texture.
3178
+ *
3179
+ * @note Deprecated compatibility wrapper over {@link writeData}.
3180
+ * @note Uses the same layout defaults and alignment rules as {@link writeData}.
3181
+ * @note Tightly packed CPU uploads can omit `bytesPerRow` and `rowsPerImage`.
3182
+ * @note If the CPU source rows are padded, pass explicit `bytesPerRow` and `rowsPerImage`.
3183
+ * @deprecated Use writeData()
3184
+ */
3185
+ copyImageData(options) {
3186
+ const { data, depth, ...writeOptions } = options;
3187
+ this.writeData(data, {
3188
+ ...writeOptions,
3189
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3190
+ });
3191
+ }
3192
+ /**
3193
+ * Calculates the memory layout of the texture, required when reading and writing data.
3194
+ * @return the backend-aligned linear layout, in particular bytesPerRow which includes any required padding for buffer copy/read paths
3195
+ */
3196
+ computeMemoryLayout(options_ = {}) {
3197
+ const options = this._normalizeTextureReadOptions(options_);
3198
+ const { width = this.width, height = this.height, depthOrArrayLayers = this.depth } = options;
3199
+ const { format, byteAlignment } = this;
3200
+ return textureFormatDecoder.computeMemoryLayout({
3201
+ format,
3202
+ width,
3203
+ height,
3204
+ depth: depthOrArrayLayers,
3205
+ byteAlignment
3206
+ });
3207
+ }
3208
+ /**
3209
+ * Read the contents of a texture into a GPU Buffer.
3210
+ * @returns A Buffer containing the texture data.
3211
+ *
3212
+ * @note The memory layout of the texture data is determined by the texture format and dimensions.
3213
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3214
+ * @note The application can call Buffer.readAsync() to read the returned buffer on the CPU.
3215
+ * @note The destination buffer must be supplied by the caller and must be large enough for the requested region.
3216
+ * @note On WebGPU this corresponds to a texture-to-buffer copy and uses buffer-copy alignment rules.
3217
+ * @note On WebGL, luma.gl emulates the same logical readback behavior.
3218
+ */
3219
+ readBuffer(options, buffer) {
3220
+ throw new Error("readBuffer not implemented");
3221
+ }
3222
+ /**
3223
+ * Reads data from a texture into an ArrayBuffer.
3224
+ * @returns An ArrayBuffer containing the texture data.
3225
+ *
3226
+ * @note The memory layout of the texture data is determined by the texture format and dimensions.
3227
+ * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3228
+ * @deprecated Use Texture.readBuffer() with an explicit destination buffer, or DynamicTexture.readAsync() for convenience readback.
3229
+ */
3230
+ readDataAsync(options) {
3231
+ throw new Error("readBuffer not implemented");
3232
+ }
3233
+ /**
3234
+ * Writes a GPU Buffer into a texture.
3235
+ *
3236
+ * @param buffer - Source GPU buffer.
3237
+ * @param options - Destination subresource, extent, and source layout options.
3238
+ * @note The memory layout of the texture data is determined by the texture format and dimensions.
3239
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3240
+ * @note On WebGPU this corresponds to a buffer-to-texture copy and uses buffer-copy alignment rules.
3241
+ * @note On WebGL, luma.gl emulates the same destination and layout semantics.
3242
+ */
3243
+ writeBuffer(buffer, options) {
3244
+ throw new Error("readBuffer not implemented");
3245
+ }
3246
+ /**
3247
+ * Writes an array buffer into a texture.
3248
+ *
3249
+ * @param data - Source texel data.
3250
+ * @param options - Destination subresource, extent, and source layout options.
3251
+ * @note If `bytesPerRow` and `rowsPerImage` are omitted, luma.gl computes a tightly packed CPU-memory layout for the requested region.
3252
+ * @note On WebGPU this corresponds to `GPUQueue.writeTexture()` and does not implicitly pad rows to 256 bytes.
3253
+ * @note On WebGL, padded CPU data is supported via the same `bytesPerRow` and `rowsPerImage` options.
3254
+ */
3255
+ writeData(data, options) {
3256
+ throw new Error("readBuffer not implemented");
3257
+ }
3258
+ // IMPLEMENTATION SPECIFIC
3259
+ /**
3260
+ * WebGL can read data synchronously.
3261
+ * @note While it is convenient, the performance penalty is very significant
3262
+ */
3263
+ readDataSyncWebGL(options) {
3264
+ throw new Error("readDataSyncWebGL not available");
3265
+ }
3266
+ /** Generate mipmaps (WebGL only) */
3267
+ generateMipmapsWebGL() {
3268
+ throw new Error("generateMipmapsWebGL not available");
3269
+ }
3270
+ // HELPERS
2393
3271
  /** Ensure we have integer coordinates */
2394
3272
  static normalizeProps(device, props) {
2395
3273
  const newProps = { ...props };
@@ -2402,7 +3280,6 @@ or create a device with the 'debug: true' prop.`;
2402
3280
  }
2403
3281
  return newProps;
2404
3282
  }
2405
- // HELPERS
2406
3283
  /** Initialize texture with supplied props */
2407
3284
  // eslint-disable-next-line max-statements
2408
3285
  _initializeData(data) {
@@ -2436,23 +3313,166 @@ or create a device with the 'debug: true' prop.`;
2436
3313
  }
2437
3314
  }
2438
3315
  _normalizeCopyImageDataOptions(options_) {
2439
- const { width, height, depth } = this;
2440
- const options = { ..._Texture.defaultCopyDataOptions, width, height, depth, ...options_ };
2441
- const info = this.device.getTextureFormatInfo(this.format);
2442
- if (!options_.bytesPerRow && !info.bytesPerPixel) {
2443
- throw new Error(`bytesPerRow must be provided for texture format ${this.format}`);
2444
- }
2445
- options.bytesPerRow = options_.bytesPerRow || width * (info.bytesPerPixel || 4);
2446
- options.rowsPerImage = options_.rowsPerImage || height;
2447
- return options;
3316
+ const { data, depth, ...writeOptions } = options_;
3317
+ const options = this._normalizeTextureWriteOptions({
3318
+ ...writeOptions,
3319
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3320
+ });
3321
+ return { data, depth: options.depthOrArrayLayers, ...options };
2448
3322
  }
2449
3323
  _normalizeCopyExternalImageOptions(options_) {
3324
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3325
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3326
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
2450
3327
  const size = this.device.getExternalImageSize(options_.image);
2451
- const options = { ..._Texture.defaultCopyExternalImageOptions, ...size, ...options_ };
2452
- options.width = Math.min(options.width, this.width - options.x);
2453
- options.height = Math.min(options.height, this.height - options.y);
3328
+ const options = {
3329
+ ..._Texture.defaultCopyExternalImageOptions,
3330
+ ...mipLevelSize,
3331
+ ...size,
3332
+ ...optionsWithoutUndefined
3333
+ };
3334
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3335
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3336
+ options.depth = Math.min(options.depth, mipLevelSize.depthOrArrayLayers - options.z);
3337
+ return options;
3338
+ }
3339
+ _normalizeTextureReadOptions(options_) {
3340
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3341
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3342
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3343
+ const options = {
3344
+ ..._Texture.defaultTextureReadOptions,
3345
+ ...mipLevelSize,
3346
+ ...optionsWithoutUndefined
3347
+ };
3348
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3349
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3350
+ options.depthOrArrayLayers = Math.min(
3351
+ options.depthOrArrayLayers,
3352
+ mipLevelSize.depthOrArrayLayers - options.z
3353
+ );
2454
3354
  return options;
2455
3355
  }
3356
+ /**
3357
+ * Normalizes a texture read request and validates the color-only readback contract used by the
3358
+ * current texture read APIs. Supported dimensions are `2d`, `cube`, `cube-array`,
3359
+ * `2d-array`, and `3d`.
3360
+ *
3361
+ * @throws if the texture format, aspect, or dimension is not supported by the first-pass
3362
+ * color-read implementation.
3363
+ */
3364
+ _getSupportedColorReadOptions(options_) {
3365
+ const options = this._normalizeTextureReadOptions(options_);
3366
+ const formatInfo = textureFormatDecoder.getInfo(this.format);
3367
+ this._validateColorReadAspect(options);
3368
+ this._validateColorReadFormat(formatInfo);
3369
+ switch (this.dimension) {
3370
+ case "2d":
3371
+ case "cube":
3372
+ case "cube-array":
3373
+ case "2d-array":
3374
+ case "3d":
3375
+ return options;
3376
+ default:
3377
+ throw new Error(`${this} color readback does not support ${this.dimension} textures`);
3378
+ }
3379
+ }
3380
+ /** Validates that a read request targets the full color aspect of the texture. */
3381
+ _validateColorReadAspect(options) {
3382
+ if (options.aspect !== "all") {
3383
+ throw new Error(`${this} color readback only supports aspect 'all'`);
3384
+ }
3385
+ }
3386
+ /** Validates that a read request targets an uncompressed color-renderable texture format. */
3387
+ _validateColorReadFormat(formatInfo) {
3388
+ if (formatInfo.compressed) {
3389
+ throw new Error(
3390
+ `${this} color readback does not support compressed formats (${this.format})`
3391
+ );
3392
+ }
3393
+ switch (formatInfo.attachment) {
3394
+ case "color":
3395
+ return;
3396
+ case "depth":
3397
+ throw new Error(`${this} color readback does not support depth formats (${this.format})`);
3398
+ case "stencil":
3399
+ throw new Error(`${this} color readback does not support stencil formats (${this.format})`);
3400
+ case "depth-stencil":
3401
+ throw new Error(
3402
+ `${this} color readback does not support depth-stencil formats (${this.format})`
3403
+ );
3404
+ default:
3405
+ throw new Error(`${this} color readback does not support format ${this.format}`);
3406
+ }
3407
+ }
3408
+ _normalizeTextureWriteOptions(options_) {
3409
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3410
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3411
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3412
+ const options = {
3413
+ ..._Texture.defaultTextureWriteOptions,
3414
+ ...mipLevelSize,
3415
+ ...optionsWithoutUndefined
3416
+ };
3417
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3418
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3419
+ options.depthOrArrayLayers = Math.min(
3420
+ options.depthOrArrayLayers,
3421
+ mipLevelSize.depthOrArrayLayers - options.z
3422
+ );
3423
+ const layout = textureFormatDecoder.computeMemoryLayout({
3424
+ format: this.format,
3425
+ width: options.width,
3426
+ height: options.height,
3427
+ depth: options.depthOrArrayLayers,
3428
+ byteAlignment: this.byteAlignment
3429
+ });
3430
+ const minimumBytesPerRow = layout.bytesPerPixel * options.width;
3431
+ options.bytesPerRow = optionsWithoutUndefined.bytesPerRow ?? layout.bytesPerRow;
3432
+ options.rowsPerImage = optionsWithoutUndefined.rowsPerImage ?? options.height;
3433
+ if (options.bytesPerRow < minimumBytesPerRow) {
3434
+ throw new Error(
3435
+ `bytesPerRow (${options.bytesPerRow}) must be at least ${minimumBytesPerRow} for ${this.format}`
3436
+ );
3437
+ }
3438
+ if (options.rowsPerImage < options.height) {
3439
+ throw new Error(
3440
+ `rowsPerImage (${options.rowsPerImage}) must be at least ${options.height} for ${this.format}`
3441
+ );
3442
+ }
3443
+ const bytesPerPixel = this.device.getTextureFormatInfo(this.format).bytesPerPixel;
3444
+ if (bytesPerPixel && options.bytesPerRow % bytesPerPixel !== 0) {
3445
+ throw new Error(
3446
+ `bytesPerRow (${options.bytesPerRow}) must be a multiple of bytesPerPixel (${bytesPerPixel}) for ${this.format}`
3447
+ );
3448
+ }
3449
+ return options;
3450
+ }
3451
+ _getMipLevelSize(mipLevel) {
3452
+ const width = Math.max(1, this.width >> mipLevel);
3453
+ const height = this.baseDimension === "1d" ? 1 : Math.max(1, this.height >> mipLevel);
3454
+ const depthOrArrayLayers = this.dimension === "3d" ? Math.max(1, this.depth >> mipLevel) : this.depth;
3455
+ return { width, height, depthOrArrayLayers };
3456
+ }
3457
+ getAllocatedByteLength() {
3458
+ let allocatedByteLength = 0;
3459
+ for (let mipLevel = 0; mipLevel < this.mipLevels; mipLevel++) {
3460
+ const { width, height, depthOrArrayLayers } = this._getMipLevelSize(mipLevel);
3461
+ allocatedByteLength += textureFormatDecoder.computeMemoryLayout({
3462
+ format: this.format,
3463
+ width,
3464
+ height,
3465
+ depth: depthOrArrayLayers,
3466
+ byteAlignment: 1
3467
+ }).byteLength;
3468
+ }
3469
+ return allocatedByteLength * this.samples;
3470
+ }
3471
+ static _omitUndefined(options) {
3472
+ return Object.fromEntries(
3473
+ Object.entries(options).filter(([, value]) => value !== void 0)
3474
+ );
3475
+ }
2456
3476
  };
2457
3477
  var Texture = _Texture;
2458
3478
  /** The texture can be bound for use as a sampled texture in a shader */
@@ -2469,13 +3489,12 @@ or create a device with the 'debug: true' prop.`;
2469
3489
  __publicField(Texture, "TEXTURE", 4);
2470
3490
  /** @deprecated Use Texture.RENDER */
2471
3491
  __publicField(Texture, "RENDER_ATTACHMENT", 16);
2472
- /** Default options */
2473
3492
  __publicField(Texture, "defaultProps", {
2474
3493
  ...Resource.defaultProps,
2475
3494
  data: null,
2476
3495
  dimension: "2d",
2477
3496
  format: "rgba8unorm",
2478
- usage: _Texture.TEXTURE | _Texture.RENDER_ATTACHMENT | _Texture.COPY_DST,
3497
+ usage: _Texture.SAMPLE | _Texture.RENDER | _Texture.COPY_DST,
2479
3498
  width: void 0,
2480
3499
  height: void 0,
2481
3500
  depth: 1,
@@ -2489,6 +3508,10 @@ or create a device with the 'debug: true' prop.`;
2489
3508
  byteOffset: 0,
2490
3509
  bytesPerRow: void 0,
2491
3510
  rowsPerImage: void 0,
3511
+ width: void 0,
3512
+ height: void 0,
3513
+ depthOrArrayLayers: void 0,
3514
+ depth: 1,
2492
3515
  mipLevel: 0,
2493
3516
  x: 0,
2494
3517
  y: 0,
@@ -2512,6 +3535,29 @@ or create a device with the 'debug: true' prop.`;
2512
3535
  premultipliedAlpha: false,
2513
3536
  flipY: false
2514
3537
  });
3538
+ __publicField(Texture, "defaultTextureReadOptions", {
3539
+ x: 0,
3540
+ y: 0,
3541
+ z: 0,
3542
+ width: void 0,
3543
+ height: void 0,
3544
+ depthOrArrayLayers: 1,
3545
+ mipLevel: 0,
3546
+ aspect: "all"
3547
+ });
3548
+ __publicField(Texture, "defaultTextureWriteOptions", {
3549
+ byteOffset: 0,
3550
+ bytesPerRow: void 0,
3551
+ rowsPerImage: void 0,
3552
+ x: 0,
3553
+ y: 0,
3554
+ z: 0,
3555
+ width: void 0,
3556
+ height: void 0,
3557
+ depthOrArrayLayers: 1,
3558
+ mipLevel: 0,
3559
+ aspect: "all"
3560
+ });
2515
3561
 
2516
3562
  // ../core/src/adapter/resources/texture-view.ts
2517
3563
  var _TextureView = class extends Resource {
@@ -2558,24 +3604,32 @@ or create a device with the 'debug: true' prop.`;
2558
3604
  const log2 = shaderLog.slice().sort((a, b) => a.lineNum - b.lineNum);
2559
3605
  switch (options?.showSourceCode || "no") {
2560
3606
  case "all":
2561
- let currentMessage = 0;
3607
+ let currentMessageIndex = 0;
2562
3608
  for (let lineNum = 1; lineNum <= lines.length; lineNum++) {
2563
- formattedLog += getNumberedLine(lines[lineNum - 1], lineNum, options);
2564
- while (log2.length > currentMessage && log2[currentMessage].lineNum === lineNum) {
2565
- const message = log2[currentMessage++];
2566
- formattedLog += formatCompilerMessage(message, lines, message.lineNum, {
3609
+ const line = lines[lineNum - 1];
3610
+ const currentMessage = log2[currentMessageIndex];
3611
+ if (line && currentMessage) {
3612
+ formattedLog += getNumberedLine(line, lineNum, options);
3613
+ }
3614
+ while (log2.length > currentMessageIndex && currentMessage.lineNum === lineNum) {
3615
+ const message = log2[currentMessageIndex++];
3616
+ if (message) {
3617
+ formattedLog += formatCompilerMessage(message, lines, message.lineNum, {
3618
+ ...options,
3619
+ inlineSource: false
3620
+ });
3621
+ }
3622
+ }
3623
+ }
3624
+ while (log2.length > currentMessageIndex) {
3625
+ const message = log2[currentMessageIndex++];
3626
+ if (message) {
3627
+ formattedLog += formatCompilerMessage(message, [], 0, {
2567
3628
  ...options,
2568
3629
  inlineSource: false
2569
3630
  });
2570
3631
  }
2571
3632
  }
2572
- while (log2.length > currentMessage) {
2573
- const message = log2[currentMessage++];
2574
- formattedLog += formatCompilerMessage(message, [], 0, {
2575
- ...options,
2576
- inlineSource: false
2577
- });
2578
- }
2579
3633
  return formattedLog;
2580
3634
  case "issues":
2581
3635
  case "no":
@@ -2597,8 +3651,8 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
2597
3651
 
2598
3652
  `;
2599
3653
  }
2600
- const color = message.type === "error" ? "red" : "#8B4000";
2601
- 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}`;
3654
+ const color = message.type === "error" ? "red" : "orange";
3655
+ 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}`;
2602
3656
  }
2603
3657
  function getNumberedLines(lines, lineNum, options) {
2604
3658
  let numberedLines = "";
@@ -2684,29 +3738,34 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
2684
3738
  }
2685
3739
  const shaderName = shaderId;
2686
3740
  const shaderTitle = `${this.stage} shader "${shaderName}"`;
2687
- let htmlLog = formatCompilerLog(messages, this.source, { showSourceCode: "all", html: true });
3741
+ const htmlLog = formatCompilerLog(messages, this.source, { showSourceCode: "all", html: true });
2688
3742
  const translatedSource = this.getTranslatedSource();
3743
+ const container = document.createElement("div");
3744
+ container.innerHTML = `<h1>Compilation error in ${shaderTitle}</h1>
3745
+ <div style="display:flex;position:fixed;top:10px;right:20px;gap:2px;">
3746
+ <button id="copy">Copy source</button><br/>
3747
+ <button id="close">Close</button>
3748
+ </div>
3749
+ <code><pre>${htmlLog}</pre></code>`;
2689
3750
  if (translatedSource) {
2690
- htmlLog += `<br /><br /><h1>Translated Source</h1><br /><br /><code style="user-select:text;"><pre>${translatedSource}</pre></code>`;
2691
- }
2692
- const button = document.createElement("Button");
2693
- button.innerHTML = `
2694
- <h1>Compilation error in ${shaderTitle}</h1><br /><br />
2695
- <code style="user-select:text;"><pre>
2696
- ${htmlLog}
2697
- </pre></code>`;
2698
- button.style.top = "10px";
2699
- button.style.left = "10px";
2700
- button.style.position = "absolute";
2701
- button.style.zIndex = "9999";
2702
- button.style.width = "100%";
2703
- button.style.textAlign = "left";
2704
- document.body.appendChild(button);
2705
- const errors = document.getElementsByClassName("luma-compiler-log-error");
2706
- errors[0]?.scrollIntoView();
2707
- button.onclick = () => {
2708
- const dataURI = `data:text/plain,${encodeURIComponent(this.source)}`;
2709
- navigator.clipboard.writeText(dataURI);
3751
+ container.innerHTML += `<br /><h1>Translated Source</h1><br /><br /><code><pre>${translatedSource}</pre></code>`;
3752
+ }
3753
+ container.style.top = "0";
3754
+ container.style.left = "0";
3755
+ container.style.background = "white";
3756
+ container.style.position = "fixed";
3757
+ container.style.zIndex = "9999";
3758
+ container.style.maxWidth = "100vw";
3759
+ container.style.maxHeight = "100vh";
3760
+ container.style.overflowY = "auto";
3761
+ document.body.appendChild(container);
3762
+ const error = container.querySelector(".luma-compiler-log-error");
3763
+ error?.scrollIntoView();
3764
+ container.querySelector("button#close").onclick = () => {
3765
+ container.remove();
3766
+ };
3767
+ container.querySelector("button#copy").onclick = () => {
3768
+ navigator.clipboard.writeText(this.source);
2710
3769
  };
2711
3770
  }
2712
3771
  };
@@ -2726,7 +3785,7 @@ ${htmlLog}
2726
3785
  function getShaderName(shader, defaultName = "unnamed") {
2727
3786
  const SHADER_NAME_REGEXP = /#define[\s*]SHADER_NAME[\s*]([A-Za-z0-9_-]+)[\s*]/;
2728
3787
  const match = SHADER_NAME_REGEXP.exec(shader);
2729
- return match ? match[1] : defaultName;
3788
+ return match?.[1] ?? defaultName;
2730
3789
  }
2731
3790
 
2732
3791
  // ../core/src/adapter/resources/framebuffer.ts
@@ -2752,7 +3811,12 @@ ${htmlLog}
2752
3811
  (colorAttachment) => colorAttachment.texture.clone(size)
2753
3812
  );
2754
3813
  const depthStencilAttachment = this.depthStencilAttachment && this.depthStencilAttachment.texture.clone(size);
2755
- return this.device.createFramebuffer({ ...this.props, colorAttachments, depthStencilAttachment });
3814
+ return this.device.createFramebuffer({
3815
+ ...this.props,
3816
+ ...size,
3817
+ colorAttachments,
3818
+ depthStencilAttachment
3819
+ });
2756
3820
  }
2757
3821
  resize(size) {
2758
3822
  let updateSize = !size;
@@ -2827,17 +3891,15 @@ ${htmlLog}
2827
3891
  * and destroys existing textures if owned
2828
3892
  */
2829
3893
  resizeAttachments(width, height) {
2830
- for (let i = 0; i < this.colorAttachments.length; ++i) {
2831
- if (this.colorAttachments[i]) {
2832
- const resizedTexture = this.colorAttachments[i].texture.clone({
2833
- width,
2834
- height
2835
- });
2836
- this.destroyAttachedResource(this.colorAttachments[i]);
2837
- this.colorAttachments[i] = resizedTexture.view;
2838
- this.attachResource(resizedTexture.view);
2839
- }
2840
- }
3894
+ this.colorAttachments.forEach((colorAttachment, i) => {
3895
+ const resizedTexture = colorAttachment.texture.clone({
3896
+ width,
3897
+ height
3898
+ });
3899
+ this.destroyAttachedResource(colorAttachment);
3900
+ this.colorAttachments[i] = resizedTexture.view;
3901
+ this.attachResource(resizedTexture.view);
3902
+ });
2841
3903
  if (this.depthStencilAttachment) {
2842
3904
  const resizedTexture = this.depthStencilAttachment.texture.clone({
2843
3905
  width,
@@ -2874,30 +3936,538 @@ ${htmlLog}
2874
3936
  linkStatus = "pending";
2875
3937
  /** The hash of the pipeline */
2876
3938
  hash = "";
3939
+ /** Optional shared backend implementation */
3940
+ sharedRenderPipeline = null;
3941
+ /** Whether shader or pipeline compilation/linking is still in progress */
3942
+ get isPending() {
3943
+ return this.linkStatus === "pending" || this.vs.compilationStatus === "pending" || this.fs?.compilationStatus === "pending";
3944
+ }
3945
+ /** Whether shader or pipeline compilation/linking has failed */
3946
+ get isErrored() {
3947
+ return this.linkStatus === "error" || this.vs.compilationStatus === "error" || this.fs?.compilationStatus === "error";
3948
+ }
2877
3949
  constructor(device, props) {
2878
3950
  super(device, props, _RenderPipeline.defaultProps);
2879
3951
  this.shaderLayout = this.props.shaderLayout;
2880
3952
  this.bufferLayout = this.props.bufferLayout || [];
3953
+ this.sharedRenderPipeline = this.props._sharedRenderPipeline || null;
3954
+ }
3955
+ };
3956
+ var RenderPipeline = _RenderPipeline;
3957
+ __publicField(RenderPipeline, "defaultProps", {
3958
+ ...Resource.defaultProps,
3959
+ vs: null,
3960
+ vertexEntryPoint: "vertexMain",
3961
+ vsConstants: {},
3962
+ fs: null,
3963
+ fragmentEntryPoint: "fragmentMain",
3964
+ fsConstants: {},
3965
+ shaderLayout: null,
3966
+ bufferLayout: [],
3967
+ topology: "triangle-list",
3968
+ colorAttachmentFormats: void 0,
3969
+ depthStencilAttachmentFormat: void 0,
3970
+ parameters: {},
3971
+ varyings: void 0,
3972
+ bufferMode: void 0,
3973
+ disableWarnings: false,
3974
+ _sharedRenderPipeline: void 0,
3975
+ bindings: void 0,
3976
+ bindGroups: void 0
3977
+ });
3978
+
3979
+ // ../core/src/adapter/resources/shared-render-pipeline.ts
3980
+ var SharedRenderPipeline = class extends Resource {
3981
+ get [Symbol.toStringTag]() {
3982
+ return "SharedRenderPipeline";
3983
+ }
3984
+ constructor(device, props) {
3985
+ super(device, props, {
3986
+ ...Resource.defaultProps,
3987
+ handle: void 0,
3988
+ vs: void 0,
3989
+ fs: void 0,
3990
+ varyings: void 0,
3991
+ bufferMode: void 0
3992
+ });
3993
+ }
3994
+ };
3995
+
3996
+ // ../core/src/adapter/resources/compute-pipeline.ts
3997
+ var _ComputePipeline = class extends Resource {
3998
+ get [Symbol.toStringTag]() {
3999
+ return "ComputePipeline";
4000
+ }
4001
+ hash = "";
4002
+ /** The merged shader layout */
4003
+ shaderLayout;
4004
+ constructor(device, props) {
4005
+ super(device, props, _ComputePipeline.defaultProps);
4006
+ this.shaderLayout = props.shaderLayout;
4007
+ }
4008
+ };
4009
+ var ComputePipeline = _ComputePipeline;
4010
+ __publicField(ComputePipeline, "defaultProps", {
4011
+ ...Resource.defaultProps,
4012
+ shader: void 0,
4013
+ entryPoint: void 0,
4014
+ constants: {},
4015
+ shaderLayout: void 0
4016
+ });
4017
+
4018
+ // ../core/src/factories/pipeline-factory.ts
4019
+ var _PipelineFactory = class {
4020
+ /** Get the singleton default pipeline factory for the specified device */
4021
+ static getDefaultPipelineFactory(device) {
4022
+ const moduleData = device.getModuleData("@luma.gl/core");
4023
+ moduleData.defaultPipelineFactory ||= new _PipelineFactory(device);
4024
+ return moduleData.defaultPipelineFactory;
4025
+ }
4026
+ device;
4027
+ _hashCounter = 0;
4028
+ _hashes = {};
4029
+ _renderPipelineCache = {};
4030
+ _computePipelineCache = {};
4031
+ _sharedRenderPipelineCache = {};
4032
+ get [Symbol.toStringTag]() {
4033
+ return "PipelineFactory";
4034
+ }
4035
+ toString() {
4036
+ return `PipelineFactory(${this.device.id})`;
4037
+ }
4038
+ constructor(device) {
4039
+ this.device = device;
4040
+ }
4041
+ /**
4042
+ * WebGL has two cache layers with different priorities:
4043
+ * - `_sharedRenderPipelineCache` owns `WEBGLSharedRenderPipeline` / `WebGLProgram` reuse.
4044
+ * - `_renderPipelineCache` owns `RenderPipeline` wrapper reuse.
4045
+ *
4046
+ * Shared WebGL program reuse is the hard requirement. Wrapper reuse is beneficial,
4047
+ * but wrapper cache misses are acceptable if that keeps the cache logic simple and
4048
+ * prevents incorrect cache hits.
4049
+ *
4050
+ * In particular, wrapper hash logic must never force program creation or linked-program
4051
+ * introspection just to decide whether a shared WebGL program can be reused.
4052
+ */
4053
+ /** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */
4054
+ createRenderPipeline(props) {
4055
+ if (!this.device.props._cachePipelines) {
4056
+ return this.device.createRenderPipeline(props);
4057
+ }
4058
+ const allProps = { ...RenderPipeline.defaultProps, ...props };
4059
+ const cache = this._renderPipelineCache;
4060
+ const hash = this._hashRenderPipeline(allProps);
4061
+ let pipeline = cache[hash]?.resource;
4062
+ if (!pipeline) {
4063
+ const sharedRenderPipeline = this.device.type === "webgl" && this.device.props._sharePipelines ? this.createSharedRenderPipeline(allProps) : void 0;
4064
+ pipeline = this.device.createRenderPipeline({
4065
+ ...allProps,
4066
+ id: allProps.id ? `${allProps.id}-cached` : uid("unnamed-cached"),
4067
+ _sharedRenderPipeline: sharedRenderPipeline
4068
+ });
4069
+ pipeline.hash = hash;
4070
+ cache[hash] = { resource: pipeline, useCount: 1 };
4071
+ if (this.device.props.debugFactories) {
4072
+ log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
4073
+ }
4074
+ } else {
4075
+ cache[hash].useCount++;
4076
+ if (this.device.props.debugFactories) {
4077
+ log.log(
4078
+ 3,
4079
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
4080
+ )();
4081
+ }
4082
+ }
4083
+ return pipeline;
4084
+ }
4085
+ /** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */
4086
+ createComputePipeline(props) {
4087
+ if (!this.device.props._cachePipelines) {
4088
+ return this.device.createComputePipeline(props);
4089
+ }
4090
+ const allProps = { ...ComputePipeline.defaultProps, ...props };
4091
+ const cache = this._computePipelineCache;
4092
+ const hash = this._hashComputePipeline(allProps);
4093
+ let pipeline = cache[hash]?.resource;
4094
+ if (!pipeline) {
4095
+ pipeline = this.device.createComputePipeline({
4096
+ ...allProps,
4097
+ id: allProps.id ? `${allProps.id}-cached` : void 0
4098
+ });
4099
+ pipeline.hash = hash;
4100
+ cache[hash] = { resource: pipeline, useCount: 1 };
4101
+ if (this.device.props.debugFactories) {
4102
+ log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
4103
+ }
4104
+ } else {
4105
+ cache[hash].useCount++;
4106
+ if (this.device.props.debugFactories) {
4107
+ log.log(
4108
+ 3,
4109
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
4110
+ )();
4111
+ }
4112
+ }
4113
+ return pipeline;
4114
+ }
4115
+ release(pipeline) {
4116
+ if (!this.device.props._cachePipelines) {
4117
+ pipeline.destroy();
4118
+ return;
4119
+ }
4120
+ const cache = this._getCache(pipeline);
4121
+ const hash = pipeline.hash;
4122
+ cache[hash].useCount--;
4123
+ if (cache[hash].useCount === 0) {
4124
+ this._destroyPipeline(pipeline);
4125
+ if (this.device.props.debugFactories) {
4126
+ log.log(3, `${this}: ${pipeline} released and destroyed`)();
4127
+ }
4128
+ } else if (cache[hash].useCount < 0) {
4129
+ log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)();
4130
+ cache[hash].useCount = 0;
4131
+ } else if (this.device.props.debugFactories) {
4132
+ log.log(3, `${this}: ${pipeline} released, count=${cache[hash].useCount}`)();
4133
+ }
4134
+ }
4135
+ createSharedRenderPipeline(props) {
4136
+ const sharedPipelineHash = this._hashSharedRenderPipeline(props);
4137
+ let sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
4138
+ if (!sharedCacheItem) {
4139
+ const sharedRenderPipeline = this.device._createSharedRenderPipelineWebGL(props);
4140
+ sharedCacheItem = { resource: sharedRenderPipeline, useCount: 0 };
4141
+ this._sharedRenderPipelineCache[sharedPipelineHash] = sharedCacheItem;
4142
+ }
4143
+ sharedCacheItem.useCount++;
4144
+ return sharedCacheItem.resource;
4145
+ }
4146
+ releaseSharedRenderPipeline(pipeline) {
4147
+ if (!pipeline.sharedRenderPipeline) {
4148
+ return;
4149
+ }
4150
+ const sharedPipelineHash = this._hashSharedRenderPipeline(pipeline.sharedRenderPipeline.props);
4151
+ const sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
4152
+ if (!sharedCacheItem) {
4153
+ return;
4154
+ }
4155
+ sharedCacheItem.useCount--;
4156
+ if (sharedCacheItem.useCount === 0) {
4157
+ sharedCacheItem.resource.destroy();
4158
+ delete this._sharedRenderPipelineCache[sharedPipelineHash];
4159
+ }
4160
+ }
4161
+ // PRIVATE
4162
+ /** Destroy a cached pipeline, removing it from the cache if configured to do so. */
4163
+ _destroyPipeline(pipeline) {
4164
+ const cache = this._getCache(pipeline);
4165
+ if (!this.device.props._destroyPipelines) {
4166
+ return false;
4167
+ }
4168
+ delete cache[pipeline.hash];
4169
+ pipeline.destroy();
4170
+ if (pipeline instanceof RenderPipeline) {
4171
+ this.releaseSharedRenderPipeline(pipeline);
4172
+ }
4173
+ return true;
4174
+ }
4175
+ /** Get the appropriate cache for the type of pipeline */
4176
+ _getCache(pipeline) {
4177
+ let cache;
4178
+ if (pipeline instanceof ComputePipeline) {
4179
+ cache = this._computePipelineCache;
4180
+ }
4181
+ if (pipeline instanceof RenderPipeline) {
4182
+ cache = this._renderPipelineCache;
4183
+ }
4184
+ if (!cache) {
4185
+ throw new Error(`${this}`);
4186
+ }
4187
+ if (!cache[pipeline.hash]) {
4188
+ throw new Error(`${this}: ${pipeline} matched incorrect entry`);
4189
+ }
4190
+ return cache;
4191
+ }
4192
+ /** Calculate a hash based on all the inputs for a compute pipeline */
4193
+ _hashComputePipeline(props) {
4194
+ const { type } = this.device;
4195
+ const shaderHash = this._getHash(props.shader.source);
4196
+ const shaderLayoutHash = this._getHash(JSON.stringify(props.shaderLayout));
4197
+ return `${type}/C/${shaderHash}SL${shaderLayoutHash}`;
4198
+ }
4199
+ /** Calculate a hash based on all the inputs for a render pipeline */
4200
+ _hashRenderPipeline(props) {
4201
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
4202
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
4203
+ const varyingHash = this._getWebGLVaryingHash(props);
4204
+ const shaderLayoutHash = this._getHash(JSON.stringify(props.shaderLayout));
4205
+ const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
4206
+ const { type } = this.device;
4207
+ switch (type) {
4208
+ case "webgl":
4209
+ const webglParameterHash = this._getHash(JSON.stringify(props.parameters));
4210
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${webglParameterHash}SL${shaderLayoutHash}BL${bufferLayoutHash}`;
4211
+ case "webgpu":
4212
+ default:
4213
+ const entryPointHash = this._getHash(
4214
+ JSON.stringify({
4215
+ vertexEntryPoint: props.vertexEntryPoint,
4216
+ fragmentEntryPoint: props.fragmentEntryPoint
4217
+ })
4218
+ );
4219
+ const parameterHash = this._getHash(JSON.stringify(props.parameters));
4220
+ const attachmentHash = this._getWebGPUAttachmentHash(props);
4221
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}EP${entryPointHash}P${parameterHash}SL${shaderLayoutHash}BL${bufferLayoutHash}A${attachmentHash}`;
4222
+ }
4223
+ }
4224
+ // This is the only gate for shared `WebGLProgram` reuse.
4225
+ // Only include inputs that affect program linking or transform-feedback linkage.
4226
+ // Wrapper-only concerns such as topology, parameters, attachment formats and layout
4227
+ // overrides must not be added here.
4228
+ _hashSharedRenderPipeline(props) {
4229
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
4230
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
4231
+ const varyingHash = this._getWebGLVaryingHash(props);
4232
+ return `webgl/S/${vsHash}/${fsHash}V${varyingHash}`;
4233
+ }
4234
+ _getHash(key) {
4235
+ if (this._hashes[key] === void 0) {
4236
+ this._hashes[key] = this._hashCounter++;
4237
+ }
4238
+ return this._hashes[key];
4239
+ }
4240
+ _getWebGLVaryingHash(props) {
4241
+ const { varyings = [], bufferMode = null } = props;
4242
+ return this._getHash(JSON.stringify({ varyings, bufferMode }));
4243
+ }
4244
+ _getWebGPUAttachmentHash(props) {
4245
+ const colorAttachmentFormats = props.colorAttachmentFormats ?? [
4246
+ this.device.preferredColorFormat
4247
+ ];
4248
+ const depthStencilAttachmentFormat = props.parameters?.depthWriteEnabled ? props.depthStencilAttachmentFormat || this.device.preferredDepthFormat : null;
4249
+ return this._getHash(
4250
+ JSON.stringify({
4251
+ colorAttachmentFormats,
4252
+ depthStencilAttachmentFormat
4253
+ })
4254
+ );
4255
+ }
4256
+ };
4257
+ var PipelineFactory = _PipelineFactory;
4258
+ __publicField(PipelineFactory, "defaultProps", { ...RenderPipeline.defaultProps });
4259
+
4260
+ // ../core/src/factories/shader-factory.ts
4261
+ var _ShaderFactory = class {
4262
+ /** Returns the default ShaderFactory for the given {@link Device}, creating one if necessary. */
4263
+ static getDefaultShaderFactory(device) {
4264
+ const moduleData = device.getModuleData("@luma.gl/core");
4265
+ moduleData.defaultShaderFactory ||= new _ShaderFactory(device);
4266
+ return moduleData.defaultShaderFactory;
4267
+ }
4268
+ device;
4269
+ _cache = {};
4270
+ get [Symbol.toStringTag]() {
4271
+ return "ShaderFactory";
4272
+ }
4273
+ toString() {
4274
+ return `${this[Symbol.toStringTag]}(${this.device.id})`;
4275
+ }
4276
+ /** @internal */
4277
+ constructor(device) {
4278
+ this.device = device;
4279
+ }
4280
+ /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */
4281
+ createShader(props) {
4282
+ if (!this.device.props._cacheShaders) {
4283
+ return this.device.createShader(props);
4284
+ }
4285
+ const key = this._hashShader(props);
4286
+ let cacheEntry = this._cache[key];
4287
+ if (!cacheEntry) {
4288
+ const resource = this.device.createShader({
4289
+ ...props,
4290
+ id: props.id ? `${props.id}-cached` : void 0
4291
+ });
4292
+ this._cache[key] = cacheEntry = { resource, useCount: 1 };
4293
+ if (this.device.props.debugFactories) {
4294
+ log.log(3, `${this}: Created new shader ${resource.id}`)();
4295
+ }
4296
+ } else {
4297
+ cacheEntry.useCount++;
4298
+ if (this.device.props.debugFactories) {
4299
+ log.log(
4300
+ 3,
4301
+ `${this}: Reusing shader ${cacheEntry.resource.id} count=${cacheEntry.useCount}`
4302
+ )();
4303
+ }
4304
+ }
4305
+ return cacheEntry.resource;
4306
+ }
4307
+ /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */
4308
+ release(shader) {
4309
+ if (!this.device.props._cacheShaders) {
4310
+ shader.destroy();
4311
+ return;
4312
+ }
4313
+ const key = this._hashShader(shader);
4314
+ const cacheEntry = this._cache[key];
4315
+ if (cacheEntry) {
4316
+ cacheEntry.useCount--;
4317
+ if (cacheEntry.useCount === 0) {
4318
+ if (this.device.props._destroyShaders) {
4319
+ delete this._cache[key];
4320
+ cacheEntry.resource.destroy();
4321
+ if (this.device.props.debugFactories) {
4322
+ log.log(3, `${this}: Releasing shader ${shader.id}, destroyed`)();
4323
+ }
4324
+ }
4325
+ } else if (cacheEntry.useCount < 0) {
4326
+ throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`);
4327
+ } else if (this.device.props.debugFactories) {
4328
+ log.log(3, `${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)();
4329
+ }
4330
+ }
4331
+ }
4332
+ // PRIVATE
4333
+ _hashShader(value) {
4334
+ return `${value.stage}:${value.source}`;
4335
+ }
4336
+ };
4337
+ var ShaderFactory = _ShaderFactory;
4338
+ __publicField(ShaderFactory, "defaultProps", { ...Shader.defaultProps });
4339
+
4340
+ // ../core/src/adapter-utils/bind-groups.ts
4341
+ function getShaderLayoutBinding(shaderLayout, bindingName, options) {
4342
+ const bindingLayout = shaderLayout.bindings.find(
4343
+ (binding) => binding.name === bindingName || `${binding.name.toLocaleLowerCase()}uniforms` === bindingName.toLocaleLowerCase()
4344
+ );
4345
+ if (!bindingLayout && !options?.ignoreWarnings) {
4346
+ log.warn(`Binding ${bindingName} not set: Not found in shader layout.`)();
4347
+ }
4348
+ return bindingLayout || null;
4349
+ }
4350
+ function normalizeBindingsByGroup(shaderLayout, bindingsOrBindGroups) {
4351
+ if (!bindingsOrBindGroups) {
4352
+ return {};
4353
+ }
4354
+ if (areBindingsGrouped(bindingsOrBindGroups)) {
4355
+ const bindGroups2 = bindingsOrBindGroups;
4356
+ return Object.fromEntries(
4357
+ Object.entries(bindGroups2).map(([group, bindings]) => [Number(group), { ...bindings }])
4358
+ );
4359
+ }
4360
+ const bindGroups = {};
4361
+ for (const [bindingName, binding] of Object.entries(bindingsOrBindGroups)) {
4362
+ const bindingLayout = getShaderLayoutBinding(shaderLayout, bindingName);
4363
+ const group = bindingLayout?.group ?? 0;
4364
+ bindGroups[group] ||= {};
4365
+ bindGroups[group][bindingName] = binding;
4366
+ }
4367
+ return bindGroups;
4368
+ }
4369
+ function flattenBindingsByGroup(bindGroups) {
4370
+ const bindings = {};
4371
+ for (const groupBindings of Object.values(bindGroups)) {
4372
+ Object.assign(bindings, groupBindings);
4373
+ }
4374
+ return bindings;
4375
+ }
4376
+ function areBindingsGrouped(bindingsOrBindGroups) {
4377
+ const keys = Object.keys(bindingsOrBindGroups);
4378
+ return keys.length > 0 && keys.every((key) => /^\d+$/.test(key));
4379
+ }
4380
+
4381
+ // ../core/src/factories/bind-group-factory.ts
4382
+ var BindGroupFactory = class {
4383
+ device;
4384
+ _layoutCacheByPipeline = /* @__PURE__ */ new WeakMap();
4385
+ _bindGroupCacheByLayout = /* @__PURE__ */ new WeakMap();
4386
+ constructor(device) {
4387
+ this.device = device;
4388
+ }
4389
+ getBindGroups(pipeline, bindings, bindGroupCacheKeys) {
4390
+ if (this.device.type !== "webgpu" || pipeline.shaderLayout.bindings.length === 0) {
4391
+ return {};
4392
+ }
4393
+ const bindingsByGroup = normalizeBindingsByGroup(pipeline.shaderLayout, bindings);
4394
+ const resolvedBindGroups = {};
4395
+ for (const group of getBindGroupIndicesUpToMax(pipeline.shaderLayout.bindings)) {
4396
+ const groupBindings = bindingsByGroup[group];
4397
+ const bindGroupLayout = this._getBindGroupLayout(pipeline, group);
4398
+ if (!groupBindings || Object.keys(groupBindings).length === 0) {
4399
+ if (!hasBindingsInGroup(pipeline.shaderLayout.bindings, group)) {
4400
+ resolvedBindGroups[group] = this._getEmptyBindGroup(
4401
+ bindGroupLayout,
4402
+ pipeline.shaderLayout,
4403
+ group
4404
+ );
4405
+ }
4406
+ continue;
4407
+ }
4408
+ const bindGroupCacheKey = bindGroupCacheKeys?.[group];
4409
+ if (bindGroupCacheKey) {
4410
+ const layoutCache = this._getLayoutBindGroupCache(bindGroupLayout);
4411
+ if (layoutCache.bindGroupsBySource.has(bindGroupCacheKey)) {
4412
+ resolvedBindGroups[group] = layoutCache.bindGroupsBySource.get(bindGroupCacheKey) || null;
4413
+ continue;
4414
+ }
4415
+ const bindGroup = this.device._createBindGroupWebGPU(
4416
+ bindGroupLayout,
4417
+ pipeline.shaderLayout,
4418
+ groupBindings,
4419
+ group
4420
+ );
4421
+ layoutCache.bindGroupsBySource.set(bindGroupCacheKey, bindGroup);
4422
+ resolvedBindGroups[group] = bindGroup;
4423
+ } else {
4424
+ resolvedBindGroups[group] = this.device._createBindGroupWebGPU(
4425
+ bindGroupLayout,
4426
+ pipeline.shaderLayout,
4427
+ groupBindings,
4428
+ group
4429
+ );
4430
+ }
4431
+ }
4432
+ return resolvedBindGroups;
4433
+ }
4434
+ _getBindGroupLayout(pipeline, group) {
4435
+ let layoutCache = this._layoutCacheByPipeline.get(pipeline);
4436
+ if (!layoutCache) {
4437
+ layoutCache = {};
4438
+ this._layoutCacheByPipeline.set(pipeline, layoutCache);
4439
+ }
4440
+ layoutCache[group] ||= this.device._createBindGroupLayoutWebGPU(pipeline, group);
4441
+ return layoutCache[group];
4442
+ }
4443
+ _getEmptyBindGroup(bindGroupLayout, shaderLayout, group) {
4444
+ const layoutCache = this._getLayoutBindGroupCache(bindGroupLayout);
4445
+ layoutCache.emptyBindGroup ||= this.device._createBindGroupWebGPU(bindGroupLayout, shaderLayout, {}, group) || null;
4446
+ return layoutCache.emptyBindGroup;
4447
+ }
4448
+ _getLayoutBindGroupCache(bindGroupLayout) {
4449
+ let layoutCache = this._bindGroupCacheByLayout.get(bindGroupLayout);
4450
+ if (!layoutCache) {
4451
+ layoutCache = { bindGroupsBySource: /* @__PURE__ */ new WeakMap() };
4452
+ this._bindGroupCacheByLayout.set(bindGroupLayout, layoutCache);
4453
+ }
4454
+ return layoutCache;
2881
4455
  }
2882
4456
  };
2883
- var RenderPipeline = _RenderPipeline;
2884
- __publicField(RenderPipeline, "defaultProps", {
2885
- ...Resource.defaultProps,
2886
- vs: null,
2887
- vertexEntryPoint: "vertexMain",
2888
- vsConstants: {},
2889
- fs: null,
2890
- fragmentEntryPoint: "fragmentMain",
2891
- fsConstants: {},
2892
- shaderLayout: null,
2893
- bufferLayout: [],
2894
- topology: "triangle-list",
2895
- colorAttachmentFormats: void 0,
2896
- depthStencilAttachmentFormat: void 0,
2897
- parameters: {},
2898
- bindings: {},
2899
- uniforms: {}
2900
- });
4457
+ function _getDefaultBindGroupFactory(device) {
4458
+ device._factories.bindGroupFactory ||= new BindGroupFactory(device);
4459
+ return device._factories.bindGroupFactory;
4460
+ }
4461
+ function getBindGroupIndicesUpToMax(bindings) {
4462
+ const maxGroup = bindings.reduce(
4463
+ (highestGroup, binding) => Math.max(highestGroup, binding.group),
4464
+ -1
4465
+ );
4466
+ return Array.from({ length: maxGroup + 1 }, (_, group) => group);
4467
+ }
4468
+ function hasBindingsInGroup(bindings, group) {
4469
+ return bindings.some((binding) => binding.group === group);
4470
+ }
2901
4471
 
2902
4472
  // ../core/src/adapter/resources/render-pass.ts
2903
4473
  var _RenderPass = class extends Resource {
@@ -2937,28 +4507,6 @@ ${htmlLog}
2937
4507
  endTimestampIndex: void 0
2938
4508
  });
2939
4509
 
2940
- // ../core/src/adapter/resources/compute-pipeline.ts
2941
- var _ComputePipeline = class extends Resource {
2942
- get [Symbol.toStringTag]() {
2943
- return "ComputePipeline";
2944
- }
2945
- hash = "";
2946
- /** The merged shader layout */
2947
- shaderLayout;
2948
- constructor(device, props) {
2949
- super(device, props, _ComputePipeline.defaultProps);
2950
- this.shaderLayout = props.shaderLayout;
2951
- }
2952
- };
2953
- var ComputePipeline = _ComputePipeline;
2954
- __publicField(ComputePipeline, "defaultProps", {
2955
- ...Resource.defaultProps,
2956
- shader: void 0,
2957
- entryPoint: void 0,
2958
- constants: {},
2959
- shaderLayout: void 0
2960
- });
2961
-
2962
4510
  // ../core/src/adapter/resources/compute-pass.ts
2963
4511
  var _ComputePass = class extends Resource {
2964
4512
  constructor(device, props) {
@@ -2981,8 +4529,69 @@ ${htmlLog}
2981
4529
  get [Symbol.toStringTag]() {
2982
4530
  return "CommandEncoder";
2983
4531
  }
4532
+ _timeProfilingQuerySet = null;
4533
+ _timeProfilingSlotCount = 0;
4534
+ _gpuTimeMs;
2984
4535
  constructor(device, props) {
2985
4536
  super(device, props, _CommandEncoder.defaultProps);
4537
+ this._timeProfilingQuerySet = props.timeProfilingQuerySet ?? null;
4538
+ this._timeProfilingSlotCount = 0;
4539
+ this._gpuTimeMs = void 0;
4540
+ }
4541
+ /**
4542
+ * Reads all resolved timestamp pairs on the current profiler query set and caches the sum
4543
+ * as milliseconds on this encoder.
4544
+ */
4545
+ async resolveTimeProfilingQuerySet() {
4546
+ this._gpuTimeMs = void 0;
4547
+ if (!this._timeProfilingQuerySet) {
4548
+ return;
4549
+ }
4550
+ const pairCount = Math.floor(this._timeProfilingSlotCount / 2);
4551
+ if (pairCount <= 0) {
4552
+ return;
4553
+ }
4554
+ const queryCount = pairCount * 2;
4555
+ const results = await this._timeProfilingQuerySet.readResults({
4556
+ firstQuery: 0,
4557
+ queryCount
4558
+ });
4559
+ let totalDurationNanoseconds = 0n;
4560
+ for (let queryIndex = 0; queryIndex < queryCount; queryIndex += 2) {
4561
+ totalDurationNanoseconds += results[queryIndex + 1] - results[queryIndex];
4562
+ }
4563
+ this._gpuTimeMs = Number(totalDurationNanoseconds) / 1e6;
4564
+ }
4565
+ /** Returns the number of query slots consumed by automatic pass profiling on this encoder. */
4566
+ getTimeProfilingSlotCount() {
4567
+ return this._timeProfilingSlotCount;
4568
+ }
4569
+ getTimeProfilingQuerySet() {
4570
+ return this._timeProfilingQuerySet;
4571
+ }
4572
+ /** Internal helper for auto-assigning timestamp slots to render/compute passes on this encoder. */
4573
+ _applyTimeProfilingToPassProps(props) {
4574
+ const passProps = props || {};
4575
+ if (!this._supportsTimestampQueries() || !this._timeProfilingQuerySet) {
4576
+ return passProps;
4577
+ }
4578
+ if (passProps.timestampQuerySet !== void 0 || passProps.beginTimestampIndex !== void 0 || passProps.endTimestampIndex !== void 0) {
4579
+ return passProps;
4580
+ }
4581
+ const beginTimestampIndex = this._timeProfilingSlotCount;
4582
+ if (beginTimestampIndex + 1 >= this._timeProfilingQuerySet.props.count) {
4583
+ return passProps;
4584
+ }
4585
+ this._timeProfilingSlotCount += 2;
4586
+ return {
4587
+ ...passProps,
4588
+ timestampQuerySet: this._timeProfilingQuerySet,
4589
+ beginTimestampIndex,
4590
+ endTimestampIndex: beginTimestampIndex + 1
4591
+ };
4592
+ }
4593
+ _supportsTimestampQueries() {
4594
+ return this.device.features.has("timestamp-query");
2986
4595
  }
2987
4596
  };
2988
4597
  var CommandEncoder = _CommandEncoder;
@@ -2991,7 +4600,8 @@ ${htmlLog}
2991
4600
  // beginComputePass(optional GPUComputePassDescriptor descriptor = {}): GPUComputePassEncoder;
2992
4601
  __publicField(CommandEncoder, "defaultProps", {
2993
4602
  ...Resource.defaultProps,
2994
- measureExecutionTime: void 0
4603
+ measureExecutionTime: void 0,
4604
+ timeProfilingQuerySet: void 0
2995
4605
  });
2996
4606
 
2997
4607
  // ../core/src/adapter/resources/command-buffer.ts
@@ -3008,13 +4618,22 @@ ${htmlLog}
3008
4618
  ...Resource.defaultProps
3009
4619
  });
3010
4620
 
3011
- // ../core/src/shadertypes/data-types/decode-shader-types.ts
4621
+ // ../core/src/shadertypes/shader-types/shader-type-decoder.ts
3012
4622
  function getVariableShaderTypeInfo(format) {
3013
- const decoded = UNIFORM_FORMATS[format];
4623
+ const resolvedFormat = resolveVariableShaderTypeAlias(format);
4624
+ const decoded = UNIFORM_FORMATS[resolvedFormat];
4625
+ if (!decoded) {
4626
+ throw new Error(`Unsupported variable shader type: ${format}`);
4627
+ }
3014
4628
  return decoded;
3015
4629
  }
3016
4630
  function getAttributeShaderTypeInfo(attributeType) {
3017
- const [primitiveType, components] = TYPE_INFO[attributeType];
4631
+ const resolvedAttributeType = resolveAttributeShaderTypeAlias(attributeType);
4632
+ const decoded = TYPE_INFO[resolvedAttributeType];
4633
+ if (!decoded) {
4634
+ throw new Error(`Unsupported attribute shader type: ${attributeType}`);
4635
+ }
4636
+ const [primitiveType, components] = decoded;
3018
4637
  const integer = primitiveType === "i32" || primitiveType === "u32";
3019
4638
  const signed = primitiveType !== "u32";
3020
4639
  const byteLength = PRIMITIVE_TYPE_SIZES[primitiveType] * components;
@@ -3026,6 +4645,33 @@ ${htmlLog}
3026
4645
  signed
3027
4646
  };
3028
4647
  }
4648
+ var ShaderTypeDecoder = class {
4649
+ getVariableShaderTypeInfo(format) {
4650
+ return getVariableShaderTypeInfo(format);
4651
+ }
4652
+ getAttributeShaderTypeInfo(attributeType) {
4653
+ return getAttributeShaderTypeInfo(attributeType);
4654
+ }
4655
+ makeShaderAttributeType(primitiveType, components) {
4656
+ return makeShaderAttributeType(primitiveType, components);
4657
+ }
4658
+ resolveAttributeShaderTypeAlias(alias) {
4659
+ return resolveAttributeShaderTypeAlias(alias);
4660
+ }
4661
+ resolveVariableShaderTypeAlias(alias) {
4662
+ return resolveVariableShaderTypeAlias(alias);
4663
+ }
4664
+ };
4665
+ function makeShaderAttributeType(primitiveType, components) {
4666
+ return components === 1 ? primitiveType : `vec${components}<${primitiveType}>`;
4667
+ }
4668
+ function resolveAttributeShaderTypeAlias(alias) {
4669
+ return WGSL_ATTRIBUTE_TYPE_ALIAS_MAP[alias] || alias;
4670
+ }
4671
+ function resolveVariableShaderTypeAlias(alias) {
4672
+ return WGSL_VARIABLE_TYPE_ALIAS_MAP[alias] || alias;
4673
+ }
4674
+ var shaderTypeDecoder = new ShaderTypeDecoder();
3029
4675
  var PRIMITIVE_TYPE_SIZES = {
3030
4676
  f32: 4,
3031
4677
  f16: 2,
@@ -3122,7 +4768,18 @@ ${htmlLog}
3122
4768
  vec4h: "vec4<f16>"
3123
4769
  };
3124
4770
  var WGSL_VARIABLE_TYPE_ALIAS_MAP = {
3125
- ...WGSL_ATTRIBUTE_TYPE_ALIAS_MAP,
4771
+ vec2i: "vec2<i32>",
4772
+ vec3i: "vec3<i32>",
4773
+ vec4i: "vec4<i32>",
4774
+ vec2u: "vec2<u32>",
4775
+ vec3u: "vec3<u32>",
4776
+ vec4u: "vec4<u32>",
4777
+ vec2f: "vec2<f32>",
4778
+ vec3f: "vec3<f32>",
4779
+ vec4f: "vec4<f32>",
4780
+ vec2h: "vec2<f16>",
4781
+ vec3h: "vec3<f16>",
4782
+ vec4h: "vec4<f16>",
3126
4783
  mat2x2f: "mat2x2<f32>",
3127
4784
  mat2x3f: "mat2x3<f32>",
3128
4785
  mat2x4f: "mat2x4<f32>",
@@ -3189,10 +4846,10 @@ ${htmlLog}
3189
4846
  if (!shaderDeclaration) {
3190
4847
  return null;
3191
4848
  }
3192
- const attributeTypeInfo = getAttributeShaderTypeInfo(shaderDeclaration.type);
3193
- const defaultVertexFormat = getCompatibleVertexFormat(attributeTypeInfo);
4849
+ const attributeTypeInfo = shaderTypeDecoder.getAttributeShaderTypeInfo(shaderDeclaration.type);
4850
+ const defaultVertexFormat = vertexFormatDecoder.getCompatibleVertexFormat(attributeTypeInfo);
3194
4851
  const vertexFormat = bufferMapping?.vertexFormat || defaultVertexFormat;
3195
- const vertexFormatInfo = getVertexFormatInfo(vertexFormat);
4852
+ const vertexFormatInfo = vertexFormatDecoder.getVertexFormatInfo(vertexFormat);
3196
4853
  return {
3197
4854
  attributeName: bufferMapping?.attributeName || shaderDeclaration.name,
3198
4855
  bufferName: bufferMapping?.bufferName || shaderDeclaration.name,
@@ -3260,7 +4917,7 @@ ${htmlLog}
3260
4917
  let byteStride = bufferLayout.byteStride;
3261
4918
  if (typeof bufferLayout.byteStride !== "number") {
3262
4919
  for (const attributeMapping2 of bufferLayout.attributes || []) {
3263
- const info = getVertexFormatInfo(attributeMapping2.format);
4920
+ const info = vertexFormatDecoder.getVertexFormatInfo(attributeMapping2.format);
3264
4921
  byteStride += info.byteLength;
3265
4922
  }
3266
4923
  }
@@ -3348,6 +5005,20 @@ ${htmlLog}
3348
5005
  count: void 0
3349
5006
  });
3350
5007
 
5008
+ // ../core/src/adapter/resources/fence.ts
5009
+ var _Fence = class extends Resource {
5010
+ get [Symbol.toStringTag]() {
5011
+ return "Fence";
5012
+ }
5013
+ constructor(device, props = {}) {
5014
+ super(device, props, _Fence.defaultProps);
5015
+ }
5016
+ };
5017
+ var Fence = _Fence;
5018
+ __publicField(Fence, "defaultProps", {
5019
+ ...Resource.defaultProps
5020
+ });
5021
+
3351
5022
  // ../core/src/adapter/resources/pipeline-layout.ts
3352
5023
  var _PipelineLayout = class extends Resource {
3353
5024
  get [Symbol.toStringTag]() {
@@ -3366,6 +5037,200 @@ ${htmlLog}
3366
5037
  }
3367
5038
  });
3368
5039
 
5040
+ // ../core/src/shadertypes/data-types/decode-data-types.ts
5041
+ function alignTo(size, count) {
5042
+ switch (count) {
5043
+ case 1:
5044
+ return size;
5045
+ case 2:
5046
+ return size + size % 2;
5047
+ default:
5048
+ return size + (4 - size % 4) % 4;
5049
+ }
5050
+ }
5051
+ function getTypedArrayConstructor(type) {
5052
+ const [, , , , Constructor] = NORMALIZED_TYPE_MAP2[type];
5053
+ return Constructor;
5054
+ }
5055
+ var NORMALIZED_TYPE_MAP2 = {
5056
+ uint8: ["uint8", "u32", 1, false, Uint8Array],
5057
+ sint8: ["sint8", "i32", 1, false, Int8Array],
5058
+ unorm8: ["uint8", "f32", 1, true, Uint8Array],
5059
+ snorm8: ["sint8", "f32", 1, true, Int8Array],
5060
+ uint16: ["uint16", "u32", 2, false, Uint16Array],
5061
+ sint16: ["sint16", "i32", 2, false, Int16Array],
5062
+ unorm16: ["uint16", "u32", 2, true, Uint16Array],
5063
+ snorm16: ["sint16", "i32", 2, true, Int16Array],
5064
+ float16: ["float16", "f16", 2, false, Uint16Array],
5065
+ float32: ["float32", "f32", 4, false, Float32Array],
5066
+ uint32: ["uint32", "u32", 4, false, Uint32Array],
5067
+ sint32: ["sint32", "i32", 4, false, Int32Array]
5068
+ };
5069
+
5070
+ // ../core/src/shadertypes/shader-types/shader-block-layout.ts
5071
+ function makeShaderBlockLayout(uniformTypes, options = {}) {
5072
+ const copiedUniformTypes = { ...uniformTypes };
5073
+ const layout = options.layout ?? "std140";
5074
+ const fields = {};
5075
+ let size = 0;
5076
+ for (const [key, uniformType] of Object.entries(copiedUniformTypes)) {
5077
+ size = addToLayout(fields, key, uniformType, size, layout);
5078
+ }
5079
+ size = alignTo(size, getTypeAlignment(copiedUniformTypes, layout));
5080
+ return {
5081
+ layout,
5082
+ byteLength: size * 4,
5083
+ uniformTypes: copiedUniformTypes,
5084
+ fields
5085
+ };
5086
+ }
5087
+ function getLeafLayoutInfo(type, layout) {
5088
+ const resolvedType = resolveVariableShaderTypeAlias(type);
5089
+ const decodedType = getVariableShaderTypeInfo(resolvedType);
5090
+ const matrixMatch = /^mat(\d)x(\d)<.+>$/.exec(resolvedType);
5091
+ if (matrixMatch) {
5092
+ const columns = Number(matrixMatch[1]);
5093
+ const rows = Number(matrixMatch[2]);
5094
+ const columnInfo = getVectorLayoutInfo(
5095
+ rows,
5096
+ resolvedType,
5097
+ decodedType.type,
5098
+ layout
5099
+ );
5100
+ const columnStride = getMatrixColumnStride(columnInfo.size, columnInfo.alignment, layout);
5101
+ return {
5102
+ alignment: columnInfo.alignment,
5103
+ size: columns * columnStride,
5104
+ components: columns * rows,
5105
+ columns,
5106
+ rows,
5107
+ columnStride,
5108
+ shaderType: resolvedType,
5109
+ type: decodedType.type
5110
+ };
5111
+ }
5112
+ const vectorMatch = /^vec(\d)<.+>$/.exec(resolvedType);
5113
+ if (vectorMatch) {
5114
+ return getVectorLayoutInfo(
5115
+ Number(vectorMatch[1]),
5116
+ resolvedType,
5117
+ decodedType.type,
5118
+ layout
5119
+ );
5120
+ }
5121
+ return {
5122
+ alignment: 1,
5123
+ size: 1,
5124
+ components: 1,
5125
+ columns: 1,
5126
+ rows: 1,
5127
+ columnStride: 1,
5128
+ shaderType: resolvedType,
5129
+ type: decodedType.type
5130
+ };
5131
+ }
5132
+ function isCompositeShaderTypeStruct(value) {
5133
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5134
+ }
5135
+ function addToLayout(fields, name2, type, offset, layout) {
5136
+ if (typeof type === "string") {
5137
+ const info = getLeafLayoutInfo(type, layout);
5138
+ const alignedOffset = alignTo(offset, info.alignment);
5139
+ fields[name2] = {
5140
+ offset: alignedOffset,
5141
+ ...info
5142
+ };
5143
+ return alignedOffset + info.size;
5144
+ }
5145
+ if (Array.isArray(type)) {
5146
+ if (Array.isArray(type[0])) {
5147
+ throw new Error(`Nested arrays are not supported for ${name2}`);
5148
+ }
5149
+ const elementType = type[0];
5150
+ const length = type[1];
5151
+ const stride = getArrayStride(elementType, layout);
5152
+ const arrayOffset = alignTo(offset, getTypeAlignment(type, layout));
5153
+ for (let i = 0; i < length; i++) {
5154
+ addToLayout(fields, `${name2}[${i}]`, elementType, arrayOffset + i * stride, layout);
5155
+ }
5156
+ return arrayOffset + stride * length;
5157
+ }
5158
+ if (isCompositeShaderTypeStruct(type)) {
5159
+ const structAlignment = getTypeAlignment(type, layout);
5160
+ let structOffset = alignTo(offset, structAlignment);
5161
+ for (const [memberName, memberType] of Object.entries(type)) {
5162
+ structOffset = addToLayout(fields, `${name2}.${memberName}`, memberType, structOffset, layout);
5163
+ }
5164
+ return alignTo(structOffset, structAlignment);
5165
+ }
5166
+ throw new Error(`Unsupported CompositeShaderType for ${name2}`);
5167
+ }
5168
+ function getTypeSize(type, layout) {
5169
+ if (typeof type === "string") {
5170
+ return getLeafLayoutInfo(type, layout).size;
5171
+ }
5172
+ if (Array.isArray(type)) {
5173
+ const elementType = type[0];
5174
+ const length = type[1];
5175
+ if (Array.isArray(elementType)) {
5176
+ throw new Error("Nested arrays are not supported");
5177
+ }
5178
+ return getArrayStride(elementType, layout) * length;
5179
+ }
5180
+ let size = 0;
5181
+ for (const memberType of Object.values(type)) {
5182
+ const compositeMemberType = memberType;
5183
+ size = alignTo(size, getTypeAlignment(compositeMemberType, layout));
5184
+ size += getTypeSize(compositeMemberType, layout);
5185
+ }
5186
+ return alignTo(size, getTypeAlignment(type, layout));
5187
+ }
5188
+ function getTypeAlignment(type, layout) {
5189
+ if (typeof type === "string") {
5190
+ return getLeafLayoutInfo(type, layout).alignment;
5191
+ }
5192
+ if (Array.isArray(type)) {
5193
+ const elementType = type[0];
5194
+ const elementAlignment = getTypeAlignment(elementType, layout);
5195
+ return uses16ByteArrayAlignment(layout) ? Math.max(elementAlignment, 4) : elementAlignment;
5196
+ }
5197
+ let maxAlignment = 1;
5198
+ for (const memberType of Object.values(type)) {
5199
+ const memberAlignment = getTypeAlignment(memberType, layout);
5200
+ maxAlignment = Math.max(maxAlignment, memberAlignment);
5201
+ }
5202
+ return uses16ByteStructAlignment(layout) ? Math.max(maxAlignment, 4) : maxAlignment;
5203
+ }
5204
+ function getVectorLayoutInfo(components, shaderType, type, layout) {
5205
+ return {
5206
+ alignment: components === 2 ? 2 : 4,
5207
+ size: components === 3 ? 3 : components,
5208
+ components,
5209
+ columns: 1,
5210
+ rows: components,
5211
+ columnStride: components === 3 ? 3 : components,
5212
+ shaderType,
5213
+ type
5214
+ };
5215
+ }
5216
+ function getArrayStride(elementType, layout) {
5217
+ const elementSize = getTypeSize(elementType, layout);
5218
+ const elementAlignment = getTypeAlignment(elementType, layout);
5219
+ return getArrayLikeStride(elementSize, elementAlignment, layout);
5220
+ }
5221
+ function getArrayLikeStride(size, alignment, layout) {
5222
+ return alignTo(size, uses16ByteArrayAlignment(layout) ? 4 : alignment);
5223
+ }
5224
+ function getMatrixColumnStride(size, alignment, layout) {
5225
+ return layout === "std140" ? 4 : alignTo(size, alignment);
5226
+ }
5227
+ function uses16ByteArrayAlignment(layout) {
5228
+ return layout === "std140" || layout === "wgsl-uniform";
5229
+ }
5230
+ function uses16ByteStructAlignment(layout) {
5231
+ return layout === "std140" || layout === "wgsl-uniform";
5232
+ }
5233
+
3369
5234
  // ../core/src/utils/array-utils-flat.ts
3370
5235
  var arrayBuffer;
3371
5236
  function getScratchArrayBuffer(byteLength) {
@@ -3390,92 +5255,201 @@ ${htmlLog}
3390
5255
  return isTypedArray(value);
3391
5256
  }
3392
5257
 
3393
- // ../core/src/portable/uniform-buffer-layout.ts
3394
- var minBufferSize = 1024;
3395
- var UniformBufferLayout = class {
3396
- layout = {};
3397
- /** number of bytes needed for buffer allocation */
3398
- byteLength;
3399
- /** Create a new UniformBufferLayout given a map of attributes. */
3400
- constructor(uniformTypes, uniformSizes = {}) {
3401
- let size = 0;
3402
- for (const [key, uniformType] of Object.entries(uniformTypes)) {
3403
- const typeAndComponents = getVariableShaderTypeInfo(uniformType);
3404
- const { type, components } = typeAndComponents;
3405
- const count = components * (uniformSizes?.[key] ?? 1);
3406
- size = alignTo(size, count);
3407
- const offset = size;
3408
- size += count;
3409
- this.layout[key] = { type, size: count, offset };
3410
- }
3411
- size += (4 - size % 4) % 4;
3412
- const actualByteLength = size * 4;
3413
- this.byteLength = Math.max(actualByteLength, minBufferSize);
3414
- }
3415
- /** Get the data for the complete buffer */
5258
+ // ../core/src/portable/shader-block-writer.ts
5259
+ var ShaderBlockWriter = class {
5260
+ /** Layout metadata used to flatten and serialize values. */
5261
+ layout;
5262
+ /**
5263
+ * Creates a writer for a precomputed shader-block layout.
5264
+ */
5265
+ constructor(layout) {
5266
+ this.layout = layout;
5267
+ }
5268
+ /**
5269
+ * Returns `true` if the flattened layout contains the given field.
5270
+ */
5271
+ has(name2) {
5272
+ return Boolean(this.layout.fields[name2]);
5273
+ }
5274
+ /**
5275
+ * Returns offset and size metadata for a flattened field.
5276
+ */
5277
+ get(name2) {
5278
+ const entry = this.layout.fields[name2];
5279
+ return entry ? { offset: entry.offset, size: entry.size } : void 0;
5280
+ }
5281
+ /**
5282
+ * Flattens nested composite values into leaf-path values understood by {@link UniformBlock}.
5283
+ *
5284
+ * Top-level values may be supplied either in nested object form matching the
5285
+ * declared composite shader types or as already-flattened leaf-path values.
5286
+ */
5287
+ getFlatUniformValues(uniformValues) {
5288
+ const flattenedUniformValues = {};
5289
+ for (const [name2, value] of Object.entries(uniformValues)) {
5290
+ const uniformType = this.layout.uniformTypes[name2];
5291
+ if (uniformType) {
5292
+ this._flattenCompositeValue(flattenedUniformValues, name2, uniformType, value);
5293
+ } else if (this.layout.fields[name2]) {
5294
+ flattenedUniformValues[name2] = value;
5295
+ }
5296
+ }
5297
+ return flattenedUniformValues;
5298
+ }
5299
+ /**
5300
+ * Serializes the supplied values into buffer-backed binary data.
5301
+ *
5302
+ * The returned view length matches {@link ShaderBlockLayout.byteLength}, which
5303
+ * is the exact packed size of the block.
5304
+ */
3416
5305
  getData(uniformValues) {
3417
- const arrayBuffer2 = getScratchArrayBuffer(this.byteLength);
5306
+ const buffer = getScratchArrayBuffer(this.layout.byteLength);
5307
+ new Uint8Array(buffer, 0, this.layout.byteLength).fill(0);
3418
5308
  const typedArrays = {
3419
- i32: new Int32Array(arrayBuffer2),
3420
- u32: new Uint32Array(arrayBuffer2),
3421
- f32: new Float32Array(arrayBuffer2),
3422
- // TODO not implemented
3423
- f16: new Uint16Array(arrayBuffer2)
5309
+ i32: new Int32Array(buffer),
5310
+ u32: new Uint32Array(buffer),
5311
+ f32: new Float32Array(buffer),
5312
+ f16: new Uint16Array(buffer)
3424
5313
  };
3425
- for (const [name2, value] of Object.entries(uniformValues)) {
3426
- const uniformLayout = this.layout[name2];
3427
- if (!uniformLayout) {
3428
- log.warn(`Supplied uniform value ${name2} not present in uniform block layout`)();
3429
- continue;
5314
+ const flattenedUniformValues = this.getFlatUniformValues(uniformValues);
5315
+ for (const [name2, value] of Object.entries(flattenedUniformValues)) {
5316
+ this._writeLeafValue(typedArrays, name2, value);
5317
+ }
5318
+ return new Uint8Array(buffer, 0, this.layout.byteLength);
5319
+ }
5320
+ /**
5321
+ * Recursively flattens nested values using the declared composite shader type.
5322
+ */
5323
+ _flattenCompositeValue(flattenedUniformValues, baseName, uniformType, value) {
5324
+ if (value === void 0) {
5325
+ return;
5326
+ }
5327
+ if (typeof uniformType === "string" || this.layout.fields[baseName]) {
5328
+ flattenedUniformValues[baseName] = value;
5329
+ return;
5330
+ }
5331
+ if (Array.isArray(uniformType)) {
5332
+ const elementType = uniformType[0];
5333
+ const length = uniformType[1];
5334
+ if (Array.isArray(elementType)) {
5335
+ throw new Error(`Nested arrays are not supported for ${baseName}`);
3430
5336
  }
3431
- const { type, size, offset } = uniformLayout;
3432
- const typedArray = typedArrays[type];
3433
- if (size === 1) {
3434
- if (typeof value !== "number" && typeof value !== "boolean") {
3435
- log.warn(
3436
- `Supplied value for single component uniform ${name2} is not a number: ${value}`
3437
- )();
5337
+ if (typeof elementType === "string" && isNumberArray(value)) {
5338
+ this._flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value);
5339
+ return;
5340
+ }
5341
+ if (!Array.isArray(value)) {
5342
+ log.warn(`Unsupported uniform array value for ${baseName}:`, value)();
5343
+ return;
5344
+ }
5345
+ for (let index = 0; index < Math.min(value.length, length); index++) {
5346
+ const elementValue = value[index];
5347
+ if (elementValue === void 0) {
3438
5348
  continue;
3439
5349
  }
3440
- typedArray[offset] = Number(value);
3441
- } else {
3442
- if (!isNumberArray(value)) {
3443
- log.warn(
3444
- `Supplied value for multi component / array uniform ${name2} is not a numeric array: ${value}`
3445
- )();
5350
+ this._flattenCompositeValue(
5351
+ flattenedUniformValues,
5352
+ `${baseName}[${index}]`,
5353
+ elementType,
5354
+ elementValue
5355
+ );
5356
+ }
5357
+ return;
5358
+ }
5359
+ if (isCompositeShaderTypeStruct(uniformType) && isCompositeUniformObject(value)) {
5360
+ for (const [key, subValue] of Object.entries(value)) {
5361
+ if (subValue === void 0) {
3446
5362
  continue;
3447
5363
  }
3448
- typedArray.set(value, offset);
5364
+ const nestedName = `${baseName}.${key}`;
5365
+ this._flattenCompositeValue(flattenedUniformValues, nestedName, uniformType[key], subValue);
3449
5366
  }
5367
+ return;
3450
5368
  }
3451
- return new Uint8Array(arrayBuffer2, 0, this.byteLength);
5369
+ log.warn(`Unsupported uniform value for ${baseName}:`, value)();
3452
5370
  }
3453
- /** Does this layout have a field with specified name */
3454
- has(name2) {
3455
- return Boolean(this.layout[name2]);
5371
+ /**
5372
+ * Expands tightly packed numeric arrays into per-element leaf fields.
5373
+ */
5374
+ _flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value) {
5375
+ const numericValue = value;
5376
+ const elementLayout = getLeafLayoutInfo(elementType, this.layout.layout);
5377
+ const packedElementLength = elementLayout.components;
5378
+ for (let index = 0; index < length; index++) {
5379
+ const start = index * packedElementLength;
5380
+ if (start >= numericValue.length) {
5381
+ break;
5382
+ }
5383
+ if (packedElementLength === 1) {
5384
+ flattenedUniformValues[`${baseName}[${index}]`] = Number(numericValue[start]);
5385
+ } else {
5386
+ flattenedUniformValues[`${baseName}[${index}]`] = sliceNumericArray(
5387
+ value,
5388
+ start,
5389
+ start + packedElementLength
5390
+ );
5391
+ }
5392
+ }
3456
5393
  }
3457
- /** Get offset and size for a field with specified name */
3458
- get(name2) {
3459
- const layout = this.layout[name2];
3460
- return layout;
5394
+ /**
5395
+ * Writes one flattened leaf value into its typed-array view.
5396
+ */
5397
+ _writeLeafValue(typedArrays, name2, value) {
5398
+ const entry = this.layout.fields[name2];
5399
+ if (!entry) {
5400
+ log.warn(`Uniform ${name2} not found in layout`)();
5401
+ return;
5402
+ }
5403
+ const { type, components, columns, rows, offset, columnStride } = entry;
5404
+ const array = typedArrays[type];
5405
+ if (components === 1) {
5406
+ array[offset] = Number(value);
5407
+ return;
5408
+ }
5409
+ const sourceValue = value;
5410
+ if (columns === 1) {
5411
+ for (let componentIndex = 0; componentIndex < components; componentIndex++) {
5412
+ array[offset + componentIndex] = Number(sourceValue[componentIndex] ?? 0);
5413
+ }
5414
+ return;
5415
+ }
5416
+ let sourceIndex = 0;
5417
+ for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
5418
+ const columnOffset = offset + columnIndex * columnStride;
5419
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
5420
+ array[columnOffset + rowIndex] = Number(sourceValue[sourceIndex++] ?? 0);
5421
+ }
5422
+ }
3461
5423
  }
3462
5424
  };
5425
+ function isCompositeUniformObject(value) {
5426
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && !ArrayBuffer.isView(value);
5427
+ }
5428
+ function sliceNumericArray(value, start, end) {
5429
+ return Array.prototype.slice.call(value, start, end);
5430
+ }
3463
5431
 
3464
5432
  // ../core/src/utils/array-equal.ts
5433
+ var MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH = 128;
3465
5434
  function arrayEqual(a, b, limit = 16) {
3466
- if (a !== b) {
3467
- return false;
5435
+ if (a === b) {
5436
+ return true;
3468
5437
  }
3469
5438
  const arrayA = a;
3470
5439
  const arrayB = b;
3471
- if (!isNumberArray(arrayA)) {
5440
+ if (!isNumberArray(arrayA) || !isNumberArray(arrayB)) {
3472
5441
  return false;
3473
5442
  }
3474
- if (isNumberArray(arrayB) && arrayA.length === arrayB.length) {
3475
- for (let i = 0; i < arrayA.length; ++i) {
3476
- if (arrayB[i] !== arrayA[i]) {
3477
- return false;
3478
- }
5443
+ if (arrayA.length !== arrayB.length) {
5444
+ return false;
5445
+ }
5446
+ const maxCompareLength = Math.min(limit, MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH);
5447
+ if (arrayA.length > maxCompareLength) {
5448
+ return false;
5449
+ }
5450
+ for (let i = 0; i < arrayA.length; ++i) {
5451
+ if (arrayB[i] !== arrayA[i]) {
5452
+ return false;
3479
5453
  }
3480
5454
  }
3481
5455
  return true;
@@ -3540,27 +5514,33 @@ ${htmlLog}
3540
5514
  };
3541
5515
 
3542
5516
  // ../core/src/portable/uniform-store.ts
5517
+ var minUniformBufferSize = 1024;
3543
5518
  var UniformStore = class {
5519
+ /** Device used to infer layout and allocate buffers. */
5520
+ device;
3544
5521
  /** Stores the uniform values for each uniform block */
3545
5522
  uniformBlocks = /* @__PURE__ */ new Map();
3546
- /** Can generate data for a uniform buffer for each block from data */
3547
- uniformBufferLayouts = /* @__PURE__ */ new Map();
5523
+ /** Flattened layout metadata for each block. */
5524
+ shaderBlockLayouts = /* @__PURE__ */ new Map();
5525
+ /** Serializers for block-backed uniform data. */
5526
+ shaderBlockWriters = /* @__PURE__ */ new Map();
3548
5527
  /** Actual buffer for the blocks */
3549
5528
  uniformBuffers = /* @__PURE__ */ new Map();
3550
5529
  /**
3551
- * Create a new UniformStore instance
3552
- * @param blocks
5530
+ * Creates a new {@link UniformStore} for the supplied device and block definitions.
3553
5531
  */
3554
- constructor(blocks) {
5532
+ constructor(device, blocks) {
5533
+ this.device = device;
3555
5534
  for (const [bufferName, block] of Object.entries(blocks)) {
3556
5535
  const uniformBufferName = bufferName;
3557
- const uniformBufferLayout = new UniformBufferLayout(
3558
- block.uniformTypes ?? {},
3559
- block.uniformSizes ?? {}
3560
- );
3561
- this.uniformBufferLayouts.set(uniformBufferName, uniformBufferLayout);
5536
+ const shaderBlockLayout = makeShaderBlockLayout(block.uniformTypes ?? {}, {
5537
+ layout: block.layout ?? getDefaultUniformBufferLayout(device)
5538
+ });
5539
+ const shaderBlockWriter = new ShaderBlockWriter(shaderBlockLayout);
5540
+ this.shaderBlockLayouts.set(uniformBufferName, shaderBlockLayout);
5541
+ this.shaderBlockWriters.set(uniformBufferName, shaderBlockWriter);
3562
5542
  const uniformBlock = new UniformBlock({ name: bufferName });
3563
- uniformBlock.setUniforms(block.defaultUniforms || {});
5543
+ uniformBlock.setUniforms(shaderBlockWriter.getFlatUniformValues(block.defaultUniforms || {}));
3564
5544
  this.uniformBlocks.set(uniformBufferName, uniformBlock);
3565
5545
  }
3566
5546
  }
@@ -3572,33 +5552,51 @@ ${htmlLog}
3572
5552
  }
3573
5553
  /**
3574
5554
  * Set uniforms
3575
- * Makes all properties partial
5555
+ *
5556
+ * Makes all group properties partial and eagerly propagates changes to any
5557
+ * managed GPU buffers.
3576
5558
  */
3577
5559
  setUniforms(uniforms) {
3578
5560
  for (const [blockName, uniformValues] of Object.entries(uniforms)) {
3579
- this.uniformBlocks.get(blockName)?.setUniforms(uniformValues);
5561
+ const uniformBufferName = blockName;
5562
+ const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
5563
+ const flattenedUniforms = shaderBlockWriter?.getFlatUniformValues(
5564
+ uniformValues || {}
5565
+ );
5566
+ this.uniformBlocks.get(uniformBufferName)?.setUniforms(flattenedUniforms || {});
3580
5567
  }
3581
5568
  this.updateUniformBuffers();
3582
5569
  }
3583
- /** Get the required minimum length of the uniform buffer */
5570
+ /**
5571
+ * Returns the allocation size for the named uniform buffer.
5572
+ *
5573
+ * This may exceed the packed layout size because minimum buffer-size policy is
5574
+ * applied at the store layer.
5575
+ */
3584
5576
  getUniformBufferByteLength(uniformBufferName) {
3585
- return this.uniformBufferLayouts.get(uniformBufferName)?.byteLength || 0;
5577
+ const packedByteLength = this.shaderBlockLayouts.get(uniformBufferName)?.byteLength || 0;
5578
+ return Math.max(packedByteLength, minUniformBufferSize);
3586
5579
  }
3587
- /** Get formatted binary memory that can be uploaded to a buffer */
5580
+ /**
5581
+ * Returns packed binary data that can be uploaded to the named uniform buffer.
5582
+ *
5583
+ * The returned view length matches the packed block size and is not padded to
5584
+ * the store's minimum allocation size.
5585
+ */
3588
5586
  getUniformBufferData(uniformBufferName) {
3589
5587
  const uniformValues = this.uniformBlocks.get(uniformBufferName)?.getAllUniforms() || {};
3590
- return this.uniformBufferLayouts.get(uniformBufferName)?.getData(uniformValues);
5588
+ const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
5589
+ return shaderBlockWriter?.getData(uniformValues) || new Uint8Array(0);
3591
5590
  }
3592
5591
  /**
3593
- * Creates an unmanaged uniform buffer (umnanaged means that application is responsible for destroying it)
3594
- * The new buffer is initialized with current / supplied values
5592
+ * Creates an unmanaged uniform buffer initialized with the current or supplied values.
3595
5593
  */
3596
- createUniformBuffer(device, uniformBufferName, uniforms) {
5594
+ createUniformBuffer(uniformBufferName, uniforms) {
3597
5595
  if (uniforms) {
3598
5596
  this.setUniforms(uniforms);
3599
5597
  }
3600
5598
  const byteLength = this.getUniformBufferByteLength(uniformBufferName);
3601
- const uniformBuffer = device.createBuffer({
5599
+ const uniformBuffer = this.device.createBuffer({
3602
5600
  usage: Buffer2.UNIFORM | Buffer2.COPY_DST,
3603
5601
  byteLength
3604
5602
  });
@@ -3606,11 +5604,11 @@ ${htmlLog}
3606
5604
  uniformBuffer.write(uniformBufferData);
3607
5605
  return uniformBuffer;
3608
5606
  }
3609
- /** Get the managed uniform buffer. "managed" resources are destroyed when the uniformStore is destroyed. */
3610
- getManagedUniformBuffer(device, uniformBufferName) {
5607
+ /** Returns the managed uniform buffer for the named block. */
5608
+ getManagedUniformBuffer(uniformBufferName) {
3611
5609
  if (!this.uniformBuffers.get(uniformBufferName)) {
3612
5610
  const byteLength = this.getUniformBufferByteLength(uniformBufferName);
3613
- const uniformBuffer = device.createBuffer({
5611
+ const uniformBuffer = this.device.createBuffer({
3614
5612
  usage: Buffer2.UNIFORM | Buffer2.COPY_DST,
3615
5613
  byteLength
3616
5614
  });
@@ -3618,7 +5616,11 @@ ${htmlLog}
3618
5616
  }
3619
5617
  return this.uniformBuffers.get(uniformBufferName);
3620
5618
  }
3621
- /** Updates all uniform buffers where values have changed */
5619
+ /**
5620
+ * Updates every managed uniform buffer whose source uniforms have changed.
5621
+ *
5622
+ * @returns The first redraw reason encountered, or `false` if nothing changed.
5623
+ */
3622
5624
  updateUniformBuffers() {
3623
5625
  let reason = false;
3624
5626
  for (const uniformBufferName of this.uniformBlocks.keys()) {
@@ -3630,7 +5632,11 @@ ${htmlLog}
3630
5632
  }
3631
5633
  return reason;
3632
5634
  }
3633
- /** Update one uniform buffer. Only updates if values have changed */
5635
+ /**
5636
+ * Updates one managed uniform buffer if its corresponding block is dirty.
5637
+ *
5638
+ * @returns The redraw reason for the update, or `false` if no write occurred.
5639
+ */
3634
5640
  updateUniformBuffer(uniformBufferName) {
3635
5641
  const uniformBlock = this.uniformBlocks.get(uniformBufferName);
3636
5642
  let uniformBuffer = this.uniformBuffers.get(uniformBufferName);
@@ -3651,8 +5657,49 @@ ${htmlLog}
3651
5657
  return reason;
3652
5658
  }
3653
5659
  };
5660
+ function getDefaultUniformBufferLayout(device) {
5661
+ return device.type === "webgpu" ? "wgsl-uniform" : "std140";
5662
+ }
5663
+
5664
+ // ../core/src/shadertypes/texture-types/texture-layout.ts
5665
+ function getTextureImageView(arrayBuffer2, memoryLayout, format, image = 0) {
5666
+ const formatInfo = textureFormatDecoder.getInfo(format);
5667
+ const bytesPerComponent = formatInfo.bytesPerPixel / formatInfo.components;
5668
+ const { bytesPerImage } = memoryLayout;
5669
+ const offset = bytesPerImage * image;
5670
+ const totalPixels = memoryLayout.bytesPerImage / bytesPerComponent;
5671
+ switch (format) {
5672
+ case "rgba8unorm":
5673
+ case "bgra8unorm":
5674
+ case "rgba8uint":
5675
+ return new Uint8Array(arrayBuffer2, offset, totalPixels);
5676
+ case "r8unorm":
5677
+ return new Uint8Array(arrayBuffer2, offset, totalPixels);
5678
+ case "r16uint":
5679
+ case "rgba16uint":
5680
+ return new Uint16Array(arrayBuffer2, offset, totalPixels);
5681
+ case "r32uint":
5682
+ case "rgba32uint":
5683
+ return new Uint32Array(arrayBuffer2, offset, totalPixels);
5684
+ case "r32float":
5685
+ return new Float32Array(arrayBuffer2, offset, totalPixels);
5686
+ case "rgba16float":
5687
+ return new Uint16Array(arrayBuffer2, offset, totalPixels);
5688
+ case "rgba32float":
5689
+ return new Float32Array(arrayBuffer2, offset, totalPixels);
5690
+ default:
5691
+ throw new Error(`Unsupported format: ${format}`);
5692
+ }
5693
+ }
5694
+ function setTextureImageData(arrayBuffer2, memoryLayout, format, data, image = 0) {
5695
+ const offset = 0;
5696
+ const totalPixels = memoryLayout.bytesPerImage / memoryLayout.bytesPerPixel;
5697
+ const subArray = data.subarray(0, totalPixels);
5698
+ const typedArray = getTextureImageView(arrayBuffer2, memoryLayout, format, image);
5699
+ typedArray.set(subArray, offset);
5700
+ }
3654
5701
 
3655
- // ../core/src/shadertypes/textures/pixel-utils.ts
5702
+ // ../core/src/shadertypes/texture-types/pixel-utils.ts
3656
5703
  function readPixel(pixelData, x, y, bitsPerChannel) {
3657
5704
  if (x < 0 || x >= pixelData.width || y < 0 || y >= pixelData.height) {
3658
5705
  throw new Error("Coordinates out of bounds.");
@@ -3662,7 +5709,7 @@ ${htmlLog}
3662
5709
  let bitOffsetWithinPixel = 0;
3663
5710
  const channels = [];
3664
5711
  for (let i = 0; i < 4; i++) {
3665
- const bits = bitsPerChannel[i];
5712
+ const bits = bitsPerChannel[i] ?? 0;
3666
5713
  if (bits <= 0) {
3667
5714
  channels.push(0);
3668
5715
  } else {
@@ -3671,14 +5718,14 @@ ${htmlLog}
3671
5718
  bitOffsetWithinPixel += bits;
3672
5719
  }
3673
5720
  }
3674
- return [channels[0], channels[1], channels[2], channels[3]];
5721
+ return [channels[0] ?? 0, channels[1] ?? 0, channels[2] ?? 0, channels[3] ?? 0];
3675
5722
  }
3676
5723
  function writePixel(dataView, bitOffset, bitsPerChannel, pixel) {
3677
5724
  let currentBitOffset = bitOffset;
3678
5725
  for (let channel = 0; channel < 4; channel++) {
3679
- const bits = bitsPerChannel[channel];
5726
+ const bits = bitsPerChannel[channel] ?? 0;
3680
5727
  const maxValue = (1 << bits) - 1;
3681
- const channelValue = pixel[channel] & maxValue;
5728
+ const channelValue = (pixel[channel] ?? 0) & maxValue;
3682
5729
  writeBitsToDataView(dataView, currentBitOffset, bits, channelValue);
3683
5730
  currentBitOffset += bits;
3684
5731
  }