@luma.gl/webgpu 9.2.6 → 9.3.0-alpha.10
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 +4 -6
- package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
- package/dist/adapter/helpers/get-bind-group.js +39 -32
- package/dist/adapter/helpers/get-bind-group.js.map +1 -1
- package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts +3 -1
- package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts.map +1 -1
- package/dist/adapter/helpers/get-vertex-buffer-layout.js +17 -12
- 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 +1 -0
- package/dist/adapter/helpers/webgpu-parameters.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 +7 -16
- package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-command-encoder.js +89 -32
- package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.d.ts +3 -3
- package/dist/adapter/resources/webgpu-compute-pass.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.js +30 -12
- package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pipeline.d.ts +7 -9
- package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pipeline.js +30 -17
- package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
- package/dist/adapter/resources/webgpu-fence.d.ts +13 -0
- package/dist/adapter/resources/webgpu-fence.d.ts.map +1 -0
- package/dist/adapter/resources/webgpu-fence.js +33 -0
- package/dist/adapter/resources/webgpu-fence.js.map +1 -0
- 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 +1 -1
- package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-pipeline-layout.js +11 -18
- 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 +6 -3
- package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-render-pass.js +78 -34
- package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
- package/dist/adapter/resources/webgpu-render-pipeline.d.ts +14 -10
- package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-render-pipeline.js +56 -35
- 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-shader.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-shader.js +17 -1
- package/dist/adapter/resources/webgpu-shader.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 +25 -3
- package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-texture.js +211 -43
- 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-adapter.d.ts.map +1 -1
- package/dist/adapter/webgpu-adapter.js +34 -34
- package/dist/adapter/webgpu-adapter.js.map +1 -1
- package/dist/adapter/webgpu-canvas-context.d.ts +6 -3
- package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
- package/dist/adapter/webgpu-canvas-context.js +90 -30
- package/dist/adapter/webgpu-canvas-context.js.map +1 -1
- package/dist/adapter/webgpu-device.d.ts +12 -2
- package/dist/adapter/webgpu-device.d.ts.map +1 -1
- package/dist/adapter/webgpu-device.js +173 -16
- 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 +8070 -547
- package/dist/dist.min.js +169 -6
- package/dist/index.cjs +1929 -410
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/wgsl/get-shader-layout-wgsl.d.ts +8 -0
- package/dist/wgsl/get-shader-layout-wgsl.d.ts.map +1 -0
- package/dist/wgsl/get-shader-layout-wgsl.js +144 -0
- package/dist/wgsl/get-shader-layout-wgsl.js.map +1 -0
- package/package.json +6 -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 +52 -46
- package/src/adapter/helpers/get-vertex-buffer-layout.ts +31 -12
- package/src/adapter/helpers/webgpu-parameters.ts +2 -0
- 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 +129 -50
- package/src/adapter/resources/webgpu-compute-pass.ts +48 -13
- package/src/adapter/resources/webgpu-compute-pipeline.ts +49 -18
- package/src/adapter/resources/webgpu-fence.ts +38 -0
- package/src/adapter/resources/webgpu-framebuffer.ts +21 -0
- package/src/adapter/resources/webgpu-pipeline-layout.ts +18 -17
- package/src/adapter/resources/webgpu-query-set.ts +185 -9
- package/src/adapter/resources/webgpu-render-pass.ts +92 -40
- package/src/adapter/resources/webgpu-render-pipeline.ts +83 -44
- package/src/adapter/resources/webgpu-sampler.ts +5 -0
- package/src/adapter/resources/webgpu-shader.ts +16 -1
- package/src/adapter/resources/webgpu-texture-view.ts +51 -11
- package/src/adapter/resources/webgpu-texture.ts +281 -101
- package/src/adapter/resources/webgpu-vertex-array.ts +1 -1
- package/src/adapter/webgpu-adapter.ts +40 -42
- package/src/adapter/webgpu-canvas-context.ts +107 -40
- package/src/adapter/webgpu-device.ts +231 -21
- package/src/adapter/webgpu-presentation-context.ts +180 -0
- package/src/index.ts +3 -0
- package/src/wgsl/get-shader-layout-wgsl.ts +165 -0
- package/dist/adapter/helpers/accessor-to-format.d.ts +0 -1
- package/dist/adapter/helpers/accessor-to-format.d.ts.map +0 -1
- package/dist/adapter/helpers/accessor-to-format.js +0 -105
- package/dist/adapter/helpers/accessor-to-format.js.map +0 -1
- package/src/adapter/helpers/accessor-to-format.ts +0 -104
|
@@ -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
|
}
|
|
@@ -3,67 +3,114 @@
|
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
5
|
import type {TypedArray, NumberArray4} from '@math.gl/types';
|
|
6
|
-
import type {RenderPassProps, RenderPassParameters,
|
|
7
|
-
import {Buffer, RenderPass, RenderPipeline, log} from '@luma.gl/core';
|
|
6
|
+
import type {RenderPassProps, RenderPassParameters, Bindings, BindingsByGroup} from '@luma.gl/core';
|
|
7
|
+
import {Buffer, RenderPass, RenderPipeline, _getDefaultBindGroupFactory, log} from '@luma.gl/core';
|
|
8
8
|
import {WebGPUDevice} from '../webgpu-device';
|
|
9
9
|
import {WebGPUBuffer} from './webgpu-buffer';
|
|
10
10
|
// import {WebGPUCommandEncoder} from './webgpu-command-encoder';
|
|
11
11
|
import {WebGPURenderPipeline} from './webgpu-render-pipeline';
|
|
12
12
|
import {WebGPUQuerySet} from './webgpu-query-set';
|
|
13
13
|
import {WebGPUFramebuffer} from './webgpu-framebuffer';
|
|
14
|
+
import {getCpuHotspotProfiler, getTimestamp} from '../helpers/cpu-hotspot-profiler';
|
|
14
15
|
|
|
15
16
|
export class WebGPURenderPass extends RenderPass {
|
|
16
17
|
readonly device: WebGPUDevice;
|
|
17
18
|
readonly handle: GPURenderPassEncoder;
|
|
19
|
+
readonly framebuffer: WebGPUFramebuffer;
|
|
18
20
|
|
|
19
21
|
/** Active pipeline */
|
|
20
22
|
pipeline: WebGPURenderPipeline | null = null;
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
/** Latest bindings applied to this pass */
|
|
25
|
+
bindings: Bindings | BindingsByGroup = {};
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
device: WebGPUDevice,
|
|
29
|
+
props: RenderPassProps = {},
|
|
30
|
+
commandEncoder: GPUCommandEncoder = device.commandEncoder.handle
|
|
31
|
+
) {
|
|
23
32
|
super(device, props);
|
|
24
33
|
this.device = device;
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
const {props: renderPassProps} = this;
|
|
35
|
+
this.framebuffer =
|
|
36
|
+
(renderPassProps.framebuffer as WebGPUFramebuffer) ||
|
|
37
|
+
device.getCanvasContext().getCurrentFramebuffer();
|
|
29
38
|
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
|
|
39
|
+
const profiler = getCpuHotspotProfiler(this.device);
|
|
40
|
+
if (profiler) {
|
|
41
|
+
const counterName:
|
|
42
|
+
| 'explicitFramebufferRenderPassCount'
|
|
43
|
+
| 'defaultFramebufferRenderPassCount' = renderPassProps.framebuffer
|
|
44
|
+
? 'explicitFramebufferRenderPassCount'
|
|
45
|
+
: 'defaultFramebufferRenderPassCount';
|
|
46
|
+
profiler[counterName] = (profiler[counterName] || 0) + 1;
|
|
33
47
|
}
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
querySet: webgpuTSQuerySet.handle,
|
|
40
|
-
beginningOfPassWriteIndex: props.beginTimestampIndex,
|
|
41
|
-
endOfPassWriteIndex: props.endTimestampIndex
|
|
42
|
-
} as GPUComputePassTimestampWrites)
|
|
43
|
-
: undefined;
|
|
44
|
-
}
|
|
49
|
+
const startTime = profiler ? getTimestamp() : 0;
|
|
50
|
+
try {
|
|
51
|
+
const descriptorAssemblyStartTime = profiler ? getTimestamp() : 0;
|
|
52
|
+
const renderPassDescriptor = this.getRenderPassDescriptor(this.framebuffer);
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
if (renderPassProps.occlusionQuerySet) {
|
|
55
|
+
renderPassDescriptor.occlusionQuerySet = (
|
|
56
|
+
renderPassProps.occlusionQuerySet as WebGPUQuerySet
|
|
57
|
+
).handle;
|
|
58
|
+
}
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
if (renderPassProps.timestampQuerySet) {
|
|
61
|
+
const webgpuTSQuerySet = renderPassProps.timestampQuerySet as WebGPUQuerySet;
|
|
62
|
+
webgpuTSQuerySet?._invalidateResults();
|
|
63
|
+
renderPassDescriptor.timestampWrites = webgpuTSQuerySet
|
|
64
|
+
? ({
|
|
65
|
+
querySet: webgpuTSQuerySet.handle,
|
|
66
|
+
beginningOfPassWriteIndex: renderPassProps.beginTimestampIndex,
|
|
67
|
+
endOfPassWriteIndex: renderPassProps.endTimestampIndex
|
|
68
|
+
} as GPURenderPassTimestampWrites)
|
|
69
|
+
: undefined;
|
|
70
|
+
}
|
|
71
|
+
if (profiler) {
|
|
72
|
+
profiler.renderPassDescriptorAssemblyCount =
|
|
73
|
+
(profiler.renderPassDescriptorAssemblyCount || 0) + 1;
|
|
74
|
+
profiler.renderPassDescriptorAssemblyTimeMs =
|
|
75
|
+
(profiler.renderPassDescriptorAssemblyTimeMs || 0) +
|
|
76
|
+
(getTimestamp() - descriptorAssemblyStartTime);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.device.pushErrorScope('validation');
|
|
80
|
+
const beginRenderPassStartTime = profiler ? getTimestamp() : 0;
|
|
81
|
+
this.handle = this.props.handle || commandEncoder.beginRenderPass(renderPassDescriptor);
|
|
82
|
+
if (profiler) {
|
|
83
|
+
profiler.renderPassBeginCount = (profiler.renderPassBeginCount || 0) + 1;
|
|
84
|
+
profiler.renderPassBeginTimeMs =
|
|
85
|
+
(profiler.renderPassBeginTimeMs || 0) + (getTimestamp() - beginRenderPassStartTime);
|
|
86
|
+
}
|
|
87
|
+
this.device.popErrorScope((error: GPUError) => {
|
|
88
|
+
this.device.reportError(new Error(`${this} creation failed:\n"${error.message}"`), this)();
|
|
89
|
+
this.device.debug();
|
|
90
|
+
});
|
|
91
|
+
this.handle.label = this.props.id;
|
|
92
|
+
log.groupCollapsed(3, `new WebGPURenderPass(${this.id})`)();
|
|
93
|
+
log.probe(3, JSON.stringify(renderPassDescriptor, null, 2))();
|
|
94
|
+
log.groupEnd(3)();
|
|
95
|
+
} finally {
|
|
96
|
+
if (profiler) {
|
|
97
|
+
profiler.renderPassSetupCount = (profiler.renderPassSetupCount || 0) + 1;
|
|
98
|
+
profiler.renderPassSetupTimeMs =
|
|
99
|
+
(profiler.renderPassSetupTimeMs || 0) + (getTimestamp() - startTime);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
61
102
|
}
|
|
62
103
|
|
|
63
|
-
override destroy(): void {
|
|
104
|
+
override destroy(): void {
|
|
105
|
+
this.destroyResource();
|
|
106
|
+
}
|
|
64
107
|
|
|
65
108
|
end(): void {
|
|
109
|
+
if (this.destroyed) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
66
112
|
this.handle.end();
|
|
113
|
+
this.destroy();
|
|
67
114
|
}
|
|
68
115
|
|
|
69
116
|
setPipeline(pipeline: RenderPipeline): void {
|
|
@@ -77,11 +124,16 @@ export class WebGPURenderPass extends RenderPass {
|
|
|
77
124
|
}
|
|
78
125
|
|
|
79
126
|
/** Sets an array of bindings (uniform buffers, samplers, textures, ...) */
|
|
80
|
-
setBindings(bindings:
|
|
81
|
-
this.
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
127
|
+
setBindings(bindings: Bindings | BindingsByGroup): void {
|
|
128
|
+
this.bindings = bindings;
|
|
129
|
+
const bindGroups =
|
|
130
|
+
(this.pipeline &&
|
|
131
|
+
_getDefaultBindGroupFactory(this.device).getBindGroups(this.pipeline, bindings)) ||
|
|
132
|
+
{};
|
|
133
|
+
for (const [group, bindGroup] of Object.entries(bindGroups)) {
|
|
134
|
+
if (bindGroup) {
|
|
135
|
+
this.handle.setBindGroup(Number(group), bindGroup as GPUBindGroup);
|
|
136
|
+
}
|
|
85
137
|
}
|
|
86
138
|
}
|
|
87
139
|
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
// luma.gl MIT license
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
3
|
+
import type {Bindings, BindingsByGroup, RenderPass, VertexArray} from '@luma.gl/core';
|
|
4
|
+
import {
|
|
5
|
+
RenderPipeline,
|
|
6
|
+
RenderPipelineProps,
|
|
7
|
+
_getDefaultBindGroupFactory,
|
|
8
|
+
log,
|
|
9
|
+
normalizeBindingsByGroup
|
|
10
|
+
} from '@luma.gl/core';
|
|
5
11
|
import {applyParametersToRenderPipelineDescriptor} from '../helpers/webgpu-parameters';
|
|
6
12
|
import {getWebGPUTextureFormat} from '../helpers/convert-texture-format';
|
|
7
|
-
import {getBindGroup} from '../helpers/get-bind-group';
|
|
8
13
|
import {getVertexBufferLayout} from '../helpers/get-vertex-buffer-layout';
|
|
9
14
|
// import {convertAttributesVertexBufferToLayout} from '../helpers/get-vertex-buffer-layout';
|
|
10
|
-
// import {mapAccessorToWebGPUFormat} from './helpers/accessor-to-format';
|
|
11
15
|
// import type {BufferAccessors} from './webgpu-pipeline';
|
|
12
16
|
|
|
13
17
|
import type {WebGPUDevice} from '../webgpu-device';
|
|
@@ -21,14 +25,14 @@ import type {WebGPURenderPass} from './webgpu-render-pass';
|
|
|
21
25
|
export class WebGPURenderPipeline extends RenderPipeline {
|
|
22
26
|
readonly device: WebGPUDevice;
|
|
23
27
|
readonly handle: GPURenderPipeline;
|
|
28
|
+
readonly descriptor: GPURenderPipelineDescriptor | null;
|
|
24
29
|
|
|
25
30
|
readonly vs: WebGPUShader;
|
|
26
31
|
readonly fs: WebGPUShader | null = null;
|
|
27
32
|
|
|
28
|
-
/**
|
|
29
|
-
private
|
|
30
|
-
private
|
|
31
|
-
private _bindGroup: GPUBindGroup | null = null;
|
|
33
|
+
/** Compatibility path for direct pipeline.setBindings() usage */
|
|
34
|
+
private _bindingsByGroup: BindingsByGroup;
|
|
35
|
+
private _bindGroupCacheKeysByGroup: Partial<Record<number, object>> = {};
|
|
32
36
|
|
|
33
37
|
override get [Symbol.toStringTag]() {
|
|
34
38
|
return 'WebGPURenderPipeline';
|
|
@@ -37,9 +41,14 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
37
41
|
constructor(device: WebGPUDevice, props: RenderPipelineProps) {
|
|
38
42
|
super(device, props);
|
|
39
43
|
this.device = device;
|
|
44
|
+
this.shaderLayout ||= this.device.getShaderLayout((props.vs as WebGPUShader).source) || {
|
|
45
|
+
attributes: [],
|
|
46
|
+
bindings: []
|
|
47
|
+
};
|
|
40
48
|
this.handle = this.props.handle as GPURenderPipeline;
|
|
49
|
+
let descriptor: GPURenderPipelineDescriptor | null = null;
|
|
41
50
|
if (!this.handle) {
|
|
42
|
-
|
|
51
|
+
descriptor = this._getRenderPipelineDescriptor();
|
|
43
52
|
log.groupCollapsed(1, `new WebGPURenderPipeline(${this.id})`)();
|
|
44
53
|
log.probe(1, JSON.stringify(descriptor, null, 2))();
|
|
45
54
|
log.groupEnd(1)();
|
|
@@ -51,13 +60,15 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
51
60
|
this.device.debug();
|
|
52
61
|
});
|
|
53
62
|
}
|
|
63
|
+
this.descriptor = descriptor;
|
|
54
64
|
this.handle.label = this.props.id;
|
|
55
65
|
|
|
56
66
|
// Note: Often the same shader in WebGPU
|
|
57
67
|
this.vs = props.vs as WebGPUShader;
|
|
58
68
|
this.fs = props.fs as WebGPUShader;
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
this._bindingsByGroup =
|
|
70
|
+
props.bindGroups || normalizeBindingsByGroup(this.shaderLayout, props.bindings);
|
|
71
|
+
this._bindGroupCacheKeysByGroup = createBindGroupCacheKeys(this._bindingsByGroup);
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
override destroy(): void {
|
|
@@ -67,17 +78,27 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
81
|
+
* Compatibility shim for code paths that still set bindings on the pipeline.
|
|
82
|
+
* The shared-model path passes bindings per draw and does not rely on this state.
|
|
72
83
|
*/
|
|
73
|
-
setBindings(bindings:
|
|
74
|
-
|
|
75
|
-
for (const [
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
setBindings(bindings: Bindings | BindingsByGroup): void {
|
|
85
|
+
const nextBindingsByGroup = normalizeBindingsByGroup(this.shaderLayout, bindings);
|
|
86
|
+
for (const [groupKey, groupBindings] of Object.entries(nextBindingsByGroup)) {
|
|
87
|
+
const group = Number(groupKey);
|
|
88
|
+
for (const [name, binding] of Object.entries(groupBindings || {})) {
|
|
89
|
+
const currentGroupBindings = this._bindingsByGroup[group] || {};
|
|
90
|
+
if (currentGroupBindings[name] !== binding) {
|
|
91
|
+
if (
|
|
92
|
+
!this._bindingsByGroup[group] ||
|
|
93
|
+
this._bindingsByGroup[group] === currentGroupBindings
|
|
94
|
+
) {
|
|
95
|
+
this._bindingsByGroup[group] = {...currentGroupBindings};
|
|
96
|
+
}
|
|
97
|
+
this._bindingsByGroup[group][name] = binding;
|
|
98
|
+
this._bindGroupCacheKeysByGroup[group] = {};
|
|
99
|
+
}
|
|
78
100
|
}
|
|
79
101
|
}
|
|
80
|
-
Object.assign(this._bindings, bindings);
|
|
81
102
|
}
|
|
82
103
|
|
|
83
104
|
/** @todo - should this be moved to renderpass? */
|
|
@@ -91,8 +112,14 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
91
112
|
firstIndex?: number;
|
|
92
113
|
firstInstance?: number;
|
|
93
114
|
baseVertex?: number;
|
|
115
|
+
bindings?: Bindings;
|
|
116
|
+
bindGroups?: BindingsByGroup;
|
|
117
|
+
_bindGroupCacheKeys?: Partial<Record<number, object>>;
|
|
118
|
+
uniforms?: Record<string, unknown>;
|
|
94
119
|
}): boolean {
|
|
95
120
|
const webgpuRenderPass = options.renderPass as WebGPURenderPass;
|
|
121
|
+
const instanceCount =
|
|
122
|
+
options.instanceCount && options.instanceCount > 0 ? options.instanceCount : 1;
|
|
96
123
|
|
|
97
124
|
// Set pipeline
|
|
98
125
|
this.device.pushErrorScope('validation');
|
|
@@ -103,9 +130,16 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
103
130
|
});
|
|
104
131
|
|
|
105
132
|
// Set bindings (uniform buffers, textures etc)
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
133
|
+
const hasExplicitBindings = Boolean(options.bindGroups || options.bindings);
|
|
134
|
+
const bindGroups = _getDefaultBindGroupFactory(this.device).getBindGroups(
|
|
135
|
+
this,
|
|
136
|
+
hasExplicitBindings ? options.bindGroups || options.bindings : this._bindingsByGroup,
|
|
137
|
+
hasExplicitBindings ? options._bindGroupCacheKeys : this._bindGroupCacheKeysByGroup
|
|
138
|
+
);
|
|
139
|
+
for (const [group, bindGroup] of Object.entries(bindGroups)) {
|
|
140
|
+
if (bindGroup) {
|
|
141
|
+
webgpuRenderPass.handle.setBindGroup(Number(group), bindGroup as GPUBindGroup);
|
|
142
|
+
}
|
|
109
143
|
}
|
|
110
144
|
|
|
111
145
|
// Set attributes
|
|
@@ -116,16 +150,17 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
116
150
|
if (options.indexCount) {
|
|
117
151
|
webgpuRenderPass.handle.drawIndexed(
|
|
118
152
|
options.indexCount,
|
|
119
|
-
|
|
120
|
-
options.firstIndex,
|
|
121
|
-
options.baseVertex,
|
|
122
|
-
options.firstInstance
|
|
153
|
+
instanceCount,
|
|
154
|
+
options.firstIndex || 0,
|
|
155
|
+
options.baseVertex || 0,
|
|
156
|
+
options.firstInstance || 0
|
|
123
157
|
);
|
|
124
158
|
} else {
|
|
125
159
|
webgpuRenderPass.handle.draw(
|
|
126
160
|
options.vertexCount || 0,
|
|
127
|
-
|
|
128
|
-
options.
|
|
161
|
+
instanceCount,
|
|
162
|
+
options.firstVertex || 0,
|
|
163
|
+
options.firstInstance || 0
|
|
129
164
|
);
|
|
130
165
|
}
|
|
131
166
|
|
|
@@ -135,22 +170,12 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
135
170
|
return true;
|
|
136
171
|
}
|
|
137
172
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Get hold of the bind group layout. We don't want to do this unless we know there is at least one bind group
|
|
145
|
-
this._bindGroupLayout = this._bindGroupLayout || this.handle.getBindGroupLayout(0);
|
|
146
|
-
|
|
147
|
-
// Set up the bindings
|
|
148
|
-
// TODO what if bindings change? We need to rebuild the bind group!
|
|
149
|
-
this._bindGroup =
|
|
150
|
-
this._bindGroup ||
|
|
151
|
-
getBindGroup(this.device.handle, this._bindGroupLayout, this.shaderLayout, this._bindings);
|
|
173
|
+
_getBindingsByGroupWebGPU(): BindingsByGroup {
|
|
174
|
+
return this._bindingsByGroup;
|
|
175
|
+
}
|
|
152
176
|
|
|
153
|
-
|
|
177
|
+
_getBindGroupCacheKeysWebGPU(): Partial<Record<number, object>> {
|
|
178
|
+
return this._bindGroupCacheKeysByGroup;
|
|
154
179
|
}
|
|
155
180
|
|
|
156
181
|
/**
|
|
@@ -161,7 +186,9 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
161
186
|
const vertex: GPUVertexState = {
|
|
162
187
|
module: (this.props.vs as WebGPUShader).handle,
|
|
163
188
|
entryPoint: this.props.vertexEntryPoint || 'main',
|
|
164
|
-
buffers: getVertexBufferLayout(this.shaderLayout, this.props.bufferLayout
|
|
189
|
+
buffers: getVertexBufferLayout(this.shaderLayout, this.props.bufferLayout, {
|
|
190
|
+
pipelineId: this.id
|
|
191
|
+
})
|
|
165
192
|
};
|
|
166
193
|
|
|
167
194
|
// Populate color targets
|
|
@@ -211,6 +238,18 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
211
238
|
return descriptor;
|
|
212
239
|
}
|
|
213
240
|
}
|
|
241
|
+
|
|
242
|
+
function createBindGroupCacheKeys(
|
|
243
|
+
bindingsByGroup: BindingsByGroup
|
|
244
|
+
): Partial<Record<number, object>> {
|
|
245
|
+
const bindGroupCacheKeys: Partial<Record<number, object>> = {};
|
|
246
|
+
for (const [groupKey, groupBindings] of Object.entries(bindingsByGroup)) {
|
|
247
|
+
if (groupBindings && Object.keys(groupBindings).length > 0) {
|
|
248
|
+
bindGroupCacheKeys[Number(groupKey)] = {};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return bindGroupCacheKeys;
|
|
252
|
+
}
|
|
214
253
|
/**
|
|
215
254
|
_setAttributeBuffers(webgpuRenderPass: WebGPURenderPass) {
|
|
216
255
|
if (this._indexBuffer) {
|
|
@@ -40,6 +40,11 @@ export class WebGPUSampler extends Sampler {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
override destroy(): void {
|
|
43
|
+
if (this.destroyed) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.destroyResource();
|
|
43
48
|
// GPUSampler does not have a destroy method
|
|
44
49
|
// this.handle.destroy();
|
|
45
50
|
// @ts-expect-error readonly
|
|
@@ -64,7 +64,22 @@ export class WebGPUShader extends Shader {
|
|
|
64
64
|
|
|
65
65
|
/** Returns compilation info for this shader */
|
|
66
66
|
async getCompilationInfo(): Promise<readonly CompilerMessage[]> {
|
|
67
|
-
|
|
67
|
+
// `_checkCompilationError()` runs asynchronously after construction, so the shader can be
|
|
68
|
+
// destroyed before we await compilation info. Snapshot the handle and treat a destroyed shader
|
|
69
|
+
// as having no compiler messages instead of dereferencing `null`.
|
|
70
|
+
const handle = this.handle;
|
|
71
|
+
if (!handle) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
let compilationInfo;
|
|
75
|
+
try {
|
|
76
|
+
compilationInfo = await handle.getCompilationInfo();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (this.device.shouldIgnoreDroppedInstanceError(error, 'getCompilationInfo')) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
68
83
|
return compilationInfo.messages;
|
|
69
84
|
}
|
|
70
85
|
}
|