@luma.gl/webgpu 9.1.9 → 9.2.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 (100) hide show
  1. package/dist/adapter/helpers/accessor-to-format.js +1 -0
  2. package/dist/adapter/helpers/accessor-to-format.js.map +1 -1
  3. package/dist/adapter/helpers/get-bind-group.d.ts +3 -1
  4. package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
  5. package/dist/adapter/helpers/get-bind-group.js +28 -10
  6. package/dist/adapter/helpers/get-bind-group.js.map +1 -1
  7. package/dist/adapter/helpers/get-vertex-buffer-layout.js +5 -5
  8. package/dist/adapter/helpers/get-vertex-buffer-layout.js.map +1 -1
  9. package/dist/adapter/helpers/webgpu-parameters.d.ts.map +1 -1
  10. package/dist/adapter/helpers/webgpu-parameters.js +89 -75
  11. package/dist/adapter/helpers/webgpu-parameters.js.map +1 -1
  12. package/dist/adapter/resources/webgpu-buffer.d.ts +13 -16
  13. package/dist/adapter/resources/webgpu-buffer.d.ts.map +1 -1
  14. package/dist/adapter/resources/webgpu-buffer.js +132 -93
  15. package/dist/adapter/resources/webgpu-buffer.js.map +1 -1
  16. package/dist/adapter/resources/webgpu-command-buffer.d.ts +10 -0
  17. package/dist/adapter/resources/webgpu-command-buffer.d.ts.map +1 -0
  18. package/dist/adapter/resources/webgpu-command-buffer.js +18 -0
  19. package/dist/adapter/resources/webgpu-command-buffer.js.map +1 -0
  20. package/dist/adapter/resources/webgpu-command-encoder.d.ts +12 -5
  21. package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
  22. package/dist/adapter/resources/webgpu-command-encoder.js +28 -5
  23. package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
  24. package/dist/adapter/resources/webgpu-compute-pass.js +1 -1
  25. package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
  26. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts +2 -2
  27. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
  28. package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
  29. package/dist/adapter/resources/webgpu-external-texture.d.ts +2 -2
  30. package/dist/adapter/resources/webgpu-external-texture.d.ts.map +1 -1
  31. package/dist/adapter/resources/webgpu-external-texture.js.map +1 -1
  32. package/dist/adapter/resources/webgpu-framebuffer.d.ts +3 -2
  33. package/dist/adapter/resources/webgpu-framebuffer.d.ts.map +1 -1
  34. package/dist/adapter/resources/webgpu-framebuffer.js +1 -0
  35. package/dist/adapter/resources/webgpu-framebuffer.js.map +1 -1
  36. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts +2 -2
  37. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
  38. package/dist/adapter/resources/webgpu-pipeline-layout.js +5 -2
  39. package/dist/adapter/resources/webgpu-pipeline-layout.js.map +1 -1
  40. package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
  41. package/dist/adapter/resources/webgpu-render-pass.js +11 -6
  42. package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
  43. package/dist/adapter/resources/webgpu-render-pipeline.d.ts +5 -4
  44. package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
  45. package/dist/adapter/resources/webgpu-render-pipeline.js +33 -20
  46. package/dist/adapter/resources/webgpu-render-pipeline.js.map +1 -1
  47. package/dist/adapter/resources/webgpu-sampler.js +1 -1
  48. package/dist/adapter/resources/webgpu-sampler.js.map +1 -1
  49. package/dist/adapter/resources/webgpu-shader.d.ts.map +1 -1
  50. package/dist/adapter/resources/webgpu-shader.js +7 -8
  51. package/dist/adapter/resources/webgpu-shader.js.map +1 -1
  52. package/dist/adapter/resources/webgpu-texture-view.d.ts.map +1 -1
  53. package/dist/adapter/resources/webgpu-texture-view.js +15 -10
  54. package/dist/adapter/resources/webgpu-texture-view.js.map +1 -1
  55. package/dist/adapter/resources/webgpu-texture.d.ts +4 -37
  56. package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
  57. package/dist/adapter/resources/webgpu-texture.js +97 -126
  58. package/dist/adapter/resources/webgpu-texture.js.map +1 -1
  59. package/dist/adapter/resources/webgpu-vertex-array.d.ts +2 -2
  60. package/dist/adapter/resources/webgpu-vertex-array.d.ts.map +1 -1
  61. package/dist/adapter/resources/webgpu-vertex-array.js +2 -2
  62. package/dist/adapter/resources/webgpu-vertex-array.js.map +1 -1
  63. package/dist/adapter/webgpu-adapter.d.ts +2 -3
  64. package/dist/adapter/webgpu-adapter.d.ts.map +1 -1
  65. package/dist/adapter/webgpu-adapter.js +54 -43
  66. package/dist/adapter/webgpu-adapter.js.map +1 -1
  67. package/dist/adapter/webgpu-canvas-context.d.ts +7 -15
  68. package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
  69. package/dist/adapter/webgpu-canvas-context.js +50 -72
  70. package/dist/adapter/webgpu-canvas-context.js.map +1 -1
  71. package/dist/adapter/webgpu-device.d.ts +16 -27
  72. package/dist/adapter/webgpu-device.d.ts.map +1 -1
  73. package/dist/adapter/webgpu-device.js +48 -65
  74. package/dist/adapter/webgpu-device.js.map +1 -1
  75. package/dist/dist.dev.js +1919 -1520
  76. package/dist/dist.min.js +6 -5
  77. package/dist/index.cjs +1749 -1397
  78. package/dist/index.cjs.map +4 -4
  79. package/package.json +3 -3
  80. package/src/adapter/helpers/get-bind-group.ts +31 -11
  81. package/src/adapter/helpers/get-vertex-buffer-layout.ts +5 -5
  82. package/src/adapter/helpers/webgpu-parameters.ts +114 -102
  83. package/src/adapter/resources/webgpu-buffer.ts +163 -102
  84. package/src/adapter/resources/webgpu-command-buffer.ts +24 -0
  85. package/src/adapter/resources/webgpu-command-encoder.ts +34 -4
  86. package/src/adapter/resources/webgpu-compute-pass.ts +1 -1
  87. package/src/adapter/resources/webgpu-compute-pipeline.ts +2 -2
  88. package/src/adapter/resources/webgpu-external-texture.ts +2 -2
  89. package/src/adapter/resources/webgpu-framebuffer.ts +3 -2
  90. package/src/adapter/resources/webgpu-pipeline-layout.ts +8 -3
  91. package/src/adapter/resources/webgpu-render-pass.ts +11 -6
  92. package/src/adapter/resources/webgpu-render-pipeline.ts +39 -24
  93. package/src/adapter/resources/webgpu-sampler.ts +1 -1
  94. package/src/adapter/resources/webgpu-shader.ts +11 -8
  95. package/src/adapter/resources/webgpu-texture-view.ts +14 -8
  96. package/src/adapter/resources/webgpu-texture.ts +106 -186
  97. package/src/adapter/resources/webgpu-vertex-array.ts +2 -2
  98. package/src/adapter/webgpu-adapter.ts +72 -58
  99. package/src/adapter/webgpu-canvas-context.ts +62 -82
  100. package/src/adapter/webgpu-device.ts +66 -105
@@ -2,12 +2,8 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {Buffer, BufferProps} from '@luma.gl/core';
6
- import type {WebGPUDevice} from '../webgpu-device';
7
-
8
- function getByteLength(props: BufferProps): number {
9
- return props.byteLength || props.data?.byteLength || 0;
10
- }
5
+ import {log, Buffer, type BufferProps, type BufferMapCallback} from '@luma.gl/core';
6
+ import {type WebGPUDevice} from '../webgpu-device';
11
7
 
12
8
  export class WebGPUBuffer extends Buffer {
13
9
  readonly device: WebGPUDevice;
@@ -18,30 +14,51 @@ export class WebGPUBuffer extends Buffer {
18
14
  super(device, props);
19
15
  this.device = device;
20
16
 
21
- this.byteLength = getByteLength(props);
22
- const mapBuffer = Boolean(props.data);
17
+ this.byteLength = props.byteLength || props.data?.byteLength || 0;
18
+ const mappedAtCreation = Boolean(this.props.onMapped || props.data);
23
19
 
24
20
  // WebGPU buffers must be aligned to 4 bytes
25
21
  const size = Math.ceil(this.byteLength / 4) * 4;
26
22
 
23
+ this.device.pushErrorScope('out-of-memory');
24
+ this.device.pushErrorScope('validation');
27
25
  this.handle =
28
26
  this.props.handle ||
29
27
  this.device.handle.createBuffer({
30
- size,
28
+ label: this.props.id,
31
29
  // usage defaults to vertex
32
30
  usage: this.props.usage || GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
33
- mappedAtCreation: this.props.mappedAtCreation || mapBuffer,
34
- label: this.props.id
31
+ mappedAtCreation,
32
+ size
35
33
  });
34
+ this.device.popErrorScope((error: GPUError) => {
35
+ this.device.reportError(new Error(`${this} creation failed ${error.message}`), this)();
36
+ this.device.debug();
37
+ });
38
+ this.device.popErrorScope((error: GPUError) => {
39
+ this.device.reportError(new Error(`${this} out of memory: ${error.message}`), this)();
40
+ this.device.debug();
41
+ });
36
42
 
37
- if (props.data) {
38
- this._writeMapped(props.data);
39
- // this.handle.writeAsync({data: props.data, map: false, unmap: false});
40
- }
41
-
42
- if (mapBuffer && !props.mappedAtCreation) {
43
- this.handle.unmap();
43
+ this.device.pushErrorScope('validation');
44
+ if (props.data || props.onMapped) {
45
+ try {
46
+ const arrayBuffer = this.handle.getMappedRange();
47
+ if (props.data) {
48
+ const typedArray = props.data;
49
+ // @ts-expect-error
50
+ new typedArray.constructor(arrayBuffer).set(typedArray);
51
+ } else {
52
+ props.onMapped?.(arrayBuffer, 'mapped');
53
+ }
54
+ } finally {
55
+ this.handle.unmap();
56
+ }
44
57
  }
58
+ this.device.popErrorScope((error: GPUError) => {
59
+ this.device.reportError(new Error(`${this} creation failed ${error.message}`), this)();
60
+ this.device.debug();
61
+ });
45
62
  }
46
63
 
47
64
  override destroy(): void {
@@ -50,111 +67,155 @@ export class WebGPUBuffer extends Buffer {
50
67
  this.handle = null;
51
68
  }
52
69
 
53
- // WebGPU provides multiple ways to write a buffer...
54
- override write(data: ArrayBufferView, byteOffset = 0) {
70
+ write(data: ArrayBufferLike | ArrayBufferView | SharedArrayBuffer, byteOffset = 0) {
71
+ const arrayBuffer = ArrayBuffer.isView(data) ? data.buffer : data;
72
+ const dataByteOffset = ArrayBuffer.isView(data) ? data.byteOffset : 0;
73
+
74
+ this.device.pushErrorScope('validation');
75
+
76
+ // WebGPU provides multiple ways to write a buffer, this is the simplest API
55
77
  this.device.handle.queue.writeBuffer(
56
78
  this.handle,
57
79
  byteOffset,
58
- data.buffer,
59
- data.byteOffset,
80
+ arrayBuffer,
81
+ dataByteOffset,
60
82
  data.byteLength
61
83
  );
84
+ this.device.popErrorScope((error: GPUError) => {
85
+ this.device.reportError(new Error(`${this}.write() ${error.message}`), this)();
86
+ this.device.debug();
87
+ });
62
88
  }
63
89
 
64
- override async readAsync(
90
+ async mapAndWriteAsync(
91
+ callback: BufferMapCallback<void>,
65
92
  byteOffset: number = 0,
66
- byteLength: number = this.byteLength
67
- ): Promise<Uint8Array> {
68
- // We need MAP_READ flag, but only COPY_DST buffers can have MAP_READ flag, so we need to create a temp buffer
69
- const tempBuffer = new WebGPUBuffer(this.device, {
70
- usage: Buffer.MAP_READ | Buffer.COPY_DST,
71
- byteLength
72
- });
93
+ byteLength: number = this.byteLength - byteOffset
94
+ ): Promise<void> {
95
+ // Unless the application created and supplied a mappable buffer, a staging buffer is needed
96
+ const isMappable = (this.usage & Buffer.MAP_WRITE) !== 0;
97
+ const mappableBuffer: WebGPUBuffer | null = !isMappable
98
+ ? this._getMappableBuffer(Buffer.MAP_WRITE | Buffer.COPY_SRC, 0, this.byteLength)
99
+ : null;
73
100
 
74
- // Now do a GPU-side copy into the temp buffer we can actually read.
75
- // TODO - we are spinning up an independent command queue here, what does this mean
76
- const commandEncoder = this.device.handle.createCommandEncoder();
77
- commandEncoder.copyBufferToBuffer(this.handle, byteOffset, tempBuffer.handle, 0, byteLength);
78
- this.device.handle.queue.submit([commandEncoder.finish()]);
101
+ const writeBuffer = mappableBuffer || this;
79
102
 
103
+ // const isWritable = this.usage & Buffer.MAP_WRITE;
80
104
  // Map the temp buffer and read the data.
81
- await tempBuffer.handle.mapAsync(GPUMapMode.READ, byteOffset, byteLength);
82
- const arrayBuffer = tempBuffer.handle.getMappedRange().slice(0);
83
- tempBuffer.handle.unmap();
84
- tempBuffer.destroy();
85
-
86
- return new Uint8Array(arrayBuffer);
105
+ this.device.pushErrorScope('validation');
106
+ try {
107
+ await this.device.handle.queue.onSubmittedWorkDone();
108
+ await writeBuffer.handle.mapAsync(GPUMapMode.WRITE, byteOffset, byteLength);
109
+ const arrayBuffer = writeBuffer.handle.getMappedRange(byteOffset, byteLength);
110
+ // eslint-disable-next-line @typescript-eslint/await-thenable
111
+ await callback(arrayBuffer, 'mapped');
112
+ writeBuffer.handle.unmap();
113
+ if (mappableBuffer) {
114
+ this._copyBuffer(mappableBuffer, byteOffset, byteLength);
115
+ }
116
+ } finally {
117
+ this.device.popErrorScope((error: GPUError) => {
118
+ this.device.reportError(new Error(`${this}.mapAndWriteAsync() ${error.message}`), this)();
119
+ this.device.debug();
120
+ });
121
+ mappableBuffer?.destroy();
122
+ }
87
123
  }
88
124
 
89
- _writeMapped<TypedArray>(typedArray: TypedArray): void {
90
- const arrayBuffer = this.handle.getMappedRange();
91
- // @ts-expect-error
92
- new typedArray.constructor(arrayBuffer).set(typedArray);
125
+ async readAsync(
126
+ byteOffset: number = 0,
127
+ byteLength = this.byteLength - byteOffset
128
+ ): Promise<Uint8Array> {
129
+ return this.mapAndReadAsync(
130
+ arrayBuffer => new Uint8Array(arrayBuffer.slice(0)),
131
+ byteOffset,
132
+ byteLength
133
+ );
93
134
  }
94
135
 
95
- // WEBGPU API
96
-
97
- mapAsync(mode: number, offset: number = 0, size?: number): Promise<void> {
98
- return this.handle.mapAsync(mode, offset, size);
99
- }
136
+ async mapAndReadAsync<T>(
137
+ callback: BufferMapCallback<T>,
138
+ byteOffset = 0,
139
+ byteLength = this.byteLength - byteOffset
140
+ ): Promise<T> {
141
+ if (byteOffset % 8 !== 0 || byteLength % 4 !== 0) {
142
+ throw new Error('byteOffset must be multiple of 8 and byteLength multiple of 4');
143
+ }
144
+ if (byteOffset + byteLength > this.handle.size) {
145
+ throw new Error('Mapping range exceeds buffer size');
146
+ }
100
147
 
101
- getMappedRange(offset: number = 0, size?: number): ArrayBuffer {
102
- return this.handle.getMappedRange(offset, size);
103
- }
148
+ // Unless the application created and supplied a mappable buffer, a staging buffer is needed
149
+ const isMappable = (this.usage & Buffer.MAP_READ) !== 0;
150
+ const mappableBuffer: WebGPUBuffer | null = !isMappable
151
+ ? this._getMappableBuffer(Buffer.MAP_READ | Buffer.COPY_DST, 0, this.byteLength)
152
+ : null;
104
153
 
105
- unmap(): void {
106
- this.handle.unmap();
107
- }
108
- }
154
+ const readBuffer = mappableBuffer || this;
109
155
 
110
- /*
111
- // Convenience API
112
- /** Read data from the buffer *
113
- async readAsync(options: {
114
- byteOffset?: number,
115
- byteLength?: number,
116
- map?: boolean,
117
- unmap?: boolean
118
- }): Promise<ArrayBuffer> {
119
- if (options.map ?? true) {
120
- await this.mapAsync(Buffer.MAP_READ, options.byteOffset, options.byteLength);
121
- }
122
- const arrayBuffer = this.getMappedRange(options.byteOffset, options.byteLength);
123
- if (options.unmap ?? true) {
124
- this.unmap();
156
+ // Map the temp buffer and read the data.
157
+ this.device.pushErrorScope('validation');
158
+ try {
159
+ await this.device.handle.queue.onSubmittedWorkDone();
160
+ if (mappableBuffer) {
161
+ mappableBuffer._copyBuffer(this);
162
+ }
163
+ await readBuffer.handle.mapAsync(GPUMapMode.READ, byteOffset, byteLength);
164
+ const arrayBuffer = readBuffer.handle.getMappedRange(byteOffset, byteLength);
165
+ // eslint-disable-next-line @typescript-eslint/await-thenable
166
+ const result = await callback(arrayBuffer, 'mapped');
167
+ readBuffer.handle.unmap();
168
+ return result;
169
+ } finally {
170
+ this.device.popErrorScope((error: GPUError) => {
171
+ this.device.reportError(new Error(`${this}.mapAndReadAsync() ${error.message}`), this)();
172
+ this.device.debug();
173
+ });
174
+ mappableBuffer?.destroy();
125
175
  }
126
- return arrayBuffer;
127
176
  }
128
177
 
129
- /** Write data to the buffer *
130
- async writeAsync(options: {
131
- data: ArrayBuffer,
132
- byteOffset?: number,
133
- byteLength?: number,
134
- map?: boolean,
135
- unmap?: boolean
136
- }): Promise<void> {
137
- if (options.map ?? true) {
138
- await this.mapAsync(Buffer.MAP_WRITE, options.byteOffset, options.byteLength);
139
- }
140
- const arrayBuffer = this.getMappedRange(options.byteOffset, options.byteLength);
141
- const destArray = new Uint8Array(arrayBuffer);
142
- const srcArray = new Uint8Array(options.data);
143
- destArray.set(srcArray);
144
- if (options.unmap ?? true) {
145
- this.unmap();
146
- }
178
+ readSyncWebGL(byteOffset?: number, byteLength?: number): Uint8Array<ArrayBuffer> {
179
+ throw new Error('Not implemented');
147
180
  }
148
- */
149
-
150
- // Mapped API (WebGPU)
151
181
 
152
- /** Maps the memory so that it can be read *
153
- // abstract mapAsync(mode, byteOffset, byteLength): Promise<void>
154
-
155
- /** Get the mapped range of data for reading or writing *
156
- // abstract getMappedRange(byteOffset, byteLength): ArrayBuffer;
182
+ // INTERNAL METHODS
183
+
184
+ /**
185
+ * @todo - A small set of mappable buffers could be cached on the device,
186
+ * however this goes against the goal of keeping core as a thin GPU API layer.
187
+ */
188
+ protected _getMappableBuffer(
189
+ usage: number, // Buffer.MAP_READ | Buffer.MAP_WRITE,
190
+ byteOffset: number,
191
+ byteLength: number
192
+ ): WebGPUBuffer {
193
+ log.warn(`${this} is not readable, creating a temporary Buffer`);
194
+ const readableBuffer = new WebGPUBuffer(this.device, {usage, byteLength});
195
+
196
+ return readableBuffer;
197
+ }
157
198
 
158
- /** unmap makes the contents of the buffer available to the GPU again *
159
- // abstract unmap(): void;
160
- */
199
+ protected _copyBuffer(
200
+ sourceBuffer: WebGPUBuffer,
201
+ byteOffset: number = 0,
202
+ byteLength: number = this.byteLength
203
+ ) {
204
+ // Now do a GPU-side copy into the temp buffer we can actually read.
205
+ // TODO - we are spinning up an independent command queue here, what does this mean
206
+ this.device.pushErrorScope('validation');
207
+ const commandEncoder = this.device.handle.createCommandEncoder();
208
+ commandEncoder.copyBufferToBuffer(
209
+ sourceBuffer.handle,
210
+ byteOffset,
211
+ this.handle,
212
+ byteOffset,
213
+ byteLength
214
+ );
215
+ this.device.handle.queue.submit([commandEncoder.finish()]);
216
+ this.device.popErrorScope((error: GPUError) => {
217
+ this.device.reportError(new Error(`${this}._getReadableBuffer() ${error.message}`), this)();
218
+ this.device.debug();
219
+ });
220
+ }
221
+ }
@@ -0,0 +1,24 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {CommandBufferProps} from '@luma.gl/core';
6
+ import {CommandBuffer} from '@luma.gl/core';
7
+
8
+ import {WebGPUDevice} from '../webgpu-device';
9
+ import type {WebGPUCommandEncoder} from './webgpu-command-encoder';
10
+
11
+ export class WebGPUCommandBuffer extends CommandBuffer {
12
+ readonly device: WebGPUDevice;
13
+ readonly handle: GPUCommandBuffer;
14
+
15
+ constructor(commandEncoder: WebGPUCommandEncoder, props: CommandBufferProps) {
16
+ super(commandEncoder.device, {});
17
+ this.device = commandEncoder.device;
18
+ this.handle =
19
+ this.props.handle ||
20
+ commandEncoder.handle.finish({
21
+ label: props?.id || 'unnamed-command-buffer'
22
+ });
23
+ }
24
+ }
@@ -2,10 +2,18 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ import type {
6
+ RenderPassProps,
7
+ ComputePassProps,
8
+ CopyTextureToTextureOptions,
9
+ CopyTextureToBufferOptions
10
+ } from '@luma.gl/core';
5
11
  import {CommandEncoder, CommandEncoderProps, Buffer, Texture} from '@luma.gl/core';
6
- import type {CopyTextureToTextureOptions, CopyTextureToBufferOptions} from '@luma.gl/core';
7
12
  import {WebGPUDevice} from '../webgpu-device';
13
+ import {WebGPUCommandBuffer} from './webgpu-command-buffer';
8
14
  import {WebGPUBuffer} from './webgpu-buffer';
15
+ import {WebGPURenderPass} from './webgpu-render-pass';
16
+ import {WebGPUComputePass} from './webgpu-compute-pass';
9
17
  import {WebGPUTexture} from './webgpu-texture';
10
18
  import {WebGPUQuerySet} from './webgpu-query-set';
11
19
 
@@ -13,12 +21,13 @@ export class WebGPUCommandEncoder extends CommandEncoder {
13
21
  readonly device: WebGPUDevice;
14
22
  readonly handle: GPUCommandEncoder;
15
23
 
16
- constructor(device: WebGPUDevice, props: CommandEncoderProps) {
24
+ constructor(device: WebGPUDevice, props: CommandEncoderProps = {}) {
17
25
  super(device, props);
18
26
  this.device = device;
19
27
  this.handle =
20
28
  props.handle ||
21
29
  this.device.handle.createCommandEncoder({
30
+ label: this.props.id
22
31
  // TODO was this removed in standard?
23
32
  // measureExecutionTime: this.props.measureExecutionTime
24
33
  });
@@ -27,8 +36,29 @@ export class WebGPUCommandEncoder extends CommandEncoder {
27
36
 
28
37
  override destroy(): void {}
29
38
 
30
- finish(options?: {id?: string}): GPUCommandBuffer {
31
- return this.finish(options);
39
+ finish(props?: CommandEncoderProps): WebGPUCommandBuffer {
40
+ this.device.pushErrorScope('validation');
41
+ const commandBuffer = new WebGPUCommandBuffer(this, {
42
+ id: props?.id || 'unnamed-command-buffer'
43
+ });
44
+ this.device.popErrorScope((error: GPUError) => {
45
+ const message = `${this} command encoding: ${error.message}. Maybe add depthWriteEnabled to your Model?`;
46
+ this.device.reportError(new Error(message), this)();
47
+ this.device.debug();
48
+ });
49
+ return commandBuffer;
50
+ }
51
+
52
+ /**
53
+ * Allows a render pass to begin against a canvas context
54
+ * @todo need to support a "Framebuffer" equivalent (aka preconfigured RenderPassDescriptors?).
55
+ */
56
+ beginRenderPass(props: RenderPassProps): WebGPURenderPass {
57
+ return new WebGPURenderPass(this.device, props);
58
+ }
59
+
60
+ beginComputePass(props: ComputePassProps): WebGPUComputePass {
61
+ return new WebGPUComputePass(this.device, props);
32
62
  }
33
63
 
34
64
  // beginRenderPass(GPURenderPassDescriptor descriptor): GPURenderPassEncoder;
@@ -33,7 +33,7 @@ export class WebGPUComputePass extends ComputePass {
33
33
 
34
34
  this.handle =
35
35
  this.props.handle ||
36
- device.commandEncoder?.beginComputePass({
36
+ device.commandEncoder.handle.beginComputePass({
37
37
  label: this.props.id,
38
38
  timestampWrites
39
39
  });
@@ -11,8 +11,8 @@ import {WebGPUShader} from './webgpu-shader';
11
11
 
12
12
  /** Creates a new compute pipeline when parameters change */
13
13
  export class WebGPUComputePipeline extends ComputePipeline {
14
- device: WebGPUDevice;
15
- handle: GPUComputePipeline;
14
+ readonly device: WebGPUDevice;
15
+ readonly handle: GPUComputePipeline;
16
16
 
17
17
  /** For internal use to create BindGroups */
18
18
  private _bindGroupLayout: GPUBindGroupLayout | null = null;
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {ExternalTexture, ExternalTextureProps, Sampler, SamplerProps} from '@luma.gl/core';
5
+ import {ExternalTexture, ExternalTextureProps, SamplerProps} from '@luma.gl/core';
6
6
  import type {WebGPUDevice} from '../webgpu-device';
7
7
  import {WebGPUSampler} from './webgpu-sampler';
8
8
 
@@ -37,7 +37,7 @@ export class WebGPUExternalTexture extends ExternalTexture {
37
37
  }
38
38
 
39
39
  /** Set default sampler */
40
- setSampler(sampler: Sampler | SamplerProps): this {
40
+ setSampler(sampler: WebGPUSampler | SamplerProps): this {
41
41
  // We can accept a sampler instance or set of props;
42
42
  this.sampler =
43
43
  sampler instanceof WebGPUSampler ? sampler : new WebGPUSampler(this.device, sampler);
@@ -13,9 +13,10 @@ import {WebGPUTextureView} from '../resources/webgpu-texture-view';
13
13
  */
14
14
  export class WebGPUFramebuffer extends Framebuffer {
15
15
  readonly device: WebGPUDevice;
16
+ readonly handle = null;
16
17
 
17
- colorAttachments: WebGPUTextureView[] = [];
18
- depthStencilAttachment: WebGPUTextureView | null = null;
18
+ readonly colorAttachments: WebGPUTextureView[] = [];
19
+ readonly depthStencilAttachment: WebGPUTextureView | null = null;
19
20
 
20
21
  constructor(device: WebGPUDevice, props: FramebufferProps) {
21
22
  super(device, props);
@@ -1,4 +1,9 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
1
5
  import {
6
+ log,
2
7
  PipelineLayout,
3
8
  PipelineLayoutProps,
4
9
  StorageBufferBindingLayout,
@@ -7,8 +12,8 @@ import {
7
12
  import {WebGPUDevice} from '../webgpu-device';
8
13
 
9
14
  export class WebGPUPipelineLayout extends PipelineLayout {
10
- device: WebGPUDevice;
11
- handle: GPUPipelineLayout;
15
+ readonly device: WebGPUDevice;
16
+ readonly handle: GPUPipelineLayout;
12
17
 
13
18
  constructor(device: WebGPUDevice, props: PipelineLayoutProps) {
14
19
  super(device, props);
@@ -101,7 +106,7 @@ export class WebGPUPipelineLayout extends PipelineLayout {
101
106
  }
102
107
 
103
108
  default: {
104
- console.warn('unhandled binding type when creating pipeline descriptor');
109
+ log.warn('unhandled binding type when creating pipeline descriptor')();
105
110
  }
106
111
  }
107
112
 
@@ -47,12 +47,12 @@ export class WebGPURenderPass extends RenderPass {
47
47
  throw new Error('commandEncoder not available');
48
48
  }
49
49
 
50
- this.device.handle.pushErrorScope('validation');
51
- this.handle = this.props.handle || device.commandEncoder.beginRenderPass(renderPassDescriptor);
52
- this.device.handle.popErrorScope().then((error: GPUError | null) => {
53
- if (error) {
54
- log.error(`${this} creation failed:\n"${error.message}"`, this)();
55
- }
50
+ this.device.pushErrorScope('validation');
51
+ this.handle =
52
+ this.props.handle || device.commandEncoder.handle.beginRenderPass(renderPassDescriptor);
53
+ this.device.popErrorScope((error: GPUError) => {
54
+ this.device.reportError(new Error(`${this} creation failed:\n"${error.message}"`), this)();
55
+ this.device.debug();
56
56
  });
57
57
  this.handle.label = this.props.id;
58
58
  log.groupCollapsed(3, `new WebGPURenderPass(${this.id})`)();
@@ -68,7 +68,12 @@ export class WebGPURenderPass extends RenderPass {
68
68
 
69
69
  setPipeline(pipeline: RenderPipeline): void {
70
70
  this.pipeline = pipeline as WebGPURenderPipeline;
71
+ this.device.pushErrorScope('validation');
71
72
  this.handle.setPipeline(this.pipeline.handle);
73
+ this.device.popErrorScope((error: GPUError) => {
74
+ this.device.reportError(new Error(`${this} setPipeline failed:\n"${error.message}"`), this)();
75
+ this.device.debug();
76
+ });
72
77
  }
73
78
 
74
79
  /** Sets an array of bindings (uniform buffers, samplers, textures, ...) */
@@ -19,17 +19,21 @@ import type {WebGPURenderPass} from './webgpu-render-pass';
19
19
 
20
20
  /** Creates a new render pipeline when parameters change */
21
21
  export class WebGPURenderPipeline extends RenderPipeline {
22
- device: WebGPUDevice;
23
- handle: GPURenderPipeline;
22
+ readonly device: WebGPUDevice;
23
+ readonly handle: GPURenderPipeline;
24
24
 
25
- vs: WebGPUShader;
26
- fs: WebGPUShader | null = null;
25
+ readonly vs: WebGPUShader;
26
+ readonly fs: WebGPUShader | null = null;
27
27
 
28
28
  /** For internal use to create BindGroups */
29
29
  private _bindings: Record<string, Binding>;
30
30
  private _bindGroupLayout: GPUBindGroupLayout | null = null;
31
31
  private _bindGroup: GPUBindGroup | null = null;
32
32
 
33
+ override get [Symbol.toStringTag]() {
34
+ return 'WebGPURenderPipeline';
35
+ }
36
+
33
37
  constructor(device: WebGPUDevice, props: RenderPipelineProps) {
34
38
  super(device, props);
35
39
  this.device = device;
@@ -40,12 +44,11 @@ export class WebGPURenderPipeline extends RenderPipeline {
40
44
  log.probe(1, JSON.stringify(descriptor, null, 2))();
41
45
  log.groupEnd(1)();
42
46
 
43
- this.device.handle.pushErrorScope('validation');
47
+ this.device.pushErrorScope('validation');
44
48
  this.handle = this.device.handle.createRenderPipeline(descriptor);
45
- this.device.handle.popErrorScope().then((error: GPUError | null) => {
46
- if (error) {
47
- log.error(`${this} creation failed:\n"${error.message}"`, this, this.props.vs?.source)();
48
- }
49
+ this.device.popErrorScope((error: GPUError) => {
50
+ this.device.reportError(new Error(`${this} creation failed:\n"${error.message}"`), this)();
51
+ this.device.debug();
49
52
  });
50
53
  }
51
54
  this.handle.label = this.props.id;
@@ -68,6 +71,12 @@ export class WebGPURenderPipeline extends RenderPipeline {
68
71
  * @todo Do we want to expose BindGroups in the API and remove this?
69
72
  */
70
73
  setBindings(bindings: Record<string, Binding>): void {
74
+ // Invalidate the cached bind group if any value has changed
75
+ for (const [name, binding] of Object.entries(bindings)) {
76
+ if (this._bindings[name] !== binding) {
77
+ this._bindGroup = null;
78
+ }
79
+ }
71
80
  Object.assign(this._bindings, bindings);
72
81
  }
73
82
 
@@ -86,12 +95,11 @@ export class WebGPURenderPipeline extends RenderPipeline {
86
95
  const webgpuRenderPass = options.renderPass as WebGPURenderPass;
87
96
 
88
97
  // Set pipeline
89
- this.device.handle.pushErrorScope('validation');
98
+ this.device.pushErrorScope('validation');
90
99
  webgpuRenderPass.handle.setPipeline(this.handle);
91
- this.device.handle.popErrorScope().then((error: GPUError | null) => {
92
- if (error) {
93
- log.error(`${this} setPipeline failed:\n"${error.message}"`, this)();
94
- }
100
+ this.device.popErrorScope((error: GPUError) => {
101
+ this.device.reportError(new Error(`${this} setPipeline failed:\n"${error.message}"`), this)();
102
+ this.device.debug();
95
103
  });
96
104
 
97
105
  // Set bindings (uniform buffers, textures etc)
@@ -156,16 +164,22 @@ export class WebGPURenderPipeline extends RenderPipeline {
156
164
  buffers: getVertexBufferLayout(this.shaderLayout, this.props.bufferLayout)
157
165
  };
158
166
 
167
+ // Populate color targets
168
+ // TODO - at the moment blend and write mask are only set on the first target
169
+ const targets: (GPUColorTargetState | null)[] = [];
170
+ if (this.props.colorAttachmentFormats) {
171
+ for (const format of this.props.colorAttachmentFormats) {
172
+ targets.push(format ? {format: getWebGPUTextureFormat(format)} : null);
173
+ }
174
+ } else {
175
+ targets.push({format: getWebGPUTextureFormat(this.device.preferredColorFormat)});
176
+ }
177
+
159
178
  // Set up the fragment stage
160
179
  const fragment: GPUFragmentState = {
161
180
  module: (this.props.fs as WebGPUShader).handle,
162
181
  entryPoint: this.props.fragmentEntryPoint || 'main',
163
- targets: [
164
- {
165
- // TODO exclamation mark hack!
166
- format: getWebGPUTextureFormat(this.device.getCanvasContext().format)
167
- }
168
- ]
182
+ targets
169
183
  };
170
184
 
171
185
  const layout = this.device.createPipelineLayout({
@@ -182,11 +196,12 @@ export class WebGPURenderPipeline extends RenderPipeline {
182
196
  layout: layout.handle
183
197
  };
184
198
 
185
- if (this.props.parameters.depthWriteEnabled && this.props.parameters.depthCompare) {
199
+ // Set depth format if required, defaulting to the preferred depth format
200
+ const depthFormat = this.props.depthStencilAttachmentFormat || this.device.preferredDepthFormat;
201
+
202
+ if (this.props.parameters.depthWriteEnabled) {
186
203
  descriptor.depthStencil = {
187
- format: 'depth24plus',
188
- depthWriteEnabled: this.props.parameters.depthWriteEnabled,
189
- depthCompare: this.props.parameters.depthCompare
204
+ format: getWebGPUTextureFormat(depthFormat)
190
205
  };
191
206
  }
192
207
 
@@ -35,7 +35,7 @@ export class WebGPUSampler extends Sampler {
35
35
  samplerDescriptor.mipmapFilter = props.mipmapFilter;
36
36
  }
37
37
 
38
- this.handle = this.handle || this.device.handle.createSampler(samplerDescriptor);
38
+ this.handle = props.handle || this.device.handle.createSampler(samplerDescriptor);
39
39
  this.handle.label = this.props.id;
40
40
  }
41
41