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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/adapter/canvas-context.d.ts +6 -182
  2. package/dist/adapter/canvas-context.d.ts.map +1 -1
  3. package/dist/adapter/canvas-context.js +5 -481
  4. package/dist/adapter/canvas-context.js.map +1 -1
  5. package/dist/adapter/canvas-observer.d.ts +32 -0
  6. package/dist/adapter/canvas-observer.d.ts.map +1 -0
  7. package/dist/adapter/canvas-observer.js +90 -0
  8. package/dist/adapter/canvas-observer.js.map +1 -0
  9. package/dist/adapter/canvas-surface.d.ts +150 -0
  10. package/dist/adapter/canvas-surface.d.ts.map +1 -0
  11. package/dist/adapter/canvas-surface.js +392 -0
  12. package/dist/adapter/canvas-surface.js.map +1 -0
  13. package/dist/adapter/device.d.ts +64 -9
  14. package/dist/adapter/device.d.ts.map +1 -1
  15. package/dist/adapter/device.js +108 -4
  16. package/dist/adapter/device.js.map +1 -1
  17. package/dist/adapter/luma.js +1 -1
  18. package/dist/adapter/presentation-context.d.ts +11 -0
  19. package/dist/adapter/presentation-context.d.ts.map +1 -0
  20. package/dist/adapter/presentation-context.js +12 -0
  21. package/dist/adapter/presentation-context.js.map +1 -0
  22. package/dist/adapter/resources/buffer.d.ts +1 -1
  23. package/dist/adapter/resources/buffer.d.ts.map +1 -1
  24. package/dist/adapter/resources/buffer.js +14 -6
  25. package/dist/adapter/resources/buffer.js.map +1 -1
  26. package/dist/adapter/resources/command-encoder.d.ts +22 -1
  27. package/dist/adapter/resources/command-encoder.d.ts.map +1 -1
  28. package/dist/adapter/resources/command-encoder.js +65 -1
  29. package/dist/adapter/resources/command-encoder.js.map +1 -1
  30. package/dist/adapter/resources/fence.d.ts +1 -1
  31. package/dist/adapter/resources/fence.d.ts.map +1 -1
  32. package/dist/adapter/resources/fence.js +3 -1
  33. package/dist/adapter/resources/fence.js.map +1 -1
  34. package/dist/adapter/resources/query-set.d.ts +17 -1
  35. package/dist/adapter/resources/query-set.d.ts.map +1 -1
  36. package/dist/adapter/resources/query-set.js.map +1 -1
  37. package/dist/adapter/resources/render-pipeline.d.ts +19 -7
  38. package/dist/adapter/resources/render-pipeline.d.ts.map +1 -1
  39. package/dist/adapter/resources/render-pipeline.js +20 -2
  40. package/dist/adapter/resources/render-pipeline.js.map +1 -1
  41. package/dist/adapter/resources/resource.d.ts +8 -0
  42. package/dist/adapter/resources/resource.d.ts.map +1 -1
  43. package/dist/adapter/resources/resource.js +240 -14
  44. package/dist/adapter/resources/resource.js.map +1 -1
  45. package/dist/adapter/resources/shared-render-pipeline.d.ts +22 -0
  46. package/dist/adapter/resources/shared-render-pipeline.d.ts.map +1 -0
  47. package/dist/adapter/resources/shared-render-pipeline.js +25 -0
  48. package/dist/adapter/resources/shared-render-pipeline.js.map +1 -0
  49. package/dist/adapter/resources/texture.d.ts +78 -12
  50. package/dist/adapter/resources/texture.d.ts.map +1 -1
  51. package/dist/adapter/resources/texture.js +182 -30
  52. package/dist/adapter/resources/texture.js.map +1 -1
  53. package/dist/dist.dev.js +952 -193
  54. package/dist/dist.min.js +6 -6
  55. package/dist/index.cjs +918 -190
  56. package/dist/index.cjs.map +4 -4
  57. package/dist/index.d.ts +4 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +2 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/shadertypes/data-types/decode-shader-types.d.ts +2 -2
  62. package/dist/shadertypes/data-types/decode-shader-types.d.ts.map +1 -1
  63. package/dist/shadertypes/data-types/decode-shader-types.js +11 -2
  64. package/dist/shadertypes/data-types/decode-shader-types.js.map +1 -1
  65. package/dist/shadertypes/textures/texture-format-decoder.d.ts.map +1 -1
  66. package/dist/shadertypes/textures/texture-format-decoder.js +51 -6
  67. package/dist/shadertypes/textures/texture-format-decoder.js.map +1 -1
  68. package/dist/shadertypes/textures/texture-format-table.d.ts.map +1 -1
  69. package/dist/shadertypes/textures/texture-format-table.js +10 -9
  70. package/dist/shadertypes/textures/texture-format-table.js.map +1 -1
  71. package/dist/shadertypes/textures/texture-formats.d.ts +5 -2
  72. package/dist/shadertypes/textures/texture-formats.d.ts.map +1 -1
  73. package/dist/shadertypes/textures/texture-formats.js.map +1 -1
  74. package/dist/shadertypes/textures/texture-layout.d.ts +1 -1
  75. package/dist/utils/array-equal.d.ts +1 -1
  76. package/dist/utils/array-equal.d.ts.map +1 -1
  77. package/dist/utils/array-equal.js +15 -9
  78. package/dist/utils/array-equal.js.map +1 -1
  79. package/dist/utils/stats-manager.d.ts.map +1 -1
  80. package/dist/utils/stats-manager.js +61 -1
  81. package/dist/utils/stats-manager.js.map +1 -1
  82. package/package.json +3 -3
  83. package/src/adapter/canvas-context.ts +7 -623
  84. package/src/adapter/canvas-observer.ts +130 -0
  85. package/src/adapter/canvas-surface.ts +521 -0
  86. package/src/adapter/device.ts +174 -13
  87. package/src/adapter/presentation-context.ts +16 -0
  88. package/src/adapter/resources/buffer.ts +13 -5
  89. package/src/adapter/resources/command-encoder.ts +91 -2
  90. package/src/adapter/resources/fence.ts +3 -1
  91. package/src/adapter/resources/query-set.ts +17 -1
  92. package/src/adapter/resources/render-pipeline.ts +42 -13
  93. package/src/adapter/resources/resource.ts +284 -14
  94. package/src/adapter/resources/shared-render-pipeline.ts +40 -0
  95. package/src/adapter/resources/texture.ts +267 -38
  96. package/src/index.ts +7 -0
  97. package/src/shadertypes/data-types/decode-shader-types.ts +13 -4
  98. package/src/shadertypes/textures/texture-format-decoder.ts +71 -6
  99. package/src/shadertypes/textures/texture-format-table.ts +10 -9
  100. package/src/shadertypes/textures/texture-formats.ts +6 -1
  101. package/src/utils/array-equal.ts +21 -9
  102. package/src/utils/stats-manager.ts +76 -2
package/dist/dist.dev.js CHANGED
@@ -46,12 +46,14 @@ var __exports__ = (() => {
46
46
  Fence: () => Fence,
47
47
  Framebuffer: () => Framebuffer,
48
48
  PipelineLayout: () => PipelineLayout,
49
+ PresentationContext: () => PresentationContext,
49
50
  QuerySet: () => QuerySet,
50
51
  RenderPass: () => RenderPass,
51
52
  RenderPipeline: () => RenderPipeline,
52
53
  Resource: () => Resource,
53
54
  Sampler: () => Sampler,
54
55
  Shader: () => Shader,
56
+ SharedRenderPipeline: () => SharedRenderPipeline,
55
57
  Texture: () => Texture,
56
58
  TextureFormatDecoder: () => TextureFormatDecoder,
57
59
  TextureView: () => TextureView,
@@ -280,6 +282,24 @@ var __exports__ = (() => {
280
282
  };
281
283
 
282
284
  // src/utils/stats-manager.ts
285
+ var GPU_TIME_AND_MEMORY_STATS = "GPU Time and Memory";
286
+ var GPU_TIME_AND_MEMORY_STAT_ORDER = [
287
+ "Adapter",
288
+ "GPU",
289
+ "GPU Type",
290
+ "GPU Backend",
291
+ "Frame Rate",
292
+ "CPU Time",
293
+ "GPU Time",
294
+ "GPU Memory",
295
+ "Buffer Memory",
296
+ "Texture Memory",
297
+ "Referenced Buffer Memory",
298
+ "Referenced Texture Memory",
299
+ "Swap Chain Texture"
300
+ ];
301
+ var ORDERED_STATS_CACHE = /* @__PURE__ */ new WeakMap();
302
+ var ORDERED_STAT_NAME_SET_CACHE = /* @__PURE__ */ new WeakMap();
283
303
  var StatsManager = class {
284
304
  stats = /* @__PURE__ */ new Map();
285
305
  getStats(name2) {
@@ -289,10 +309,50 @@ var __exports__ = (() => {
289
309
  if (!this.stats.has(name2)) {
290
310
  this.stats.set(name2, new Stats({ id: name2 }));
291
311
  }
292
- return this.stats.get(name2);
312
+ const stats = this.stats.get(name2);
313
+ if (name2 === GPU_TIME_AND_MEMORY_STATS) {
314
+ initializeStats(stats, GPU_TIME_AND_MEMORY_STAT_ORDER);
315
+ }
316
+ return stats;
293
317
  }
294
318
  };
295
319
  var lumaStats = new StatsManager();
320
+ function initializeStats(stats, orderedStatNames) {
321
+ const statsMap = stats.stats;
322
+ let addedOrderedStat = false;
323
+ for (const statName of orderedStatNames) {
324
+ if (!statsMap[statName]) {
325
+ stats.get(statName);
326
+ addedOrderedStat = true;
327
+ }
328
+ }
329
+ const statCount = Object.keys(statsMap).length;
330
+ const cachedStats = ORDERED_STATS_CACHE.get(stats);
331
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
332
+ return;
333
+ }
334
+ const reorderedStats = {};
335
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE.get(orderedStatNames);
336
+ if (!orderedStatNamesSet) {
337
+ orderedStatNamesSet = new Set(orderedStatNames);
338
+ ORDERED_STAT_NAME_SET_CACHE.set(orderedStatNames, orderedStatNamesSet);
339
+ }
340
+ for (const statName of orderedStatNames) {
341
+ if (statsMap[statName]) {
342
+ reorderedStats[statName] = statsMap[statName];
343
+ }
344
+ }
345
+ for (const [statName, stat] of Object.entries(statsMap)) {
346
+ if (!orderedStatNamesSet.has(statName)) {
347
+ reorderedStats[statName] = stat;
348
+ }
349
+ }
350
+ for (const statName of Object.keys(statsMap)) {
351
+ delete statsMap[statName];
352
+ }
353
+ Object.assign(statsMap, reorderedStats);
354
+ ORDERED_STATS_CACHE.set(stats, { orderedStatNames, statCount });
355
+ }
296
356
 
297
357
  // ../../node_modules/@probe.gl/env/dist/lib/globals.js
298
358
  var window_ = globalThis;
@@ -827,6 +887,57 @@ var __exports__ = (() => {
827
887
  }
828
888
 
829
889
  // src/adapter/resources/resource.ts
890
+ var CPU_HOTSPOT_PROFILER_MODULE = "cpu-hotspot-profiler";
891
+ var RESOURCE_COUNTS_STATS = "GPU Resource Counts";
892
+ var LEGACY_RESOURCE_COUNTS_STATS = "Resource Counts";
893
+ var GPU_TIME_AND_MEMORY_STATS2 = "GPU Time and Memory";
894
+ var BASE_RESOURCE_COUNT_ORDER = [
895
+ "Resources",
896
+ "Buffers",
897
+ "Textures",
898
+ "Samplers",
899
+ "TextureViews",
900
+ "Framebuffers",
901
+ "QuerySets",
902
+ "Shaders",
903
+ "RenderPipelines",
904
+ "ComputePipelines",
905
+ "PipelineLayouts",
906
+ "VertexArrays",
907
+ "RenderPasss",
908
+ "ComputePasss",
909
+ "CommandEncoders",
910
+ "CommandBuffers"
911
+ ];
912
+ var WEBGL_RESOURCE_COUNT_ORDER = [
913
+ "Resources",
914
+ "Buffers",
915
+ "Textures",
916
+ "Samplers",
917
+ "TextureViews",
918
+ "Framebuffers",
919
+ "QuerySets",
920
+ "Shaders",
921
+ "RenderPipelines",
922
+ "SharedRenderPipelines",
923
+ "ComputePipelines",
924
+ "PipelineLayouts",
925
+ "VertexArrays",
926
+ "RenderPasss",
927
+ "ComputePasss",
928
+ "CommandEncoders",
929
+ "CommandBuffers"
930
+ ];
931
+ var BASE_RESOURCE_COUNT_STAT_ORDER = BASE_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
932
+ `${resourceType} Created`,
933
+ `${resourceType} Active`
934
+ ]);
935
+ var WEBGL_RESOURCE_COUNT_STAT_ORDER = WEBGL_RESOURCE_COUNT_ORDER.flatMap((resourceType) => [
936
+ `${resourceType} Created`,
937
+ `${resourceType} Active`
938
+ ]);
939
+ var ORDERED_STATS_CACHE2 = /* @__PURE__ */ new WeakMap();
940
+ var ORDERED_STAT_NAME_SET_CACHE2 = /* @__PURE__ */ new WeakMap();
830
941
  var Resource = class {
831
942
  toString() {
832
943
  return `${this[Symbol.toStringTag] || this.constructor.name}:"${this.id}"`;
@@ -843,6 +954,8 @@ var __exports__ = (() => {
843
954
  destroyed = false;
844
955
  /** For resources that allocate GPU memory */
845
956
  allocatedBytes = 0;
957
+ /** Stats bucket currently holding the tracked allocation */
958
+ allocatedBytesName = null;
846
959
  /** Attached resources will be destroyed when this resource is destroyed. Tracks auto-created "sub" resources. */
847
960
  _attachedResources = /* @__PURE__ */ new Set();
848
961
  /**
@@ -864,6 +977,9 @@ var __exports__ = (() => {
864
977
  * destroy can be called on any resource to release it before it is garbage collected.
865
978
  */
866
979
  destroy() {
980
+ if (this.destroyed) {
981
+ return;
982
+ }
867
983
  this.destroyResource();
868
984
  }
869
985
  /** @deprecated Use destroy() */
@@ -902,7 +1018,7 @@ var __exports__ = (() => {
902
1018
  }
903
1019
  /** Destroy all owned resources. Make sure the resources are no longer needed before calling. */
904
1020
  destroyAttachedResources() {
905
- for (const resource of Object.values(this._attachedResources)) {
1021
+ for (const resource of this._attachedResources) {
906
1022
  resource.destroy();
907
1023
  }
908
1024
  this._attachedResources = /* @__PURE__ */ new Set();
@@ -910,37 +1026,107 @@ var __exports__ = (() => {
910
1026
  // PROTECTED METHODS
911
1027
  /** Perform all destroy steps. Can be called by derived resources when overriding destroy() */
912
1028
  destroyResource() {
1029
+ if (this.destroyed) {
1030
+ return;
1031
+ }
913
1032
  this.destroyAttachedResources();
914
1033
  this.removeStats();
915
1034
  this.destroyed = true;
916
1035
  }
917
1036
  /** Called by .destroy() to track object destruction. Subclass must call if overriding destroy() */
918
1037
  removeStats() {
919
- const stats = this._device.statsManager.getStats("Resource Counts");
920
- const name2 = this[Symbol.toStringTag];
921
- stats.get(`${name2}s Active`).decrementCount();
1038
+ const profiler = getCpuHotspotProfiler(this._device);
1039
+ const startTime = profiler ? getTimestamp() : 0;
1040
+ const statsObjects = [
1041
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1042
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1043
+ ];
1044
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1045
+ for (const stats of statsObjects) {
1046
+ initializeStats2(stats, orderedStatNames);
1047
+ }
1048
+ const name2 = this.getStatsName();
1049
+ for (const stats of statsObjects) {
1050
+ stats.get("Resources Active").decrementCount();
1051
+ stats.get(`${name2}s Active`).decrementCount();
1052
+ }
1053
+ if (profiler) {
1054
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1055
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1056
+ }
922
1057
  }
923
1058
  /** Called by subclass to track memory allocations */
924
- trackAllocatedMemory(bytes, name2 = this[Symbol.toStringTag]) {
925
- const stats = this._device.statsManager.getStats("Resource Counts");
1059
+ trackAllocatedMemory(bytes, name2 = this.getStatsName()) {
1060
+ const profiler = getCpuHotspotProfiler(this._device);
1061
+ const startTime = profiler ? getTimestamp() : 0;
1062
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
1063
+ if (this.allocatedBytes > 0 && this.allocatedBytesName) {
1064
+ stats.get("GPU Memory").subtractCount(this.allocatedBytes);
1065
+ stats.get(`${this.allocatedBytesName} Memory`).subtractCount(this.allocatedBytes);
1066
+ }
926
1067
  stats.get("GPU Memory").addCount(bytes);
927
1068
  stats.get(`${name2} Memory`).addCount(bytes);
1069
+ if (profiler) {
1070
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1071
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1072
+ }
928
1073
  this.allocatedBytes = bytes;
1074
+ this.allocatedBytesName = name2;
1075
+ }
1076
+ /** Called by subclass to track handle-backed memory allocations separately from owned allocations */
1077
+ trackReferencedMemory(bytes, name2 = this.getStatsName()) {
1078
+ this.trackAllocatedMemory(bytes, `Referenced ${name2}`);
929
1079
  }
930
1080
  /** Called by subclass to track memory deallocations */
931
- trackDeallocatedMemory(name2 = this[Symbol.toStringTag]) {
932
- const stats = this._device.statsManager.getStats("Resource Counts");
1081
+ trackDeallocatedMemory(name2 = this.getStatsName()) {
1082
+ if (this.allocatedBytes === 0) {
1083
+ this.allocatedBytesName = null;
1084
+ return;
1085
+ }
1086
+ const profiler = getCpuHotspotProfiler(this._device);
1087
+ const startTime = profiler ? getTimestamp() : 0;
1088
+ const stats = this._device.statsManager.getStats(GPU_TIME_AND_MEMORY_STATS2);
933
1089
  stats.get("GPU Memory").subtractCount(this.allocatedBytes);
934
- stats.get(`${name2} Memory`).subtractCount(this.allocatedBytes);
1090
+ stats.get(`${this.allocatedBytesName || name2} Memory`).subtractCount(this.allocatedBytes);
1091
+ if (profiler) {
1092
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1093
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1094
+ }
935
1095
  this.allocatedBytes = 0;
1096
+ this.allocatedBytesName = null;
1097
+ }
1098
+ /** Called by subclass to deallocate handle-backed memory tracked via trackReferencedMemory() */
1099
+ trackDeallocatedReferencedMemory(name2 = this.getStatsName()) {
1100
+ this.trackDeallocatedMemory(`Referenced ${name2}`);
936
1101
  }
937
1102
  /** Called by resource constructor to track object creation */
938
1103
  addStats() {
939
- const stats = this._device.statsManager.getStats("Resource Counts");
940
- const name2 = this[Symbol.toStringTag];
941
- stats.get("Resources Created").incrementCount();
942
- stats.get(`${name2}s Created`).incrementCount();
943
- stats.get(`${name2}s Active`).incrementCount();
1104
+ const name2 = this.getStatsName();
1105
+ const profiler = getCpuHotspotProfiler(this._device);
1106
+ const startTime = profiler ? getTimestamp() : 0;
1107
+ const statsObjects = [
1108
+ this._device.statsManager.getStats(RESOURCE_COUNTS_STATS),
1109
+ this._device.statsManager.getStats(LEGACY_RESOURCE_COUNTS_STATS)
1110
+ ];
1111
+ const orderedStatNames = getResourceCountStatOrder(this._device);
1112
+ for (const stats of statsObjects) {
1113
+ initializeStats2(stats, orderedStatNames);
1114
+ }
1115
+ for (const stats of statsObjects) {
1116
+ stats.get("Resources Created").incrementCount();
1117
+ stats.get("Resources Active").incrementCount();
1118
+ stats.get(`${name2}s Created`).incrementCount();
1119
+ stats.get(`${name2}s Active`).incrementCount();
1120
+ }
1121
+ if (profiler) {
1122
+ profiler.statsBookkeepingCalls = (profiler.statsBookkeepingCalls || 0) + 1;
1123
+ profiler.statsBookkeepingTimeMs = (profiler.statsBookkeepingTimeMs || 0) + (getTimestamp() - startTime);
1124
+ }
1125
+ recordTransientCanvasResourceCreate(this._device, name2);
1126
+ }
1127
+ /** Canonical resource name used for stats buckets. */
1128
+ getStatsName() {
1129
+ return getCanonicalResourceName(this);
944
1130
  }
945
1131
  };
946
1132
  /** Default properties for resource */
@@ -958,6 +1144,96 @@ var __exports__ = (() => {
958
1144
  }
959
1145
  return mergedProps;
960
1146
  }
1147
+ function initializeStats2(stats, orderedStatNames) {
1148
+ const statsMap = stats.stats;
1149
+ let addedOrderedStat = false;
1150
+ for (const statName of orderedStatNames) {
1151
+ if (!statsMap[statName]) {
1152
+ stats.get(statName);
1153
+ addedOrderedStat = true;
1154
+ }
1155
+ }
1156
+ const statCount = Object.keys(statsMap).length;
1157
+ const cachedStats = ORDERED_STATS_CACHE2.get(stats);
1158
+ if (!addedOrderedStat && cachedStats?.orderedStatNames === orderedStatNames && cachedStats.statCount === statCount) {
1159
+ return;
1160
+ }
1161
+ const reorderedStats = {};
1162
+ let orderedStatNamesSet = ORDERED_STAT_NAME_SET_CACHE2.get(orderedStatNames);
1163
+ if (!orderedStatNamesSet) {
1164
+ orderedStatNamesSet = new Set(orderedStatNames);
1165
+ ORDERED_STAT_NAME_SET_CACHE2.set(orderedStatNames, orderedStatNamesSet);
1166
+ }
1167
+ for (const statName of orderedStatNames) {
1168
+ if (statsMap[statName]) {
1169
+ reorderedStats[statName] = statsMap[statName];
1170
+ }
1171
+ }
1172
+ for (const [statName, stat] of Object.entries(statsMap)) {
1173
+ if (!orderedStatNamesSet.has(statName)) {
1174
+ reorderedStats[statName] = stat;
1175
+ }
1176
+ }
1177
+ for (const statName of Object.keys(statsMap)) {
1178
+ delete statsMap[statName];
1179
+ }
1180
+ Object.assign(statsMap, reorderedStats);
1181
+ ORDERED_STATS_CACHE2.set(stats, { orderedStatNames, statCount });
1182
+ }
1183
+ function getResourceCountStatOrder(device) {
1184
+ return device.type === "webgl" ? WEBGL_RESOURCE_COUNT_STAT_ORDER : BASE_RESOURCE_COUNT_STAT_ORDER;
1185
+ }
1186
+ function getCpuHotspotProfiler(device) {
1187
+ const profiler = device.userData[CPU_HOTSPOT_PROFILER_MODULE];
1188
+ return profiler?.enabled ? profiler : null;
1189
+ }
1190
+ function getTimestamp() {
1191
+ return globalThis.performance?.now?.() ?? Date.now();
1192
+ }
1193
+ function recordTransientCanvasResourceCreate(device, name2) {
1194
+ const profiler = getCpuHotspotProfiler(device);
1195
+ if (!profiler || !profiler.activeDefaultFramebufferAcquireDepth) {
1196
+ return;
1197
+ }
1198
+ profiler.transientCanvasResourceCreates = (profiler.transientCanvasResourceCreates || 0) + 1;
1199
+ switch (name2) {
1200
+ case "Texture":
1201
+ profiler.transientCanvasTextureCreates = (profiler.transientCanvasTextureCreates || 0) + 1;
1202
+ break;
1203
+ case "TextureView":
1204
+ profiler.transientCanvasTextureViewCreates = (profiler.transientCanvasTextureViewCreates || 0) + 1;
1205
+ break;
1206
+ case "Sampler":
1207
+ profiler.transientCanvasSamplerCreates = (profiler.transientCanvasSamplerCreates || 0) + 1;
1208
+ break;
1209
+ case "Framebuffer":
1210
+ profiler.transientCanvasFramebufferCreates = (profiler.transientCanvasFramebufferCreates || 0) + 1;
1211
+ break;
1212
+ default:
1213
+ break;
1214
+ }
1215
+ }
1216
+ function getCanonicalResourceName(resource) {
1217
+ let prototype = Object.getPrototypeOf(resource);
1218
+ while (prototype) {
1219
+ const parentPrototype = Object.getPrototypeOf(prototype);
1220
+ if (!parentPrototype || parentPrototype === Resource.prototype) {
1221
+ return getPrototypeToStringTag(prototype) || resource[Symbol.toStringTag] || resource.constructor.name;
1222
+ }
1223
+ prototype = parentPrototype;
1224
+ }
1225
+ return resource[Symbol.toStringTag] || resource.constructor.name;
1226
+ }
1227
+ function getPrototypeToStringTag(prototype) {
1228
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, Symbol.toStringTag);
1229
+ if (typeof descriptor?.get === "function") {
1230
+ return descriptor.get.call(prototype);
1231
+ }
1232
+ if (typeof descriptor?.value === "string") {
1233
+ return descriptor.value;
1234
+ }
1235
+ return null;
1236
+ }
961
1237
 
962
1238
  // src/adapter/resources/buffer.ts
963
1239
  var _Buffer = class extends Resource {
@@ -997,18 +1273,26 @@ var __exports__ = (() => {
997
1273
  /** A partial CPU-side copy of the data in this buffer, for debugging purposes */
998
1274
  debugData = new ArrayBuffer(0);
999
1275
  /** This doesn't handle partial non-zero offset updates correctly */
1000
- _setDebugData(data, byteOffset, byteLength) {
1001
- const arrayBuffer2 = ArrayBuffer.isView(data) ? data.buffer : data;
1276
+ _setDebugData(data, _byteOffset, byteLength) {
1277
+ let arrayBufferView = null;
1278
+ let arrayBuffer2;
1279
+ if (ArrayBuffer.isView(data)) {
1280
+ arrayBufferView = data;
1281
+ arrayBuffer2 = data.buffer;
1282
+ } else {
1283
+ arrayBuffer2 = data;
1284
+ }
1002
1285
  const debugDataLength = Math.min(
1003
1286
  data ? data.byteLength : byteLength,
1004
1287
  _Buffer.DEBUG_DATA_MAX_LENGTH
1005
1288
  );
1006
1289
  if (arrayBuffer2 === null) {
1007
1290
  this.debugData = new ArrayBuffer(debugDataLength);
1008
- } else if (byteOffset === 0 && byteLength === arrayBuffer2.byteLength) {
1009
- this.debugData = arrayBuffer2.slice(0, debugDataLength);
1010
1291
  } else {
1011
- this.debugData = arrayBuffer2.slice(byteOffset, byteOffset + debugDataLength);
1292
+ const sourceByteOffset = Math.min(arrayBufferView?.byteOffset || 0, arrayBuffer2.byteLength);
1293
+ const availableByteLength = Math.max(0, arrayBuffer2.byteLength - sourceByteOffset);
1294
+ const copyByteLength = Math.min(debugDataLength, availableByteLength);
1295
+ this.debugData = new Uint8Array(arrayBuffer2, sourceByteOffset, copyByteLength).slice().buffer;
1012
1296
  }
1013
1297
  }
1014
1298
  };
@@ -1204,6 +1488,7 @@ var __exports__ = (() => {
1204
1488
  var float16_renderable = "float16-renderable-webgl";
1205
1489
  var rgb9e5ufloat_renderable = "rgb9e5ufloat-renderable-webgl";
1206
1490
  var snorm8_renderable = "snorm8-renderable-webgl";
1491
+ var norm16_webgl = "norm16-webgl";
1207
1492
  var norm16_renderable = "norm16-renderable-webgl";
1208
1493
  var snorm16_renderable = "snorm16-renderable-webgl";
1209
1494
  var float32_filterable = "float32-filterable";
@@ -1237,16 +1522,16 @@ var __exports__ = (() => {
1237
1522
  "rgba8sint": {},
1238
1523
  "bgra8unorm": {},
1239
1524
  "bgra8unorm-srgb": {},
1240
- "r16unorm": { f: norm16_renderable },
1241
- "rg16unorm": { render: norm16_renderable },
1242
- "rgb16unorm-webgl": { f: norm16_renderable },
1525
+ "r16unorm": { f: norm16_webgl, render: norm16_renderable },
1526
+ "rg16unorm": { f: norm16_webgl, render: norm16_renderable },
1527
+ "rgb16unorm-webgl": { f: norm16_webgl, render: false },
1243
1528
  // rgb not renderable
1244
- "rgba16unorm": { render: norm16_renderable },
1245
- "r16snorm": { f: snorm16_renderable },
1246
- "rg16snorm": { render: snorm16_renderable },
1247
- "rgb16snorm-webgl": { f: norm16_renderable },
1529
+ "rgba16unorm": { f: norm16_webgl, render: norm16_renderable },
1530
+ "r16snorm": { f: norm16_webgl, render: snorm16_renderable },
1531
+ "rg16snorm": { f: norm16_webgl, render: snorm16_renderable },
1532
+ "rgb16snorm-webgl": { f: norm16_webgl, render: false },
1248
1533
  // rgb not renderable
1249
- "rgba16snorm": { render: snorm16_renderable },
1534
+ "rgba16snorm": { f: norm16_webgl, render: snorm16_renderable },
1250
1535
  "r16uint": {},
1251
1536
  "rg16uint": {},
1252
1537
  "rgba16uint": {},
@@ -1349,7 +1634,7 @@ var __exports__ = (() => {
1349
1634
  // WEBGL_compressed_texture_pvrtc
1350
1635
  "pvrtc-rgb4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1351
1636
  "pvrtc-rgba4unorm-webgl": { f: texture_compression_pvrtc_webgl },
1352
- "pvrtc-rbg2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1637
+ "pvrtc-rgb2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1353
1638
  "pvrtc-rgba2unorm-webgl": { f: texture_compression_pvrtc_webgl },
1354
1639
  // WEBGL_compressed_texture_etc1
1355
1640
  "etc1-rbg-unorm-webgl": { f: texture_compression_etc1_webgl },
@@ -1416,10 +1701,19 @@ var __exports__ = (() => {
1416
1701
  depth,
1417
1702
  byteAlignment
1418
1703
  }) {
1419
- const { bytesPerPixel } = textureFormatDecoder.getInfo(format);
1420
- const unpaddedBytesPerRow = width * bytesPerPixel;
1704
+ const formatInfo = textureFormatDecoder.getInfo(format);
1705
+ const {
1706
+ bytesPerPixel,
1707
+ bytesPerBlock = bytesPerPixel,
1708
+ blockWidth = 1,
1709
+ blockHeight = 1,
1710
+ compressed = false
1711
+ } = formatInfo;
1712
+ const blockColumns = compressed ? Math.ceil(width / blockWidth) : width;
1713
+ const blockRows = compressed ? Math.ceil(height / blockHeight) : height;
1714
+ const unpaddedBytesPerRow = blockColumns * bytesPerBlock;
1421
1715
  const bytesPerRow = Math.ceil(unpaddedBytesPerRow / byteAlignment) * byteAlignment;
1422
- const rowsPerImage = height;
1716
+ const rowsPerImage = blockRows;
1423
1717
  const byteLength = bytesPerRow * rowsPerImage * depth;
1424
1718
  return {
1425
1719
  bytesPerPixel,
@@ -1445,7 +1739,8 @@ var __exports__ = (() => {
1445
1739
  const isSigned = formatInfo?.signed;
1446
1740
  const isInteger = formatInfo?.integer;
1447
1741
  const isWebGLSpecific = formatInfo?.webgl;
1448
- formatCapabilities.render &&= !isSigned;
1742
+ const isCompressed = Boolean(formatInfo?.compressed);
1743
+ formatCapabilities.render &&= !isDepthStencil && !isCompressed;
1449
1744
  formatCapabilities.filter &&= !isDepthStencil && !isSigned && !isInteger && !isWebGLSpecific;
1450
1745
  return formatCapabilities;
1451
1746
  }
@@ -1457,6 +1752,7 @@ var __exports__ = (() => {
1457
1752
  formatInfo.bytesPerPixel = 1;
1458
1753
  formatInfo.srgb = false;
1459
1754
  formatInfo.compressed = true;
1755
+ formatInfo.bytesPerBlock = getCompressedTextureBlockByteLength(format);
1460
1756
  const blockSize = getCompressedTextureBlockSize(format);
1461
1757
  if (blockSize) {
1462
1758
  formatInfo.blockWidth = blockSize.blockWidth;
@@ -1542,8 +1838,29 @@ var __exports__ = (() => {
1542
1838
  const [, blockWidth, blockHeight] = matches;
1543
1839
  return { blockWidth: Number(blockWidth), blockHeight: Number(blockHeight) };
1544
1840
  }
1841
+ if (format.startsWith("bc") || format.startsWith("etc1") || format.startsWith("etc2") || format.startsWith("eac") || format.startsWith("atc")) {
1842
+ return { blockWidth: 4, blockHeight: 4 };
1843
+ }
1844
+ if (format.startsWith("pvrtc-rgb4") || format.startsWith("pvrtc-rgba4")) {
1845
+ return { blockWidth: 4, blockHeight: 4 };
1846
+ }
1847
+ if (format.startsWith("pvrtc-rgb2") || format.startsWith("pvrtc-rgba2")) {
1848
+ return { blockWidth: 8, blockHeight: 4 };
1849
+ }
1545
1850
  return null;
1546
1851
  }
1852
+ function getCompressedTextureBlockByteLength(format) {
1853
+ 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") {
1854
+ return 8;
1855
+ }
1856
+ 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") {
1857
+ return 16;
1858
+ }
1859
+ if (format.startsWith("pvrtc")) {
1860
+ return 8;
1861
+ }
1862
+ return 16;
1863
+ }
1547
1864
 
1548
1865
  // src/image-utils/image-types.ts
1549
1866
  function isExternalImage(data) {
@@ -1604,6 +1921,8 @@ var __exports__ = (() => {
1604
1921
  /** Used by other luma.gl modules to store data on the device */
1605
1922
  _moduleData = {};
1606
1923
  _textureCaps = {};
1924
+ /** Internal timestamp query set used when GPU timing collection is enabled for this device. */
1925
+ _debugGPUTimeQuery = null;
1607
1926
  constructor(props) {
1608
1927
  this.props = { ..._Device.defaultProps, ...props };
1609
1928
  this.id = this.props.id || uid(this[Symbol.toStringTag].toLowerCase());
@@ -1657,6 +1976,16 @@ var __exports__ = (() => {
1657
1976
  isTextureFormatCompressed(format) {
1658
1977
  return textureFormatDecoder.isCompressed(format);
1659
1978
  }
1979
+ /** Returns the compressed texture formats that can be created and sampled on this device */
1980
+ getSupportedCompressedTextureFormats() {
1981
+ const supportedFormats = [];
1982
+ for (const format of Object.keys(getTextureFormatTable())) {
1983
+ if (this.isTextureFormatCompressed(format) && this.isTextureFormatSupported(format)) {
1984
+ supportedFormats.push(format);
1985
+ }
1986
+ }
1987
+ return supportedFormats;
1988
+ }
1660
1989
  // DEBUG METHODS
1661
1990
  pushDebugGroup(groupLabel) {
1662
1991
  this.commandEncoder.pushDebugGroup(groupLabel);
@@ -1739,6 +2068,70 @@ or create a device with the 'debug: true' prop.`;
1739
2068
  beginComputePass(props) {
1740
2069
  return this.commandEncoder.beginComputePass(props);
1741
2070
  }
2071
+ /**
2072
+ * Generate mipmaps for a WebGPU texture.
2073
+ * WebGPU textures must be created up front with the required mip count, usage flags, and a format that supports the chosen generation path.
2074
+ * WebGL uses `Texture.generateMipmapsWebGL()` directly because the backend manages mip generation on the texture object itself.
2075
+ */
2076
+ generateMipmapsWebGPU(_texture) {
2077
+ throw new Error("not implemented");
2078
+ }
2079
+ /** Internal helper for creating a shareable WebGL render-pipeline implementation. */
2080
+ _createSharedRenderPipelineWebGL(_props) {
2081
+ throw new Error("_createSharedRenderPipelineWebGL() not implemented");
2082
+ }
2083
+ /**
2084
+ * Internal helper that returns `true` when timestamp-query GPU timing should be
2085
+ * collected for this device.
2086
+ */
2087
+ _supportsDebugGPUTime() {
2088
+ return this.features.has("timestamp-query") && Boolean(this.props.debug || this.props.debugGPUTime);
2089
+ }
2090
+ /**
2091
+ * Internal helper that enables device-managed GPU timing collection on the
2092
+ * default command encoder. Reuses the existing query set if timing is already enabled.
2093
+ *
2094
+ * @param queryCount - Number of timestamp slots reserved for profiled passes.
2095
+ * @returns The device-managed timestamp QuerySet, or `null` when timing is not supported or could not be enabled.
2096
+ */
2097
+ _enableDebugGPUTime(queryCount = 256) {
2098
+ if (!this._supportsDebugGPUTime()) {
2099
+ return null;
2100
+ }
2101
+ if (this._debugGPUTimeQuery) {
2102
+ return this._debugGPUTimeQuery;
2103
+ }
2104
+ try {
2105
+ this._debugGPUTimeQuery = this.createQuerySet({ type: "timestamp", count: queryCount });
2106
+ this.commandEncoder = this.createCommandEncoder({
2107
+ id: this.commandEncoder.props.id,
2108
+ timeProfilingQuerySet: this._debugGPUTimeQuery
2109
+ });
2110
+ } catch {
2111
+ this._debugGPUTimeQuery = null;
2112
+ }
2113
+ return this._debugGPUTimeQuery;
2114
+ }
2115
+ /**
2116
+ * Internal helper that disables device-managed GPU timing collection and restores
2117
+ * the default command encoder to an unprofiled state.
2118
+ */
2119
+ _disableDebugGPUTime() {
2120
+ if (!this._debugGPUTimeQuery) {
2121
+ return;
2122
+ }
2123
+ if (this.commandEncoder.getTimeProfilingQuerySet() === this._debugGPUTimeQuery) {
2124
+ this.commandEncoder = this.createCommandEncoder({
2125
+ id: this.commandEncoder.props.id
2126
+ });
2127
+ }
2128
+ this._debugGPUTimeQuery.destroy();
2129
+ this._debugGPUTimeQuery = null;
2130
+ }
2131
+ /** Internal helper that returns `true` when device-managed GPU timing is currently active. */
2132
+ _isDebugGPUTimeEnabled() {
2133
+ return this._debugGPUTimeQuery !== null;
2134
+ }
1742
2135
  // DEPRECATED METHODS
1743
2136
  /** @deprecated Use getDefaultCanvasContext() */
1744
2137
  getCanvasContext() {
@@ -1846,7 +2239,8 @@ or create a device with the 'debug: true' prop.`;
1846
2239
  onVisibilityChange: (context) => log.log(1, `${context} Visibility changed ${context.isVisible}`)(),
1847
2240
  onDevicePixelRatioChange: (context, info) => log.log(1, `${context} DPR changed ${info.oldRatio} => ${context.devicePixelRatio}`)(),
1848
2241
  // Debug flags
1849
- debug: log.get("debug") || void 0,
2242
+ debug: getDefaultDebugValue(),
2243
+ debugGPUTime: false,
1850
2244
  debugShaders: log.get("debug-shaders") || void 0,
1851
2245
  debugFramebuffers: Boolean(log.get("debug-framebuffers")),
1852
2246
  debugFactories: Boolean(log.get("debug-factories")),
@@ -1857,9 +2251,11 @@ or create a device with the 'debug: true' prop.`;
1857
2251
  // Experimental
1858
2252
  _reuseDevices: false,
1859
2253
  _requestMaxLimits: true,
1860
- _cacheShaders: false,
1861
- _cachePipelines: false,
1862
- _cacheDestroyPolicy: "unused",
2254
+ _cacheShaders: true,
2255
+ _destroyShaders: false,
2256
+ _cachePipelines: true,
2257
+ _sharePipelines: true,
2258
+ _destroyPipelines: false,
1863
2259
  // TODO - Change these after confirming things work as expected
1864
2260
  _initializeFeatures: true,
1865
2261
  _disabledFeatures: {
@@ -1868,6 +2264,25 @@ or create a device with the 'debug: true' prop.`;
1868
2264
  // INTERNAL
1869
2265
  _handle: void 0
1870
2266
  });
2267
+ function _getDefaultDebugValue(logDebugValue, nodeEnv) {
2268
+ if (logDebugValue !== void 0 && logDebugValue !== null) {
2269
+ return Boolean(logDebugValue);
2270
+ }
2271
+ if (nodeEnv !== void 0) {
2272
+ return nodeEnv !== "production";
2273
+ }
2274
+ return false;
2275
+ }
2276
+ function getDefaultDebugValue() {
2277
+ return _getDefaultDebugValue(log.get("debug"), getNodeEnv());
2278
+ }
2279
+ function getNodeEnv() {
2280
+ const processObject = globalThis.process;
2281
+ if (!processObject?.env) {
2282
+ return void 0;
2283
+ }
2284
+ return processObject.env["NODE_ENV"];
2285
+ }
1871
2286
 
1872
2287
  // src/adapter/luma.ts
1873
2288
  var STARTUP_MESSAGE = "set luma.log.level=1 (or higher) to trace rendering";
@@ -2045,6 +2460,100 @@ or create a device with the 'debug: true' prop.`;
2045
2460
  return pageLoadPromise;
2046
2461
  }
2047
2462
 
2463
+ // src/adapter/canvas-observer.ts
2464
+ var CanvasObserver = class {
2465
+ props;
2466
+ _resizeObserver;
2467
+ _intersectionObserver;
2468
+ _observeDevicePixelRatioTimeout = null;
2469
+ _observeDevicePixelRatioMediaQuery = null;
2470
+ _handleDevicePixelRatioChange = () => this._refreshDevicePixelRatio();
2471
+ _trackPositionInterval = null;
2472
+ _started = false;
2473
+ get started() {
2474
+ return this._started;
2475
+ }
2476
+ constructor(props) {
2477
+ this.props = props;
2478
+ }
2479
+ start() {
2480
+ if (this._started || !this.props.canvas) {
2481
+ return;
2482
+ }
2483
+ this._started = true;
2484
+ this._intersectionObserver ||= new IntersectionObserver(
2485
+ (entries) => this.props.onIntersection(entries)
2486
+ );
2487
+ this._resizeObserver ||= new ResizeObserver((entries) => this.props.onResize(entries));
2488
+ this._intersectionObserver.observe(this.props.canvas);
2489
+ try {
2490
+ this._resizeObserver.observe(this.props.canvas, { box: "device-pixel-content-box" });
2491
+ } catch {
2492
+ this._resizeObserver.observe(this.props.canvas, { box: "content-box" });
2493
+ }
2494
+ this._observeDevicePixelRatioTimeout = setTimeout(() => this._refreshDevicePixelRatio(), 0);
2495
+ if (this.props.trackPosition) {
2496
+ this._trackPosition();
2497
+ }
2498
+ }
2499
+ stop() {
2500
+ if (!this._started) {
2501
+ return;
2502
+ }
2503
+ this._started = false;
2504
+ if (this._observeDevicePixelRatioTimeout) {
2505
+ clearTimeout(this._observeDevicePixelRatioTimeout);
2506
+ this._observeDevicePixelRatioTimeout = null;
2507
+ }
2508
+ if (this._observeDevicePixelRatioMediaQuery) {
2509
+ this._observeDevicePixelRatioMediaQuery.removeEventListener(
2510
+ "change",
2511
+ this._handleDevicePixelRatioChange
2512
+ );
2513
+ this._observeDevicePixelRatioMediaQuery = null;
2514
+ }
2515
+ if (this._trackPositionInterval) {
2516
+ clearInterval(this._trackPositionInterval);
2517
+ this._trackPositionInterval = null;
2518
+ }
2519
+ this._resizeObserver?.disconnect();
2520
+ this._intersectionObserver?.disconnect();
2521
+ }
2522
+ _refreshDevicePixelRatio() {
2523
+ if (!this._started) {
2524
+ return;
2525
+ }
2526
+ this.props.onDevicePixelRatioChange();
2527
+ this._observeDevicePixelRatioMediaQuery?.removeEventListener(
2528
+ "change",
2529
+ this._handleDevicePixelRatioChange
2530
+ );
2531
+ this._observeDevicePixelRatioMediaQuery = matchMedia(
2532
+ `(resolution: ${window.devicePixelRatio}dppx)`
2533
+ );
2534
+ this._observeDevicePixelRatioMediaQuery.addEventListener(
2535
+ "change",
2536
+ this._handleDevicePixelRatioChange,
2537
+ { once: true }
2538
+ );
2539
+ }
2540
+ _trackPosition(intervalMs = 100) {
2541
+ if (this._trackPositionInterval) {
2542
+ return;
2543
+ }
2544
+ this._trackPositionInterval = setInterval(() => {
2545
+ if (!this._started) {
2546
+ if (this._trackPositionInterval) {
2547
+ clearInterval(this._trackPositionInterval);
2548
+ this._trackPositionInterval = null;
2549
+ }
2550
+ } else {
2551
+ this.props.onPositionChange();
2552
+ }
2553
+ }, intervalMs);
2554
+ }
2555
+ };
2556
+
2048
2557
  // src/utils/promise-utils.ts
2049
2558
  function withResolvers() {
2050
2559
  let resolve;
@@ -2069,8 +2578,8 @@ or create a device with the 'debug: true' prop.`;
2069
2578
  return value;
2070
2579
  }
2071
2580
 
2072
- // src/adapter/canvas-context.ts
2073
- var _CanvasContext = class {
2581
+ // src/adapter/canvas-surface.ts
2582
+ var _CanvasSurface = class {
2074
2583
  static isHTMLCanvas(canvas) {
2075
2584
  return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
2076
2585
  }
@@ -2106,11 +2615,7 @@ or create a device with the 'debug: true' prop.`;
2106
2615
  drawingBufferHeight;
2107
2616
  /** Resolves when the canvas is initialized, i.e. when the ResizeObserver has updated the pixel size */
2108
2617
  _initializedResolvers = withResolvers();
2109
- /** ResizeObserver to track canvas size changes */
2110
- _resizeObserver;
2111
- /** IntersectionObserver to track canvas visibility changes */
2112
- _intersectionObserver;
2113
- _observeDevicePixelRatioTimeout = null;
2618
+ _canvasObserver;
2114
2619
  /** Position of the canvas in the document, updated by a timer */
2115
2620
  _position = [0, 0];
2116
2621
  /** Whether this canvas context has been destroyed */
@@ -2121,7 +2626,7 @@ or create a device with the 'debug: true' prop.`;
2121
2626
  return `${this[Symbol.toStringTag]}(${this.id})`;
2122
2627
  }
2123
2628
  constructor(props) {
2124
- this.props = { ..._CanvasContext.defaultProps, ...props };
2629
+ this.props = { ..._CanvasSurface.defaultProps, ...props };
2125
2630
  props = this.props;
2126
2631
  this.initialized = this._initializedResolvers.promise;
2127
2632
  if (!isBrowser()) {
@@ -2133,11 +2638,11 @@ or create a device with the 'debug: true' prop.`;
2133
2638
  } else {
2134
2639
  this.canvas = props.canvas;
2135
2640
  }
2136
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2641
+ if (_CanvasSurface.isHTMLCanvas(this.canvas)) {
2137
2642
  this.id = props.id || this.canvas.id;
2138
2643
  this.type = "html-canvas";
2139
2644
  this.htmlCanvas = this.canvas;
2140
- } else if (_CanvasContext.isOffscreenCanvas(this.canvas)) {
2645
+ } else if (_CanvasSurface.isOffscreenCanvas(this.canvas)) {
2141
2646
  this.id = props.id || "offscreen-canvas";
2142
2647
  this.type = "offscreen-canvas";
2143
2648
  this.offscreenCanvas = this.canvas;
@@ -2153,33 +2658,20 @@ or create a device with the 'debug: true' prop.`;
2153
2658
  this.drawingBufferHeight = this.canvas.height;
2154
2659
  this.devicePixelRatio = globalThis.devicePixelRatio || 1;
2155
2660
  this._position = [0, 0];
2156
- if (_CanvasContext.isHTMLCanvas(this.canvas)) {
2157
- this._intersectionObserver = new IntersectionObserver(
2158
- (entries) => this._handleIntersection(entries)
2159
- );
2160
- this._intersectionObserver.observe(this.canvas);
2161
- this._resizeObserver = new ResizeObserver((entries) => this._handleResize(entries));
2162
- try {
2163
- this._resizeObserver.observe(this.canvas, { box: "device-pixel-content-box" });
2164
- } catch {
2165
- this._resizeObserver.observe(this.canvas, { box: "content-box" });
2166
- }
2167
- this._observeDevicePixelRatioTimeout = setTimeout(() => this._observeDevicePixelRatio(), 0);
2168
- if (this.props.trackPosition) {
2169
- this._trackPosition();
2170
- }
2171
- }
2661
+ this._canvasObserver = new CanvasObserver({
2662
+ canvas: this.htmlCanvas,
2663
+ trackPosition: this.props.trackPosition,
2664
+ onResize: (entries) => this._handleResize(entries),
2665
+ onIntersection: (entries) => this._handleIntersection(entries),
2666
+ onDevicePixelRatioChange: () => this._observeDevicePixelRatio(),
2667
+ onPositionChange: () => this.updatePosition()
2668
+ });
2172
2669
  }
2173
2670
  destroy() {
2174
2671
  if (!this.destroyed) {
2175
2672
  this.destroyed = true;
2176
- if (this._observeDevicePixelRatioTimeout) {
2177
- clearTimeout(this._observeDevicePixelRatioTimeout);
2178
- this._observeDevicePixelRatioTimeout = null;
2179
- }
2673
+ this._stopObservers();
2180
2674
  this.device = null;
2181
- this._resizeObserver?.disconnect();
2182
- this._intersectionObserver?.disconnect();
2183
2675
  }
2184
2676
  }
2185
2677
  setProps(props) {
@@ -2194,41 +2686,22 @@ or create a device with the 'debug: true' prop.`;
2194
2686
  this._resizeDrawingBufferIfNeeded();
2195
2687
  return this._getCurrentFramebuffer(options);
2196
2688
  }
2197
- // SIZE METHODS
2198
- /**
2199
- * Returns the size covered by the canvas in CSS pixels
2200
- * @note This can be different from the actual device pixel size of a canvas due to DPR scaling, and rounding to integer pixels
2201
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2202
- */
2203
2689
  getCSSSize() {
2204
2690
  return [this.cssWidth, this.cssHeight];
2205
2691
  }
2206
2692
  getPosition() {
2207
2693
  return this._position;
2208
2694
  }
2209
- /**
2210
- * Returns the size covered by the canvas in actual device pixels.
2211
- * @note This can be different from the 'CSS' size of a canvas due to DPR scaling, and rounding to integer pixels
2212
- * @note This is independent of the canvas' internal drawing buffer size (.width, .height).
2213
- */
2214
2695
  getDevicePixelSize() {
2215
2696
  return [this.devicePixelWidth, this.devicePixelHeight];
2216
2697
  }
2217
- /** Get the drawing buffer size (number of pixels GPU is rendering into, can be different from CSS size) */
2218
2698
  getDrawingBufferSize() {
2219
2699
  return [this.drawingBufferWidth, this.drawingBufferHeight];
2220
2700
  }
2221
- /** Returns the biggest allowed framebuffer size. @todo Allow the application to limit this? */
2222
2701
  getMaxDrawingBufferSize() {
2223
2702
  const maxTextureDimension = this.device.limits.maxTextureDimension2D;
2224
2703
  return [maxTextureDimension, maxTextureDimension];
2225
2704
  }
2226
- /**
2227
- * Update the canvas drawing buffer size.
2228
- * @note - Called automatically if props.autoResize is true.
2229
- * @note - Defers update of drawing buffer size until framebuffer is requested to avoid flicker
2230
- * (resizing clears the drawing buffer)!
2231
- */
2232
2705
  setDrawingBufferSize(width, height) {
2233
2706
  width = Math.floor(width);
2234
2707
  height = Math.floor(height);
@@ -2239,19 +2712,10 @@ or create a device with the 'debug: true' prop.`;
2239
2712
  this.drawingBufferHeight = height;
2240
2713
  this._needsDrawingBufferResize = true;
2241
2714
  }
2242
- /**
2243
- * Returns the current DPR (number of physical pixels per CSS pixel), if props.useDevicePixels is true
2244
- * @note This can be a fractional (non-integer) number, e.g. when the user zooms in the browser.
2245
- * @note This function handles the non-HTML canvas cases
2246
- */
2247
2715
  getDevicePixelRatio() {
2248
- const dpr = typeof window !== "undefined" && window.devicePixelRatio;
2249
- return dpr || 1;
2716
+ const devicePixelRatio2 = typeof window !== "undefined" && window.devicePixelRatio;
2717
+ return devicePixelRatio2 || 1;
2250
2718
  }
2251
- // DEPRECATED METHODS
2252
- /**
2253
- * Maps CSS pixel position to device pixel position
2254
- */
2255
2719
  cssToDevicePixels(cssPixel, yInvert = true) {
2256
2720
  const ratio = this.cssToDeviceRatio();
2257
2721
  const [width, height] = this.getDrawingBufferSize();
@@ -2261,10 +2725,10 @@ or create a device with the 'debug: true' prop.`;
2261
2725
  getPixelSize() {
2262
2726
  return this.getDevicePixelSize();
2263
2727
  }
2264
- /** @deprecated - TODO which values should we use for aspect */
2728
+ /** @deprecated Use the current drawing buffer size for projection setup. */
2265
2729
  getAspect() {
2266
- const [width, height] = this.getDevicePixelSize();
2267
- return width / height;
2730
+ const [width, height] = this.getDrawingBufferSize();
2731
+ return width > 0 && height > 0 ? width / height : 1;
2268
2732
  }
2269
2733
  /** @deprecated Returns multiplier need to convert CSS size to Device size */
2270
2734
  cssToDeviceRatio() {
@@ -2280,17 +2744,36 @@ or create a device with the 'debug: true' prop.`;
2280
2744
  resize(size) {
2281
2745
  this.setDrawingBufferSize(size.width, size.height);
2282
2746
  }
2283
- // IMPLEMENTATION
2284
- /**
2285
- * Allows subclass constructor to override the canvas id for auto created canvases.
2286
- * This can really help when debugging DOM in apps that create multiple devices
2287
- */
2288
2747
  _setAutoCreatedCanvasId(id) {
2289
2748
  if (this.htmlCanvas?.id === "lumagl-auto-created-canvas") {
2290
2749
  this.htmlCanvas.id = id;
2291
2750
  }
2292
2751
  }
2293
- /** reacts to an observed intersection */
2752
+ /**
2753
+ * Starts DOM observation after the derived context and its device are fully initialized.
2754
+ *
2755
+ * `CanvasSurface` construction runs before subclasses can assign `this.device`, and the
2756
+ * default WebGL canvas context is created before `WebGLDevice` has initialized `limits`,
2757
+ * `features`, and the rest of its runtime state. Deferring observer startup avoids early
2758
+ * `ResizeObserver` and DPR callbacks running against a partially initialized device.
2759
+ */
2760
+ _startObservers() {
2761
+ if (this.destroyed) {
2762
+ return;
2763
+ }
2764
+ this._canvasObserver.start();
2765
+ }
2766
+ /**
2767
+ * Stops all DOM observation and timers associated with a canvas surface.
2768
+ *
2769
+ * This pairs with `_startObservers()` so teardown uses the same lifecycle whether a context is
2770
+ * explicitly destroyed, abandoned during device reuse, or temporarily has not started observing
2771
+ * yet. Centralizing shutdown here keeps resize/DPR/position watchers from surviving past the
2772
+ * lifetime of the owning device.
2773
+ */
2774
+ _stopObservers() {
2775
+ this._canvasObserver.stop();
2776
+ }
2294
2777
  _handleIntersection(entries) {
2295
2778
  if (this.destroyed) {
2296
2779
  return;
@@ -2305,11 +2788,6 @@ or create a device with the 'debug: true' prop.`;
2305
2788
  this.device.props.onVisibilityChange(this);
2306
2789
  }
2307
2790
  }
2308
- /**
2309
- * Reacts to an observed resize by using the most accurate pixel size information the browser can provide
2310
- * @see https://web.dev/articles/device-pixel-content-box
2311
- * @see https://webgpufundamentals.org/webgpu/lessons/webgpu-resizing-the-canvas.html
2312
- */
2313
2791
  _handleResize(entries) {
2314
2792
  if (this.destroyed) {
2315
2793
  return;
@@ -2330,12 +2808,14 @@ or create a device with the 'debug: true' prop.`;
2330
2808
  this._updateDrawingBufferSize();
2331
2809
  this.device.props.onResize(this, { oldPixelSize });
2332
2810
  }
2333
- /** Initiate a deferred update for the canvas drawing buffer size */
2334
2811
  _updateDrawingBufferSize() {
2335
2812
  if (this.props.autoResize) {
2336
2813
  if (typeof this.props.useDevicePixels === "number") {
2337
- const dpr = this.props.useDevicePixels;
2338
- this.setDrawingBufferSize(this.cssWidth * dpr, this.cssHeight * dpr);
2814
+ const devicePixelRatio2 = this.props.useDevicePixels;
2815
+ this.setDrawingBufferSize(
2816
+ this.cssWidth * devicePixelRatio2,
2817
+ this.cssHeight * devicePixelRatio2
2818
+ );
2339
2819
  } else if (this.props.useDevicePixels) {
2340
2820
  this.setDrawingBufferSize(this.devicePixelWidth, this.devicePixelHeight);
2341
2821
  } else {
@@ -2346,7 +2826,6 @@ or create a device with the 'debug: true' prop.`;
2346
2826
  this.isInitialized = true;
2347
2827
  this.updatePosition();
2348
2828
  }
2349
- /** Perform a deferred resize of the drawing buffer if needed */
2350
2829
  _resizeDrawingBufferIfNeeded() {
2351
2830
  if (this._needsDrawingBufferResize) {
2352
2831
  this._needsDrawingBufferResize = false;
@@ -2358,36 +2837,17 @@ or create a device with the 'debug: true' prop.`;
2358
2837
  }
2359
2838
  }
2360
2839
  }
2361
- /** Monitor DPR changes */
2362
2840
  _observeDevicePixelRatio() {
2363
- if (this.destroyed) {
2841
+ if (this.destroyed || !this._canvasObserver.started) {
2364
2842
  return;
2365
2843
  }
2366
2844
  const oldRatio = this.devicePixelRatio;
2367
2845
  this.devicePixelRatio = window.devicePixelRatio;
2368
2846
  this.updatePosition();
2369
- this.device.props.onDevicePixelRatioChange?.(this, { oldRatio });
2370
- matchMedia(`(resolution: ${this.devicePixelRatio}dppx)`).addEventListener(
2371
- "change",
2372
- () => this._observeDevicePixelRatio(),
2373
- { once: true }
2374
- );
2375
- }
2376
- /** Start tracking positions with a timer */
2377
- _trackPosition(intervalMs = 100) {
2378
- const intervalId = setInterval(() => {
2379
- if (this.destroyed) {
2380
- clearInterval(intervalId);
2381
- } else {
2382
- this.updatePosition();
2383
- }
2384
- }, intervalMs);
2847
+ this.device.props.onDevicePixelRatioChange?.(this, {
2848
+ oldRatio
2849
+ });
2385
2850
  }
2386
- /**
2387
- * Calculated the absolute position of the canvas
2388
- * @note - getBoundingClientRect() is normally cheap but can be expensive
2389
- * if called before browser has finished a reflow. Should not be the case here.
2390
- */
2391
2851
  updatePosition() {
2392
2852
  if (this.destroyed) {
2393
2853
  return;
@@ -2400,13 +2860,15 @@ or create a device with the 'debug: true' prop.`;
2400
2860
  if (positionChanged) {
2401
2861
  const oldPosition = this._position;
2402
2862
  this._position = position;
2403
- this.device.props.onPositionChange?.(this, { oldPosition });
2863
+ this.device.props.onPositionChange?.(this, {
2864
+ oldPosition
2865
+ });
2404
2866
  }
2405
2867
  }
2406
2868
  }
2407
2869
  };
2408
- var CanvasContext = _CanvasContext;
2409
- __publicField(CanvasContext, "defaultProps", {
2870
+ var CanvasSurface = _CanvasSurface;
2871
+ __publicField(CanvasSurface, "defaultProps", {
2410
2872
  id: void 0,
2411
2873
  canvas: null,
2412
2874
  width: 800,
@@ -2434,7 +2896,7 @@ or create a device with the 'debug: true' prop.`;
2434
2896
  }
2435
2897
  function getCanvasFromDOM(canvasId) {
2436
2898
  const canvas = document.getElementById(canvasId);
2437
- if (!CanvasContext.isHTMLCanvas(canvas)) {
2899
+ if (!CanvasSurface.isHTMLCanvas(canvas)) {
2438
2900
  throw new Error("Object is not a canvas element");
2439
2901
  }
2440
2902
  return canvas;
@@ -2458,33 +2920,40 @@ or create a device with the 'debug: true' prop.`;
2458
2920
  const point = pixel;
2459
2921
  const x = scaleX(point[0], ratio, width);
2460
2922
  let y = scaleY(point[1], ratio, height, yInvert);
2461
- let t = scaleX(point[0] + 1, ratio, width);
2462
- const xHigh = t === width - 1 ? t : t - 1;
2463
- t = scaleY(point[1] + 1, ratio, height, yInvert);
2923
+ let temporary = scaleX(point[0] + 1, ratio, width);
2924
+ const xHigh = temporary === width - 1 ? temporary : temporary - 1;
2925
+ temporary = scaleY(point[1] + 1, ratio, height, yInvert);
2464
2926
  let yHigh;
2465
2927
  if (yInvert) {
2466
- t = t === 0 ? t : t + 1;
2928
+ temporary = temporary === 0 ? temporary : temporary + 1;
2467
2929
  yHigh = y;
2468
- y = t;
2930
+ y = temporary;
2469
2931
  } else {
2470
- yHigh = t === height - 1 ? t : t - 1;
2932
+ yHigh = temporary === height - 1 ? temporary : temporary - 1;
2471
2933
  }
2472
2934
  return {
2473
2935
  x,
2474
2936
  y,
2475
- // when ratio < 1, current css pixel and next css pixel may point to same device pixel, set width/height to 1 in those cases.
2476
2937
  width: Math.max(xHigh - x + 1, 1),
2477
2938
  height: Math.max(yHigh - y + 1, 1)
2478
2939
  };
2479
2940
  }
2480
2941
  function scaleX(x, ratio, width) {
2481
- const r = Math.min(Math.round(x * ratio), width - 1);
2482
- return r;
2942
+ return Math.min(Math.round(x * ratio), width - 1);
2483
2943
  }
2484
2944
  function scaleY(y, ratio, height, yInvert) {
2485
2945
  return yInvert ? Math.max(0, height - 1 - Math.round(y * ratio)) : Math.min(Math.round(y * ratio), height - 1);
2486
2946
  }
2487
2947
 
2948
+ // src/adapter/canvas-context.ts
2949
+ var CanvasContext = class extends CanvasSurface {
2950
+ };
2951
+ __publicField(CanvasContext, "defaultProps", CanvasSurface.defaultProps);
2952
+
2953
+ // src/adapter/presentation-context.ts
2954
+ var PresentationContext = class extends CanvasSurface {
2955
+ };
2956
+
2488
2957
  // src/adapter/resources/sampler.ts
2489
2958
  var _Sampler = class extends Resource {
2490
2959
  get [Symbol.toStringTag]() {
@@ -2539,6 +3008,8 @@ or create a device with the 'debug: true' prop.`;
2539
3008
  depth;
2540
3009
  /** mip levels in this texture */
2541
3010
  mipLevels;
3011
+ /** sample count */
3012
+ samples;
2542
3013
  /** Rows are multiples of this length, padded with extra bytes if needed */
2543
3014
  byteAlignment;
2544
3015
  /** The ready promise is always resolved. It is provided for type compatibility with DynamicTexture. */
@@ -2564,6 +3035,7 @@ or create a device with the 'debug: true' prop.`;
2564
3035
  this.height = this.props.height;
2565
3036
  this.depth = this.props.depth;
2566
3037
  this.mipLevels = this.props.mipLevels;
3038
+ this.samples = this.props.samples || 1;
2567
3039
  if (this.dimension === "cube") {
2568
3040
  this.depth = 6;
2569
3041
  }
@@ -2597,9 +3069,25 @@ or create a device with the 'debug: true' prop.`;
2597
3069
  setSampler(sampler) {
2598
3070
  this.sampler = sampler instanceof Sampler ? sampler : this.device.createSampler(sampler);
2599
3071
  }
3072
+ /**
3073
+ * Copy raw image data (bytes) into the texture.
3074
+ *
3075
+ * @note Deprecated compatibility wrapper over {@link writeData}.
3076
+ * @note Uses the same layout defaults and alignment rules as {@link writeData}.
3077
+ * @note Tightly packed CPU uploads can omit `bytesPerRow` and `rowsPerImage`.
3078
+ * @note If the CPU source rows are padded, pass explicit `bytesPerRow` and `rowsPerImage`.
3079
+ * @deprecated Use writeData()
3080
+ */
3081
+ copyImageData(options) {
3082
+ const { data, depth, ...writeOptions } = options;
3083
+ this.writeData(data, {
3084
+ ...writeOptions,
3085
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3086
+ });
3087
+ }
2600
3088
  /**
2601
3089
  * Calculates the memory layout of the texture, required when reading and writing data.
2602
- * @return the memory layout of the texture, in particular bytesPerRow which includes required padding
3090
+ * @return the backend-aligned linear layout, in particular bytesPerRow which includes any required padding for buffer copy/read paths
2603
3091
  */
2604
3092
  computeMemoryLayout(options_ = {}) {
2605
3093
  const options = this._normalizeTextureReadOptions(options_);
@@ -2618,9 +3106,11 @@ or create a device with the 'debug: true' prop.`;
2618
3106
  * @returns A Buffer containing the texture data.
2619
3107
  *
2620
3108
  * @note The memory layout of the texture data is determined by the texture format and dimensions.
2621
- * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3109
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
2622
3110
  * @note The application can call Buffer.readAsync()
2623
3111
  * @note If not supplied a buffer will be created and the application needs to call Buffer.destroy
3112
+ * @note On WebGPU this corresponds to a texture-to-buffer copy and uses buffer-copy alignment rules.
3113
+ * @note On WebGL, luma.gl emulates the same logical readback behavior.
2624
3114
  */
2625
3115
  readBuffer(options, buffer) {
2626
3116
  throw new Error("readBuffer not implemented");
@@ -2636,10 +3126,14 @@ or create a device with the 'debug: true' prop.`;
2636
3126
  throw new Error("readBuffer not implemented");
2637
3127
  }
2638
3128
  /**
2639
- * Writes an GPU Buffer into a texture.
3129
+ * Writes a GPU Buffer into a texture.
2640
3130
  *
3131
+ * @param buffer - Source GPU buffer.
3132
+ * @param options - Destination subresource, extent, and source layout options.
2641
3133
  * @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.
3134
+ * @note The application can call Texture.computeMemoryLayout() to compute the backend-aligned layout.
3135
+ * @note On WebGPU this corresponds to a buffer-to-texture copy and uses buffer-copy alignment rules.
3136
+ * @note On WebGL, luma.gl emulates the same destination and layout semantics.
2643
3137
  */
2644
3138
  writeBuffer(buffer, options) {
2645
3139
  throw new Error("readBuffer not implemented");
@@ -2647,8 +3141,11 @@ or create a device with the 'debug: true' prop.`;
2647
3141
  /**
2648
3142
  * Writes an array buffer into a texture.
2649
3143
  *
2650
- * @note The memory layout of the texture data is determined by the texture format and dimensions.
2651
- * @note The application can call Texture.computeMemoryLayout() to compute the layout.
3144
+ * @param data - Source texel data.
3145
+ * @param options - Destination subresource, extent, and source layout options.
3146
+ * @note If `bytesPerRow` and `rowsPerImage` are omitted, luma.gl computes a tightly packed CPU-memory layout for the requested region.
3147
+ * @note On WebGPU this corresponds to `GPUQueue.writeTexture()` and does not implicitly pad rows to 256 bytes.
3148
+ * @note On WebGL, padded CPU data is supported via the same `bytesPerRow` and `rowsPerImage` options.
2652
3149
  */
2653
3150
  writeData(data, options) {
2654
3151
  throw new Error("readBuffer not implemented");
@@ -2711,37 +3208,166 @@ or create a device with the 'debug: true' prop.`;
2711
3208
  }
2712
3209
  }
2713
3210
  _normalizeCopyImageDataOptions(options_) {
2714
- const { width, height, depth } = this;
2715
- const options = { ..._Texture.defaultCopyDataOptions, width, height, depth, ...options_ };
2716
- const info = this.device.getTextureFormatInfo(this.format);
2717
- if (!options_.bytesPerRow && !info.bytesPerPixel) {
2718
- throw new Error(`bytesPerRow must be provided for texture format ${this.format}`);
2719
- }
2720
- options.bytesPerRow = options_.bytesPerRow || width * (info.bytesPerPixel || 4);
2721
- options.rowsPerImage = options_.rowsPerImage || height;
2722
- return options;
3211
+ const { data, depth, ...writeOptions } = options_;
3212
+ const options = this._normalizeTextureWriteOptions({
3213
+ ...writeOptions,
3214
+ depthOrArrayLayers: writeOptions.depthOrArrayLayers ?? depth
3215
+ });
3216
+ return { data, depth: options.depthOrArrayLayers, ...options };
2723
3217
  }
2724
3218
  _normalizeCopyExternalImageOptions(options_) {
3219
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3220
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3221
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
2725
3222
  const size = this.device.getExternalImageSize(options_.image);
2726
- const options = { ..._Texture.defaultCopyExternalImageOptions, ...size, ...options_ };
2727
- options.width = Math.min(options.width, this.width - options.x);
2728
- options.height = Math.min(options.height, this.height - options.y);
3223
+ const options = {
3224
+ ..._Texture.defaultCopyExternalImageOptions,
3225
+ ...mipLevelSize,
3226
+ ...size,
3227
+ ...optionsWithoutUndefined
3228
+ };
3229
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3230
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3231
+ options.depth = Math.min(options.depth, mipLevelSize.depthOrArrayLayers - options.z);
2729
3232
  return options;
2730
3233
  }
2731
3234
  _normalizeTextureReadOptions(options_) {
2732
- const { width, height } = this;
2733
- const options = { ..._Texture.defaultTextureReadOptions, width, height, ...options_ };
2734
- options.width = Math.min(options.width, this.width - options.x);
2735
- options.height = Math.min(options.height, this.height - options.y);
3235
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3236
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3237
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3238
+ const options = {
3239
+ ..._Texture.defaultTextureReadOptions,
3240
+ ...mipLevelSize,
3241
+ ...optionsWithoutUndefined
3242
+ };
3243
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3244
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3245
+ options.depthOrArrayLayers = Math.min(
3246
+ options.depthOrArrayLayers,
3247
+ mipLevelSize.depthOrArrayLayers - options.z
3248
+ );
2736
3249
  return options;
2737
3250
  }
3251
+ /**
3252
+ * Normalizes a texture read request and validates the color-only readback contract used by the
3253
+ * current texture read APIs. Supported dimensions are `2d`, `cube`, `cube-array`,
3254
+ * `2d-array`, and `3d`.
3255
+ *
3256
+ * @throws if the texture format, aspect, or dimension is not supported by the first-pass
3257
+ * color-read implementation.
3258
+ */
3259
+ _getSupportedColorReadOptions(options_) {
3260
+ const options = this._normalizeTextureReadOptions(options_);
3261
+ const formatInfo = textureFormatDecoder.getInfo(this.format);
3262
+ this._validateColorReadAspect(options);
3263
+ this._validateColorReadFormat(formatInfo);
3264
+ switch (this.dimension) {
3265
+ case "2d":
3266
+ case "cube":
3267
+ case "cube-array":
3268
+ case "2d-array":
3269
+ case "3d":
3270
+ return options;
3271
+ default:
3272
+ throw new Error(`${this} color readback does not support ${this.dimension} textures`);
3273
+ }
3274
+ }
3275
+ /** Validates that a read request targets the full color aspect of the texture. */
3276
+ _validateColorReadAspect(options) {
3277
+ if (options.aspect !== "all") {
3278
+ throw new Error(`${this} color readback only supports aspect 'all'`);
3279
+ }
3280
+ }
3281
+ /** Validates that a read request targets an uncompressed color-renderable texture format. */
3282
+ _validateColorReadFormat(formatInfo) {
3283
+ if (formatInfo.compressed) {
3284
+ throw new Error(
3285
+ `${this} color readback does not support compressed formats (${this.format})`
3286
+ );
3287
+ }
3288
+ switch (formatInfo.attachment) {
3289
+ case "color":
3290
+ return;
3291
+ case "depth":
3292
+ throw new Error(`${this} color readback does not support depth formats (${this.format})`);
3293
+ case "stencil":
3294
+ throw new Error(`${this} color readback does not support stencil formats (${this.format})`);
3295
+ case "depth-stencil":
3296
+ throw new Error(
3297
+ `${this} color readback does not support depth-stencil formats (${this.format})`
3298
+ );
3299
+ default:
3300
+ throw new Error(`${this} color readback does not support format ${this.format}`);
3301
+ }
3302
+ }
2738
3303
  _normalizeTextureWriteOptions(options_) {
2739
- const { width, height } = this;
2740
- const options = { ..._Texture.defaultTextureReadOptions, width, height, ...options_ };
2741
- options.width = Math.min(options.width, this.width - options.x);
2742
- options.height = Math.min(options.height, this.height - options.y);
3304
+ const optionsWithoutUndefined = _Texture._omitUndefined(options_);
3305
+ const mipLevel = optionsWithoutUndefined.mipLevel ?? 0;
3306
+ const mipLevelSize = this._getMipLevelSize(mipLevel);
3307
+ const options = {
3308
+ ..._Texture.defaultTextureWriteOptions,
3309
+ ...mipLevelSize,
3310
+ ...optionsWithoutUndefined
3311
+ };
3312
+ options.width = Math.min(options.width, mipLevelSize.width - options.x);
3313
+ options.height = Math.min(options.height, mipLevelSize.height - options.y);
3314
+ options.depthOrArrayLayers = Math.min(
3315
+ options.depthOrArrayLayers,
3316
+ mipLevelSize.depthOrArrayLayers - options.z
3317
+ );
3318
+ const layout = textureFormatDecoder.computeMemoryLayout({
3319
+ format: this.format,
3320
+ width: options.width,
3321
+ height: options.height,
3322
+ depth: options.depthOrArrayLayers,
3323
+ byteAlignment: this.byteAlignment
3324
+ });
3325
+ const minimumBytesPerRow = layout.bytesPerPixel * options.width;
3326
+ options.bytesPerRow = optionsWithoutUndefined.bytesPerRow ?? layout.bytesPerRow;
3327
+ options.rowsPerImage = optionsWithoutUndefined.rowsPerImage ?? options.height;
3328
+ if (options.bytesPerRow < minimumBytesPerRow) {
3329
+ throw new Error(
3330
+ `bytesPerRow (${options.bytesPerRow}) must be at least ${minimumBytesPerRow} for ${this.format}`
3331
+ );
3332
+ }
3333
+ if (options.rowsPerImage < options.height) {
3334
+ throw new Error(
3335
+ `rowsPerImage (${options.rowsPerImage}) must be at least ${options.height} for ${this.format}`
3336
+ );
3337
+ }
3338
+ const bytesPerPixel = this.device.getTextureFormatInfo(this.format).bytesPerPixel;
3339
+ if (bytesPerPixel && options.bytesPerRow % bytesPerPixel !== 0) {
3340
+ throw new Error(
3341
+ `bytesPerRow (${options.bytesPerRow}) must be a multiple of bytesPerPixel (${bytesPerPixel}) for ${this.format}`
3342
+ );
3343
+ }
2743
3344
  return options;
2744
3345
  }
3346
+ _getMipLevelSize(mipLevel) {
3347
+ const width = Math.max(1, this.width >> mipLevel);
3348
+ const height = this.baseDimension === "1d" ? 1 : Math.max(1, this.height >> mipLevel);
3349
+ const depthOrArrayLayers = this.dimension === "3d" ? Math.max(1, this.depth >> mipLevel) : this.depth;
3350
+ return { width, height, depthOrArrayLayers };
3351
+ }
3352
+ getAllocatedByteLength() {
3353
+ let allocatedByteLength = 0;
3354
+ for (let mipLevel = 0; mipLevel < this.mipLevels; mipLevel++) {
3355
+ const { width, height, depthOrArrayLayers } = this._getMipLevelSize(mipLevel);
3356
+ allocatedByteLength += textureFormatDecoder.computeMemoryLayout({
3357
+ format: this.format,
3358
+ width,
3359
+ height,
3360
+ depth: depthOrArrayLayers,
3361
+ byteAlignment: 1
3362
+ }).byteLength;
3363
+ }
3364
+ return allocatedByteLength * this.samples;
3365
+ }
3366
+ static _omitUndefined(options) {
3367
+ return Object.fromEntries(
3368
+ Object.entries(options).filter(([, value]) => value !== void 0)
3369
+ );
3370
+ }
2745
3371
  };
2746
3372
  var Texture = _Texture;
2747
3373
  /** The texture can be bound for use as a sampled texture in a shader */
@@ -2777,6 +3403,10 @@ or create a device with the 'debug: true' prop.`;
2777
3403
  byteOffset: 0,
2778
3404
  bytesPerRow: void 0,
2779
3405
  rowsPerImage: void 0,
3406
+ width: void 0,
3407
+ height: void 0,
3408
+ depthOrArrayLayers: void 0,
3409
+ depth: 1,
2780
3410
  mipLevel: 0,
2781
3411
  x: 0,
2782
3412
  y: 0,
@@ -2810,6 +3440,19 @@ or create a device with the 'debug: true' prop.`;
2810
3440
  mipLevel: 0,
2811
3441
  aspect: "all"
2812
3442
  });
3443
+ __publicField(Texture, "defaultTextureWriteOptions", {
3444
+ byteOffset: 0,
3445
+ bytesPerRow: void 0,
3446
+ rowsPerImage: void 0,
3447
+ x: 0,
3448
+ y: 0,
3449
+ z: 0,
3450
+ width: void 0,
3451
+ height: void 0,
3452
+ depthOrArrayLayers: 1,
3453
+ mipLevel: 0,
3454
+ aspect: "all"
3455
+ });
2813
3456
 
2814
3457
  // src/adapter/resources/texture-view.ts
2815
3458
  var _TextureView = class extends Resource {
@@ -3188,10 +3831,21 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3188
3831
  linkStatus = "pending";
3189
3832
  /** The hash of the pipeline */
3190
3833
  hash = "";
3834
+ /** Optional shared backend implementation */
3835
+ sharedRenderPipeline = null;
3836
+ /** Whether shader or pipeline compilation/linking is still in progress */
3837
+ get isPending() {
3838
+ return this.linkStatus === "pending" || this.vs.compilationStatus === "pending" || this.fs?.compilationStatus === "pending";
3839
+ }
3840
+ /** Whether shader or pipeline compilation/linking has failed */
3841
+ get isErrored() {
3842
+ return this.linkStatus === "error" || this.vs.compilationStatus === "error" || this.fs?.compilationStatus === "error";
3843
+ }
3191
3844
  constructor(device, props) {
3192
3845
  super(device, props, _RenderPipeline.defaultProps);
3193
3846
  this.shaderLayout = this.props.shaderLayout;
3194
3847
  this.bufferLayout = this.props.bufferLayout || [];
3848
+ this.sharedRenderPipeline = this.props._sharedRenderPipeline || null;
3195
3849
  }
3196
3850
  };
3197
3851
  var RenderPipeline = _RenderPipeline;
@@ -3209,10 +3863,30 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3209
3863
  colorAttachmentFormats: void 0,
3210
3864
  depthStencilAttachmentFormat: void 0,
3211
3865
  parameters: {},
3212
- bindings: {},
3213
- uniforms: {}
3866
+ varyings: void 0,
3867
+ bufferMode: void 0,
3868
+ disableWarnings: false,
3869
+ _sharedRenderPipeline: void 0,
3870
+ bindings: void 0
3214
3871
  });
3215
3872
 
3873
+ // src/adapter/resources/shared-render-pipeline.ts
3874
+ var SharedRenderPipeline = class extends Resource {
3875
+ get [Symbol.toStringTag]() {
3876
+ return "SharedRenderPipeline";
3877
+ }
3878
+ constructor(device, props) {
3879
+ super(device, props, {
3880
+ ...Resource.defaultProps,
3881
+ handle: void 0,
3882
+ vs: void 0,
3883
+ fs: void 0,
3884
+ varyings: void 0,
3885
+ bufferMode: void 0
3886
+ });
3887
+ }
3888
+ };
3889
+
3216
3890
  // src/adapter/resources/render-pass.ts
3217
3891
  var _RenderPass = class extends Resource {
3218
3892
  get [Symbol.toStringTag]() {
@@ -3295,8 +3969,69 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3295
3969
  get [Symbol.toStringTag]() {
3296
3970
  return "CommandEncoder";
3297
3971
  }
3972
+ _timeProfilingQuerySet = null;
3973
+ _timeProfilingSlotCount = 0;
3974
+ _gpuTimeMs;
3298
3975
  constructor(device, props) {
3299
3976
  super(device, props, _CommandEncoder.defaultProps);
3977
+ this._timeProfilingQuerySet = props.timeProfilingQuerySet ?? null;
3978
+ this._timeProfilingSlotCount = 0;
3979
+ this._gpuTimeMs = void 0;
3980
+ }
3981
+ /**
3982
+ * Reads all resolved timestamp pairs on the current profiler query set and caches the sum
3983
+ * as milliseconds on this encoder.
3984
+ */
3985
+ async resolveTimeProfilingQuerySet() {
3986
+ this._gpuTimeMs = void 0;
3987
+ if (!this._timeProfilingQuerySet) {
3988
+ return;
3989
+ }
3990
+ const pairCount = Math.floor(this._timeProfilingSlotCount / 2);
3991
+ if (pairCount <= 0) {
3992
+ return;
3993
+ }
3994
+ const queryCount = pairCount * 2;
3995
+ const results = await this._timeProfilingQuerySet.readResults({
3996
+ firstQuery: 0,
3997
+ queryCount
3998
+ });
3999
+ let totalDurationNanoseconds = 0n;
4000
+ for (let queryIndex = 0; queryIndex < queryCount; queryIndex += 2) {
4001
+ totalDurationNanoseconds += results[queryIndex + 1] - results[queryIndex];
4002
+ }
4003
+ this._gpuTimeMs = Number(totalDurationNanoseconds) / 1e6;
4004
+ }
4005
+ /** Returns the number of query slots consumed by automatic pass profiling on this encoder. */
4006
+ getTimeProfilingSlotCount() {
4007
+ return this._timeProfilingSlotCount;
4008
+ }
4009
+ getTimeProfilingQuerySet() {
4010
+ return this._timeProfilingQuerySet;
4011
+ }
4012
+ /** Internal helper for auto-assigning timestamp slots to render/compute passes on this encoder. */
4013
+ _applyTimeProfilingToPassProps(props) {
4014
+ const passProps = props || {};
4015
+ if (!this._supportsTimestampQueries() || !this._timeProfilingQuerySet) {
4016
+ return passProps;
4017
+ }
4018
+ if (passProps.timestampQuerySet !== void 0 || passProps.beginTimestampIndex !== void 0 || passProps.endTimestampIndex !== void 0) {
4019
+ return passProps;
4020
+ }
4021
+ const beginTimestampIndex = this._timeProfilingSlotCount;
4022
+ if (beginTimestampIndex + 1 >= this._timeProfilingQuerySet.props.count) {
4023
+ return passProps;
4024
+ }
4025
+ this._timeProfilingSlotCount += 2;
4026
+ return {
4027
+ ...passProps,
4028
+ timestampQuerySet: this._timeProfilingQuerySet,
4029
+ beginTimestampIndex,
4030
+ endTimestampIndex: beginTimestampIndex + 1
4031
+ };
4032
+ }
4033
+ _supportsTimestampQueries() {
4034
+ return this.device.features.has("timestamp-query");
3300
4035
  }
3301
4036
  };
3302
4037
  var CommandEncoder = _CommandEncoder;
@@ -3305,7 +4040,8 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3305
4040
  // beginComputePass(optional GPUComputePassDescriptor descriptor = {}): GPUComputePassEncoder;
3306
4041
  __publicField(CommandEncoder, "defaultProps", {
3307
4042
  ...Resource.defaultProps,
3308
- measureExecutionTime: void 0
4043
+ measureExecutionTime: void 0,
4044
+ timeProfilingQuerySet: void 0
3309
4045
  });
3310
4046
 
3311
4047
  // src/adapter/resources/command-buffer.ts
@@ -3324,11 +4060,20 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3324
4060
 
3325
4061
  // src/shadertypes/data-types/decode-shader-types.ts
3326
4062
  function getVariableShaderTypeInfo(format) {
3327
- const decoded = UNIFORM_FORMATS[format];
4063
+ const resolvedFormat = resolveVariableShaderTypeAlias(format);
4064
+ const decoded = UNIFORM_FORMATS[resolvedFormat];
4065
+ if (!decoded) {
4066
+ throw new Error(`Unsupported variable shader type: ${format}`);
4067
+ }
3328
4068
  return decoded;
3329
4069
  }
3330
4070
  function getAttributeShaderTypeInfo(attributeType) {
3331
- const [primitiveType, components] = TYPE_INFO[attributeType];
4071
+ const resolvedAttributeType = resolveAttributeShaderTypeAlias(attributeType);
4072
+ const decoded = TYPE_INFO[resolvedAttributeType];
4073
+ if (!decoded) {
4074
+ throw new Error(`Unsupported attribute shader type: ${attributeType}`);
4075
+ }
4076
+ const [primitiveType, components] = decoded;
3332
4077
  const integer = primitiveType === "i32" || primitiveType === "u32";
3333
4078
  const signed = primitiveType !== "u32";
3334
4079
  const byteLength = PRIMITIVE_TYPE_SIZES[primitiveType] * components;
@@ -3340,6 +4085,12 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3340
4085
  signed
3341
4086
  };
3342
4087
  }
4088
+ function resolveAttributeShaderTypeAlias(alias) {
4089
+ return WGSL_ATTRIBUTE_TYPE_ALIAS_MAP[alias] || alias;
4090
+ }
4091
+ function resolveVariableShaderTypeAlias(alias) {
4092
+ return WGSL_VARIABLE_TYPE_ALIAS_MAP[alias] || alias;
4093
+ }
3343
4094
  var PRIMITIVE_TYPE_SIZES = {
3344
4095
  f32: 4,
3345
4096
  f16: 2,
@@ -3664,7 +4415,9 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3664
4415
 
3665
4416
  // src/adapter/resources/fence.ts
3666
4417
  var _Fence = class extends Resource {
3667
- [Symbol.toStringTag] = "WEBGLFence";
4418
+ get [Symbol.toStringTag]() {
4419
+ return "Fence";
4420
+ }
3668
4421
  constructor(device, props = {}) {
3669
4422
  super(device, props, _Fence.defaultProps);
3670
4423
  }
@@ -3824,20 +4577,26 @@ ${numberedLines}${positionIndicator}${message.type.toUpperCase()}: ${message.mes
3824
4577
  }
3825
4578
 
3826
4579
  // src/utils/array-equal.ts
4580
+ var MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH = 128;
3827
4581
  function arrayEqual(a, b, limit = 16) {
3828
- if (a !== b) {
3829
- return false;
4582
+ if (a === b) {
4583
+ return true;
3830
4584
  }
3831
4585
  const arrayA = a;
3832
4586
  const arrayB = b;
3833
- if (!isNumberArray(arrayA)) {
4587
+ if (!isNumberArray(arrayA) || !isNumberArray(arrayB)) {
3834
4588
  return false;
3835
4589
  }
3836
- if (isNumberArray(arrayB) && arrayA.length === arrayB.length) {
3837
- for (let i = 0; i < arrayA.length; ++i) {
3838
- if (arrayB[i] !== arrayA[i]) {
3839
- return false;
3840
- }
4590
+ if (arrayA.length !== arrayB.length) {
4591
+ return false;
4592
+ }
4593
+ const maxCompareLength = Math.min(limit, MAX_ELEMENTWISE_ARRAY_COMPARE_LENGTH);
4594
+ if (arrayA.length > maxCompareLength) {
4595
+ return false;
4596
+ }
4597
+ for (let i = 0; i < arrayA.length; ++i) {
4598
+ if (arrayB[i] !== arrayA[i]) {
4599
+ return false;
3841
4600
  }
3842
4601
  }
3843
4602
  return true;