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

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 +1934 -415
  2. package/dist/dist.min.js +6 -6
  3. package/dist/index.cjs +736 -285
  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 +1 -1
  10. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.d.ts.map +1 -1
  11. package/dist/passes/postprocessing/image-adjust-filters/brightnesscontrast.js +5 -6
  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 +1 -1
  18. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.d.ts.map +1 -1
  19. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.js +25 -32
  20. package/dist/passes/postprocessing/image-adjust-filters/huesaturation.js.map +1 -1
  21. package/dist/passes/postprocessing/image-adjust-filters/noise.d.ts +1 -1
  22. package/dist/passes/postprocessing/image-adjust-filters/noise.d.ts.map +1 -1
  23. package/dist/passes/postprocessing/image-adjust-filters/noise.js +9 -8
  24. package/dist/passes/postprocessing/image-adjust-filters/noise.js.map +1 -1
  25. package/dist/passes/postprocessing/image-adjust-filters/sepia.d.ts +1 -1
  26. package/dist/passes/postprocessing/image-adjust-filters/sepia.d.ts.map +1 -1
  27. package/dist/passes/postprocessing/image-adjust-filters/sepia.js +11 -10
  28. package/dist/passes/postprocessing/image-adjust-filters/sepia.js.map +1 -1
  29. package/dist/passes/postprocessing/image-adjust-filters/vibrance.d.ts +1 -1
  30. package/dist/passes/postprocessing/image-adjust-filters/vibrance.js +9 -9
  31. package/dist/passes/postprocessing/image-adjust-filters/vignette.d.ts +1 -1
  32. package/dist/passes/postprocessing/image-adjust-filters/vignette.d.ts.map +1 -1
  33. package/dist/passes/postprocessing/image-adjust-filters/vignette.js +10 -14
  34. package/dist/passes/postprocessing/image-adjust-filters/vignette.js.map +1 -1
  35. package/dist/passes/postprocessing/image-blur-filters/tiltshift.d.ts +2 -2
  36. package/dist/passes/postprocessing/image-blur-filters/tiltshift.d.ts.map +1 -1
  37. package/dist/passes/postprocessing/image-blur-filters/tiltshift.js +35 -17
  38. package/dist/passes/postprocessing/image-blur-filters/tiltshift.js.map +1 -1
  39. package/dist/passes/postprocessing/image-blur-filters/triangleblur.d.ts +2 -2
  40. package/dist/passes/postprocessing/image-blur-filters/triangleblur.d.ts.map +1 -1
  41. package/dist/passes/postprocessing/image-blur-filters/triangleblur.js +26 -17
  42. package/dist/passes/postprocessing/image-blur-filters/triangleblur.js.map +1 -1
  43. package/dist/passes/postprocessing/image-blur-filters/zoomblur.d.ts +2 -2
  44. package/dist/passes/postprocessing/image-blur-filters/zoomblur.d.ts.map +1 -1
  45. package/dist/passes/postprocessing/image-blur-filters/zoomblur.js +21 -12
  46. package/dist/passes/postprocessing/image-blur-filters/zoomblur.js.map +1 -1
  47. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.d.ts +1 -1
  48. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.d.ts.map +1 -1
  49. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.js +19 -17
  50. package/dist/passes/postprocessing/image-fun-filters/colorhalftone.js.map +1 -1
  51. package/dist/passes/postprocessing/image-fun-filters/dotscreen.d.ts +1 -1
  52. package/dist/passes/postprocessing/image-fun-filters/dotscreen.js +11 -11
  53. package/dist/passes/postprocessing/image-fun-filters/edgework.d.ts +2 -2
  54. package/dist/passes/postprocessing/image-fun-filters/edgework.d.ts.map +1 -1
  55. package/dist/passes/postprocessing/image-fun-filters/edgework.js +84 -13
  56. package/dist/passes/postprocessing/image-fun-filters/edgework.js.map +1 -1
  57. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.d.ts +1 -1
  58. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.d.ts.map +1 -1
  59. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.js +34 -22
  60. package/dist/passes/postprocessing/image-fun-filters/hexagonalpixelate.js.map +1 -1
  61. package/dist/passes/postprocessing/image-fun-filters/ink.d.ts +1 -1
  62. package/dist/passes/postprocessing/image-fun-filters/ink.d.ts.map +1 -1
  63. package/dist/passes/postprocessing/image-fun-filters/ink.js +25 -16
  64. package/dist/passes/postprocessing/image-fun-filters/ink.js.map +1 -1
  65. package/dist/passes/postprocessing/image-fun-filters/magnify.d.ts +1 -1
  66. package/dist/passes/postprocessing/image-fun-filters/magnify.d.ts.map +1 -1
  67. package/dist/passes/postprocessing/image-fun-filters/magnify.js +22 -12
  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 +27 -13
  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 +20 -15
  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 +3 -3
  82. package/src/passes/postprocessing/fxaa/fxaa.ts +288 -0
  83. package/src/passes/postprocessing/image-adjust-filters/brightnesscontrast.ts +5 -6
  84. package/src/passes/postprocessing/image-adjust-filters/denoise.ts +34 -26
  85. package/src/passes/postprocessing/image-adjust-filters/huesaturation.ts +27 -34
  86. package/src/passes/postprocessing/image-adjust-filters/noise.ts +9 -8
  87. package/src/passes/postprocessing/image-adjust-filters/sepia.ts +11 -10
  88. package/src/passes/postprocessing/image-adjust-filters/vibrance.ts +9 -9
  89. package/src/passes/postprocessing/image-adjust-filters/vignette.ts +10 -14
  90. package/src/passes/postprocessing/image-blur-filters/tiltshift.ts +37 -19
  91. package/src/passes/postprocessing/image-blur-filters/triangleblur.ts +26 -17
  92. package/src/passes/postprocessing/image-blur-filters/zoomblur.ts +22 -13
  93. package/src/passes/postprocessing/image-fun-filters/colorhalftone.ts +19 -17
  94. package/src/passes/postprocessing/image-fun-filters/dotscreen.ts +11 -11
  95. package/src/passes/postprocessing/image-fun-filters/edgework.ts +85 -14
  96. package/src/passes/postprocessing/image-fun-filters/hexagonalpixelate.ts +38 -26
  97. package/src/passes/postprocessing/image-fun-filters/ink.ts +25 -16
  98. package/src/passes/postprocessing/image-fun-filters/magnify.ts +22 -12
  99. package/src/passes/postprocessing/image-warp-filters/bulgepinch.ts +27 -13
  100. package/src/passes/postprocessing/image-warp-filters/swirl.ts +20 -15
  101. package/src/passes/postprocessing/image-warp-filters/warp.ts +9 -4
package/dist/dist.dev.js CHANGED
@@ -66,44 +66,48 @@ var __exports__ = (() => {
66
66
  ExternalTexture: () => ExternalTexture,
67
67
  Fence: () => Fence,
68
68
  Framebuffer: () => Framebuffer,
69
+ PipelineFactory: () => PipelineFactory,
69
70
  PipelineLayout: () => PipelineLayout,
71
+ PresentationContext: () => PresentationContext,
70
72
  QuerySet: () => QuerySet,
71
73
  RenderPass: () => RenderPass,
72
74
  RenderPipeline: () => RenderPipeline,
73
75
  Resource: () => Resource,
74
76
  Sampler: () => Sampler,
75
77
  Shader: () => Shader,
78
+ ShaderFactory: () => ShaderFactory,
79
+ SharedRenderPipeline: () => SharedRenderPipeline,
76
80
  Texture: () => Texture,
77
- TextureFormatDecoder: () => TextureFormatDecoder,
78
81
  TextureView: () => TextureView,
79
82
  TransformFeedback: () => TransformFeedback,
80
83
  UniformBlock: () => UniformBlock,
81
84
  UniformBufferLayout: () => UniformBufferLayout,
82
85
  UniformStore: () => UniformStore,
83
86
  VertexArray: () => VertexArray,
87
+ _getDefaultBindGroupFactory: () => _getDefaultBindGroupFactory,
84
88
  _getTextureFormatDefinition: () => getTextureFormatDefinition,
85
89
  _getTextureFormatTable: () => getTextureFormatTable,
86
90
  assert: () => assert2,
87
91
  assertDefined: () => assertDefined,
92
+ dataTypeDecoder: () => dataTypeDecoder,
93
+ flattenBindingsByGroup: () => flattenBindingsByGroup,
88
94
  getAttributeInfosFromLayouts: () => getAttributeInfosFromLayouts,
89
95
  getAttributeShaderTypeInfo: () => getAttributeShaderTypeInfo,
90
- getDataType: () => getDataType,
91
- getDataTypeInfo: () => getDataTypeInfo,
92
96
  getExternalImageSize: () => getExternalImageSize,
93
- getNormalizedDataType: () => getNormalizedDataType,
94
97
  getScratchArray: () => getScratchArray,
98
+ getShaderLayoutBinding: () => getShaderLayoutBinding,
95
99
  getTextureImageView: () => getTextureImageView,
96
100
  getTypedArrayConstructor: () => getTypedArrayConstructor,
97
101
  getVariableShaderTypeInfo: () => getVariableShaderTypeInfo,
98
- getVertexFormatFromAttribute: () => getVertexFormatFromAttribute,
99
- getVertexFormatInfo: () => getVertexFormatInfo,
100
102
  isExternalImage: () => isExternalImage,
101
103
  log: () => log,
102
104
  luma: () => luma,
103
- makeVertexFormat: () => makeVertexFormat,
105
+ normalizeBindingsByGroup: () => normalizeBindingsByGroup,
104
106
  readPixel: () => readPixel,
105
107
  setTextureImageData: () => setTextureImageData,
108
+ shaderTypeDecoder: () => shaderTypeDecoder,
106
109
  textureFormatDecoder: () => textureFormatDecoder,
110
+ vertexFormatDecoder: () => vertexFormatDecoder,
107
111
  writePixel: () => writePixel
108
112
  });
109
113
 
@@ -301,6 +305,24 @@ var __exports__ = (() => {
301
305
  };
302
306
 
303
307
  // ../core/src/utils/stats-manager.ts
308
+ var GPU_TIME_AND_MEMORY_STATS = "GPU Time and Memory";
309
+ var GPU_TIME_AND_MEMORY_STAT_ORDER = [
310
+ "Adapter",
311
+ "GPU",
312
+ "GPU Type",
313
+ "GPU Backend",
314
+ "Frame Rate",
315
+ "CPU Time",
316
+ "GPU Time",
317
+ "GPU Memory",
318
+ "Buffer Memory",
319
+ "Texture Memory",
320
+ "Referenced Buffer Memory",
321
+ "Referenced Texture Memory",
322
+ "Swap Chain Texture"
323
+ ];
324
+ var ORDERED_STATS_CACHE = /* @__PURE__ */ new WeakMap();
325
+ var ORDERED_STAT_NAME_SET_CACHE = /* @__PURE__ */ new WeakMap();
304
326
  var StatsManager = class {
305
327
  stats = /* @__PURE__ */ new Map();
306
328
  getStats(name2) {
@@ -310,10 +332,50 @@ var __exports__ = (() => {
310
332
  if (!this.stats.has(name2)) {
311
333
  this.stats.set(name2, new Stats({ id: name2 }));
312
334
  }
313
- return this.stats.get(name2);
335
+ const stats = this.stats.get(name2);
336
+ if (name2 === GPU_TIME_AND_MEMORY_STATS) {
337
+ initializeStats(stats, GPU_TIME_AND_MEMORY_STAT_ORDER);
338
+ }
339
+ return stats;
314
340
  }
315
341
  };
316
342
  var lumaStats = new StatsManager();
343
+ function initializeStats(stats, orderedStatNames) {
344
+ const statsMap = stats.stats;
345
+ let addedOrderedStat = false;
346
+ for (const statName of orderedStatNames) {
347
+ if (!statsMap[statName]) {
348
+ stats.get(statName);
349
+ addedOrderedStat = true;
350
+ }
351
+ }
352
+ const statCount = Object.keys(statsMap).length;
353
+ const cachedStats = ORDERED_STATS_CACHE.get(stats);
354
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
355
+ return;
356
+ }
357
+ const reorderedStats = {};
358
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE.get(orderedStatNames);
359
+ if (!orderedStatNamesSet) {
360
+ orderedStatNamesSet = new Set(orderedStatNames);
361
+ ORDERED_STAT_NAME_SET_CACHE.set(orderedStatNames, orderedStatNamesSet);
362
+ }
363
+ for (const statName of orderedStatNames) {
364
+ if (statsMap[statName]) {
365
+ reorderedStats[statName] = statsMap[statName];
366
+ }
367
+ }
368
+ for (const [statName, stat] of Object.entries(statsMap)) {
369
+ if (!orderedStatNamesSet.has(statName)) {
370
+ reorderedStats[statName] = stat;
371
+ }
372
+ }
373
+ for (const statName of Object.keys(statsMap)) {
374
+ delete statsMap[statName];
375
+ }
376
+ Object.assign(statsMap, reorderedStats);
377
+ ORDERED_STATS_CACHE.set(stats, { orderedStatNames, statCount });
378
+ }
317
379
 
318
380
  // ../../node_modules/@probe.gl/env/dist/lib/globals.js
319
381
  var window_ = globalThis;
@@ -848,6 +910,57 @@ var __exports__ = (() => {
848
910
  }
849
911
 
850
912
  // ../core/src/adapter/resources/resource.ts
913
+ var CPU_HOTSPOT_PROFILER_MODULE = "cpu-hotspot-profiler";
914
+ var RESOURCE_COUNTS_STATS = "GPU Resource Counts";
915
+ var LEGACY_RESOURCE_COUNTS_STATS = "Resource Counts";
916
+ var GPU_TIME_AND_MEMORY_STATS2 = "GPU Time and Memory";
917
+ var BASE_RESOURCE_COUNT_ORDER = [
918
+ "Resources",
919
+ "Buffers",
920
+ "Textures",
921
+ "Samplers",
922
+ "TextureViews",
923
+ "Framebuffers",
924
+ "QuerySets",
925
+ "Shaders",
926
+ "RenderPipelines",
927
+ "ComputePipelines",
928
+ "PipelineLayouts",
929
+ "VertexArrays",
930
+ "RenderPasss",
931
+ "ComputePasss",
932
+ "CommandEncoders",
933
+ "CommandBuffers"
934
+ ];
935
+ var WEBGL_RESOURCE_COUNT_ORDER = [
936
+ "Resources",
937
+ "Buffers",
938
+ "Textures",
939
+ "Samplers",
940
+ "TextureViews",
941
+ "Framebuffers",
942
+ "QuerySets",
943
+ "Shaders",
944
+ "RenderPipelines",
945
+ "SharedRenderPipelines",
946
+ "ComputePipelines",
947
+ "PipelineLayouts",
948
+ "VertexArrays",
949
+ "RenderPasss",
950
+ "ComputePasss",
951
+ "CommandEncoders",
952
+ "CommandBuffers"
953
+ ];
954
+ var BASE_RESOURCE_COUNT_STAT_ORDER = BASE_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
955
+ `${resourceType} Created`,
956
+ `${resourceType} Active`
957
+ ]);
958
+ var WEBGL_RESOURCE_COUNT_STAT_ORDER = WEBGL_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
959
+ `${resourceType} Created`,
960
+ `${resourceType} Active`
961
+ ]);
962
+ var ORDERED_STATS_CACHE2 = /* @__PURE__ */ new WeakMap();
963
+ var ORDERED_STAT_NAME_SET_CACHE2 = /* @__PURE__ */ new WeakMap();
851
964
  var Resource = class {
852
965
  toString() {
853
966
  return `${this[Symbol.toStringTag] || this.constructor.name}:"${this.id}"`;
@@ -864,6 +977,8 @@ var __exports__ = (() => {
864
977
  destroyed = false;
865
978
  /** For resources that allocate GPU memory */
866
979
  allocatedBytes = 0;
980
+ /** Stats bucket currently holding the tracked allocation */
981
+ allocatedBytesName = null;
867
982
  /** Attached resources will be destroyed when this resource is destroyed. Tracks auto-created "sub" resources. */
868
983
  _attachedResources = /* @__PURE__ */ new Set();
869
984
  /**
@@ -885,6 +1000,9 @@ var __exports__ = (() => {
885
1000
  * destroy can be called on any resource to release it before it is garbage collected.
886
1001
  */
887
1002
  destroy() {
1003
+ if (this.destroyed) {
1004
+ return;
1005
+ }
888
1006
  this.destroyResource();
889
1007
  }
890
1008
  /** @deprecated Use destroy() */
@@ -923,7 +1041,7 @@ var __exports__ = (() => {
923
1041
  }
924
1042
  /** Destroy all owned resources. Make sure the resources are no longer needed before calling. */
925
1043
  destroyAttachedResources() {
926
- for (const resource of Object.values(this._attachedResources)) {
1044
+ for (const resource of this._attachedResources) {
927
1045
  resource.destroy();
928
1046
  }
929
1047
  this._attachedResources = /* @__PURE__ */ new Set();
@@ -931,37 +1049,107 @@ var __exports__ = (() => {
931
1049
  // PROTECTED METHODS
932
1050
  /** Perform all destroy steps. Can be called by derived resources when overriding destroy() */
933
1051
  destroyResource() {
1052
+ if (this.destroyed) {
1053
+ return;
1054
+ }
934
1055
  this.destroyAttachedResources();
935
1056
  this.removeStats();
936
1057
  this.destroyed = true;
937
1058
  }
938
1059
  /** Called by .destroy() to track object destruction. Subclass must call if overriding destroy() */
939
1060
  removeStats() {
940
- const stats = this._device.statsManager.getStats("Resource Counts");
941
- const name2 = this[Symbol.toStringTag];
942
- stats.get(`${name2}s Active`).decrementCount();
1061
+ const profiler = getCpuHotspotProfiler(this._device);
1062
+ const startTime = profiler ? getTimestamp() : 0;
1063
+ const statsObjects = [
1064
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1065
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1066
+ ];
1067
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1068
+ for (const stats of statsObjects) {
1069
+ initializeStats2(stats, orderedStatNames);
1070
+ }
1071
+ const name2 = this.getStatsName();
1072
+ for (const stats of statsObjects) {
1073
+ stats.get("Resources Active").decrementCount();
1074
+ stats.get(`${name2}s Active`).decrementCount();
1075
+ }
1076
+ if (profiler) {
1077
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1078
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1079
+ }
943
1080
  }
944
1081
  /** Called by subclass to track memory allocations */
945
- trackAllocatedMemory(bytes, name2 = this[Symbol.toStringTag]) {
946
- const stats = this._device.statsManager.getStats("Resource Counts");
1082
+ trackAllocatedMemory(bytes, name2 = this.getStatsName()) {
1083
+ const profiler = getCpuHotspotProfiler(this._device);
1084
+ const startTime = profiler ? getTimestamp() : 0;
1085
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
1086
+ if (this.allocatedBytes > 0 && this.allocatedBytesName) {
1087
+ stats.get("GPU Memory").subtractCount(this.allocatedBytes);
1088
+ stats.get(`${this.allocatedBytesName} Memory`).subtractCount(this.allocatedBytes);
1089
+ }
947
1090
  stats.get("GPU Memory").addCount(bytes);
948
1091
  stats.get(`${name2} Memory`).addCount(bytes);
1092
+ if (profiler) {
1093
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1094
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1095
+ }
949
1096
  this.allocatedBytes = bytes;
1097
+ this.allocatedBytesName = name2;
1098
+ }
1099
+ /** Called by subclass to track handle-backed memory allocations separately from owned allocations */
1100
+ trackReferencedMemory(bytes, name2 = this.getStatsName()) {
1101
+ this.trackAllocatedMemory(bytes, `Referenced ${name2}`);
950
1102
  }
951
1103
  /** Called by subclass to track memory deallocations */
952
- trackDeallocatedMemory(name2 = this[Symbol.toStringTag]) {
953
- const stats = this._device.statsManager.getStats("Resource Counts");
1104
+ trackDeallocatedMemory(name2 = this.getStatsName()) {
1105
+ if (this.allocatedBytes === 0) {
1106
+ this.allocatedBytesName = null;
1107
+ return;
1108
+ }
1109
+ const profiler = getCpuHotspotProfiler(this._device);
1110
+ const startTime = profiler ? getTimestamp() : 0;
1111
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
954
1112
  stats.get("GPU Memory").subtractCount(this.allocatedBytes);
955
- stats.get(`${name2} Memory`).subtractCount(this.allocatedBytes);
1113
+ stats.get(`${this.allocatedBytesName || name2} Memory`).subtractCount(this.allocatedBytes);
1114
+ if (profiler) {
1115
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1116
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1117
+ }
956
1118
  this.allocatedBytes = 0;
1119
+ this.allocatedBytesName = null;
1120
+ }
1121
+ /** Called by subclass to deallocate handle-backed memory tracked via trackReferencedMemory() */
1122
+ trackDeallocatedReferencedMemory(name2 = this.getStatsName()) {
1123
+ this.trackDeallocatedMemory(`Referenced ${name2}`);
957
1124
  }
958
1125
  /** Called by resource constructor to track object creation */
959
1126
  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();
1127
+ const name2 = this.getStatsName();
1128
+ const profiler = getCpuHotspotProfiler(this._device);
1129
+ const startTime = profiler ? getTimestamp() : 0;
1130
+ const statsObjects = [
1131
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1132
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1133
+ ];
1134
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1135
+ for (const stats of statsObjects) {
1136
+ initializeStats2(stats, orderedStatNames);
1137
+ }
1138
+ for (const stats of statsObjects) {
1139
+ stats.get("Resources Created").incrementCount();
1140
+ stats.get("Resources Active").incrementCount();
1141
+ stats.get(`${name2}s Created`).incrementCount();
1142
+ stats.get(`${name2}s Active`).incrementCount();
1143
+ }
1144
+ if (profiler) {
1145
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1146
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1147
+ }
1148
+ recordTransientCanvasResourceCreate(this._device, name2);
1149
+ }
1150
+ /** Canonical resource name used for stats buckets. */
1151
+ getStatsName() {
1152
+ return getCanonicalResourceName(this);
965
1153
  }
966
1154
  };
967
1155
  /** Default properties for resource */
@@ -979,6 +1167,96 @@ var __exports__ = (() => {
979
1167
  }
980
1168
  return mergedProps;
981
1169
  }
1170
+ function initializeStats2(stats, orderedStatNames) {
1171
+ const statsMap = stats.stats;
1172
+ let addedOrderedStat = false;
1173
+ for (const statName of orderedStatNames) {
1174
+ if (!statsMap[statName]) {
1175
+ stats.get(statName);
1176
+ addedOrderedStat = true;
1177
+ }
1178
+ }
1179
+ const statCount = Object.keys(statsMap).length;
1180
+ const cachedStats = ORDERED_STATS_CACHE2.get(stats);
1181
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
1182
+ return;
1183
+ }
1184
+ const reorderedStats = {};
1185
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE2.get(orderedStatNames);
1186
+ if (!orderedStatNamesSet) {
1187
+ orderedStatNamesSet = new Set(orderedStatNames);
1188
+ ORDERED_STAT_NAME_SET_CACHE2.set(orderedStatNames, orderedStatNamesSet);
1189
+ }
1190
+ for (const statName of orderedStatNames) {
1191
+ if (statsMap[statName]) {
1192
+ reorderedStats[statName] = statsMap[statName];
1193
+ }
1194
+ }
1195
+ for (const [statName, stat] of Object.entries(statsMap)) {
1196
+ if (!orderedStatNamesSet.has(statName)) {
1197
+ reorderedStats[statName] = stat;
1198
+ }
1199
+ }
1200
+ for (const statName of Object.keys(statsMap)) {
1201
+ delete statsMap[statName];
1202
+ }
1203
+ Object.assign(statsMap, reorderedStats);
1204
+ ORDERED_STATS_CACHE2.set(stats, { orderedStatNames, statCount });
1205
+ }
1206
+ function getResourceCountStatOrder(device) {
1207
+ return device.type === "webgl" ? WEBGL_RESOURCE_COUNT_STAT_ORDER : BASE_RESOURCE_COUNT_STAT_ORDER;
1208
+ }
1209
+ function getCpuHotspotProfiler(device) {
1210
+ const profiler = device.userData[CPU_HOTSPOT_PROFILER_MODULE];
1211
+ return profiler?.enabled ? profiler : null;
1212
+ }
1213
+ function getTimestamp() {
1214
+ return globalThis.performance?.now?.() ?? Date.now();
1215
+ }
1216
+ function recordTransientCanvasResourceCreate(device, name2) {
1217
+ const profiler = getCpuHotspotProfiler(device);
1218
+ if (!profiler || !profiler.activeDefaultFramebufferAcquireDepth) {
1219
+ return;
1220
+ }
1221
+ profiler.transientCanvasResourceCreates = (profiler.transientCanvasResourceCreates || 0) + 1;
1222
+ switch (name2) {
1223
+ case "Texture":
1224
+ profiler.transientCanvasTextureCreates = (profiler.transientCanvasTextureCreates || 0) + 1;
1225
+ break;
1226
+ case "TextureView":
1227
+ profiler.transientCanvasTextureViewCreates = (profiler.transientCanvasTextureViewCreates || 0) + 1;
1228
+ break;
1229
+ case "Sampler":
1230
+ profiler.transientCanvasSamplerCreates = (profiler.transientCanvasSamplerCreates || 0) + 1;
1231
+ break;
1232
+ case "Framebuffer":
1233
+ profiler.transientCanvasFramebufferCreates = (profiler.transientCanvasFramebufferCreates || 0) + 1;
1234
+ break;
1235
+ default:
1236
+ break;
1237
+ }
1238
+ }
1239
+ function getCanonicalResourceName(resource) {
1240
+ let prototype = Object.getPrototypeOf(resource);
1241
+ while (prototype) {
1242
+ const parentPrototype = Object.getPrototypeOf(prototype);
1243
+ if (!parentPrototype || parentPrototype === Resource.prototype) {
1244
+ return getPrototypeToStringTag(prototype) || resource[Symbol.toStringTag] || resource.constructor.name;
1245
+ }
1246
+ prototype = parentPrototype;
1247
+ }
1248
+ return resource[Symbol.toStringTag] || resource.constructor.name;
1249
+ }
1250
+ function getPrototypeToStringTag(prototype) {
1251
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, Symbol.toStringTag);
1252
+ if (typeof descriptor?.get === "function") {
1253
+ return descriptor.get.call(prototype);
1254
+ }
1255
+ if (typeof descriptor?.value === "string") {
1256
+ return descriptor.value;
1257
+ }
1258
+ return null;
1259
+ }
982
1260
 
983
1261
  // ../core/src/adapter/resources/buffer.ts
984
1262
  var _Buffer = class extends Resource {
@@ -1018,18 +1296,26 @@ var __exports__ = (() => {
1018
1296
  /** A partial CPU-side copy of the data in this buffer, for debugging purposes */
1019
1297
  debugData = new ArrayBuffer(0);
1020
1298
  /** This doesn't handle partial non-zero offset updates correctly */
1021
- _setDebugData(data, byteOffset, byteLength) {
1022
- const arrayBuffer2 = ArrayBuffer.isView(data) ? data.buffer : data;
1299
+ _setDebugData(data, _byteOffset, byteLength) {
1300
+ let arrayBufferView = null;
1301
+ let arrayBuffer2;
1302
+ if (ArrayBuffer.isView(data)) {
1303
+ arrayBufferView = data;
1304
+ arrayBuffer2 = data.buffer;
1305
+ } else {
1306
+ arrayBuffer2 = data;
1307
+ }
1023
1308
  const debugDataLength = Math.min(
1024
1309
  data ? data.byteLength : byteLength,
1025
1310
  _Buffer.DEBUG_DATA_MAX_LENGTH
1026
1311
  );
1027
1312
  if (arrayBuffer2 === null) {
1028
1313
  this.debugData = new ArrayBuffer(debugDataLength);
1029
- } else if (byteOffset === 0 && byteLength === arrayBuffer2.byteLength) {
1030
- this.debugData = arrayBuffer2.slice(0, debugDataLength);
1031
1314
  } else {
1032
- this.debugData = arrayBuffer2.slice(byteOffset, byteOffset + debugDataLength);
1315
+ const sourceByteOffset = Math.min(arrayBufferView?.byteOffset || 0, arrayBuffer2.byteLength);
1316
+ const availableByteLength = Math.max(0, arrayBuffer2.byteLength - sourceByteOffset);
1317
+ const copyByteLength = Math.min(debugDataLength, availableByteLength);
1318
+ this.debugData = new Uint8Array(arrayBuffer2, sourceByteOffset, copyByteLength).slice().buffer;
1033
1319
  }
1034
1320
  }
1035
1321
  };
@@ -1063,62 +1349,73 @@ var __exports__ = (() => {
1063
1349
  onMapped: void 0
1064
1350
  });
1065
1351
 
1066
- // ../core/src/shadertypes/data-types/decode-data-types.ts
1067
- function getDataTypeInfo(type) {
1068
- const normalized = type.includes("norm");
1069
- const integer = !normalized && !type.startsWith("float");
1070
- const signed = type.startsWith("s");
1071
- const typeInfo = NORMALIZED_TYPE_MAP[type];
1072
- const [signedType, primitiveType, byteLength] = typeInfo || ["uint8 ", "i32", 1];
1073
- return {
1074
- signedType,
1075
- primitiveType,
1076
- byteLength,
1077
- normalized,
1078
- integer,
1079
- signed
1080
- };
1081
- }
1082
- function getNormalizedDataType(signedDataType) {
1083
- const dataType = signedDataType;
1084
- switch (dataType) {
1085
- case "uint8":
1086
- return "unorm8";
1087
- case "sint8":
1088
- return "snorm8";
1089
- case "uint16":
1090
- return "unorm16";
1091
- case "sint16":
1092
- return "snorm16";
1093
- default:
1094
- return dataType;
1352
+ // ../core/src/shadertypes/data-types/data-type-decoder.ts
1353
+ var DataTypeDecoder = class {
1354
+ /**
1355
+ * Gets info about a data type constant (signed or normalized)
1356
+ * @returns underlying primitive / signed types, byte length, normalization, integer, signed flags
1357
+ */
1358
+ getDataTypeInfo(type) {
1359
+ const [signedType, primitiveType, byteLength] = NORMALIZED_TYPE_MAP[type];
1360
+ const normalized = type.includes("norm");
1361
+ const integer = !normalized && !type.startsWith("float");
1362
+ const signed = type.startsWith("s");
1363
+ return {
1364
+ signedType,
1365
+ primitiveType,
1366
+ byteLength,
1367
+ normalized,
1368
+ integer,
1369
+ signed
1370
+ // TODO - add webglOnly flag
1371
+ };
1095
1372
  }
1096
- }
1097
- function alignTo(size, count) {
1098
- switch (count) {
1099
- case 1:
1100
- return size;
1101
- case 2:
1102
- return size + size % 2;
1103
- default:
1104
- return size + (4 - size % 4) % 4;
1373
+ /** Build a vertex format from a signed data type and a component */
1374
+ getNormalizedDataType(signedDataType) {
1375
+ const dataType = signedDataType;
1376
+ switch (dataType) {
1377
+ case "uint8":
1378
+ return "unorm8";
1379
+ case "sint8":
1380
+ return "snorm8";
1381
+ case "uint16":
1382
+ return "unorm16";
1383
+ case "sint16":
1384
+ return "snorm16";
1385
+ default:
1386
+ return dataType;
1387
+ }
1105
1388
  }
1106
- }
1107
- function getDataType(arrayOrType) {
1108
- const Constructor = ArrayBuffer.isView(arrayOrType) ? arrayOrType.constructor : arrayOrType;
1109
- if (Constructor === Uint8ClampedArray) {
1110
- return "uint8";
1389
+ /** Align offset to 1, 2 or 4 elements (4, 8 or 16 bytes) */
1390
+ alignTo(size, count) {
1391
+ switch (count) {
1392
+ case 1:
1393
+ return size;
1394
+ case 2:
1395
+ return size + size % 2;
1396
+ default:
1397
+ return size + (4 - size % 4) % 4;
1398
+ }
1111
1399
  }
1112
- const info = Object.values(NORMALIZED_TYPE_MAP).find((entry) => Constructor === entry[4]);
1113
- if (!info) {
1114
- throw new Error(Constructor.name);
1400
+ /** Returns the VariableShaderType that corresponds to a typed array */
1401
+ getDataType(arrayOrType) {
1402
+ const Constructor = ArrayBuffer.isView(arrayOrType) ? arrayOrType.constructor : arrayOrType;
1403
+ if (Constructor === Uint8ClampedArray) {
1404
+ return "uint8";
1405
+ }
1406
+ const info = Object.values(NORMALIZED_TYPE_MAP).find((entry) => Constructor === entry[4]);
1407
+ if (!info) {
1408
+ throw new Error(Constructor.name);
1409
+ }
1410
+ return info[0];
1115
1411
  }
1116
- return info[0];
1117
- }
1118
- function getTypedArrayConstructor(type) {
1119
- const [, , , , Constructor] = NORMALIZED_TYPE_MAP[type];
1120
- return Constructor;
1121
- }
1412
+ /** Returns the TypedArray that corresponds to a shader data type */
1413
+ getTypedArrayConstructor(type) {
1414
+ const [, , , , Constructor] = NORMALIZED_TYPE_MAP[type];
1415
+ return Constructor;
1416
+ }
1417
+ };
1418
+ var dataTypeDecoder = new DataTypeDecoder();
1122
1419
  var NORMALIZED_TYPE_MAP = {
1123
1420
  uint8: ["uint8", "u32", 1, false, Uint8Array],
1124
1421
  sint8: ["sint8", "i32", 1, false, Int8Array],
@@ -1134,87 +1431,99 @@ var __exports__ = (() => {
1134
1431
  sint32: ["sint32", "i32", 4, false, Int32Array]
1135
1432
  };
1136
1433
 
1137
- // ../core/src/shadertypes/vertex-arrays/decode-vertex-format.ts
1138
- function getVertexFormatInfo(format) {
1139
- let webglOnly;
1140
- if (format.endsWith("-webgl")) {
1141
- format.replace("-webgl", "");
1142
- webglOnly = true;
1143
- }
1144
- const [type_, count] = format.split("x");
1145
- const type = type_;
1146
- const components = count ? parseInt(count) : 1;
1147
- const decodedType = getDataTypeInfo(type);
1148
- const result = {
1149
- type,
1150
- components,
1151
- byteLength: decodedType.byteLength * components,
1152
- integer: decodedType.integer,
1153
- signed: decodedType.signed,
1154
- normalized: decodedType.normalized
1155
- };
1156
- if (webglOnly) {
1157
- result.webglOnly = true;
1158
- }
1159
- return result;
1160
- }
1161
- function makeVertexFormat(signedDataType, components, normalized) {
1162
- const dataType = normalized ? getNormalizedDataType(signedDataType) : signedDataType;
1163
- switch (dataType) {
1164
- case "unorm8":
1165
- if (components === 1) {
1166
- return "unorm8";
1167
- }
1168
- if (components === 3) {
1169
- return "unorm8x3-webgl";
1170
- }
1171
- return `${dataType}x${components}`;
1172
- case "snorm8":
1173
- case "uint8":
1174
- case "sint8":
1175
- case "uint16":
1176
- case "sint16":
1177
- case "unorm16":
1178
- case "snorm16":
1179
- case "float16":
1180
- if (components === 1 || components === 3) {
1181
- throw new Error(`size: ${components}`);
1182
- }
1183
- return `${dataType}x${components}`;
1184
- default:
1185
- return components === 1 ? dataType : `${dataType}x${components}`;
1434
+ // ../core/src/shadertypes/vertex-types/vertex-format-decoder.ts
1435
+ var VertexFormatDecoder = class {
1436
+ /**
1437
+ * Decodes a vertex format, returning type, components, byte length and flags (integer, signed, normalized)
1438
+ */
1439
+ getVertexFormatInfo(format) {
1440
+ let webglOnly;
1441
+ if (format.endsWith("-webgl")) {
1442
+ format.replace("-webgl", "");
1443
+ webglOnly = true;
1444
+ }
1445
+ const [type_, count] = format.split("x");
1446
+ const type = type_;
1447
+ const components = count ? parseInt(count) : 1;
1448
+ const decodedType = dataTypeDecoder.getDataTypeInfo(type);
1449
+ const result = {
1450
+ type,
1451
+ components,
1452
+ byteLength: decodedType.byteLength * components,
1453
+ integer: decodedType.integer,
1454
+ signed: decodedType.signed,
1455
+ normalized: decodedType.normalized
1456
+ };
1457
+ if (webglOnly) {
1458
+ result.webglOnly = true;
1459
+ }
1460
+ return result;
1186
1461
  }
1187
- }
1188
- function getVertexFormatFromAttribute(typedArray, size, normalized) {
1189
- if (!size || size > 4) {
1190
- throw new Error(`size ${size}`);
1462
+ /** Build a vertex format from a signed data type and a component */
1463
+ makeVertexFormat(signedDataType, components, normalized) {
1464
+ const dataType = normalized ? dataTypeDecoder.getNormalizedDataType(signedDataType) : signedDataType;
1465
+ switch (dataType) {
1466
+ case "unorm8":
1467
+ if (components === 1) {
1468
+ return "unorm8";
1469
+ }
1470
+ if (components === 3) {
1471
+ return "unorm8x3-webgl";
1472
+ }
1473
+ return `${dataType}x${components}`;
1474
+ case "snorm8":
1475
+ case "uint8":
1476
+ case "sint8":
1477
+ case "uint16":
1478
+ case "sint16":
1479
+ case "unorm16":
1480
+ case "snorm16":
1481
+ case "float16":
1482
+ if (components === 1 || components === 3) {
1483
+ throw new Error(`size: ${components}`);
1484
+ }
1485
+ return `${dataType}x${components}`;
1486
+ default:
1487
+ return components === 1 ? dataType : `${dataType}x${components}`;
1488
+ }
1191
1489
  }
1192
- const components = size;
1193
- const signedDataType = getDataType(typedArray);
1194
- return makeVertexFormat(signedDataType, components, normalized);
1195
- }
1196
- function getCompatibleVertexFormat(opts) {
1197
- let vertexType;
1198
- switch (opts.primitiveType) {
1199
- case "f32":
1200
- vertexType = "float32";
1201
- break;
1202
- case "i32":
1203
- vertexType = "sint32";
1204
- break;
1205
- case "u32":
1206
- vertexType = "uint32";
1207
- break;
1208
- case "f16":
1209
- return opts.components <= 2 ? "float16x2" : "float16x4";
1490
+ /** Get the vertex format for an attribute with TypedArray and size */
1491
+ getVertexFormatFromAttribute(typedArray, size, normalized) {
1492
+ if (!size || size > 4) {
1493
+ throw new Error(`size ${size}`);
1494
+ }
1495
+ const components = size;
1496
+ const signedDataType = dataTypeDecoder.getDataType(typedArray);
1497
+ return this.makeVertexFormat(signedDataType, components, normalized);
1210
1498
  }
1211
- if (opts.components === 1) {
1212
- return vertexType;
1499
+ /**
1500
+ * Return a "default" vertex format for a certain shader data type
1501
+ * The simplest vertex format that matches the shader attribute's data type
1502
+ */
1503
+ getCompatibleVertexFormat(opts) {
1504
+ let vertexType;
1505
+ switch (opts.primitiveType) {
1506
+ case "f32":
1507
+ vertexType = "float32";
1508
+ break;
1509
+ case "i32":
1510
+ vertexType = "sint32";
1511
+ break;
1512
+ case "u32":
1513
+ vertexType = "uint32";
1514
+ break;
1515
+ case "f16":
1516
+ return opts.components <= 2 ? "float16x2" : "float16x4";
1517
+ }
1518
+ if (opts.components === 1) {
1519
+ return vertexType;
1520
+ }
1521
+ return `${vertexType}x${opts.components}`;
1213
1522
  }
1214
- return `${vertexType}x${opts.components}`;
1215
- }
1523
+ };
1524
+ var vertexFormatDecoder = new VertexFormatDecoder();
1216
1525
 
1217
- // ../core/src/shadertypes/textures/texture-format-table.ts
1526
+ // ../core/src/shadertypes/texture-types/texture-format-table.ts
1218
1527
  var texture_compression_bc = "texture-compression-bc";
1219
1528
  var texture_compression_astc = "texture-compression-astc";
1220
1529
  var texture_compression_etc2 = "texture-compression-etc2";
@@ -1225,6 +1534,7 @@ var __exports__ = (() => {
1225
1534
  var float16_renderable = "float16-renderable-webgl";
1226
1535
  var rgb9e5ufloat_renderable = "rgb9e5ufloat-renderable-webgl";
1227
1536
  var snorm8_renderable = "snorm8-renderable-webgl";
1537
+ var norm16_webgl = "norm16-webgl";
1228
1538
  var norm16_renderable = "norm16-renderable-webgl";
1229
1539
  var snorm16_renderable = "snorm16-renderable-webgl";
1230
1540
  var float32_filterable = "float32-filterable";
@@ -1258,16 +1568,16 @@ var __exports__ = (() => {
1258
1568
  "rgba8sint": {},
1259
1569
  "bgra8unorm": {},
1260
1570
  "bgra8unorm-srgb": {},
1261
- "r16unorm": { f: norm16_renderable },
1262
- "rg16unorm": { render: norm16_renderable },
1263
- "rgb16unorm-webgl": { f: norm16_renderable },
1571
+ "r16unorm": { f: norm16_webgl, render: norm16_renderable },
1572
+ "rg16unorm": { f: norm16_webgl, render: norm16_renderable },
1573
+ "rgb16unorm-webgl": { f: norm16_webgl, render: false },
1264
1574
  // rgb not renderable
1265
- "rgba16unorm": { render: norm16_renderable },
1266
- "r16snorm": { f: snorm16_renderable },
1267
- "rg16snorm": { render: snorm16_renderable },
1268
- "rgb16snorm-webgl": { f: norm16_renderable },
1575
+ "rgba16unorm": { f: norm16_webgl, render: norm16_renderable },
1576
+ "r16snorm": { f: norm16_webgl, render: snorm16_renderable },
1577
+ "rg16snorm": { f: norm16_webgl, render: snorm16_renderable },
1578
+ "rgb16snorm-webgl": { f: norm16_webgl, render: false },
1269
1579
  // rgb not renderable
1270
- "rgba16snorm": { render: snorm16_renderable },
1580
+ "rgba16snorm": { f: norm16_webgl, render: snorm16_renderable },
1271
1581
  "r16uint": {},
1272
1582
  "rg16uint": {},
1273
1583
  "rgba16uint": {},
@@ -1370,7 +1680,7 @@ var __exports__ = (() => {
1370
1680
  // WEBGL_compressed_texture_pvrtc
1371
1681
  "pvrtc-rgb4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1372
1682
  "pvrtc-rgba4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1373
- "pvrtc-rbg2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1683
+ "pvrtc-rgb2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1374
1684
  "pvrtc-rgba2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1375
1685
  // WEBGL_compressed_texture_etc1
1376
1686
  "etc1-rbg-unorm-webgl": { f: texture_compression_etc1_webgl },
@@ -1384,7 +1694,7 @@ var __exports__ = (() => {
1384
1694
  ...TEXTURE_FORMAT_COMPRESSED_TABLE
1385
1695
  };
1386
1696
 
1387
- // ../core/src/shadertypes/textures/texture-format-decoder.ts
1697
+ // ../core/src/shadertypes/texture-types/texture-format-decoder.ts
1388
1698
  var RGB_FORMAT_REGEX = /^(r|rg|rgb|rgba|bgra)([0-9]*)([a-z]*)(-srgb)?(-webgl)?$/;
1389
1699
  var COLOR_FORMAT_PREFIXES = ["rgb", "rgba", "bgra"];
1390
1700
  var DEPTH_FORMAT_PREFIXES = ["depth", "stencil"];
@@ -1437,10 +1747,19 @@ var __exports__ = (() => {
1437
1747
  depth,
1438
1748
  byteAlignment
1439
1749
  }) {
1440
- const { bytesPerPixel } = textureFormatDecoder.getInfo(format);
1441
- const unpaddedBytesPerRow = width * bytesPerPixel;
1750
+ const formatInfo = textureFormatDecoder.getInfo(format);
1751
+ const {
1752
+ bytesPerPixel,
1753
+ bytesPerBlock = bytesPerPixel,
1754
+ blockWidth = 1,
1755
+ blockHeight = 1,
1756
+ compressed = false
1757
+ } = formatInfo;
1758
+ const blockColumns = compressed ? Math.ceil(width / blockWidth) : width;
1759
+ const blockRows = compressed ? Math.ceil(height / blockHeight) : height;
1760
+ const unpaddedBytesPerRow = blockColumns * bytesPerBlock;
1442
1761
  const bytesPerRow = Math.ceil(unpaddedBytesPerRow / byteAlignment) * byteAlignment;
1443
- const rowsPerImage = height;
1762
+ const rowsPerImage = blockRows;
1444
1763
  const byteLength = bytesPerRow * rowsPerImage * depth;
1445
1764
  return {
1446
1765
  bytesPerPixel,
@@ -1466,7 +1785,8 @@ var __exports__ = (() => {
1466
1785
  const isSigned = formatInfo?.signed;
1467
1786
  const isInteger = formatInfo?.integer;
1468
1787
  const isWebGLSpecific = formatInfo?.webgl;
1469
- formatCapabilities.render &&= !isSigned;
1788
+ const isCompressed = Boolean(formatInfo?.compressed);
1789
+ formatCapabilities.render &&= !isDepthStencil && !isCompressed;
1470
1790
  formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1471
1791
  return formatCapabilities;
1472
1792
  }
@@ -1478,17 +1798,18 @@ var __exports__ = (() => {
1478
1798
  formatInfo.bytesPerPixel = 1;
1479
1799
  formatInfo.srgb = false;
1480
1800
  formatInfo.compressed = true;
1801
+ formatInfo.bytesPerBlock = getCompressedTextureBlockByteLength(format);
1481
1802
  const blockSize = getCompressedTextureBlockSize(format);
1482
1803
  if (blockSize) {
1483
1804
  formatInfo.blockWidth = blockSize.blockWidth;
1484
1805
  formatInfo.blockHeight = blockSize.blockHeight;
1485
1806
  }
1486
1807
  }
1487
- const matches = RGB_FORMAT_REGEX.exec(format);
1808
+ const matches = !formatInfo.packed ? RGB_FORMAT_REGEX.exec(format) : null;
1488
1809
  if (matches) {
1489
1810
  const [, channels, length, type, srgb, suffix] = matches;
1490
1811
  const dataType = `${type}${length}`;
1491
- const decodedType = getDataTypeInfo(dataType);
1812
+ const decodedType = dataTypeDecoder.getDataTypeInfo(dataType);
1492
1813
  const bits = decodedType.byteLength * 8;
1493
1814
  const components = channels?.length ?? 1;
1494
1815
  const bitsPerChannel = [
@@ -1563,10 +1884,31 @@ var __exports__ = (() => {
1563
1884
  const [, blockWidth, blockHeight] = matches;
1564
1885
  return { blockWidth: Number(blockWidth), blockHeight: Number(blockHeight) };
1565
1886
  }
1887
+ if (format.startsWith("bc") || format.startsWith("etc1") || format.startsWith("etc2") || format.startsWith("eac") || format.startsWith("atc")) {
1888
+ return { blockWidth: 4, blockHeight: 4 };
1889
+ }
1890
+ if (format.startsWith("pvrtc-rgb4") || format.startsWith("pvrtc-rgba4")) {
1891
+ return { blockWidth: 4, blockHeight: 4 };
1892
+ }
1893
+ if (format.startsWith("pvrtc-rgb2") || format.startsWith("pvrtc-rgba2")) {
1894
+ return { blockWidth: 8, blockHeight: 4 };
1895
+ }
1566
1896
  return null;
1567
1897
  }
1898
+ function getCompressedTextureBlockByteLength(format) {
1899
+ 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") {
1900
+ return 8;
1901
+ }
1902
+ 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") {
1903
+ return 16;
1904
+ }
1905
+ if (format.startsWith("pvrtc")) {
1906
+ return 8;
1907
+ }
1908
+ return 16;
1909
+ }
1568
1910
 
1569
- // ../core/src/image-utils/image-types.ts
1911
+ // ../core/src/shadertypes/image-types/image-types.ts
1570
1912
  function isExternalImage(data) {
1571
1913
  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;
1572
1914
  }
@@ -1589,6 +1931,52 @@ var __exports__ = (() => {
1589
1931
  // ../core/src/adapter/device.ts
1590
1932
  var DeviceLimits = class {
1591
1933
  };
1934
+ function formatErrorLogArguments(context, args) {
1935
+ const formattedContext = formatErrorLogValue(context);
1936
+ const formattedArgs = args.map(formatErrorLogValue).filter((arg) => arg !== void 0);
1937
+ return [formattedContext, ...formattedArgs].filter((arg) => arg !== void 0);
1938
+ }
1939
+ function formatErrorLogValue(value) {
1940
+ if (value === void 0) {
1941
+ return void 0;
1942
+ }
1943
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1944
+ return value;
1945
+ }
1946
+ if (value instanceof Error) {
1947
+ return value.message;
1948
+ }
1949
+ if (Array.isArray(value)) {
1950
+ return value.map(formatErrorLogValue);
1951
+ }
1952
+ if (typeof value === "object") {
1953
+ if (hasCustomToString(value)) {
1954
+ const stringValue = String(value);
1955
+ if (stringValue !== "[object Object]") {
1956
+ return stringValue;
1957
+ }
1958
+ }
1959
+ if (looksLikeGPUCompilationMessage(value)) {
1960
+ return formatGPUCompilationMessage(value);
1961
+ }
1962
+ return value.constructor?.name || "Object";
1963
+ }
1964
+ return String(value);
1965
+ }
1966
+ function hasCustomToString(value) {
1967
+ return "toString" in value && typeof value.toString === "function" && value.toString !== Object.prototype.toString;
1968
+ }
1969
+ function looksLikeGPUCompilationMessage(value) {
1970
+ return "message" in value && "type" in value;
1971
+ }
1972
+ function formatGPUCompilationMessage(value) {
1973
+ const type = typeof value.type === "string" ? value.type : "message";
1974
+ const message = typeof value.message === "string" ? value.message : "";
1975
+ const lineNum = typeof value.lineNum === "number" ? value.lineNum : null;
1976
+ const linePos = typeof value.linePos === "number" ? value.linePos : null;
1977
+ const location = lineNum !== null && linePos !== null ? ` @ ${lineNum}:${linePos}` : lineNum !== null ? ` @ ${lineNum}` : "";
1978
+ return `${type}${location}: ${message}`.trim();
1979
+ }
1592
1980
  var DeviceFeatures = class {
1593
1981
  features;
1594
1982
  disabledFeatures;
@@ -1618,6 +2006,8 @@ var __exports__ = (() => {
1618
2006
  userData = {};
1619
2007
  /** stats */
1620
2008
  statsManager = lumaStats;
2009
+ /** Internal per-device factory storage */
2010
+ _factories = {};
1621
2011
  /** An abstract timestamp used for change tracking */
1622
2012
  timestamp = 0;
1623
2013
  /** True if this device has been reused during device creation (app has multiple references) */
@@ -1625,12 +2015,15 @@ var __exports__ = (() => {
1625
2015
  /** Used by other luma.gl modules to store data on the device */
1626
2016
  _moduleData = {};
1627
2017
  _textureCaps = {};
2018
+ /** Internal timestamp query set used when GPU timing collection is enabled for this device. */
2019
+ _debugGPUTimeQuery = null;
1628
2020
  constructor(props) {
1629
2021
  this.props = { ..._Device.defaultProps, ...props };
1630
2022
  this.id = this.props.id || uid(this[Symbol.toStringTag].toLowerCase());
1631
2023
  }
2024
+ // TODO - just expose the shadertypes decoders?
1632
2025
  getVertexFormatInfo(format) {
1633
- return getVertexFormatInfo(format);
2026
+ return vertexFormatDecoder.getVertexFormatInfo(format);
1634
2027
  }
1635
2028
  isVertexFormatSupported(format) {
1636
2029
  return true;
@@ -1678,6 +2071,16 @@ var __exports__ = (() => {
1678
2071
  isTextureFormatCompressed(format) {
1679
2072
  return textureFormatDecoder.isCompressed(format);
1680
2073
  }
2074
+ /** Returns the compressed texture formats that can be created and sampled on this device */
2075
+ getSupportedCompressedTextureFormats() {
2076
+ const supportedFormats = [];
2077
+ for (const format of Object.keys(getTextureFormatTable())) {
2078
+ if (this.isTextureFormatCompressed(format) && this.isTextureFormatSupported(format)) {
2079
+ supportedFormats.push(format);
2080
+ }
2081
+ }
2082
+ return supportedFormats;
2083
+ }
1681
2084
  // DEBUG METHODS
1682
2085
  pushDebugGroup(groupLabel) {
1683
2086
  this.commandEncoder.pushDebugGroup(groupLabel);
@@ -1720,12 +2123,12 @@ var __exports__ = (() => {
1720
2123
  reportError(error, context, ...args) {
1721
2124
  const isHandled = this.props.onError(error, context);
1722
2125
  if (!isHandled) {
2126
+ const logArguments = formatErrorLogArguments(context, args);
1723
2127
  return log.error(
1724
2128
  this.type === "webgl" ? "%cWebGL" : "%cWebGPU",
1725
2129
  "color: white; background: red; padding: 2px 6px; border-radius: 3px;",
1726
2130
  error.message,
1727
- context,
1728
- ...args
2131
+ ...logArguments
1729
2132
  );
1730
2133
  }
1731
2134
  return () => {
@@ -1760,6 +2163,78 @@ or create a device with the 'debug: true' prop.`;
1760
2163
  beginComputePass(props) {
1761
2164
  return this.commandEncoder.beginComputePass(props);
1762
2165
  }
2166
+ /**
2167
+ * Generate mipmaps for a WebGPU texture.
2168
+ * WebGPU textures must be created up front with the required mip count, usage flags, and a format that supports the chosen generation path.
2169
+ * WebGL uses `Texture.generateMipmapsWebGL()` directly because the backend manages mip generation on the texture object itself.
2170
+ */
2171
+ generateMipmapsWebGPU(_texture) {
2172
+ throw new Error("not implemented");
2173
+ }
2174
+ /** Internal helper for creating a shareable WebGL render-pipeline implementation. */
2175
+ _createSharedRenderPipelineWebGL(_props) {
2176
+ throw new Error("_createSharedRenderPipelineWebGL() not implemented");
2177
+ }
2178
+ /** Internal WebGPU-only helper for retrieving the native bind-group layout for a pipeline group. */
2179
+ _createBindGroupLayoutWebGPU(_pipeline, _group) {
2180
+ throw new Error("_createBindGroupLayoutWebGPU() not implemented");
2181
+ }
2182
+ /** Internal WebGPU-only helper for creating a native bind group. */
2183
+ _createBindGroupWebGPU(_bindGroupLayout, _shaderLayout, _bindings, _group) {
2184
+ throw new Error("_createBindGroupWebGPU() not implemented");
2185
+ }
2186
+ /**
2187
+ * Internal helper that returns `true` when timestamp-query GPU timing should be
2188
+ * collected for this device.
2189
+ */
2190
+ _supportsDebugGPUTime() {
2191
+ return this.features.has("timestamp-query") && Boolean(this.props.debug || this.props.debugGPUTime);
2192
+ }
2193
+ /**
2194
+ * Internal helper that enables device-managed GPU timing collection on the
2195
+ * default command encoder. Reuses the existing query set if timing is already enabled.
2196
+ *
2197
+ * @param queryCount - Number of timestamp slots reserved for profiled passes.
2198
+ * @returns The device-managed timestamp QuerySet, or `null` when timing is not supported or could not be enabled.
2199
+ */
2200
+ _enableDebugGPUTime(queryCount = 256) {
2201
+ if (!this._supportsDebugGPUTime()) {
2202
+ return null;
2203
+ }
2204
+ if (this._debugGPUTimeQuery) {
2205
+ return this._debugGPUTimeQuery;
2206
+ }
2207
+ try {
2208
+ this._debugGPUTimeQuery = this.createQuerySet({ type: "timestamp", count: queryCount });
2209
+ this.commandEncoder = this.createCommandEncoder({
2210
+ id: this.commandEncoder.props.id,
2211
+ timeProfilingQuerySet: this._debugGPUTimeQuery
2212
+ });
2213
+ } catch {
2214
+ this._debugGPUTimeQuery = null;
2215
+ }
2216
+ return this._debugGPUTimeQuery;
2217
+ }
2218
+ /**
2219
+ * Internal helper that disables device-managed GPU timing collection and restores
2220
+ * the default command encoder to an unprofiled state.
2221
+ */
2222
+ _disableDebugGPUTime() {
2223
+ if (!this._debugGPUTimeQuery) {
2224
+ return;
2225
+ }
2226
+ if (this.commandEncoder.getTimeProfilingQuerySet() === this._debugGPUTimeQuery) {
2227
+ this.commandEncoder = this.createCommandEncoder({
2228
+ id: this.commandEncoder.props.id
2229
+ });
2230
+ }
2231
+ this._debugGPUTimeQuery.destroy();
2232
+ this._debugGPUTimeQuery = null;
2233
+ }
2234
+ /** Internal helper that returns `true` when device-managed GPU timing is currently active. */
2235
+ _isDebugGPUTimeEnabled() {
2236
+ return this._debugGPUTimeQuery !== null;
2237
+ }
1763
2238
  // DEPRECATED METHODS
1764
2239
  /** @deprecated Use getDefaultCanvasContext() */
1765
2240
  getCanvasContext() {
@@ -1867,7 +2342,8 @@ or create a device with the 'debug: true' prop.`;
1867
2342
  onVisibilityChange: (context) => log.log(1, `${context} Visibility changed ${context.isVisible}`)(),
1868
2343
  onDevicePixelRatioChange: (context, info) => log.log(1, `${context} DPR changed ${info.oldRatio} => ${context.devicePixelRatio}`)(),
1869
2344
  // Debug flags
1870
- debug: log.get("debug") || void 0,
2345
+ debug: getDefaultDebugValue(),
2346
+ debugGPUTime: false,
1871
2347
  debugShaders: log.get("debug-shaders") || void 0,
1872
2348
  debugFramebuffers: Boolean(log.get("debug-framebuffers")),
1873
2349
  debugFactories: Boolean(log.get("debug-factories")),
@@ -1878,9 +2354,11 @@ or create a device with the 'debug: true' prop.`;
1878
2354
  // Experimental
1879
2355
  _reuseDevices: false,
1880
2356
  _requestMaxLimits: true,
1881
- _cacheShaders: false,
1882
- _cachePipelines: false,
1883
- _cacheDestroyPolicy: "unused",
2357
+ _cacheShaders: true,
2358
+ _destroyShaders: false,
2359
+ _cachePipelines: true,
2360
+ _sharePipelines: true,
2361
+ _destroyPipelines: false,
1884
2362
  // TODO - Change these after confirming things work as expected
1885
2363
  _initializeFeatures: true,
1886
2364
  _disabledFeatures: {
@@ -1889,6 +2367,25 @@ or create a device with the 'debug: true' prop.`;
1889
2367
  // INTERNAL
1890
2368
  _handle: void 0
1891
2369
  });
2370
+ function _getDefaultDebugValue(logDebugValue, nodeEnv) {
2371
+ if (logDebugValue !== void 0 && logDebugValue !== null) {
2372
+ return Boolean(logDebugValue);
2373
+ }
2374
+ if (nodeEnv !== void 0) {
2375
+ return nodeEnv !== "production";
2376
+ }
2377
+ return false;
2378
+ }
2379
+ function getDefaultDebugValue() {
2380
+ return _getDefaultDebugValue(log.get("debug"), getNodeEnv());
2381
+ }
2382
+ function getNodeEnv() {
2383
+ const processObject = globalThis.process;
2384
+ if (!processObject?.env) {
2385
+ return void 0;
2386
+ }
2387
+ return processObject.env["NODE_ENV"];
2388
+ }
1892
2389
 
1893
2390
  // ../core/src/adapter/luma.ts
1894
2391
  var STARTUP_MESSAGE = "set luma.log.level=1 (or higher) to trace rendering";
@@ -2066,6 +2563,100 @@ or create a device with the 'debug: true' prop.`;
2066
2563
  return pageLoadPromise;
2067
2564
  }
2068
2565
 
2566
+ // ../core/src/adapter/canvas-observer.ts
2567
+ var CanvasObserver = class {
2568
+ props;
2569
+ _resizeObserver;
2570
+ _intersectionObserver;
2571
+ _observeDevicePixelRatioTimeout = null;
2572
+ _observeDevicePixelRatioMediaQuery = null;
2573
+ _handleDevicePixelRatioChange = () => this._refreshDevicePixelRatio();
2574
+ _trackPositionInterval = null;
2575
+ _started = false;
2576
+ get started() {
2577
+ return this._started;
2578
+ }
2579
+ constructor(props) {
2580
+ this.props = props;
2581
+ }
2582
+ start() {
2583
+ if (this._started || !this.props.canvas) {
2584
+ return;
2585
+ }
2586
+ this._started = true;
2587
+ this._intersectionObserver ||= new IntersectionObserver(
2588
+ (entries) => this.props.onIntersection(entries)
2589
+ );
2590
+ this._resizeObserver ||= new ResizeObserver((entries) => this.props.onResize(entries));
2591
+ this._intersectionObserver.observe(this.props.canvas);
2592
+ try {
2593
+ this._resizeObserver.observe(this.props.canvas, { box: "device-pixel-content-box" });
2594
+ } catch {
2595
+ this._resizeObserver.observe(this.props.canvas, { box: "content-box" });
2596
+ }
2597
+ this._observeDevicePixelRatioTimeout = setTimeout(() => this._refreshDevicePixelRatio(), 0);
2598
+ if (this.props.trackPosition) {
2599
+ this._trackPosition();
2600
+ }
2601
+ }
2602
+ stop() {
2603
+ if (!this._started) {
2604
+ return;
2605
+ }
2606
+ this._started = false;
2607
+ if (this._observeDevicePixelRatioTimeout) {
2608
+ clearTimeout(this._observeDevicePixelRatioTimeout);
2609
+ this._observeDevicePixelRatioTimeout = null;
2610
+ }
2611
+ if (this._observeDevicePixelRatioMediaQuery) {
2612
+ this._observeDevicePixelRatioMediaQuery.removeEventListener(
2613
+ "change",
2614
+ this._handleDevicePixelRatioChange
2615
+ );
2616
+ this._observeDevicePixelRatioMediaQuery = null;
2617
+ }
2618
+ if (this._trackPositionInterval) {
2619
+ clearInterval(this._trackPositionInterval);
2620
+ this._trackPositionInterval = null;
2621
+ }
2622
+ this._resizeObserver?.disconnect();
2623
+ this._intersectionObserver?.disconnect();
2624
+ }
2625
+ _refreshDevicePixelRatio() {
2626
+ if (!this._started) {
2627
+ return;
2628
+ }
2629
+ this.props.onDevicePixelRatioChange();
2630
+ this._observeDevicePixelRatioMediaQuery?.removeEventListener(
2631
+ "change",
2632
+ this._handleDevicePixelRatioChange
2633
+ );
2634
+ this._observeDevicePixelRatioMediaQuery = matchMedia(
2635
+ `(resolution: ${window.devicePixelRatio}dppx)`
2636
+ );
2637
+ this._observeDevicePixelRatioMediaQuery.addEventListener(
2638
+ "change",
2639
+ this._handleDevicePixelRatioChange,
2640
+ { once: true }
2641
+ );
2642
+ }
2643
+ _trackPosition(intervalMs = 100) {
2644
+ if (this._trackPositionInterval) {
2645
+ return;
2646
+ }
2647
+ this._trackPositionInterval = setInterval(() => {
2648
+ if (!this._started) {
2649
+ if (this._trackPositionInterval) {
2650
+ clearInterval(this._trackPositionInterval);
2651
+ this._trackPositionInterval = null;
2652
+ }
2653
+ } else {
2654
+ this.props.onPositionChange();
2655
+ }
2656
+ }, intervalMs);
2657
+ }
2658
+ };
2659
+
2069
2660
  // ../core/src/utils/promise-utils.ts
2070
2661
  function withResolvers() {
2071
2662
  let resolve;
@@ -2090,8 +2681,8 @@ or create a device with the 'debug: true' prop.`;
2090
2681
  return value;
2091
2682
  }
2092
2683
 
2093
- // ../core/src/adapter/canvas-context.ts
2094
- var _CanvasContext = class {
2684
+ // ../core/src/adapter/canvas-surface.ts
2685
+ var _CanvasSurface = class {
2095
2686
  static isHTMLCanvas(canvas) {
2096
2687
  return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
2097
2688
  }
@@ -2127,11 +2718,7 @@ or create a device with the 'debug: true' prop.`;
2127
2718
  drawingBufferHeight;
2128
2719
  /** Resolves when the canvas is initialized, i.e. when the ResizeObserver has updated the pixel size */
2129
2720
  _initializedResolvers = withResolvers();
2130
- /** ResizeObserver to track canvas size changes */
2131
- _resizeObserver;
2132
- /** IntersectionObserver to track canvas visibility changes */
2133
- _intersectionObserver;
2134
- _observeDevicePixelRatioTimeout = null;
2721
+ _canvasObserver;
2135
2722
  /** Position of the canvas in the document, updated by a timer */
2136
2723
  _position = [0, 0];
2137
2724
  /** Whether this canvas context has been destroyed */
@@ -2142,7 +2729,7 @@ or create a device with the 'debug: true' prop.`;
2142
2729
  return `${this[Symbol.toStringTag]}(${this.id})`;
2143
2730
  }
2144
2731
  constructor(props) {
2145
- this.props = { ..._CanvasContext.defaultProps, ...props };
2732
+ this.props = { ..._CanvasSurface.defaultProps, ...props };
2146
2733
  props = this.props;
2147
2734
  this.initialized = this._initializedResolvers.promise;
2148
2735
  if (!isBrowser()) {
@@ -2154,11 +2741,11 @@ or create a device with the 'debug: true' prop.`;
2154
2741
  } else {
2155
2742
  this.canvas = props.canvas;
2156
2743
  }
2157
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2744
+ if (_CanvasSurface.isHTMLCanvas(this.canvas)) {
2158
2745
  this.id = props.id || this.canvas.id;
2159
2746
  this.type = "html-canvas";
2160
2747
  this.htmlCanvas = this.canvas;
2161
- } else if (_CanvasContext.isOffscreenCanvas(this.canvas)) {
2748
+ } else if (_CanvasSurface.isOffscreenCanvas(this.canvas)) {
2162
2749
  this.id = props.id || "offscreen-canvas";
2163
2750
  this.type = "offscreen-canvas";
2164
2751
  this.offscreenCanvas = this.canvas;
@@ -2174,33 +2761,20 @@ or create a device with the 'debug: true' prop.`;
2174
2761
  this.drawingBufferHeight = this.canvas.height;
2175
2762
  this.devicePixelRatio = globalThis.devicePixelRatio || 1;
2176
2763
  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
- }
2764
+ this._canvasObserver = new CanvasObserver({
2765
+ canvas: this.htmlCanvas,
2766
+ trackPosition: this.props.trackPosition,
2767
+ onResize: (entries) => this._handleResize(entries),
2768
+ onIntersection: (entries) => this._handleIntersection(entries),
2769
+ onDevicePixelRatioChange: () => this._observeDevicePixelRatio(),
2770
+ onPositionChange: () => this.updatePosition()
2771
+ });
2193
2772
  }
2194
2773
  destroy() {
2195
2774
  if (!this.destroyed) {
2196
2775
  this.destroyed = true;
2197
- if (this._observeDevicePixelRatioTimeout) {
2198
- clearTimeout(this._observeDevicePixelRatioTimeout);
2199
- this._observeDevicePixelRatioTimeout = null;
2200
- }
2776
+ this._stopObservers();
2201
2777
  this.device = null;
2202
- this._resizeObserver?.disconnect();
2203
- this._intersectionObserver?.disconnect();
2204
2778
  }
2205
2779
  }
2206
2780
  setProps(props) {
@@ -2215,41 +2789,22 @@ or create a device with the 'debug: true' prop.`;
2215
2789
  this._resizeDrawingBufferIfNeeded();
2216
2790
  return this._getCurrentFramebuffer(options);
2217
2791
  }
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
2792
  getCSSSize() {
2225
2793
  return [this.cssWidth, this.cssHeight];
2226
2794
  }
2227
2795
  getPosition() {
2228
2796
  return this._position;
2229
2797
  }
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
2798
  getDevicePixelSize() {
2236
2799
  return [this.devicePixelWidth, this.devicePixelHeight];
2237
2800
  }
2238
- /** Get the drawing buffer size (number of pixels GPU is rendering into, can be different from CSS size) */
2239
2801
  getDrawingBufferSize() {
2240
2802
  return [this.drawingBufferWidth, this.drawingBufferHeight];
2241
2803
  }
2242
- /** Returns the biggest allowed framebuffer size. @todo Allow the application to limit this? */
2243
2804
  getMaxDrawingBufferSize() {
2244
2805
  const maxTextureDimension = this.device.limits.maxTextureDimension2D;
2245
2806
  return [maxTextureDimension, maxTextureDimension];
2246
2807
  }
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
2808
  setDrawingBufferSize(width, height) {
2254
2809
  width = Math.floor(width);
2255
2810
  height = Math.floor(height);
@@ -2260,19 +2815,10 @@ or create a device with the 'debug: true' prop.`;
2260
2815
  this.drawingBufferHeight = height;
2261
2816
  this._needsDrawingBufferResize = true;
2262
2817
  }
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
2818
  getDevicePixelRatio() {
2269
- const dpr = typeof window !== "undefined" && window.devicePixelRatio;
2270
- return dpr || 1;
2819
+ const devicePixelRatio2 = typeof window !== "undefined" && window.devicePixelRatio;
2820
+ return devicePixelRatio2 || 1;
2271
2821
  }
2272
- // DEPRECATED METHODS
2273
- /**
2274
- * Maps CSS pixel position to device pixel position
2275
- */
2276
2822
  cssToDevicePixels(cssPixel, yInvert = true) {
2277
2823
  const ratio = this.cssToDeviceRatio();
2278
2824
  const [width, height] = this.getDrawingBufferSize();
@@ -2282,10 +2828,10 @@ or create a device with the 'debug: true' prop.`;
2282
2828
  getPixelSize() {
2283
2829
  return this.getDevicePixelSize();
2284
2830
  }
2285
- /** @deprecated - TODO which values should we use for aspect */
2831
+ /** @deprecated Use the current drawing buffer size for projection setup. */
2286
2832
  getAspect() {
2287
- const [width, height] = this.getDevicePixelSize();
2288
- return width / height;
2833
+ const [width, height] = this.getDrawingBufferSize();
2834
+ return width > 0 && height > 0 ? width / height : 1;
2289
2835
  }
2290
2836
  /** @deprecated Returns multiplier need to convert CSS size to Device size */
2291
2837
  cssToDeviceRatio() {
@@ -2301,17 +2847,36 @@ or create a device with the 'debug: true' prop.`;
2301
2847
  resize(size) {
2302
2848
  this.setDrawingBufferSize(size.width, size.height);
2303
2849
  }
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
2850
  _setAutoCreatedCanvasId(id) {
2310
2851
  if (this.htmlCanvas?.id === "lumagl-auto-created-canvas") {
2311
2852
  this.htmlCanvas.id = id;
2312
2853
  }
2313
2854
  }
2314
- /** reacts to an observed intersection */
2855
+ /**
2856
+ * Starts DOM observation after the derived context and its device are fully initialized.
2857
+ *
2858
+ * `CanvasSurface` construction runs before subclasses can assign `this.device`, and the
2859
+ * default WebGL canvas context is created before `WebGLDevice` has initialized `limits`,
2860
+ * `features`, and the rest of its runtime state. Deferring observer startup avoids early
2861
+ * `ResizeObserver` and DPR callbacks running against a partially initialized device.
2862
+ */
2863
+ _startObservers() {
2864
+ if (this.destroyed) {
2865
+ return;
2866
+ }
2867
+ this._canvasObserver.start();
2868
+ }
2869
+ /**
2870
+ * Stops all DOM observation and timers associated with a canvas surface.
2871
+ *
2872
+ * This pairs with `_startObservers()` so teardown uses the same lifecycle whether a context is
2873
+ * explicitly destroyed, abandoned during device reuse, or temporarily has not started observing
2874
+ * yet. Centralizing shutdown here keeps resize/DPR/position watchers from surviving past the
2875
+ * lifetime of the owning device.
2876
+ */
2877
+ _stopObservers() {
2878
+ this._canvasObserver.stop();
2879
+ }
2315
2880
  _handleIntersection(entries) {
2316
2881
  if (this.destroyed) {
2317
2882
  return;
@@ -2326,11 +2891,6 @@ or create a device with the 'debug: true' prop.`;
2326
2891
  this.device.props.onVisibilityChange(this);
2327
2892
  }
2328
2893
  }
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
2894
  _handleResize(entries) {
2335
2895
  if (this.destroyed) {
2336
2896
  return;
@@ -2351,12 +2911,14 @@ or create a device with the 'debug: true' prop.`;
2351
2911
  this._updateDrawingBufferSize();
2352
2912
  this.device.props.onResize(this, { oldPixelSize });
2353
2913
  }
2354
- /** Initiate a deferred update for the canvas drawing buffer size */
2355
2914
  _updateDrawingBufferSize() {
2356
2915
  if (this.props.autoResize) {
2357
2916
  if (typeof this.props.useDevicePixels === "number") {
2358
- const dpr = this.props.useDevicePixels;
2359
- this.setDrawingBufferSize(this.cssWidth * dpr, this.cssHeight * dpr);
2917
+ const devicePixelRatio2 = this.props.useDevicePixels;
2918
+ this.setDrawingBufferSize(
2919
+ this.cssWidth * devicePixelRatio2,
2920
+ this.cssHeight * devicePixelRatio2
2921
+ );
2360
2922
  } else if (this.props.useDevicePixels) {
2361
2923
  this.setDrawingBufferSize(this.devicePixelWidth, this.devicePixelHeight);
2362
2924
  } else {
@@ -2367,7 +2929,6 @@ or create a device with the 'debug: true' prop.`;
2367
2929
  this.isInitialized = true;
2368
2930
  this.updatePosition();
2369
2931
  }
2370
- /** Perform a deferred resize of the drawing buffer if needed */
2371
2932
  _resizeDrawingBufferIfNeeded() {
2372
2933
  if (this._needsDrawingBufferResize) {
2373
2934
  this._needsDrawingBufferResize = false;
@@ -2379,36 +2940,17 @@ or create a device with the 'debug: true' prop.`;
2379
2940
  }
2380
2941
  }
2381
2942
  }
2382
- /** Monitor DPR changes */
2383
2943
  _observeDevicePixelRatio() {
2384
- if (this.destroyed) {
2944
+ if (this.destroyed || !this._canvasObserver.started) {
2385
2945
  return;
2386
2946
  }
2387
2947
  const oldRatio = this.devicePixelRatio;
2388
2948
  this.devicePixelRatio = window.devicePixelRatio;
2389
2949
  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);
2950
+ this.device.props.onDevicePixelRatioChange?.(this, {
2951
+ oldRatio
2952
+ });
2406
2953
  }
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
2954
  updatePosition() {
2413
2955
  if (this.destroyed) {
2414
2956
  return;
@@ -2421,13 +2963,15 @@ or create a device with the 'debug: true' prop.`;
2421
2963
  if (positionChanged) {
2422
2964
  const oldPosition = this._position;
2423
2965
  this._position = position;
2424
- this.device.props.onPositionChange?.(this, { oldPosition });
2966
+ this.device.props.onPositionChange?.(this, {
2967
+ oldPosition
2968
+ });
2425
2969
  }
2426
2970
  }
2427
2971
  }
2428
2972
  };
2429
- var CanvasContext = _CanvasContext;
2430
- __publicField(CanvasContext, "defaultProps", {
2973
+ var CanvasSurface = _CanvasSurface;
2974
+ __publicField(CanvasSurface, "defaultProps", {
2431
2975
  id: void 0,
2432
2976
  canvas: null,
2433
2977
  width: 800,
@@ -2455,7 +2999,7 @@ or create a device with the 'debug: true' prop.`;
2455
2999
  }
2456
3000
  function getCanvasFromDOM(canvasId) {
2457
3001
  const canvas = document.getElementById(canvasId);
2458
- if (!CanvasContext.isHTMLCanvas(canvas)) {
3002
+ if (!CanvasSurface.isHTMLCanvas(canvas)) {
2459
3003
  throw new Error("Object is not a canvas element");
2460
3004
  }
2461
3005
  return canvas;
@@ -2479,33 +3023,40 @@ or create a device with the 'debug: true' prop.`;
2479
3023
  const point = pixel;
2480
3024
  const x = scaleX(point[0], ratio, width);
2481
3025
  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);
3026
+ let temporary = scaleX(point[0] + 1, ratio, width);
3027
+ const xHigh = temporary === width - 1 ? temporary : temporary - 1;
3028
+ temporary = scaleY(point[1] + 1, ratio, height, yInvert);
2485
3029
  let yHigh;
2486
3030
  if (yInvert) {
2487
- t = t === 0 ? t : t + 1;
3031
+ temporary = temporary === 0 ? temporary : temporary + 1;
2488
3032
  yHigh = y;
2489
- y = t;
3033
+ y = temporary;
2490
3034
  } else {
2491
- yHigh = t === height - 1 ? t : t - 1;
3035
+ yHigh = temporary === height - 1 ? temporary : temporary - 1;
2492
3036
  }
2493
3037
  return {
2494
3038
  x,
2495
3039
  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
3040
  width: Math.max(xHigh - x + 1, 1),
2498
3041
  height: Math.max(yHigh - y + 1, 1)
2499
3042
  };
2500
3043
  }
2501
3044
  function scaleX(x, ratio, width) {
2502
- const r = Math.min(Math.round(x * ratio), width - 1);
2503
- return r;
3045
+ return Math.min(Math.round(x * ratio), width - 1);
2504
3046
  }
2505
3047
  function scaleY(y, ratio, height, yInvert) {
2506
3048
  return yInvert ? Math.max(0, height - 1 - Math.round(y * ratio)) : Math.min(Math.round(y * ratio), height - 1);
2507
3049
  }
2508
3050
 
3051
+ // ../core/src/adapter/canvas-context.ts
3052
+ var CanvasContext = class extends CanvasSurface {
3053
+ };
3054
+ __publicField(CanvasContext, "defaultProps", CanvasSurface.defaultProps);
3055
+
3056
+ // ../core/src/adapter/presentation-context.ts
3057
+ var PresentationContext = class extends CanvasSurface {
3058
+ };
3059
+
2509
3060
  // ../core/src/adapter/resources/sampler.ts
2510
3061
  var _Sampler = class extends Resource {
2511
3062
  get [Symbol.toStringTag]() {
@@ -2560,6 +3111,8 @@ or create a device with the 'debug: true' prop.`;
2560
3111
  depth;
2561
3112
  /** mip levels in this texture */
2562
3113
  mipLevels;
3114
+ /** sample count */
3115
+ samples;
2563
3116
  /** Rows are multiples of this length, padded with extra bytes if needed */
2564
3117
  byteAlignment;
2565
3118
  /** The ready promise is always resolved. It is provided for type compatibility with DynamicTexture. */
@@ -2585,6 +3138,7 @@ or create a device with the 'debug: true' prop.`;
2585
3138
  this.height = this.props.height;
2586
3139
  this.depth = this.props.depth;
2587
3140
  this.mipLevels = this.props.mipLevels;
3141
+ this.samples = this.props.samples || 1;
2588
3142
  if (this.dimension === "cube") {
2589
3143
  this.depth = 6;
2590
3144
  }
@@ -2618,9 +3172,25 @@ or create a device with the 'debug: true' prop.`;
2618
3172
  setSampler(sampler) {
2619
3173
  this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
2620
3174
  }
3175
+ /**
3176
+ * Copy raw image data (bytes) into the texture.
3177
+ *
3178
+ * @note Deprecated compatibility wrapper over {@link writeData}.
3179
+ * @note Uses the same layout defaults and alignment rules as {@link writeData}.
3180
+ * @note Tightly packed CPU uploads can omit `bytesPerRow` and `rowsPerImage`.
3181
+ * @note If the CPU source rows are padded, pass explicit `bytesPerRow` and `rowsPerImage`.
3182
+ * @deprecated Use writeData()
3183
+ */
3184
+ copyImageData(options) {
3185
+ const { data, depth, ...writeOptions } = options;
3186
+ this.writeData(data, {
3187
+ ...writeOptions,
3188
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3189
+ });
3190
+ }
2621
3191
  /**
2622
3192
  * 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
3193
+ * @return the backend-aligned linear layout, in particular bytesPerRow which includes any required padding for buffer copy/read paths
2624
3194
  */
2625
3195
  computeMemoryLayout(options_ = {}) {
2626
3196
  const options = this._normalizeTextureReadOptions(options_);
@@ -2639,9 +3209,11 @@ or create a device with the 'debug: true' prop.`;
2639
3209
  * @returns A Buffer containing the texture data.
2640
3210
  *
2641
3211
  * @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.
2643
- * @note The application can call Buffer.readAsync()
2644
- * @note If not supplied a buffer will be created and the application needs to call Buffer.destroy
3212
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3213
+ * @note The application can call Buffer.readAsync() to read the returned buffer on the CPU.
3214
+ * @note The destination buffer must be supplied by the caller and must be large enough for the requested region.
3215
+ * @note On WebGPU this corresponds to a texture-to-buffer copy and uses buffer-copy alignment rules.
3216
+ * @note On WebGL, luma.gl emulates the same logical readback behavior.
2645
3217
  */
2646
3218
  readBuffer(options, buffer) {
2647
3219
  throw new Error("readBuffer not implemented");
@@ -2652,15 +3224,20 @@ or create a device with the 'debug: true' prop.`;
2652
3224
  *
2653
3225
  * @note The memory layout of the texture data is determined by the texture format and dimensions.
2654
3226
  * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3227
+ * @deprecated Use Texture.readBuffer() with an explicit destination buffer, or DynamicTexture.readAsync() for convenience readback.
2655
3228
  */
2656
3229
  readDataAsync(options) {
2657
3230
  throw new Error("readBuffer not implemented");
2658
3231
  }
2659
3232
  /**
2660
- * Writes an GPU Buffer into a texture.
3233
+ * Writes a GPU Buffer into a texture.
2661
3234
  *
3235
+ * @param buffer - Source GPU buffer.
3236
+ * @param options - Destination subresource, extent, and source layout options.
2662
3237
  * @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.
3238
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3239
+ * @note On WebGPU this corresponds to a buffer-to-texture copy and uses buffer-copy alignment rules.
3240
+ * @note On WebGL, luma.gl emulates the same destination and layout semantics.
2664
3241
  */
2665
3242
  writeBuffer(buffer, options) {
2666
3243
  throw new Error("readBuffer not implemented");
@@ -2668,8 +3245,11 @@ or create a device with the 'debug: true' prop.`;
2668
3245
  /**
2669
3246
  * Writes an array buffer into a texture.
2670
3247
  *
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.
3248
+ * @param data - Source texel data.
3249
+ * @param options - Destination subresource, extent, and source layout options.
3250
+ * @note If `bytesPerRow` and `rowsPerImage` are omitted, luma.gl computes a tightly packed CPU-memory layout for the requested region.
3251
+ * @note On WebGPU this corresponds to `GPUQueue.writeTexture()` and does not implicitly pad rows to 256 bytes.
3252
+ * @note On WebGL, padded CPU data is supported via the same `bytesPerRow` and `rowsPerImage` options.
2673
3253
  */
2674
3254
  writeData(data, options) {
2675
3255
  throw new Error("readBuffer not implemented");
@@ -2732,37 +3312,166 @@ or create a device with the 'debug: true' prop.`;
2732
3312
  }
2733
3313
  }
2734
3314
  _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;
3315
+ const { data, depth, ...writeOptions } = options_;
3316
+ const options = this._normalizeTextureWriteOptions({
3317
+ ...writeOptions,
3318
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3319
+ });
3320
+ return { data, depth: options.depthOrArrayLayers, ...options };
2744
3321
  }
2745
3322
  _normalizeCopyExternalImageOptions(options_) {
3323
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3324
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3325
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
2746
3326
  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);
3327
+ const options = {
3328
+ ..._Texture.defaultCopyExternalImageOptions,
3329
+ ...mipLevelSize,
3330
+ ...size,
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.depth = Math.min(options.depth, mipLevelSize.depthOrArrayLayers - options.z);
2750
3336
  return options;
2751
3337
  }
2752
3338
  _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);
3339
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3340
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3341
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3342
+ const options = {
3343
+ ..._Texture.defaultTextureReadOptions,
3344
+ ...mipLevelSize,
3345
+ ...optionsWithoutUndefined
3346
+ };
3347
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3348
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3349
+ options.depthOrArrayLayers = Math.min(
3350
+ options.depthOrArrayLayers,
3351
+ mipLevelSize.depthOrArrayLayers - options.z
3352
+ );
2757
3353
  return options;
2758
3354
  }
3355
+ /**
3356
+ * Normalizes a texture read request and validates the color-only readback contract used by the
3357
+ * current texture read APIs. Supported dimensions are `2d`, `cube`, `cube-array`,
3358
+ * `2d-array`, and `3d`.
3359
+ *
3360
+ * @throws if the texture format, aspect, or dimension is not supported by the first-pass
3361
+ * color-read implementation.
3362
+ */
3363
+ _getSupportedColorReadOptions(options_) {
3364
+ const options = this._normalizeTextureReadOptions(options_);
3365
+ const formatInfo = textureFormatDecoder.getInfo(this.format);
3366
+ this._validateColorReadAspect(options);
3367
+ this._validateColorReadFormat(formatInfo);
3368
+ switch (this.dimension) {
3369
+ case "2d":
3370
+ case "cube":
3371
+ case "cube-array":
3372
+ case "2d-array":
3373
+ case "3d":
3374
+ return options;
3375
+ default:
3376
+ throw new Error(`${this} color readback does not support ${this.dimension} textures`);
3377
+ }
3378
+ }
3379
+ /** Validates that a read request targets the full color aspect of the texture. */
3380
+ _validateColorReadAspect(options) {
3381
+ if (options.aspect !== "all") {
3382
+ throw new Error(`${this} color readback only supports aspect 'all'`);
3383
+ }
3384
+ }
3385
+ /** Validates that a read request targets an uncompressed color-renderable texture format. */
3386
+ _validateColorReadFormat(formatInfo) {
3387
+ if (formatInfo.compressed) {
3388
+ throw new Error(
3389
+ `${this} color readback does not support compressed formats (${this.format})`
3390
+ );
3391
+ }
3392
+ switch (formatInfo.attachment) {
3393
+ case "color":
3394
+ return;
3395
+ case "depth":
3396
+ throw new Error(`${this} color readback does not support depth formats (${this.format})`);
3397
+ case "stencil":
3398
+ throw new Error(`${this} color readback does not support stencil formats (${this.format})`);
3399
+ case "depth-stencil":
3400
+ throw new Error(
3401
+ `${this} color readback does not support depth-stencil formats (${this.format})`
3402
+ );
3403
+ default:
3404
+ throw new Error(`${this} color readback does not support format ${this.format}`);
3405
+ }
3406
+ }
2759
3407
  _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);
3408
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3409
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3410
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3411
+ const options = {
3412
+ ..._Texture.defaultTextureWriteOptions,
3413
+ ...mipLevelSize,
3414
+ ...optionsWithoutUndefined
3415
+ };
3416
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3417
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3418
+ options.depthOrArrayLayers = Math.min(
3419
+ options.depthOrArrayLayers,
3420
+ mipLevelSize.depthOrArrayLayers - options.z
3421
+ );
3422
+ const layout = textureFormatDecoder.computeMemoryLayout({
3423
+ format: this.format,
3424
+ width: options.width,
3425
+ height: options.height,
3426
+ depth: options.depthOrArrayLayers,
3427
+ byteAlignment: this.byteAlignment
3428
+ });
3429
+ const minimumBytesPerRow = layout.bytesPerPixel * options.width;
3430
+ options.bytesPerRow = optionsWithoutUndefined.bytesPerRow ?? layout.bytesPerRow;
3431
+ options.rowsPerImage = optionsWithoutUndefined.rowsPerImage ?? options.height;
3432
+ if (options.bytesPerRow < minimumBytesPerRow) {
3433
+ throw new Error(
3434
+ `bytesPerRow (${options.bytesPerRow}) must be at least ${minimumBytesPerRow} for ${this.format}`
3435
+ );
3436
+ }
3437
+ if (options.rowsPerImage < options.height) {
3438
+ throw new Error(
3439
+ `rowsPerImage (${options.rowsPerImage}) must be at least ${options.height} for ${this.format}`
3440
+ );
3441
+ }
3442
+ const bytesPerPixel = this.device.getTextureFormatInfo(this.format).bytesPerPixel;
3443
+ if (bytesPerPixel && options.bytesPerRow % bytesPerPixel !== 0) {
3444
+ throw new Error(
3445
+ `bytesPerRow (${options.bytesPerRow}) must be a multiple of bytesPerPixel (${bytesPerPixel}) for ${this.format}`
3446
+ );
3447
+ }
2764
3448
  return options;
2765
3449
  }
3450
+ _getMipLevelSize(mipLevel) {
3451
+ const width = Math.max(1, this.width >> mipLevel);
3452
+ const height = this.baseDimension === "1d" ? 1 : Math.max(1, this.height >> mipLevel);
3453
+ const depthOrArrayLayers = this.dimension === "3d" ? Math.max(1, this.depth >> mipLevel) : this.depth;
3454
+ return { width, height, depthOrArrayLayers };
3455
+ }
3456
+ getAllocatedByteLength() {
3457
+ let allocatedByteLength = 0;
3458
+ for (let mipLevel = 0; mipLevel < this.mipLevels; mipLevel++) {
3459
+ const { width, height, depthOrArrayLayers } = this._getMipLevelSize(mipLevel);
3460
+ allocatedByteLength += textureFormatDecoder.computeMemoryLayout({
3461
+ format: this.format,
3462
+ width,
3463
+ height,
3464
+ depth: depthOrArrayLayers,
3465
+ byteAlignment: 1
3466
+ }).byteLength;
3467
+ }
3468
+ return allocatedByteLength * this.samples;
3469
+ }
3470
+ static _omitUndefined(options) {
3471
+ return Object.fromEntries(
3472
+ Object.entries(options).filter(([, value]) => value !== void 0)
3473
+ );
3474
+ }
2766
3475
  };
2767
3476
  var Texture = _Texture;
2768
3477
  /** The texture can be bound for use as a sampled texture in a shader */
@@ -2798,6 +3507,10 @@ or create a device with the 'debug: true' prop.`;
2798
3507
  byteOffset: 0,
2799
3508
  bytesPerRow: void 0,
2800
3509
  rowsPerImage: void 0,
3510
+ width: void 0,
3511
+ height: void 0,
3512
+ depthOrArrayLayers: void 0,
3513
+ depth: 1,
2801
3514
  mipLevel: 0,
2802
3515
  x: 0,
2803
3516
  y: 0,
@@ -2831,6 +3544,19 @@ or create a device with the 'debug: true' prop.`;
2831
3544
  mipLevel: 0,
2832
3545
  aspect: "all"
2833
3546
  });
3547
+ __publicField(Texture, "defaultTextureWriteOptions", {
3548
+ byteOffset: 0,
3549
+ bytesPerRow: void 0,
3550
+ rowsPerImage: void 0,
3551
+ x: 0,
3552
+ y: 0,
3553
+ z: 0,
3554
+ width: void 0,
3555
+ height: void 0,
3556
+ depthOrArrayLayers: 1,
3557
+ mipLevel: 0,
3558
+ aspect: "all"
3559
+ });
2834
3560
 
2835
3561
  // ../core/src/adapter/resources/texture-view.ts
2836
3562
  var _TextureView = class extends Resource {
@@ -3209,10 +3935,21 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3209
3935
  linkStatus = "pending";
3210
3936
  /** The hash of the pipeline */
3211
3937
  hash = "";
3938
+ /** Optional shared backend implementation */
3939
+ sharedRenderPipeline = null;
3940
+ /** Whether shader or pipeline compilation/linking is still in progress */
3941
+ get isPending() {
3942
+ return this.linkStatus === "pending" || this.vs.compilationStatus === "pending" || this.fs?.compilationStatus === "pending";
3943
+ }
3944
+ /** Whether shader or pipeline compilation/linking has failed */
3945
+ get isErrored() {
3946
+ return this.linkStatus === "error" || this.vs.compilationStatus === "error" || this.fs?.compilationStatus === "error";
3947
+ }
3212
3948
  constructor(device, props) {
3213
3949
  super(device, props, _RenderPipeline.defaultProps);
3214
3950
  this.shaderLayout = this.props.shaderLayout;
3215
3951
  this.bufferLayout = this.props.bufferLayout || [];
3952
+ this.sharedRenderPipeline = this.props._sharedRenderPipeline || null;
3216
3953
  }
3217
3954
  };
3218
3955
  var RenderPipeline = _RenderPipeline;
@@ -3230,10 +3967,507 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3230
3967
  colorAttachmentFormats: void 0,
3231
3968
  depthStencilAttachmentFormat: void 0,
3232
3969
  parameters: {},
3233
- bindings: {},
3234
- uniforms: {}
3970
+ varyings: void 0,
3971
+ bufferMode: void 0,
3972
+ disableWarnings: false,
3973
+ _sharedRenderPipeline: void 0,
3974
+ bindings: void 0,
3975
+ bindGroups: void 0
3235
3976
  });
3236
3977
 
3978
+ // ../core/src/adapter/resources/shared-render-pipeline.ts
3979
+ var SharedRenderPipeline = class extends Resource {
3980
+ get [Symbol.toStringTag]() {
3981
+ return "SharedRenderPipeline";
3982
+ }
3983
+ constructor(device, props) {
3984
+ super(device, props, {
3985
+ ...Resource.defaultProps,
3986
+ handle: void 0,
3987
+ vs: void 0,
3988
+ fs: void 0,
3989
+ varyings: void 0,
3990
+ bufferMode: void 0
3991
+ });
3992
+ }
3993
+ };
3994
+
3995
+ // ../core/src/adapter/resources/compute-pipeline.ts
3996
+ var _ComputePipeline = class extends Resource {
3997
+ get [Symbol.toStringTag]() {
3998
+ return "ComputePipeline";
3999
+ }
4000
+ hash = "";
4001
+ /** The merged shader layout */
4002
+ shaderLayout;
4003
+ constructor(device, props) {
4004
+ super(device, props, _ComputePipeline.defaultProps);
4005
+ this.shaderLayout = props.shaderLayout;
4006
+ }
4007
+ };
4008
+ var ComputePipeline = _ComputePipeline;
4009
+ __publicField(ComputePipeline, "defaultProps", {
4010
+ ...Resource.defaultProps,
4011
+ shader: void 0,
4012
+ entryPoint: void 0,
4013
+ constants: {},
4014
+ shaderLayout: void 0
4015
+ });
4016
+
4017
+ // ../core/src/factories/pipeline-factory.ts
4018
+ var _PipelineFactory = class {
4019
+ /** Get the singleton default pipeline factory for the specified device */
4020
+ static getDefaultPipelineFactory(device) {
4021
+ const moduleData = device.getModuleData("@luma.gl/core");
4022
+ moduleData.defaultPipelineFactory ||= new _PipelineFactory(device);
4023
+ return moduleData.defaultPipelineFactory;
4024
+ }
4025
+ device;
4026
+ _hashCounter = 0;
4027
+ _hashes = {};
4028
+ _renderPipelineCache = {};
4029
+ _computePipelineCache = {};
4030
+ _sharedRenderPipelineCache = {};
4031
+ get [Symbol.toStringTag]() {
4032
+ return "PipelineFactory";
4033
+ }
4034
+ toString() {
4035
+ return `PipelineFactory(${this.device.id})`;
4036
+ }
4037
+ constructor(device) {
4038
+ this.device = device;
4039
+ }
4040
+ /**
4041
+ * WebGL has two cache layers with different priorities:
4042
+ * - `_sharedRenderPipelineCache` owns `WEBGLSharedRenderPipeline` / `WebGLProgram` reuse.
4043
+ * - `_renderPipelineCache` owns `RenderPipeline` wrapper reuse.
4044
+ *
4045
+ * Shared WebGL program reuse is the hard requirement. Wrapper reuse is beneficial,
4046
+ * but wrapper cache misses are acceptable if that keeps the cache logic simple and
4047
+ * prevents incorrect cache hits.
4048
+ *
4049
+ * In particular, wrapper hash logic must never force program creation or linked-program
4050
+ * introspection just to decide whether a shared WebGL program can be reused.
4051
+ */
4052
+ /** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */
4053
+ createRenderPipeline(props) {
4054
+ if (!this.device.props._cachePipelines) {
4055
+ return this.device.createRenderPipeline(props);
4056
+ }
4057
+ const allProps = { ...RenderPipeline.defaultProps, ...props };
4058
+ const cache = this._renderPipelineCache;
4059
+ const hash = this._hashRenderPipeline(allProps);
4060
+ let pipeline = cache[hash]?.resource;
4061
+ if (!pipeline) {
4062
+ const sharedRenderPipeline = this.device.type === "webgl" && this.device.props._sharePipelines ? this.createSharedRenderPipeline(allProps) : void 0;
4063
+ pipeline = this.device.createRenderPipeline({
4064
+ ...allProps,
4065
+ id: allProps.id ? `${allProps.id}-cached` : uid("unnamed-cached"),
4066
+ _sharedRenderPipeline: sharedRenderPipeline
4067
+ });
4068
+ pipeline.hash = hash;
4069
+ cache[hash] = { resource: pipeline, useCount: 1 };
4070
+ if (this.device.props.debugFactories) {
4071
+ log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
4072
+ }
4073
+ } else {
4074
+ cache[hash].useCount++;
4075
+ if (this.device.props.debugFactories) {
4076
+ log.log(
4077
+ 3,
4078
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
4079
+ )();
4080
+ }
4081
+ }
4082
+ return pipeline;
4083
+ }
4084
+ /** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */
4085
+ createComputePipeline(props) {
4086
+ if (!this.device.props._cachePipelines) {
4087
+ return this.device.createComputePipeline(props);
4088
+ }
4089
+ const allProps = { ...ComputePipeline.defaultProps, ...props };
4090
+ const cache = this._computePipelineCache;
4091
+ const hash = this._hashComputePipeline(allProps);
4092
+ let pipeline = cache[hash]?.resource;
4093
+ if (!pipeline) {
4094
+ pipeline = this.device.createComputePipeline({
4095
+ ...allProps,
4096
+ id: allProps.id ? `${allProps.id}-cached` : void 0
4097
+ });
4098
+ pipeline.hash = hash;
4099
+ cache[hash] = { resource: pipeline, useCount: 1 };
4100
+ if (this.device.props.debugFactories) {
4101
+ log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
4102
+ }
4103
+ } else {
4104
+ cache[hash].useCount++;
4105
+ if (this.device.props.debugFactories) {
4106
+ log.log(
4107
+ 3,
4108
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
4109
+ )();
4110
+ }
4111
+ }
4112
+ return pipeline;
4113
+ }
4114
+ release(pipeline) {
4115
+ if (!this.device.props._cachePipelines) {
4116
+ pipeline.destroy();
4117
+ return;
4118
+ }
4119
+ const cache = this._getCache(pipeline);
4120
+ const hash = pipeline.hash;
4121
+ cache[hash].useCount--;
4122
+ if (cache[hash].useCount === 0) {
4123
+ this._destroyPipeline(pipeline);
4124
+ if (this.device.props.debugFactories) {
4125
+ log.log(3, `${this}: ${pipeline} released and destroyed`)();
4126
+ }
4127
+ } else if (cache[hash].useCount < 0) {
4128
+ log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)();
4129
+ cache[hash].useCount = 0;
4130
+ } else if (this.device.props.debugFactories) {
4131
+ log.log(3, `${this}: ${pipeline} released, count=${cache[hash].useCount}`)();
4132
+ }
4133
+ }
4134
+ createSharedRenderPipeline(props) {
4135
+ const sharedPipelineHash = this._hashSharedRenderPipeline(props);
4136
+ let sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
4137
+ if (!sharedCacheItem) {
4138
+ const sharedRenderPipeline = this.device._createSharedRenderPipelineWebGL(props);
4139
+ sharedCacheItem = { resource: sharedRenderPipeline, useCount: 0 };
4140
+ this._sharedRenderPipelineCache[sharedPipelineHash] = sharedCacheItem;
4141
+ }
4142
+ sharedCacheItem.useCount++;
4143
+ return sharedCacheItem.resource;
4144
+ }
4145
+ releaseSharedRenderPipeline(pipeline) {
4146
+ if (!pipeline.sharedRenderPipeline) {
4147
+ return;
4148
+ }
4149
+ const sharedPipelineHash = this._hashSharedRenderPipeline(pipeline.sharedRenderPipeline.props);
4150
+ const sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
4151
+ if (!sharedCacheItem) {
4152
+ return;
4153
+ }
4154
+ sharedCacheItem.useCount--;
4155
+ if (sharedCacheItem.useCount === 0) {
4156
+ sharedCacheItem.resource.destroy();
4157
+ delete this._sharedRenderPipelineCache[sharedPipelineHash];
4158
+ }
4159
+ }
4160
+ // PRIVATE
4161
+ /** Destroy a cached pipeline, removing it from the cache if configured to do so. */
4162
+ _destroyPipeline(pipeline) {
4163
+ const cache = this._getCache(pipeline);
4164
+ if (!this.device.props._destroyPipelines) {
4165
+ return false;
4166
+ }
4167
+ delete cache[pipeline.hash];
4168
+ pipeline.destroy();
4169
+ if (pipeline instanceof RenderPipeline) {
4170
+ this.releaseSharedRenderPipeline(pipeline);
4171
+ }
4172
+ return true;
4173
+ }
4174
+ /** Get the appropriate cache for the type of pipeline */
4175
+ _getCache(pipeline) {
4176
+ let cache;
4177
+ if (pipeline instanceof ComputePipeline) {
4178
+ cache = this._computePipelineCache;
4179
+ }
4180
+ if (pipeline instanceof RenderPipeline) {
4181
+ cache = this._renderPipelineCache;
4182
+ }
4183
+ if (!cache) {
4184
+ throw new Error(`${this}`);
4185
+ }
4186
+ if (!cache[pipeline.hash]) {
4187
+ throw new Error(`${this}: ${pipeline} matched incorrect entry`);
4188
+ }
4189
+ return cache;
4190
+ }
4191
+ /** Calculate a hash based on all the inputs for a compute pipeline */
4192
+ _hashComputePipeline(props) {
4193
+ const { type } = this.device;
4194
+ const shaderHash = this._getHash(props.shader.source);
4195
+ const shaderLayoutHash = this._getHash(JSON.stringify(props.shaderLayout));
4196
+ return `${type}/C/${shaderHash}SL${shaderLayoutHash}`;
4197
+ }
4198
+ /** Calculate a hash based on all the inputs for a render pipeline */
4199
+ _hashRenderPipeline(props) {
4200
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
4201
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
4202
+ const varyingHash = this._getWebGLVaryingHash(props);
4203
+ const shaderLayoutHash = this._getHash(JSON.stringify(props.shaderLayout));
4204
+ const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
4205
+ const { type } = this.device;
4206
+ switch (type) {
4207
+ case "webgl":
4208
+ const webglParameterHash = this._getHash(JSON.stringify(props.parameters));
4209
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${webglParameterHash}SL${shaderLayoutHash}BL${bufferLayoutHash}`;
4210
+ case "webgpu":
4211
+ default:
4212
+ const entryPointHash = this._getHash(
4213
+ JSON.stringify({
4214
+ vertexEntryPoint: props.vertexEntryPoint,
4215
+ fragmentEntryPoint: props.fragmentEntryPoint
4216
+ })
4217
+ );
4218
+ const parameterHash = this._getHash(JSON.stringify(props.parameters));
4219
+ const attachmentHash = this._getWebGPUAttachmentHash(props);
4220
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}EP${entryPointHash}P${parameterHash}SL${shaderLayoutHash}BL${bufferLayoutHash}A${attachmentHash}`;
4221
+ }
4222
+ }
4223
+ // This is the only gate for shared `WebGLProgram` reuse.
4224
+ // Only include inputs that affect program linking or transform-feedback linkage.
4225
+ // Wrapper-only concerns such as topology, parameters, attachment formats and layout
4226
+ // overrides must not be added here.
4227
+ _hashSharedRenderPipeline(props) {
4228
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
4229
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
4230
+ const varyingHash = this._getWebGLVaryingHash(props);
4231
+ return `webgl/S/${vsHash}/${fsHash}V${varyingHash}`;
4232
+ }
4233
+ _getHash(key) {
4234
+ if (this._hashes[key] === void 0) {
4235
+ this._hashes[key] = this._hashCounter++;
4236
+ }
4237
+ return this._hashes[key];
4238
+ }
4239
+ _getWebGLVaryingHash(props) {
4240
+ const { varyings = [], bufferMode = null } = props;
4241
+ return this._getHash(JSON.stringify({ varyings, bufferMode }));
4242
+ }
4243
+ _getWebGPUAttachmentHash(props) {
4244
+ const colorAttachmentFormats = props.colorAttachmentFormats ?? [
4245
+ this.device.preferredColorFormat
4246
+ ];
4247
+ const depthStencilAttachmentFormat = props.parameters?.depthWriteEnabled ? props.depthStencilAttachmentFormat || this.device.preferredDepthFormat : null;
4248
+ return this._getHash(
4249
+ JSON.stringify({
4250
+ colorAttachmentFormats,
4251
+ depthStencilAttachmentFormat
4252
+ })
4253
+ );
4254
+ }
4255
+ };
4256
+ var PipelineFactory = _PipelineFactory;
4257
+ __publicField(PipelineFactory, "defaultProps", { ...RenderPipeline.defaultProps });
4258
+
4259
+ // ../core/src/factories/shader-factory.ts
4260
+ var _ShaderFactory = class {
4261
+ /** Returns the default ShaderFactory for the given {@link Device}, creating one if necessary. */
4262
+ static getDefaultShaderFactory(device) {
4263
+ const moduleData = device.getModuleData("@luma.gl/core");
4264
+ moduleData.defaultShaderFactory ||= new _ShaderFactory(device);
4265
+ return moduleData.defaultShaderFactory;
4266
+ }
4267
+ device;
4268
+ _cache = {};
4269
+ get [Symbol.toStringTag]() {
4270
+ return "ShaderFactory";
4271
+ }
4272
+ toString() {
4273
+ return `${this[Symbol.toStringTag]}(${this.device.id})`;
4274
+ }
4275
+ /** @internal */
4276
+ constructor(device) {
4277
+ this.device = device;
4278
+ }
4279
+ /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */
4280
+ createShader(props) {
4281
+ if (!this.device.props._cacheShaders) {
4282
+ return this.device.createShader(props);
4283
+ }
4284
+ const key = this._hashShader(props);
4285
+ let cacheEntry = this._cache[key];
4286
+ if (!cacheEntry) {
4287
+ const resource = this.device.createShader({
4288
+ ...props,
4289
+ id: props.id ? `${props.id}-cached` : void 0
4290
+ });
4291
+ this._cache[key] = cacheEntry = { resource, useCount: 1 };
4292
+ if (this.device.props.debugFactories) {
4293
+ log.log(3, `${this}: Created new shader ${resource.id}`)();
4294
+ }
4295
+ } else {
4296
+ cacheEntry.useCount++;
4297
+ if (this.device.props.debugFactories) {
4298
+ log.log(
4299
+ 3,
4300
+ `${this}: Reusing shader ${cacheEntry.resource.id} count=${cacheEntry.useCount}`
4301
+ )();
4302
+ }
4303
+ }
4304
+ return cacheEntry.resource;
4305
+ }
4306
+ /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */
4307
+ release(shader) {
4308
+ if (!this.device.props._cacheShaders) {
4309
+ shader.destroy();
4310
+ return;
4311
+ }
4312
+ const key = this._hashShader(shader);
4313
+ const cacheEntry = this._cache[key];
4314
+ if (cacheEntry) {
4315
+ cacheEntry.useCount--;
4316
+ if (cacheEntry.useCount === 0) {
4317
+ if (this.device.props._destroyShaders) {
4318
+ delete this._cache[key];
4319
+ cacheEntry.resource.destroy();
4320
+ if (this.device.props.debugFactories) {
4321
+ log.log(3, `${this}: Releasing shader ${shader.id}, destroyed`)();
4322
+ }
4323
+ }
4324
+ } else if (cacheEntry.useCount < 0) {
4325
+ throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`);
4326
+ } else if (this.device.props.debugFactories) {
4327
+ log.log(3, `${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)();
4328
+ }
4329
+ }
4330
+ }
4331
+ // PRIVATE
4332
+ _hashShader(value) {
4333
+ return `${value.stage}:${value.source}`;
4334
+ }
4335
+ };
4336
+ var ShaderFactory = _ShaderFactory;
4337
+ __publicField(ShaderFactory, "defaultProps", { ...Shader.defaultProps });
4338
+
4339
+ // ../core/src/adapter-utils/bind-groups.ts
4340
+ function getShaderLayoutBinding(shaderLayout, bindingName, options) {
4341
+ const bindingLayout = shaderLayout.bindings.find(
4342
+ (binding) => binding.name === bindingName || `${binding.name.toLocaleLowerCase()}uniforms` === bindingName.toLocaleLowerCase()
4343
+ );
4344
+ if (!bindingLayout && !options?.ignoreWarnings) {
4345
+ log.warn(`Binding ${bindingName} not set: Not found in shader layout.`)();
4346
+ }
4347
+ return bindingLayout || null;
4348
+ }
4349
+ function normalizeBindingsByGroup(shaderLayout, bindingsOrBindGroups) {
4350
+ if (!bindingsOrBindGroups) {
4351
+ return {};
4352
+ }
4353
+ if (areBindingsGrouped(bindingsOrBindGroups)) {
4354
+ const bindGroups2 = bindingsOrBindGroups;
4355
+ return Object.fromEntries(
4356
+ Object.entries(bindGroups2).map(([group, bindings]) => [Number(group), { ...bindings }])
4357
+ );
4358
+ }
4359
+ const bindGroups = {};
4360
+ for (const [bindingName, binding] of Object.entries(bindingsOrBindGroups)) {
4361
+ const bindingLayout = getShaderLayoutBinding(shaderLayout, bindingName);
4362
+ const group = bindingLayout?.group ?? 0;
4363
+ bindGroups[group] ||= {};
4364
+ bindGroups[group][bindingName] = binding;
4365
+ }
4366
+ return bindGroups;
4367
+ }
4368
+ function flattenBindingsByGroup(bindGroups) {
4369
+ const bindings = {};
4370
+ for (const groupBindings of Object.values(bindGroups)) {
4371
+ Object.assign(bindings, groupBindings);
4372
+ }
4373
+ return bindings;
4374
+ }
4375
+ function areBindingsGrouped(bindingsOrBindGroups) {
4376
+ const keys = Object.keys(bindingsOrBindGroups);
4377
+ return keys.length > 0 && keys.every((key) => /^\d+$/.test(key));
4378
+ }
4379
+
4380
+ // ../core/src/factories/bind-group-factory.ts
4381
+ var BindGroupFactory = class {
4382
+ device;
4383
+ _layoutCacheByPipeline = /* @__PURE__ */ new WeakMap();
4384
+ _bindGroupCacheByLayout = /* @__PURE__ */ new WeakMap();
4385
+ constructor(device) {
4386
+ this.device = device;
4387
+ }
4388
+ getBindGroups(pipeline, bindings, bindGroupCacheKeys) {
4389
+ if (this.device.type !== "webgpu" || pipeline.shaderLayout.bindings.length === 0) {
4390
+ return {};
4391
+ }
4392
+ const bindingsByGroup = normalizeBindingsByGroup(pipeline.shaderLayout, bindings);
4393
+ const resolvedBindGroups = {};
4394
+ for (const group of getBindGroupIndicesUpToMax(pipeline.shaderLayout.bindings)) {
4395
+ const groupBindings = bindingsByGroup[group];
4396
+ const bindGroupLayout = this._getBindGroupLayout(pipeline, group);
4397
+ if (!groupBindings || Object.keys(groupBindings).length === 0) {
4398
+ if (!hasBindingsInGroup(pipeline.shaderLayout.bindings, group)) {
4399
+ resolvedBindGroups[group] = this._getEmptyBindGroup(
4400
+ bindGroupLayout,
4401
+ pipeline.shaderLayout,
4402
+ group
4403
+ );
4404
+ }
4405
+ continue;
4406
+ }
4407
+ const bindGroupCacheKey = bindGroupCacheKeys?.[group];
4408
+ if (bindGroupCacheKey) {
4409
+ const layoutCache = this._getLayoutBindGroupCache(bindGroupLayout);
4410
+ if (layoutCache.bindGroupsBySource.has(bindGroupCacheKey)) {
4411
+ resolvedBindGroups[group] = layoutCache.bindGroupsBySource.get(bindGroupCacheKey) || null;
4412
+ continue;
4413
+ }
4414
+ const bindGroup = this.device._createBindGroupWebGPU(
4415
+ bindGroupLayout,
4416
+ pipeline.shaderLayout,
4417
+ groupBindings,
4418
+ group
4419
+ );
4420
+ layoutCache.bindGroupsBySource.set(bindGroupCacheKey, bindGroup);
4421
+ resolvedBindGroups[group] = bindGroup;
4422
+ } else {
4423
+ resolvedBindGroups[group] = this.device._createBindGroupWebGPU(
4424
+ bindGroupLayout,
4425
+ pipeline.shaderLayout,
4426
+ groupBindings,
4427
+ group
4428
+ );
4429
+ }
4430
+ }
4431
+ return resolvedBindGroups;
4432
+ }
4433
+ _getBindGroupLayout(pipeline, group) {
4434
+ let layoutCache = this._layoutCacheByPipeline.get(pipeline);
4435
+ if (!layoutCache) {
4436
+ layoutCache = {};
4437
+ this._layoutCacheByPipeline.set(pipeline, layoutCache);
4438
+ }
4439
+ layoutCache[group] ||= this.device._createBindGroupLayoutWebGPU(pipeline, group);
4440
+ return layoutCache[group];
4441
+ }
4442
+ _getEmptyBindGroup(bindGroupLayout, shaderLayout, group) {
4443
+ const layoutCache = this._getLayoutBindGroupCache(bindGroupLayout);
4444
+ layoutCache.emptyBindGroup ||= this.device._createBindGroupWebGPU(bindGroupLayout, shaderLayout, {}, group) || null;
4445
+ return layoutCache.emptyBindGroup;
4446
+ }
4447
+ _getLayoutBindGroupCache(bindGroupLayout) {
4448
+ let layoutCache = this._bindGroupCacheByLayout.get(bindGroupLayout);
4449
+ if (!layoutCache) {
4450
+ layoutCache = { bindGroupsBySource: /* @__PURE__ */ new WeakMap() };
4451
+ this._bindGroupCacheByLayout.set(bindGroupLayout, layoutCache);
4452
+ }
4453
+ return layoutCache;
4454
+ }
4455
+ };
4456
+ function _getDefaultBindGroupFactory(device) {
4457
+ device._factories.bindGroupFactory ||= new BindGroupFactory(device);
4458
+ return device._factories.bindGroupFactory;
4459
+ }
4460
+ function getBindGroupIndicesUpToMax(bindings) {
4461
+ const maxGroup = bindings.reduce(
4462
+ (highestGroup, binding) => Math.max(highestGroup, binding.group),
4463
+ -1
4464
+ );
4465
+ return Array.from({ length: maxGroup + 1 }, (_, group) => group);
4466
+ }
4467
+ function hasBindingsInGroup(bindings, group) {
4468
+ return bindings.some((binding) => binding.group === group);
4469
+ }
4470
+
3237
4471
  // ../core/src/adapter/resources/render-pass.ts
3238
4472
  var _RenderPass = class extends Resource {
3239
4473
  get [Symbol.toStringTag]() {
@@ -3272,28 +4506,6 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3272
4506
  endTimestampIndex: void 0
3273
4507
  });
3274
4508
 
3275
- // ../core/src/adapter/resources/compute-pipeline.ts
3276
- var _ComputePipeline = class extends Resource {
3277
- get [Symbol.toStringTag]() {
3278
- return "ComputePipeline";
3279
- }
3280
- hash = "";
3281
- /** The merged shader layout */
3282
- shaderLayout;
3283
- constructor(device, props) {
3284
- super(device, props, _ComputePipeline.defaultProps);
3285
- this.shaderLayout = props.shaderLayout;
3286
- }
3287
- };
3288
- var ComputePipeline = _ComputePipeline;
3289
- __publicField(ComputePipeline, "defaultProps", {
3290
- ...Resource.defaultProps,
3291
- shader: void 0,
3292
- entryPoint: void 0,
3293
- constants: {},
3294
- shaderLayout: void 0
3295
- });
3296
-
3297
4509
  // ../core/src/adapter/resources/compute-pass.ts
3298
4510
  var _ComputePass = class extends Resource {
3299
4511
  constructor(device, props) {
@@ -3316,8 +4528,69 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3316
4528
  get [Symbol.toStringTag]() {
3317
4529
  return "CommandEncoder";
3318
4530
  }
4531
+ _timeProfilingQuerySet = null;
4532
+ _timeProfilingSlotCount = 0;
4533
+ _gpuTimeMs;
3319
4534
  constructor(device, props) {
3320
4535
  super(device, props, _CommandEncoder.defaultProps);
4536
+ this._timeProfilingQuerySet = props.timeProfilingQuerySet ?? null;
4537
+ this._timeProfilingSlotCount = 0;
4538
+ this._gpuTimeMs = void 0;
4539
+ }
4540
+ /**
4541
+ * Reads all resolved timestamp pairs on the current profiler query set and caches the sum
4542
+ * as milliseconds on this encoder.
4543
+ */
4544
+ async resolveTimeProfilingQuerySet() {
4545
+ this._gpuTimeMs = void 0;
4546
+ if (!this._timeProfilingQuerySet) {
4547
+ return;
4548
+ }
4549
+ const pairCount = Math.floor(this._timeProfilingSlotCount / 2);
4550
+ if (pairCount <= 0) {
4551
+ return;
4552
+ }
4553
+ const queryCount = pairCount * 2;
4554
+ const results = await this._timeProfilingQuerySet.readResults({
4555
+ firstQuery: 0,
4556
+ queryCount
4557
+ });
4558
+ let totalDurationNanoseconds = 0n;
4559
+ for (let queryIndex = 0; queryIndex < queryCount; queryIndex += 2) {
4560
+ totalDurationNanoseconds += results[queryIndex + 1] - results[queryIndex];
4561
+ }
4562
+ this._gpuTimeMs = Number(totalDurationNanoseconds) / 1e6;
4563
+ }
4564
+ /** Returns the number of query slots consumed by automatic pass profiling on this encoder. */
4565
+ getTimeProfilingSlotCount() {
4566
+ return this._timeProfilingSlotCount;
4567
+ }
4568
+ getTimeProfilingQuerySet() {
4569
+ return this._timeProfilingQuerySet;
4570
+ }
4571
+ /** Internal helper for auto-assigning timestamp slots to render/compute passes on this encoder. */
4572
+ _applyTimeProfilingToPassProps(props) {
4573
+ const passProps = props || {};
4574
+ if (!this._supportsTimestampQueries() || !this._timeProfilingQuerySet) {
4575
+ return passProps;
4576
+ }
4577
+ if (passProps.timestampQuerySet !== void 0 || passProps.beginTimestampIndex !== void 0 || passProps.endTimestampIndex !== void 0) {
4578
+ return passProps;
4579
+ }
4580
+ const beginTimestampIndex = this._timeProfilingSlotCount;
4581
+ if (beginTimestampIndex + 1 >= this._timeProfilingQuerySet.props.count) {
4582
+ return passProps;
4583
+ }
4584
+ this._timeProfilingSlotCount += 2;
4585
+ return {
4586
+ ...passProps,
4587
+ timestampQuerySet: this._timeProfilingQuerySet,
4588
+ beginTimestampIndex,
4589
+ endTimestampIndex: beginTimestampIndex + 1
4590
+ };
4591
+ }
4592
+ _supportsTimestampQueries() {
4593
+ return this.device.features.has("timestamp-query");
3321
4594
  }
3322
4595
  };
3323
4596
  var CommandEncoder = _CommandEncoder;
@@ -3326,7 +4599,8 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3326
4599
  // beginComputePass(optional GPUComputePassDescriptor descriptor = {}): GPUComputePassEncoder;
3327
4600
  __publicField(CommandEncoder, "defaultProps", {
3328
4601
  ...Resource.defaultProps,
3329
- measureExecutionTime: void 0
4602
+ measureExecutionTime: void 0,
4603
+ timeProfilingQuerySet: void 0
3330
4604
  });
3331
4605
 
3332
4606
  // ../core/src/adapter/resources/command-buffer.ts
@@ -3343,13 +4617,22 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3343
4617
  ...Resource.defaultProps
3344
4618
  });
3345
4619
 
3346
- // ../core/src/shadertypes/data-types/decode-shader-types.ts
4620
+ // ../core/src/shadertypes/shader-types/shader-type-decoder.ts
3347
4621
  function getVariableShaderTypeInfo(format) {
3348
- const decoded = UNIFORM_FORMATS[format];
4622
+ const resolvedFormat = resolveVariableShaderTypeAlias(format);
4623
+ const decoded = UNIFORM_FORMATS[resolvedFormat];
4624
+ if (!decoded) {
4625
+ throw new Error(`Unsupported variable shader type: ${format}`);
4626
+ }
3349
4627
  return decoded;
3350
4628
  }
3351
4629
  function getAttributeShaderTypeInfo(attributeType) {
3352
- const [primitiveType, components] = TYPE_INFO[attributeType];
4630
+ const resolvedAttributeType = resolveAttributeShaderTypeAlias(attributeType);
4631
+ const decoded = TYPE_INFO[resolvedAttributeType];
4632
+ if (!decoded) {
4633
+ throw new Error(`Unsupported attribute shader type: ${attributeType}`);
4634
+ }
4635
+ const [primitiveType, components] = decoded;
3353
4636
  const integer = primitiveType === "i32" || primitiveType === "u32";
3354
4637
  const signed = primitiveType !== "u32";
3355
4638
  const byteLength = PRIMITIVE_TYPE_SIZES[primitiveType] * components;
@@ -3361,6 +4644,33 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3361
4644
  signed
3362
4645
  };
3363
4646
  }
4647
+ var ShaderTypeDecoder = class {
4648
+ getVariableShaderTypeInfo(format) {
4649
+ return getVariableShaderTypeInfo(format);
4650
+ }
4651
+ getAttributeShaderTypeInfo(attributeType) {
4652
+ return getAttributeShaderTypeInfo(attributeType);
4653
+ }
4654
+ makeShaderAttributeType(primitiveType, components) {
4655
+ return makeShaderAttributeType(primitiveType, components);
4656
+ }
4657
+ resolveAttributeShaderTypeAlias(alias) {
4658
+ return resolveAttributeShaderTypeAlias(alias);
4659
+ }
4660
+ resolveVariableShaderTypeAlias(alias) {
4661
+ return resolveVariableShaderTypeAlias(alias);
4662
+ }
4663
+ };
4664
+ function makeShaderAttributeType(primitiveType, components) {
4665
+ return components === 1 ? primitiveType : `vec${components}<${primitiveType}>`;
4666
+ }
4667
+ function resolveAttributeShaderTypeAlias(alias) {
4668
+ return WGSL_ATTRIBUTE_TYPE_ALIAS_MAP[alias] || alias;
4669
+ }
4670
+ function resolveVariableShaderTypeAlias(alias) {
4671
+ return WGSL_VARIABLE_TYPE_ALIAS_MAP[alias] || alias;
4672
+ }
4673
+ var shaderTypeDecoder = new ShaderTypeDecoder();
3364
4674
  var PRIMITIVE_TYPE_SIZES = {
3365
4675
  f32: 4,
3366
4676
  f16: 2,
@@ -3457,7 +4767,18 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3457
4767
  vec4h: "vec4<f16>"
3458
4768
  };
3459
4769
  var WGSL_VARIABLE_TYPE_ALIAS_MAP = {
3460
- ...WGSL_ATTRIBUTE_TYPE_ALIAS_MAP,
4770
+ vec2i: "vec2<i32>",
4771
+ vec3i: "vec3<i32>",
4772
+ vec4i: "vec4<i32>",
4773
+ vec2u: "vec2<u32>",
4774
+ vec3u: "vec3<u32>",
4775
+ vec4u: "vec4<u32>",
4776
+ vec2f: "vec2<f32>",
4777
+ vec3f: "vec3<f32>",
4778
+ vec4f: "vec4<f32>",
4779
+ vec2h: "vec2<f16>",
4780
+ vec3h: "vec3<f16>",
4781
+ vec4h: "vec4<f16>",
3461
4782
  mat2x2f: "mat2x2<f32>",
3462
4783
  mat2x3f: "mat2x3<f32>",
3463
4784
  mat2x4f: "mat2x4<f32>",
@@ -3524,10 +4845,10 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3524
4845
  if (!shaderDeclaration) {
3525
4846
  return null;
3526
4847
  }
3527
- const attributeTypeInfo = getAttributeShaderTypeInfo(shaderDeclaration.type);
3528
- const defaultVertexFormat = getCompatibleVertexFormat(attributeTypeInfo);
4848
+ const attributeTypeInfo = shaderTypeDecoder.getAttributeShaderTypeInfo(shaderDeclaration.type);
4849
+ const defaultVertexFormat = vertexFormatDecoder.getCompatibleVertexFormat(attributeTypeInfo);
3529
4850
  const vertexFormat = bufferMapping?.vertexFormat || defaultVertexFormat;
3530
- const vertexFormatInfo = getVertexFormatInfo(vertexFormat);
4851
+ const vertexFormatInfo = vertexFormatDecoder.getVertexFormatInfo(vertexFormat);
3531
4852
  return {
3532
4853
  attributeName: bufferMapping?.attributeName || shaderDeclaration.name,
3533
4854
  bufferName: bufferMapping?.bufferName || shaderDeclaration.name,
@@ -3595,7 +4916,7 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3595
4916
  let byteStride = bufferLayout.byteStride;
3596
4917
  if (typeof bufferLayout.byteStride !== "number") {
3597
4918
  for (const attributeMapping2 of bufferLayout.attributes || []) {
3598
- const info = getVertexFormatInfo(attributeMapping2.format);
4919
+ const info = vertexFormatDecoder.getVertexFormatInfo(attributeMapping2.format);
3599
4920
  byteStride += info.byteLength;
3600
4921
  }
3601
4922
  }
@@ -3685,7 +5006,9 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3685
5006
 
3686
5007
  // ../core/src/adapter/resources/fence.ts
3687
5008
  var _Fence = class extends Resource {
3688
- [Symbol.toStringTag] = "WEBGLFence";
5009
+ get [Symbol.toStringTag]() {
5010
+ return "Fence";
5011
+ }
3689
5012
  constructor(device, props = {}) {
3690
5013
  super(device, props, _Fence.defaultProps);
3691
5014
  }
@@ -3713,6 +5036,36 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3713
5036
  }
3714
5037
  });
3715
5038
 
5039
+ // ../core/src/shadertypes/data-types/decode-data-types.ts
5040
+ function alignTo(size, count) {
5041
+ switch (count) {
5042
+ case 1:
5043
+ return size;
5044
+ case 2:
5045
+ return size + size % 2;
5046
+ default:
5047
+ return size + (4 - size % 4) % 4;
5048
+ }
5049
+ }
5050
+ function getTypedArrayConstructor(type) {
5051
+ const [, , , , Constructor] = NORMALIZED_TYPE_MAP2[type];
5052
+ return Constructor;
5053
+ }
5054
+ var NORMALIZED_TYPE_MAP2 = {
5055
+ uint8: ["uint8", "u32", 1, false, Uint8Array],
5056
+ sint8: ["sint8", "i32", 1, false, Int8Array],
5057
+ unorm8: ["uint8", "f32", 1, true, Uint8Array],
5058
+ snorm8: ["sint8", "f32", 1, true, Int8Array],
5059
+ uint16: ["uint16", "u32", 2, false, Uint16Array],
5060
+ sint16: ["sint16", "i32", 2, false, Int16Array],
5061
+ unorm16: ["uint16", "u32", 2, true, Uint16Array],
5062
+ snorm16: ["sint16", "i32", 2, true, Int16Array],
5063
+ float16: ["float16", "f16", 2, false, Uint16Array],
5064
+ float32: ["float32", "f32", 4, false, Float32Array],
5065
+ uint32: ["uint32", "u32", 4, false, Uint32Array],
5066
+ sint32: ["sint32", "i32", 4, false, Int32Array]
5067
+ };
5068
+
3716
5069
  // ../core/src/utils/array-utils-flat.ts
3717
5070
  var arrayBuffer;
3718
5071
  function getScratchArrayBuffer(byteLength) {
@@ -3726,19 +5079,32 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3726
5079
  return new Type(scratchArrayBuffer, 0, length);
3727
5080
  }
3728
5081
 
5082
+ // ../core/src/utils/is-array.ts
5083
+ function isTypedArray(value) {
5084
+ return ArrayBuffer.isView(value) && !(value instanceof DataView);
5085
+ }
5086
+ function isNumberArray(value) {
5087
+ if (Array.isArray(value)) {
5088
+ return value.length === 0 || typeof value[0] === "number";
5089
+ }
5090
+ return isTypedArray(value);
5091
+ }
5092
+
3729
5093
  // ../core/src/portable/uniform-buffer-layout.ts
3730
5094
  var minBufferSize = 1024;
3731
5095
  var UniformBufferLayout = class {
3732
5096
  layout = {};
5097
+ uniformTypes;
3733
5098
  /** number of bytes needed for buffer allocation */
3734
5099
  byteLength;
3735
5100
  /** Create a new UniformBufferLayout given a map of attributes. */
3736
- constructor(uniformTypes, uniformSizes = {}) {
5101
+ constructor(uniformTypes) {
5102
+ this.uniformTypes = { ...uniformTypes };
3737
5103
  let size = 0;
3738
- for (const [key, uniformType] of Object.entries(uniformTypes)) {
3739
- size = this._addToLayout(key, uniformType, size, uniformSizes?.[key]);
5104
+ for (const [key, uniformType] of Object.entries(this.uniformTypes)) {
5105
+ size = this._addToLayout(key, uniformType, size);
3740
5106
  }
3741
- size += (4 - size % 4) % 4;
5107
+ size = alignTo(size, 4);
3742
5108
  this.byteLength = Math.max(size * 4, minBufferSize);
3743
5109
  }
3744
5110
  /** Does this layout have a field with specified name */
@@ -3750,115 +5116,264 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3750
5116
  const layout = this.layout[name2];
3751
5117
  return layout;
3752
5118
  }
5119
+ /** Flatten nested uniform values into leaf-path values understood by UniformBlock. */
5120
+ getFlatUniformValues(uniformValues) {
5121
+ const flattenedUniformValues = {};
5122
+ for (const [name2, value] of Object.entries(uniformValues)) {
5123
+ const uniformType = this.uniformTypes[name2];
5124
+ if (uniformType) {
5125
+ this._flattenCompositeValue(flattenedUniformValues, name2, uniformType, value);
5126
+ } else if (this.layout[name2]) {
5127
+ flattenedUniformValues[name2] = value;
5128
+ }
5129
+ }
5130
+ return flattenedUniformValues;
5131
+ }
3753
5132
  /** Get the data for the complete buffer */
3754
5133
  getData(uniformValues) {
3755
5134
  const buffer = getScratchArrayBuffer(this.byteLength);
5135
+ new Uint8Array(buffer, 0, this.byteLength).fill(0);
3756
5136
  const typedArrays = {
3757
5137
  i32: new Int32Array(buffer),
3758
5138
  u32: new Uint32Array(buffer),
3759
5139
  f32: new Float32Array(buffer),
3760
5140
  f16: new Uint16Array(buffer)
3761
5141
  };
3762
- for (const [name2, value] of Object.entries(uniformValues)) {
3763
- this._writeCompositeValue(typedArrays, name2, value);
5142
+ const flattenedUniformValues = this.getFlatUniformValues(uniformValues);
5143
+ for (const [name2, value] of Object.entries(flattenedUniformValues)) {
5144
+ this._writeLeafValue(typedArrays, name2, value);
3764
5145
  }
3765
5146
  return new Uint8Array(buffer, 0, this.byteLength);
3766
5147
  }
3767
5148
  // Recursively add a uniform to the layout
3768
- _addToLayout(name2, type, offset, count = 1) {
5149
+ _addToLayout(name2, type, offset) {
3769
5150
  if (typeof type === "string") {
3770
- const info = getVariableShaderTypeInfo(type);
3771
- const sizeInSlots = info.components * count;
3772
- const alignedOffset = alignTo(offset, info.components);
5151
+ const info = getLeafLayoutInfo(type);
5152
+ const alignedOffset = alignTo(offset, info.alignment);
3773
5153
  this.layout[name2] = {
3774
5154
  offset: alignedOffset,
3775
- size: sizeInSlots,
3776
- type: info.type
5155
+ ...info
3777
5156
  };
3778
- return alignedOffset + sizeInSlots;
5157
+ return alignedOffset + info.size;
3779
5158
  }
3780
5159
  if (Array.isArray(type)) {
5160
+ if (Array.isArray(type[0])) {
5161
+ throw new Error(`Nested arrays are not supported for ${name2}`);
5162
+ }
3781
5163
  const elementType = type[0];
3782
- const length = count > 1 ? count : type.length > 1 ? type[1] : 1;
3783
- let arrayOffset = alignTo(offset, 4);
5164
+ const length = type[1];
5165
+ const stride = alignTo(getTypeSize(elementType), 4);
5166
+ const arrayOffset = alignTo(offset, 4);
3784
5167
  for (let i = 0; i < length; i++) {
3785
- arrayOffset = this._addToLayout(`${name2}[${i}]`, elementType, arrayOffset);
5168
+ this._addToLayout(`${name2}[${i}]`, elementType, arrayOffset + i * stride);
3786
5169
  }
3787
- return arrayOffset;
5170
+ return arrayOffset + stride * length;
3788
5171
  }
3789
- if (typeof type === "object") {
5172
+ if (isCompositeShaderTypeStruct(type)) {
3790
5173
  let structOffset = alignTo(offset, 4);
3791
5174
  for (const [memberName, memberType] of Object.entries(type)) {
3792
5175
  structOffset = this._addToLayout(`${name2}.${memberName}`, memberType, structOffset);
3793
5176
  }
3794
- return structOffset;
5177
+ return alignTo(structOffset, 4);
3795
5178
  }
3796
5179
  throw new Error(`Unsupported CompositeShaderType for ${name2}`);
3797
5180
  }
3798
- _writeCompositeValue(typedArrays, baseName, value) {
3799
- if (this.layout[baseName]) {
3800
- this._writeToBuffer(typedArrays, baseName, value);
5181
+ _flattenCompositeValue(flattenedUniformValues, baseName, uniformType, value) {
5182
+ if (value === void 0) {
5183
+ return;
5184
+ }
5185
+ if (typeof uniformType === "string" || this.layout[baseName]) {
5186
+ flattenedUniformValues[baseName] = value;
3801
5187
  return;
3802
5188
  }
3803
- if (Array.isArray(value)) {
3804
- for (let i = 0; i < value.length; i++) {
3805
- const element = value[i];
3806
- const indexedName = `${baseName}[${i}]`;
3807
- this._writeCompositeValue(typedArrays, indexedName, element);
5189
+ if (Array.isArray(uniformType)) {
5190
+ const elementType = uniformType[0];
5191
+ const length = uniformType[1];
5192
+ if (Array.isArray(elementType)) {
5193
+ throw new Error(`Nested arrays are not supported for ${baseName}`);
5194
+ }
5195
+ if (typeof elementType === "string" && isNumberArray(value)) {
5196
+ this._flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value);
5197
+ return;
5198
+ }
5199
+ if (!Array.isArray(value)) {
5200
+ log.warn(`Unsupported uniform array value for ${baseName}:`, value)();
5201
+ return;
5202
+ }
5203
+ for (let index = 0; index < Math.min(value.length, length); index++) {
5204
+ const elementValue = value[index];
5205
+ if (elementValue === void 0) {
5206
+ continue;
5207
+ }
5208
+ this._flattenCompositeValue(
5209
+ flattenedUniformValues,
5210
+ `${baseName}[${index}]`,
5211
+ elementType,
5212
+ elementValue
5213
+ );
3808
5214
  }
3809
5215
  return;
3810
5216
  }
3811
- if (typeof value === "object" && value !== null) {
5217
+ if (isCompositeShaderTypeStruct(uniformType) && isCompositeUniformObject(value)) {
3812
5218
  for (const [key, subValue] of Object.entries(value)) {
5219
+ if (subValue === void 0) {
5220
+ continue;
5221
+ }
3813
5222
  const nestedName = `${baseName}.${key}`;
3814
- this._writeCompositeValue(typedArrays, nestedName, subValue);
5223
+ this._flattenCompositeValue(flattenedUniformValues, nestedName, uniformType[key], subValue);
3815
5224
  }
3816
5225
  return;
3817
5226
  }
3818
5227
  log.warn(`Unsupported uniform value for ${baseName}:`, value)();
3819
5228
  }
3820
- _writeToBuffer(typedArrays, name2, value) {
5229
+ _flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value) {
5230
+ const numericValue = value;
5231
+ const elementLayout = getLeafLayoutInfo(elementType);
5232
+ const packedElementLength = elementLayout.components;
5233
+ for (let index = 0; index < length; index++) {
5234
+ const start = index * packedElementLength;
5235
+ if (start >= numericValue.length) {
5236
+ break;
5237
+ }
5238
+ if (packedElementLength === 1) {
5239
+ flattenedUniformValues[`${baseName}[${index}]`] = Number(numericValue[start]);
5240
+ } else {
5241
+ flattenedUniformValues[`${baseName}[${index}]`] = sliceNumericArray(
5242
+ value,
5243
+ start,
5244
+ start + packedElementLength
5245
+ );
5246
+ }
5247
+ }
5248
+ }
5249
+ _writeLeafValue(typedArrays, name2, value) {
3821
5250
  const layout = this.layout[name2];
3822
5251
  if (!layout) {
3823
5252
  log.warn(`Uniform ${name2} not found in layout`)();
3824
5253
  return;
3825
5254
  }
3826
- const { type, size, offset } = layout;
5255
+ const { type, components, columns, rows, offset } = layout;
3827
5256
  const array = typedArrays[type];
3828
- if (size === 1) {
5257
+ if (components === 1) {
3829
5258
  array[offset] = Number(value);
3830
- } else {
3831
- array.set(value, offset);
5259
+ return;
5260
+ }
5261
+ const sourceValue = value;
5262
+ if (columns === 1) {
5263
+ for (let componentIndex = 0; componentIndex < components; componentIndex++) {
5264
+ array[offset + componentIndex] = Number(sourceValue[componentIndex] ?? 0);
5265
+ }
5266
+ return;
5267
+ }
5268
+ let sourceIndex = 0;
5269
+ for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
5270
+ const columnOffset = offset + columnIndex * 4;
5271
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
5272
+ array[columnOffset + rowIndex] = Number(sourceValue[sourceIndex++] ?? 0);
5273
+ }
3832
5274
  }
3833
5275
  }
3834
5276
  };
3835
-
3836
- // ../core/src/utils/is-array.ts
3837
- function isTypedArray(value) {
3838
- return ArrayBuffer.isView(value) && !(value instanceof DataView);
5277
+ function getTypeSize(type) {
5278
+ if (typeof type === "string") {
5279
+ return getLeafLayoutInfo(type).size;
5280
+ }
5281
+ if (Array.isArray(type)) {
5282
+ const elementType = type[0];
5283
+ const length = type[1];
5284
+ if (Array.isArray(elementType)) {
5285
+ throw new Error("Nested arrays are not supported");
5286
+ }
5287
+ return alignTo(getTypeSize(elementType), 4) * length;
5288
+ }
5289
+ let size = 0;
5290
+ for (const memberType of Object.values(type)) {
5291
+ const compositeMemberType = memberType;
5292
+ size = alignTo(size, getTypeAlignment(compositeMemberType));
5293
+ size += getTypeSize(compositeMemberType);
5294
+ }
5295
+ return alignTo(size, 4);
3839
5296
  }
3840
- function isNumberArray(value) {
3841
- if (Array.isArray(value)) {
3842
- return value.length === 0 || typeof value[0] === "number";
5297
+ function getTypeAlignment(type) {
5298
+ if (typeof type === "string") {
5299
+ return getLeafLayoutInfo(type).alignment;
3843
5300
  }
3844
- return isTypedArray(value);
5301
+ if (Array.isArray(type)) {
5302
+ return 4;
5303
+ }
5304
+ return 4;
5305
+ }
5306
+ function getLeafLayoutInfo(type) {
5307
+ const resolvedType = resolveVariableShaderTypeAlias(type);
5308
+ const decodedType = getVariableShaderTypeInfo(resolvedType);
5309
+ const matrixMatch = /^mat(\d)x(\d)<.+>$/.exec(resolvedType);
5310
+ if (matrixMatch) {
5311
+ const columns = Number(matrixMatch[1]);
5312
+ const rows = Number(matrixMatch[2]);
5313
+ return {
5314
+ alignment: 4,
5315
+ size: columns * 4,
5316
+ components: columns * rows,
5317
+ columns,
5318
+ rows,
5319
+ shaderType: resolvedType,
5320
+ type: decodedType.type
5321
+ };
5322
+ }
5323
+ const vectorMatch = /^vec(\d)<.+>$/.exec(resolvedType);
5324
+ if (vectorMatch) {
5325
+ const components = Number(vectorMatch[1]);
5326
+ return {
5327
+ alignment: components === 2 ? 2 : 4,
5328
+ size: components === 3 ? 4 : components,
5329
+ components,
5330
+ columns: 1,
5331
+ rows: components,
5332
+ shaderType: resolvedType,
5333
+ type: decodedType.type
5334
+ };
5335
+ }
5336
+ return {
5337
+ alignment: 1,
5338
+ size: 1,
5339
+ components: 1,
5340
+ columns: 1,
5341
+ rows: 1,
5342
+ shaderType: resolvedType,
5343
+ type: decodedType.type
5344
+ };
5345
+ }
5346
+ function isCompositeShaderTypeStruct(value) {
5347
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5348
+ }
5349
+ function isCompositeUniformObject(value) {
5350
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && !ArrayBuffer.isView(value);
5351
+ }
5352
+ function sliceNumericArray(value, start, end) {
5353
+ return Array.prototype.slice.call(value, start, end);
3845
5354
  }
3846
5355
 
3847
5356
  // ../core/src/utils/array-equal.ts
5357
+ var MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH = 128;
3848
5358
  function arrayEqual(a, b, limit = 16) {
3849
- if (a !== b) {
3850
- return false;
5359
+ if (a === b) {
5360
+ return true;
3851
5361
  }
3852
5362
  const arrayA = a;
3853
5363
  const arrayB = b;
3854
- if (!isNumberArray(arrayA)) {
5364
+ if (!isNumberArray(arrayA) || !isNumberArray(arrayB)) {
3855
5365
  return false;
3856
5366
  }
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
- }
5367
+ if (arrayA.length !== arrayB.length) {
5368
+ return false;
5369
+ }
5370
+ const maxCompareLength = Math.min(limit, MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH);
5371
+ if (arrayA.length > maxCompareLength) {
5372
+ return false;
5373
+ }
5374
+ for (let i = 0; i < arrayA.length; ++i) {
5375
+ if (arrayB[i] !== arrayA[i]) {
5376
+ return false;
3862
5377
  }
3863
5378
  }
3864
5379
  return true;
@@ -3937,13 +5452,12 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3937
5452
  constructor(blocks) {
3938
5453
  for (const [bufferName, block] of Object.entries(blocks)) {
3939
5454
  const uniformBufferName = bufferName;
3940
- const uniformBufferLayout = new UniformBufferLayout(
3941
- block.uniformTypes ?? {},
3942
- block.uniformSizes ?? {}
3943
- );
5455
+ const uniformBufferLayout = new UniformBufferLayout(block.uniformTypes ?? {});
3944
5456
  this.uniformBufferLayouts.set(uniformBufferName, uniformBufferLayout);
3945
5457
  const uniformBlock = new UniformBlock({ name: bufferName });
3946
- uniformBlock.setUniforms(block.defaultUniforms || {});
5458
+ uniformBlock.setUniforms(
5459
+ uniformBufferLayout.getFlatUniformValues(block.defaultUniforms || {})
5460
+ );
3947
5461
  this.uniformBlocks.set(uniformBufferName, uniformBlock);
3948
5462
  }
3949
5463
  }
@@ -3959,7 +5473,12 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3959
5473
  */
3960
5474
  setUniforms(uniforms) {
3961
5475
  for (const [blockName, uniformValues] of Object.entries(uniforms)) {
3962
- this.uniformBlocks.get(blockName)?.setUniforms(uniformValues);
5476
+ const uniformBufferName = blockName;
5477
+ const uniformBufferLayout = this.uniformBufferLayouts.get(uniformBufferName);
5478
+ const flattenedUniforms = uniformBufferLayout?.getFlatUniformValues(
5479
+ uniformValues || {}
5480
+ );
5481
+ this.uniformBlocks.get(uniformBufferName)?.setUniforms(flattenedUniforms || {});
3963
5482
  }
3964
5483
  this.updateUniformBuffers();
3965
5484
  }
@@ -4035,7 +5554,7 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
4035
5554
  }
4036
5555
  };
4037
5556
 
4038
- // ../core/src/shadertypes/textures/texture-layout.ts
5557
+ // ../core/src/shadertypes/texture-types/texture-layout.ts
4039
5558
  function getTextureImageView(arrayBuffer2, memoryLayout, format, image = 0) {
4040
5559
  const formatInfo = textureFormatDecoder.getInfo(format);
4041
5560
  const bytesPerComponent = formatInfo.bytesPerPixel / formatInfo.components;
@@ -4073,7 +5592,7 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
4073
5592
  typedArray.set(subArray, offset);
4074
5593
  }
4075
5594
 
4076
- // ../core/src/shadertypes/textures/pixel-utils.ts
5595
+ // ../core/src/shadertypes/texture-types/pixel-utils.ts
4077
5596
  function readPixel(pixelData, x, y, bitsPerChannel) {
4078
5597
  if (x < 0 || x >= pixelData.width || y < 0 || y >= pixelData.height) {
4079
5598
  throw new Error("Coordinates out of bounds.");