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

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 (39) hide show
  1. package/dist/adapter/resources/webgpu-fence.d.ts +13 -0
  2. package/dist/adapter/resources/webgpu-fence.d.ts.map +1 -0
  3. package/dist/adapter/resources/webgpu-fence.js +25 -0
  4. package/dist/adapter/resources/webgpu-fence.js.map +1 -0
  5. package/dist/adapter/resources/webgpu-texture.d.ts +12 -3
  6. package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
  7. package/dist/adapter/resources/webgpu-texture.js +143 -26
  8. package/dist/adapter/resources/webgpu-texture.js.map +1 -1
  9. package/dist/adapter/webgpu-adapter.d.ts.map +1 -1
  10. package/dist/adapter/webgpu-adapter.js +34 -34
  11. package/dist/adapter/webgpu-adapter.js.map +1 -1
  12. package/dist/adapter/webgpu-canvas-context.d.ts +4 -3
  13. package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
  14. package/dist/adapter/webgpu-canvas-context.js +22 -21
  15. package/dist/adapter/webgpu-canvas-context.js.map +1 -1
  16. package/dist/adapter/webgpu-device.d.ts +3 -1
  17. package/dist/adapter/webgpu-device.d.ts.map +1 -1
  18. package/dist/adapter/webgpu-device.js +14 -3
  19. package/dist/adapter/webgpu-device.js.map +1 -1
  20. package/dist/dist.dev.js +6438 -274
  21. package/dist/dist.min.js +10 -6
  22. package/dist/index.cjs +347 -86
  23. package/dist/index.cjs.map +4 -4
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/wgsl/get-shader-layout-wgsl.d.ts +8 -0
  29. package/dist/wgsl/get-shader-layout-wgsl.d.ts.map +1 -0
  30. package/dist/wgsl/get-shader-layout-wgsl.js +136 -0
  31. package/dist/wgsl/get-shader-layout-wgsl.js.map +1 -0
  32. package/package.json +5 -4
  33. package/src/adapter/resources/webgpu-fence.ts +30 -0
  34. package/src/adapter/resources/webgpu-texture.ts +202 -88
  35. package/src/adapter/webgpu-adapter.ts +40 -42
  36. package/src/adapter/webgpu-canvas-context.ts +25 -23
  37. package/src/adapter/webgpu-device.ts +19 -4
  38. package/src/index.ts +3 -0
  39. package/src/wgsl/get-shader-layout-wgsl.ts +156 -0
package/dist/index.d.ts CHANGED
@@ -5,4 +5,6 @@ export { WebGPUBuffer } from "./adapter/resources/webgpu-buffer.js";
5
5
  export { WebGPUTexture } from "./adapter/resources/webgpu-texture.js";
6
6
  export { WebGPUSampler } from "./adapter/resources/webgpu-sampler.js";
7
7
  export { WebGPUShader } from "./adapter/resources/webgpu-shader.js";
8
+ export { WebGPUFence } from "./adapter/resources/webgpu-fence.js";
9
+ export { getShaderLayoutFromWGSL } from "./wgsl/get-shader-layout-wgsl.js";
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAAC,aAAa,EAAC,oCAAiC;AAC5D,OAAO,EAAC,aAAa,EAAC,oCAAiC;AAGvD,OAAO,EAAC,YAAY,EAAC,mCAAgC;AACrD,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,aAAa,EAAC,8CAA2C;AACjE,OAAO,EAAC,aAAa,EAAC,8CAA2C;AACjE,OAAO,EAAC,YAAY,EAAC,6CAA0C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAAC,aAAa,EAAC,oCAAiC;AAC5D,OAAO,EAAC,aAAa,EAAC,oCAAiC;AAGvD,OAAO,EAAC,YAAY,EAAC,mCAAgC;AACrD,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,aAAa,EAAC,8CAA2C;AACjE,OAAO,EAAC,aAAa,EAAC,8CAA2C;AACjE,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAE7D,OAAO,EAAC,uBAAuB,EAAC,yCAAsC"}
package/dist/index.js CHANGED
@@ -8,4 +8,6 @@ export { WebGPUBuffer } from "./adapter/resources/webgpu-buffer.js";
8
8
  export { WebGPUTexture } from "./adapter/resources/webgpu-texture.js";
9
9
  export { WebGPUSampler } from "./adapter/resources/webgpu-sampler.js";
10
10
  export { WebGPUShader } from "./adapter/resources/webgpu-shader.js";
11
+ export { WebGPUFence } from "./adapter/resources/webgpu-fence.js";
12
+ export { getShaderLayoutFromWGSL } from "./wgsl/get-shader-layout-wgsl.js";
11
13
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,UAAU;AACV,+BAA+B;AAC/B,oCAAoC;AAIpC,OAAO,EAAC,aAAa,EAAC,oCAAiC;AAEvD,mDAAmD;AACnD,OAAO,EAAC,YAAY,EAAC,mCAAgC;AACrD,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,aAAa,EAAC,8CAA2C;AACjE,OAAO,EAAC,aAAa,EAAC,8CAA2C;AACjE,OAAO,EAAC,YAAY,EAAC,6CAA0C"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,UAAU;AACV,+BAA+B;AAC/B,oCAAoC;AAIpC,OAAO,EAAC,aAAa,EAAC,oCAAiC;AAEvD,mDAAmD;AACnD,OAAO,EAAC,YAAY,EAAC,mCAAgC;AACrD,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,aAAa,EAAC,8CAA2C;AACjE,OAAO,EAAC,aAAa,EAAC,8CAA2C;AACjE,OAAO,EAAC,YAAY,EAAC,6CAA0C;AAC/D,OAAO,EAAC,WAAW,EAAC,4CAAyC;AAE7D,OAAO,EAAC,uBAAuB,EAAC,yCAAsC"}
@@ -0,0 +1,8 @@
1
+ import { ShaderLayout } from '@luma.gl/core';
2
+ /**
3
+ * Parse a ShaderLayout from WGSL shader source code.
4
+ * @param source WGSL source code (can contain both @vertex and @fragment entry points)
5
+ * @returns
6
+ */
7
+ export declare function getShaderLayoutFromWGSL(source: string): ShaderLayout;
8
+ //# sourceMappingURL=get-shader-layout-wgsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-shader-layout-wgsl.d.ts","sourceRoot":"","sources":["../../src/wgsl/get-shader-layout-wgsl.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,YAAY,EAA4B,MAAM,eAAe,CAAC;AAG3F;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAuEpE"}
@@ -0,0 +1,136 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+ import { log } from '@luma.gl/core';
5
+ import { WgslReflect, ResourceType } from 'wgsl_reflect';
6
+ /**
7
+ * Parse a ShaderLayout from WGSL shader source code.
8
+ * @param source WGSL source code (can contain both @vertex and @fragment entry points)
9
+ * @returns
10
+ */
11
+ export function getShaderLayoutFromWGSL(source) {
12
+ const shaderLayout = { attributes: [], bindings: [] };
13
+ let parsedWGSL;
14
+ try {
15
+ parsedWGSL = parseWGSL(source);
16
+ }
17
+ catch (error) {
18
+ log.error(error.message)();
19
+ return shaderLayout;
20
+ }
21
+ for (const uniform of parsedWGSL.uniforms) {
22
+ const members = [];
23
+ // @ts-expect-error
24
+ for (const attribute of uniform.type?.members || []) {
25
+ members.push({
26
+ name: attribute.name,
27
+ type: getType(attribute.type)
28
+ });
29
+ }
30
+ shaderLayout.bindings.push({
31
+ type: 'uniform',
32
+ name: uniform.name,
33
+ group: uniform.group,
34
+ location: uniform.binding,
35
+ // @ts-expect-error TODO - unused for now but needs fixing
36
+ members
37
+ });
38
+ }
39
+ for (const texture of parsedWGSL.textures) {
40
+ const bindingDeclaration = {
41
+ type: 'texture',
42
+ name: texture.name,
43
+ group: texture.group,
44
+ location: texture.binding,
45
+ ...getTextureBindingFromReflect(texture)
46
+ };
47
+ shaderLayout.bindings.push(bindingDeclaration);
48
+ }
49
+ for (const sampler of parsedWGSL.samplers) {
50
+ shaderLayout.bindings.push({
51
+ type: 'sampler',
52
+ name: sampler.name,
53
+ group: sampler.group,
54
+ location: sampler.binding
55
+ });
56
+ }
57
+ const vertex = parsedWGSL.entry.vertex[0]; // "main"
58
+ // Vertex shader inputs
59
+ const attributeCount = vertex?.inputs.length || 0; // inputs to "main"
60
+ for (let i = 0; i < attributeCount; i++) {
61
+ const wgslAttribute = vertex.inputs[i];
62
+ // locationType can be "builtin"
63
+ if (wgslAttribute.locationType === 'location') {
64
+ const type = getType(wgslAttribute.type);
65
+ shaderLayout.attributes.push({
66
+ name: wgslAttribute.name,
67
+ location: Number(wgslAttribute.location),
68
+ type
69
+ });
70
+ }
71
+ }
72
+ return shaderLayout;
73
+ }
74
+ /** Get a valid shader attribute type string from a wgsl-reflect type */
75
+ function getType(type) {
76
+ // @ts-expect-error WgslReflect type checks needed
77
+ return type?.format ? `${type.name}<${type.format.name}>` : type.name;
78
+ }
79
+ function parseWGSL(source) {
80
+ try {
81
+ return new WgslReflect(source);
82
+ }
83
+ catch (error) {
84
+ if (error instanceof Error) {
85
+ throw error;
86
+ }
87
+ let message = 'WGSL parse error';
88
+ if (typeof error === 'object' && error?.message) {
89
+ message += `: ${error.message} `;
90
+ }
91
+ if (typeof error === 'object' && error?.token) {
92
+ message += error.token.line || '';
93
+ }
94
+ throw new Error(message, { cause: error });
95
+ }
96
+ }
97
+ function getTextureBindingFromReflect(v, // VariableInfo for a texture
98
+ opts // optional: if you know the runtime format
99
+ ) {
100
+ if (v.resourceType !== ResourceType.Texture) {
101
+ throw new Error('Not a texture binding');
102
+ }
103
+ const typeName = v.type.name; // e.g. "texture_2d", "texture_cube_array", "texture_multisampled_2d"
104
+ // @ts-expect-error v.type.format is not always defined
105
+ const component = v.type.format?.name;
106
+ // viewDimension
107
+ const viewDimension = typeName.includes('cube_array')
108
+ ? 'cube-array'
109
+ : typeName.includes('cube')
110
+ ? 'cube'
111
+ : typeName.includes('2d_array')
112
+ ? '2d-array'
113
+ : typeName.includes('3d')
114
+ ? '3d'
115
+ : typeName.includes('1d')
116
+ ? '1d'
117
+ : '2d';
118
+ // multisampled
119
+ const multisampled = typeName === 'texture_multisampled_2d';
120
+ // sampleType
121
+ let sampleType;
122
+ if (typeName.startsWith('texture_depth')) {
123
+ sampleType = 'depth';
124
+ }
125
+ else if (component === 'i32') {
126
+ sampleType = 'sint';
127
+ }
128
+ else if (component === 'u32') {
129
+ sampleType = 'uint';
130
+ }
131
+ else {
132
+ sampleType = 'float'; // default to float
133
+ }
134
+ return { viewDimension, sampleType, multisampled };
135
+ }
136
+ //# sourceMappingURL=get-shader-layout-wgsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-shader-layout-wgsl.js","sourceRoot":"","sources":["../../src/wgsl/get-shader-layout-wgsl.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luma.gl/webgpu",
3
- "version": "9.2.5",
3
+ "version": "9.3.0-alpha.2",
4
4
  "description": "WebGPU adapter for the luma.gl core API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,11 +37,12 @@
37
37
  "prepublishOnly": "npm run build-minified-bundle && npm run build-dev-bundle"
38
38
  },
39
39
  "peerDependencies": {
40
- "@luma.gl/core": "~9.2.0"
40
+ "@luma.gl/core": "9.2.0-alpha.6"
41
41
  },
42
42
  "dependencies": {
43
43
  "@probe.gl/env": "^4.0.8",
44
- "@webgpu/types": "^0.1.34"
44
+ "@webgpu/types": "^0.1.34",
45
+ "wgsl_reflect": "^1.2.1"
45
46
  },
46
- "gitHead": "a8efd26edd0c61c7bb29ea700c6c38f544f60326"
47
+ "gitHead": "7fedf8d8902f58490a4ffca9a873daee3c732f24"
47
48
  }
@@ -0,0 +1,30 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Fence, type FenceProps} from '@luma.gl/core';
6
+ import {WebGPUDevice} from '../webgpu-device';
7
+
8
+ /** WebGPU fence implemented by waiting for submitted work */
9
+ export class WebGPUFence extends Fence {
10
+ readonly device: WebGPUDevice;
11
+ readonly handle: null = null;
12
+ readonly signaled: Promise<void>;
13
+ private _signaled = false;
14
+
15
+ constructor(device: WebGPUDevice, props: FenceProps = {}) {
16
+ super(device, {});
17
+ this.device = device;
18
+ this.signaled = device.handle.queue.onSubmittedWorkDone().then(() => {
19
+ this._signaled = true;
20
+ });
21
+ }
22
+
23
+ isSignaled(): boolean {
24
+ return this._signaled;
25
+ }
26
+
27
+ override destroy(): void {
28
+ // Nothing to release for WebGPU fence
29
+ }
30
+ }
@@ -1,18 +1,24 @@
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 CopyImageDataOptions,
7
+ type TextureReadOptions,
8
+ type TextureWriteOptions,
9
+ type SamplerProps,
10
+ Buffer,
11
+ Texture,
12
+ log,
13
+ textureFormatDecoder
8
14
  } from '@luma.gl/core';
9
- import {Texture, log} from '@luma.gl/core';
10
15
 
11
16
  import {getWebGPUTextureFormat} from '../helpers/convert-texture-format';
12
17
  import type {WebGPUDevice} from '../webgpu-device';
13
18
  import {WebGPUSampler} from './webgpu-sampler';
14
19
  import {WebGPUTextureView} from './webgpu-texture-view';
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;
@@ -20,13 +26,9 @@ export class WebGPUTexture extends Texture {
20
26
  view: WebGPUTextureView;
21
27
 
22
28
  constructor(device: WebGPUDevice, props: TextureProps) {
23
- super(device, props);
29
+ super(device, props, {byteAlignment: 256}); // WebGPU requires row width to be a multiple of 256 bytes
24
30
  this.device = device;
25
31
 
26
- if (this.dimension === 'cube') {
27
- this.depth = 6;
28
- }
29
-
30
32
  this.device.pushErrorScope('out-of-memory');
31
33
  this.device.pushErrorScope('validation');
32
34
 
@@ -58,7 +60,9 @@ export class WebGPUTexture extends Texture {
58
60
  // TODO - Read all properties directly from the supplied handle?
59
61
  if (this.props.handle) {
60
62
  this.handle.label ||= this.id;
63
+ // @ts-expect-error readonly
61
64
  this.width = this.handle.width;
65
+ // @ts-expect-error readonly
62
66
  this.height = this.handle.height;
63
67
  }
64
68
 
@@ -90,10 +94,43 @@ export class WebGPUTexture extends Texture {
90
94
  return new WebGPUTextureView(this.device, {...props, texture: this});
91
95
  }
92
96
 
97
+ copyExternalImage(options_: CopyExternalImageOptions): {width: number; height: number} {
98
+ const options = this._normalizeCopyExternalImageOptions(options_);
99
+
100
+ this.device.pushErrorScope('validation');
101
+ this.device.handle.queue.copyExternalImageToTexture(
102
+ // source: GPUImageCopyExternalImage
103
+ {
104
+ source: options.image,
105
+ origin: [options.sourceX, options.sourceY],
106
+ flipY: false // options.flipY
107
+ },
108
+ // destination: GPUImageCopyTextureTagged
109
+ {
110
+ texture: this.handle,
111
+ origin: [options.x, options.y, options.z],
112
+ mipLevel: options.mipLevel,
113
+ aspect: options.aspect,
114
+ colorSpace: options.colorSpace,
115
+ premultipliedAlpha: options.premultipliedAlpha
116
+ },
117
+ // copySize: GPUExtent3D
118
+ [options.width, options.height, options.depth] // depth is always 1 for 2D textures
119
+ );
120
+ this.device.popErrorScope((error: GPUError) => {
121
+ this.device.reportError(new Error(`copyExternalImage: ${error.message}`), this)();
122
+ this.device.debug();
123
+ });
124
+
125
+ // TODO - should these be clipped to the texture size minus x,y,z?
126
+ return {width: options.width, height: options.height};
127
+ }
128
+
93
129
  copyImageData(options_: CopyImageDataOptions): void {
94
130
  const {width, height, depth} = this;
95
131
  const options = this._normalizeCopyImageDataOptions(options_);
96
132
  this.device.pushErrorScope('validation');
133
+
97
134
  this.device.handle.queue.writeTexture(
98
135
  // destination: GPUImageCopyTexture
99
136
  {
@@ -121,108 +158,185 @@ export class WebGPUTexture extends Texture {
121
158
  });
122
159
  }
123
160
 
124
- copyExternalImage(options_: CopyExternalImageOptions): {width: number; height: number} {
125
- const options = this._normalizeCopyExternalImageOptions(options_);
161
+ override generateMipmapsWebGL(): void {
162
+ log.warn(`${this}: generateMipmaps not supported in WebGPU`)();
163
+ }
164
+
165
+ getImageDataLayout(options: TextureReadOptions): {
166
+ byteLength: number;
167
+ bytesPerRow: number;
168
+ rowsPerImage: number;
169
+ } {
170
+ return {
171
+ byteLength: 0,
172
+ bytesPerRow: 0,
173
+ rowsPerImage: 0
174
+ };
175
+ }
176
+
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;
188
+
189
+ const layout = this.computeMemoryLayout(options);
190
+
191
+ const {bytesPerRow, rowsPerImage, byteLength} = layout;
192
+
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;
201
+
202
+ // Record commands to copy from the texture to the buffer.
203
+ const gpuDevice = this.device.handle;
126
204
 
127
205
  this.device.pushErrorScope('validation');
128
- this.device.handle.queue.copyExternalImageToTexture(
129
- // source: GPUImageCopyExternalImage
206
+ const commandEncoder = gpuDevice.createCommandEncoder();
207
+ commandEncoder.copyTextureToBuffer(
208
+ // source
130
209
  {
131
- source: options.image,
132
- origin: [options.sourceX, options.sourceY],
133
- flipY: options.flipY
210
+ texture: this.handle,
211
+ origin: {x, y, z},
212
+ // origin: [options.x, options.y, 0], // options.depth],
213
+ mipLevel,
214
+ aspect
215
+ // colorSpace: options.colorSpace,
216
+ // premultipliedAlpha: options.premultipliedAlpha
134
217
  },
135
- // destination: GPUImageCopyTextureTagged
218
+ // destination
136
219
  {
137
- texture: this.handle,
138
- origin: [options.x, options.y, 0], // options.depth],
139
- mipLevel: options.mipLevel,
140
- aspect: options.aspect,
141
- colorSpace: options.colorSpace,
142
- premultipliedAlpha: options.premultipliedAlpha
220
+ buffer: gpuReadBuffer,
221
+ offset: 0,
222
+ bytesPerRow,
223
+ rowsPerImage
143
224
  },
144
- // copySize: GPUExtent3D
145
- [options.width, options.height, 1]
225
+ // copy size
226
+ {
227
+ width,
228
+ height,
229
+ depthOrArrayLayers
230
+ }
146
231
  );
232
+
233
+ // Submit the command.
234
+ const commandBuffer = commandEncoder.finish();
235
+ this.device.handle.queue.submit([commandBuffer]);
147
236
  this.device.popErrorScope((error: GPUError) => {
148
- this.device.reportError(new Error(`copyExternalImage: ${error.message}`), this)();
237
+ this.device.reportError(new Error(`${this} readBuffer: ${error.message}`), this)();
149
238
  this.device.debug();
150
239
  });
151
240
 
152
- // TODO - should these be clipped to the texture size minus x,y,z?
153
- return {width: options.width, height: options.height};
241
+ return readBuffer;
154
242
  }
155
243
 
156
- override generateMipmapsWebGL(): void {
157
- log.warn(`${this}: generateMipmaps not supported in WebGPU`)();
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;
158
249
  }
159
250
 
160
- // WebGPU specific
161
-
162
- /*
163
- async readPixels() {
164
- const readbackBuffer = device.createBuffer({
165
- usage: Buffer.COPY_DST | Buffer.MAP_READ,
166
- size: 4 * textureWidth * textureHeight,
167
- });
251
+ override writeBuffer(buffer: Buffer, options: TextureWriteOptions = {}) {
252
+ 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
+ } = options;
168
262
 
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],
175
- );
176
- device.submit([encoder.finish()]);
263
+ const layout = this.computeMemoryLayout(options);
177
264
 
178
265
  // Get the data on the CPU.
179
- await buffer.mapAndReadAsync(GPUMapMode.READ);
180
- saveScreenshot(buffer.getMappedRange());
181
- buffer.unmap();
182
- }
266
+ // await buffer.mapAndReadAsync();
183
267
 
184
- setImageData(imageData, usage): this {
185
- let data = null;
186
-
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;
205
- }
268
+ const {bytesPerRow, rowsPerImage} = layout;
269
+
270
+ const gpuDevice = this.device.handle;
206
271
 
207
- setBuffer(textureDataBuffer, {bytesPerRow}): this {
208
- const commandEncoder = this.device.handle.createCommandEncoder();
272
+ this.device.pushErrorScope('validation');
273
+ const commandEncoder = gpuDevice.createCommandEncoder();
209
274
  commandEncoder.copyBufferToTexture(
210
275
  {
211
- buffer: textureDataBuffer,
212
- bytesPerRow
276
+ buffer: buffer.handle as GPUBuffer,
277
+ offset: 0,
278
+ bytesPerRow,
279
+ rowsPerImage
213
280
  },
214
281
  {
215
- texture: this.handle
282
+ texture: this.handle,
283
+ origin: {x, y, z},
284
+ mipLevel,
285
+ aspect
216
286
  },
217
- {
218
- width,
219
- height,
220
- depth
221
- }
287
+ {width, height, depthOrArrayLayers}
222
288
  );
289
+ const commandBuffer = commandEncoder.finish();
290
+ this.device.handle.queue.submit([commandBuffer]);
291
+ this.device.popErrorScope((error: GPUError) => {
292
+ this.device.reportError(new Error(`${this} writeBuffer: ${error.message}`), this)();
293
+ this.device.debug();
294
+ });
295
+ }
223
296
 
224
- this.device.handle.defaultQueue.submit([commandEncoder.finish()]);
225
- return this;
297
+ override writeData(data: ArrayBuffer | ArrayBufferView, options: TextureWriteOptions = {}): void {
298
+ 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({
312
+ format: this.format,
313
+ width: this.width,
314
+ height: this.height,
315
+ depth: this.depth,
316
+ byteAlignment: this.byteAlignment
317
+ });
318
+
319
+ const {bytesPerRow, rowsPerImage} = layout;
320
+
321
+ this.device.pushErrorScope('validation');
322
+ device.handle.queue.writeTexture(
323
+ {
324
+ texture: this.handle,
325
+ mipLevel,
326
+ aspect,
327
+ origin: {x, y, z}
328
+ },
329
+ data,
330
+ {
331
+ offset: 0,
332
+ bytesPerRow,
333
+ rowsPerImage
334
+ },
335
+ {width, height, depthOrArrayLayers}
336
+ );
337
+ this.device.popErrorScope((error: GPUError) => {
338
+ this.device.reportError(new Error(`${this} writeData: ${error.message}`), this)();
339
+ this.device.debug();
340
+ });
226
341
  }
227
- */
228
342
  }