@luma.gl/webgpu 9.2.6 → 9.3.0-alpha.11
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 +8 -6
- package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
- package/dist/adapter/helpers/get-bind-group.js +110 -30
- 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 +62 -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 +35 -35
- 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 +176 -19
- 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 +8160 -551
- package/dist/dist.min.js +171 -6
- package/dist/index.cjs +2001 -414
- 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 +182 -42
- 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 +90 -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 +41 -43
- package/src/adapter/webgpu-canvas-context.ts +108 -41
- package/src/adapter/webgpu-device.ts +243 -25
- 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)();
|
|
@@ -47,17 +56,21 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
47
56
|
this.device.pushErrorScope('validation');
|
|
48
57
|
this.handle = this.device.handle.createRenderPipeline(descriptor);
|
|
49
58
|
this.device.popErrorScope((error: GPUError) => {
|
|
59
|
+
this.linkStatus = 'error';
|
|
50
60
|
this.device.reportError(new Error(`${this} creation failed:\n"${error.message}"`), this)();
|
|
51
61
|
this.device.debug();
|
|
52
62
|
});
|
|
53
63
|
}
|
|
64
|
+
this.descriptor = descriptor;
|
|
54
65
|
this.handle.label = this.props.id;
|
|
66
|
+
this.linkStatus = 'success';
|
|
55
67
|
|
|
56
68
|
// Note: Often the same shader in WebGPU
|
|
57
69
|
this.vs = props.vs as WebGPUShader;
|
|
58
70
|
this.fs = props.fs as WebGPUShader;
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
this._bindingsByGroup =
|
|
72
|
+
props.bindGroups || normalizeBindingsByGroup(this.shaderLayout, props.bindings);
|
|
73
|
+
this._bindGroupCacheKeysByGroup = createBindGroupCacheKeys(this._bindingsByGroup);
|
|
61
74
|
}
|
|
62
75
|
|
|
63
76
|
override destroy(): void {
|
|
@@ -67,17 +80,27 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
83
|
+
* Compatibility shim for code paths that still set bindings on the pipeline.
|
|
84
|
+
* The shared-model path passes bindings per draw and does not rely on this state.
|
|
72
85
|
*/
|
|
73
|
-
setBindings(bindings:
|
|
74
|
-
|
|
75
|
-
for (const [
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
setBindings(bindings: Bindings | BindingsByGroup): void {
|
|
87
|
+
const nextBindingsByGroup = normalizeBindingsByGroup(this.shaderLayout, bindings);
|
|
88
|
+
for (const [groupKey, groupBindings] of Object.entries(nextBindingsByGroup)) {
|
|
89
|
+
const group = Number(groupKey);
|
|
90
|
+
for (const [name, binding] of Object.entries(groupBindings || {})) {
|
|
91
|
+
const currentGroupBindings = this._bindingsByGroup[group] || {};
|
|
92
|
+
if (currentGroupBindings[name] !== binding) {
|
|
93
|
+
if (
|
|
94
|
+
!this._bindingsByGroup[group] ||
|
|
95
|
+
this._bindingsByGroup[group] === currentGroupBindings
|
|
96
|
+
) {
|
|
97
|
+
this._bindingsByGroup[group] = {...currentGroupBindings};
|
|
98
|
+
}
|
|
99
|
+
this._bindingsByGroup[group][name] = binding;
|
|
100
|
+
this._bindGroupCacheKeysByGroup[group] = {};
|
|
101
|
+
}
|
|
78
102
|
}
|
|
79
103
|
}
|
|
80
|
-
Object.assign(this._bindings, bindings);
|
|
81
104
|
}
|
|
82
105
|
|
|
83
106
|
/** @todo - should this be moved to renderpass? */
|
|
@@ -91,8 +114,19 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
91
114
|
firstIndex?: number;
|
|
92
115
|
firstInstance?: number;
|
|
93
116
|
baseVertex?: number;
|
|
117
|
+
bindings?: Bindings;
|
|
118
|
+
bindGroups?: BindingsByGroup;
|
|
119
|
+
_bindGroupCacheKeys?: Partial<Record<number, object>>;
|
|
120
|
+
uniforms?: Record<string, unknown>;
|
|
94
121
|
}): boolean {
|
|
122
|
+
if (this.isErrored) {
|
|
123
|
+
log.info(2, `RenderPipeline:${this.id}.draw() aborted - pipeline initialization failed`)();
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
95
127
|
const webgpuRenderPass = options.renderPass as WebGPURenderPass;
|
|
128
|
+
const instanceCount =
|
|
129
|
+
options.instanceCount && options.instanceCount > 0 ? options.instanceCount : 1;
|
|
96
130
|
|
|
97
131
|
// Set pipeline
|
|
98
132
|
this.device.pushErrorScope('validation');
|
|
@@ -103,9 +137,16 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
103
137
|
});
|
|
104
138
|
|
|
105
139
|
// Set bindings (uniform buffers, textures etc)
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
140
|
+
const hasExplicitBindings = Boolean(options.bindGroups || options.bindings);
|
|
141
|
+
const bindGroups = _getDefaultBindGroupFactory(this.device).getBindGroups(
|
|
142
|
+
this,
|
|
143
|
+
hasExplicitBindings ? options.bindGroups || options.bindings : this._bindingsByGroup,
|
|
144
|
+
hasExplicitBindings ? options._bindGroupCacheKeys : this._bindGroupCacheKeysByGroup
|
|
145
|
+
);
|
|
146
|
+
for (const [group, bindGroup] of Object.entries(bindGroups)) {
|
|
147
|
+
if (bindGroup) {
|
|
148
|
+
webgpuRenderPass.handle.setBindGroup(Number(group), bindGroup as GPUBindGroup);
|
|
149
|
+
}
|
|
109
150
|
}
|
|
110
151
|
|
|
111
152
|
// Set attributes
|
|
@@ -116,16 +157,17 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
116
157
|
if (options.indexCount) {
|
|
117
158
|
webgpuRenderPass.handle.drawIndexed(
|
|
118
159
|
options.indexCount,
|
|
119
|
-
|
|
120
|
-
options.firstIndex,
|
|
121
|
-
options.baseVertex,
|
|
122
|
-
options.firstInstance
|
|
160
|
+
instanceCount,
|
|
161
|
+
options.firstIndex || 0,
|
|
162
|
+
options.baseVertex || 0,
|
|
163
|
+
options.firstInstance || 0
|
|
123
164
|
);
|
|
124
165
|
} else {
|
|
125
166
|
webgpuRenderPass.handle.draw(
|
|
126
167
|
options.vertexCount || 0,
|
|
127
|
-
|
|
128
|
-
options.
|
|
168
|
+
instanceCount,
|
|
169
|
+
options.firstVertex || 0,
|
|
170
|
+
options.firstInstance || 0
|
|
129
171
|
);
|
|
130
172
|
}
|
|
131
173
|
|
|
@@ -135,22 +177,12 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
135
177
|
return true;
|
|
136
178
|
}
|
|
137
179
|
|
|
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);
|
|
180
|
+
_getBindingsByGroupWebGPU(): BindingsByGroup {
|
|
181
|
+
return this._bindingsByGroup;
|
|
182
|
+
}
|
|
152
183
|
|
|
153
|
-
|
|
184
|
+
_getBindGroupCacheKeysWebGPU(): Partial<Record<number, object>> {
|
|
185
|
+
return this._bindGroupCacheKeysByGroup;
|
|
154
186
|
}
|
|
155
187
|
|
|
156
188
|
/**
|
|
@@ -161,7 +193,9 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
161
193
|
const vertex: GPUVertexState = {
|
|
162
194
|
module: (this.props.vs as WebGPUShader).handle,
|
|
163
195
|
entryPoint: this.props.vertexEntryPoint || 'main',
|
|
164
|
-
buffers: getVertexBufferLayout(this.shaderLayout, this.props.bufferLayout
|
|
196
|
+
buffers: getVertexBufferLayout(this.shaderLayout, this.props.bufferLayout, {
|
|
197
|
+
pipelineId: this.id
|
|
198
|
+
})
|
|
165
199
|
};
|
|
166
200
|
|
|
167
201
|
// Populate color targets
|
|
@@ -211,6 +245,18 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
211
245
|
return descriptor;
|
|
212
246
|
}
|
|
213
247
|
}
|
|
248
|
+
|
|
249
|
+
function createBindGroupCacheKeys(
|
|
250
|
+
bindingsByGroup: BindingsByGroup
|
|
251
|
+
): Partial<Record<number, object>> {
|
|
252
|
+
const bindGroupCacheKeys: Partial<Record<number, object>> = {};
|
|
253
|
+
for (const [groupKey, groupBindings] of Object.entries(bindingsByGroup)) {
|
|
254
|
+
if (groupBindings && Object.keys(groupBindings).length > 0) {
|
|
255
|
+
bindGroupCacheKeys[Number(groupKey)] = {};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return bindGroupCacheKeys;
|
|
259
|
+
}
|
|
214
260
|
/**
|
|
215
261
|
_setAttributeBuffers(webgpuRenderPass: WebGPURenderPass) {
|
|
216
262
|
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
|
}
|