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

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luma.gl/webgpu",
3
- "version": "9.3.0-alpha.2",
3
+ "version": "9.3.0-alpha.4",
4
4
  "description": "WebGPU adapter for the luma.gl core API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,9 +40,9 @@
40
40
  "@luma.gl/core": "9.2.0-alpha.6"
41
41
  },
42
42
  "dependencies": {
43
- "@probe.gl/env": "^4.0.8",
43
+ "@probe.gl/env": "^4.1.1",
44
44
  "@webgpu/types": "^0.1.34",
45
45
  "wgsl_reflect": "^1.2.1"
46
46
  },
47
- "gitHead": "7fedf8d8902f58490a4ffca9a873daee3c732f24"
47
+ "gitHead": "7486e7b0377fb6ab961b4499828681bede60f3b1"
48
48
  }
@@ -3,10 +3,11 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import type {ComputeShaderLayout, BindingDeclaration, Binding} from '@luma.gl/core';
6
- import {Buffer, Sampler, Texture, log} from '@luma.gl/core';
6
+ import {Buffer, Sampler, Texture, TextureView, log} from '@luma.gl/core';
7
7
  import type {WebGPUBuffer} from '../resources/webgpu-buffer';
8
8
  import type {WebGPUSampler} from '../resources/webgpu-sampler';
9
9
  import type {WebGPUTexture} from '../resources/webgpu-texture';
10
+ import type {WebGPUTextureView} from '../resources/webgpu-texture-view';
10
11
 
11
12
  /**
12
13
  * Create a WebGPU "bind group layout" from an array of luma.gl bindings
@@ -76,7 +77,7 @@ function getBindGroupEntries(
76
77
  for (const [bindingName, value] of Object.entries(bindings)) {
77
78
  let bindingLayout = getShaderLayoutBinding(shaderLayout, bindingName);
78
79
  if (bindingLayout) {
79
- const entry = getBindGroupEntry(value, bindingLayout.location);
80
+ const entry = getBindGroupEntry(value, bindingLayout.location, undefined, bindingName);
80
81
  if (entry) {
81
82
  entries.push(entry);
82
83
  }
@@ -88,7 +89,12 @@ function getBindGroupEntries(
88
89
  ignoreWarnings: true
89
90
  });
90
91
  if (bindingLayout) {
91
- const entry = getBindGroupEntry(value, bindingLayout.location, {sampler: true});
92
+ const entry = getBindGroupEntry(
93
+ value,
94
+ bindingLayout.location,
95
+ {sampler: true},
96
+ bindingName
97
+ );
92
98
  if (entry) {
93
99
  entries.push(entry);
94
100
  }
@@ -102,7 +108,8 @@ function getBindGroupEntries(
102
108
  function getBindGroupEntry(
103
109
  binding: Binding,
104
110
  index: number,
105
- options?: {sampler?: boolean}
111
+ options?: {sampler?: boolean},
112
+ bindingName: string = 'unknown'
106
113
  ): GPUBindGroupEntry | null {
107
114
  if (binding instanceof Buffer) {
108
115
  return {
@@ -118,6 +125,12 @@ function getBindGroupEntry(
118
125
  resource: (binding as WebGPUSampler).handle
119
126
  };
120
127
  }
128
+ if (binding instanceof TextureView) {
129
+ return {
130
+ binding: index,
131
+ resource: (binding as WebGPUTextureView).handle
132
+ };
133
+ }
121
134
  if (binding instanceof Texture) {
122
135
  if (options?.sampler) {
123
136
  return {
@@ -130,6 +143,6 @@ function getBindGroupEntry(
130
143
  resource: (binding as WebGPUTexture).view.handle
131
144
  };
132
145
  }
133
- log.warn(`invalid binding ${name}`, binding);
146
+ log.warn(`invalid binding ${bindingName}`, binding);
134
147
  return null;
135
148
  }
@@ -5,20 +5,28 @@
5
5
  import {log, Buffer, type BufferProps, type BufferMapCallback} from '@luma.gl/core';
6
6
  import {type WebGPUDevice} from '../webgpu-device';
7
7
 
8
+ /**
9
+ * WebGPU implementation of Buffer
10
+ * For byte alignment requirements see:
11
+ * @see https://www.w3.org/TR/webgpu/#dom-gpubuffer-mapasync
12
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapAsync
13
+ */
8
14
  export class WebGPUBuffer extends Buffer {
9
15
  readonly device: WebGPUDevice;
10
16
  readonly handle: GPUBuffer;
11
17
  readonly byteLength: number;
18
+ readonly paddedByteLength: number;
12
19
 
13
20
  constructor(device: WebGPUDevice, props: BufferProps) {
14
21
  super(device, props);
15
22
  this.device = device;
16
23
 
17
24
  this.byteLength = props.byteLength || props.data?.byteLength || 0;
25
+ this.paddedByteLength = Math.ceil(this.byteLength / 4) * 4;
18
26
  const mappedAtCreation = Boolean(this.props.onMapped || props.data);
19
27
 
20
28
  // WebGPU buffers must be aligned to 4 bytes
21
- const size = Math.ceil(this.byteLength / 4) * 4;
29
+ const size = this.paddedByteLength;
22
30
 
23
31
  this.device.pushErrorScope('out-of-memory');
24
32
  this.device.pushErrorScope('validation');
@@ -92,10 +100,11 @@ export class WebGPUBuffer extends Buffer {
92
100
  byteOffset: number = 0,
93
101
  byteLength: number = this.byteLength - byteOffset
94
102
  ): Promise<void> {
103
+ const alignedByteLength = Math.ceil(byteLength / 4) * 4;
95
104
  // Unless the application created and supplied a mappable buffer, a staging buffer is needed
96
105
  const isMappable = (this.usage & Buffer.MAP_WRITE) !== 0;
97
106
  const mappableBuffer: WebGPUBuffer | null = !isMappable
98
- ? this._getMappableBuffer(Buffer.MAP_WRITE | Buffer.COPY_SRC, 0, this.byteLength)
107
+ ? this._getMappableBuffer(Buffer.MAP_WRITE | Buffer.COPY_SRC, 0, this.paddedByteLength)
99
108
  : null;
100
109
 
101
110
  const writeBuffer = mappableBuffer || this;
@@ -105,13 +114,15 @@ export class WebGPUBuffer extends Buffer {
105
114
  this.device.pushErrorScope('validation');
106
115
  try {
107
116
  await this.device.handle.queue.onSubmittedWorkDone();
108
- await writeBuffer.handle.mapAsync(GPUMapMode.WRITE, byteOffset, byteLength);
109
- const arrayBuffer = writeBuffer.handle.getMappedRange(byteOffset, byteLength);
117
+ await writeBuffer.handle.mapAsync(GPUMapMode.WRITE, byteOffset, alignedByteLength);
118
+ const mappedRange = writeBuffer.handle.getMappedRange(byteOffset, alignedByteLength);
119
+ const arrayBuffer = mappedRange.slice(0, byteLength);
110
120
  // eslint-disable-next-line @typescript-eslint/await-thenable
111
121
  await callback(arrayBuffer, 'mapped');
122
+ new Uint8Array(mappedRange).set(new Uint8Array(arrayBuffer), 0);
112
123
  writeBuffer.handle.unmap();
113
124
  if (mappableBuffer) {
114
- this._copyBuffer(mappableBuffer, byteOffset, byteLength);
125
+ this._copyBuffer(mappableBuffer, byteOffset, alignedByteLength);
115
126
  }
116
127
  } finally {
117
128
  this.device.popErrorScope((error: GPUError) => {
@@ -138,17 +149,33 @@ export class WebGPUBuffer extends Buffer {
138
149
  byteOffset = 0,
139
150
  byteLength = this.byteLength - byteOffset
140
151
  ): Promise<T> {
152
+ const requestedEnd = byteOffset + byteLength;
153
+ if (requestedEnd > this.byteLength) {
154
+ throw new Error('Mapping range exceeds buffer size');
155
+ }
156
+
157
+ let mappedByteOffset = byteOffset;
158
+ let mappedByteLength = byteLength;
159
+ let sliceByteOffset = 0;
160
+ let lifetime: 'mapped' | 'copied' = 'mapped';
161
+
162
+ // WebGPU mapAsync requires 8-byte offsets and 4-byte lengths.
141
163
  if (byteOffset % 8 !== 0 || byteLength % 4 !== 0) {
142
- throw new Error('byteOffset must be multiple of 8 and byteLength multiple of 4');
164
+ mappedByteOffset = Math.floor(byteOffset / 8) * 8;
165
+ const alignedEnd = Math.ceil(requestedEnd / 4) * 4;
166
+ mappedByteLength = alignedEnd - mappedByteOffset;
167
+ sliceByteOffset = byteOffset - mappedByteOffset;
168
+ lifetime = 'copied';
143
169
  }
144
- if (byteOffset + byteLength > this.handle.size) {
170
+
171
+ if (mappedByteOffset + mappedByteLength > this.paddedByteLength) {
145
172
  throw new Error('Mapping range exceeds buffer size');
146
173
  }
147
174
 
148
175
  // Unless the application created and supplied a mappable buffer, a staging buffer is needed
149
176
  const isMappable = (this.usage & Buffer.MAP_READ) !== 0;
150
177
  const mappableBuffer: WebGPUBuffer | null = !isMappable
151
- ? this._getMappableBuffer(Buffer.MAP_READ | Buffer.COPY_DST, 0, this.byteLength)
178
+ ? this._getMappableBuffer(Buffer.MAP_READ | Buffer.COPY_DST, 0, this.paddedByteLength)
152
179
  : null;
153
180
 
154
181
  const readBuffer = mappableBuffer || this;
@@ -158,12 +185,16 @@ export class WebGPUBuffer extends Buffer {
158
185
  try {
159
186
  await this.device.handle.queue.onSubmittedWorkDone();
160
187
  if (mappableBuffer) {
161
- mappableBuffer._copyBuffer(this);
188
+ mappableBuffer._copyBuffer(this, mappedByteOffset, mappedByteLength);
162
189
  }
163
- await readBuffer.handle.mapAsync(GPUMapMode.READ, byteOffset, byteLength);
164
- const arrayBuffer = readBuffer.handle.getMappedRange(byteOffset, byteLength);
190
+ await readBuffer.handle.mapAsync(GPUMapMode.READ, mappedByteOffset, mappedByteLength);
191
+ const arrayBuffer = readBuffer.handle.getMappedRange(mappedByteOffset, mappedByteLength);
192
+ const mappedRange =
193
+ lifetime === 'mapped'
194
+ ? arrayBuffer
195
+ : arrayBuffer.slice(sliceByteOffset, sliceByteOffset + byteLength);
165
196
  // eslint-disable-next-line @typescript-eslint/await-thenable
166
- const result = await callback(arrayBuffer, 'mapped');
197
+ const result = await callback(mappedRange, lifetime);
167
198
  readBuffer.handle.unmap();
168
199
  return result;
169
200
  } finally {
@@ -47,8 +47,7 @@ export class WebGPUPipelineLayout extends PipelineLayout {
47
47
  // TODO (kaapp): This only supports the first group, but so does the rest of the code
48
48
  const bindGroupEntries: GPUBindGroupLayoutEntry[] = [];
49
49
 
50
- for (let i = 0; i < this.props.shaderLayout.bindings.length; i++) {
51
- const binding = this.props.shaderLayout.bindings[i];
50
+ for (const binding of this.props.shaderLayout.bindings) {
52
51
  const bindingTypeInfo: Omit<GPUBindGroupLayoutEntry, 'binding' | 'visibility'> = {};
53
52
 
54
53
  switch (binding.type) {