@luma.gl/webgpu 9.3.0-alpha.4 → 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 +22 -18
- package/dist/adapter/helpers/get-bind-group.js.map +1 -1
- package/dist/adapter/resources/webgpu-buffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-buffer.js +19 -3
- 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-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 +2815 -1681
- package/dist/dist.min.js +167 -8
- package/dist/index.cjs +1314 -199
- package/dist/index.cjs.map +4 -4
- package/package.json +4 -4
- 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 +26 -24
- package/src/adapter/resources/webgpu-buffer.ts +18 -3
- 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-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
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type {ComputeShaderLayout, BindingDeclaration, Binding} from '@luma.gl/core';
|
|
6
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';
|
|
@@ -29,21 +30,19 @@ export function makeBindGroupLayout(
|
|
|
29
30
|
* Create a WebGPU "bind group" from an array of luma.gl bindings
|
|
30
31
|
*/
|
|
31
32
|
export function getBindGroup(
|
|
32
|
-
device:
|
|
33
|
+
device: WebGPUDevice,
|
|
33
34
|
bindGroupLayout: GPUBindGroupLayout,
|
|
34
35
|
shaderLayout: ComputeShaderLayout,
|
|
35
36
|
bindings: Record<string, Binding>
|
|
36
37
|
): GPUBindGroup {
|
|
37
38
|
const entries = getBindGroupEntries(bindings, shaderLayout);
|
|
38
39
|
device.pushErrorScope('validation');
|
|
39
|
-
const bindGroup = device.createBindGroup({
|
|
40
|
+
const bindGroup = device.handle.createBindGroup({
|
|
40
41
|
layout: bindGroupLayout,
|
|
41
42
|
entries
|
|
42
43
|
});
|
|
43
|
-
device.popErrorScope(
|
|
44
|
-
|
|
45
|
-
log.error(`bindGroup creation: ${error.message}`, bindGroup)();
|
|
46
|
-
}
|
|
44
|
+
device.popErrorScope((error: GPUError) => {
|
|
45
|
+
log.error(`bindGroup creation: ${error.message}`, bindGroup)();
|
|
47
46
|
});
|
|
48
47
|
return bindGroup;
|
|
49
48
|
}
|
|
@@ -75,28 +74,31 @@ function getBindGroupEntries(
|
|
|
75
74
|
const entries: GPUBindGroupEntry[] = [];
|
|
76
75
|
|
|
77
76
|
for (const [bindingName, value] of Object.entries(bindings)) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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;
|
|
81
88
|
if (entry) {
|
|
82
89
|
entries.push(entry);
|
|
83
90
|
}
|
|
84
|
-
}
|
|
85
91
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
bindingName
|
|
97
|
-
);
|
|
98
|
-
if (entry) {
|
|
99
|
-
entries.push(entry);
|
|
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);
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
}
|
|
@@ -67,12 +67,27 @@ export class WebGPUBuffer extends Buffer {
|
|
|
67
67
|
this.device.reportError(new Error(`${this} creation failed ${error.message}`), this)();
|
|
68
68
|
this.device.debug();
|
|
69
69
|
});
|
|
70
|
+
|
|
71
|
+
if (!this.props.handle) {
|
|
72
|
+
this.trackAllocatedMemory(size);
|
|
73
|
+
} else {
|
|
74
|
+
this.trackReferencedMemory(size, 'Buffer');
|
|
75
|
+
}
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
override destroy(): void {
|
|
73
|
-
this.handle
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
}
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
write(data: ArrayBufferLike | ArrayBufferView | SharedArrayBuffer, byteOffset = 0) {
|
|
@@ -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
|
}
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {QuerySet, QuerySetProps} from '@luma.gl/core';
|
|
5
|
+
import {Buffer, QuerySet, QuerySetProps} from '@luma.gl/core';
|
|
6
6
|
import {WebGPUDevice} from '../webgpu-device';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
7
|
+
import {WebGPUBuffer} from './webgpu-buffer';
|
|
8
|
+
import {
|
|
9
|
+
getCpuHotspotSubmitReason,
|
|
10
|
+
setCpuHotspotSubmitReason
|
|
11
|
+
} from '../helpers/cpu-hotspot-profiler';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Immutable
|
|
@@ -17,6 +17,12 @@ export class WebGPUQuerySet extends QuerySet {
|
|
|
17
17
|
readonly device: WebGPUDevice;
|
|
18
18
|
readonly handle: GPUQuerySet;
|
|
19
19
|
|
|
20
|
+
protected _resolveBuffer: WebGPUBuffer | null = null;
|
|
21
|
+
protected _readBuffer: WebGPUBuffer | null = null;
|
|
22
|
+
protected _cachedResults: bigint[] | null = null;
|
|
23
|
+
protected _readResultsPromise: Promise<bigint[]> | null = null;
|
|
24
|
+
protected _resultsPendingResolution: boolean = false;
|
|
25
|
+
|
|
20
26
|
constructor(device: WebGPUDevice, props: QuerySetProps) {
|
|
21
27
|
super(device, props);
|
|
22
28
|
this.device = device;
|
|
@@ -30,8 +36,178 @@ export class WebGPUQuerySet extends QuerySet {
|
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
override destroy(): void {
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
if (!this.destroyed) {
|
|
40
|
+
this.handle?.destroy();
|
|
41
|
+
this.destroyResource();
|
|
42
|
+
// @ts-expect-error readonly
|
|
43
|
+
this.handle = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
isResultAvailable(queryIndex?: number): boolean {
|
|
48
|
+
if (!this._cachedResults) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return queryIndex === undefined
|
|
53
|
+
? true
|
|
54
|
+
: queryIndex >= 0 && queryIndex < this._cachedResults.length;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async readResults(options?: {firstQuery?: number; queryCount?: number}): Promise<bigint[]> {
|
|
58
|
+
const firstQuery = options?.firstQuery || 0;
|
|
59
|
+
const queryCount = options?.queryCount || this.props.count - firstQuery;
|
|
60
|
+
|
|
61
|
+
if (firstQuery < 0 || queryCount < 0 || firstQuery + queryCount > this.props.count) {
|
|
62
|
+
throw new Error('Query read range is out of bounds');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let needsFreshResults = true;
|
|
66
|
+
while (needsFreshResults) {
|
|
67
|
+
if (!this._readResultsPromise) {
|
|
68
|
+
this._readResultsPromise = this._readAllResults();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const readResultsPromise = this._readResultsPromise;
|
|
72
|
+
const results = await readResultsPromise;
|
|
73
|
+
|
|
74
|
+
// A later submit may have invalidated the query set while this read was in flight.
|
|
75
|
+
// Retry so each caller observes the freshest resolved results instead of stale data.
|
|
76
|
+
needsFreshResults = this._resultsPendingResolution;
|
|
77
|
+
if (!needsFreshResults) {
|
|
78
|
+
return results.slice(firstQuery, firstQuery + queryCount);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
throw new Error('Query read unexpectedly failed to resolve');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async readTimestampDuration(beginIndex: number, endIndex: number): Promise<number> {
|
|
86
|
+
if (this.props.type !== 'timestamp') {
|
|
87
|
+
throw new Error('Timestamp durations require a timestamp QuerySet');
|
|
88
|
+
}
|
|
89
|
+
if (beginIndex < 0 || endIndex <= beginIndex || endIndex >= this.props.count) {
|
|
90
|
+
throw new Error('Timestamp duration range is out of bounds');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const results = await this.readResults({
|
|
94
|
+
firstQuery: beginIndex,
|
|
95
|
+
queryCount: endIndex - beginIndex + 1
|
|
96
|
+
});
|
|
97
|
+
return Number(results[results.length - 1] - results[0]) / 1e6;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Marks any cached query results as stale after new writes have been encoded. */
|
|
101
|
+
_invalidateResults(): void {
|
|
102
|
+
this._cachedResults = null;
|
|
103
|
+
this._resultsPendingResolution = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected async _readAllResults(): Promise<bigint[]> {
|
|
107
|
+
this._ensureBuffers();
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Use a dedicated encoder so async profiling reads cannot flush or replace the
|
|
111
|
+
// device's active frame encoder while application rendering is in flight.
|
|
112
|
+
if (this._resultsPendingResolution) {
|
|
113
|
+
const commandEncoder = this.device.createCommandEncoder({
|
|
114
|
+
id: `${this.id}-read-results`
|
|
115
|
+
});
|
|
116
|
+
commandEncoder.resolveQuerySet(this, this._resolveBuffer!);
|
|
117
|
+
commandEncoder.copyBufferToBuffer({
|
|
118
|
+
sourceBuffer: this._resolveBuffer!,
|
|
119
|
+
destinationBuffer: this._readBuffer!,
|
|
120
|
+
size: this._resolveBuffer!.byteLength
|
|
121
|
+
});
|
|
122
|
+
const commandBuffer = commandEncoder.finish({
|
|
123
|
+
id: `${this.id}-read-results-command-buffer`
|
|
124
|
+
});
|
|
125
|
+
const previousSubmitReason = getCpuHotspotSubmitReason(this.device) || undefined;
|
|
126
|
+
setCpuHotspotSubmitReason(this.device, 'query-readback');
|
|
127
|
+
try {
|
|
128
|
+
this.device.submit(commandBuffer);
|
|
129
|
+
} finally {
|
|
130
|
+
setCpuHotspotSubmitReason(this.device, previousSubmitReason);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const data = await this._readBuffer!.readAsync(0, this._readBuffer!.byteLength);
|
|
135
|
+
const resultView = new BigUint64Array(data.buffer, data.byteOffset, this.props.count);
|
|
136
|
+
this._cachedResults = Array.from(resultView, value => value);
|
|
137
|
+
this._resultsPendingResolution = false;
|
|
138
|
+
return this._cachedResults;
|
|
139
|
+
} finally {
|
|
140
|
+
this._readResultsPromise = null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
protected _ensureBuffers(): void {
|
|
145
|
+
if (this._resolveBuffer && this._readBuffer) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const byteLength = this.props.count * 8;
|
|
150
|
+
this._resolveBuffer = this.device.createBuffer({
|
|
151
|
+
id: `${this.id}-resolve-buffer`,
|
|
152
|
+
usage: Buffer.QUERY_RESOLVE | Buffer.COPY_SRC,
|
|
153
|
+
byteLength
|
|
154
|
+
});
|
|
155
|
+
this.attachResource(this._resolveBuffer);
|
|
156
|
+
|
|
157
|
+
this._readBuffer = this.device.createBuffer({
|
|
158
|
+
id: `${this.id}-read-buffer`,
|
|
159
|
+
usage: Buffer.COPY_DST | Buffer.MAP_READ,
|
|
160
|
+
byteLength
|
|
161
|
+
});
|
|
162
|
+
this.attachResource(this._readBuffer);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
_encodeResolveToReadBuffer(
|
|
166
|
+
commandEncoder: {
|
|
167
|
+
resolveQuerySet: (
|
|
168
|
+
querySet: WebGPUQuerySet,
|
|
169
|
+
destination: WebGPUBuffer,
|
|
170
|
+
options?: {firstQuery?: number; queryCount?: number; destinationOffset?: number}
|
|
171
|
+
) => void;
|
|
172
|
+
copyBufferToBuffer: (options: {
|
|
173
|
+
sourceBuffer: WebGPUBuffer;
|
|
174
|
+
destinationBuffer: WebGPUBuffer;
|
|
175
|
+
sourceOffset?: number;
|
|
176
|
+
destinationOffset?: number;
|
|
177
|
+
size?: number;
|
|
178
|
+
}) => void;
|
|
179
|
+
},
|
|
180
|
+
options?: {firstQuery?: number; queryCount?: number}
|
|
181
|
+
): boolean {
|
|
182
|
+
if (!this._resultsPendingResolution) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If a readback is already mapping the shared read buffer, defer to the fallback read path.
|
|
187
|
+
// That path will submit resolve/copy commands once the current read has completed.
|
|
188
|
+
if (this._readResultsPromise) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this._ensureBuffers();
|
|
193
|
+
const firstQuery = options?.firstQuery || 0;
|
|
194
|
+
const queryCount = options?.queryCount || this.props.count - firstQuery;
|
|
195
|
+
const byteLength = queryCount * BigUint64Array.BYTES_PER_ELEMENT;
|
|
196
|
+
const byteOffset = firstQuery * BigUint64Array.BYTES_PER_ELEMENT;
|
|
197
|
+
|
|
198
|
+
commandEncoder.resolveQuerySet(this, this._resolveBuffer!, {
|
|
199
|
+
firstQuery,
|
|
200
|
+
queryCount,
|
|
201
|
+
destinationOffset: byteOffset
|
|
202
|
+
});
|
|
203
|
+
commandEncoder.copyBufferToBuffer({
|
|
204
|
+
sourceBuffer: this._resolveBuffer!,
|
|
205
|
+
sourceOffset: byteOffset,
|
|
206
|
+
destinationBuffer: this._readBuffer!,
|
|
207
|
+
destinationOffset: byteOffset,
|
|
208
|
+
size: byteLength
|
|
209
|
+
});
|
|
210
|
+
this._resultsPendingResolution = false;
|
|
211
|
+
return true;
|
|
36
212
|
}
|
|
37
213
|
}
|