@luma.gl/webgpu 9.3.0-alpha.2 → 9.3.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/adapter/helpers/cpu-hotspot-profiler.d.ts +54 -0
  2. package/dist/adapter/helpers/cpu-hotspot-profiler.d.ts.map +1 -0
  3. package/dist/adapter/helpers/cpu-hotspot-profiler.js +26 -0
  4. package/dist/adapter/helpers/cpu-hotspot-profiler.js.map +1 -0
  5. package/dist/adapter/helpers/generate-mipmaps-webgpu.d.ts +7 -0
  6. package/dist/adapter/helpers/generate-mipmaps-webgpu.d.ts.map +1 -0
  7. package/dist/adapter/helpers/generate-mipmaps-webgpu.js +490 -0
  8. package/dist/adapter/helpers/generate-mipmaps-webgpu.js.map +1 -0
  9. package/dist/adapter/helpers/get-bind-group.d.ts +2 -1
  10. package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
  11. package/dist/adapter/helpers/get-bind-group.js +31 -21
  12. package/dist/adapter/helpers/get-bind-group.js.map +1 -1
  13. package/dist/adapter/resources/webgpu-buffer.d.ts +7 -0
  14. package/dist/adapter/resources/webgpu-buffer.d.ts.map +1 -1
  15. package/dist/adapter/resources/webgpu-buffer.js +58 -15
  16. package/dist/adapter/resources/webgpu-buffer.js.map +1 -1
  17. package/dist/adapter/resources/webgpu-command-buffer.js +1 -1
  18. package/dist/adapter/resources/webgpu-command-buffer.js.map +1 -1
  19. package/dist/adapter/resources/webgpu-command-encoder.d.ts +5 -4
  20. package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
  21. package/dist/adapter/resources/webgpu-command-encoder.js +23 -5
  22. package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
  23. package/dist/adapter/resources/webgpu-compute-pass.d.ts +1 -1
  24. package/dist/adapter/resources/webgpu-compute-pass.d.ts.map +1 -1
  25. package/dist/adapter/resources/webgpu-compute-pass.js +14 -6
  26. package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
  27. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
  28. package/dist/adapter/resources/webgpu-compute-pipeline.js +19 -3
  29. package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
  30. package/dist/adapter/resources/webgpu-framebuffer.d.ts +6 -0
  31. package/dist/adapter/resources/webgpu-framebuffer.d.ts.map +1 -1
  32. package/dist/adapter/resources/webgpu-framebuffer.js +16 -0
  33. package/dist/adapter/resources/webgpu-framebuffer.js.map +1 -1
  34. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
  35. package/dist/adapter/resources/webgpu-pipeline-layout.js +1 -2
  36. package/dist/adapter/resources/webgpu-pipeline-layout.js.map +1 -1
  37. package/dist/adapter/resources/webgpu-query-set.d.ts +33 -4
  38. package/dist/adapter/resources/webgpu-query-set.d.ts.map +1 -1
  39. package/dist/adapter/resources/webgpu-query-set.js +145 -4
  40. package/dist/adapter/resources/webgpu-query-set.js.map +1 -1
  41. package/dist/adapter/resources/webgpu-render-pass.d.ts +3 -0
  42. package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
  43. package/dist/adapter/resources/webgpu-render-pass.js +74 -30
  44. package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
  45. package/dist/adapter/resources/webgpu-render-pipeline.d.ts +7 -4
  46. package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
  47. package/dist/adapter/resources/webgpu-render-pipeline.js +26 -15
  48. package/dist/adapter/resources/webgpu-render-pipeline.js.map +1 -1
  49. package/dist/adapter/resources/webgpu-sampler.d.ts.map +1 -1
  50. package/dist/adapter/resources/webgpu-sampler.js +4 -0
  51. package/dist/adapter/resources/webgpu-sampler.js.map +1 -1
  52. package/dist/adapter/resources/webgpu-texture-view.d.ts +6 -0
  53. package/dist/adapter/resources/webgpu-texture-view.d.ts.map +1 -1
  54. package/dist/adapter/resources/webgpu-texture-view.js +47 -11
  55. package/dist/adapter/resources/webgpu-texture-view.js.map +1 -1
  56. package/dist/adapter/resources/webgpu-texture.d.ts +10 -4
  57. package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
  58. package/dist/adapter/resources/webgpu-texture.js +116 -57
  59. package/dist/adapter/resources/webgpu-texture.js.map +1 -1
  60. package/dist/adapter/resources/webgpu-vertex-array.js +1 -1
  61. package/dist/adapter/resources/webgpu-vertex-array.js.map +1 -1
  62. package/dist/adapter/webgpu-canvas-context.d.ts +2 -0
  63. package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
  64. package/dist/adapter/webgpu-canvas-context.js +78 -19
  65. package/dist/adapter/webgpu-canvas-context.js.map +1 -1
  66. package/dist/adapter/webgpu-device.d.ts +5 -1
  67. package/dist/adapter/webgpu-device.d.ts.map +1 -1
  68. package/dist/adapter/webgpu-device.js +113 -9
  69. package/dist/adapter/webgpu-device.js.map +1 -1
  70. package/dist/adapter/webgpu-presentation-context.d.ts +25 -0
  71. package/dist/adapter/webgpu-presentation-context.d.ts.map +1 -0
  72. package/dist/adapter/webgpu-presentation-context.js +144 -0
  73. package/dist/adapter/webgpu-presentation-context.js.map +1 -0
  74. package/dist/dist.dev.js +2864 -1702
  75. package/dist/dist.min.js +167 -8
  76. package/dist/index.cjs +1363 -225
  77. package/dist/index.cjs.map +4 -4
  78. package/package.json +5 -5
  79. package/src/adapter/helpers/cpu-hotspot-profiler.ts +70 -0
  80. package/src/adapter/helpers/generate-mipmaps-webgpu.ts +583 -0
  81. package/src/adapter/helpers/get-bind-group.ts +37 -22
  82. package/src/adapter/resources/webgpu-buffer.ts +61 -15
  83. package/src/adapter/resources/webgpu-command-buffer.ts +1 -1
  84. package/src/adapter/resources/webgpu-command-encoder.ts +32 -6
  85. package/src/adapter/resources/webgpu-compute-pass.ts +14 -6
  86. package/src/adapter/resources/webgpu-compute-pipeline.ts +21 -3
  87. package/src/adapter/resources/webgpu-framebuffer.ts +21 -0
  88. package/src/adapter/resources/webgpu-pipeline-layout.ts +1 -2
  89. package/src/adapter/resources/webgpu-query-set.ts +185 -9
  90. package/src/adapter/resources/webgpu-render-pass.ts +82 -34
  91. package/src/adapter/resources/webgpu-render-pipeline.ts +36 -19
  92. package/src/adapter/resources/webgpu-sampler.ts +5 -0
  93. package/src/adapter/resources/webgpu-texture-view.ts +51 -11
  94. package/src/adapter/resources/webgpu-texture.ts +142 -93
  95. package/src/adapter/resources/webgpu-vertex-array.ts +1 -1
  96. package/src/adapter/webgpu-canvas-context.ts +91 -26
  97. package/src/adapter/webgpu-device.ts +128 -9
  98. package/src/adapter/webgpu-presentation-context.ts +180 -0
package/dist/index.cjs CHANGED
@@ -7,8 +7,8 @@ var __esm = (fn, res) => function __init() {
7
7
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
8
  };
9
9
  var __export = (target, all) => {
10
- for (var name2 in all)
11
- __defProp(target, name2, { get: all[name2], enumerable: true });
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
12
  };
13
13
  var __copyProps = (to, from, except, desc) => {
14
14
  if (from && typeof from === "object" || typeof from === "function") {
@@ -30,13 +30,15 @@ var init_webgpu_buffer = __esm({
30
30
  device;
31
31
  handle;
32
32
  byteLength;
33
+ paddedByteLength;
33
34
  constructor(device, props) {
34
35
  var _a, _b;
35
36
  super(device, props);
36
37
  this.device = device;
37
38
  this.byteLength = props.byteLength || ((_a = props.data) == null ? void 0 : _a.byteLength) || 0;
39
+ this.paddedByteLength = Math.ceil(this.byteLength / 4) * 4;
38
40
  const mappedAtCreation = Boolean(this.props.onMapped || props.data);
39
- const size = Math.ceil(this.byteLength / 4) * 4;
41
+ const size = this.paddedByteLength;
40
42
  this.device.pushErrorScope("out-of-memory");
41
43
  this.device.pushErrorScope("validation");
42
44
  this.handle = this.props.handle || this.device.handle.createBuffer({
@@ -72,11 +74,24 @@ var init_webgpu_buffer = __esm({
72
74
  this.device.reportError(new Error(`${this} creation failed ${error.message}`), this)();
73
75
  this.device.debug();
74
76
  });
77
+ if (!this.props.handle) {
78
+ this.trackAllocatedMemory(size);
79
+ } else {
80
+ this.trackReferencedMemory(size, "Buffer");
81
+ }
75
82
  }
76
83
  destroy() {
77
- var _a;
78
- (_a = this.handle) == null ? void 0 : _a.destroy();
79
- this.handle = null;
84
+ if (!this.destroyed && this.handle) {
85
+ this.removeStats();
86
+ if (!this.props.handle) {
87
+ this.trackDeallocatedMemory();
88
+ this.handle.destroy();
89
+ } else {
90
+ this.trackDeallocatedReferencedMemory("Buffer");
91
+ }
92
+ this.destroyed = true;
93
+ this.handle = null;
94
+ }
80
95
  }
81
96
  write(data, byteOffset = 0) {
82
97
  const arrayBuffer = ArrayBuffer.isView(data) ? data.buffer : data;
@@ -89,18 +104,21 @@ var init_webgpu_buffer = __esm({
89
104
  });
90
105
  }
91
106
  async mapAndWriteAsync(callback, byteOffset = 0, byteLength = this.byteLength - byteOffset) {
107
+ const alignedByteLength = Math.ceil(byteLength / 4) * 4;
92
108
  const isMappable = (this.usage & import_core.Buffer.MAP_WRITE) !== 0;
93
- const mappableBuffer = !isMappable ? this._getMappableBuffer(import_core.Buffer.MAP_WRITE | import_core.Buffer.COPY_SRC, 0, this.byteLength) : null;
109
+ const mappableBuffer = !isMappable ? this._getMappableBuffer(import_core.Buffer.MAP_WRITE | import_core.Buffer.COPY_SRC, 0, this.paddedByteLength) : null;
94
110
  const writeBuffer = mappableBuffer || this;
95
111
  this.device.pushErrorScope("validation");
96
112
  try {
97
113
  await this.device.handle.queue.onSubmittedWorkDone();
98
- await writeBuffer.handle.mapAsync(GPUMapMode.WRITE, byteOffset, byteLength);
99
- const arrayBuffer = writeBuffer.handle.getMappedRange(byteOffset, byteLength);
114
+ await writeBuffer.handle.mapAsync(GPUMapMode.WRITE, byteOffset, alignedByteLength);
115
+ const mappedRange = writeBuffer.handle.getMappedRange(byteOffset, alignedByteLength);
116
+ const arrayBuffer = mappedRange.slice(0, byteLength);
100
117
  await callback(arrayBuffer, "mapped");
118
+ new Uint8Array(mappedRange).set(new Uint8Array(arrayBuffer), 0);
101
119
  writeBuffer.handle.unmap();
102
120
  if (mappableBuffer) {
103
- this._copyBuffer(mappableBuffer, byteOffset, byteLength);
121
+ this._copyBuffer(mappableBuffer, byteOffset, alignedByteLength);
104
122
  }
105
123
  } finally {
106
124
  this.device.popErrorScope((error) => {
@@ -114,24 +132,37 @@ var init_webgpu_buffer = __esm({
114
132
  return this.mapAndReadAsync((arrayBuffer) => new Uint8Array(arrayBuffer.slice(0)), byteOffset, byteLength);
115
133
  }
116
134
  async mapAndReadAsync(callback, byteOffset = 0, byteLength = this.byteLength - byteOffset) {
135
+ const requestedEnd = byteOffset + byteLength;
136
+ if (requestedEnd > this.byteLength) {
137
+ throw new Error("Mapping range exceeds buffer size");
138
+ }
139
+ let mappedByteOffset = byteOffset;
140
+ let mappedByteLength = byteLength;
141
+ let sliceByteOffset = 0;
142
+ let lifetime = "mapped";
117
143
  if (byteOffset % 8 !== 0 || byteLength % 4 !== 0) {
118
- throw new Error("byteOffset must be multiple of 8 and byteLength multiple of 4");
144
+ mappedByteOffset = Math.floor(byteOffset / 8) * 8;
145
+ const alignedEnd = Math.ceil(requestedEnd / 4) * 4;
146
+ mappedByteLength = alignedEnd - mappedByteOffset;
147
+ sliceByteOffset = byteOffset - mappedByteOffset;
148
+ lifetime = "copied";
119
149
  }
120
- if (byteOffset + byteLength > this.handle.size) {
150
+ if (mappedByteOffset + mappedByteLength > this.paddedByteLength) {
121
151
  throw new Error("Mapping range exceeds buffer size");
122
152
  }
123
153
  const isMappable = (this.usage & import_core.Buffer.MAP_READ) !== 0;
124
- const mappableBuffer = !isMappable ? this._getMappableBuffer(import_core.Buffer.MAP_READ | import_core.Buffer.COPY_DST, 0, this.byteLength) : null;
154
+ const mappableBuffer = !isMappable ? this._getMappableBuffer(import_core.Buffer.MAP_READ | import_core.Buffer.COPY_DST, 0, this.paddedByteLength) : null;
125
155
  const readBuffer = mappableBuffer || this;
126
156
  this.device.pushErrorScope("validation");
127
157
  try {
128
158
  await this.device.handle.queue.onSubmittedWorkDone();
129
159
  if (mappableBuffer) {
130
- mappableBuffer._copyBuffer(this);
160
+ mappableBuffer._copyBuffer(this, mappedByteOffset, mappedByteLength);
131
161
  }
132
- await readBuffer.handle.mapAsync(GPUMapMode.READ, byteOffset, byteLength);
133
- const arrayBuffer = readBuffer.handle.getMappedRange(byteOffset, byteLength);
134
- const result = await callback(arrayBuffer, "mapped");
162
+ await readBuffer.handle.mapAsync(GPUMapMode.READ, mappedByteOffset, mappedByteLength);
163
+ const arrayBuffer = readBuffer.handle.getMappedRange(mappedByteOffset, mappedByteLength);
164
+ const mappedRange = lifetime === "mapped" ? arrayBuffer : arrayBuffer.slice(sliceByteOffset, sliceByteOffset + byteLength);
165
+ const result = await callback(mappedRange, lifetime);
135
166
  readBuffer.handle.unmap();
136
167
  return result;
137
168
  } finally {
@@ -208,18 +239,47 @@ var init_webgpu_sampler = __esm({
208
239
  this.handle.label = this.props.id;
209
240
  }
210
241
  destroy() {
242
+ if (this.destroyed) {
243
+ return;
244
+ }
245
+ this.destroyResource();
211
246
  this.handle = null;
212
247
  }
213
248
  };
214
249
  }
215
250
  });
216
251
 
252
+ // dist/adapter/helpers/cpu-hotspot-profiler.js
253
+ function getCpuHotspotProfiler(owner) {
254
+ const profiler = owner.userData[CPU_HOTSPOT_PROFILER_MODULE];
255
+ return (profiler == null ? void 0 : profiler.enabled) ? profiler : null;
256
+ }
257
+ function getCpuHotspotSubmitReason(owner) {
258
+ return owner.userData[CPU_HOTSPOT_SUBMIT_REASON] || null;
259
+ }
260
+ function setCpuHotspotSubmitReason(owner, submitReason) {
261
+ owner.userData[CPU_HOTSPOT_SUBMIT_REASON] = submitReason;
262
+ }
263
+ function getTimestamp() {
264
+ var _a, _b;
265
+ return ((_b = (_a = globalThis.performance) == null ? void 0 : _a.now) == null ? void 0 : _b.call(_a)) ?? Date.now();
266
+ }
267
+ var CPU_HOTSPOT_PROFILER_MODULE, CPU_HOTSPOT_SUBMIT_REASON;
268
+ var init_cpu_hotspot_profiler = __esm({
269
+ "dist/adapter/helpers/cpu-hotspot-profiler.js"() {
270
+ "use strict";
271
+ CPU_HOTSPOT_PROFILER_MODULE = "cpu-hotspot-profiler";
272
+ CPU_HOTSPOT_SUBMIT_REASON = "cpu-hotspot-submit-reason";
273
+ }
274
+ });
275
+
217
276
  // dist/adapter/resources/webgpu-texture-view.js
218
277
  var import_core3, WebGPUTextureView;
219
278
  var init_webgpu_texture_view = __esm({
220
279
  "dist/adapter/resources/webgpu-texture-view.js"() {
221
280
  "use strict";
222
281
  import_core3 = require("@luma.gl/core");
282
+ init_cpu_hotspot_profiler();
223
283
  WebGPUTextureView = class extends import_core3.TextureView {
224
284
  device;
225
285
  handle;
@@ -229,8 +289,7 @@ var init_webgpu_texture_view = __esm({
229
289
  this.device = device;
230
290
  this.texture = props.texture;
231
291
  this.device.pushErrorScope("validation");
232
- this.handle = // props.handle ||
233
- this.texture.handle.createView({
292
+ this.handle = this.texture.handle.createView({
234
293
  format: this.props.format || this.texture.format,
235
294
  dimension: this.props.dimension || this.texture.dimension,
236
295
  aspect: this.props.aspect,
@@ -246,8 +305,42 @@ var init_webgpu_texture_view = __esm({
246
305
  this.handle.label = this.props.id;
247
306
  }
248
307
  destroy() {
308
+ if (this.destroyed) {
309
+ return;
310
+ }
311
+ this.destroyResource();
249
312
  this.handle = null;
250
313
  }
314
+ /**
315
+ * Internal-only hook for the cached CanvasContext/PresentationContext swapchain path.
316
+ * Rebuilds the default view when the per-frame canvas texture handle changes, without
317
+ * replacing the long-lived luma.gl wrapper object.
318
+ */
319
+ _reinitialize(texture) {
320
+ this.texture = texture;
321
+ const profiler = getCpuHotspotProfiler(this.device);
322
+ this.device.pushErrorScope("validation");
323
+ const createViewStartTime = profiler ? getTimestamp() : 0;
324
+ const handle = this.texture.handle.createView({
325
+ format: this.props.format || this.texture.format,
326
+ dimension: this.props.dimension || this.texture.dimension,
327
+ aspect: this.props.aspect,
328
+ baseMipLevel: this.props.baseMipLevel,
329
+ mipLevelCount: this.props.mipLevelCount,
330
+ baseArrayLayer: this.props.baseArrayLayer,
331
+ arrayLayerCount: this.props.arrayLayerCount
332
+ });
333
+ if (profiler) {
334
+ profiler.textureViewReinitializeCount = (profiler.textureViewReinitializeCount || 0) + 1;
335
+ profiler.textureViewReinitializeTimeMs = (profiler.textureViewReinitializeTimeMs || 0) + (getTimestamp() - createViewStartTime);
336
+ }
337
+ this.device.popErrorScope((error) => {
338
+ this.device.reportError(new Error(`TextureView constructor: ${error.message}`), this)();
339
+ this.device.debug();
340
+ });
341
+ handle.label = this.props.id;
342
+ this.handle = handle;
343
+ }
251
344
  };
252
345
  }
253
346
  });
@@ -266,9 +359,18 @@ var init_webgpu_texture = __esm({
266
359
  handle;
267
360
  sampler;
268
361
  view;
362
+ _allocatedByteLength = 0;
269
363
  constructor(device, props) {
270
364
  super(device, props, { byteAlignment: 256 });
271
365
  this.device = device;
366
+ if (props.sampler instanceof WebGPUSampler) {
367
+ this.sampler = props.sampler;
368
+ } else if (props.sampler === void 0) {
369
+ this.sampler = this.device.getDefaultSampler();
370
+ } else {
371
+ this.sampler = new WebGPUSampler(this.device, props.sampler || {});
372
+ this.attachResource(this.sampler);
373
+ }
272
374
  this.device.pushErrorScope("out-of-memory");
273
375
  this.device.pushErrorScope("validation");
274
376
  this.handle = this.props.handle || this.device.handle.createTexture({
@@ -297,7 +399,6 @@ var init_webgpu_texture = __esm({
297
399
  this.width = this.handle.width;
298
400
  this.height = this.handle.height;
299
401
  }
300
- this.sampler = props.sampler instanceof WebGPUSampler ? props.sampler : new WebGPUSampler(this.device, props.sampler || {});
301
402
  this.view = new WebGPUTextureView(this.device, {
302
403
  ...this.props,
303
404
  texture: this,
@@ -305,11 +406,26 @@ var init_webgpu_texture = __esm({
305
406
  // Note: arrayLayerCount controls the view of array textures, but does not apply to 3d texture depths
306
407
  arrayLayerCount: this.dimension !== "3d" ? this.depth : 1
307
408
  });
409
+ this.attachResource(this.view);
308
410
  this._initializeData(props.data);
411
+ this._allocatedByteLength = this.getAllocatedByteLength();
412
+ if (!this.props.handle) {
413
+ this.trackAllocatedMemory(this._allocatedByteLength, "Texture");
414
+ } else {
415
+ this.trackReferencedMemory(this._allocatedByteLength, "Texture");
416
+ }
309
417
  }
310
418
  destroy() {
311
- var _a;
312
- (_a = this.handle) == null ? void 0 : _a.destroy();
419
+ if (this.destroyed) {
420
+ return;
421
+ }
422
+ if (!this.props.handle && this.handle) {
423
+ this.trackDeallocatedMemory("Texture");
424
+ this.handle.destroy();
425
+ } else if (this.handle) {
426
+ this.trackDeallocatedReferencedMemory("Texture");
427
+ }
428
+ this.destroyResource();
313
429
  this.handle = null;
314
430
  }
315
431
  createView(props) {
@@ -345,36 +461,6 @@ var init_webgpu_texture = __esm({
345
461
  });
346
462
  return { width: options.width, height: options.height };
347
463
  }
348
- copyImageData(options_) {
349
- const { width, height, depth } = this;
350
- const options = this._normalizeCopyImageDataOptions(options_);
351
- this.device.pushErrorScope("validation");
352
- this.device.handle.queue.writeTexture(
353
- // destination: GPUImageCopyTexture
354
- {
355
- // texture subresource
356
- texture: this.handle,
357
- mipLevel: options.mipLevel,
358
- aspect: options.aspect,
359
- // origin to write to
360
- origin: [options.x, options.y, options.z]
361
- },
362
- // data
363
- options.data,
364
- // dataLayout: GPUImageDataLayout
365
- {
366
- offset: options.byteOffset,
367
- bytesPerRow: options.bytesPerRow,
368
- rowsPerImage: options.rowsPerImage
369
- },
370
- // size: GPUExtent3D - extents of the content to write
371
- [width, height, depth]
372
- );
373
- this.device.popErrorScope((error) => {
374
- this.device.reportError(new Error(`copyImageData: ${error.message}`), this)();
375
- this.device.debug();
376
- });
377
- }
378
464
  generateMipmapsWebGL() {
379
465
  import_core4.log.warn(`${this}: generateMipmaps not supported in WebGPU`)();
380
466
  }
@@ -386,13 +472,16 @@ var init_webgpu_texture = __esm({
386
472
  };
387
473
  }
388
474
  readBuffer(options = {}, buffer) {
389
- const { x = 0, y = 0, z = 0, width = this.width, height = this.height, depthOrArrayLayers = this.depth, mipLevel = 0, aspect = "all" } = options;
390
- const layout = this.computeMemoryLayout(options);
475
+ const { x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect } = this._getSupportedColorReadOptions(options);
476
+ const layout = this.computeMemoryLayout({ x, y, z, width, height, depthOrArrayLayers, mipLevel });
391
477
  const { bytesPerRow, rowsPerImage, byteLength } = layout;
392
478
  const readBuffer = buffer || this.device.createBuffer({
393
479
  byteLength,
394
480
  usage: import_core4.Buffer.COPY_DST | import_core4.Buffer.MAP_READ
395
481
  });
482
+ if (readBuffer.byteLength < byteLength) {
483
+ throw new Error(`${this} readBuffer target is too small (${readBuffer.byteLength} < ${byteLength})`);
484
+ }
396
485
  const gpuReadBuffer = readBuffer.handle;
397
486
  const gpuDevice = this.device.handle;
398
487
  this.device.pushErrorScope("validation");
@@ -436,16 +525,15 @@ var init_webgpu_texture = __esm({
436
525
  buffer.destroy();
437
526
  return data.buffer;
438
527
  }
439
- writeBuffer(buffer, options = {}) {
440
- const { x = 0, y = 0, z = 0, width = this.width, height = this.height, depthOrArrayLayers = this.depth, mipLevel = 0, aspect = "all" } = options;
441
- const layout = this.computeMemoryLayout(options);
442
- const { bytesPerRow, rowsPerImage } = layout;
528
+ writeBuffer(buffer, options_ = {}) {
529
+ const options = this._normalizeTextureWriteOptions(options_);
530
+ const { x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect, byteOffset, bytesPerRow, rowsPerImage } = options;
443
531
  const gpuDevice = this.device.handle;
444
532
  this.device.pushErrorScope("validation");
445
533
  const commandEncoder = gpuDevice.createCommandEncoder();
446
534
  commandEncoder.copyBufferToTexture({
447
535
  buffer: buffer.handle,
448
- offset: 0,
536
+ offset: byteOffset,
449
537
  bytesPerRow,
450
538
  rowsPerImage
451
539
  }, {
@@ -461,33 +549,88 @@ var init_webgpu_texture = __esm({
461
549
  this.device.debug();
462
550
  });
463
551
  }
464
- writeData(data, options = {}) {
552
+ writeData(data, options_ = {}) {
465
553
  const device = this.device;
466
- const { x = 0, y = 0, z = 0, width = this.width, height = this.height, depthOrArrayLayers = this.depth, mipLevel = 0, aspect = "all" } = options;
467
- const layout = import_core4.textureFormatDecoder.computeMemoryLayout({
554
+ const options = this._normalizeTextureWriteOptions(options_);
555
+ const { x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect, byteOffset } = options;
556
+ const source = data;
557
+ const formatInfo = this.device.getTextureFormatInfo(this.format);
558
+ const packedSourceLayout = import_core4.textureFormatDecoder.computeMemoryLayout({
468
559
  format: this.format,
469
- width: this.width,
470
- height: this.height,
471
- depth: this.depth,
472
- byteAlignment: this.byteAlignment
560
+ width,
561
+ height,
562
+ depth: depthOrArrayLayers,
563
+ byteAlignment: 1
473
564
  });
474
- const { bytesPerRow, rowsPerImage } = layout;
565
+ const bytesPerRow = options_.bytesPerRow ?? packedSourceLayout.bytesPerRow;
566
+ const rowsPerImage = options_.rowsPerImage ?? packedSourceLayout.rowsPerImage;
567
+ let copyWidth = width;
568
+ let copyHeight = height;
569
+ if (formatInfo.compressed) {
570
+ const blockWidth = formatInfo.blockWidth || 1;
571
+ const blockHeight = formatInfo.blockHeight || 1;
572
+ copyWidth = Math.ceil(width / blockWidth) * blockWidth;
573
+ copyHeight = Math.ceil(height / blockHeight) * blockHeight;
574
+ }
475
575
  this.device.pushErrorScope("validation");
476
576
  device.handle.queue.writeTexture({
477
577
  texture: this.handle,
478
578
  mipLevel,
479
579
  aspect,
480
580
  origin: { x, y, z }
481
- }, data, {
482
- offset: 0,
581
+ }, source, {
582
+ offset: byteOffset,
483
583
  bytesPerRow,
484
584
  rowsPerImage
485
- }, { width, height, depthOrArrayLayers });
585
+ }, { width: copyWidth, height: copyHeight, depthOrArrayLayers });
486
586
  this.device.popErrorScope((error) => {
487
587
  this.device.reportError(new Error(`${this} writeData: ${error.message}`), this)();
488
588
  this.device.debug();
489
589
  });
490
590
  }
591
+ /**
592
+ * Internal-only hook for the cached CanvasContext/PresentationContext swapchain path.
593
+ * Rebinds this handle-backed texture wrapper to the current per-frame canvas texture
594
+ * without allocating a new luma.gl Texture or TextureView wrapper.
595
+ */
596
+ _reinitialize(handle, props) {
597
+ const nextWidth = (props == null ? void 0 : props.width) ?? handle.width ?? this.width;
598
+ const nextHeight = (props == null ? void 0 : props.height) ?? handle.height ?? this.height;
599
+ const nextDepth = (props == null ? void 0 : props.depth) ?? this.depth;
600
+ const nextFormat = (props == null ? void 0 : props.format) ?? this.format;
601
+ const allocationMayHaveChanged = nextWidth !== this.width || nextHeight !== this.height || nextDepth !== this.depth || nextFormat !== this.format;
602
+ handle.label ||= this.id;
603
+ this.handle = handle;
604
+ this.width = nextWidth;
605
+ this.height = nextHeight;
606
+ if ((props == null ? void 0 : props.depth) !== void 0) {
607
+ this.depth = nextDepth;
608
+ }
609
+ if ((props == null ? void 0 : props.format) !== void 0) {
610
+ this.format = nextFormat;
611
+ }
612
+ this.props.handle = handle;
613
+ if ((props == null ? void 0 : props.width) !== void 0) {
614
+ this.props.width = props.width;
615
+ }
616
+ if ((props == null ? void 0 : props.height) !== void 0) {
617
+ this.props.height = props.height;
618
+ }
619
+ if ((props == null ? void 0 : props.depth) !== void 0) {
620
+ this.props.depth = props.depth;
621
+ }
622
+ if ((props == null ? void 0 : props.format) !== void 0) {
623
+ this.props.format = props.format;
624
+ }
625
+ if (allocationMayHaveChanged) {
626
+ const nextAllocation = this.getAllocatedByteLength();
627
+ if (nextAllocation !== this._allocatedByteLength) {
628
+ this._allocatedByteLength = nextAllocation;
629
+ this.trackReferencedMemory(nextAllocation, "Texture");
630
+ }
631
+ }
632
+ this.view._reinitialize(this);
633
+ }
491
634
  };
492
635
  }
493
636
  });
@@ -801,14 +944,12 @@ var init_webgpu_parameters = __esm({
801
944
  function getBindGroup(device, bindGroupLayout, shaderLayout, bindings) {
802
945
  const entries = getBindGroupEntries(bindings, shaderLayout);
803
946
  device.pushErrorScope("validation");
804
- const bindGroup = device.createBindGroup({
947
+ const bindGroup = device.handle.createBindGroup({
805
948
  layout: bindGroupLayout,
806
949
  entries
807
950
  });
808
- device.popErrorScope().then((error) => {
809
- if (error) {
810
- import_core8.log.error(`bindGroup creation: ${error.message}`, bindGroup)();
811
- }
951
+ device.popErrorScope((error) => {
952
+ import_core8.log.error(`bindGroup creation: ${error.message}`, bindGroup)();
812
953
  });
813
954
  return bindGroup;
814
955
  }
@@ -822,28 +963,28 @@ function getShaderLayoutBinding(shaderLayout, bindingName, options) {
822
963
  function getBindGroupEntries(bindings, shaderLayout) {
823
964
  const entries = [];
824
965
  for (const [bindingName, value] of Object.entries(bindings)) {
825
- let bindingLayout = getShaderLayoutBinding(shaderLayout, bindingName);
826
- if (bindingLayout) {
827
- const entry = getBindGroupEntry(value, bindingLayout.location);
966
+ const exactBindingLayout = shaderLayout.bindings.find((binding) => binding.name === bindingName);
967
+ const bindingLayout = exactBindingLayout || getShaderLayoutBinding(shaderLayout, bindingName);
968
+ const isShadowedAlias = !exactBindingLayout && bindingLayout ? bindingLayout.name in bindings : false;
969
+ if (!isShadowedAlias) {
970
+ const entry = bindingLayout ? getBindGroupEntry(value, bindingLayout.location, void 0, bindingName) : null;
828
971
  if (entry) {
829
972
  entries.push(entry);
830
973
  }
831
- }
832
- if (value instanceof import_core8.Texture) {
833
- bindingLayout = getShaderLayoutBinding(shaderLayout, `${bindingName}Sampler`, {
834
- ignoreWarnings: true
835
- });
836
- if (bindingLayout) {
837
- const entry = getBindGroupEntry(value, bindingLayout.location, { sampler: true });
838
- if (entry) {
839
- entries.push(entry);
974
+ if (value instanceof import_core8.Texture) {
975
+ const samplerBindingLayout = getShaderLayoutBinding(shaderLayout, `${bindingName}Sampler`, {
976
+ ignoreWarnings: true
977
+ });
978
+ const samplerEntry = samplerBindingLayout ? getBindGroupEntry(value, samplerBindingLayout.location, { sampler: true }, bindingName) : null;
979
+ if (samplerEntry) {
980
+ entries.push(samplerEntry);
840
981
  }
841
982
  }
842
983
  }
843
984
  }
844
985
  return entries;
845
986
  }
846
- function getBindGroupEntry(binding, index, options) {
987
+ function getBindGroupEntry(binding, index, options, bindingName = "unknown") {
847
988
  if (binding instanceof import_core8.Buffer) {
848
989
  return {
849
990
  binding: index,
@@ -858,6 +999,12 @@ function getBindGroupEntry(binding, index, options) {
858
999
  resource: binding.handle
859
1000
  };
860
1001
  }
1002
+ if (binding instanceof import_core8.TextureView) {
1003
+ return {
1004
+ binding: index,
1005
+ resource: binding.handle
1006
+ };
1007
+ }
861
1008
  if (binding instanceof import_core8.Texture) {
862
1009
  if (options == null ? void 0 : options.sampler) {
863
1010
  return {
@@ -870,7 +1017,7 @@ function getBindGroupEntry(binding, index, options) {
870
1017
  resource: binding.view.handle
871
1018
  };
872
1019
  }
873
- import_core8.log.warn(`invalid binding ${name}`, binding);
1020
+ import_core8.log.warn(`invalid binding ${bindingName}`, binding);
874
1021
  return null;
875
1022
  }
876
1023
  var import_core8;
@@ -952,17 +1099,17 @@ function getVertexBufferLayout(shaderLayout, bufferLayout) {
952
1099
  });
953
1100
  return vertexBufferLayouts;
954
1101
  }
955
- function findAttributeLayout(shaderLayout, name2, attributeNames) {
956
- const attribute = shaderLayout.attributes.find((attribute_) => attribute_.name === name2);
1102
+ function findAttributeLayout(shaderLayout, name, attributeNames) {
1103
+ const attribute = shaderLayout.attributes.find((attribute_) => attribute_.name === name);
957
1104
  if (!attribute) {
958
- import_core9.log.warn(`Supplied attribute not present in shader layout: ${name2}`)();
1105
+ import_core9.log.warn(`Supplied attribute not present in shader layout: ${name}`)();
959
1106
  return null;
960
1107
  }
961
1108
  if (attributeNames) {
962
- if (attributeNames.has(name2)) {
963
- throw new Error(`Found multiple entries for attribute: ${name2}`);
1109
+ if (attributeNames.has(name)) {
1110
+ throw new Error(`Found multiple entries for attribute: ${name}`);
964
1111
  }
965
- attributeNames.add(name2);
1112
+ attributeNames.add(name);
966
1113
  }
967
1114
  return attribute;
968
1115
  }
@@ -975,7 +1122,7 @@ var init_get_vertex_buffer_layout = __esm({
975
1122
  });
976
1123
 
977
1124
  // dist/adapter/resources/webgpu-render-pipeline.js
978
- var import_core10, WebGPURenderPipeline;
1125
+ var import_core10, EMPTY_BINDINGS, WebGPURenderPipeline;
979
1126
  var init_webgpu_render_pipeline = __esm({
980
1127
  "dist/adapter/resources/webgpu-render-pipeline.js"() {
981
1128
  "use strict";
@@ -984,13 +1131,15 @@ var init_webgpu_render_pipeline = __esm({
984
1131
  init_convert_texture_format();
985
1132
  init_get_bind_group();
986
1133
  init_get_vertex_buffer_layout();
1134
+ EMPTY_BINDINGS = {};
987
1135
  WebGPURenderPipeline = class extends import_core10.RenderPipeline {
988
1136
  device;
989
1137
  handle;
990
1138
  vs;
991
1139
  fs = null;
992
- /** For internal use to create BindGroups */
1140
+ /** Compatibility path for direct pipeline.setBindings() usage */
993
1141
  _bindings;
1142
+ /** For internal use to create BindGroups */
994
1143
  _bindGroupLayout = null;
995
1144
  _bindGroup = null;
996
1145
  get [Symbol.toStringTag]() {
@@ -1016,26 +1165,36 @@ var init_webgpu_render_pipeline = __esm({
1016
1165
  this.handle.label = this.props.id;
1017
1166
  this.vs = props.vs;
1018
1167
  this.fs = props.fs;
1019
- this._bindings = { ...this.props.bindings };
1168
+ this._bindings = props.bindings || EMPTY_BINDINGS;
1020
1169
  }
1021
1170
  destroy() {
1022
1171
  this.handle = null;
1023
1172
  }
1024
1173
  /**
1025
- * @todo Use renderpass.setBindings() ?
1026
- * @todo Do we want to expose BindGroups in the API and remove this?
1174
+ * Compatibility shim for code paths that still set bindings on the pipeline.
1175
+ * The shared-model path passes bindings per draw and does not rely on this state.
1027
1176
  */
1028
1177
  setBindings(bindings) {
1029
- for (const [name2, binding] of Object.entries(bindings)) {
1030
- if (this._bindings[name2] !== binding) {
1031
- this._bindGroup = null;
1178
+ let bindingsChanged = false;
1179
+ for (const [name, binding] of Object.entries(bindings)) {
1180
+ if (this._bindings[name] !== binding) {
1181
+ if (!bindingsChanged) {
1182
+ if (this._bindings === this.props.bindings || this._bindings === EMPTY_BINDINGS) {
1183
+ this._bindings = { ...this._bindings };
1184
+ }
1185
+ bindingsChanged = true;
1186
+ }
1187
+ this._bindings[name] = binding;
1032
1188
  }
1033
1189
  }
1034
- Object.assign(this._bindings, bindings);
1190
+ if (bindingsChanged) {
1191
+ this._bindGroup = null;
1192
+ }
1035
1193
  }
1036
1194
  /** @todo - should this be moved to renderpass? */
1037
1195
  draw(options) {
1038
1196
  const webgpuRenderPass = options.renderPass;
1197
+ const instanceCount = options.instanceCount && options.instanceCount > 0 ? options.instanceCount : 1;
1039
1198
  this.device.pushErrorScope("validation");
1040
1199
  webgpuRenderPass.handle.setPipeline(this.handle);
1041
1200
  this.device.popErrorScope((error) => {
@@ -1043,31 +1202,29 @@ var init_webgpu_render_pipeline = __esm({
1043
1202
  "${error.message}"`), this)();
1044
1203
  this.device.debug();
1045
1204
  });
1046
- const bindGroup = this._getBindGroup();
1205
+ const bindGroup = this._getBindGroup(options.bindings);
1047
1206
  if (bindGroup) {
1048
1207
  webgpuRenderPass.handle.setBindGroup(0, bindGroup);
1049
1208
  }
1050
1209
  options.vertexArray.bindBeforeRender(options.renderPass);
1051
1210
  if (options.indexCount) {
1052
- webgpuRenderPass.handle.drawIndexed(options.indexCount, options.instanceCount, options.firstIndex, options.baseVertex, options.firstInstance);
1211
+ webgpuRenderPass.handle.drawIndexed(options.indexCount, instanceCount, options.firstIndex || 0, options.baseVertex || 0, options.firstInstance || 0);
1053
1212
  } else {
1054
- webgpuRenderPass.handle.draw(
1055
- options.vertexCount || 0,
1056
- options.instanceCount || 1,
1057
- // If 0, nothing will be drawn
1058
- options.firstInstance
1059
- );
1213
+ webgpuRenderPass.handle.draw(options.vertexCount || 0, instanceCount, options.firstVertex || 0, options.firstInstance || 0);
1060
1214
  }
1061
1215
  options.vertexArray.unbindAfterRender(options.renderPass);
1062
1216
  return true;
1063
1217
  }
1064
1218
  /** Return a bind group created by setBindings */
1065
- _getBindGroup() {
1219
+ _getBindGroup(bindings) {
1066
1220
  if (this.shaderLayout.bindings.length === 0) {
1067
1221
  return null;
1068
1222
  }
1069
1223
  this._bindGroupLayout = this._bindGroupLayout || this.handle.getBindGroupLayout(0);
1070
- this._bindGroup = this._bindGroup || getBindGroup(this.device.handle, this._bindGroupLayout, this.shaderLayout, this._bindings);
1224
+ if (bindings) {
1225
+ return getBindGroup(this.device, this._bindGroupLayout, this.shaderLayout, bindings);
1226
+ }
1227
+ this._bindGroup = this._bindGroup || getBindGroup(this.device, this._bindGroupLayout, this.shaderLayout, this._bindings);
1071
1228
  return this._bindGroup;
1072
1229
  }
1073
1230
  /**
@@ -1134,17 +1291,33 @@ var init_webgpu_framebuffer = __esm({
1134
1291
  }
1135
1292
  updateAttachments() {
1136
1293
  }
1294
+ /**
1295
+ * Internal-only hook for the cached CanvasContext/PresentationContext swapchain path.
1296
+ * Rebinds the long-lived default framebuffer wrapper to the current per-frame color view
1297
+ * and optional depth attachment without allocating a new luma.gl Framebuffer object.
1298
+ */
1299
+ _reinitialize(colorAttachment, depthStencilAttachment) {
1300
+ this.colorAttachments[0] = colorAttachment;
1301
+ this.depthStencilAttachment = depthStencilAttachment;
1302
+ this.width = colorAttachment.texture.width;
1303
+ this.height = colorAttachment.texture.height;
1304
+ this.props.width = this.width;
1305
+ this.props.height = this.height;
1306
+ this.props.colorAttachments = [colorAttachment.texture];
1307
+ this.props.depthStencilAttachment = (depthStencilAttachment == null ? void 0 : depthStencilAttachment.texture) || null;
1308
+ }
1137
1309
  };
1138
1310
  }
1139
1311
  });
1140
1312
 
1141
1313
  // dist/adapter/resources/webgpu-compute-pipeline.js
1142
- var import_core12, WebGPUComputePipeline;
1314
+ var import_core12, EMPTY_BINDINGS2, WebGPUComputePipeline;
1143
1315
  var init_webgpu_compute_pipeline = __esm({
1144
1316
  "dist/adapter/resources/webgpu-compute-pipeline.js"() {
1145
1317
  "use strict";
1146
1318
  import_core12 = require("@luma.gl/core");
1147
1319
  init_get_bind_group();
1320
+ EMPTY_BINDINGS2 = {};
1148
1321
  WebGPUComputePipeline = class extends import_core12.ComputePipeline {
1149
1322
  device;
1150
1323
  handle;
@@ -1152,7 +1325,7 @@ var init_webgpu_compute_pipeline = __esm({
1152
1325
  _bindGroupLayout = null;
1153
1326
  _bindGroup = null;
1154
1327
  /** For internal use to create BindGroups */
1155
- _bindings = {};
1328
+ _bindings;
1156
1329
  constructor(device, props) {
1157
1330
  super(device, props);
1158
1331
  this.device = device;
@@ -1166,18 +1339,33 @@ var init_webgpu_compute_pipeline = __esm({
1166
1339
  },
1167
1340
  layout: "auto"
1168
1341
  });
1342
+ this._bindings = EMPTY_BINDINGS2;
1169
1343
  }
1170
1344
  /**
1171
1345
  * @todo Use renderpass.setBindings() ?
1172
1346
  * @todo Do we want to expose BindGroups in the API and remove this?
1173
1347
  */
1174
1348
  setBindings(bindings) {
1175
- Object.assign(this._bindings, bindings);
1349
+ let bindingsChanged = false;
1350
+ for (const [name, binding] of Object.entries(bindings)) {
1351
+ if (this._bindings[name] !== binding) {
1352
+ if (!bindingsChanged) {
1353
+ if (this._bindings === EMPTY_BINDINGS2) {
1354
+ this._bindings = {};
1355
+ }
1356
+ bindingsChanged = true;
1357
+ }
1358
+ this._bindings[name] = binding;
1359
+ }
1360
+ }
1361
+ if (bindingsChanged) {
1362
+ this._bindGroup = null;
1363
+ }
1176
1364
  }
1177
1365
  /** Return a bind group created by setBindings */
1178
1366
  _getBindGroup() {
1179
1367
  this._bindGroupLayout = this._bindGroupLayout || this.handle.getBindGroupLayout(0);
1180
- this._bindGroup = this._bindGroup || getBindGroup(this.device.handle, this._bindGroupLayout, this.shaderLayout, this._bindings);
1368
+ this._bindGroup = this._bindGroup || getBindGroup(this.device, this._bindGroupLayout, this.shaderLayout, this._bindings);
1181
1369
  return this._bindGroup;
1182
1370
  }
1183
1371
  };
@@ -1193,7 +1381,7 @@ var init_webgpu_vertex_array = __esm({
1193
1381
  import_env = require("@probe.gl/env");
1194
1382
  WebGPUVertexArray = class extends import_core13.VertexArray {
1195
1383
  get [Symbol.toStringTag]() {
1196
- return "WebGPUVertexArray";
1384
+ return "VertexArray";
1197
1385
  }
1198
1386
  device;
1199
1387
  /** Vertex Array is just a helper class under WebGPU */
@@ -1256,10 +1444,13 @@ var init_webgpu_canvas_context = __esm({
1256
1444
  "use strict";
1257
1445
  import_core14 = require("@luma.gl/core");
1258
1446
  init_webgpu_framebuffer();
1447
+ init_cpu_hotspot_profiler();
1259
1448
  WebGPUCanvasContext = class extends import_core14.CanvasContext {
1260
1449
  device;
1261
1450
  handle;
1451
+ colorAttachment = null;
1262
1452
  depthStencilAttachment = null;
1453
+ framebuffer = null;
1263
1454
  get [Symbol.toStringTag]() {
1264
1455
  return "WebGPUCanvasContext";
1265
1456
  }
@@ -1273,9 +1464,22 @@ var init_webgpu_canvas_context = __esm({
1273
1464
  this.handle = context;
1274
1465
  this._setAutoCreatedCanvasId(`${this.device.id}-canvas`);
1275
1466
  this._configureDevice();
1467
+ this._startObservers();
1276
1468
  }
1277
1469
  /** Destroy any textures produced while configured and remove the context configuration. */
1278
1470
  destroy() {
1471
+ if (this.framebuffer) {
1472
+ this.framebuffer.destroy();
1473
+ this.framebuffer = null;
1474
+ }
1475
+ if (this.colorAttachment) {
1476
+ this.colorAttachment.destroy();
1477
+ this.colorAttachment = null;
1478
+ }
1479
+ if (this.depthStencilAttachment) {
1480
+ this.depthStencilAttachment.destroy();
1481
+ this.depthStencilAttachment = null;
1482
+ }
1279
1483
  this.handle.unconfigure();
1280
1484
  super.destroy();
1281
1485
  }
@@ -1294,41 +1498,78 @@ var init_webgpu_canvas_context = __esm({
1294
1498
  colorSpace: this.props.colorSpace,
1295
1499
  alphaMode: this.props.alphaMode
1296
1500
  });
1501
+ this._createDepthStencilAttachment(this.device.preferredDepthFormat);
1297
1502
  }
1298
1503
  /** Update framebuffer with properly resized "swap chain" texture views */
1299
1504
  _getCurrentFramebuffer(options = {
1300
1505
  depthStencilFormat: "depth24plus"
1301
1506
  }) {
1302
- const currentColorAttachment = this._getCurrentTexture();
1303
- if (currentColorAttachment.width !== this.drawingBufferWidth || currentColorAttachment.height !== this.drawingBufferHeight) {
1304
- const [oldWidth, oldHeight] = this.getDrawingBufferSize();
1305
- this.drawingBufferWidth = currentColorAttachment.width;
1306
- this.drawingBufferHeight = currentColorAttachment.height;
1307
- import_core14.log.log(1, `${this}: Resized to compensate for initial canvas size mismatch ${oldWidth}x${oldHeight} => ${this.drawingBufferWidth}x${this.drawingBufferHeight}px`)();
1308
- }
1309
- if (options == null ? void 0 : options.depthStencilFormat) {
1310
- this._createDepthStencilAttachment(options == null ? void 0 : options.depthStencilFormat);
1311
- }
1312
- return new WebGPUFramebuffer(this.device, {
1313
- colorAttachments: [currentColorAttachment],
1314
- depthStencilAttachment: this.depthStencilAttachment
1315
- });
1507
+ var _a;
1508
+ const profiler = getCpuHotspotProfiler(this.device);
1509
+ const startTime = profiler ? getTimestamp() : 0;
1510
+ if (profiler) {
1511
+ profiler.framebufferAcquireCount = (profiler.framebufferAcquireCount || 0) + 1;
1512
+ profiler.activeDefaultFramebufferAcquireDepth = (profiler.activeDefaultFramebufferAcquireDepth || 0) + 1;
1513
+ }
1514
+ try {
1515
+ const currentColorAttachment = this._getCurrentTexture();
1516
+ if (currentColorAttachment.width !== this.drawingBufferWidth || currentColorAttachment.height !== this.drawingBufferHeight) {
1517
+ const [oldWidth, oldHeight] = this.getDrawingBufferSize();
1518
+ this.drawingBufferWidth = currentColorAttachment.width;
1519
+ this.drawingBufferHeight = currentColorAttachment.height;
1520
+ import_core14.log.log(1, `${this}: Resized to compensate for initial canvas size mismatch ${oldWidth}x${oldHeight} => ${this.drawingBufferWidth}x${this.drawingBufferHeight}px`)();
1521
+ }
1522
+ if (options == null ? void 0 : options.depthStencilFormat) {
1523
+ this._createDepthStencilAttachment(options == null ? void 0 : options.depthStencilFormat);
1524
+ }
1525
+ this.framebuffer ||= new WebGPUFramebuffer(this.device, {
1526
+ id: `${this.id}#framebuffer`,
1527
+ colorAttachments: [currentColorAttachment],
1528
+ depthStencilAttachment: null
1529
+ });
1530
+ this.framebuffer._reinitialize(currentColorAttachment.view, (options == null ? void 0 : options.depthStencilFormat) ? ((_a = this.depthStencilAttachment) == null ? void 0 : _a.view) || null : null);
1531
+ return this.framebuffer;
1532
+ } finally {
1533
+ if (profiler) {
1534
+ profiler.activeDefaultFramebufferAcquireDepth = (profiler.activeDefaultFramebufferAcquireDepth || 1) - 1;
1535
+ profiler.framebufferAcquireTimeMs = (profiler.framebufferAcquireTimeMs || 0) + (getTimestamp() - startTime);
1536
+ }
1537
+ }
1316
1538
  }
1317
1539
  // PRIMARY METHODS
1318
1540
  /** Wrap the current canvas context texture in a luma.gl texture */
1319
1541
  _getCurrentTexture() {
1542
+ const profiler = getCpuHotspotProfiler(this.device);
1543
+ const currentTextureStartTime = profiler ? getTimestamp() : 0;
1320
1544
  const handle = this.handle.getCurrentTexture();
1321
- return this.device.createTexture({
1322
- id: `${this.id}#color-texture`,
1545
+ if (profiler) {
1546
+ profiler.currentTextureAcquireCount = (profiler.currentTextureAcquireCount || 0) + 1;
1547
+ profiler.currentTextureAcquireTimeMs = (profiler.currentTextureAcquireTimeMs || 0) + (getTimestamp() - currentTextureStartTime);
1548
+ }
1549
+ if (!this.colorAttachment) {
1550
+ this.colorAttachment = this.device.createTexture({
1551
+ id: `${this.id}#color-texture`,
1552
+ handle,
1553
+ format: this.device.preferredColorFormat,
1554
+ width: handle.width,
1555
+ height: handle.height
1556
+ });
1557
+ return this.colorAttachment;
1558
+ }
1559
+ this.colorAttachment._reinitialize(handle, {
1323
1560
  handle,
1324
1561
  format: this.device.preferredColorFormat,
1325
1562
  width: handle.width,
1326
1563
  height: handle.height
1327
1564
  });
1565
+ return this.colorAttachment;
1328
1566
  }
1329
1567
  /** We build render targets on demand (i.e. not when size changes but when about to render) */
1330
1568
  _createDepthStencilAttachment(depthStencilFormat) {
1331
- if (!this.depthStencilAttachment) {
1569
+ var _a;
1570
+ const needsNewDepthStencilAttachment = !this.depthStencilAttachment || this.depthStencilAttachment.width !== this.drawingBufferWidth || this.depthStencilAttachment.height !== this.drawingBufferHeight || this.depthStencilAttachment.format !== depthStencilFormat;
1571
+ if (needsNewDepthStencilAttachment) {
1572
+ (_a = this.depthStencilAttachment) == null ? void 0 : _a.destroy();
1332
1573
  this.depthStencilAttachment = this.device.createTexture({
1333
1574
  id: `${this.id}#depth-stencil-texture`,
1334
1575
  usage: import_core14.Texture.RENDER_ATTACHMENT,
@@ -1343,17 +1584,157 @@ var init_webgpu_canvas_context = __esm({
1343
1584
  }
1344
1585
  });
1345
1586
 
1587
+ // dist/adapter/webgpu-presentation-context.js
1588
+ var import_core15, WebGPUPresentationContext;
1589
+ var init_webgpu_presentation_context = __esm({
1590
+ "dist/adapter/webgpu-presentation-context.js"() {
1591
+ "use strict";
1592
+ import_core15 = require("@luma.gl/core");
1593
+ init_webgpu_framebuffer();
1594
+ init_cpu_hotspot_profiler();
1595
+ WebGPUPresentationContext = class extends import_core15.PresentationContext {
1596
+ device;
1597
+ handle;
1598
+ colorAttachment = null;
1599
+ depthStencilAttachment = null;
1600
+ framebuffer = null;
1601
+ get [Symbol.toStringTag]() {
1602
+ return "WebGPUPresentationContext";
1603
+ }
1604
+ constructor(device, props = {}) {
1605
+ super(props);
1606
+ const contextLabel = `${this[Symbol.toStringTag]}(${this.id})`;
1607
+ const context = this.canvas.getContext("webgpu");
1608
+ if (!context) {
1609
+ throw new Error(`${contextLabel}: Failed to create WebGPU presentation context`);
1610
+ }
1611
+ this.device = device;
1612
+ this.handle = context;
1613
+ this._setAutoCreatedCanvasId(`${this.device.id}-presentation-canvas`);
1614
+ this._configureDevice();
1615
+ this._startObservers();
1616
+ }
1617
+ destroy() {
1618
+ if (this.framebuffer) {
1619
+ this.framebuffer.destroy();
1620
+ this.framebuffer = null;
1621
+ }
1622
+ if (this.colorAttachment) {
1623
+ this.colorAttachment.destroy();
1624
+ this.colorAttachment = null;
1625
+ }
1626
+ if (this.depthStencilAttachment) {
1627
+ this.depthStencilAttachment.destroy();
1628
+ this.depthStencilAttachment = null;
1629
+ }
1630
+ this.handle.unconfigure();
1631
+ super.destroy();
1632
+ }
1633
+ present() {
1634
+ this.device.submit();
1635
+ }
1636
+ _configureDevice() {
1637
+ if (this.depthStencilAttachment) {
1638
+ this.depthStencilAttachment.destroy();
1639
+ this.depthStencilAttachment = null;
1640
+ }
1641
+ this.handle.configure({
1642
+ device: this.device.handle,
1643
+ format: this.device.preferredColorFormat,
1644
+ colorSpace: this.props.colorSpace,
1645
+ alphaMode: this.props.alphaMode
1646
+ });
1647
+ this._createDepthStencilAttachment(this.device.preferredDepthFormat);
1648
+ }
1649
+ _getCurrentFramebuffer(options = {
1650
+ depthStencilFormat: "depth24plus"
1651
+ }) {
1652
+ var _a;
1653
+ const profiler = getCpuHotspotProfiler(this.device);
1654
+ const startTime = profiler ? getTimestamp() : 0;
1655
+ if (profiler) {
1656
+ profiler.framebufferAcquireCount = (profiler.framebufferAcquireCount || 0) + 1;
1657
+ }
1658
+ try {
1659
+ const currentColorAttachment = this._getCurrentTexture();
1660
+ if (currentColorAttachment.width !== this.drawingBufferWidth || currentColorAttachment.height !== this.drawingBufferHeight) {
1661
+ const [oldWidth, oldHeight] = this.getDrawingBufferSize();
1662
+ this.drawingBufferWidth = currentColorAttachment.width;
1663
+ this.drawingBufferHeight = currentColorAttachment.height;
1664
+ import_core15.log.log(1, `${this[Symbol.toStringTag]}(${this.id}): Resized to compensate for initial canvas size mismatch ${oldWidth}x${oldHeight} => ${this.drawingBufferWidth}x${this.drawingBufferHeight}px`)();
1665
+ }
1666
+ if (options == null ? void 0 : options.depthStencilFormat) {
1667
+ this._createDepthStencilAttachment(options.depthStencilFormat);
1668
+ }
1669
+ this.framebuffer ||= new WebGPUFramebuffer(this.device, {
1670
+ id: `${this.id}#framebuffer`,
1671
+ colorAttachments: [currentColorAttachment],
1672
+ depthStencilAttachment: null
1673
+ });
1674
+ this.framebuffer._reinitialize(currentColorAttachment.view, (options == null ? void 0 : options.depthStencilFormat) ? ((_a = this.depthStencilAttachment) == null ? void 0 : _a.view) || null : null);
1675
+ return this.framebuffer;
1676
+ } finally {
1677
+ if (profiler) {
1678
+ profiler.framebufferAcquireTimeMs = (profiler.framebufferAcquireTimeMs || 0) + (getTimestamp() - startTime);
1679
+ }
1680
+ }
1681
+ }
1682
+ _getCurrentTexture() {
1683
+ const profiler = getCpuHotspotProfiler(this.device);
1684
+ const currentTextureStartTime = profiler ? getTimestamp() : 0;
1685
+ const handle = this.handle.getCurrentTexture();
1686
+ if (profiler) {
1687
+ profiler.currentTextureAcquireCount = (profiler.currentTextureAcquireCount || 0) + 1;
1688
+ profiler.currentTextureAcquireTimeMs = (profiler.currentTextureAcquireTimeMs || 0) + (getTimestamp() - currentTextureStartTime);
1689
+ }
1690
+ if (!this.colorAttachment) {
1691
+ this.colorAttachment = this.device.createTexture({
1692
+ id: `${this.id}#color-texture`,
1693
+ handle,
1694
+ format: this.device.preferredColorFormat,
1695
+ width: handle.width,
1696
+ height: handle.height
1697
+ });
1698
+ return this.colorAttachment;
1699
+ }
1700
+ this.colorAttachment._reinitialize(handle, {
1701
+ handle,
1702
+ format: this.device.preferredColorFormat,
1703
+ width: handle.width,
1704
+ height: handle.height
1705
+ });
1706
+ return this.colorAttachment;
1707
+ }
1708
+ _createDepthStencilAttachment(depthStencilFormat) {
1709
+ var _a;
1710
+ const needsNewDepthStencilAttachment = !this.depthStencilAttachment || this.depthStencilAttachment.width !== this.drawingBufferWidth || this.depthStencilAttachment.height !== this.drawingBufferHeight || this.depthStencilAttachment.format !== depthStencilFormat;
1711
+ if (needsNewDepthStencilAttachment) {
1712
+ (_a = this.depthStencilAttachment) == null ? void 0 : _a.destroy();
1713
+ this.depthStencilAttachment = this.device.createTexture({
1714
+ id: `${this.id}#depth-stencil-texture`,
1715
+ usage: import_core15.Texture.RENDER_ATTACHMENT,
1716
+ format: depthStencilFormat,
1717
+ width: this.drawingBufferWidth,
1718
+ height: this.drawingBufferHeight
1719
+ });
1720
+ }
1721
+ return this.depthStencilAttachment;
1722
+ }
1723
+ };
1724
+ }
1725
+ });
1726
+
1346
1727
  // dist/adapter/resources/webgpu-command-buffer.js
1347
- var import_core15, WebGPUCommandBuffer;
1728
+ var import_core16, WebGPUCommandBuffer;
1348
1729
  var init_webgpu_command_buffer = __esm({
1349
1730
  "dist/adapter/resources/webgpu-command-buffer.js"() {
1350
1731
  "use strict";
1351
- import_core15 = require("@luma.gl/core");
1352
- WebGPUCommandBuffer = class extends import_core15.CommandBuffer {
1732
+ import_core16 = require("@luma.gl/core");
1733
+ WebGPUCommandBuffer = class extends import_core16.CommandBuffer {
1353
1734
  device;
1354
1735
  handle;
1355
1736
  constructor(commandEncoder, props) {
1356
- super(commandEncoder.device, {});
1737
+ super(commandEncoder.device, props);
1357
1738
  this.device = commandEncoder.device;
1358
1739
  this.handle = this.props.handle || commandEncoder.handle.finish({
1359
1740
  label: (props == null ? void 0 : props.id) || "unnamed-command-buffer"
@@ -1367,52 +1748,85 @@ var init_webgpu_command_buffer = __esm({
1367
1748
  function convertColor(color) {
1368
1749
  return { r: color[0], g: color[1], b: color[2], a: color[3] };
1369
1750
  }
1370
- var import_core16, WebGPURenderPass;
1751
+ var import_core17, WebGPURenderPass;
1371
1752
  var init_webgpu_render_pass = __esm({
1372
1753
  "dist/adapter/resources/webgpu-render-pass.js"() {
1373
1754
  "use strict";
1374
- import_core16 = require("@luma.gl/core");
1375
- WebGPURenderPass = class extends import_core16.RenderPass {
1755
+ import_core17 = require("@luma.gl/core");
1756
+ init_cpu_hotspot_profiler();
1757
+ WebGPURenderPass = class extends import_core17.RenderPass {
1376
1758
  device;
1377
1759
  handle;
1760
+ framebuffer;
1378
1761
  /** Active pipeline */
1379
1762
  pipeline = null;
1763
+ /** Latest bindings applied to this pass */
1764
+ bindings = {};
1380
1765
  constructor(device, props = {}) {
1381
1766
  super(device, props);
1382
1767
  this.device = device;
1383
- const framebuffer = props.framebuffer || device.getCanvasContext().getCurrentFramebuffer();
1384
- const renderPassDescriptor = this.getRenderPassDescriptor(framebuffer);
1385
- const webgpuQuerySet = props.timestampQuerySet;
1386
- if (webgpuQuerySet) {
1387
- renderPassDescriptor.occlusionQuerySet = webgpuQuerySet.handle;
1388
- }
1389
- if (device.features.has("timestamp-query")) {
1390
- const webgpuTSQuerySet = props.timestampQuerySet;
1391
- renderPassDescriptor.timestampWrites = webgpuTSQuerySet ? {
1392
- querySet: webgpuTSQuerySet.handle,
1393
- beginningOfPassWriteIndex: props.beginTimestampIndex,
1394
- endOfPassWriteIndex: props.endTimestampIndex
1395
- } : void 0;
1396
- }
1397
- if (!device.commandEncoder) {
1398
- throw new Error("commandEncoder not available");
1768
+ const { props: renderPassProps } = this;
1769
+ this.framebuffer = renderPassProps.framebuffer || device.getCanvasContext().getCurrentFramebuffer();
1770
+ const profiler = getCpuHotspotProfiler(this.device);
1771
+ if (profiler) {
1772
+ const counterName = renderPassProps.framebuffer ? "explicitFramebufferRenderPassCount" : "defaultFramebufferRenderPassCount";
1773
+ profiler[counterName] = (profiler[counterName] || 0) + 1;
1399
1774
  }
1400
- this.device.pushErrorScope("validation");
1401
- this.handle = this.props.handle || device.commandEncoder.handle.beginRenderPass(renderPassDescriptor);
1402
- this.device.popErrorScope((error) => {
1403
- this.device.reportError(new Error(`${this} creation failed:
1775
+ const startTime = profiler ? getTimestamp() : 0;
1776
+ try {
1777
+ const descriptorAssemblyStartTime = profiler ? getTimestamp() : 0;
1778
+ const renderPassDescriptor = this.getRenderPassDescriptor(this.framebuffer);
1779
+ if (renderPassProps.occlusionQuerySet) {
1780
+ renderPassDescriptor.occlusionQuerySet = renderPassProps.occlusionQuerySet.handle;
1781
+ }
1782
+ if (renderPassProps.timestampQuerySet) {
1783
+ const webgpuTSQuerySet = renderPassProps.timestampQuerySet;
1784
+ webgpuTSQuerySet == null ? void 0 : webgpuTSQuerySet._invalidateResults();
1785
+ renderPassDescriptor.timestampWrites = webgpuTSQuerySet ? {
1786
+ querySet: webgpuTSQuerySet.handle,
1787
+ beginningOfPassWriteIndex: renderPassProps.beginTimestampIndex,
1788
+ endOfPassWriteIndex: renderPassProps.endTimestampIndex
1789
+ } : void 0;
1790
+ }
1791
+ if (profiler) {
1792
+ profiler.renderPassDescriptorAssemblyCount = (profiler.renderPassDescriptorAssemblyCount || 0) + 1;
1793
+ profiler.renderPassDescriptorAssemblyTimeMs = (profiler.renderPassDescriptorAssemblyTimeMs || 0) + (getTimestamp() - descriptorAssemblyStartTime);
1794
+ }
1795
+ if (!device.commandEncoder) {
1796
+ throw new Error("commandEncoder not available");
1797
+ }
1798
+ this.device.pushErrorScope("validation");
1799
+ const beginRenderPassStartTime = profiler ? getTimestamp() : 0;
1800
+ this.handle = this.props.handle || device.commandEncoder.handle.beginRenderPass(renderPassDescriptor);
1801
+ if (profiler) {
1802
+ profiler.renderPassBeginCount = (profiler.renderPassBeginCount || 0) + 1;
1803
+ profiler.renderPassBeginTimeMs = (profiler.renderPassBeginTimeMs || 0) + (getTimestamp() - beginRenderPassStartTime);
1804
+ }
1805
+ this.device.popErrorScope((error) => {
1806
+ this.device.reportError(new Error(`${this} creation failed:
1404
1807
  "${error.message}"`), this)();
1405
- this.device.debug();
1406
- });
1407
- this.handle.label = this.props.id;
1408
- import_core16.log.groupCollapsed(3, `new WebGPURenderPass(${this.id})`)();
1409
- import_core16.log.probe(3, JSON.stringify(renderPassDescriptor, null, 2))();
1410
- import_core16.log.groupEnd(3)();
1808
+ this.device.debug();
1809
+ });
1810
+ this.handle.label = this.props.id;
1811
+ import_core17.log.groupCollapsed(3, `new WebGPURenderPass(${this.id})`)();
1812
+ import_core17.log.probe(3, JSON.stringify(renderPassDescriptor, null, 2))();
1813
+ import_core17.log.groupEnd(3)();
1814
+ } finally {
1815
+ if (profiler) {
1816
+ profiler.renderPassSetupCount = (profiler.renderPassSetupCount || 0) + 1;
1817
+ profiler.renderPassSetupTimeMs = (profiler.renderPassSetupTimeMs || 0) + (getTimestamp() - startTime);
1818
+ }
1819
+ }
1411
1820
  }
1412
1821
  destroy() {
1822
+ this.destroyResource();
1413
1823
  }
1414
1824
  end() {
1825
+ if (this.destroyed) {
1826
+ return;
1827
+ }
1415
1828
  this.handle.end();
1829
+ this.destroy();
1416
1830
  }
1417
1831
  setPipeline(pipeline) {
1418
1832
  this.pipeline = pipeline;
@@ -1426,9 +1840,9 @@ var init_webgpu_render_pass = __esm({
1426
1840
  }
1427
1841
  /** Sets an array of bindings (uniform buffers, samplers, textures, ...) */
1428
1842
  setBindings(bindings) {
1429
- var _a, _b;
1430
- (_a = this.pipeline) == null ? void 0 : _a.setBindings(bindings);
1431
- const bindGroup = (_b = this.pipeline) == null ? void 0 : _b._getBindGroup();
1843
+ var _a;
1844
+ this.bindings = bindings;
1845
+ const bindGroup = (_a = this.pipeline) == null ? void 0 : _a._getBindGroup(bindings);
1432
1846
  if (bindGroup) {
1433
1847
  this.handle.setBindGroup(0, bindGroup);
1434
1848
  }
@@ -1493,7 +1907,7 @@ var init_webgpu_render_pass = __esm({
1493
1907
  return {
1494
1908
  // clear values
1495
1909
  loadOp: this.props.clearColor !== false ? "clear" : "load",
1496
- clearValue: convertColor(((_a = this.props.clearColors) == null ? void 0 : _a[index]) || this.props.clearColor || import_core16.RenderPass.defaultClearColor),
1910
+ clearValue: convertColor(((_a = this.props.clearColors) == null ? void 0 : _a[index]) || this.props.clearColor || import_core17.RenderPass.defaultClearColor),
1497
1911
  storeOp: this.props.discard ? "discard" : "store",
1498
1912
  // ...colorAttachment,
1499
1913
  view: colorAttachment.handle
@@ -1528,26 +1942,28 @@ var init_webgpu_render_pass = __esm({
1528
1942
  });
1529
1943
 
1530
1944
  // dist/adapter/resources/webgpu-compute-pass.js
1531
- var import_core17, WebGPUComputePass;
1945
+ var import_core18, WebGPUComputePass;
1532
1946
  var init_webgpu_compute_pass = __esm({
1533
1947
  "dist/adapter/resources/webgpu-compute-pass.js"() {
1534
1948
  "use strict";
1535
- import_core17 = require("@luma.gl/core");
1536
- WebGPUComputePass = class extends import_core17.ComputePass {
1949
+ import_core18 = require("@luma.gl/core");
1950
+ WebGPUComputePass = class extends import_core18.ComputePass {
1537
1951
  device;
1538
1952
  handle;
1539
1953
  _webgpuPipeline = null;
1540
- constructor(device, props) {
1954
+ constructor(device, props = {}) {
1541
1955
  super(device, props);
1542
1956
  this.device = device;
1957
+ const { props: computePassProps } = this;
1543
1958
  let timestampWrites;
1544
- if (device.features.has("timestamp-query")) {
1545
- const webgpuQuerySet = props.timestampQuerySet;
1959
+ if (computePassProps.timestampQuerySet) {
1960
+ const webgpuQuerySet = computePassProps.timestampQuerySet;
1546
1961
  if (webgpuQuerySet) {
1962
+ webgpuQuerySet._invalidateResults();
1547
1963
  timestampWrites = {
1548
1964
  querySet: webgpuQuerySet.handle,
1549
- beginningOfPassWriteIndex: props.beginTimestampIndex,
1550
- endOfPassWriteIndex: props.endTimestampIndex
1965
+ beginningOfPassWriteIndex: computePassProps.beginTimestampIndex,
1966
+ endOfPassWriteIndex: computePassProps.endTimestampIndex
1551
1967
  };
1552
1968
  }
1553
1969
  }
@@ -1558,9 +1974,14 @@ var init_webgpu_compute_pass = __esm({
1558
1974
  }
1559
1975
  /** @note no WebGPU destroy method, just gc */
1560
1976
  destroy() {
1977
+ this.destroyResource();
1561
1978
  }
1562
1979
  end() {
1980
+ if (this.destroyed) {
1981
+ return;
1982
+ }
1563
1983
  this.handle.end();
1984
+ this.destroy();
1564
1985
  }
1565
1986
  setPipeline(pipeline) {
1566
1987
  const wgpuPipeline = pipeline;
@@ -1610,15 +2031,15 @@ var init_webgpu_compute_pass = __esm({
1610
2031
  });
1611
2032
 
1612
2033
  // dist/adapter/resources/webgpu-command-encoder.js
1613
- var import_core18, WebGPUCommandEncoder;
2034
+ var import_core19, WebGPUCommandEncoder;
1614
2035
  var init_webgpu_command_encoder = __esm({
1615
2036
  "dist/adapter/resources/webgpu-command-encoder.js"() {
1616
2037
  "use strict";
1617
- import_core18 = require("@luma.gl/core");
2038
+ import_core19 = require("@luma.gl/core");
1618
2039
  init_webgpu_command_buffer();
1619
2040
  init_webgpu_render_pass();
1620
2041
  init_webgpu_compute_pass();
1621
- WebGPUCommandEncoder = class extends import_core18.CommandEncoder {
2042
+ WebGPUCommandEncoder = class extends import_core19.CommandEncoder {
1622
2043
  device;
1623
2044
  handle;
1624
2045
  constructor(device, props = {}) {
@@ -1632,6 +2053,7 @@ var init_webgpu_command_encoder = __esm({
1632
2053
  this.handle.label = this.props.id;
1633
2054
  }
1634
2055
  destroy() {
2056
+ this.destroyResource();
1635
2057
  }
1636
2058
  finish(props) {
1637
2059
  this.device.pushErrorScope("validation");
@@ -1643,17 +2065,18 @@ var init_webgpu_command_encoder = __esm({
1643
2065
  this.device.reportError(new Error(message), this)();
1644
2066
  this.device.debug();
1645
2067
  });
2068
+ this.destroy();
1646
2069
  return commandBuffer;
1647
2070
  }
1648
2071
  /**
1649
2072
  * Allows a render pass to begin against a canvas context
1650
2073
  * @todo need to support a "Framebuffer" equivalent (aka preconfigured RenderPassDescriptors?).
1651
2074
  */
1652
- beginRenderPass(props) {
1653
- return new WebGPURenderPass(this.device, props);
2075
+ beginRenderPass(props = {}) {
2076
+ return new WebGPURenderPass(this.device, this._applyTimeProfilingToPassProps(props));
1654
2077
  }
1655
- beginComputePass(props) {
1656
- return new WebGPUComputePass(this.device, props);
2078
+ beginComputePass(props = {}) {
2079
+ return new WebGPUComputePass(this.device, this._applyTimeProfilingToPassProps(props));
1657
2080
  }
1658
2081
  // beginRenderPass(GPURenderPassDescriptor descriptor): GPURenderPassEncoder;
1659
2082
  // beginComputePass(optional GPUComputePassDescriptor descriptor = {}): GPUComputePassEncoder;
@@ -1701,19 +2124,40 @@ var init_webgpu_command_encoder = __esm({
1701
2124
  const webgpuBuffer = destination;
1702
2125
  this.handle.resolveQuerySet(webgpuQuerySet.handle, (options == null ? void 0 : options.firstQuery) || 0, (options == null ? void 0 : options.queryCount) || querySet.props.count - ((options == null ? void 0 : options.firstQuery) || 0), webgpuBuffer.handle, (options == null ? void 0 : options.destinationOffset) || 0);
1703
2126
  }
2127
+ writeTimestamp(querySet, queryIndex) {
2128
+ querySet._invalidateResults();
2129
+ const writeTimestamp = this.handle.writeTimestamp;
2130
+ if (writeTimestamp) {
2131
+ writeTimestamp.call(this.handle, querySet.handle, queryIndex);
2132
+ return;
2133
+ }
2134
+ const computePass = this.handle.beginComputePass({
2135
+ timestampWrites: {
2136
+ querySet: querySet.handle,
2137
+ beginningOfPassWriteIndex: queryIndex
2138
+ }
2139
+ });
2140
+ computePass.end();
2141
+ }
1704
2142
  };
1705
2143
  }
1706
2144
  });
1707
2145
 
1708
2146
  // dist/adapter/resources/webgpu-query-set.js
1709
- var import_core19, WebGPUQuerySet;
2147
+ var import_core20, WebGPUQuerySet;
1710
2148
  var init_webgpu_query_set = __esm({
1711
2149
  "dist/adapter/resources/webgpu-query-set.js"() {
1712
2150
  "use strict";
1713
- import_core19 = require("@luma.gl/core");
1714
- WebGPUQuerySet = class extends import_core19.QuerySet {
2151
+ import_core20 = require("@luma.gl/core");
2152
+ init_cpu_hotspot_profiler();
2153
+ WebGPUQuerySet = class extends import_core20.QuerySet {
1715
2154
  device;
1716
2155
  handle;
2156
+ _resolveBuffer = null;
2157
+ _readBuffer = null;
2158
+ _cachedResults = null;
2159
+ _readResultsPromise = null;
2160
+ _resultsPendingResolution = false;
1717
2161
  constructor(device, props) {
1718
2162
  super(device, props);
1719
2163
  this.device = device;
@@ -1725,20 +2169,145 @@ var init_webgpu_query_set = __esm({
1725
2169
  }
1726
2170
  destroy() {
1727
2171
  var _a;
1728
- (_a = this.handle) == null ? void 0 : _a.destroy();
1729
- this.handle = null;
2172
+ if (!this.destroyed) {
2173
+ (_a = this.handle) == null ? void 0 : _a.destroy();
2174
+ this.destroyResource();
2175
+ this.handle = null;
2176
+ }
2177
+ }
2178
+ isResultAvailable(queryIndex) {
2179
+ if (!this._cachedResults) {
2180
+ return false;
2181
+ }
2182
+ return queryIndex === void 0 ? true : queryIndex >= 0 && queryIndex < this._cachedResults.length;
2183
+ }
2184
+ async readResults(options) {
2185
+ const firstQuery = (options == null ? void 0 : options.firstQuery) || 0;
2186
+ const queryCount = (options == null ? void 0 : options.queryCount) || this.props.count - firstQuery;
2187
+ if (firstQuery < 0 || queryCount < 0 || firstQuery + queryCount > this.props.count) {
2188
+ throw new Error("Query read range is out of bounds");
2189
+ }
2190
+ let needsFreshResults = true;
2191
+ while (needsFreshResults) {
2192
+ if (!this._readResultsPromise) {
2193
+ this._readResultsPromise = this._readAllResults();
2194
+ }
2195
+ const readResultsPromise = this._readResultsPromise;
2196
+ const results = await readResultsPromise;
2197
+ needsFreshResults = this._resultsPendingResolution;
2198
+ if (!needsFreshResults) {
2199
+ return results.slice(firstQuery, firstQuery + queryCount);
2200
+ }
2201
+ }
2202
+ throw new Error("Query read unexpectedly failed to resolve");
2203
+ }
2204
+ async readTimestampDuration(beginIndex, endIndex) {
2205
+ if (this.props.type !== "timestamp") {
2206
+ throw new Error("Timestamp durations require a timestamp QuerySet");
2207
+ }
2208
+ if (beginIndex < 0 || endIndex <= beginIndex || endIndex >= this.props.count) {
2209
+ throw new Error("Timestamp duration range is out of bounds");
2210
+ }
2211
+ const results = await this.readResults({
2212
+ firstQuery: beginIndex,
2213
+ queryCount: endIndex - beginIndex + 1
2214
+ });
2215
+ return Number(results[results.length - 1] - results[0]) / 1e6;
2216
+ }
2217
+ /** Marks any cached query results as stale after new writes have been encoded. */
2218
+ _invalidateResults() {
2219
+ this._cachedResults = null;
2220
+ this._resultsPendingResolution = true;
2221
+ }
2222
+ async _readAllResults() {
2223
+ this._ensureBuffers();
2224
+ try {
2225
+ if (this._resultsPendingResolution) {
2226
+ const commandEncoder = this.device.createCommandEncoder({
2227
+ id: `${this.id}-read-results`
2228
+ });
2229
+ commandEncoder.resolveQuerySet(this, this._resolveBuffer);
2230
+ commandEncoder.copyBufferToBuffer({
2231
+ sourceBuffer: this._resolveBuffer,
2232
+ destinationBuffer: this._readBuffer,
2233
+ size: this._resolveBuffer.byteLength
2234
+ });
2235
+ const commandBuffer = commandEncoder.finish({
2236
+ id: `${this.id}-read-results-command-buffer`
2237
+ });
2238
+ const previousSubmitReason = getCpuHotspotSubmitReason(this.device) || void 0;
2239
+ setCpuHotspotSubmitReason(this.device, "query-readback");
2240
+ try {
2241
+ this.device.submit(commandBuffer);
2242
+ } finally {
2243
+ setCpuHotspotSubmitReason(this.device, previousSubmitReason);
2244
+ }
2245
+ }
2246
+ const data = await this._readBuffer.readAsync(0, this._readBuffer.byteLength);
2247
+ const resultView = new BigUint64Array(data.buffer, data.byteOffset, this.props.count);
2248
+ this._cachedResults = Array.from(resultView, (value) => value);
2249
+ this._resultsPendingResolution = false;
2250
+ return this._cachedResults;
2251
+ } finally {
2252
+ this._readResultsPromise = null;
2253
+ }
2254
+ }
2255
+ _ensureBuffers() {
2256
+ if (this._resolveBuffer && this._readBuffer) {
2257
+ return;
2258
+ }
2259
+ const byteLength = this.props.count * 8;
2260
+ this._resolveBuffer = this.device.createBuffer({
2261
+ id: `${this.id}-resolve-buffer`,
2262
+ usage: import_core20.Buffer.QUERY_RESOLVE | import_core20.Buffer.COPY_SRC,
2263
+ byteLength
2264
+ });
2265
+ this.attachResource(this._resolveBuffer);
2266
+ this._readBuffer = this.device.createBuffer({
2267
+ id: `${this.id}-read-buffer`,
2268
+ usage: import_core20.Buffer.COPY_DST | import_core20.Buffer.MAP_READ,
2269
+ byteLength
2270
+ });
2271
+ this.attachResource(this._readBuffer);
2272
+ }
2273
+ _encodeResolveToReadBuffer(commandEncoder, options) {
2274
+ if (!this._resultsPendingResolution) {
2275
+ return false;
2276
+ }
2277
+ if (this._readResultsPromise) {
2278
+ return false;
2279
+ }
2280
+ this._ensureBuffers();
2281
+ const firstQuery = (options == null ? void 0 : options.firstQuery) || 0;
2282
+ const queryCount = (options == null ? void 0 : options.queryCount) || this.props.count - firstQuery;
2283
+ const byteLength = queryCount * BigUint64Array.BYTES_PER_ELEMENT;
2284
+ const byteOffset = firstQuery * BigUint64Array.BYTES_PER_ELEMENT;
2285
+ commandEncoder.resolveQuerySet(this, this._resolveBuffer, {
2286
+ firstQuery,
2287
+ queryCount,
2288
+ destinationOffset: byteOffset
2289
+ });
2290
+ commandEncoder.copyBufferToBuffer({
2291
+ sourceBuffer: this._resolveBuffer,
2292
+ sourceOffset: byteOffset,
2293
+ destinationBuffer: this._readBuffer,
2294
+ destinationOffset: byteOffset,
2295
+ size: byteLength
2296
+ });
2297
+ this._resultsPendingResolution = false;
2298
+ return true;
1730
2299
  }
1731
2300
  };
1732
2301
  }
1733
2302
  });
1734
2303
 
1735
2304
  // dist/adapter/resources/webgpu-pipeline-layout.js
1736
- var import_core20, WebGPUPipelineLayout, isStorageTextureBindingLayout;
2305
+ var import_core21, WebGPUPipelineLayout, isStorageTextureBindingLayout;
1737
2306
  var init_webgpu_pipeline_layout = __esm({
1738
2307
  "dist/adapter/resources/webgpu-pipeline-layout.js"() {
1739
2308
  "use strict";
1740
- import_core20 = require("@luma.gl/core");
1741
- WebGPUPipelineLayout = class extends import_core20.PipelineLayout {
2309
+ import_core21 = require("@luma.gl/core");
2310
+ WebGPUPipelineLayout = class extends import_core21.PipelineLayout {
1742
2311
  device;
1743
2312
  handle;
1744
2313
  constructor(device, props) {
@@ -1763,8 +2332,7 @@ var init_webgpu_pipeline_layout = __esm({
1763
2332
  }
1764
2333
  mapShaderLayoutToBindGroupEntries() {
1765
2334
  const bindGroupEntries = [];
1766
- for (let i = 0; i < this.props.shaderLayout.bindings.length; i++) {
1767
- const binding = this.props.shaderLayout.bindings[i];
2335
+ for (const binding of this.props.shaderLayout.bindings) {
1768
2336
  const bindingTypeInfo = {};
1769
2337
  switch (binding.type) {
1770
2338
  case "uniform": {
@@ -1816,7 +2384,7 @@ var init_webgpu_pipeline_layout = __esm({
1816
2384
  break;
1817
2385
  }
1818
2386
  default: {
1819
- import_core20.log.warn("unhandled binding type when creating pipeline descriptor")();
2387
+ import_core21.log.warn("unhandled binding type when creating pipeline descriptor")();
1820
2388
  }
1821
2389
  }
1822
2390
  const VISIBILITY_ALL = GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE;
@@ -1836,12 +2404,12 @@ var init_webgpu_pipeline_layout = __esm({
1836
2404
  });
1837
2405
 
1838
2406
  // dist/adapter/resources/webgpu-fence.js
1839
- var import_core21, WebGPUFence;
2407
+ var import_core22, WebGPUFence;
1840
2408
  var init_webgpu_fence = __esm({
1841
2409
  "dist/adapter/resources/webgpu-fence.js"() {
1842
2410
  "use strict";
1843
- import_core21 = require("@luma.gl/core");
1844
- WebGPUFence = class extends import_core21.Fence {
2411
+ import_core22 = require("@luma.gl/core");
2412
+ WebGPUFence = class extends import_core22.Fence {
1845
2413
  device;
1846
2414
  handle = null;
1847
2415
  signaled;
@@ -1870,7 +2438,7 @@ function getShaderLayoutFromWGSL(source) {
1870
2438
  try {
1871
2439
  parsedWGSL = parseWGSL(source);
1872
2440
  } catch (error) {
1873
- import_core22.log.error(error.message)();
2441
+ import_core23.log.error(error.message)();
1874
2442
  return shaderLayout;
1875
2443
  }
1876
2444
  for (const uniform of parsedWGSL.uniforms) {
@@ -1964,25 +2532,508 @@ function getTextureBindingFromReflect(v, opts) {
1964
2532
  }
1965
2533
  return { viewDimension, sampleType, multisampled };
1966
2534
  }
1967
- var import_core22, import_wgsl_reflect;
2535
+ var import_core23, import_wgsl_reflect;
1968
2536
  var init_get_shader_layout_wgsl = __esm({
1969
2537
  "dist/wgsl/get-shader-layout-wgsl.js"() {
1970
2538
  "use strict";
1971
- import_core22 = require("@luma.gl/core");
2539
+ import_core23 = require("@luma.gl/core");
1972
2540
  import_wgsl_reflect = require("wgsl_reflect");
1973
2541
  }
1974
2542
  });
1975
2543
 
2544
+ // dist/adapter/helpers/generate-mipmaps-webgpu.js
2545
+ function generateMipmapsWebGPU(device, texture) {
2546
+ if (texture.mipLevels <= 1) {
2547
+ return;
2548
+ }
2549
+ if (texture.dimension === "3d") {
2550
+ generateMipmaps3D(device, texture);
2551
+ return;
2552
+ }
2553
+ if (RENDER_DIMENSIONS.includes(texture.dimension)) {
2554
+ generateMipmapsRender(device, texture);
2555
+ return;
2556
+ }
2557
+ throw new Error(`Cannot generate mipmaps for texture dimension "${texture.dimension}" with WebGPU.`);
2558
+ }
2559
+ function generateMipmapsRender(device, texture) {
2560
+ validateFormatCapabilities(device, texture, ["render", "filter"], "render");
2561
+ const colorAttachmentFormat = getColorAttachmentFormat(texture.format, "render", texture.dimension);
2562
+ const viewDimension = texture.dimension;
2563
+ const shaderSource = getRenderMipmapWGSL(viewDimension);
2564
+ const sampler = device.createSampler({ minFilter: "linear", magFilter: "linear" });
2565
+ const uniformsBuffer = device.createBuffer({
2566
+ byteLength: 16,
2567
+ usage: import_core24.Buffer.UNIFORM | import_core24.Buffer.COPY_DST
2568
+ });
2569
+ const uniformValues = new Uint32Array(1);
2570
+ const sourceTextureLayout = {
2571
+ type: "texture",
2572
+ name: "sourceTexture",
2573
+ group: 0,
2574
+ location: 1,
2575
+ viewDimension,
2576
+ sampleType: "float"
2577
+ };
2578
+ const uniformsLayout = {
2579
+ type: "uniform",
2580
+ name: "uniforms",
2581
+ group: 0,
2582
+ location: 2
2583
+ };
2584
+ const renderShaderLayout = {
2585
+ attributes: [],
2586
+ bindings: [RENDER_SOURCE_SAMPLER_LAYOUT, sourceTextureLayout, uniformsLayout]
2587
+ };
2588
+ const vertexShader = device.createShader({
2589
+ id: "mipmap-generation-render-vs",
2590
+ source: shaderSource,
2591
+ language: "wgsl",
2592
+ stage: "vertex"
2593
+ });
2594
+ const fragmentShader = device.createShader({
2595
+ id: "mipmap-generation-render-fs",
2596
+ source: shaderSource,
2597
+ language: "wgsl",
2598
+ stage: "fragment"
2599
+ });
2600
+ const renderPipeline = device.createRenderPipeline({
2601
+ id: `mipmap-generation-render:${texture.dimension}:${texture.format}`,
2602
+ vs: vertexShader,
2603
+ fs: fragmentShader,
2604
+ shaderLayout: renderShaderLayout,
2605
+ colorAttachmentFormats: [colorAttachmentFormat],
2606
+ topology: "triangle-list"
2607
+ });
2608
+ let sourceWidth = texture.width;
2609
+ let sourceHeight = texture.height;
2610
+ const layerCount = texture.dimension === "2d" ? 1 : texture.depth;
2611
+ function renderMipmapLayer(sourceView, baseMipLevel, baseArrayLayer, destinationWidth, destinationHeight) {
2612
+ uniformValues[0] = baseArrayLayer;
2613
+ uniformsBuffer.write(uniformValues);
2614
+ const destinationView = texture.createView({
2615
+ dimension: "2d",
2616
+ baseMipLevel,
2617
+ mipLevelCount: 1,
2618
+ baseArrayLayer,
2619
+ arrayLayerCount: 1
2620
+ });
2621
+ const framebuffer = device.createFramebuffer({
2622
+ colorAttachments: [destinationView]
2623
+ });
2624
+ const renderPass = device.beginRenderPass({
2625
+ id: `mipmap-generation:${texture.format}:${baseMipLevel}:${baseArrayLayer}`,
2626
+ framebuffer
2627
+ });
2628
+ try {
2629
+ renderPass.setPipeline(renderPipeline);
2630
+ renderPass.setBindings({
2631
+ sourceSampler: sampler,
2632
+ sourceTexture: sourceView,
2633
+ uniforms: uniformsBuffer
2634
+ });
2635
+ renderPass.setParameters({
2636
+ viewport: [0, 0, destinationWidth, destinationHeight, 0, 1],
2637
+ scissorRect: [0, 0, destinationWidth, destinationHeight]
2638
+ });
2639
+ renderPass.draw({ vertexCount: 3 });
2640
+ renderPass.end();
2641
+ device.submit();
2642
+ } finally {
2643
+ destinationView.destroy();
2644
+ framebuffer.destroy();
2645
+ }
2646
+ }
2647
+ try {
2648
+ for (let baseMipLevel = 1; baseMipLevel < texture.mipLevels; ++baseMipLevel) {
2649
+ validateFormatCapabilities(device, texture, ["render", "filter"], "render");
2650
+ const sourceMipLevel = baseMipLevel - 1;
2651
+ const destinationWidth = Math.max(1, sourceWidth >> 1);
2652
+ const destinationHeight = Math.max(1, sourceHeight >> 1);
2653
+ const sourceView = texture.createView({
2654
+ dimension: viewDimension,
2655
+ baseMipLevel: sourceMipLevel,
2656
+ mipLevelCount: 1,
2657
+ baseArrayLayer: 0,
2658
+ arrayLayerCount: texture.depth
2659
+ });
2660
+ try {
2661
+ for (let baseArrayLayer = 0; baseArrayLayer < layerCount; ++baseArrayLayer) {
2662
+ renderMipmapLayer(sourceView, baseMipLevel, baseArrayLayer, destinationWidth, destinationHeight);
2663
+ }
2664
+ } finally {
2665
+ sourceView.destroy();
2666
+ }
2667
+ sourceWidth = destinationWidth;
2668
+ sourceHeight = destinationHeight;
2669
+ }
2670
+ } finally {
2671
+ renderPipeline.destroy();
2672
+ vertexShader.destroy();
2673
+ fragmentShader.destroy();
2674
+ sampler.destroy();
2675
+ uniformsBuffer.destroy();
2676
+ }
2677
+ }
2678
+ function getColorAttachmentFormat(format, path, dimension) {
2679
+ if (import_core24.textureFormatDecoder.isColor(format)) {
2680
+ return format;
2681
+ }
2682
+ throw new Error(`Cannot run ${path} mipmap generation for ${dimension} texture with format "${format}". Only color textures can be used for this operation. Required capabilities: color. Actual capabilities: color=false.`);
2683
+ }
2684
+ function generateMipmaps3D(device, texture) {
2685
+ validateFormatCapabilities(device, texture, ["filter", "store"], "compute");
2686
+ const format = getColorAttachmentFormat(texture.format, "compute", texture.dimension);
2687
+ const shaderSource = get3DComputeMipmapWGSL(format);
2688
+ const destinationTextureLayout = {
2689
+ type: "storage",
2690
+ name: "destinationTexture",
2691
+ group: 0,
2692
+ location: 1,
2693
+ format,
2694
+ viewDimension: "3d",
2695
+ access: "write-only"
2696
+ };
2697
+ const computeShaderLayout = {
2698
+ bindings: [COMPUTE_SOURCE_TEXTURE_LAYOUT, destinationTextureLayout, COMPUTE_UNIFORMS_LAYOUT]
2699
+ };
2700
+ const computeShader = device.createShader({
2701
+ id: "mipmap-generation-compute",
2702
+ source: shaderSource,
2703
+ language: "wgsl",
2704
+ stage: "compute"
2705
+ });
2706
+ const computePipeline = device.createComputePipeline({
2707
+ id: `mipmap-generation-compute:${texture.format}`,
2708
+ shader: computeShader,
2709
+ shaderLayout: computeShaderLayout
2710
+ });
2711
+ const uniformsBuffer = device.createBuffer({
2712
+ byteLength: 32,
2713
+ usage: import_core24.Buffer.UNIFORM | import_core24.Buffer.COPY_DST
2714
+ });
2715
+ const uniformValues = new Uint32Array(8);
2716
+ let sourceWidth = texture.width;
2717
+ let sourceHeight = texture.height;
2718
+ let sourceDepth = texture.depth;
2719
+ try {
2720
+ for (let destinationMipLevel = 1; destinationMipLevel < texture.mipLevels; ++destinationMipLevel) {
2721
+ validateFormatCapabilities(device, texture, ["filter", "store"], "compute");
2722
+ const destinationWidth = Math.max(1, sourceWidth >> 1);
2723
+ const destinationHeight = Math.max(1, sourceHeight >> 1);
2724
+ const destinationDepth = Math.max(1, sourceDepth >> 1);
2725
+ uniformValues[0] = sourceWidth;
2726
+ uniformValues[1] = sourceHeight;
2727
+ uniformValues[2] = sourceDepth;
2728
+ uniformValues[3] = destinationWidth;
2729
+ uniformValues[4] = destinationHeight;
2730
+ uniformValues[5] = destinationDepth;
2731
+ uniformValues[6] = 0;
2732
+ uniformsBuffer.write(uniformValues);
2733
+ const sourceView = texture.createView({
2734
+ dimension: "3d",
2735
+ baseMipLevel: destinationMipLevel - 1,
2736
+ mipLevelCount: 1,
2737
+ baseArrayLayer: 0,
2738
+ arrayLayerCount: 1
2739
+ });
2740
+ const destinationView = texture.createView({
2741
+ dimension: "3d",
2742
+ baseMipLevel: destinationMipLevel,
2743
+ mipLevelCount: 1,
2744
+ baseArrayLayer: 0,
2745
+ arrayLayerCount: 1
2746
+ });
2747
+ computePipeline.setBindings({
2748
+ sourceTexture: sourceView,
2749
+ destinationTexture: destinationView,
2750
+ uniforms: uniformsBuffer
2751
+ });
2752
+ try {
2753
+ const workgroupsX = Math.ceil(destinationWidth / WORKGROUP_SIZE.x);
2754
+ const workgroupsY = Math.ceil(destinationHeight / WORKGROUP_SIZE.y);
2755
+ const workgroupsZ = Math.ceil(destinationDepth / WORKGROUP_SIZE.z);
2756
+ const computePass = device.beginComputePass({});
2757
+ computePass.setPipeline(computePipeline);
2758
+ computePass.dispatch(workgroupsX, workgroupsY, workgroupsZ);
2759
+ computePass.end();
2760
+ device.submit();
2761
+ } finally {
2762
+ sourceView.destroy();
2763
+ destinationView.destroy();
2764
+ }
2765
+ sourceWidth = destinationWidth;
2766
+ sourceHeight = destinationHeight;
2767
+ sourceDepth = destinationDepth;
2768
+ }
2769
+ } finally {
2770
+ computePipeline.destroy();
2771
+ computeShader.destroy();
2772
+ uniformsBuffer.destroy();
2773
+ }
2774
+ }
2775
+ function validateFormatCapabilities(device, texture, requiredCapabilities, path) {
2776
+ const { format, dimension } = texture;
2777
+ const capabilities = device.getTextureFormatCapabilities(format);
2778
+ const missingCapabilities = requiredCapabilities.filter((capability) => !capabilities[capability]);
2779
+ if (missingCapabilities.length > 0) {
2780
+ const required = requiredCapabilities.join(" + ");
2781
+ const actual = requiredCapabilities.map((capability) => `${capability}=${capabilities[capability]}`).join(", ");
2782
+ throw new Error(`Cannot run ${path} mipmap generation for ${dimension} texture with format "${format}". Required capabilities: ${required}. Actual capabilities: ${actual}.`);
2783
+ }
2784
+ }
2785
+ function getSourceTextureType(dimension) {
2786
+ switch (dimension) {
2787
+ case "2d":
2788
+ return "texture_2d<f32>";
2789
+ case "2d-array":
2790
+ return "texture_2d_array<f32>";
2791
+ case "cube":
2792
+ return "texture_cube<f32>";
2793
+ case "cube-array":
2794
+ return "texture_cube_array<f32>";
2795
+ default:
2796
+ throw new Error(`Unsupported render dimension "${dimension}" for mipmap generation.`);
2797
+ }
2798
+ }
2799
+ function getRenderMipmapWGSL(dimension) {
2800
+ const sourceSnippet = getRenderMipmapSampleSnippet(dimension);
2801
+ return `
2802
+ struct MipmapUniforms {
2803
+ sourceLayer: u32,
2804
+ };
2805
+
2806
+ fn _touchUniform(uniforms: MipmapUniforms) {
2807
+ let unusedSourceLayer = uniforms.sourceLayer;
2808
+ }
2809
+
2810
+ const faceMat = array(
2811
+ mat3x3f(
2812
+ 0.0, 0.0, -2.0,
2813
+ 0.0, -2.0, 0.0,
2814
+ 1.0, 1.0, 1.0
2815
+ ), // pos-x
2816
+ mat3x3f(
2817
+ 0.0, 0.0, 2.0,
2818
+ 0.0, -2.0, 0.0,
2819
+ -1.0, 1.0, -1.0
2820
+ ), // neg-x
2821
+ mat3x3f(
2822
+ 2.0, 0.0, 0.0,
2823
+ 0.0, 0.0, 2.0,
2824
+ -1.0, 1.0, -1.0
2825
+ ), // pos-y
2826
+ mat3x3f(
2827
+ 2.0, 0.0, 0.0,
2828
+ 0.0, 0.0, -2.0,
2829
+ -1.0, -1.0, 1.0
2830
+ ), // neg-y
2831
+ mat3x3f(
2832
+ 2.0, 0.0, 0.0,
2833
+ 0.0, -2.0, 0.0,
2834
+ -1.0, 1.0, 1.0
2835
+ ), // pos-z
2836
+ mat3x3f(
2837
+ -2.0, 0.0, 0.0,
2838
+ 0.0, -2.0, 0.0,
2839
+ 1.0, 1.0, -1.0
2840
+ ) // neg-z
2841
+ );
2842
+
2843
+ struct FragmentInputs {
2844
+ @builtin(position) position: vec4f,
2845
+ @location(0) texcoord: vec2f
2846
+ };
2847
+
2848
+ struct VertexOutput {
2849
+ @builtin(position) position: vec4f,
2850
+ @location(0) texcoord: vec2f
2851
+ };
2852
+
2853
+ @group(0) @binding(0) var sourceSampler: sampler;
2854
+ @group(0) @binding(1) var sourceTexture: ${getSourceTextureType(dimension)};
2855
+ @group(0) @binding(2) var<uniform> uniforms: MipmapUniforms;
2856
+
2857
+ @vertex
2858
+ fn vertexMain(
2859
+ @builtin(vertex_index) vertexIndex: u32
2860
+ ) -> VertexOutput {
2861
+ const positions = array(
2862
+ vec2f(-1.0, -1.0),
2863
+ vec2f(-1.0, 3.0),
2864
+ vec2f( 3.0, -1.0)
2865
+ );
2866
+
2867
+ let xy = positions[vertexIndex];
2868
+ return VertexOutput(
2869
+ vec4f(xy, 0.0, 1.0),
2870
+ xy * vec2f(0.5, -0.5) + vec2f(0.5)
2871
+ );
2872
+ }
2873
+
2874
+ @fragment
2875
+ fn fragmentMain(fsInput: VertexOutput) -> @location(0) vec4f {
2876
+ _touchUniform(uniforms);
2877
+ return ${sourceSnippet};
2878
+ }
2879
+ `;
2880
+ }
2881
+ function getRenderMipmapSampleSnippet(dimension) {
2882
+ const layer = "uniforms.sourceLayer";
2883
+ switch (dimension) {
2884
+ case "2d":
2885
+ return "textureSampleLevel(sourceTexture, sourceSampler, fsInput.texcoord, 0.0)";
2886
+ case "2d-array":
2887
+ return `textureSampleLevel(sourceTexture, sourceSampler, fsInput.texcoord, i32(${layer}), 0.0)`;
2888
+ case "cube":
2889
+ return `textureSampleLevel(sourceTexture, sourceSampler, faceMat[i32(${layer})] * vec3f(fract(fsInput.texcoord), 1.0), 0.0)`;
2890
+ case "cube-array":
2891
+ return `textureSampleLevel(sourceTexture, sourceSampler, faceMat[i32(${layer} % 6u)] * vec3f(fract(fsInput.texcoord), 1.0), i32(${layer} / 6u), 0.0)`;
2892
+ default:
2893
+ throw new Error(`Unsupported render dimension "${dimension}" for mipmap generation.`);
2894
+ }
2895
+ }
2896
+ function get3DComputeMipmapWGSL(format) {
2897
+ return `
2898
+ struct MipmapUniforms {
2899
+ sourceWidth: u32,
2900
+ sourceHeight: u32,
2901
+ sourceDepth: u32,
2902
+ destinationWidth: u32,
2903
+ destinationHeight: u32,
2904
+ destinationDepth: u32,
2905
+ padding: u32,
2906
+ };
2907
+
2908
+ @group(0) @binding(0) var sourceTexture: texture_3d<f32>;
2909
+ @group(0) @binding(1) var destinationTexture: texture_storage_3d<${format}, write>;
2910
+ @group(0) @binding(2) var<uniform> uniforms: MipmapUniforms;
2911
+
2912
+ @compute @workgroup_size(${WORKGROUP_SIZE.x}, ${WORKGROUP_SIZE.y}, ${WORKGROUP_SIZE.z})
2913
+ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
2914
+ if (
2915
+ id.x >= uniforms.destinationWidth ||
2916
+ id.y >= uniforms.destinationHeight ||
2917
+ id.z >= uniforms.destinationDepth
2918
+ ) {
2919
+ return;
2920
+ }
2921
+
2922
+ let sourceBase = id * 2u;
2923
+ let sourceX0 = min(sourceBase.x, uniforms.sourceWidth - 1u);
2924
+ let sourceY0 = min(sourceBase.y, uniforms.sourceHeight - 1u);
2925
+ let sourceZ0 = min(sourceBase.z, uniforms.sourceDepth - 1u);
2926
+
2927
+ let sourceX1 = min(sourceBase.x + 1u, uniforms.sourceWidth - 1u);
2928
+ let sourceY1 = min(sourceBase.y + 1u, uniforms.sourceHeight - 1u);
2929
+ let sourceZ1 = min(sourceBase.z + 1u, uniforms.sourceDepth - 1u);
2930
+
2931
+ var sum = textureLoad(
2932
+ sourceTexture,
2933
+ vec3<i32>(i32(sourceX0), i32(sourceY0), i32(sourceZ0)),
2934
+ 0
2935
+ );
2936
+ sum += textureLoad(
2937
+ sourceTexture,
2938
+ vec3<i32>(i32(sourceX1), i32(sourceY0), i32(sourceZ0)),
2939
+ 0
2940
+ );
2941
+ sum += textureLoad(
2942
+ sourceTexture,
2943
+ vec3<i32>(i32(sourceX0), i32(sourceY1), i32(sourceZ0)),
2944
+ 0
2945
+ );
2946
+ sum += textureLoad(
2947
+ sourceTexture,
2948
+ vec3<i32>(i32(sourceX1), i32(sourceY1), i32(sourceZ0)),
2949
+ 0
2950
+ );
2951
+ sum += textureLoad(
2952
+ sourceTexture,
2953
+ vec3<i32>(i32(sourceX0), i32(sourceY0), i32(sourceZ1)),
2954
+ 0
2955
+ );
2956
+ sum += textureLoad(
2957
+ sourceTexture,
2958
+ vec3<i32>(i32(sourceX1), i32(sourceY0), i32(sourceZ1)),
2959
+ 0
2960
+ );
2961
+ sum += textureLoad(
2962
+ sourceTexture,
2963
+ vec3<i32>(i32(sourceX0), i32(sourceY1), i32(sourceZ1)),
2964
+ 0
2965
+ );
2966
+ sum += textureLoad(
2967
+ sourceTexture,
2968
+ vec3<i32>(i32(sourceX1), i32(sourceY1), i32(sourceZ1)),
2969
+ 0
2970
+ );
2971
+
2972
+ textureStore(
2973
+ destinationTexture,
2974
+ vec3<i32>(i32(id.x), i32(id.y), i32(id.z)),
2975
+ vec4<f32>(sum.xyz / 8.0, sum.w / 8.0)
2976
+ );
2977
+ }
2978
+ `;
2979
+ }
2980
+ var import_core24, RENDER_DIMENSIONS, WORKGROUP_SIZE, RENDER_SOURCE_SAMPLER_LAYOUT, COMPUTE_SOURCE_TEXTURE_LAYOUT, COMPUTE_UNIFORMS_LAYOUT;
2981
+ var init_generate_mipmaps_webgpu = __esm({
2982
+ "dist/adapter/helpers/generate-mipmaps-webgpu.js"() {
2983
+ "use strict";
2984
+ import_core24 = require("@luma.gl/core");
2985
+ RENDER_DIMENSIONS = [
2986
+ "2d",
2987
+ "2d-array",
2988
+ "cube",
2989
+ "cube-array"
2990
+ ];
2991
+ WORKGROUP_SIZE = {
2992
+ x: 4,
2993
+ y: 4,
2994
+ z: 4
2995
+ };
2996
+ RENDER_SOURCE_SAMPLER_LAYOUT = {
2997
+ type: "sampler",
2998
+ name: "sourceSampler",
2999
+ group: 0,
3000
+ location: 0
3001
+ };
3002
+ COMPUTE_SOURCE_TEXTURE_LAYOUT = {
3003
+ type: "texture",
3004
+ name: "sourceTexture",
3005
+ group: 0,
3006
+ location: 0,
3007
+ viewDimension: "3d",
3008
+ sampleType: "float"
3009
+ };
3010
+ COMPUTE_UNIFORMS_LAYOUT = {
3011
+ type: "uniform",
3012
+ name: "uniforms",
3013
+ group: 0,
3014
+ location: 2
3015
+ };
3016
+ }
3017
+ });
3018
+
1976
3019
  // dist/adapter/webgpu-device.js
1977
3020
  var webgpu_device_exports = {};
1978
3021
  __export(webgpu_device_exports, {
1979
3022
  WebGPUDevice: () => WebGPUDevice
1980
3023
  });
1981
- var import_core23, WebGPUDevice;
3024
+ function scheduleMicrotask(callback) {
3025
+ if (globalThis.queueMicrotask) {
3026
+ globalThis.queueMicrotask(callback);
3027
+ return;
3028
+ }
3029
+ Promise.resolve().then(callback).catch(() => {
3030
+ });
3031
+ }
3032
+ var import_core25, WebGPUDevice;
1982
3033
  var init_webgpu_device = __esm({
1983
3034
  "dist/adapter/webgpu-device.js"() {
1984
3035
  "use strict";
1985
- import_core23 = require("@luma.gl/core");
3036
+ import_core25 = require("@luma.gl/core");
1986
3037
  init_webgpu_buffer();
1987
3038
  init_webgpu_texture();
1988
3039
  init_webgpu_external_texture();
@@ -1993,12 +3044,15 @@ var init_webgpu_device = __esm({
1993
3044
  init_webgpu_compute_pipeline();
1994
3045
  init_webgpu_vertex_array();
1995
3046
  init_webgpu_canvas_context();
3047
+ init_webgpu_presentation_context();
1996
3048
  init_webgpu_command_encoder();
1997
3049
  init_webgpu_query_set();
1998
3050
  init_webgpu_pipeline_layout();
1999
3051
  init_webgpu_fence();
2000
3052
  init_get_shader_layout_wgsl();
2001
- WebGPUDevice = class extends import_core23.Device {
3053
+ init_generate_mipmaps_webgpu();
3054
+ init_cpu_hotspot_profiler();
3055
+ WebGPUDevice = class extends import_core25.Device {
2002
3056
  /** The underlying WebGPU device */
2003
3057
  handle;
2004
3058
  /* The underlying WebGPU adapter */
@@ -2015,6 +3069,7 @@ var init_webgpu_device = __esm({
2015
3069
  lost;
2016
3070
  canvasContext = null;
2017
3071
  _isLost = false;
3072
+ _defaultSampler = null;
2018
3073
  commandEncoder;
2019
3074
  get [Symbol.toStringTag]() {
2020
3075
  return "WebGPUDevice";
@@ -2041,7 +3096,7 @@ var init_webgpu_device = __esm({
2041
3096
  this._isLost = true;
2042
3097
  resolve({ reason: "destroyed", message: lostInfo.message });
2043
3098
  });
2044
- const canvasContextProps = import_core23.Device._getCanvasContextProps(props);
3099
+ const canvasContextProps = import_core25.Device._getCanvasContextProps(props);
2045
3100
  if (canvasContextProps) {
2046
3101
  this.canvasContext = new WebGPUCanvasContext(this, this.adapter, canvasContextProps);
2047
3102
  }
@@ -2052,6 +3107,10 @@ var init_webgpu_device = __esm({
2052
3107
  // const {glsl = true} = props;
2053
3108
  // this.glslang = glsl && await loadGlslangModule();
2054
3109
  destroy() {
3110
+ var _a, _b;
3111
+ (_a = this.commandEncoder) == null ? void 0 : _a.destroy();
3112
+ (_b = this._defaultSampler) == null ? void 0 : _b.destroy();
3113
+ this._defaultSampler = null;
2055
3114
  this.handle.destroy();
2056
3115
  }
2057
3116
  get isLost() {
@@ -2080,6 +3139,12 @@ var init_webgpu_device = __esm({
2080
3139
  createSampler(props) {
2081
3140
  return new WebGPUSampler(this, props);
2082
3141
  }
3142
+ getDefaultSampler() {
3143
+ this._defaultSampler ||= new WebGPUSampler(this, {
3144
+ id: `${this.id}-default-sampler`
3145
+ });
3146
+ return this._defaultSampler;
3147
+ }
2083
3148
  createRenderPipeline(props) {
2084
3149
  return new WebGPURenderPipeline(this, props);
2085
3150
  }
@@ -2108,32 +3173,106 @@ var init_webgpu_device = __esm({
2108
3173
  createCanvasContext(props) {
2109
3174
  return new WebGPUCanvasContext(this, this.adapter, props);
2110
3175
  }
3176
+ createPresentationContext(props) {
3177
+ return new WebGPUPresentationContext(this, props);
3178
+ }
2111
3179
  createPipelineLayout(props) {
2112
3180
  return new WebGPUPipelineLayout(this, props);
2113
3181
  }
3182
+ generateMipmapsWebGPU(texture) {
3183
+ generateMipmapsWebGPU(this, texture);
3184
+ }
2114
3185
  submit(commandBuffer) {
3186
+ let submittedCommandEncoder = null;
2115
3187
  if (!commandBuffer) {
2116
- commandBuffer = this.commandEncoder.finish();
3188
+ submittedCommandEncoder = this.commandEncoder;
3189
+ if (submittedCommandEncoder.getTimeProfilingSlotCount() > 0 && submittedCommandEncoder.getTimeProfilingQuerySet() instanceof WebGPUQuerySet) {
3190
+ const querySet = submittedCommandEncoder.getTimeProfilingQuerySet();
3191
+ querySet._encodeResolveToReadBuffer(submittedCommandEncoder, {
3192
+ firstQuery: 0,
3193
+ queryCount: submittedCommandEncoder.getTimeProfilingSlotCount()
3194
+ });
3195
+ }
3196
+ commandBuffer = submittedCommandEncoder.finish();
2117
3197
  this.commandEncoder.destroy();
2118
- this.commandEncoder = this.createCommandEncoder({ id: `${this.id}-default-encoder` });
3198
+ this.commandEncoder = this.createCommandEncoder({
3199
+ id: submittedCommandEncoder.props.id,
3200
+ timeProfilingQuerySet: submittedCommandEncoder.getTimeProfilingQuerySet()
3201
+ });
3202
+ }
3203
+ const profiler = getCpuHotspotProfiler(this);
3204
+ const startTime = profiler ? getTimestamp() : 0;
3205
+ const submitReason = getCpuHotspotSubmitReason(this);
3206
+ try {
3207
+ this.pushErrorScope("validation");
3208
+ const queueSubmitStartTime = profiler ? getTimestamp() : 0;
3209
+ this.handle.queue.submit([commandBuffer.handle]);
3210
+ if (profiler) {
3211
+ profiler.queueSubmitCount = (profiler.queueSubmitCount || 0) + 1;
3212
+ profiler.queueSubmitTimeMs = (profiler.queueSubmitTimeMs || 0) + (getTimestamp() - queueSubmitStartTime);
3213
+ }
3214
+ this.popErrorScope((error) => {
3215
+ this.reportError(new Error(`${this} command submission: ${error.message}`), this)();
3216
+ this.debug();
3217
+ });
3218
+ if (submittedCommandEncoder) {
3219
+ const submitResolveKickoffStartTime = profiler ? getTimestamp() : 0;
3220
+ scheduleMicrotask(() => {
3221
+ submittedCommandEncoder.resolveTimeProfilingQuerySet().then(() => {
3222
+ this.commandEncoder._gpuTimeMs = submittedCommandEncoder._gpuTimeMs;
3223
+ }).catch(() => {
3224
+ });
3225
+ });
3226
+ if (profiler) {
3227
+ profiler.submitResolveKickoffCount = (profiler.submitResolveKickoffCount || 0) + 1;
3228
+ profiler.submitResolveKickoffTimeMs = (profiler.submitResolveKickoffTimeMs || 0) + (getTimestamp() - submitResolveKickoffStartTime);
3229
+ }
3230
+ }
3231
+ } finally {
3232
+ if (profiler) {
3233
+ profiler.submitCount = (profiler.submitCount || 0) + 1;
3234
+ profiler.submitTimeMs = (profiler.submitTimeMs || 0) + (getTimestamp() - startTime);
3235
+ const reasonCountKey = submitReason === "query-readback" ? "queryReadbackSubmitCount" : "defaultSubmitCount";
3236
+ const reasonTimeKey = submitReason === "query-readback" ? "queryReadbackSubmitTimeMs" : "defaultSubmitTimeMs";
3237
+ profiler[reasonCountKey] = (profiler[reasonCountKey] || 0) + 1;
3238
+ profiler[reasonTimeKey] = (profiler[reasonTimeKey] || 0) + (getTimestamp() - startTime);
3239
+ }
3240
+ const commandBufferDestroyStartTime = profiler ? getTimestamp() : 0;
3241
+ commandBuffer.destroy();
3242
+ if (profiler) {
3243
+ profiler.commandBufferDestroyCount = (profiler.commandBufferDestroyCount || 0) + 1;
3244
+ profiler.commandBufferDestroyTimeMs = (profiler.commandBufferDestroyTimeMs || 0) + (getTimestamp() - commandBufferDestroyStartTime);
3245
+ }
2119
3246
  }
2120
- this.pushErrorScope("validation");
2121
- this.handle.queue.submit([commandBuffer.handle]);
2122
- this.popErrorScope((error) => {
2123
- this.reportError(new Error(`${this} command submission: ${error.message}`), this)();
2124
- this.debug();
2125
- });
2126
3247
  }
2127
3248
  // WebGPU specific
2128
3249
  pushErrorScope(scope) {
3250
+ if (!this.props.debug) {
3251
+ return;
3252
+ }
3253
+ const profiler = getCpuHotspotProfiler(this);
3254
+ const startTime = profiler ? getTimestamp() : 0;
2129
3255
  this.handle.pushErrorScope(scope);
3256
+ if (profiler) {
3257
+ profiler.errorScopePushCount = (profiler.errorScopePushCount || 0) + 1;
3258
+ profiler.errorScopeTimeMs = (profiler.errorScopeTimeMs || 0) + (getTimestamp() - startTime);
3259
+ }
2130
3260
  }
2131
3261
  popErrorScope(handler) {
3262
+ if (!this.props.debug) {
3263
+ return;
3264
+ }
3265
+ const profiler = getCpuHotspotProfiler(this);
3266
+ const startTime = profiler ? getTimestamp() : 0;
2132
3267
  this.handle.popErrorScope().then((error) => {
2133
3268
  if (error) {
2134
3269
  handler(error);
2135
3270
  }
2136
3271
  });
3272
+ if (profiler) {
3273
+ profiler.errorScopePopCount = (profiler.errorScopePopCount || 0) + 1;
3274
+ profiler.errorScopeTimeMs = (profiler.errorScopeTimeMs || 0) + (getTimestamp() - startTime);
3275
+ }
2137
3276
  }
2138
3277
  // PRIVATE METHODS
2139
3278
  _getInfo() {
@@ -2174,7 +3313,6 @@ var init_webgpu_device = __esm({
2174
3313
  features.add("snorm16-renderable-webgl");
2175
3314
  }
2176
3315
  const WEBGPU_ALWAYS_FEATURES = [
2177
- "timer-query-webgl",
2178
3316
  "compilation-status-async-webgl",
2179
3317
  "float32-renderable-webgl",
2180
3318
  "float16-renderable-webgl",
@@ -2185,7 +3323,7 @@ var init_webgpu_device = __esm({
2185
3323
  for (const feature of WEBGPU_ALWAYS_FEATURES) {
2186
3324
  features.add(feature);
2187
3325
  }
2188
- return new import_core23.DeviceFeatures(Array.from(features), this.props._disabledFeatures);
3326
+ return new import_core25.DeviceFeatures(Array.from(features), this.props._disabledFeatures);
2189
3327
  }
2190
3328
  _getDeviceSpecificTextureFormatCapabilities(capabilities) {
2191
3329
  const { format } = capabilities;
@@ -2213,8 +3351,8 @@ __export(dist_exports, {
2213
3351
  module.exports = __toCommonJS(dist_exports);
2214
3352
 
2215
3353
  // dist/adapter/webgpu-adapter.js
2216
- var import_core24 = require("@luma.gl/core");
2217
- var WebGPUAdapter = class extends import_core24.Adapter {
3354
+ var import_core26 = require("@luma.gl/core");
3355
+ var WebGPUAdapter = class extends import_core26.Adapter {
2218
3356
  /** type of device's created by this adapter */
2219
3357
  type = "webgpu";
2220
3358
  isSupported() {
@@ -2261,14 +3399,14 @@ var WebGPUAdapter = class extends import_core24.Adapter {
2261
3399
  requiredLimits
2262
3400
  });
2263
3401
  const { WebGPUDevice: WebGPUDevice2 } = await Promise.resolve().then(() => (init_webgpu_device(), webgpu_device_exports));
2264
- import_core24.log.groupCollapsed(1, "WebGPUDevice created")();
3402
+ import_core26.log.groupCollapsed(1, "WebGPUDevice created")();
2265
3403
  try {
2266
3404
  const device = new WebGPUDevice2(props, gpuDevice, adapter, adapterInfo);
2267
- import_core24.log.probe(1, "Device created. For more info, set chrome://flags/#enable-webgpu-developer-features")();
2268
- import_core24.log.table(1, device.info)();
3405
+ import_core26.log.probe(1, "Device created. For more info, set chrome://flags/#enable-webgpu-developer-features")();
3406
+ import_core26.log.table(1, device.info)();
2269
3407
  return device;
2270
3408
  } finally {
2271
- import_core24.log.groupEnd(1)();
3409
+ import_core26.log.groupEnd(1)();
2272
3410
  }
2273
3411
  }
2274
3412
  async attach(handle) {