@luma.gl/webgpu 9.2.5 → 9.3.0-alpha.10

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 (137) 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 +39 -32
  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 +7 -0
  21. package/dist/adapter/resources/webgpu-buffer.d.ts.map +1 -1
  22. package/dist/adapter/resources/webgpu-buffer.js +58 -15
  23. package/dist/adapter/resources/webgpu-buffer.js.map +1 -1
  24. package/dist/adapter/resources/webgpu-command-buffer.js +1 -1
  25. package/dist/adapter/resources/webgpu-command-buffer.js.map +1 -1
  26. package/dist/adapter/resources/webgpu-command-encoder.d.ts +7 -16
  27. package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
  28. package/dist/adapter/resources/webgpu-command-encoder.js +89 -32
  29. package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
  30. package/dist/adapter/resources/webgpu-compute-pass.d.ts +3 -3
  31. package/dist/adapter/resources/webgpu-compute-pass.d.ts.map +1 -1
  32. package/dist/adapter/resources/webgpu-compute-pass.js +30 -12
  33. package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
  34. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts +7 -9
  35. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
  36. package/dist/adapter/resources/webgpu-compute-pipeline.js +30 -17
  37. package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
  38. package/dist/adapter/resources/webgpu-fence.d.ts +13 -0
  39. package/dist/adapter/resources/webgpu-fence.d.ts.map +1 -0
  40. package/dist/adapter/resources/webgpu-fence.js +33 -0
  41. package/dist/adapter/resources/webgpu-fence.js.map +1 -0
  42. package/dist/adapter/resources/webgpu-framebuffer.d.ts +6 -0
  43. package/dist/adapter/resources/webgpu-framebuffer.d.ts.map +1 -1
  44. package/dist/adapter/resources/webgpu-framebuffer.js +16 -0
  45. package/dist/adapter/resources/webgpu-framebuffer.js.map +1 -1
  46. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts +1 -1
  47. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
  48. package/dist/adapter/resources/webgpu-pipeline-layout.js +11 -18
  49. package/dist/adapter/resources/webgpu-pipeline-layout.js.map +1 -1
  50. package/dist/adapter/resources/webgpu-query-set.d.ts +33 -4
  51. package/dist/adapter/resources/webgpu-query-set.d.ts.map +1 -1
  52. package/dist/adapter/resources/webgpu-query-set.js +145 -4
  53. package/dist/adapter/resources/webgpu-query-set.js.map +1 -1
  54. package/dist/adapter/resources/webgpu-render-pass.d.ts +6 -3
  55. package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
  56. package/dist/adapter/resources/webgpu-render-pass.js +78 -34
  57. package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
  58. package/dist/adapter/resources/webgpu-render-pipeline.d.ts +14 -10
  59. package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
  60. package/dist/adapter/resources/webgpu-render-pipeline.js +56 -35
  61. package/dist/adapter/resources/webgpu-render-pipeline.js.map +1 -1
  62. package/dist/adapter/resources/webgpu-sampler.d.ts.map +1 -1
  63. package/dist/adapter/resources/webgpu-sampler.js +4 -0
  64. package/dist/adapter/resources/webgpu-sampler.js.map +1 -1
  65. package/dist/adapter/resources/webgpu-shader.d.ts.map +1 -1
  66. package/dist/adapter/resources/webgpu-shader.js +17 -1
  67. package/dist/adapter/resources/webgpu-shader.js.map +1 -1
  68. package/dist/adapter/resources/webgpu-texture-view.d.ts +6 -0
  69. package/dist/adapter/resources/webgpu-texture-view.d.ts.map +1 -1
  70. package/dist/adapter/resources/webgpu-texture-view.js +47 -11
  71. package/dist/adapter/resources/webgpu-texture-view.js.map +1 -1
  72. package/dist/adapter/resources/webgpu-texture.d.ts +25 -3
  73. package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
  74. package/dist/adapter/resources/webgpu-texture.js +211 -43
  75. package/dist/adapter/resources/webgpu-texture.js.map +1 -1
  76. package/dist/adapter/resources/webgpu-vertex-array.js +1 -1
  77. package/dist/adapter/resources/webgpu-vertex-array.js.map +1 -1
  78. package/dist/adapter/webgpu-adapter.d.ts.map +1 -1
  79. package/dist/adapter/webgpu-adapter.js +34 -34
  80. package/dist/adapter/webgpu-adapter.js.map +1 -1
  81. package/dist/adapter/webgpu-canvas-context.d.ts +6 -3
  82. package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
  83. package/dist/adapter/webgpu-canvas-context.js +90 -30
  84. package/dist/adapter/webgpu-canvas-context.js.map +1 -1
  85. package/dist/adapter/webgpu-device.d.ts +12 -2
  86. package/dist/adapter/webgpu-device.d.ts.map +1 -1
  87. package/dist/adapter/webgpu-device.js +173 -16
  88. package/dist/adapter/webgpu-device.js.map +1 -1
  89. package/dist/adapter/webgpu-presentation-context.d.ts +25 -0
  90. package/dist/adapter/webgpu-presentation-context.d.ts.map +1 -0
  91. package/dist/adapter/webgpu-presentation-context.js +144 -0
  92. package/dist/adapter/webgpu-presentation-context.js.map +1 -0
  93. package/dist/dist.dev.js +8070 -547
  94. package/dist/dist.min.js +169 -6
  95. package/dist/index.cjs +1929 -410
  96. package/dist/index.cjs.map +4 -4
  97. package/dist/index.d.ts +2 -0
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +2 -0
  100. package/dist/index.js.map +1 -1
  101. package/dist/wgsl/get-shader-layout-wgsl.d.ts +8 -0
  102. package/dist/wgsl/get-shader-layout-wgsl.d.ts.map +1 -0
  103. package/dist/wgsl/get-shader-layout-wgsl.js +144 -0
  104. package/dist/wgsl/get-shader-layout-wgsl.js.map +1 -0
  105. package/package.json +6 -5
  106. package/src/adapter/helpers/cpu-hotspot-profiler.ts +70 -0
  107. package/src/adapter/helpers/generate-mipmaps-webgpu.ts +583 -0
  108. package/src/adapter/helpers/get-bind-group.ts +52 -46
  109. package/src/adapter/helpers/get-vertex-buffer-layout.ts +31 -12
  110. package/src/adapter/helpers/webgpu-parameters.ts +2 -0
  111. package/src/adapter/resources/webgpu-buffer.ts +61 -15
  112. package/src/adapter/resources/webgpu-command-buffer.ts +1 -1
  113. package/src/adapter/resources/webgpu-command-encoder.ts +129 -50
  114. package/src/adapter/resources/webgpu-compute-pass.ts +48 -13
  115. package/src/adapter/resources/webgpu-compute-pipeline.ts +49 -18
  116. package/src/adapter/resources/webgpu-fence.ts +38 -0
  117. package/src/adapter/resources/webgpu-framebuffer.ts +21 -0
  118. package/src/adapter/resources/webgpu-pipeline-layout.ts +18 -17
  119. package/src/adapter/resources/webgpu-query-set.ts +185 -9
  120. package/src/adapter/resources/webgpu-render-pass.ts +92 -40
  121. package/src/adapter/resources/webgpu-render-pipeline.ts +83 -44
  122. package/src/adapter/resources/webgpu-sampler.ts +5 -0
  123. package/src/adapter/resources/webgpu-shader.ts +16 -1
  124. package/src/adapter/resources/webgpu-texture-view.ts +51 -11
  125. package/src/adapter/resources/webgpu-texture.ts +281 -101
  126. package/src/adapter/resources/webgpu-vertex-array.ts +1 -1
  127. package/src/adapter/webgpu-adapter.ts +40 -42
  128. package/src/adapter/webgpu-canvas-context.ts +107 -40
  129. package/src/adapter/webgpu-device.ts +231 -21
  130. package/src/adapter/webgpu-presentation-context.ts +180 -0
  131. package/src/index.ts +3 -0
  132. package/src/wgsl/get-shader-layout-wgsl.ts +165 -0
  133. package/dist/adapter/helpers/accessor-to-format.d.ts +0 -1
  134. package/dist/adapter/helpers/accessor-to-format.d.ts.map +0 -1
  135. package/dist/adapter/helpers/accessor-to-format.js +0 -105
  136. package/dist/adapter/helpers/accessor-to-format.js.map +0 -1
  137. package/src/adapter/helpers/accessor-to-format.ts +0 -104
@@ -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
  }
@@ -1,30 +1,43 @@
1
1
  // luma.gl, MIT license
2
- import type {
3
- TextureProps,
4
- TextureViewProps,
5
- CopyExternalImageOptions,
6
- CopyImageDataOptions,
7
- SamplerProps
2
+ import {
3
+ type TextureProps,
4
+ type TextureViewProps,
5
+ type CopyExternalImageOptions,
6
+ type TextureReadOptions,
7
+ type TextureWriteOptions,
8
+ type SamplerProps,
9
+ Buffer,
10
+ Texture,
11
+ log,
12
+ textureFormatDecoder
8
13
  } from '@luma.gl/core';
9
- import {Texture, log} from '@luma.gl/core';
10
14
 
11
15
  import {getWebGPUTextureFormat} from '../helpers/convert-texture-format';
12
16
  import type {WebGPUDevice} from '../webgpu-device';
13
17
  import {WebGPUSampler} from './webgpu-sampler';
14
18
  import {WebGPUTextureView} from './webgpu-texture-view';
19
+ import {WebGPUBuffer} from './webgpu-buffer';
15
20
 
21
+ /** WebGPU implementation of the luma.gl core Texture resource */
16
22
  export class WebGPUTexture extends Texture {
17
23
  readonly device: WebGPUDevice;
18
24
  readonly handle: GPUTexture;
19
25
  sampler: WebGPUSampler;
20
26
  view: WebGPUTextureView;
27
+ private _allocatedByteLength: number = 0;
21
28
 
22
29
  constructor(device: WebGPUDevice, props: TextureProps) {
23
- super(device, props);
30
+ // WebGPU buffer copies use 256-byte row alignment. queue.writeTexture() can use tightly packed rows.
31
+ super(device, props, {byteAlignment: 256});
24
32
  this.device = device;
25
33
 
26
- if (this.dimension === 'cube') {
27
- this.depth = 6;
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);
28
41
  }
29
42
 
30
43
  this.device.pushErrorScope('out-of-memory');
@@ -54,19 +67,14 @@ export class WebGPUTexture extends Texture {
54
67
  this.device.debug();
55
68
  });
56
69
 
57
- // Update props if external handle was supplied - used mainly by CanvasContext.getDefaultFramebuffer()
58
- // TODO - Read all properties directly from the supplied handle?
59
70
  if (this.props.handle) {
60
71
  this.handle.label ||= this.id;
72
+ // @ts-expect-error readonly
61
73
  this.width = this.handle.width;
74
+ // @ts-expect-error readonly
62
75
  this.height = this.handle.height;
63
76
  }
64
77
 
65
- this.sampler =
66
- props.sampler instanceof WebGPUSampler
67
- ? props.sampler
68
- : new WebGPUSampler(this.device, (props.sampler as SamplerProps) || {});
69
-
70
78
  this.view = new WebGPUTextureView(this.device, {
71
79
  ...this.props,
72
80
  texture: this,
@@ -74,14 +82,34 @@ export class WebGPUTexture extends Texture {
74
82
  // Note: arrayLayerCount controls the view of array textures, but does not apply to 3d texture depths
75
83
  arrayLayerCount: this.dimension !== '3d' ? this.depth : 1
76
84
  });
85
+ this.attachResource(this.view);
77
86
 
78
87
  // Set initial data
79
88
  // Texture base class strips out the data prop from this.props, so we need to handle it here
80
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
+ }
81
98
  }
82
99
 
83
100
  override destroy(): void {
84
- 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();
85
113
  // @ts-expect-error readonly
86
114
  this.handle = null;
87
115
  }
@@ -90,37 +118,6 @@ export class WebGPUTexture extends Texture {
90
118
  return new WebGPUTextureView(this.device, {...props, texture: this});
91
119
  }
92
120
 
93
- copyImageData(options_: CopyImageDataOptions): void {
94
- const {width, height, depth} = this;
95
- const options = this._normalizeCopyImageDataOptions(options_);
96
- this.device.pushErrorScope('validation');
97
- this.device.handle.queue.writeTexture(
98
- // destination: GPUImageCopyTexture
99
- {
100
- // texture subresource
101
- texture: this.handle,
102
- mipLevel: options.mipLevel,
103
- aspect: options.aspect,
104
- // origin to write to
105
- origin: [options.x, options.y, options.z]
106
- },
107
- // data
108
- options.data,
109
- // dataLayout: GPUImageDataLayout
110
- {
111
- offset: options.byteOffset,
112
- bytesPerRow: options.bytesPerRow,
113
- rowsPerImage: options.rowsPerImage
114
- },
115
- // size: GPUExtent3D - extents of the content to write
116
- [width, height, depth]
117
- );
118
- this.device.popErrorScope((error: GPUError) => {
119
- this.device.reportError(new Error(`copyImageData: ${error.message}`), this)();
120
- this.device.debug();
121
- });
122
- }
123
-
124
121
  copyExternalImage(options_: CopyExternalImageOptions): {width: number; height: number} {
125
122
  const options = this._normalizeCopyExternalImageOptions(options_);
126
123
 
@@ -130,19 +127,19 @@ export class WebGPUTexture extends Texture {
130
127
  {
131
128
  source: options.image,
132
129
  origin: [options.sourceX, options.sourceY],
133
- flipY: options.flipY
130
+ flipY: false // options.flipY
134
131
  },
135
132
  // destination: GPUImageCopyTextureTagged
136
133
  {
137
134
  texture: this.handle,
138
- origin: [options.x, options.y, 0], // options.depth],
135
+ origin: [options.x, options.y, options.z],
139
136
  mipLevel: options.mipLevel,
140
137
  aspect: options.aspect,
141
138
  colorSpace: options.colorSpace,
142
139
  premultipliedAlpha: options.premultipliedAlpha
143
140
  },
144
141
  // copySize: GPUExtent3D
145
- [options.width, options.height, 1]
142
+ [options.width, options.height, options.depth] // depth is always 1 for 2D textures
146
143
  );
147
144
  this.device.popErrorScope((error: GPUError) => {
148
145
  this.device.reportError(new Error(`copyExternalImage: ${error.message}`), this)();
@@ -157,72 +154,255 @@ export class WebGPUTexture extends Texture {
157
154
  log.warn(`${this}: generateMipmaps not supported in WebGPU`)();
158
155
  }
159
156
 
160
- // WebGPU specific
157
+ getImageDataLayout(options: TextureReadOptions): {
158
+ byteLength: number;
159
+ bytesPerRow: number;
160
+ rowsPerImage: number;
161
+ } {
162
+ return {
163
+ byteLength: 0,
164
+ bytesPerRow: 0,
165
+ rowsPerImage: 0
166
+ };
167
+ }
161
168
 
162
- /*
163
- async readPixels() {
164
- const readbackBuffer = device.createBuffer({
165
- usage: Buffer.COPY_DST | Buffer.MAP_READ,
166
- size: 4 * textureWidth * textureHeight,
167
- });
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;
179
+
180
+ const layout = this.computeMemoryLayout({width, height, depthOrArrayLayers, mipLevel});
181
+
182
+ const {byteLength} = layout;
168
183
 
169
- // Copy data from the texture to the buffer.
170
- const encoder = device.createCommandEncoder();
171
- encoder.copyTextureToBuffer(
172
- { texture },
173
- { buffer, rowPitch: textureWidth * 4 },
174
- [textureWidth, textureHeight],
184
+ if (buffer.byteLength < byteOffset + byteLength) {
185
+ throw new Error(
186
+ `${this} readBuffer target is too small (${buffer.byteLength} < ${byteOffset + byteLength})`
187
+ );
188
+ }
189
+
190
+ const gpuDevice = this.device.handle;
191
+ this.device.pushErrorScope('validation');
192
+ const commandEncoder = gpuDevice.createCommandEncoder();
193
+ this.copyToBuffer(
194
+ commandEncoder,
195
+ {x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect, byteOffset},
196
+ buffer
175
197
  );
176
- device.submit([encoder.finish()]);
177
198
 
178
- // Get the data on the CPU.
179
- await buffer.mapAndReadAsync(GPUMapMode.READ);
180
- saveScreenshot(buffer.getMappedRange());
181
- buffer.unmap();
182
- }
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
+ });
183
205
 
184
- setImageData(imageData, usage): this {
185
- let data = null;
206
+ return buffer;
207
+ }
186
208
 
187
- const bytesPerRow = Math.ceil((img.width * 4) / 256) * 256;
188
- if (bytesPerRow == img.width * 4) {
189
- data = imageData.data;
190
- } else {
191
- data = new Uint8Array(bytesPerRow * img.height);
192
- let imagePixelIndex = 0;
193
- for (let y = 0; y < img.height; ++y) {
194
- for (let x = 0; x < img.width; ++x) {
195
- const i = x * 4 + y * bytesPerRow;
196
- data[i] = imageData.data[imagePixelIndex];
197
- data[i + 1] = imageData.data[imagePixelIndex + 1];
198
- data[i + 2] = imageData.data[imagePixelIndex + 2];
199
- data[i + 3] = imageData.data[imagePixelIndex + 3];
200
- imagePixelIndex += 4;
201
- }
202
- }
203
- }
204
- return this;
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
+ );
205
213
  }
206
214
 
207
- setBuffer(textureDataBuffer, {bytesPerRow}): this {
208
- const commandEncoder = this.device.handle.createCommandEncoder();
209
- commandEncoder.copyBufferToTexture(
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
+
237
+ commandEncoder.copyTextureToBuffer(
210
238
  {
211
- buffer: textureDataBuffer,
212
- bytesPerRow
239
+ texture: this.handle,
240
+ origin: {x, y, z},
241
+ mipLevel,
242
+ aspect
213
243
  },
214
244
  {
215
- texture: this.handle
245
+ buffer: webgpuBuffer.handle,
246
+ offset: byteOffset,
247
+ bytesPerRow: effectiveBytesPerRow,
248
+ rowsPerImage: effectiveRowsPerImage
216
249
  },
217
250
  {
218
251
  width,
219
252
  height,
220
- depth
253
+ depthOrArrayLayers
221
254
  }
222
255
  );
256
+ }
257
+
258
+ override writeBuffer(buffer: Buffer, options_: TextureWriteOptions = {}) {
259
+ const options = this._normalizeTextureWriteOptions(options_);
260
+ const {
261
+ x,
262
+ y,
263
+ z,
264
+ width,
265
+ height,
266
+ depthOrArrayLayers,
267
+ mipLevel,
268
+ aspect,
269
+ byteOffset,
270
+ bytesPerRow,
271
+ rowsPerImage
272
+ } = options;
223
273
 
224
- this.device.handle.defaultQueue.submit([commandEncoder.finish()]);
225
- return this;
274
+ const gpuDevice = this.device.handle;
275
+
276
+ this.device.pushErrorScope('validation');
277
+ const commandEncoder = gpuDevice.createCommandEncoder();
278
+ commandEncoder.copyBufferToTexture(
279
+ {
280
+ buffer: buffer.handle as GPUBuffer,
281
+ offset: byteOffset,
282
+ bytesPerRow,
283
+ rowsPerImage
284
+ },
285
+ {
286
+ texture: this.handle,
287
+ origin: {x, y, z},
288
+ mipLevel,
289
+ aspect
290
+ },
291
+ {width, height, depthOrArrayLayers}
292
+ );
293
+ const commandBuffer = commandEncoder.finish();
294
+ this.device.handle.queue.submit([commandBuffer]);
295
+ this.device.popErrorScope((error: GPUError) => {
296
+ this.device.reportError(new Error(`${this} writeBuffer: ${error.message}`), this)();
297
+ this.device.debug();
298
+ });
299
+ }
300
+
301
+ override writeData(
302
+ data: ArrayBuffer | SharedArrayBuffer | ArrayBufferView,
303
+ options_: TextureWriteOptions = {}
304
+ ): void {
305
+ const device = this.device;
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
+ format: this.format,
313
+ width,
314
+ height,
315
+ depth: depthOrArrayLayers,
316
+ byteAlignment: 1
317
+ });
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
+ }
329
+
330
+ this.device.pushErrorScope('validation');
331
+ device.handle.queue.writeTexture(
332
+ {
333
+ texture: this.handle,
334
+ mipLevel,
335
+ aspect,
336
+ origin: {x, y, z}
337
+ },
338
+ source,
339
+ {
340
+ offset: byteOffset,
341
+ bytesPerRow,
342
+ rowsPerImage
343
+ },
344
+ {width: copyWidth, height: copyHeight, depthOrArrayLayers}
345
+ );
346
+ this.device.popErrorScope((error: GPUError) => {
347
+ this.device.reportError(new Error(`${this} writeData: ${error.message}`), this)();
348
+ this.device.debug();
349
+ });
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);
226
407
  }
227
- */
228
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;
@@ -35,62 +35,60 @@ export class WebGPUAdapter extends Adapter {
35
35
  throw new Error('WebGPU not available. Recent Chrome browsers should work.');
36
36
  }
37
37
 
38
- log.groupCollapsed(1, 'WebGPUDevice created')();
39
- try {
40
- const adapter = await navigator.gpu.requestAdapter({
41
- powerPreference: 'high-performance'
42
- // forceSoftware: false
43
- });
38
+ const adapter = await navigator.gpu.requestAdapter({
39
+ powerPreference: 'high-performance'
40
+ // forceSoftware: false
41
+ });
44
42
 
45
- if (!adapter) {
46
- throw new Error('Failed to request WebGPU adapter');
47
- }
43
+ if (!adapter) {
44
+ throw new Error('Failed to request WebGPU adapter');
45
+ }
48
46
 
49
- // Note: adapter.requestAdapterInfo() has been replaced with adapter.info. Fall back in case adapter.info is not available
50
- const adapterInfo =
51
- adapter.info ||
52
- // @ts-ignore
53
- (await adapter.requestAdapterInfo?.());
54
- log.probe(2, 'Adapter available', adapterInfo)();
55
-
56
- const requiredFeatures: GPUFeatureName[] = [];
57
- const requiredLimits: Record<string, number> = {};
58
-
59
- if (props._requestMaxLimits) {
60
- // Require all features
61
- requiredFeatures.push(...(Array.from(adapter.features) as GPUFeatureName[]));
62
-
63
- // Require all limits
64
- // Filter out chrome specific keys (avoid crash)
65
- const limits = Object.keys(adapter.limits).filter(
66
- key => !['minSubgroupSize', 'maxSubgroupSize'].includes(key)
67
- );
68
- for (const key of limits) {
69
- const limit = key as keyof GPUSupportedLimits;
70
- const value = adapter.limits[limit];
71
- if (typeof value === 'number') {
72
- requiredLimits[limit] = value;
73
- }
47
+ // Note: adapter.requestAdapterInfo() has been replaced with adapter.info. Fall back in case adapter.info is not available
48
+ const adapterInfo =
49
+ adapter.info ||
50
+ // @ts-ignore
51
+ (await adapter.requestAdapterInfo?.());
52
+ // log.probe(2, 'Adapter available', adapterInfo)();
53
+
54
+ const requiredFeatures: GPUFeatureName[] = [];
55
+ const requiredLimits: Record<string, number> = {};
56
+
57
+ if (props._requestMaxLimits) {
58
+ // Require all features
59
+ requiredFeatures.push(...(Array.from(adapter.features) as GPUFeatureName[]));
60
+
61
+ // Require all limits
62
+ // Filter out chrome specific keys (avoid crash)
63
+ const limits = Object.keys(adapter.limits).filter(
64
+ key => !['minSubgroupSize', 'maxSubgroupSize'].includes(key)
65
+ );
66
+ for (const key of limits) {
67
+ const limit = key as keyof GPUSupportedLimits;
68
+ const value = adapter.limits[limit];
69
+ if (typeof value === 'number') {
70
+ requiredLimits[limit] = value;
74
71
  }
75
72
  }
73
+ }
76
74
 
77
- const gpuDevice = await adapter.requestDevice({
78
- requiredFeatures,
79
- requiredLimits
80
- });
75
+ const gpuDevice = await adapter.requestDevice({
76
+ requiredFeatures,
77
+ requiredLimits
78
+ });
81
79
 
82
- log.probe(1, 'GPUDevice available')();
80
+ // log.probe(1, 'GPUDevice available')();
83
81
 
84
- const {WebGPUDevice} = await import('./webgpu-device');
82
+ const {WebGPUDevice} = await import('./webgpu-device');
85
83
 
84
+ log.groupCollapsed(1, 'WebGPUDevice created')();
85
+ try {
86
86
  const device = new WebGPUDevice(props, gpuDevice, adapter, adapterInfo);
87
-
88
87
  log.probe(
89
88
  1,
90
89
  'Device created. For more info, set chrome://flags/#enable-webgpu-developer-features'
91
90
  )();
92
91
  log.table(1, device.info)();
93
-
94
92
  return device;
95
93
  } finally {
96
94
  log.groupEnd(1)();