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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/dist.dev.js +952 -193
  2. package/dist/dist.min.js +6 -6
  3. package/dist/index.cjs +419 -258
  4. package/dist/index.cjs.map +2 -2
  5. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.d.ts +1 -1
  6. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.d.ts.map +1 -1
  7. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.js +5 -4
  8. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.js.map +1 -1
  9. package/dist/passes/postprocessing/image-adjust-filters/denoise.d.ts +1 -1
  10. package/dist/passes/postprocessing/image-adjust-filters/denoise.d.ts.map +1 -1
  11. package/dist/passes/postprocessing/image-adjust-filters/denoise.js +29 -21
  12. package/dist/passes/postprocessing/image-adjust-filters/denoise.js.map +1 -1
  13. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.d.ts +1 -1
  14. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.d.ts.map +1 -1
  15. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.js +26 -32
  16. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.js.map +1 -1
  17. package/dist/passes/postprocessing/image-adjust-filters/noise.d.ts +1 -1
  18. package/dist/passes/postprocessing/image-adjust-filters/noise.d.ts.map +1 -1
  19. package/dist/passes/postprocessing/image-adjust-filters/noise.js +8 -7
  20. package/dist/passes/postprocessing/image-adjust-filters/noise.js.map +1 -1
  21. package/dist/passes/postprocessing/image-adjust-filters/sepia.d.ts +1 -1
  22. package/dist/passes/postprocessing/image-adjust-filters/sepia.d.ts.map +1 -1
  23. package/dist/passes/postprocessing/image-adjust-filters/sepia.js +10 -9
  24. package/dist/passes/postprocessing/image-adjust-filters/sepia.js.map +1 -1
  25. package/dist/passes/postprocessing/image-adjust-filters/vibrance.d.ts +1 -1
  26. package/dist/passes/postprocessing/image-adjust-filters/vibrance.d.ts.map +1 -1
  27. package/dist/passes/postprocessing/image-adjust-filters/vibrance.js +9 -8
  28. package/dist/passes/postprocessing/image-adjust-filters/vibrance.js.map +1 -1
  29. package/dist/passes/postprocessing/image-adjust-filters/vignette.d.ts +1 -1
  30. package/dist/passes/postprocessing/image-adjust-filters/vignette.d.ts.map +1 -1
  31. package/dist/passes/postprocessing/image-adjust-filters/vignette.js +9 -13
  32. package/dist/passes/postprocessing/image-adjust-filters/vignette.js.map +1 -1
  33. package/dist/passes/postprocessing/image-blur-filters/tiltshift.d.ts +2 -2
  34. package/dist/passes/postprocessing/image-blur-filters/tiltshift.d.ts.map +1 -1
  35. package/dist/passes/postprocessing/image-blur-filters/tiltshift.js +34 -16
  36. package/dist/passes/postprocessing/image-blur-filters/tiltshift.js.map +1 -1
  37. package/dist/passes/postprocessing/image-blur-filters/triangleblur.d.ts +2 -2
  38. package/dist/passes/postprocessing/image-blur-filters/triangleblur.d.ts.map +1 -1
  39. package/dist/passes/postprocessing/image-blur-filters/triangleblur.js +21 -12
  40. package/dist/passes/postprocessing/image-blur-filters/triangleblur.js.map +1 -1
  41. package/dist/passes/postprocessing/image-blur-filters/zoomblur.d.ts +2 -2
  42. package/dist/passes/postprocessing/image-blur-filters/zoomblur.d.ts.map +1 -1
  43. package/dist/passes/postprocessing/image-blur-filters/zoomblur.js +20 -11
  44. package/dist/passes/postprocessing/image-blur-filters/zoomblur.js.map +1 -1
  45. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.d.ts +1 -1
  46. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.d.ts.map +1 -1
  47. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.js +18 -16
  48. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.js.map +1 -1
  49. package/dist/passes/postprocessing/image-fun-filters/dotscreen.d.ts +1 -1
  50. package/dist/passes/postprocessing/image-fun-filters/dotscreen.js +11 -11
  51. package/dist/passes/postprocessing/image-fun-filters/edgework.d.ts +2 -2
  52. package/dist/passes/postprocessing/image-fun-filters/edgework.d.ts.map +1 -1
  53. package/dist/passes/postprocessing/image-fun-filters/edgework.js +83 -12
  54. package/dist/passes/postprocessing/image-fun-filters/edgework.js.map +1 -1
  55. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.d.ts +1 -1
  56. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.d.ts.map +1 -1
  57. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.js +33 -21
  58. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.js.map +1 -1
  59. package/dist/passes/postprocessing/image-fun-filters/ink.d.ts +1 -1
  60. package/dist/passes/postprocessing/image-fun-filters/ink.d.ts.map +1 -1
  61. package/dist/passes/postprocessing/image-fun-filters/ink.js +24 -15
  62. package/dist/passes/postprocessing/image-fun-filters/ink.js.map +1 -1
  63. package/dist/passes/postprocessing/image-fun-filters/magnify.d.ts +1 -1
  64. package/dist/passes/postprocessing/image-fun-filters/magnify.d.ts.map +1 -1
  65. package/dist/passes/postprocessing/image-fun-filters/magnify.js +16 -11
  66. package/dist/passes/postprocessing/image-fun-filters/magnify.js.map +1 -1
  67. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.d.ts +2 -2
  68. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.d.ts.map +1 -1
  69. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.js +24 -10
  70. package/dist/passes/postprocessing/image-warp-filters/bulgepinch.js.map +1 -1
  71. package/dist/passes/postprocessing/image-warp-filters/swirl.d.ts +2 -2
  72. package/dist/passes/postprocessing/image-warp-filters/swirl.d.ts.map +1 -1
  73. package/dist/passes/postprocessing/image-warp-filters/swirl.js +18 -13
  74. package/dist/passes/postprocessing/image-warp-filters/swirl.js.map +1 -1
  75. package/dist/passes/postprocessing/image-warp-filters/warp.d.ts +1 -1
  76. package/dist/passes/postprocessing/image-warp-filters/warp.d.ts.map +1 -1
  77. package/dist/passes/postprocessing/image-warp-filters/warp.js +9 -4
  78. package/dist/passes/postprocessing/image-warp-filters/warp.js.map +1 -1
  79. package/package.json +2 -2
  80. package/src/passes/postprocessing/image-adjust-filters/brightnesscontrast.ts +5 -4
  81. package/src/passes/postprocessing/image-adjust-filters/denoise.ts +31 -23
  82. package/src/passes/postprocessing/image-adjust-filters/huesaturation.ts +28 -34
  83. package/src/passes/postprocessing/image-adjust-filters/noise.ts +8 -7
  84. package/src/passes/postprocessing/image-adjust-filters/sepia.ts +10 -9
  85. package/src/passes/postprocessing/image-adjust-filters/vibrance.ts +9 -8
  86. package/src/passes/postprocessing/image-adjust-filters/vignette.ts +9 -13
  87. package/src/passes/postprocessing/image-blur-filters/tiltshift.ts +36 -18
  88. package/src/passes/postprocessing/image-blur-filters/triangleblur.ts +21 -12
  89. package/src/passes/postprocessing/image-blur-filters/zoomblur.ts +21 -12
  90. package/src/passes/postprocessing/image-fun-filters/colorhalftone.ts +18 -16
  91. package/src/passes/postprocessing/image-fun-filters/dotscreen.ts +11 -11
  92. package/src/passes/postprocessing/image-fun-filters/edgework.ts +84 -13
  93. package/src/passes/postprocessing/image-fun-filters/hexagonalpixelate.ts +36 -24
  94. package/src/passes/postprocessing/image-fun-filters/ink.ts +24 -15
  95. package/src/passes/postprocessing/image-fun-filters/magnify.ts +16 -11
  96. package/src/passes/postprocessing/image-warp-filters/bulgepinch.ts +24 -10
  97. package/src/passes/postprocessing/image-warp-filters/swirl.ts +18 -13
  98. package/src/passes/postprocessing/image-warp-filters/warp.ts +9 -4
package/dist/dist.dev.js CHANGED
@@ -67,12 +67,14 @@ var __exports__ = (() => {
67
67
  Fence: () => Fence,
68
68
  Framebuffer: () => Framebuffer,
69
69
  PipelineLayout: () => PipelineLayout,
70
+ PresentationContext: () => PresentationContext,
70
71
  QuerySet: () => QuerySet,
71
72
  RenderPass: () => RenderPass,
72
73
  RenderPipeline: () => RenderPipeline,
73
74
  Resource: () => Resource,
74
75
  Sampler: () => Sampler,
75
76
  Shader: () => Shader,
77
+ SharedRenderPipeline: () => SharedRenderPipeline,
76
78
  Texture: () => Texture,
77
79
  TextureFormatDecoder: () => TextureFormatDecoder,
78
80
  TextureView: () => TextureView,
@@ -301,6 +303,24 @@ var __exports__ = (() => {
301
303
  };
302
304
 
303
305
  // ../core/src/utils/stats-manager.ts
306
+ var GPU_TIME_AND_MEMORY_STATS = "GPU Time and Memory";
307
+ var GPU_TIME_AND_MEMORY_STAT_ORDER = [
308
+ "Adapter",
309
+ "GPU",
310
+ "GPU Type",
311
+ "GPU Backend",
312
+ "Frame Rate",
313
+ "CPU Time",
314
+ "GPU Time",
315
+ "GPU Memory",
316
+ "Buffer Memory",
317
+ "Texture Memory",
318
+ "Referenced Buffer Memory",
319
+ "Referenced Texture Memory",
320
+ "Swap Chain Texture"
321
+ ];
322
+ var ORDERED_STATS_CACHE = /* @__PURE__ */ new WeakMap();
323
+ var ORDERED_STAT_NAME_SET_CACHE = /* @__PURE__ */ new WeakMap();
304
324
  var StatsManager = class {
305
325
  stats = /* @__PURE__ */ new Map();
306
326
  getStats(name2) {
@@ -310,10 +330,50 @@ var __exports__ = (() => {
310
330
  if (!this.stats.has(name2)) {
311
331
  this.stats.set(name2, new Stats({ id: name2 }));
312
332
  }
313
- return this.stats.get(name2);
333
+ const stats = this.stats.get(name2);
334
+ if (name2 === GPU_TIME_AND_MEMORY_STATS) {
335
+ initializeStats(stats, GPU_TIME_AND_MEMORY_STAT_ORDER);
336
+ }
337
+ return stats;
314
338
  }
315
339
  };
316
340
  var lumaStats = new StatsManager();
341
+ function initializeStats(stats, orderedStatNames) {
342
+ const statsMap = stats.stats;
343
+ let addedOrderedStat = false;
344
+ for (const statName of orderedStatNames) {
345
+ if (!statsMap[statName]) {
346
+ stats.get(statName);
347
+ addedOrderedStat = true;
348
+ }
349
+ }
350
+ const statCount = Object.keys(statsMap).length;
351
+ const cachedStats = ORDERED_STATS_CACHE.get(stats);
352
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
353
+ return;
354
+ }
355
+ const reorderedStats = {};
356
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE.get(orderedStatNames);
357
+ if (!orderedStatNamesSet) {
358
+ orderedStatNamesSet = new Set(orderedStatNames);
359
+ ORDERED_STAT_NAME_SET_CACHE.set(orderedStatNames, orderedStatNamesSet);
360
+ }
361
+ for (const statName of orderedStatNames) {
362
+ if (statsMap[statName]) {
363
+ reorderedStats[statName] = statsMap[statName];
364
+ }
365
+ }
366
+ for (const [statName, stat] of Object.entries(statsMap)) {
367
+ if (!orderedStatNamesSet.has(statName)) {
368
+ reorderedStats[statName] = stat;
369
+ }
370
+ }
371
+ for (const statName of Object.keys(statsMap)) {
372
+ delete statsMap[statName];
373
+ }
374
+ Object.assign(statsMap, reorderedStats);
375
+ ORDERED_STATS_CACHE.set(stats, { orderedStatNames, statCount });
376
+ }
317
377
 
318
378
  // ../../node_modules/@probe.gl/env/dist/lib/globals.js
319
379
  var window_ = globalThis;
@@ -848,6 +908,57 @@ var __exports__ = (() => {
848
908
  }
849
909
 
850
910
  // ../core/src/adapter/resources/resource.ts
911
+ var CPU_HOTSPOT_PROFILER_MODULE = "cpu-hotspot-profiler";
912
+ var RESOURCE_COUNTS_STATS = "GPU Resource Counts";
913
+ var LEGACY_RESOURCE_COUNTS_STATS = "Resource Counts";
914
+ var GPU_TIME_AND_MEMORY_STATS2 = "GPU Time and Memory";
915
+ var BASE_RESOURCE_COUNT_ORDER = [
916
+ "Resources",
917
+ "Buffers",
918
+ "Textures",
919
+ "Samplers",
920
+ "TextureViews",
921
+ "Framebuffers",
922
+ "QuerySets",
923
+ "Shaders",
924
+ "RenderPipelines",
925
+ "ComputePipelines",
926
+ "PipelineLayouts",
927
+ "VertexArrays",
928
+ "RenderPasss",
929
+ "ComputePasss",
930
+ "CommandEncoders",
931
+ "CommandBuffers"
932
+ ];
933
+ var WEBGL_RESOURCE_COUNT_ORDER = [
934
+ "Resources",
935
+ "Buffers",
936
+ "Textures",
937
+ "Samplers",
938
+ "TextureViews",
939
+ "Framebuffers",
940
+ "QuerySets",
941
+ "Shaders",
942
+ "RenderPipelines",
943
+ "SharedRenderPipelines",
944
+ "ComputePipelines",
945
+ "PipelineLayouts",
946
+ "VertexArrays",
947
+ "RenderPasss",
948
+ "ComputePasss",
949
+ "CommandEncoders",
950
+ "CommandBuffers"
951
+ ];
952
+ var BASE_RESOURCE_COUNT_STAT_ORDER = BASE_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
953
+ `${resourceType} Created`,
954
+ `${resourceType} Active`
955
+ ]);
956
+ var WEBGL_RESOURCE_COUNT_STAT_ORDER = WEBGL_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
957
+ `${resourceType} Created`,
958
+ `${resourceType} Active`
959
+ ]);
960
+ var ORDERED_STATS_CACHE2 = /* @__PURE__ */ new WeakMap();
961
+ var ORDERED_STAT_NAME_SET_CACHE2 = /* @__PURE__ */ new WeakMap();
851
962
  var Resource = class {
852
963
  toString() {
853
964
  return `${this[Symbol.toStringTag] || this.constructor.name}:"${this.id}"`;
@@ -864,6 +975,8 @@ var __exports__ = (() => {
864
975
  destroyed = false;
865
976
  /** For resources that allocate GPU memory */
866
977
  allocatedBytes = 0;
978
+ /** Stats bucket currently holding the tracked allocation */
979
+ allocatedBytesName = null;
867
980
  /** Attached resources will be destroyed when this resource is destroyed. Tracks auto-created "sub" resources. */
868
981
  _attachedResources = /* @__PURE__ */ new Set();
869
982
  /**
@@ -885,6 +998,9 @@ var __exports__ = (() => {
885
998
  * destroy can be called on any resource to release it before it is garbage collected.
886
999
  */
887
1000
  destroy() {
1001
+ if (this.destroyed) {
1002
+ return;
1003
+ }
888
1004
  this.destroyResource();
889
1005
  }
890
1006
  /** @deprecated Use destroy() */
@@ -923,7 +1039,7 @@ var __exports__ = (() => {
923
1039
  }
924
1040
  /** Destroy all owned resources. Make sure the resources are no longer needed before calling. */
925
1041
  destroyAttachedResources() {
926
- for (const resource of Object.values(this._attachedResources)) {
1042
+ for (const resource of this._attachedResources) {
927
1043
  resource.destroy();
928
1044
  }
929
1045
  this._attachedResources = /* @__PURE__ */ new Set();
@@ -931,37 +1047,107 @@ var __exports__ = (() => {
931
1047
  // PROTECTED METHODS
932
1048
  /** Perform all destroy steps. Can be called by derived resources when overriding destroy() */
933
1049
  destroyResource() {
1050
+ if (this.destroyed) {
1051
+ return;
1052
+ }
934
1053
  this.destroyAttachedResources();
935
1054
  this.removeStats();
936
1055
  this.destroyed = true;
937
1056
  }
938
1057
  /** Called by .destroy() to track object destruction. Subclass must call if overriding destroy() */
939
1058
  removeStats() {
940
- const stats = this._device.statsManager.getStats("Resource Counts");
941
- const name2 = this[Symbol.toStringTag];
942
- stats.get(`${name2}s Active`).decrementCount();
1059
+ const profiler = getCpuHotspotProfiler(this._device);
1060
+ const startTime = profiler ? getTimestamp() : 0;
1061
+ const statsObjects = [
1062
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1063
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1064
+ ];
1065
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1066
+ for (const stats of statsObjects) {
1067
+ initializeStats2(stats, orderedStatNames);
1068
+ }
1069
+ const name2 = this.getStatsName();
1070
+ for (const stats of statsObjects) {
1071
+ stats.get("Resources Active").decrementCount();
1072
+ stats.get(`${name2}s Active`).decrementCount();
1073
+ }
1074
+ if (profiler) {
1075
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1076
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1077
+ }
943
1078
  }
944
1079
  /** Called by subclass to track memory allocations */
945
- trackAllocatedMemory(bytes, name2 = this[Symbol.toStringTag]) {
946
- const stats = this._device.statsManager.getStats("Resource Counts");
1080
+ trackAllocatedMemory(bytes, name2 = this.getStatsName()) {
1081
+ const profiler = getCpuHotspotProfiler(this._device);
1082
+ const startTime = profiler ? getTimestamp() : 0;
1083
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
1084
+ if (this.allocatedBytes > 0 && this.allocatedBytesName) {
1085
+ stats.get("GPU Memory").subtractCount(this.allocatedBytes);
1086
+ stats.get(`${this.allocatedBytesName} Memory`).subtractCount(this.allocatedBytes);
1087
+ }
947
1088
  stats.get("GPU Memory").addCount(bytes);
948
1089
  stats.get(`${name2} Memory`).addCount(bytes);
1090
+ if (profiler) {
1091
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1092
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1093
+ }
949
1094
  this.allocatedBytes = bytes;
1095
+ this.allocatedBytesName = name2;
1096
+ }
1097
+ /** Called by subclass to track handle-backed memory allocations separately from owned allocations */
1098
+ trackReferencedMemory(bytes, name2 = this.getStatsName()) {
1099
+ this.trackAllocatedMemory(bytes, `Referenced ${name2}`);
950
1100
  }
951
1101
  /** Called by subclass to track memory deallocations */
952
- trackDeallocatedMemory(name2 = this[Symbol.toStringTag]) {
953
- const stats = this._device.statsManager.getStats("Resource Counts");
1102
+ trackDeallocatedMemory(name2 = this.getStatsName()) {
1103
+ if (this.allocatedBytes === 0) {
1104
+ this.allocatedBytesName = null;
1105
+ return;
1106
+ }
1107
+ const profiler = getCpuHotspotProfiler(this._device);
1108
+ const startTime = profiler ? getTimestamp() : 0;
1109
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
954
1110
  stats.get("GPU Memory").subtractCount(this.allocatedBytes);
955
- stats.get(`${name2} Memory`).subtractCount(this.allocatedBytes);
1111
+ stats.get(`${this.allocatedBytesName || name2} Memory`).subtractCount(this.allocatedBytes);
1112
+ if (profiler) {
1113
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1114
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1115
+ }
956
1116
  this.allocatedBytes = 0;
1117
+ this.allocatedBytesName = null;
1118
+ }
1119
+ /** Called by subclass to deallocate handle-backed memory tracked via trackReferencedMemory() */
1120
+ trackDeallocatedReferencedMemory(name2 = this.getStatsName()) {
1121
+ this.trackDeallocatedMemory(`Referenced ${name2}`);
957
1122
  }
958
1123
  /** Called by resource constructor to track object creation */
959
1124
  addStats() {
960
- const stats = this._device.statsManager.getStats("Resource Counts");
961
- const name2 = this[Symbol.toStringTag];
962
- stats.get("Resources Created").incrementCount();
963
- stats.get(`${name2}s Created`).incrementCount();
964
- stats.get(`${name2}s Active`).incrementCount();
1125
+ const name2 = this.getStatsName();
1126
+ const profiler = getCpuHotspotProfiler(this._device);
1127
+ const startTime = profiler ? getTimestamp() : 0;
1128
+ const statsObjects = [
1129
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1130
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1131
+ ];
1132
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1133
+ for (const stats of statsObjects) {
1134
+ initializeStats2(stats, orderedStatNames);
1135
+ }
1136
+ for (const stats of statsObjects) {
1137
+ stats.get("Resources Created").incrementCount();
1138
+ stats.get("Resources Active").incrementCount();
1139
+ stats.get(`${name2}s Created`).incrementCount();
1140
+ stats.get(`${name2}s Active`).incrementCount();
1141
+ }
1142
+ if (profiler) {
1143
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1144
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1145
+ }
1146
+ recordTransientCanvasResourceCreate(this._device, name2);
1147
+ }
1148
+ /** Canonical resource name used for stats buckets. */
1149
+ getStatsName() {
1150
+ return getCanonicalResourceName(this);
965
1151
  }
966
1152
  };
967
1153
  /** Default properties for resource */
@@ -979,6 +1165,96 @@ var __exports__ = (() => {
979
1165
  }
980
1166
  return mergedProps;
981
1167
  }
1168
+ function initializeStats2(stats, orderedStatNames) {
1169
+ const statsMap = stats.stats;
1170
+ let addedOrderedStat = false;
1171
+ for (const statName of orderedStatNames) {
1172
+ if (!statsMap[statName]) {
1173
+ stats.get(statName);
1174
+ addedOrderedStat = true;
1175
+ }
1176
+ }
1177
+ const statCount = Object.keys(statsMap).length;
1178
+ const cachedStats = ORDERED_STATS_CACHE2.get(stats);
1179
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
1180
+ return;
1181
+ }
1182
+ const reorderedStats = {};
1183
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE2.get(orderedStatNames);
1184
+ if (!orderedStatNamesSet) {
1185
+ orderedStatNamesSet = new Set(orderedStatNames);
1186
+ ORDERED_STAT_NAME_SET_CACHE2.set(orderedStatNames, orderedStatNamesSet);
1187
+ }
1188
+ for (const statName of orderedStatNames) {
1189
+ if (statsMap[statName]) {
1190
+ reorderedStats[statName] = statsMap[statName];
1191
+ }
1192
+ }
1193
+ for (const [statName, stat] of Object.entries(statsMap)) {
1194
+ if (!orderedStatNamesSet.has(statName)) {
1195
+ reorderedStats[statName] = stat;
1196
+ }
1197
+ }
1198
+ for (const statName of Object.keys(statsMap)) {
1199
+ delete statsMap[statName];
1200
+ }
1201
+ Object.assign(statsMap, reorderedStats);
1202
+ ORDERED_STATS_CACHE2.set(stats, { orderedStatNames, statCount });
1203
+ }
1204
+ function getResourceCountStatOrder(device) {
1205
+ return device.type === "webgl" ? WEBGL_RESOURCE_COUNT_STAT_ORDER : BASE_RESOURCE_COUNT_STAT_ORDER;
1206
+ }
1207
+ function getCpuHotspotProfiler(device) {
1208
+ const profiler = device.userData[CPU_HOTSPOT_PROFILER_MODULE];
1209
+ return profiler?.enabled ? profiler : null;
1210
+ }
1211
+ function getTimestamp() {
1212
+ return globalThis.performance?.now?.() ?? Date.now();
1213
+ }
1214
+ function recordTransientCanvasResourceCreate(device, name2) {
1215
+ const profiler = getCpuHotspotProfiler(device);
1216
+ if (!profiler || !profiler.activeDefaultFramebufferAcquireDepth) {
1217
+ return;
1218
+ }
1219
+ profiler.transientCanvasResourceCreates = (profiler.transientCanvasResourceCreates || 0) + 1;
1220
+ switch (name2) {
1221
+ case "Texture":
1222
+ profiler.transientCanvasTextureCreates = (profiler.transientCanvasTextureCreates || 0) + 1;
1223
+ break;
1224
+ case "TextureView":
1225
+ profiler.transientCanvasTextureViewCreates = (profiler.transientCanvasTextureViewCreates || 0) + 1;
1226
+ break;
1227
+ case "Sampler":
1228
+ profiler.transientCanvasSamplerCreates = (profiler.transientCanvasSamplerCreates || 0) + 1;
1229
+ break;
1230
+ case "Framebuffer":
1231
+ profiler.transientCanvasFramebufferCreates = (profiler.transientCanvasFramebufferCreates || 0) + 1;
1232
+ break;
1233
+ default:
1234
+ break;
1235
+ }
1236
+ }
1237
+ function getCanonicalResourceName(resource) {
1238
+ let prototype = Object.getPrototypeOf(resource);
1239
+ while (prototype) {
1240
+ const parentPrototype = Object.getPrototypeOf(prototype);
1241
+ if (!parentPrototype || parentPrototype === Resource.prototype) {
1242
+ return getPrototypeToStringTag(prototype) || resource[Symbol.toStringTag] || resource.constructor.name;
1243
+ }
1244
+ prototype = parentPrototype;
1245
+ }
1246
+ return resource[Symbol.toStringTag] || resource.constructor.name;
1247
+ }
1248
+ function getPrototypeToStringTag(prototype) {
1249
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, Symbol.toStringTag);
1250
+ if (typeof descriptor?.get === "function") {
1251
+ return descriptor.get.call(prototype);
1252
+ }
1253
+ if (typeof descriptor?.value === "string") {
1254
+ return descriptor.value;
1255
+ }
1256
+ return null;
1257
+ }
982
1258
 
983
1259
  // ../core/src/adapter/resources/buffer.ts
984
1260
  var _Buffer = class extends Resource {
@@ -1018,18 +1294,26 @@ var __exports__ = (() => {
1018
1294
  /** A partial CPU-side copy of the data in this buffer, for debugging purposes */
1019
1295
  debugData = new ArrayBuffer(0);
1020
1296
  /** This doesn't handle partial non-zero offset updates correctly */
1021
- _setDebugData(data, byteOffset, byteLength) {
1022
- const arrayBuffer2 = ArrayBuffer.isView(data) ? data.buffer : data;
1297
+ _setDebugData(data, _byteOffset, byteLength) {
1298
+ let arrayBufferView = null;
1299
+ let arrayBuffer2;
1300
+ if (ArrayBuffer.isView(data)) {
1301
+ arrayBufferView = data;
1302
+ arrayBuffer2 = data.buffer;
1303
+ } else {
1304
+ arrayBuffer2 = data;
1305
+ }
1023
1306
  const debugDataLength = Math.min(
1024
1307
  data ? data.byteLength : byteLength,
1025
1308
  _Buffer.DEBUG_DATA_MAX_LENGTH
1026
1309
  );
1027
1310
  if (arrayBuffer2 === null) {
1028
1311
  this.debugData = new ArrayBuffer(debugDataLength);
1029
- } else if (byteOffset === 0 && byteLength === arrayBuffer2.byteLength) {
1030
- this.debugData = arrayBuffer2.slice(0, debugDataLength);
1031
1312
  } else {
1032
- this.debugData = arrayBuffer2.slice(byteOffset, byteOffset + debugDataLength);
1313
+ const sourceByteOffset = Math.min(arrayBufferView?.byteOffset || 0, arrayBuffer2.byteLength);
1314
+ const availableByteLength = Math.max(0, arrayBuffer2.byteLength - sourceByteOffset);
1315
+ const copyByteLength = Math.min(debugDataLength, availableByteLength);
1316
+ this.debugData = new Uint8Array(arrayBuffer2, sourceByteOffset, copyByteLength).slice().buffer;
1033
1317
  }
1034
1318
  }
1035
1319
  };
@@ -1225,6 +1509,7 @@ var __exports__ = (() => {
1225
1509
  var float16_renderable = "float16-renderable-webgl";
1226
1510
  var rgb9e5ufloat_renderable = "rgb9e5ufloat-renderable-webgl";
1227
1511
  var snorm8_renderable = "snorm8-renderable-webgl";
1512
+ var norm16_webgl = "norm16-webgl";
1228
1513
  var norm16_renderable = "norm16-renderable-webgl";
1229
1514
  var snorm16_renderable = "snorm16-renderable-webgl";
1230
1515
  var float32_filterable = "float32-filterable";
@@ -1258,16 +1543,16 @@ var __exports__ = (() => {
1258
1543
  "rgba8sint": {},
1259
1544
  "bgra8unorm": {},
1260
1545
  "bgra8unorm-srgb": {},
1261
- "r16unorm": { f: norm16_renderable },
1262
- "rg16unorm": { render: norm16_renderable },
1263
- "rgb16unorm-webgl": { f: norm16_renderable },
1546
+ "r16unorm": { f: norm16_webgl, render: norm16_renderable },
1547
+ "rg16unorm": { f: norm16_webgl, render: norm16_renderable },
1548
+ "rgb16unorm-webgl": { f: norm16_webgl, render: false },
1264
1549
  // rgb not renderable
1265
- "rgba16unorm": { render: norm16_renderable },
1266
- "r16snorm": { f: snorm16_renderable },
1267
- "rg16snorm": { render: snorm16_renderable },
1268
- "rgb16snorm-webgl": { f: norm16_renderable },
1550
+ "rgba16unorm": { f: norm16_webgl, render: norm16_renderable },
1551
+ "r16snorm": { f: norm16_webgl, render: snorm16_renderable },
1552
+ "rg16snorm": { f: norm16_webgl, render: snorm16_renderable },
1553
+ "rgb16snorm-webgl": { f: norm16_webgl, render: false },
1269
1554
  // rgb not renderable
1270
- "rgba16snorm": { render: snorm16_renderable },
1555
+ "rgba16snorm": { f: norm16_webgl, render: snorm16_renderable },
1271
1556
  "r16uint": {},
1272
1557
  "rg16uint": {},
1273
1558
  "rgba16uint": {},
@@ -1370,7 +1655,7 @@ var __exports__ = (() => {
1370
1655
  // WEBGL_compressed_texture_pvrtc
1371
1656
  "pvrtc-rgb4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1372
1657
  "pvrtc-rgba4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1373
- "pvrtc-rbg2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1658
+ "pvrtc-rgb2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1374
1659
  "pvrtc-rgba2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1375
1660
  // WEBGL_compressed_texture_etc1
1376
1661
  "etc1-rbg-unorm-webgl": { f: texture_compression_etc1_webgl },
@@ -1437,10 +1722,19 @@ var __exports__ = (() => {
1437
1722
  depth,
1438
1723
  byteAlignment
1439
1724
  }) {
1440
- const { bytesPerPixel } = textureFormatDecoder.getInfo(format);
1441
- const unpaddedBytesPerRow = width * bytesPerPixel;
1725
+ const formatInfo = textureFormatDecoder.getInfo(format);
1726
+ const {
1727
+ bytesPerPixel,
1728
+ bytesPerBlock = bytesPerPixel,
1729
+ blockWidth = 1,
1730
+ blockHeight = 1,
1731
+ compressed = false
1732
+ } = formatInfo;
1733
+ const blockColumns = compressed ? Math.ceil(width / blockWidth) : width;
1734
+ const blockRows = compressed ? Math.ceil(height / blockHeight) : height;
1735
+ const unpaddedBytesPerRow = blockColumns * bytesPerBlock;
1442
1736
  const bytesPerRow = Math.ceil(unpaddedBytesPerRow / byteAlignment) * byteAlignment;
1443
- const rowsPerImage = height;
1737
+ const rowsPerImage = blockRows;
1444
1738
  const byteLength = bytesPerRow * rowsPerImage * depth;
1445
1739
  return {
1446
1740
  bytesPerPixel,
@@ -1466,7 +1760,8 @@ var __exports__ = (() => {
1466
1760
  const isSigned = formatInfo?.signed;
1467
1761
  const isInteger = formatInfo?.integer;
1468
1762
  const isWebGLSpecific = formatInfo?.webgl;
1469
- formatCapabilities.render &&= !isSigned;
1763
+ const isCompressed = Boolean(formatInfo?.compressed);
1764
+ formatCapabilities.render &&= !isDepthStencil && !isCompressed;
1470
1765
  formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1471
1766
  return formatCapabilities;
1472
1767
  }
@@ -1478,6 +1773,7 @@ var __exports__ = (() => {
1478
1773
  formatInfo.bytesPerPixel = 1;
1479
1774
  formatInfo.srgb = false;
1480
1775
  formatInfo.compressed = true;
1776
+ formatInfo.bytesPerBlock = getCompressedTextureBlockByteLength(format);
1481
1777
  const blockSize = getCompressedTextureBlockSize(format);
1482
1778
  if (blockSize) {
1483
1779
  formatInfo.blockWidth = blockSize.blockWidth;
@@ -1563,8 +1859,29 @@ var __exports__ = (() => {
1563
1859
  const [, blockWidth, blockHeight] = matches;
1564
1860
  return { blockWidth: Number(blockWidth), blockHeight: Number(blockHeight) };
1565
1861
  }
1862
+ if (format.startsWith("bc") || format.startsWith("etc1") || format.startsWith("etc2") || format.startsWith("eac") || format.startsWith("atc")) {
1863
+ return { blockWidth: 4, blockHeight: 4 };
1864
+ }
1865
+ if (format.startsWith("pvrtc-rgb4") || format.startsWith("pvrtc-rgba4")) {
1866
+ return { blockWidth: 4, blockHeight: 4 };
1867
+ }
1868
+ if (format.startsWith("pvrtc-rgb2") || format.startsWith("pvrtc-rgba2")) {
1869
+ return { blockWidth: 8, blockHeight: 4 };
1870
+ }
1566
1871
  return null;
1567
1872
  }
1873
+ function getCompressedTextureBlockByteLength(format) {
1874
+ if (format.startsWith("bc1") || format.startsWith("bc4") || format.startsWith("etc1") || format.startsWith("etc2-rgb8") || format.startsWith("etc2-rgb8a1") || format.startsWith("eac-r11") || format === "atc-rgb-unorm-webgl") {
1875
+ return 8;
1876
+ }
1877
+ if (format.startsWith("bc2") || format.startsWith("bc3") || format.startsWith("bc5") || format.startsWith("bc6h") || format.startsWith("bc7") || format.startsWith("etc2-rgba8") || format.startsWith("eac-rg11") || format.startsWith("astc") || format === "atc-rgba-unorm-webgl" || format === "atc-rgbai-unorm-webgl") {
1878
+ return 16;
1879
+ }
1880
+ if (format.startsWith("pvrtc")) {
1881
+ return 8;
1882
+ }
1883
+ return 16;
1884
+ }
1568
1885
 
1569
1886
  // ../core/src/image-utils/image-types.ts
1570
1887
  function isExternalImage(data) {
@@ -1625,6 +1942,8 @@ var __exports__ = (() => {
1625
1942
  /** Used by other luma.gl modules to store data on the device */
1626
1943
  _moduleData = {};
1627
1944
  _textureCaps = {};
1945
+ /** Internal timestamp query set used when GPU timing collection is enabled for this device. */
1946
+ _debugGPUTimeQuery = null;
1628
1947
  constructor(props) {
1629
1948
  this.props = { ..._Device.defaultProps, ...props };
1630
1949
  this.id = this.props.id || uid(this[Symbol.toStringTag].toLowerCase());
@@ -1678,6 +1997,16 @@ var __exports__ = (() => {
1678
1997
  isTextureFormatCompressed(format) {
1679
1998
  return textureFormatDecoder.isCompressed(format);
1680
1999
  }
2000
+ /** Returns the compressed texture formats that can be created and sampled on this device */
2001
+ getSupportedCompressedTextureFormats() {
2002
+ const supportedFormats = [];
2003
+ for (const format of Object.keys(getTextureFormatTable())) {
2004
+ if (this.isTextureFormatCompressed(format) && this.isTextureFormatSupported(format)) {
2005
+ supportedFormats.push(format);
2006
+ }
2007
+ }
2008
+ return supportedFormats;
2009
+ }
1681
2010
  // DEBUG METHODS
1682
2011
  pushDebugGroup(groupLabel) {
1683
2012
  this.commandEncoder.pushDebugGroup(groupLabel);
@@ -1760,6 +2089,70 @@ or create a device with the 'debug: true' prop.`;
1760
2089
  beginComputePass(props) {
1761
2090
  return this.commandEncoder.beginComputePass(props);
1762
2091
  }
2092
+ /**
2093
+ * Generate mipmaps for a WebGPU texture.
2094
+ * WebGPU textures must be created up front with the required mip count, usage flags, and a format that supports the chosen generation path.
2095
+ * WebGL uses `Texture.generateMipmapsWebGL()` directly because the backend manages mip generation on the texture object itself.
2096
+ */
2097
+ generateMipmapsWebGPU(_texture) {
2098
+ throw new Error("not implemented");
2099
+ }
2100
+ /** Internal helper for creating a shareable WebGL render-pipeline implementation. */
2101
+ _createSharedRenderPipelineWebGL(_props) {
2102
+ throw new Error("_createSharedRenderPipelineWebGL() not implemented");
2103
+ }
2104
+ /**
2105
+ * Internal helper that returns `true` when timestamp-query GPU timing should be
2106
+ * collected for this device.
2107
+ */
2108
+ _supportsDebugGPUTime() {
2109
+ return this.features.has("timestamp-query") && Boolean(this.props.debug || this.props.debugGPUTime);
2110
+ }
2111
+ /**
2112
+ * Internal helper that enables device-managed GPU timing collection on the
2113
+ * default command encoder. Reuses the existing query set if timing is already enabled.
2114
+ *
2115
+ * @param queryCount - Number of timestamp slots reserved for profiled passes.
2116
+ * @returns The device-managed timestamp QuerySet, or `null` when timing is not supported or could not be enabled.
2117
+ */
2118
+ _enableDebugGPUTime(queryCount = 256) {
2119
+ if (!this._supportsDebugGPUTime()) {
2120
+ return null;
2121
+ }
2122
+ if (this._debugGPUTimeQuery) {
2123
+ return this._debugGPUTimeQuery;
2124
+ }
2125
+ try {
2126
+ this._debugGPUTimeQuery = this.createQuerySet({ type: "timestamp", count: queryCount });
2127
+ this.commandEncoder = this.createCommandEncoder({
2128
+ id: this.commandEncoder.props.id,
2129
+ timeProfilingQuerySet: this._debugGPUTimeQuery
2130
+ });
2131
+ } catch {
2132
+ this._debugGPUTimeQuery = null;
2133
+ }
2134
+ return this._debugGPUTimeQuery;
2135
+ }
2136
+ /**
2137
+ * Internal helper that disables device-managed GPU timing collection and restores
2138
+ * the default command encoder to an unprofiled state.
2139
+ */
2140
+ _disableDebugGPUTime() {
2141
+ if (!this._debugGPUTimeQuery) {
2142
+ return;
2143
+ }
2144
+ if (this.commandEncoder.getTimeProfilingQuerySet() === this._debugGPUTimeQuery) {
2145
+ this.commandEncoder = this.createCommandEncoder({
2146
+ id: this.commandEncoder.props.id
2147
+ });
2148
+ }
2149
+ this._debugGPUTimeQuery.destroy();
2150
+ this._debugGPUTimeQuery = null;
2151
+ }
2152
+ /** Internal helper that returns `true` when device-managed GPU timing is currently active. */
2153
+ _isDebugGPUTimeEnabled() {
2154
+ return this._debugGPUTimeQuery !== null;
2155
+ }
1763
2156
  // DEPRECATED METHODS
1764
2157
  /** @deprecated Use getDefaultCanvasContext() */
1765
2158
  getCanvasContext() {
@@ -1867,7 +2260,8 @@ or create a device with the 'debug: true' prop.`;
1867
2260
  onVisibilityChange: (context) => log.log(1, `${context} Visibility changed ${context.isVisible}`)(),
1868
2261
  onDevicePixelRatioChange: (context, info) => log.log(1, `${context} DPR changed ${info.oldRatio} => ${context.devicePixelRatio}`)(),
1869
2262
  // Debug flags
1870
- debug: log.get("debug") || void 0,
2263
+ debug: getDefaultDebugValue(),
2264
+ debugGPUTime: false,
1871
2265
  debugShaders: log.get("debug-shaders") || void 0,
1872
2266
  debugFramebuffers: Boolean(log.get("debug-framebuffers")),
1873
2267
  debugFactories: Boolean(log.get("debug-factories")),
@@ -1878,9 +2272,11 @@ or create a device with the 'debug: true' prop.`;
1878
2272
  // Experimental
1879
2273
  _reuseDevices: false,
1880
2274
  _requestMaxLimits: true,
1881
- _cacheShaders: false,
1882
- _cachePipelines: false,
1883
- _cacheDestroyPolicy: "unused",
2275
+ _cacheShaders: true,
2276
+ _destroyShaders: false,
2277
+ _cachePipelines: true,
2278
+ _sharePipelines: true,
2279
+ _destroyPipelines: false,
1884
2280
  // TODO - Change these after confirming things work as expected
1885
2281
  _initializeFeatures: true,
1886
2282
  _disabledFeatures: {
@@ -1889,6 +2285,25 @@ or create a device with the 'debug: true' prop.`;
1889
2285
  // INTERNAL
1890
2286
  _handle: void 0
1891
2287
  });
2288
+ function _getDefaultDebugValue(logDebugValue, nodeEnv) {
2289
+ if (logDebugValue !== void 0 && logDebugValue !== null) {
2290
+ return Boolean(logDebugValue);
2291
+ }
2292
+ if (nodeEnv !== void 0) {
2293
+ return nodeEnv !== "production";
2294
+ }
2295
+ return false;
2296
+ }
2297
+ function getDefaultDebugValue() {
2298
+ return _getDefaultDebugValue(log.get("debug"), getNodeEnv());
2299
+ }
2300
+ function getNodeEnv() {
2301
+ const processObject = globalThis.process;
2302
+ if (!processObject?.env) {
2303
+ return void 0;
2304
+ }
2305
+ return processObject.env["NODE_ENV"];
2306
+ }
1892
2307
 
1893
2308
  // ../core/src/adapter/luma.ts
1894
2309
  var STARTUP_MESSAGE = "set luma.log.level=1 (or higher) to trace rendering";
@@ -2066,6 +2481,100 @@ or create a device with the 'debug: true' prop.`;
2066
2481
  return pageLoadPromise;
2067
2482
  }
2068
2483
 
2484
+ // ../core/src/adapter/canvas-observer.ts
2485
+ var CanvasObserver = class {
2486
+ props;
2487
+ _resizeObserver;
2488
+ _intersectionObserver;
2489
+ _observeDevicePixelRatioTimeout = null;
2490
+ _observeDevicePixelRatioMediaQuery = null;
2491
+ _handleDevicePixelRatioChange = () => this._refreshDevicePixelRatio();
2492
+ _trackPositionInterval = null;
2493
+ _started = false;
2494
+ get started() {
2495
+ return this._started;
2496
+ }
2497
+ constructor(props) {
2498
+ this.props = props;
2499
+ }
2500
+ start() {
2501
+ if (this._started || !this.props.canvas) {
2502
+ return;
2503
+ }
2504
+ this._started = true;
2505
+ this._intersectionObserver ||= new IntersectionObserver(
2506
+ (entries) => this.props.onIntersection(entries)
2507
+ );
2508
+ this._resizeObserver ||= new ResizeObserver((entries) => this.props.onResize(entries));
2509
+ this._intersectionObserver.observe(this.props.canvas);
2510
+ try {
2511
+ this._resizeObserver.observe(this.props.canvas, { box: "device-pixel-content-box" });
2512
+ } catch {
2513
+ this._resizeObserver.observe(this.props.canvas, { box: "content-box" });
2514
+ }
2515
+ this._observeDevicePixelRatioTimeout = setTimeout(() => this._refreshDevicePixelRatio(), 0);
2516
+ if (this.props.trackPosition) {
2517
+ this._trackPosition();
2518
+ }
2519
+ }
2520
+ stop() {
2521
+ if (!this._started) {
2522
+ return;
2523
+ }
2524
+ this._started = false;
2525
+ if (this._observeDevicePixelRatioTimeout) {
2526
+ clearTimeout(this._observeDevicePixelRatioTimeout);
2527
+ this._observeDevicePixelRatioTimeout = null;
2528
+ }
2529
+ if (this._observeDevicePixelRatioMediaQuery) {
2530
+ this._observeDevicePixelRatioMediaQuery.removeEventListener(
2531
+ "change",
2532
+ this._handleDevicePixelRatioChange
2533
+ );
2534
+ this._observeDevicePixelRatioMediaQuery = null;
2535
+ }
2536
+ if (this._trackPositionInterval) {
2537
+ clearInterval(this._trackPositionInterval);
2538
+ this._trackPositionInterval = null;
2539
+ }
2540
+ this._resizeObserver?.disconnect();
2541
+ this._intersectionObserver?.disconnect();
2542
+ }
2543
+ _refreshDevicePixelRatio() {
2544
+ if (!this._started) {
2545
+ return;
2546
+ }
2547
+ this.props.onDevicePixelRatioChange();
2548
+ this._observeDevicePixelRatioMediaQuery?.removeEventListener(
2549
+ "change",
2550
+ this._handleDevicePixelRatioChange
2551
+ );
2552
+ this._observeDevicePixelRatioMediaQuery = matchMedia(
2553
+ `(resolution: ${window.devicePixelRatio}dppx)`
2554
+ );
2555
+ this._observeDevicePixelRatioMediaQuery.addEventListener(
2556
+ "change",
2557
+ this._handleDevicePixelRatioChange,
2558
+ { once: true }
2559
+ );
2560
+ }
2561
+ _trackPosition(intervalMs = 100) {
2562
+ if (this._trackPositionInterval) {
2563
+ return;
2564
+ }
2565
+ this._trackPositionInterval = setInterval(() => {
2566
+ if (!this._started) {
2567
+ if (this._trackPositionInterval) {
2568
+ clearInterval(this._trackPositionInterval);
2569
+ this._trackPositionInterval = null;
2570
+ }
2571
+ } else {
2572
+ this.props.onPositionChange();
2573
+ }
2574
+ }, intervalMs);
2575
+ }
2576
+ };
2577
+
2069
2578
  // ../core/src/utils/promise-utils.ts
2070
2579
  function withResolvers() {
2071
2580
  let resolve;
@@ -2090,8 +2599,8 @@ or create a device with the 'debug: true' prop.`;
2090
2599
  return value;
2091
2600
  }
2092
2601
 
2093
- // ../core/src/adapter/canvas-context.ts
2094
- var _CanvasContext = class {
2602
+ // ../core/src/adapter/canvas-surface.ts
2603
+ var _CanvasSurface = class {
2095
2604
  static isHTMLCanvas(canvas) {
2096
2605
  return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
2097
2606
  }
@@ -2127,11 +2636,7 @@ or create a device with the 'debug: true' prop.`;
2127
2636
  drawingBufferHeight;
2128
2637
  /** Resolves when the canvas is initialized, i.e. when the ResizeObserver has updated the pixel size */
2129
2638
  _initializedResolvers = withResolvers();
2130
- /** ResizeObserver to track canvas size changes */
2131
- _resizeObserver;
2132
- /** IntersectionObserver to track canvas visibility changes */
2133
- _intersectionObserver;
2134
- _observeDevicePixelRatioTimeout = null;
2639
+ _canvasObserver;
2135
2640
  /** Position of the canvas in the document, updated by a timer */
2136
2641
  _position = [0, 0];
2137
2642
  /** Whether this canvas context has been destroyed */
@@ -2142,7 +2647,7 @@ or create a device with the 'debug: true' prop.`;
2142
2647
  return `${this[Symbol.toStringTag]}(${this.id})`;
2143
2648
  }
2144
2649
  constructor(props) {
2145
- this.props = { ..._CanvasContext.defaultProps, ...props };
2650
+ this.props = { ..._CanvasSurface.defaultProps, ...props };
2146
2651
  props = this.props;
2147
2652
  this.initialized = this._initializedResolvers.promise;
2148
2653
  if (!isBrowser()) {
@@ -2154,11 +2659,11 @@ or create a device with the 'debug: true' prop.`;
2154
2659
  } else {
2155
2660
  this.canvas = props.canvas;
2156
2661
  }
2157
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2662
+ if (_CanvasSurface.isHTMLCanvas(this.canvas)) {
2158
2663
  this.id = props.id || this.canvas.id;
2159
2664
  this.type = "html-canvas";
2160
2665
  this.htmlCanvas = this.canvas;
2161
- } else if (_CanvasContext.isOffscreenCanvas(this.canvas)) {
2666
+ } else if (_CanvasSurface.isOffscreenCanvas(this.canvas)) {
2162
2667
  this.id = props.id || "offscreen-canvas";
2163
2668
  this.type = "offscreen-canvas";
2164
2669
  this.offscreenCanvas = this.canvas;
@@ -2174,33 +2679,20 @@ or create a device with the 'debug: true' prop.`;
2174
2679
  this.drawingBufferHeight = this.canvas.height;
2175
2680
  this.devicePixelRatio = globalThis.devicePixelRatio || 1;
2176
2681
  this._position = [0, 0];
2177
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2178
- this._intersectionObserver = new IntersectionObserver(
2179
- (entries) => this._handleIntersection(entries)
2180
- );
2181
- this._intersectionObserver.observe(this.canvas);
2182
- this._resizeObserver = new ResizeObserver((entries) => this._handleResize(entries));
2183
- try {
2184
- this._resizeObserver.observe(this.canvas, { box: "device-pixel-content-box" });
2185
- } catch {
2186
- this._resizeObserver.observe(this.canvas, { box: "content-box" });
2187
- }
2188
- this._observeDevicePixelRatioTimeout = setTimeout(() => this._observeDevicePixelRatio(), 0);
2189
- if (this.props.trackPosition) {
2190
- this._trackPosition();
2191
- }
2192
- }
2682
+ this._canvasObserver = new CanvasObserver({
2683
+ canvas: this.htmlCanvas,
2684
+ trackPosition: this.props.trackPosition,
2685
+ onResize: (entries) => this._handleResize(entries),
2686
+ onIntersection: (entries) => this._handleIntersection(entries),
2687
+ onDevicePixelRatioChange: () => this._observeDevicePixelRatio(),
2688
+ onPositionChange: () => this.updatePosition()
2689
+ });
2193
2690
  }
2194
2691
  destroy() {
2195
2692
  if (!this.destroyed) {
2196
2693
  this.destroyed = true;
2197
- if (this._observeDevicePixelRatioTimeout) {
2198
- clearTimeout(this._observeDevicePixelRatioTimeout);
2199
- this._observeDevicePixelRatioTimeout = null;
2200
- }
2694
+ this._stopObservers();
2201
2695
  this.device = null;
2202
- this._resizeObserver?.disconnect();
2203
- this._intersectionObserver?.disconnect();
2204
2696
  }
2205
2697
  }
2206
2698
  setProps(props) {
@@ -2215,41 +2707,22 @@ or create a device with the 'debug: true' prop.`;
2215
2707
  this._resizeDrawingBufferIfNeeded();
2216
2708
  return this._getCurrentFramebuffer(options);
2217
2709
  }
2218
- // SIZE METHODS
2219
- /**
2220
- * Returns the size covered by the canvas in CSS pixels
2221
- * @note This can be different from the actual device pixel size of a canvas due to DPR scaling, and rounding to integer pixels
2222
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2223
- */
2224
2710
  getCSSSize() {
2225
2711
  return [this.cssWidth, this.cssHeight];
2226
2712
  }
2227
2713
  getPosition() {
2228
2714
  return this._position;
2229
2715
  }
2230
- /**
2231
- * Returns the size covered by the canvas in actual device pixels.
2232
- * @note This can be different from the 'CSS' size of a canvas due to DPR scaling, and rounding to integer pixels
2233
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2234
- */
2235
2716
  getDevicePixelSize() {
2236
2717
  return [this.devicePixelWidth, this.devicePixelHeight];
2237
2718
  }
2238
- /** Get the drawing buffer size (number of pixels GPU is rendering into, can be different from CSS size) */
2239
2719
  getDrawingBufferSize() {
2240
2720
  return [this.drawingBufferWidth, this.drawingBufferHeight];
2241
2721
  }
2242
- /** Returns the biggest allowed framebuffer size. @todo Allow the application to limit this? */
2243
2722
  getMaxDrawingBufferSize() {
2244
2723
  const maxTextureDimension = this.device.limits.maxTextureDimension2D;
2245
2724
  return [maxTextureDimension, maxTextureDimension];
2246
2725
  }
2247
- /**
2248
- * Update the canvas drawing buffer size.
2249
- * @note - Called automatically if props.autoResize is true.
2250
- * @note - Defers update of drawing buffer size until framebuffer is requested to avoid flicker
2251
- * (resizing clears the drawing buffer)!
2252
- */
2253
2726
  setDrawingBufferSize(width, height) {
2254
2727
  width = Math.floor(width);
2255
2728
  height = Math.floor(height);
@@ -2260,19 +2733,10 @@ or create a device with the 'debug: true' prop.`;
2260
2733
  this.drawingBufferHeight = height;
2261
2734
  this._needsDrawingBufferResize = true;
2262
2735
  }
2263
- /**
2264
- * Returns the current DPR (number of physical pixels per CSS pixel), if props.useDevicePixels is true
2265
- * @note This can be a fractional (non-integer) number, e.g. when the user zooms in the browser.
2266
- * @note This function handles the non-HTML canvas cases
2267
- */
2268
2736
  getDevicePixelRatio() {
2269
- const dpr = typeof window !== "undefined" && window.devicePixelRatio;
2270
- return dpr || 1;
2737
+ const devicePixelRatio2 = typeof window !== "undefined" && window.devicePixelRatio;
2738
+ return devicePixelRatio2 || 1;
2271
2739
  }
2272
- // DEPRECATED METHODS
2273
- /**
2274
- * Maps CSS pixel position to device pixel position
2275
- */
2276
2740
  cssToDevicePixels(cssPixel, yInvert = true) {
2277
2741
  const ratio = this.cssToDeviceRatio();
2278
2742
  const [width, height] = this.getDrawingBufferSize();
@@ -2282,10 +2746,10 @@ or create a device with the 'debug: true' prop.`;
2282
2746
  getPixelSize() {
2283
2747
  return this.getDevicePixelSize();
2284
2748
  }
2285
- /** @deprecated - TODO which values should we use for aspect */
2749
+ /** @deprecated Use the current drawing buffer size for projection setup. */
2286
2750
  getAspect() {
2287
- const [width, height] = this.getDevicePixelSize();
2288
- return width / height;
2751
+ const [width, height] = this.getDrawingBufferSize();
2752
+ return width > 0 && height > 0 ? width / height : 1;
2289
2753
  }
2290
2754
  /** @deprecated Returns multiplier need to convert CSS size to Device size */
2291
2755
  cssToDeviceRatio() {
@@ -2301,17 +2765,36 @@ or create a device with the 'debug: true' prop.`;
2301
2765
  resize(size) {
2302
2766
  this.setDrawingBufferSize(size.width, size.height);
2303
2767
  }
2304
- // IMPLEMENTATION
2305
- /**
2306
- * Allows subclass constructor to override the canvas id for auto created canvases.
2307
- * This can really help when debugging DOM in apps that create multiple devices
2308
- */
2309
2768
  _setAutoCreatedCanvasId(id) {
2310
2769
  if (this.htmlCanvas?.id === "lumagl-auto-created-canvas") {
2311
2770
  this.htmlCanvas.id = id;
2312
2771
  }
2313
2772
  }
2314
- /** reacts to an observed intersection */
2773
+ /**
2774
+ * Starts DOM observation after the derived context and its device are fully initialized.
2775
+ *
2776
+ * `CanvasSurface` construction runs before subclasses can assign `this.device`, and the
2777
+ * default WebGL canvas context is created before `WebGLDevice` has initialized `limits`,
2778
+ * `features`, and the rest of its runtime state. Deferring observer startup avoids early
2779
+ * `ResizeObserver` and DPR callbacks running against a partially initialized device.
2780
+ */
2781
+ _startObservers() {
2782
+ if (this.destroyed) {
2783
+ return;
2784
+ }
2785
+ this._canvasObserver.start();
2786
+ }
2787
+ /**
2788
+ * Stops all DOM observation and timers associated with a canvas surface.
2789
+ *
2790
+ * This pairs with `_startObservers()` so teardown uses the same lifecycle whether a context is
2791
+ * explicitly destroyed, abandoned during device reuse, or temporarily has not started observing
2792
+ * yet. Centralizing shutdown here keeps resize/DPR/position watchers from surviving past the
2793
+ * lifetime of the owning device.
2794
+ */
2795
+ _stopObservers() {
2796
+ this._canvasObserver.stop();
2797
+ }
2315
2798
  _handleIntersection(entries) {
2316
2799
  if (this.destroyed) {
2317
2800
  return;
@@ -2326,11 +2809,6 @@ or create a device with the 'debug: true' prop.`;
2326
2809
  this.device.props.onVisibilityChange(this);
2327
2810
  }
2328
2811
  }
2329
- /**
2330
- * Reacts to an observed resize by using the most accurate pixel size information the browser can provide
2331
- * @see https://web.dev/articles/device-pixel-content-box
2332
- * @see https://webgpufundamentals.org/webgpu/lessons/webgpu-resizing-the-canvas.html
2333
- */
2334
2812
  _handleResize(entries) {
2335
2813
  if (this.destroyed) {
2336
2814
  return;
@@ -2351,12 +2829,14 @@ or create a device with the 'debug: true' prop.`;
2351
2829
  this._updateDrawingBufferSize();
2352
2830
  this.device.props.onResize(this, { oldPixelSize });
2353
2831
  }
2354
- /** Initiate a deferred update for the canvas drawing buffer size */
2355
2832
  _updateDrawingBufferSize() {
2356
2833
  if (this.props.autoResize) {
2357
2834
  if (typeof this.props.useDevicePixels === "number") {
2358
- const dpr = this.props.useDevicePixels;
2359
- this.setDrawingBufferSize(this.cssWidth * dpr, this.cssHeight * dpr);
2835
+ const devicePixelRatio2 = this.props.useDevicePixels;
2836
+ this.setDrawingBufferSize(
2837
+ this.cssWidth * devicePixelRatio2,
2838
+ this.cssHeight * devicePixelRatio2
2839
+ );
2360
2840
  } else if (this.props.useDevicePixels) {
2361
2841
  this.setDrawingBufferSize(this.devicePixelWidth, this.devicePixelHeight);
2362
2842
  } else {
@@ -2367,7 +2847,6 @@ or create a device with the 'debug: true' prop.`;
2367
2847
  this.isInitialized = true;
2368
2848
  this.updatePosition();
2369
2849
  }
2370
- /** Perform a deferred resize of the drawing buffer if needed */
2371
2850
  _resizeDrawingBufferIfNeeded() {
2372
2851
  if (this._needsDrawingBufferResize) {
2373
2852
  this._needsDrawingBufferResize = false;
@@ -2379,36 +2858,17 @@ or create a device with the 'debug: true' prop.`;
2379
2858
  }
2380
2859
  }
2381
2860
  }
2382
- /** Monitor DPR changes */
2383
2861
  _observeDevicePixelRatio() {
2384
- if (this.destroyed) {
2862
+ if (this.destroyed || !this._canvasObserver.started) {
2385
2863
  return;
2386
2864
  }
2387
2865
  const oldRatio = this.devicePixelRatio;
2388
2866
  this.devicePixelRatio = window.devicePixelRatio;
2389
2867
  this.updatePosition();
2390
- this.device.props.onDevicePixelRatioChange?.(this, { oldRatio });
2391
- matchMedia(`(resolution: ${this.devicePixelRatio}dppx)`).addEventListener(
2392
- "change",
2393
- () => this._observeDevicePixelRatio(),
2394
- { once: true }
2395
- );
2396
- }
2397
- /** Start tracking positions with a timer */
2398
- _trackPosition(intervalMs = 100) {
2399
- const intervalId = setInterval(() => {
2400
- if (this.destroyed) {
2401
- clearInterval(intervalId);
2402
- } else {
2403
- this.updatePosition();
2404
- }
2405
- }, intervalMs);
2868
+ this.device.props.onDevicePixelRatioChange?.(this, {
2869
+ oldRatio
2870
+ });
2406
2871
  }
2407
- /**
2408
- * Calculated the absolute position of the canvas
2409
- * @note - getBoundingClientRect() is normally cheap but can be expensive
2410
- * if called before browser has finished a reflow. Should not be the case here.
2411
- */
2412
2872
  updatePosition() {
2413
2873
  if (this.destroyed) {
2414
2874
  return;
@@ -2421,13 +2881,15 @@ or create a device with the 'debug: true' prop.`;
2421
2881
  if (positionChanged) {
2422
2882
  const oldPosition = this._position;
2423
2883
  this._position = position;
2424
- this.device.props.onPositionChange?.(this, { oldPosition });
2884
+ this.device.props.onPositionChange?.(this, {
2885
+ oldPosition
2886
+ });
2425
2887
  }
2426
2888
  }
2427
2889
  }
2428
2890
  };
2429
- var CanvasContext = _CanvasContext;
2430
- __publicField(CanvasContext, "defaultProps", {
2891
+ var CanvasSurface = _CanvasSurface;
2892
+ __publicField(CanvasSurface, "defaultProps", {
2431
2893
  id: void 0,
2432
2894
  canvas: null,
2433
2895
  width: 800,
@@ -2455,7 +2917,7 @@ or create a device with the 'debug: true' prop.`;
2455
2917
  }
2456
2918
  function getCanvasFromDOM(canvasId) {
2457
2919
  const canvas = document.getElementById(canvasId);
2458
- if (!CanvasContext.isHTMLCanvas(canvas)) {
2920
+ if (!CanvasSurface.isHTMLCanvas(canvas)) {
2459
2921
  throw new Error("Object is not a canvas element");
2460
2922
  }
2461
2923
  return canvas;
@@ -2479,33 +2941,40 @@ or create a device with the 'debug: true' prop.`;
2479
2941
  const point = pixel;
2480
2942
  const x = scaleX(point[0], ratio, width);
2481
2943
  let y = scaleY(point[1], ratio, height, yInvert);
2482
- let t = scaleX(point[0] + 1, ratio, width);
2483
- const xHigh = t === width - 1 ? t : t - 1;
2484
- t = scaleY(point[1] + 1, ratio, height, yInvert);
2944
+ let temporary = scaleX(point[0] + 1, ratio, width);
2945
+ const xHigh = temporary === width - 1 ? temporary : temporary - 1;
2946
+ temporary = scaleY(point[1] + 1, ratio, height, yInvert);
2485
2947
  let yHigh;
2486
2948
  if (yInvert) {
2487
- t = t === 0 ? t : t + 1;
2949
+ temporary = temporary === 0 ? temporary : temporary + 1;
2488
2950
  yHigh = y;
2489
- y = t;
2951
+ y = temporary;
2490
2952
  } else {
2491
- yHigh = t === height - 1 ? t : t - 1;
2953
+ yHigh = temporary === height - 1 ? temporary : temporary - 1;
2492
2954
  }
2493
2955
  return {
2494
2956
  x,
2495
2957
  y,
2496
- // when ratio < 1, current css pixel and next css pixel may point to same device pixel, set width/height to 1 in those cases.
2497
2958
  width: Math.max(xHigh - x + 1, 1),
2498
2959
  height: Math.max(yHigh - y + 1, 1)
2499
2960
  };
2500
2961
  }
2501
2962
  function scaleX(x, ratio, width) {
2502
- const r = Math.min(Math.round(x * ratio), width - 1);
2503
- return r;
2963
+ return Math.min(Math.round(x * ratio), width - 1);
2504
2964
  }
2505
2965
  function scaleY(y, ratio, height, yInvert) {
2506
2966
  return yInvert ? Math.max(0, height - 1 - Math.round(y * ratio)) : Math.min(Math.round(y * ratio), height - 1);
2507
2967
  }
2508
2968
 
2969
+ // ../core/src/adapter/canvas-context.ts
2970
+ var CanvasContext = class extends CanvasSurface {
2971
+ };
2972
+ __publicField(CanvasContext, "defaultProps", CanvasSurface.defaultProps);
2973
+
2974
+ // ../core/src/adapter/presentation-context.ts
2975
+ var PresentationContext = class extends CanvasSurface {
2976
+ };
2977
+
2509
2978
  // ../core/src/adapter/resources/sampler.ts
2510
2979
  var _Sampler = class extends Resource {
2511
2980
  get [Symbol.toStringTag]() {
@@ -2560,6 +3029,8 @@ or create a device with the 'debug: true' prop.`;
2560
3029
  depth;
2561
3030
  /** mip levels in this texture */
2562
3031
  mipLevels;
3032
+ /** sample count */
3033
+ samples;
2563
3034
  /** Rows are multiples of this length, padded with extra bytes if needed */
2564
3035
  byteAlignment;
2565
3036
  /** The ready promise is always resolved. It is provided for type compatibility with DynamicTexture. */
@@ -2585,6 +3056,7 @@ or create a device with the 'debug: true' prop.`;
2585
3056
  this.height = this.props.height;
2586
3057
  this.depth = this.props.depth;
2587
3058
  this.mipLevels = this.props.mipLevels;
3059
+ this.samples = this.props.samples || 1;
2588
3060
  if (this.dimension === "cube") {
2589
3061
  this.depth = 6;
2590
3062
  }
@@ -2618,9 +3090,25 @@ or create a device with the 'debug: true' prop.`;
2618
3090
  setSampler(sampler) {
2619
3091
  this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
2620
3092
  }
3093
+ /**
3094
+ * Copy raw image data (bytes) into the texture.
3095
+ *
3096
+ * @note Deprecated compatibility wrapper over {@link writeData}.
3097
+ * @note Uses the same layout defaults and alignment rules as {@link writeData}.
3098
+ * @note Tightly packed CPU uploads can omit `bytesPerRow` and `rowsPerImage`.
3099
+ * @note If the CPU source rows are padded, pass explicit `bytesPerRow` and `rowsPerImage`.
3100
+ * @deprecated Use writeData()
3101
+ */
3102
+ copyImageData(options) {
3103
+ const { data, depth, ...writeOptions } = options;
3104
+ this.writeData(data, {
3105
+ ...writeOptions,
3106
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3107
+ });
3108
+ }
2621
3109
  /**
2622
3110
  * Calculates the memory layout of the texture, required when reading and writing data.
2623
- * @return the memory layout of the texture, in particular bytesPerRow which includes required padding
3111
+ * @return the backend-aligned linear layout, in particular bytesPerRow which includes any required padding for buffer copy/read paths
2624
3112
  */
2625
3113
  computeMemoryLayout(options_ = {}) {
2626
3114
  const options = this._normalizeTextureReadOptions(options_);
@@ -2639,9 +3127,11 @@ or create a device with the 'debug: true' prop.`;
2639
3127
  * @returns A Buffer containing the texture data.
2640
3128
  *
2641
3129
  * @note The memory layout of the texture data is determined by the texture format and dimensions.
2642
- * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3130
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
2643
3131
  * @note The application can call Buffer.readAsync()
2644
3132
  * @note If not supplied a buffer will be created and the application needs to call Buffer.destroy
3133
+ * @note On WebGPU this corresponds to a texture-to-buffer copy and uses buffer-copy alignment rules.
3134
+ * @note On WebGL, luma.gl emulates the same logical readback behavior.
2645
3135
  */
2646
3136
  readBuffer(options, buffer) {
2647
3137
  throw new Error("readBuffer not implemented");
@@ -2657,10 +3147,14 @@ or create a device with the 'debug: true' prop.`;
2657
3147
  throw new Error("readBuffer not implemented");
2658
3148
  }
2659
3149
  /**
2660
- * Writes an GPU Buffer into a texture.
3150
+ * Writes a GPU Buffer into a texture.
2661
3151
  *
3152
+ * @param buffer - Source GPU buffer.
3153
+ * @param options - Destination subresource, extent, and source layout options.
2662
3154
  * @note The memory layout of the texture data is determined by the texture format and dimensions.
2663
- * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3155
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3156
+ * @note On WebGPU this corresponds to a buffer-to-texture copy and uses buffer-copy alignment rules.
3157
+ * @note On WebGL, luma.gl emulates the same destination and layout semantics.
2664
3158
  */
2665
3159
  writeBuffer(buffer, options) {
2666
3160
  throw new Error("readBuffer not implemented");
@@ -2668,8 +3162,11 @@ or create a device with the 'debug: true' prop.`;
2668
3162
  /**
2669
3163
  * Writes an array buffer into a texture.
2670
3164
  *
2671
- * @note The memory layout of the texture data is determined by the texture format and dimensions.
2672
- * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3165
+ * @param data - Source texel data.
3166
+ * @param options - Destination subresource, extent, and source layout options.
3167
+ * @note If `bytesPerRow` and `rowsPerImage` are omitted, luma.gl computes a tightly packed CPU-memory layout for the requested region.
3168
+ * @note On WebGPU this corresponds to `GPUQueue.writeTexture()` and does not implicitly pad rows to 256 bytes.
3169
+ * @note On WebGL, padded CPU data is supported via the same `bytesPerRow` and `rowsPerImage` options.
2673
3170
  */
2674
3171
  writeData(data, options) {
2675
3172
  throw new Error("readBuffer not implemented");
@@ -2732,37 +3229,166 @@ or create a device with the 'debug: true' prop.`;
2732
3229
  }
2733
3230
  }
2734
3231
  _normalizeCopyImageDataOptions(options_) {
2735
- const { width, height, depth } = this;
2736
- const options = { ..._Texture.defaultCopyDataOptions, width, height, depth, ...options_ };
2737
- const info = this.device.getTextureFormatInfo(this.format);
2738
- if (!options_.bytesPerRow && !info.bytesPerPixel) {
2739
- throw new Error(`bytesPerRow must be provided for texture format ${this.format}`);
2740
- }
2741
- options.bytesPerRow = options_.bytesPerRow || width * (info.bytesPerPixel || 4);
2742
- options.rowsPerImage = options_.rowsPerImage || height;
2743
- return options;
3232
+ const { data, depth, ...writeOptions } = options_;
3233
+ const options = this._normalizeTextureWriteOptions({
3234
+ ...writeOptions,
3235
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3236
+ });
3237
+ return { data, depth: options.depthOrArrayLayers, ...options };
2744
3238
  }
2745
3239
  _normalizeCopyExternalImageOptions(options_) {
3240
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3241
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3242
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
2746
3243
  const size = this.device.getExternalImageSize(options_.image);
2747
- const options = { ..._Texture.defaultCopyExternalImageOptions, ...size, ...options_ };
2748
- options.width = Math.min(options.width, this.width - options.x);
2749
- options.height = Math.min(options.height, this.height - options.y);
3244
+ const options = {
3245
+ ..._Texture.defaultCopyExternalImageOptions,
3246
+ ...mipLevelSize,
3247
+ ...size,
3248
+ ...optionsWithoutUndefined
3249
+ };
3250
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3251
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3252
+ options.depth = Math.min(options.depth, mipLevelSize.depthOrArrayLayers - options.z);
2750
3253
  return options;
2751
3254
  }
2752
3255
  _normalizeTextureReadOptions(options_) {
2753
- const { width, height } = this;
2754
- const options = { ..._Texture.defaultTextureReadOptions, width, height, ...options_ };
2755
- options.width = Math.min(options.width, this.width - options.x);
2756
- options.height = Math.min(options.height, this.height - options.y);
3256
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3257
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3258
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3259
+ const options = {
3260
+ ..._Texture.defaultTextureReadOptions,
3261
+ ...mipLevelSize,
3262
+ ...optionsWithoutUndefined
3263
+ };
3264
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3265
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3266
+ options.depthOrArrayLayers = Math.min(
3267
+ options.depthOrArrayLayers,
3268
+ mipLevelSize.depthOrArrayLayers - options.z
3269
+ );
2757
3270
  return options;
2758
3271
  }
3272
+ /**
3273
+ * Normalizes a texture read request and validates the color-only readback contract used by the
3274
+ * current texture read APIs. Supported dimensions are `2d`, `cube`, `cube-array`,
3275
+ * `2d-array`, and `3d`.
3276
+ *
3277
+ * @throws if the texture format, aspect, or dimension is not supported by the first-pass
3278
+ * color-read implementation.
3279
+ */
3280
+ _getSupportedColorReadOptions(options_) {
3281
+ const options = this._normalizeTextureReadOptions(options_);
3282
+ const formatInfo = textureFormatDecoder.getInfo(this.format);
3283
+ this._validateColorReadAspect(options);
3284
+ this._validateColorReadFormat(formatInfo);
3285
+ switch (this.dimension) {
3286
+ case "2d":
3287
+ case "cube":
3288
+ case "cube-array":
3289
+ case "2d-array":
3290
+ case "3d":
3291
+ return options;
3292
+ default:
3293
+ throw new Error(`${this} color readback does not support ${this.dimension} textures`);
3294
+ }
3295
+ }
3296
+ /** Validates that a read request targets the full color aspect of the texture. */
3297
+ _validateColorReadAspect(options) {
3298
+ if (options.aspect !== "all") {
3299
+ throw new Error(`${this} color readback only supports aspect 'all'`);
3300
+ }
3301
+ }
3302
+ /** Validates that a read request targets an uncompressed color-renderable texture format. */
3303
+ _validateColorReadFormat(formatInfo) {
3304
+ if (formatInfo.compressed) {
3305
+ throw new Error(
3306
+ `${this} color readback does not support compressed formats (${this.format})`
3307
+ );
3308
+ }
3309
+ switch (formatInfo.attachment) {
3310
+ case "color":
3311
+ return;
3312
+ case "depth":
3313
+ throw new Error(`${this} color readback does not support depth formats (${this.format})`);
3314
+ case "stencil":
3315
+ throw new Error(`${this} color readback does not support stencil formats (${this.format})`);
3316
+ case "depth-stencil":
3317
+ throw new Error(
3318
+ `${this} color readback does not support depth-stencil formats (${this.format})`
3319
+ );
3320
+ default:
3321
+ throw new Error(`${this} color readback does not support format ${this.format}`);
3322
+ }
3323
+ }
2759
3324
  _normalizeTextureWriteOptions(options_) {
2760
- const { width, height } = this;
2761
- const options = { ..._Texture.defaultTextureReadOptions, width, height, ...options_ };
2762
- options.width = Math.min(options.width, this.width - options.x);
2763
- options.height = Math.min(options.height, this.height - options.y);
3325
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3326
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3327
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3328
+ const options = {
3329
+ ..._Texture.defaultTextureWriteOptions,
3330
+ ...mipLevelSize,
3331
+ ...optionsWithoutUndefined
3332
+ };
3333
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3334
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3335
+ options.depthOrArrayLayers = Math.min(
3336
+ options.depthOrArrayLayers,
3337
+ mipLevelSize.depthOrArrayLayers - options.z
3338
+ );
3339
+ const layout = textureFormatDecoder.computeMemoryLayout({
3340
+ format: this.format,
3341
+ width: options.width,
3342
+ height: options.height,
3343
+ depth: options.depthOrArrayLayers,
3344
+ byteAlignment: this.byteAlignment
3345
+ });
3346
+ const minimumBytesPerRow = layout.bytesPerPixel * options.width;
3347
+ options.bytesPerRow = optionsWithoutUndefined.bytesPerRow ?? layout.bytesPerRow;
3348
+ options.rowsPerImage = optionsWithoutUndefined.rowsPerImage ?? options.height;
3349
+ if (options.bytesPerRow < minimumBytesPerRow) {
3350
+ throw new Error(
3351
+ `bytesPerRow (${options.bytesPerRow}) must be at least ${minimumBytesPerRow} for ${this.format}`
3352
+ );
3353
+ }
3354
+ if (options.rowsPerImage < options.height) {
3355
+ throw new Error(
3356
+ `rowsPerImage (${options.rowsPerImage}) must be at least ${options.height} for ${this.format}`
3357
+ );
3358
+ }
3359
+ const bytesPerPixel = this.device.getTextureFormatInfo(this.format).bytesPerPixel;
3360
+ if (bytesPerPixel && options.bytesPerRow % bytesPerPixel !== 0) {
3361
+ throw new Error(
3362
+ `bytesPerRow (${options.bytesPerRow}) must be a multiple of bytesPerPixel (${bytesPerPixel}) for ${this.format}`
3363
+ );
3364
+ }
2764
3365
  return options;
2765
3366
  }
3367
+ _getMipLevelSize(mipLevel) {
3368
+ const width = Math.max(1, this.width >> mipLevel);
3369
+ const height = this.baseDimension === "1d" ? 1 : Math.max(1, this.height >> mipLevel);
3370
+ const depthOrArrayLayers = this.dimension === "3d" ? Math.max(1, this.depth >> mipLevel) : this.depth;
3371
+ return { width, height, depthOrArrayLayers };
3372
+ }
3373
+ getAllocatedByteLength() {
3374
+ let allocatedByteLength = 0;
3375
+ for (let mipLevel = 0; mipLevel < this.mipLevels; mipLevel++) {
3376
+ const { width, height, depthOrArrayLayers } = this._getMipLevelSize(mipLevel);
3377
+ allocatedByteLength += textureFormatDecoder.computeMemoryLayout({
3378
+ format: this.format,
3379
+ width,
3380
+ height,
3381
+ depth: depthOrArrayLayers,
3382
+ byteAlignment: 1
3383
+ }).byteLength;
3384
+ }
3385
+ return allocatedByteLength * this.samples;
3386
+ }
3387
+ static _omitUndefined(options) {
3388
+ return Object.fromEntries(
3389
+ Object.entries(options).filter(([, value]) => value !== void 0)
3390
+ );
3391
+ }
2766
3392
  };
2767
3393
  var Texture = _Texture;
2768
3394
  /** The texture can be bound for use as a sampled texture in a shader */
@@ -2798,6 +3424,10 @@ or create a device with the 'debug: true' prop.`;
2798
3424
  byteOffset: 0,
2799
3425
  bytesPerRow: void 0,
2800
3426
  rowsPerImage: void 0,
3427
+ width: void 0,
3428
+ height: void 0,
3429
+ depthOrArrayLayers: void 0,
3430
+ depth: 1,
2801
3431
  mipLevel: 0,
2802
3432
  x: 0,
2803
3433
  y: 0,
@@ -2831,6 +3461,19 @@ or create a device with the 'debug: true' prop.`;
2831
3461
  mipLevel: 0,
2832
3462
  aspect: "all"
2833
3463
  });
3464
+ __publicField(Texture, "defaultTextureWriteOptions", {
3465
+ byteOffset: 0,
3466
+ bytesPerRow: void 0,
3467
+ rowsPerImage: void 0,
3468
+ x: 0,
3469
+ y: 0,
3470
+ z: 0,
3471
+ width: void 0,
3472
+ height: void 0,
3473
+ depthOrArrayLayers: 1,
3474
+ mipLevel: 0,
3475
+ aspect: "all"
3476
+ });
2834
3477
 
2835
3478
  // ../core/src/adapter/resources/texture-view.ts
2836
3479
  var _TextureView = class extends Resource {
@@ -3209,10 +3852,21 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3209
3852
  linkStatus = "pending";
3210
3853
  /** The hash of the pipeline */
3211
3854
  hash = "";
3855
+ /** Optional shared backend implementation */
3856
+ sharedRenderPipeline = null;
3857
+ /** Whether shader or pipeline compilation/linking is still in progress */
3858
+ get isPending() {
3859
+ return this.linkStatus === "pending" || this.vs.compilationStatus === "pending" || this.fs?.compilationStatus === "pending";
3860
+ }
3861
+ /** Whether shader or pipeline compilation/linking has failed */
3862
+ get isErrored() {
3863
+ return this.linkStatus === "error" || this.vs.compilationStatus === "error" || this.fs?.compilationStatus === "error";
3864
+ }
3212
3865
  constructor(device, props) {
3213
3866
  super(device, props, _RenderPipeline.defaultProps);
3214
3867
  this.shaderLayout = this.props.shaderLayout;
3215
3868
  this.bufferLayout = this.props.bufferLayout || [];
3869
+ this.sharedRenderPipeline = this.props._sharedRenderPipeline || null;
3216
3870
  }
3217
3871
  };
3218
3872
  var RenderPipeline = _RenderPipeline;
@@ -3230,10 +3884,30 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3230
3884
  colorAttachmentFormats: void 0,
3231
3885
  depthStencilAttachmentFormat: void 0,
3232
3886
  parameters: {},
3233
- bindings: {},
3234
- uniforms: {}
3887
+ varyings: void 0,
3888
+ bufferMode: void 0,
3889
+ disableWarnings: false,
3890
+ _sharedRenderPipeline: void 0,
3891
+ bindings: void 0
3235
3892
  });
3236
3893
 
3894
+ // ../core/src/adapter/resources/shared-render-pipeline.ts
3895
+ var SharedRenderPipeline = class extends Resource {
3896
+ get [Symbol.toStringTag]() {
3897
+ return "SharedRenderPipeline";
3898
+ }
3899
+ constructor(device, props) {
3900
+ super(device, props, {
3901
+ ...Resource.defaultProps,
3902
+ handle: void 0,
3903
+ vs: void 0,
3904
+ fs: void 0,
3905
+ varyings: void 0,
3906
+ bufferMode: void 0
3907
+ });
3908
+ }
3909
+ };
3910
+
3237
3911
  // ../core/src/adapter/resources/render-pass.ts
3238
3912
  var _RenderPass = class extends Resource {
3239
3913
  get [Symbol.toStringTag]() {
@@ -3316,8 +3990,69 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3316
3990
  get [Symbol.toStringTag]() {
3317
3991
  return "CommandEncoder";
3318
3992
  }
3993
+ _timeProfilingQuerySet = null;
3994
+ _timeProfilingSlotCount = 0;
3995
+ _gpuTimeMs;
3319
3996
  constructor(device, props) {
3320
3997
  super(device, props, _CommandEncoder.defaultProps);
3998
+ this._timeProfilingQuerySet = props.timeProfilingQuerySet ?? null;
3999
+ this._timeProfilingSlotCount = 0;
4000
+ this._gpuTimeMs = void 0;
4001
+ }
4002
+ /**
4003
+ * Reads all resolved timestamp pairs on the current profiler query set and caches the sum
4004
+ * as milliseconds on this encoder.
4005
+ */
4006
+ async resolveTimeProfilingQuerySet() {
4007
+ this._gpuTimeMs = void 0;
4008
+ if (!this._timeProfilingQuerySet) {
4009
+ return;
4010
+ }
4011
+ const pairCount = Math.floor(this._timeProfilingSlotCount / 2);
4012
+ if (pairCount <= 0) {
4013
+ return;
4014
+ }
4015
+ const queryCount = pairCount * 2;
4016
+ const results = await this._timeProfilingQuerySet.readResults({
4017
+ firstQuery: 0,
4018
+ queryCount
4019
+ });
4020
+ let totalDurationNanoseconds = 0n;
4021
+ for (let queryIndex = 0; queryIndex < queryCount; queryIndex += 2) {
4022
+ totalDurationNanoseconds += results[queryIndex + 1] - results[queryIndex];
4023
+ }
4024
+ this._gpuTimeMs = Number(totalDurationNanoseconds) / 1e6;
4025
+ }
4026
+ /** Returns the number of query slots consumed by automatic pass profiling on this encoder. */
4027
+ getTimeProfilingSlotCount() {
4028
+ return this._timeProfilingSlotCount;
4029
+ }
4030
+ getTimeProfilingQuerySet() {
4031
+ return this._timeProfilingQuerySet;
4032
+ }
4033
+ /** Internal helper for auto-assigning timestamp slots to render/compute passes on this encoder. */
4034
+ _applyTimeProfilingToPassProps(props) {
4035
+ const passProps = props || {};
4036
+ if (!this._supportsTimestampQueries() || !this._timeProfilingQuerySet) {
4037
+ return passProps;
4038
+ }
4039
+ if (passProps.timestampQuerySet !== void 0 || passProps.beginTimestampIndex !== void 0 || passProps.endTimestampIndex !== void 0) {
4040
+ return passProps;
4041
+ }
4042
+ const beginTimestampIndex = this._timeProfilingSlotCount;
4043
+ if (beginTimestampIndex + 1 >= this._timeProfilingQuerySet.props.count) {
4044
+ return passProps;
4045
+ }
4046
+ this._timeProfilingSlotCount += 2;
4047
+ return {
4048
+ ...passProps,
4049
+ timestampQuerySet: this._timeProfilingQuerySet,
4050
+ beginTimestampIndex,
4051
+ endTimestampIndex: beginTimestampIndex + 1
4052
+ };
4053
+ }
4054
+ _supportsTimestampQueries() {
4055
+ return this.device.features.has("timestamp-query");
3321
4056
  }
3322
4057
  };
3323
4058
  var CommandEncoder = _CommandEncoder;
@@ -3326,7 +4061,8 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3326
4061
  // beginComputePass(optional GPUComputePassDescriptor descriptor = {}): GPUComputePassEncoder;
3327
4062
  __publicField(CommandEncoder, "defaultProps", {
3328
4063
  ...Resource.defaultProps,
3329
- measureExecutionTime: void 0
4064
+ measureExecutionTime: void 0,
4065
+ timeProfilingQuerySet: void 0
3330
4066
  });
3331
4067
 
3332
4068
  // ../core/src/adapter/resources/command-buffer.ts
@@ -3345,11 +4081,20 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3345
4081
 
3346
4082
  // ../core/src/shadertypes/data-types/decode-shader-types.ts
3347
4083
  function getVariableShaderTypeInfo(format) {
3348
- const decoded = UNIFORM_FORMATS[format];
4084
+ const resolvedFormat = resolveVariableShaderTypeAlias(format);
4085
+ const decoded = UNIFORM_FORMATS[resolvedFormat];
4086
+ if (!decoded) {
4087
+ throw new Error(`Unsupported variable shader type: ${format}`);
4088
+ }
3349
4089
  return decoded;
3350
4090
  }
3351
4091
  function getAttributeShaderTypeInfo(attributeType) {
3352
- const [primitiveType, components] = TYPE_INFO[attributeType];
4092
+ const resolvedAttributeType = resolveAttributeShaderTypeAlias(attributeType);
4093
+ const decoded = TYPE_INFO[resolvedAttributeType];
4094
+ if (!decoded) {
4095
+ throw new Error(`Unsupported attribute shader type: ${attributeType}`);
4096
+ }
4097
+ const [primitiveType, components] = decoded;
3353
4098
  const integer = primitiveType === "i32" || primitiveType === "u32";
3354
4099
  const signed = primitiveType !== "u32";
3355
4100
  const byteLength = PRIMITIVE_TYPE_SIZES[primitiveType] * components;
@@ -3361,6 +4106,12 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3361
4106
  signed
3362
4107
  };
3363
4108
  }
4109
+ function resolveAttributeShaderTypeAlias(alias) {
4110
+ return WGSL_ATTRIBUTE_TYPE_ALIAS_MAP[alias] || alias;
4111
+ }
4112
+ function resolveVariableShaderTypeAlias(alias) {
4113
+ return WGSL_VARIABLE_TYPE_ALIAS_MAP[alias] || alias;
4114
+ }
3364
4115
  var PRIMITIVE_TYPE_SIZES = {
3365
4116
  f32: 4,
3366
4117
  f16: 2,
@@ -3685,7 +4436,9 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3685
4436
 
3686
4437
  // ../core/src/adapter/resources/fence.ts
3687
4438
  var _Fence = class extends Resource {
3688
- [Symbol.toStringTag] = "WEBGLFence";
4439
+ get [Symbol.toStringTag]() {
4440
+ return "Fence";
4441
+ }
3689
4442
  constructor(device, props = {}) {
3690
4443
  super(device, props, _Fence.defaultProps);
3691
4444
  }
@@ -3845,20 +4598,26 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3845
4598
  }
3846
4599
 
3847
4600
  // ../core/src/utils/array-equal.ts
4601
+ var MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH = 128;
3848
4602
  function arrayEqual(a, b, limit = 16) {
3849
- if (a !== b) {
3850
- return false;
4603
+ if (a === b) {
4604
+ return true;
3851
4605
  }
3852
4606
  const arrayA = a;
3853
4607
  const arrayB = b;
3854
- if (!isNumberArray(arrayA)) {
4608
+ if (!isNumberArray(arrayA) || !isNumberArray(arrayB)) {
3855
4609
  return false;
3856
4610
  }
3857
- if (isNumberArray(arrayB) && arrayA.length === arrayB.length) {
3858
- for (let i = 0; i < arrayA.length; ++i) {
3859
- if (arrayB[i] !== arrayA[i]) {
3860
- return false;
3861
- }
4611
+ if (arrayA.length !== arrayB.length) {
4612
+ return false;
4613
+ }
4614
+ const maxCompareLength = Math.min(limit, MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH);
4615
+ if (arrayA.length > maxCompareLength) {
4616
+ return false;
4617
+ }
4618
+ for (let i = 0; i < arrayA.length; ++i) {
4619
+ if (arrayB[i] !== arrayA[i]) {
4620
+ return false;
3862
4621
  }
3863
4622
  }
3864
4623
  return true;