@luma.gl/webgpu 9.3.0-alpha.2 → 9.3.0-alpha.6
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/cpu-hotspot-profiler.d.ts +54 -0
- package/dist/adapter/helpers/cpu-hotspot-profiler.d.ts.map +1 -0
- package/dist/adapter/helpers/cpu-hotspot-profiler.js +26 -0
- package/dist/adapter/helpers/cpu-hotspot-profiler.js.map +1 -0
- package/dist/adapter/helpers/generate-mipmaps-webgpu.d.ts +7 -0
- package/dist/adapter/helpers/generate-mipmaps-webgpu.d.ts.map +1 -0
- package/dist/adapter/helpers/generate-mipmaps-webgpu.js +490 -0
- package/dist/adapter/helpers/generate-mipmaps-webgpu.js.map +1 -0
- package/dist/adapter/helpers/get-bind-group.d.ts +2 -1
- package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
- package/dist/adapter/helpers/get-bind-group.js +31 -21
- package/dist/adapter/helpers/get-bind-group.js.map +1 -1
- package/dist/adapter/resources/webgpu-buffer.d.ts +7 -0
- package/dist/adapter/resources/webgpu-buffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-buffer.js +58 -15
- package/dist/adapter/resources/webgpu-buffer.js.map +1 -1
- package/dist/adapter/resources/webgpu-command-buffer.js +1 -1
- package/dist/adapter/resources/webgpu-command-buffer.js.map +1 -1
- package/dist/adapter/resources/webgpu-command-encoder.d.ts +5 -4
- package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-command-encoder.js +23 -5
- package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.d.ts +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.js +14 -6
- package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pipeline.js +19 -3
- package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
- package/dist/adapter/resources/webgpu-framebuffer.d.ts +6 -0
- package/dist/adapter/resources/webgpu-framebuffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-framebuffer.js +16 -0
- package/dist/adapter/resources/webgpu-framebuffer.js.map +1 -1
- package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-pipeline-layout.js +1 -2
- package/dist/adapter/resources/webgpu-pipeline-layout.js.map +1 -1
- package/dist/adapter/resources/webgpu-query-set.d.ts +33 -4
- package/dist/adapter/resources/webgpu-query-set.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-query-set.js +145 -4
- package/dist/adapter/resources/webgpu-query-set.js.map +1 -1
- package/dist/adapter/resources/webgpu-render-pass.d.ts +3 -0
- package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-render-pass.js +74 -30
- package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
- package/dist/adapter/resources/webgpu-render-pipeline.d.ts +7 -4
- package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-render-pipeline.js +26 -15
- package/dist/adapter/resources/webgpu-render-pipeline.js.map +1 -1
- package/dist/adapter/resources/webgpu-sampler.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-sampler.js +4 -0
- package/dist/adapter/resources/webgpu-sampler.js.map +1 -1
- package/dist/adapter/resources/webgpu-texture-view.d.ts +6 -0
- package/dist/adapter/resources/webgpu-texture-view.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-texture-view.js +47 -11
- package/dist/adapter/resources/webgpu-texture-view.js.map +1 -1
- package/dist/adapter/resources/webgpu-texture.d.ts +10 -4
- package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-texture.js +116 -57
- package/dist/adapter/resources/webgpu-texture.js.map +1 -1
- package/dist/adapter/resources/webgpu-vertex-array.js +1 -1
- package/dist/adapter/resources/webgpu-vertex-array.js.map +1 -1
- package/dist/adapter/webgpu-canvas-context.d.ts +2 -0
- package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
- package/dist/adapter/webgpu-canvas-context.js +78 -19
- package/dist/adapter/webgpu-canvas-context.js.map +1 -1
- package/dist/adapter/webgpu-device.d.ts +5 -1
- package/dist/adapter/webgpu-device.d.ts.map +1 -1
- package/dist/adapter/webgpu-device.js +113 -9
- package/dist/adapter/webgpu-device.js.map +1 -1
- package/dist/adapter/webgpu-presentation-context.d.ts +25 -0
- package/dist/adapter/webgpu-presentation-context.d.ts.map +1 -0
- package/dist/adapter/webgpu-presentation-context.js +144 -0
- package/dist/adapter/webgpu-presentation-context.js.map +1 -0
- package/dist/dist.dev.js +2864 -1702
- package/dist/dist.min.js +167 -8
- package/dist/index.cjs +1363 -225
- package/dist/index.cjs.map +4 -4
- package/package.json +5 -5
- package/src/adapter/helpers/cpu-hotspot-profiler.ts +70 -0
- package/src/adapter/helpers/generate-mipmaps-webgpu.ts +583 -0
- package/src/adapter/helpers/get-bind-group.ts +37 -22
- package/src/adapter/resources/webgpu-buffer.ts +61 -15
- package/src/adapter/resources/webgpu-command-buffer.ts +1 -1
- package/src/adapter/resources/webgpu-command-encoder.ts +32 -6
- package/src/adapter/resources/webgpu-compute-pass.ts +14 -6
- package/src/adapter/resources/webgpu-compute-pipeline.ts +21 -3
- package/src/adapter/resources/webgpu-framebuffer.ts +21 -0
- package/src/adapter/resources/webgpu-pipeline-layout.ts +1 -2
- package/src/adapter/resources/webgpu-query-set.ts +185 -9
- package/src/adapter/resources/webgpu-render-pass.ts +82 -34
- package/src/adapter/resources/webgpu-render-pipeline.ts +36 -19
- package/src/adapter/resources/webgpu-sampler.ts +5 -0
- package/src/adapter/resources/webgpu-texture-view.ts +51 -11
- package/src/adapter/resources/webgpu-texture.ts +142 -93
- package/src/adapter/resources/webgpu-vertex-array.ts +1 -1
- package/src/adapter/webgpu-canvas-context.ts +91 -26
- package/src/adapter/webgpu-device.ts +128 -9
- package/src/adapter/webgpu-presentation-context.ts +180 -0
|
@@ -3,10 +3,12 @@
|
|
|
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
|
+
import type {WebGPUDevice} from '../webgpu-device';
|
|
7
8
|
import type {WebGPUBuffer} from '../resources/webgpu-buffer';
|
|
8
9
|
import type {WebGPUSampler} from '../resources/webgpu-sampler';
|
|
9
10
|
import type {WebGPUTexture} from '../resources/webgpu-texture';
|
|
11
|
+
import type {WebGPUTextureView} from '../resources/webgpu-texture-view';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Create a WebGPU "bind group layout" from an array of luma.gl bindings
|
|
@@ -28,21 +30,19 @@ export function makeBindGroupLayout(
|
|
|
28
30
|
* Create a WebGPU "bind group" from an array of luma.gl bindings
|
|
29
31
|
*/
|
|
30
32
|
export function getBindGroup(
|
|
31
|
-
device:
|
|
33
|
+
device: WebGPUDevice,
|
|
32
34
|
bindGroupLayout: GPUBindGroupLayout,
|
|
33
35
|
shaderLayout: ComputeShaderLayout,
|
|
34
36
|
bindings: Record<string, Binding>
|
|
35
37
|
): GPUBindGroup {
|
|
36
38
|
const entries = getBindGroupEntries(bindings, shaderLayout);
|
|
37
39
|
device.pushErrorScope('validation');
|
|
38
|
-
const bindGroup = device.createBindGroup({
|
|
40
|
+
const bindGroup = device.handle.createBindGroup({
|
|
39
41
|
layout: bindGroupLayout,
|
|
40
42
|
entries
|
|
41
43
|
});
|
|
42
|
-
device.popErrorScope(
|
|
43
|
-
|
|
44
|
-
log.error(`bindGroup creation: ${error.message}`, bindGroup)();
|
|
45
|
-
}
|
|
44
|
+
device.popErrorScope((error: GPUError) => {
|
|
45
|
+
log.error(`bindGroup creation: ${error.message}`, bindGroup)();
|
|
46
46
|
});
|
|
47
47
|
return bindGroup;
|
|
48
48
|
}
|
|
@@ -74,23 +74,31 @@ function getBindGroupEntries(
|
|
|
74
74
|
const entries: GPUBindGroupEntry[] = [];
|
|
75
75
|
|
|
76
76
|
for (const [bindingName, value] of Object.entries(bindings)) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
const exactBindingLayout = shaderLayout.bindings.find(binding => binding.name === bindingName);
|
|
78
|
+
const bindingLayout = exactBindingLayout || getShaderLayoutBinding(shaderLayout, bindingName);
|
|
79
|
+
const isShadowedAlias =
|
|
80
|
+
!exactBindingLayout && bindingLayout ? bindingLayout.name in bindings : false;
|
|
81
|
+
|
|
82
|
+
// Mirror the WebGL path: when both `foo` and `fooUniforms` exist in the bindings map,
|
|
83
|
+
// prefer the exact shader binding name and ignore the alias entry.
|
|
84
|
+
if (!isShadowedAlias) {
|
|
85
|
+
const entry = bindingLayout
|
|
86
|
+
? getBindGroupEntry(value, bindingLayout.location, undefined, bindingName)
|
|
87
|
+
: null;
|
|
80
88
|
if (entry) {
|
|
81
89
|
entries.push(entry);
|
|
82
90
|
}
|
|
83
|
-
}
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
// TODO - hack to automatically bind samplers to supplied texture default samplers
|
|
93
|
+
if (value instanceof Texture) {
|
|
94
|
+
const samplerBindingLayout = getShaderLayoutBinding(shaderLayout, `${bindingName}Sampler`, {
|
|
95
|
+
ignoreWarnings: true
|
|
96
|
+
});
|
|
97
|
+
const samplerEntry = samplerBindingLayout
|
|
98
|
+
? getBindGroupEntry(value, samplerBindingLayout.location, {sampler: true}, bindingName)
|
|
99
|
+
: null;
|
|
100
|
+
if (samplerEntry) {
|
|
101
|
+
entries.push(samplerEntry);
|
|
94
102
|
}
|
|
95
103
|
}
|
|
96
104
|
}
|
|
@@ -102,7 +110,8 @@ function getBindGroupEntries(
|
|
|
102
110
|
function getBindGroupEntry(
|
|
103
111
|
binding: Binding,
|
|
104
112
|
index: number,
|
|
105
|
-
options?: {sampler?: boolean}
|
|
113
|
+
options?: {sampler?: boolean},
|
|
114
|
+
bindingName: string = 'unknown'
|
|
106
115
|
): GPUBindGroupEntry | null {
|
|
107
116
|
if (binding instanceof Buffer) {
|
|
108
117
|
return {
|
|
@@ -118,6 +127,12 @@ function getBindGroupEntry(
|
|
|
118
127
|
resource: (binding as WebGPUSampler).handle
|
|
119
128
|
};
|
|
120
129
|
}
|
|
130
|
+
if (binding instanceof TextureView) {
|
|
131
|
+
return {
|
|
132
|
+
binding: index,
|
|
133
|
+
resource: (binding as WebGPUTextureView).handle
|
|
134
|
+
};
|
|
135
|
+
}
|
|
121
136
|
if (binding instanceof Texture) {
|
|
122
137
|
if (options?.sampler) {
|
|
123
138
|
return {
|
|
@@ -130,6 +145,6 @@ function getBindGroupEntry(
|
|
|
130
145
|
resource: (binding as WebGPUTexture).view.handle
|
|
131
146
|
};
|
|
132
147
|
}
|
|
133
|
-
log.warn(`invalid binding ${
|
|
148
|
+
log.warn(`invalid binding ${bindingName}`, binding);
|
|
134
149
|
return null;
|
|
135
150
|
}
|
|
@@ -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 =
|
|
29
|
+
const size = this.paddedByteLength;
|
|
22
30
|
|
|
23
31
|
this.device.pushErrorScope('out-of-memory');
|
|
24
32
|
this.device.pushErrorScope('validation');
|
|
@@ -59,12 +67,27 @@ export class WebGPUBuffer extends Buffer {
|
|
|
59
67
|
this.device.reportError(new Error(`${this} creation failed ${error.message}`), this)();
|
|
60
68
|
this.device.debug();
|
|
61
69
|
});
|
|
70
|
+
|
|
71
|
+
if (!this.props.handle) {
|
|
72
|
+
this.trackAllocatedMemory(size);
|
|
73
|
+
} else {
|
|
74
|
+
this.trackReferencedMemory(size, 'Buffer');
|
|
75
|
+
}
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
override destroy(): void {
|
|
65
|
-
this.handle
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
if (!this.destroyed && this.handle) {
|
|
80
|
+
this.removeStats();
|
|
81
|
+
if (!this.props.handle) {
|
|
82
|
+
this.trackDeallocatedMemory();
|
|
83
|
+
this.handle.destroy();
|
|
84
|
+
} else {
|
|
85
|
+
this.trackDeallocatedReferencedMemory('Buffer');
|
|
86
|
+
}
|
|
87
|
+
this.destroyed = true;
|
|
88
|
+
// @ts-expect-error readonly
|
|
89
|
+
this.handle = null;
|
|
90
|
+
}
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
write(data: ArrayBufferLike | ArrayBufferView | SharedArrayBuffer, byteOffset = 0) {
|
|
@@ -92,10 +115,11 @@ export class WebGPUBuffer extends Buffer {
|
|
|
92
115
|
byteOffset: number = 0,
|
|
93
116
|
byteLength: number = this.byteLength - byteOffset
|
|
94
117
|
): Promise<void> {
|
|
118
|
+
const alignedByteLength = Math.ceil(byteLength / 4) * 4;
|
|
95
119
|
// Unless the application created and supplied a mappable buffer, a staging buffer is needed
|
|
96
120
|
const isMappable = (this.usage & Buffer.MAP_WRITE) !== 0;
|
|
97
121
|
const mappableBuffer: WebGPUBuffer | null = !isMappable
|
|
98
|
-
? this._getMappableBuffer(Buffer.MAP_WRITE | Buffer.COPY_SRC, 0, this.
|
|
122
|
+
? this._getMappableBuffer(Buffer.MAP_WRITE | Buffer.COPY_SRC, 0, this.paddedByteLength)
|
|
99
123
|
: null;
|
|
100
124
|
|
|
101
125
|
const writeBuffer = mappableBuffer || this;
|
|
@@ -105,13 +129,15 @@ export class WebGPUBuffer extends Buffer {
|
|
|
105
129
|
this.device.pushErrorScope('validation');
|
|
106
130
|
try {
|
|
107
131
|
await this.device.handle.queue.onSubmittedWorkDone();
|
|
108
|
-
await writeBuffer.handle.mapAsync(GPUMapMode.WRITE, byteOffset,
|
|
109
|
-
const
|
|
132
|
+
await writeBuffer.handle.mapAsync(GPUMapMode.WRITE, byteOffset, alignedByteLength);
|
|
133
|
+
const mappedRange = writeBuffer.handle.getMappedRange(byteOffset, alignedByteLength);
|
|
134
|
+
const arrayBuffer = mappedRange.slice(0, byteLength);
|
|
110
135
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
111
136
|
await callback(arrayBuffer, 'mapped');
|
|
137
|
+
new Uint8Array(mappedRange).set(new Uint8Array(arrayBuffer), 0);
|
|
112
138
|
writeBuffer.handle.unmap();
|
|
113
139
|
if (mappableBuffer) {
|
|
114
|
-
this._copyBuffer(mappableBuffer, byteOffset,
|
|
140
|
+
this._copyBuffer(mappableBuffer, byteOffset, alignedByteLength);
|
|
115
141
|
}
|
|
116
142
|
} finally {
|
|
117
143
|
this.device.popErrorScope((error: GPUError) => {
|
|
@@ -138,17 +164,33 @@ export class WebGPUBuffer extends Buffer {
|
|
|
138
164
|
byteOffset = 0,
|
|
139
165
|
byteLength = this.byteLength - byteOffset
|
|
140
166
|
): Promise<T> {
|
|
167
|
+
const requestedEnd = byteOffset + byteLength;
|
|
168
|
+
if (requestedEnd > this.byteLength) {
|
|
169
|
+
throw new Error('Mapping range exceeds buffer size');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let mappedByteOffset = byteOffset;
|
|
173
|
+
let mappedByteLength = byteLength;
|
|
174
|
+
let sliceByteOffset = 0;
|
|
175
|
+
let lifetime: 'mapped' | 'copied' = 'mapped';
|
|
176
|
+
|
|
177
|
+
// WebGPU mapAsync requires 8-byte offsets and 4-byte lengths.
|
|
141
178
|
if (byteOffset % 8 !== 0 || byteLength % 4 !== 0) {
|
|
142
|
-
|
|
179
|
+
mappedByteOffset = Math.floor(byteOffset / 8) * 8;
|
|
180
|
+
const alignedEnd = Math.ceil(requestedEnd / 4) * 4;
|
|
181
|
+
mappedByteLength = alignedEnd - mappedByteOffset;
|
|
182
|
+
sliceByteOffset = byteOffset - mappedByteOffset;
|
|
183
|
+
lifetime = 'copied';
|
|
143
184
|
}
|
|
144
|
-
|
|
185
|
+
|
|
186
|
+
if (mappedByteOffset + mappedByteLength > this.paddedByteLength) {
|
|
145
187
|
throw new Error('Mapping range exceeds buffer size');
|
|
146
188
|
}
|
|
147
189
|
|
|
148
190
|
// Unless the application created and supplied a mappable buffer, a staging buffer is needed
|
|
149
191
|
const isMappable = (this.usage & Buffer.MAP_READ) !== 0;
|
|
150
192
|
const mappableBuffer: WebGPUBuffer | null = !isMappable
|
|
151
|
-
? this._getMappableBuffer(Buffer.MAP_READ | Buffer.COPY_DST, 0, this.
|
|
193
|
+
? this._getMappableBuffer(Buffer.MAP_READ | Buffer.COPY_DST, 0, this.paddedByteLength)
|
|
152
194
|
: null;
|
|
153
195
|
|
|
154
196
|
const readBuffer = mappableBuffer || this;
|
|
@@ -158,12 +200,16 @@ export class WebGPUBuffer extends Buffer {
|
|
|
158
200
|
try {
|
|
159
201
|
await this.device.handle.queue.onSubmittedWorkDone();
|
|
160
202
|
if (mappableBuffer) {
|
|
161
|
-
mappableBuffer._copyBuffer(this);
|
|
203
|
+
mappableBuffer._copyBuffer(this, mappedByteOffset, mappedByteLength);
|
|
162
204
|
}
|
|
163
|
-
await readBuffer.handle.mapAsync(GPUMapMode.READ,
|
|
164
|
-
const arrayBuffer = readBuffer.handle.getMappedRange(
|
|
205
|
+
await readBuffer.handle.mapAsync(GPUMapMode.READ, mappedByteOffset, mappedByteLength);
|
|
206
|
+
const arrayBuffer = readBuffer.handle.getMappedRange(mappedByteOffset, mappedByteLength);
|
|
207
|
+
const mappedRange =
|
|
208
|
+
lifetime === 'mapped'
|
|
209
|
+
? arrayBuffer
|
|
210
|
+
: arrayBuffer.slice(sliceByteOffset, sliceByteOffset + byteLength);
|
|
165
211
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
166
|
-
const result = await callback(
|
|
212
|
+
const result = await callback(mappedRange, lifetime);
|
|
167
213
|
readBuffer.handle.unmap();
|
|
168
214
|
return result;
|
|
169
215
|
} finally {
|
|
@@ -13,7 +13,7 @@ export class WebGPUCommandBuffer extends CommandBuffer {
|
|
|
13
13
|
readonly handle: GPUCommandBuffer;
|
|
14
14
|
|
|
15
15
|
constructor(commandEncoder: WebGPUCommandEncoder, props: CommandBufferProps) {
|
|
16
|
-
super(commandEncoder.device,
|
|
16
|
+
super(commandEncoder.device, props);
|
|
17
17
|
this.device = commandEncoder.device;
|
|
18
18
|
this.handle =
|
|
19
19
|
this.props.handle ||
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
|
+
CommandBufferProps,
|
|
6
7
|
RenderPassProps,
|
|
7
8
|
ComputePassProps,
|
|
8
9
|
CopyTextureToTextureOptions,
|
|
@@ -34,9 +35,11 @@ export class WebGPUCommandEncoder extends CommandEncoder {
|
|
|
34
35
|
this.handle.label = this.props.id;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
override destroy(): void {
|
|
38
|
+
override destroy(): void {
|
|
39
|
+
this.destroyResource();
|
|
40
|
+
}
|
|
38
41
|
|
|
39
|
-
finish(props?:
|
|
42
|
+
finish(props?: CommandBufferProps): WebGPUCommandBuffer {
|
|
40
43
|
this.device.pushErrorScope('validation');
|
|
41
44
|
const commandBuffer = new WebGPUCommandBuffer(this, {
|
|
42
45
|
id: props?.id || 'unnamed-command-buffer'
|
|
@@ -46,6 +49,7 @@ export class WebGPUCommandEncoder extends CommandEncoder {
|
|
|
46
49
|
this.device.reportError(new Error(message), this)();
|
|
47
50
|
this.device.debug();
|
|
48
51
|
});
|
|
52
|
+
this.destroy();
|
|
49
53
|
return commandBuffer;
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -53,12 +57,12 @@ export class WebGPUCommandEncoder extends CommandEncoder {
|
|
|
53
57
|
* Allows a render pass to begin against a canvas context
|
|
54
58
|
* @todo need to support a "Framebuffer" equivalent (aka preconfigured RenderPassDescriptors?).
|
|
55
59
|
*/
|
|
56
|
-
beginRenderPass(props: RenderPassProps): WebGPURenderPass {
|
|
57
|
-
return new WebGPURenderPass(this.device, props);
|
|
60
|
+
beginRenderPass(props: RenderPassProps = {}): WebGPURenderPass {
|
|
61
|
+
return new WebGPURenderPass(this.device, this._applyTimeProfilingToPassProps(props));
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
beginComputePass(props: ComputePassProps): WebGPUComputePass {
|
|
61
|
-
return new WebGPUComputePass(this.device, props);
|
|
64
|
+
beginComputePass(props: ComputePassProps = {}): WebGPUComputePass {
|
|
65
|
+
return new WebGPUComputePass(this.device, this._applyTimeProfilingToPassProps(props));
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
// beginRenderPass(GPURenderPassDescriptor descriptor): GPURenderPassEncoder;
|
|
@@ -174,6 +178,28 @@ export class WebGPUCommandEncoder extends CommandEncoder {
|
|
|
174
178
|
options?.destinationOffset || 0
|
|
175
179
|
);
|
|
176
180
|
}
|
|
181
|
+
|
|
182
|
+
writeTimestamp(querySet: WebGPUQuerySet, queryIndex: number): void {
|
|
183
|
+
querySet._invalidateResults();
|
|
184
|
+
const writeTimestamp = (
|
|
185
|
+
this.handle as GPUCommandEncoder & {
|
|
186
|
+
writeTimestamp?: (querySet: GPUQuerySet, queryIndex: number) => void;
|
|
187
|
+
}
|
|
188
|
+
).writeTimestamp;
|
|
189
|
+
|
|
190
|
+
if (writeTimestamp) {
|
|
191
|
+
writeTimestamp.call(this.handle, querySet.handle, queryIndex);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const computePass = this.handle.beginComputePass({
|
|
196
|
+
timestampWrites: {
|
|
197
|
+
querySet: querySet.handle,
|
|
198
|
+
beginningOfPassWriteIndex: queryIndex
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
computePass.end();
|
|
202
|
+
}
|
|
177
203
|
}
|
|
178
204
|
|
|
179
205
|
/*
|
|
@@ -14,19 +14,21 @@ export class WebGPUComputePass extends ComputePass {
|
|
|
14
14
|
|
|
15
15
|
_webgpuPipeline: WebGPUComputePipeline | null = null;
|
|
16
16
|
|
|
17
|
-
constructor(device: WebGPUDevice, props: ComputePassProps) {
|
|
17
|
+
constructor(device: WebGPUDevice, props: ComputePassProps = {}) {
|
|
18
18
|
super(device, props);
|
|
19
19
|
this.device = device;
|
|
20
|
+
const {props: computePassProps} = this;
|
|
20
21
|
|
|
21
22
|
// Set up queries
|
|
22
23
|
let timestampWrites: GPUComputePassTimestampWrites | undefined;
|
|
23
|
-
if (
|
|
24
|
-
const webgpuQuerySet =
|
|
24
|
+
if (computePassProps.timestampQuerySet) {
|
|
25
|
+
const webgpuQuerySet = computePassProps.timestampQuerySet as WebGPUQuerySet;
|
|
25
26
|
if (webgpuQuerySet) {
|
|
27
|
+
webgpuQuerySet._invalidateResults();
|
|
26
28
|
timestampWrites = {
|
|
27
29
|
querySet: webgpuQuerySet.handle,
|
|
28
|
-
beginningOfPassWriteIndex:
|
|
29
|
-
endOfPassWriteIndex:
|
|
30
|
+
beginningOfPassWriteIndex: computePassProps.beginTimestampIndex,
|
|
31
|
+
endOfPassWriteIndex: computePassProps.endTimestampIndex
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -40,10 +42,16 @@ export class WebGPUComputePass extends ComputePass {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/** @note no WebGPU destroy method, just gc */
|
|
43
|
-
override destroy(): void {
|
|
45
|
+
override destroy(): void {
|
|
46
|
+
this.destroyResource();
|
|
47
|
+
}
|
|
44
48
|
|
|
45
49
|
end(): void {
|
|
50
|
+
if (this.destroyed) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
46
53
|
this.handle.end();
|
|
54
|
+
this.destroy();
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
setPipeline(pipeline: ComputePipeline): void {
|
|
@@ -7,6 +7,8 @@ import {getBindGroup} from '../helpers/get-bind-group';
|
|
|
7
7
|
import {WebGPUDevice} from '../webgpu-device';
|
|
8
8
|
import {WebGPUShader} from './webgpu-shader';
|
|
9
9
|
|
|
10
|
+
const EMPTY_BINDINGS: Record<string, Binding> = {};
|
|
11
|
+
|
|
10
12
|
// COMPUTE PIPELINE
|
|
11
13
|
|
|
12
14
|
/** Creates a new compute pipeline when parameters change */
|
|
@@ -18,7 +20,7 @@ export class WebGPUComputePipeline extends ComputePipeline {
|
|
|
18
20
|
private _bindGroupLayout: GPUBindGroupLayout | null = null;
|
|
19
21
|
private _bindGroup: GPUBindGroup | null = null;
|
|
20
22
|
/** For internal use to create BindGroups */
|
|
21
|
-
private _bindings: Record<string, Binding
|
|
23
|
+
private _bindings: Record<string, Binding>;
|
|
22
24
|
|
|
23
25
|
constructor(device: WebGPUDevice, props: ComputePipelineProps) {
|
|
24
26
|
super(device, props);
|
|
@@ -37,6 +39,8 @@ export class WebGPUComputePipeline extends ComputePipeline {
|
|
|
37
39
|
},
|
|
38
40
|
layout: 'auto'
|
|
39
41
|
});
|
|
42
|
+
|
|
43
|
+
this._bindings = EMPTY_BINDINGS;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
/**
|
|
@@ -44,7 +48,21 @@ export class WebGPUComputePipeline extends ComputePipeline {
|
|
|
44
48
|
* @todo Do we want to expose BindGroups in the API and remove this?
|
|
45
49
|
*/
|
|
46
50
|
setBindings(bindings: Record<string, Binding>): void {
|
|
47
|
-
|
|
51
|
+
let bindingsChanged = false;
|
|
52
|
+
for (const [name, binding] of Object.entries(bindings)) {
|
|
53
|
+
if (this._bindings[name] !== binding) {
|
|
54
|
+
if (!bindingsChanged) {
|
|
55
|
+
if (this._bindings === EMPTY_BINDINGS) {
|
|
56
|
+
this._bindings = {};
|
|
57
|
+
}
|
|
58
|
+
bindingsChanged = true;
|
|
59
|
+
}
|
|
60
|
+
this._bindings[name] = binding;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (bindingsChanged) {
|
|
64
|
+
this._bindGroup = null;
|
|
65
|
+
}
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
/** Return a bind group created by setBindings */
|
|
@@ -55,7 +73,7 @@ export class WebGPUComputePipeline extends ComputePipeline {
|
|
|
55
73
|
// Set up the bindings
|
|
56
74
|
this._bindGroup =
|
|
57
75
|
this._bindGroup ||
|
|
58
|
-
getBindGroup(this.device
|
|
76
|
+
getBindGroup(this.device, this._bindGroupLayout, this.shaderLayout, this._bindings);
|
|
59
77
|
|
|
60
78
|
return this._bindGroup;
|
|
61
79
|
}
|
|
@@ -29,4 +29,25 @@ export class WebGPUFramebuffer extends Framebuffer {
|
|
|
29
29
|
protected updateAttachments(): void {
|
|
30
30
|
// WebGPU framebuffers are JS only objects, nothing to update
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Internal-only hook for the cached CanvasContext/PresentationContext swapchain path.
|
|
35
|
+
* Rebinds the long-lived default framebuffer wrapper to the current per-frame color view
|
|
36
|
+
* and optional depth attachment without allocating a new luma.gl Framebuffer object.
|
|
37
|
+
*/
|
|
38
|
+
_reinitialize(
|
|
39
|
+
colorAttachment: WebGPUTextureView,
|
|
40
|
+
depthStencilAttachment: WebGPUTextureView | null
|
|
41
|
+
): void {
|
|
42
|
+
this.colorAttachments[0] = colorAttachment;
|
|
43
|
+
// @ts-expect-error Internal-only canvas wrapper reuse mutates this otherwise-readonly attachment.
|
|
44
|
+
this.depthStencilAttachment = depthStencilAttachment;
|
|
45
|
+
this.width = colorAttachment.texture.width;
|
|
46
|
+
this.height = colorAttachment.texture.height;
|
|
47
|
+
|
|
48
|
+
this.props.width = this.width;
|
|
49
|
+
this.props.height = this.height;
|
|
50
|
+
this.props.colorAttachments = [colorAttachment.texture];
|
|
51
|
+
this.props.depthStencilAttachment = depthStencilAttachment?.texture || null;
|
|
52
|
+
}
|
|
32
53
|
}
|
|
@@ -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 (
|
|
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) {
|