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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) 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 +4 -6
  10. package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
  11. package/dist/adapter/helpers/get-bind-group.js +31 -30
  12. package/dist/adapter/helpers/get-bind-group.js.map +1 -1
  13. package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts +3 -1
  14. package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts.map +1 -1
  15. package/dist/adapter/helpers/get-vertex-buffer-layout.js +17 -12
  16. package/dist/adapter/helpers/get-vertex-buffer-layout.js.map +1 -1
  17. package/dist/adapter/helpers/webgpu-parameters.d.ts.map +1 -1
  18. package/dist/adapter/helpers/webgpu-parameters.js +1 -0
  19. package/dist/adapter/helpers/webgpu-parameters.js.map +1 -1
  20. package/dist/adapter/resources/webgpu-buffer.d.ts.map +1 -1
  21. package/dist/adapter/resources/webgpu-buffer.js +19 -3
  22. package/dist/adapter/resources/webgpu-buffer.js.map +1 -1
  23. package/dist/adapter/resources/webgpu-command-buffer.js +1 -1
  24. package/dist/adapter/resources/webgpu-command-buffer.js.map +1 -1
  25. package/dist/adapter/resources/webgpu-command-encoder.d.ts +7 -16
  26. package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
  27. package/dist/adapter/resources/webgpu-command-encoder.js +89 -32
  28. package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
  29. package/dist/adapter/resources/webgpu-compute-pass.d.ts +3 -3
  30. package/dist/adapter/resources/webgpu-compute-pass.d.ts.map +1 -1
  31. package/dist/adapter/resources/webgpu-compute-pass.js +30 -12
  32. package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
  33. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts +7 -9
  34. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
  35. package/dist/adapter/resources/webgpu-compute-pipeline.js +30 -17
  36. package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
  37. package/dist/adapter/resources/webgpu-fence.d.ts.map +1 -1
  38. package/dist/adapter/resources/webgpu-fence.js +9 -1
  39. package/dist/adapter/resources/webgpu-fence.js.map +1 -1
  40. package/dist/adapter/resources/webgpu-framebuffer.d.ts +6 -0
  41. package/dist/adapter/resources/webgpu-framebuffer.d.ts.map +1 -1
  42. package/dist/adapter/resources/webgpu-framebuffer.js +16 -0
  43. package/dist/adapter/resources/webgpu-framebuffer.js.map +1 -1
  44. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts +1 -1
  45. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
  46. package/dist/adapter/resources/webgpu-pipeline-layout.js +10 -16
  47. package/dist/adapter/resources/webgpu-pipeline-layout.js.map +1 -1
  48. package/dist/adapter/resources/webgpu-query-set.d.ts +33 -4
  49. package/dist/adapter/resources/webgpu-query-set.d.ts.map +1 -1
  50. package/dist/adapter/resources/webgpu-query-set.js +145 -4
  51. package/dist/adapter/resources/webgpu-query-set.js.map +1 -1
  52. package/dist/adapter/resources/webgpu-render-pass.d.ts +6 -3
  53. package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
  54. package/dist/adapter/resources/webgpu-render-pass.js +78 -34
  55. package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
  56. package/dist/adapter/resources/webgpu-render-pipeline.d.ts +14 -10
  57. package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
  58. package/dist/adapter/resources/webgpu-render-pipeline.js +56 -35
  59. package/dist/adapter/resources/webgpu-render-pipeline.js.map +1 -1
  60. package/dist/adapter/resources/webgpu-sampler.d.ts.map +1 -1
  61. package/dist/adapter/resources/webgpu-sampler.js +4 -0
  62. package/dist/adapter/resources/webgpu-sampler.js.map +1 -1
  63. package/dist/adapter/resources/webgpu-shader.d.ts.map +1 -1
  64. package/dist/adapter/resources/webgpu-shader.js +17 -1
  65. package/dist/adapter/resources/webgpu-shader.js.map +1 -1
  66. package/dist/adapter/resources/webgpu-texture-view.d.ts +6 -0
  67. package/dist/adapter/resources/webgpu-texture-view.d.ts.map +1 -1
  68. package/dist/adapter/resources/webgpu-texture-view.js +47 -11
  69. package/dist/adapter/resources/webgpu-texture-view.js.map +1 -1
  70. package/dist/adapter/resources/webgpu-texture.d.ts +18 -5
  71. package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
  72. package/dist/adapter/resources/webgpu-texture.js +148 -97
  73. package/dist/adapter/resources/webgpu-texture.js.map +1 -1
  74. package/dist/adapter/resources/webgpu-vertex-array.js +1 -1
  75. package/dist/adapter/resources/webgpu-vertex-array.js.map +1 -1
  76. package/dist/adapter/webgpu-canvas-context.d.ts +2 -0
  77. package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
  78. package/dist/adapter/webgpu-canvas-context.js +78 -19
  79. package/dist/adapter/webgpu-canvas-context.js.map +1 -1
  80. package/dist/adapter/webgpu-device.d.ts +10 -2
  81. package/dist/adapter/webgpu-device.d.ts.map +1 -1
  82. package/dist/adapter/webgpu-device.js +159 -13
  83. package/dist/adapter/webgpu-device.js.map +1 -1
  84. package/dist/adapter/webgpu-presentation-context.d.ts +25 -0
  85. package/dist/adapter/webgpu-presentation-context.d.ts.map +1 -0
  86. package/dist/adapter/webgpu-presentation-context.js +144 -0
  87. package/dist/adapter/webgpu-presentation-context.js.map +1 -0
  88. package/dist/dist.dev.js +3180 -1849
  89. package/dist/dist.min.js +168 -9
  90. package/dist/index.cjs +1640 -405
  91. package/dist/index.cjs.map +4 -4
  92. package/dist/wgsl/get-shader-layout-wgsl.d.ts.map +1 -1
  93. package/dist/wgsl/get-shader-layout-wgsl.js +8 -0
  94. package/dist/wgsl/get-shader-layout-wgsl.js.map +1 -1
  95. package/package.json +5 -5
  96. package/src/adapter/helpers/cpu-hotspot-profiler.ts +70 -0
  97. package/src/adapter/helpers/generate-mipmaps-webgpu.ts +583 -0
  98. package/src/adapter/helpers/get-bind-group.ts +42 -49
  99. package/src/adapter/helpers/get-vertex-buffer-layout.ts +31 -12
  100. package/src/adapter/helpers/webgpu-parameters.ts +2 -0
  101. package/src/adapter/resources/webgpu-buffer.ts +18 -3
  102. package/src/adapter/resources/webgpu-command-buffer.ts +1 -1
  103. package/src/adapter/resources/webgpu-command-encoder.ts +129 -50
  104. package/src/adapter/resources/webgpu-compute-pass.ts +48 -13
  105. package/src/adapter/resources/webgpu-compute-pipeline.ts +49 -18
  106. package/src/adapter/resources/webgpu-fence.ts +11 -3
  107. package/src/adapter/resources/webgpu-framebuffer.ts +21 -0
  108. package/src/adapter/resources/webgpu-pipeline-layout.ts +16 -14
  109. package/src/adapter/resources/webgpu-query-set.ts +185 -9
  110. package/src/adapter/resources/webgpu-render-pass.ts +92 -40
  111. package/src/adapter/resources/webgpu-render-pipeline.ts +83 -44
  112. package/src/adapter/resources/webgpu-sampler.ts +5 -0
  113. package/src/adapter/resources/webgpu-shader.ts +16 -1
  114. package/src/adapter/resources/webgpu-texture-view.ts +51 -11
  115. package/src/adapter/resources/webgpu-texture.ts +198 -132
  116. package/src/adapter/resources/webgpu-vertex-array.ts +1 -1
  117. package/src/adapter/webgpu-canvas-context.ts +91 -26
  118. package/src/adapter/webgpu-device.ts +212 -17
  119. package/src/adapter/webgpu-presentation-context.ts +180 -0
  120. package/src/wgsl/get-shader-layout-wgsl.ts +9 -0
  121. package/dist/adapter/helpers/accessor-to-format.d.ts +0 -1
  122. package/dist/adapter/helpers/accessor-to-format.d.ts.map +0 -1
  123. package/dist/adapter/helpers/accessor-to-format.js +0 -105
  124. package/dist/adapter/helpers/accessor-to-format.js.map +0 -1
  125. package/src/adapter/helpers/accessor-to-format.ts +0 -104
@@ -40,6 +40,11 @@ export class WebGPUSampler extends Sampler {
40
40
  }
41
41
 
42
42
  override destroy(): void {
43
+ if (this.destroyed) {
44
+ return;
45
+ }
46
+
47
+ this.destroyResource();
43
48
  // GPUSampler does not have a destroy method
44
49
  // this.handle.destroy();
45
50
  // @ts-expect-error readonly
@@ -64,7 +64,22 @@ export class WebGPUShader extends Shader {
64
64
 
65
65
  /** Returns compilation info for this shader */
66
66
  async getCompilationInfo(): Promise<readonly CompilerMessage[]> {
67
- const compilationInfo = await this.handle.getCompilationInfo();
67
+ // `_checkCompilationError()` runs asynchronously after construction, so the shader can be
68
+ // destroyed before we await compilation info. Snapshot the handle and treat a destroyed shader
69
+ // as having no compiler messages instead of dereferencing `null`.
70
+ const handle = this.handle;
71
+ if (!handle) {
72
+ return [];
73
+ }
74
+ let compilationInfo;
75
+ try {
76
+ compilationInfo = await handle.getCompilationInfo();
77
+ } catch (error) {
78
+ if (this.device.shouldIgnoreDroppedInstanceError(error, 'getCompilationInfo')) {
79
+ return [];
80
+ }
81
+ throw error;
82
+ }
68
83
  return compilationInfo.messages;
69
84
  }
70
85
  }
@@ -5,6 +5,7 @@
5
5
  import {TextureView, TextureViewProps} from '@luma.gl/core';
6
6
  import type {WebGPUDevice} from '../webgpu-device';
7
7
  import type {WebGPUTexture} from './webgpu-texture';
8
+ import {getCpuHotspotProfiler, getTimestamp} from '../helpers/cpu-hotspot-profiler';
8
9
 
9
10
  /*
10
11
  // type = sampler
@@ -39,17 +40,15 @@ export class WebGPUTextureView extends TextureView {
39
40
  this.texture = props.texture;
40
41
 
41
42
  this.device.pushErrorScope('validation');
42
- this.handle =
43
- // props.handle ||
44
- this.texture.handle.createView({
45
- format: (this.props.format || this.texture.format) as GPUTextureFormat,
46
- dimension: this.props.dimension || this.texture.dimension,
47
- aspect: this.props.aspect,
48
- baseMipLevel: this.props.baseMipLevel,
49
- mipLevelCount: this.props.mipLevelCount,
50
- baseArrayLayer: this.props.baseArrayLayer,
51
- arrayLayerCount: this.props.arrayLayerCount
52
- });
43
+ this.handle = this.texture.handle.createView({
44
+ format: (this.props.format || this.texture.format) as GPUTextureFormat,
45
+ dimension: this.props.dimension || this.texture.dimension,
46
+ aspect: this.props.aspect,
47
+ baseMipLevel: this.props.baseMipLevel,
48
+ mipLevelCount: this.props.mipLevelCount,
49
+ baseArrayLayer: this.props.baseArrayLayer,
50
+ arrayLayerCount: this.props.arrayLayerCount
51
+ });
53
52
  this.device.popErrorScope((error: GPUError) => {
54
53
  this.device.reportError(new Error(`TextureView constructor: ${error.message}`), this)();
55
54
  this.device.debug();
@@ -59,9 +58,50 @@ export class WebGPUTextureView extends TextureView {
59
58
  }
60
59
 
61
60
  override destroy(): void {
61
+ if (this.destroyed) {
62
+ return;
63
+ }
64
+
65
+ this.destroyResource();
62
66
  // GPUTextureView does not have a destroy method
63
67
  // this.handle.destroy();
64
68
  // @ts-expect-error readonly
65
69
  this.handle = null;
66
70
  }
71
+
72
+ /**
73
+ * Internal-only hook for the cached CanvasContext/PresentationContext swapchain path.
74
+ * Rebuilds the default view when the per-frame canvas texture handle changes, without
75
+ * replacing the long-lived luma.gl wrapper object.
76
+ */
77
+ _reinitialize(texture: WebGPUTexture): void {
78
+ // @ts-expect-error readonly
79
+ this.texture = texture;
80
+
81
+ const profiler = getCpuHotspotProfiler(this.device);
82
+ this.device.pushErrorScope('validation');
83
+ const createViewStartTime = profiler ? getTimestamp() : 0;
84
+ const handle = this.texture.handle.createView({
85
+ format: (this.props.format || this.texture.format) as GPUTextureFormat,
86
+ dimension: this.props.dimension || this.texture.dimension,
87
+ aspect: this.props.aspect,
88
+ baseMipLevel: this.props.baseMipLevel,
89
+ mipLevelCount: this.props.mipLevelCount,
90
+ baseArrayLayer: this.props.baseArrayLayer,
91
+ arrayLayerCount: this.props.arrayLayerCount
92
+ });
93
+ if (profiler) {
94
+ profiler.textureViewReinitializeCount = (profiler.textureViewReinitializeCount || 0) + 1;
95
+ profiler.textureViewReinitializeTimeMs =
96
+ (profiler.textureViewReinitializeTimeMs || 0) + (getTimestamp() - createViewStartTime);
97
+ }
98
+ this.device.popErrorScope((error: GPUError) => {
99
+ this.device.reportError(new Error(`TextureView constructor: ${error.message}`), this)();
100
+ this.device.debug();
101
+ });
102
+
103
+ handle.label = this.props.id;
104
+ // @ts-expect-error readonly
105
+ this.handle = handle;
106
+ }
67
107
  }
@@ -3,7 +3,6 @@ import {
3
3
  type TextureProps,
4
4
  type TextureViewProps,
5
5
  type CopyExternalImageOptions,
6
- type CopyImageDataOptions,
7
6
  type TextureReadOptions,
8
7
  type TextureWriteOptions,
9
8
  type SamplerProps,
@@ -17,6 +16,7 @@ import {getWebGPUTextureFormat} from '../helpers/convert-texture-format';
17
16
  import type {WebGPUDevice} from '../webgpu-device';
18
17
  import {WebGPUSampler} from './webgpu-sampler';
19
18
  import {WebGPUTextureView} from './webgpu-texture-view';
19
+ import {WebGPUBuffer} from './webgpu-buffer';
20
20
 
21
21
  /** WebGPU implementation of the luma.gl core Texture resource */
22
22
  export class WebGPUTexture extends Texture {
@@ -24,11 +24,22 @@ export class WebGPUTexture extends Texture {
24
24
  readonly handle: GPUTexture;
25
25
  sampler: WebGPUSampler;
26
26
  view: WebGPUTextureView;
27
+ private _allocatedByteLength: number = 0;
27
28
 
28
29
  constructor(device: WebGPUDevice, props: TextureProps) {
29
- super(device, props, {byteAlignment: 256}); // WebGPU requires row width to be a multiple of 256 bytes
30
+ // WebGPU buffer copies use 256-byte row alignment. queue.writeTexture() can use tightly packed rows.
31
+ super(device, props, {byteAlignment: 256});
30
32
  this.device = device;
31
33
 
34
+ if (props.sampler instanceof WebGPUSampler) {
35
+ this.sampler = props.sampler;
36
+ } else if (props.sampler === undefined) {
37
+ this.sampler = this.device.getDefaultSampler();
38
+ } else {
39
+ this.sampler = new WebGPUSampler(this.device, (props.sampler as SamplerProps) || {});
40
+ this.attachResource(this.sampler);
41
+ }
42
+
32
43
  this.device.pushErrorScope('out-of-memory');
33
44
  this.device.pushErrorScope('validation');
34
45
 
@@ -56,8 +67,6 @@ export class WebGPUTexture extends Texture {
56
67
  this.device.debug();
57
68
  });
58
69
 
59
- // Update props if external handle was supplied - used mainly by CanvasContext.getDefaultFramebuffer()
60
- // TODO - Read all properties directly from the supplied handle?
61
70
  if (this.props.handle) {
62
71
  this.handle.label ||= this.id;
63
72
  // @ts-expect-error readonly
@@ -66,11 +75,6 @@ export class WebGPUTexture extends Texture {
66
75
  this.height = this.handle.height;
67
76
  }
68
77
 
69
- this.sampler =
70
- props.sampler instanceof WebGPUSampler
71
- ? props.sampler
72
- : new WebGPUSampler(this.device, (props.sampler as SamplerProps) || {});
73
-
74
78
  this.view = new WebGPUTextureView(this.device, {
75
79
  ...this.props,
76
80
  texture: this,
@@ -78,14 +82,34 @@ export class WebGPUTexture extends Texture {
78
82
  // Note: arrayLayerCount controls the view of array textures, but does not apply to 3d texture depths
79
83
  arrayLayerCount: this.dimension !== '3d' ? this.depth : 1
80
84
  });
85
+ this.attachResource(this.view);
81
86
 
82
87
  // Set initial data
83
88
  // Texture base class strips out the data prop from this.props, so we need to handle it here
84
89
  this._initializeData(props.data);
90
+
91
+ this._allocatedByteLength = this.getAllocatedByteLength();
92
+
93
+ if (!this.props.handle) {
94
+ this.trackAllocatedMemory(this._allocatedByteLength, 'Texture');
95
+ } else {
96
+ this.trackReferencedMemory(this._allocatedByteLength, 'Texture');
97
+ }
85
98
  }
86
99
 
87
100
  override destroy(): void {
88
- this.handle?.destroy();
101
+ if (this.destroyed) {
102
+ return;
103
+ }
104
+
105
+ if (!this.props.handle && this.handle) {
106
+ this.trackDeallocatedMemory('Texture');
107
+ this.handle.destroy();
108
+ } else if (this.handle) {
109
+ this.trackDeallocatedReferencedMemory('Texture');
110
+ }
111
+
112
+ this.destroyResource();
89
113
  // @ts-expect-error readonly
90
114
  this.handle = null;
91
115
  }
@@ -126,38 +150,6 @@ export class WebGPUTexture extends Texture {
126
150
  return {width: options.width, height: options.height};
127
151
  }
128
152
 
129
- copyImageData(options_: CopyImageDataOptions): void {
130
- const {width, height, depth} = this;
131
- const options = this._normalizeCopyImageDataOptions(options_);
132
- this.device.pushErrorScope('validation');
133
-
134
- this.device.handle.queue.writeTexture(
135
- // destination: GPUImageCopyTexture
136
- {
137
- // texture subresource
138
- texture: this.handle,
139
- mipLevel: options.mipLevel,
140
- aspect: options.aspect,
141
- // origin to write to
142
- origin: [options.x, options.y, options.z]
143
- },
144
- // data
145
- options.data,
146
- // dataLayout: GPUImageDataLayout
147
- {
148
- offset: options.byteOffset,
149
- bytesPerRow: options.bytesPerRow,
150
- rowsPerImage: options.rowsPerImage
151
- },
152
- // size: GPUExtent3D - extents of the content to write
153
- [width, height, depth]
154
- );
155
- this.device.popErrorScope((error: GPUError) => {
156
- this.device.reportError(new Error(`copyImageData: ${error.message}`), this)();
157
- this.device.debug();
158
- });
159
- }
160
-
161
153
  override generateMipmapsWebGL(): void {
162
154
  log.warn(`${this}: generateMipmaps not supported in WebGPU`)();
163
155
  }
@@ -174,99 +166,111 @@ export class WebGPUTexture extends Texture {
174
166
  };
175
167
  }
176
168
 
177
- override readBuffer(options: TextureReadOptions = {}, buffer?: Buffer): Buffer {
178
- const {
179
- x = 0,
180
- y = 0,
181
- z = 0,
182
- width = this.width,
183
- height = this.height,
184
- depthOrArrayLayers = this.depth,
185
- mipLevel = 0,
186
- aspect = 'all'
187
- } = options;
169
+ override readBuffer(
170
+ options: TextureReadOptions & {byteOffset?: number} = {},
171
+ buffer?: Buffer
172
+ ): Buffer {
173
+ if (!buffer) {
174
+ throw new Error(`${this} readBuffer requires a destination buffer`);
175
+ }
176
+ const {x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect} =
177
+ this._getSupportedColorReadOptions(options);
178
+ const byteOffset = options.byteOffset ?? 0;
188
179
 
189
- const layout = this.computeMemoryLayout(options);
180
+ const layout = this.computeMemoryLayout({width, height, depthOrArrayLayers, mipLevel});
190
181
 
191
- const {bytesPerRow, rowsPerImage, byteLength} = layout;
182
+ const {byteLength} = layout;
192
183
 
193
- // Create a GPUBuffer to hold the copied pixel data.
194
- const readBuffer =
195
- buffer ||
196
- this.device.createBuffer({
197
- byteLength,
198
- usage: Buffer.COPY_DST | Buffer.MAP_READ
199
- });
200
- const gpuReadBuffer = readBuffer.handle as GPUBuffer;
184
+ if (buffer.byteLength < byteOffset + byteLength) {
185
+ throw new Error(
186
+ `${this} readBuffer target is too small (${buffer.byteLength} < ${byteOffset + byteLength})`
187
+ );
188
+ }
201
189
 
202
- // Record commands to copy from the texture to the buffer.
203
190
  const gpuDevice = this.device.handle;
204
-
205
191
  this.device.pushErrorScope('validation');
206
192
  const commandEncoder = gpuDevice.createCommandEncoder();
193
+ this.copyToBuffer(
194
+ commandEncoder,
195
+ {x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect, byteOffset},
196
+ buffer
197
+ );
198
+
199
+ const commandBuffer = commandEncoder.finish();
200
+ this.device.handle.queue.submit([commandBuffer]);
201
+ this.device.popErrorScope((error: GPUError) => {
202
+ this.device.reportError(new Error(`${this} readBuffer: ${error.message}`), this)();
203
+ this.device.debug();
204
+ });
205
+
206
+ return buffer;
207
+ }
208
+
209
+ override async readDataAsync(options: TextureReadOptions = {}): Promise<ArrayBuffer> {
210
+ throw new Error(
211
+ `${this} readDataAsync is deprecated; use readBuffer() with an explicit destination buffer or DynamicTexture.readAsync()`
212
+ );
213
+ }
214
+
215
+ copyToBuffer(
216
+ commandEncoder: GPUCommandEncoder,
217
+ options: TextureReadOptions & {
218
+ byteOffset?: number;
219
+ bytesPerRow?: number;
220
+ rowsPerImage?: number;
221
+ } = {},
222
+ buffer: Buffer
223
+ ): void {
224
+ const {
225
+ byteOffset = 0,
226
+ bytesPerRow: requestedBytesPerRow,
227
+ rowsPerImage: requestedRowsPerImage,
228
+ ...textureReadOptions
229
+ } = options;
230
+ const {x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect} =
231
+ this._getSupportedColorReadOptions(textureReadOptions);
232
+ const layout = this.computeMemoryLayout({width, height, depthOrArrayLayers, mipLevel});
233
+ const effectiveBytesPerRow = requestedBytesPerRow ?? layout.bytesPerRow;
234
+ const effectiveRowsPerImage = requestedRowsPerImage ?? layout.rowsPerImage;
235
+ const webgpuBuffer = buffer as WebGPUBuffer;
236
+
207
237
  commandEncoder.copyTextureToBuffer(
208
- // source
209
238
  {
210
239
  texture: this.handle,
211
240
  origin: {x, y, z},
212
- // origin: [options.x, options.y, 0], // options.depth],
213
241
  mipLevel,
214
242
  aspect
215
- // colorSpace: options.colorSpace,
216
- // premultipliedAlpha: options.premultipliedAlpha
217
243
  },
218
- // destination
219
244
  {
220
- buffer: gpuReadBuffer,
221
- offset: 0,
222
- bytesPerRow,
223
- rowsPerImage
245
+ buffer: webgpuBuffer.handle,
246
+ offset: byteOffset,
247
+ bytesPerRow: effectiveBytesPerRow,
248
+ rowsPerImage: effectiveRowsPerImage
224
249
  },
225
- // copy size
226
250
  {
227
251
  width,
228
252
  height,
229
253
  depthOrArrayLayers
230
254
  }
231
255
  );
232
-
233
- // Submit the command.
234
- const commandBuffer = commandEncoder.finish();
235
- this.device.handle.queue.submit([commandBuffer]);
236
- this.device.popErrorScope((error: GPUError) => {
237
- this.device.reportError(new Error(`${this} readBuffer: ${error.message}`), this)();
238
- this.device.debug();
239
- });
240
-
241
- return readBuffer;
242
- }
243
-
244
- override async readDataAsync(options: TextureReadOptions = {}): Promise<ArrayBuffer> {
245
- const buffer = this.readBuffer(options);
246
- const data = await buffer.readAsync();
247
- buffer.destroy();
248
- return data.buffer as ArrayBuffer;
249
256
  }
250
257
 
251
- override writeBuffer(buffer: Buffer, options: TextureWriteOptions = {}) {
258
+ override writeBuffer(buffer: Buffer, options_: TextureWriteOptions = {}) {
259
+ const options = this._normalizeTextureWriteOptions(options_);
252
260
  const {
253
- x = 0,
254
- y = 0,
255
- z = 0,
256
- width = this.width,
257
- height = this.height,
258
- depthOrArrayLayers = this.depth,
259
- mipLevel = 0,
260
- aspect = 'all'
261
+ x,
262
+ y,
263
+ z,
264
+ width,
265
+ height,
266
+ depthOrArrayLayers,
267
+ mipLevel,
268
+ aspect,
269
+ byteOffset,
270
+ bytesPerRow,
271
+ rowsPerImage
261
272
  } = options;
262
273
 
263
- const layout = this.computeMemoryLayout(options);
264
-
265
- // Get the data on the CPU.
266
- // await buffer.mapAndReadAsync();
267
-
268
- const {bytesPerRow, rowsPerImage} = layout;
269
-
270
274
  const gpuDevice = this.device.handle;
271
275
 
272
276
  this.device.pushErrorScope('validation');
@@ -274,7 +278,7 @@ export class WebGPUTexture extends Texture {
274
278
  commandEncoder.copyBufferToTexture(
275
279
  {
276
280
  buffer: buffer.handle as GPUBuffer,
277
- offset: 0,
281
+ offset: byteOffset,
278
282
  bytesPerRow,
279
283
  rowsPerImage
280
284
  },
@@ -294,29 +298,34 @@ export class WebGPUTexture extends Texture {
294
298
  });
295
299
  }
296
300
 
297
- override writeData(data: ArrayBuffer | ArrayBufferView, options: TextureWriteOptions = {}): void {
301
+ override writeData(
302
+ data: ArrayBuffer | SharedArrayBuffer | ArrayBufferView,
303
+ options_: TextureWriteOptions = {}
304
+ ): void {
298
305
  const device = this.device;
299
-
300
- const {
301
- x = 0,
302
- y = 0,
303
- z = 0,
304
- width = this.width,
305
- height = this.height,
306
- depthOrArrayLayers = this.depth,
307
- mipLevel = 0,
308
- aspect = 'all'
309
- } = options;
310
-
311
- const layout = textureFormatDecoder.computeMemoryLayout({
306
+ const options = this._normalizeTextureWriteOptions(options_);
307
+ const {x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect, byteOffset} = options;
308
+ const source = data as GPUAllowSharedBufferSource;
309
+ const formatInfo = this.device.getTextureFormatInfo(this.format);
310
+ // queue.writeTexture() defaults to tightly packed rows, unlike WebGPU buffer copy paths.
311
+ const packedSourceLayout = textureFormatDecoder.computeMemoryLayout({
312
312
  format: this.format,
313
- width: this.width,
314
- height: this.height,
315
- depth: this.depth,
316
- byteAlignment: this.byteAlignment
313
+ width,
314
+ height,
315
+ depth: depthOrArrayLayers,
316
+ byteAlignment: 1
317
317
  });
318
-
319
- const {bytesPerRow, rowsPerImage} = layout;
318
+ const bytesPerRow = options_.bytesPerRow ?? packedSourceLayout.bytesPerRow;
319
+ const rowsPerImage = options_.rowsPerImage ?? packedSourceLayout.rowsPerImage;
320
+ let copyWidth = width;
321
+ let copyHeight = height;
322
+
323
+ if (formatInfo.compressed) {
324
+ const blockWidth = formatInfo.blockWidth || 1;
325
+ const blockHeight = formatInfo.blockHeight || 1;
326
+ copyWidth = Math.ceil(width / blockWidth) * blockWidth;
327
+ copyHeight = Math.ceil(height / blockHeight) * blockHeight;
328
+ }
320
329
 
321
330
  this.device.pushErrorScope('validation');
322
331
  device.handle.queue.writeTexture(
@@ -326,17 +335,74 @@ export class WebGPUTexture extends Texture {
326
335
  aspect,
327
336
  origin: {x, y, z}
328
337
  },
329
- data,
338
+ source,
330
339
  {
331
- offset: 0,
340
+ offset: byteOffset,
332
341
  bytesPerRow,
333
342
  rowsPerImage
334
343
  },
335
- {width, height, depthOrArrayLayers}
344
+ {width: copyWidth, height: copyHeight, depthOrArrayLayers}
336
345
  );
337
346
  this.device.popErrorScope((error: GPUError) => {
338
347
  this.device.reportError(new Error(`${this} writeData: ${error.message}`), this)();
339
348
  this.device.debug();
340
349
  });
341
350
  }
351
+
352
+ /**
353
+ * Internal-only hook for the cached CanvasContext/PresentationContext swapchain path.
354
+ * Rebinds this handle-backed texture wrapper to the current per-frame canvas texture
355
+ * without allocating a new luma.gl Texture or TextureView wrapper.
356
+ */
357
+ _reinitialize(handle: GPUTexture, props?: Partial<TextureProps>): void {
358
+ const nextWidth = props?.width ?? handle.width ?? this.width;
359
+ const nextHeight = props?.height ?? handle.height ?? this.height;
360
+ const nextDepth = props?.depth ?? this.depth;
361
+ const nextFormat = props?.format ?? this.format;
362
+ const allocationMayHaveChanged =
363
+ nextWidth !== this.width ||
364
+ nextHeight !== this.height ||
365
+ nextDepth !== this.depth ||
366
+ nextFormat !== this.format;
367
+ handle.label ||= this.id;
368
+
369
+ // @ts-expect-error readonly
370
+ this.handle = handle;
371
+ // @ts-expect-error readonly
372
+ this.width = nextWidth;
373
+ // @ts-expect-error readonly
374
+ this.height = nextHeight;
375
+
376
+ if (props?.depth !== undefined) {
377
+ // @ts-expect-error readonly
378
+ this.depth = nextDepth;
379
+ }
380
+ if (props?.format !== undefined) {
381
+ // @ts-expect-error readonly
382
+ this.format = nextFormat;
383
+ }
384
+
385
+ this.props.handle = handle;
386
+ if (props?.width !== undefined) {
387
+ this.props.width = props.width;
388
+ }
389
+ if (props?.height !== undefined) {
390
+ this.props.height = props.height;
391
+ }
392
+ if (props?.depth !== undefined) {
393
+ this.props.depth = props.depth;
394
+ }
395
+ if (props?.format !== undefined) {
396
+ this.props.format = props.format;
397
+ }
398
+
399
+ if (allocationMayHaveChanged) {
400
+ const nextAllocation = this.getAllocatedByteLength();
401
+ if (nextAllocation !== this._allocatedByteLength) {
402
+ this._allocatedByteLength = nextAllocation;
403
+ this.trackReferencedMemory(nextAllocation, 'Texture');
404
+ }
405
+ }
406
+ this.view._reinitialize(this);
407
+ }
342
408
  }
@@ -14,7 +14,7 @@ import {WebGPURenderPass} from './webgpu-render-pass';
14
14
  /** VertexArrayObject wrapper */
15
15
  export class WebGPUVertexArray extends VertexArray {
16
16
  override get [Symbol.toStringTag](): string {
17
- return 'WebGPUVertexArray';
17
+ return 'VertexArray';
18
18
  }
19
19
 
20
20
  readonly device: WebGPUDevice;