@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.
- package/dist/adapter/helpers/accessor-to-format.js +1 -0
- package/dist/adapter/helpers/accessor-to-format.js.map +1 -1
- package/dist/adapter/helpers/get-bind-group.d.ts +3 -1
- package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
- package/dist/adapter/helpers/get-bind-group.js +28 -10
- package/dist/adapter/helpers/get-bind-group.js.map +1 -1
- package/dist/adapter/helpers/get-vertex-buffer-layout.js +5 -5
- package/dist/adapter/helpers/get-vertex-buffer-layout.js.map +1 -1
- package/dist/adapter/helpers/webgpu-parameters.d.ts.map +1 -1
- package/dist/adapter/helpers/webgpu-parameters.js +89 -75
- package/dist/adapter/helpers/webgpu-parameters.js.map +1 -1
- package/dist/adapter/resources/webgpu-buffer.d.ts +13 -16
- package/dist/adapter/resources/webgpu-buffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-buffer.js +132 -93
- package/dist/adapter/resources/webgpu-buffer.js.map +1 -1
- package/dist/adapter/resources/webgpu-command-buffer.d.ts +10 -0
- package/dist/adapter/resources/webgpu-command-buffer.d.ts.map +1 -0
- package/dist/adapter/resources/webgpu-command-buffer.js +18 -0
- package/dist/adapter/resources/webgpu-command-buffer.js.map +1 -0
- package/dist/adapter/resources/webgpu-command-encoder.d.ts +12 -5
- package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-command-encoder.js +28 -5
- package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.js +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pipeline.d.ts +2 -2
- package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
- package/dist/adapter/resources/webgpu-external-texture.d.ts +2 -2
- package/dist/adapter/resources/webgpu-external-texture.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-external-texture.js.map +1 -1
- package/dist/adapter/resources/webgpu-framebuffer.d.ts +3 -2
- package/dist/adapter/resources/webgpu-framebuffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-framebuffer.js +1 -0
- package/dist/adapter/resources/webgpu-framebuffer.js.map +1 -1
- package/dist/adapter/resources/webgpu-pipeline-layout.d.ts +2 -2
- package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-pipeline-layout.js +5 -2
- package/dist/adapter/resources/webgpu-pipeline-layout.js.map +1 -1
- package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-render-pass.js +11 -6
- package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
- package/dist/adapter/resources/webgpu-render-pipeline.d.ts +5 -4
- package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-render-pipeline.js +33 -20
- package/dist/adapter/resources/webgpu-render-pipeline.js.map +1 -1
- package/dist/adapter/resources/webgpu-sampler.js +1 -1
- package/dist/adapter/resources/webgpu-sampler.js.map +1 -1
- package/dist/adapter/resources/webgpu-shader.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-shader.js +7 -8
- package/dist/adapter/resources/webgpu-shader.js.map +1 -1
- package/dist/adapter/resources/webgpu-texture-view.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-texture-view.js +15 -10
- package/dist/adapter/resources/webgpu-texture-view.js.map +1 -1
- package/dist/adapter/resources/webgpu-texture.d.ts +4 -37
- package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-texture.js +97 -126
- package/dist/adapter/resources/webgpu-texture.js.map +1 -1
- package/dist/adapter/resources/webgpu-vertex-array.d.ts +2 -2
- package/dist/adapter/resources/webgpu-vertex-array.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-vertex-array.js +2 -2
- package/dist/adapter/resources/webgpu-vertex-array.js.map +1 -1
- package/dist/adapter/webgpu-adapter.d.ts +2 -3
- package/dist/adapter/webgpu-adapter.d.ts.map +1 -1
- package/dist/adapter/webgpu-adapter.js +54 -43
- package/dist/adapter/webgpu-adapter.js.map +1 -1
- package/dist/adapter/webgpu-canvas-context.d.ts +7 -15
- package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
- package/dist/adapter/webgpu-canvas-context.js +50 -72
- package/dist/adapter/webgpu-canvas-context.js.map +1 -1
- package/dist/adapter/webgpu-device.d.ts +16 -27
- package/dist/adapter/webgpu-device.d.ts.map +1 -1
- package/dist/adapter/webgpu-device.js +48 -65
- package/dist/adapter/webgpu-device.js.map +1 -1
- package/dist/dist.dev.js +1919 -1520
- package/dist/dist.min.js +6 -5
- package/dist/index.cjs +1749 -1397
- package/dist/index.cjs.map +4 -4
- package/package.json +3 -3
- package/src/adapter/helpers/get-bind-group.ts +31 -11
- package/src/adapter/helpers/get-vertex-buffer-layout.ts +5 -5
- package/src/adapter/helpers/webgpu-parameters.ts +114 -102
- package/src/adapter/resources/webgpu-buffer.ts +163 -102
- package/src/adapter/resources/webgpu-command-buffer.ts +24 -0
- package/src/adapter/resources/webgpu-command-encoder.ts +34 -4
- package/src/adapter/resources/webgpu-compute-pass.ts +1 -1
- package/src/adapter/resources/webgpu-compute-pipeline.ts +2 -2
- package/src/adapter/resources/webgpu-external-texture.ts +2 -2
- package/src/adapter/resources/webgpu-framebuffer.ts +3 -2
- package/src/adapter/resources/webgpu-pipeline-layout.ts +8 -3
- package/src/adapter/resources/webgpu-render-pass.ts +11 -6
- package/src/adapter/resources/webgpu-render-pipeline.ts +39 -24
- package/src/adapter/resources/webgpu-sampler.ts +1 -1
- package/src/adapter/resources/webgpu-shader.ts +11 -8
- package/src/adapter/resources/webgpu-texture-view.ts +14 -8
- package/src/adapter/resources/webgpu-texture.ts +106 -186
- package/src/adapter/resources/webgpu-vertex-array.ts +2 -2
- package/src/adapter/webgpu-adapter.ts +72 -58
- package/src/adapter/webgpu-canvas-context.ts +62 -82
- 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
|
|
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 =
|
|
22
|
-
const
|
|
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
|
-
|
|
28
|
+
label: this.props.id,
|
|
31
29
|
// usage defaults to vertex
|
|
32
30
|
usage: this.props.usage || GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
33
|
-
mappedAtCreation
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
90
|
+
async mapAndWriteAsync(
|
|
91
|
+
callback: BufferMapCallback<void>,
|
|
65
92
|
byteOffset: number = 0,
|
|
66
|
-
byteLength: number = this.byteLength
|
|
67
|
-
): Promise<
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
106
|
-
this.handle.unmap();
|
|
107
|
-
}
|
|
108
|
-
}
|
|
154
|
+
const readBuffer = mappableBuffer || this;
|
|
109
155
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
await
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.
|
|
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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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(
|
|
31
|
-
|
|
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;
|
|
@@ -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,
|
|
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:
|
|
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
|
-
|
|
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.
|
|
51
|
-
this.handle =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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.
|
|
47
|
+
this.device.pushErrorScope('validation');
|
|
44
48
|
this.handle = this.device.handle.createRenderPipeline(descriptor);
|
|
45
|
-
this.device.
|
|
46
|
-
|
|
47
|
-
|
|
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.
|
|
98
|
+
this.device.pushErrorScope('validation');
|
|
90
99
|
webgpuRenderPass.handle.setPipeline(this.handle);
|
|
91
|
-
this.device.
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
38
|
+
this.handle = props.handle || this.device.handle.createSampler(samplerDescriptor);
|
|
39
39
|
this.handle.label = this.props.id;
|
|
40
40
|
}
|
|
41
41
|
|