@luma.gl/webgpu 9.3.0-alpha.2 → 9.3.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/helpers/cpu-hotspot-profiler.d.ts +54 -0
- package/dist/adapter/helpers/cpu-hotspot-profiler.d.ts.map +1 -0
- package/dist/adapter/helpers/cpu-hotspot-profiler.js +26 -0
- package/dist/adapter/helpers/cpu-hotspot-profiler.js.map +1 -0
- package/dist/adapter/helpers/generate-mipmaps-webgpu.d.ts +7 -0
- package/dist/adapter/helpers/generate-mipmaps-webgpu.d.ts.map +1 -0
- package/dist/adapter/helpers/generate-mipmaps-webgpu.js +490 -0
- package/dist/adapter/helpers/generate-mipmaps-webgpu.js.map +1 -0
- package/dist/adapter/helpers/get-bind-group.d.ts +2 -1
- package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
- package/dist/adapter/helpers/get-bind-group.js +31 -21
- package/dist/adapter/helpers/get-bind-group.js.map +1 -1
- package/dist/adapter/resources/webgpu-buffer.d.ts +7 -0
- package/dist/adapter/resources/webgpu-buffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-buffer.js +58 -15
- package/dist/adapter/resources/webgpu-buffer.js.map +1 -1
- package/dist/adapter/resources/webgpu-command-buffer.js +1 -1
- package/dist/adapter/resources/webgpu-command-buffer.js.map +1 -1
- package/dist/adapter/resources/webgpu-command-encoder.d.ts +5 -4
- package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-command-encoder.js +23 -5
- package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.d.ts +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pass.js +14 -6
- package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-compute-pipeline.js +19 -3
- package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
- package/dist/adapter/resources/webgpu-framebuffer.d.ts +6 -0
- package/dist/adapter/resources/webgpu-framebuffer.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-framebuffer.js +16 -0
- package/dist/adapter/resources/webgpu-framebuffer.js.map +1 -1
- package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-pipeline-layout.js +1 -2
- package/dist/adapter/resources/webgpu-pipeline-layout.js.map +1 -1
- package/dist/adapter/resources/webgpu-query-set.d.ts +33 -4
- package/dist/adapter/resources/webgpu-query-set.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-query-set.js +145 -4
- package/dist/adapter/resources/webgpu-query-set.js.map +1 -1
- package/dist/adapter/resources/webgpu-render-pass.d.ts +3 -0
- package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-render-pass.js +74 -30
- package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
- package/dist/adapter/resources/webgpu-render-pipeline.d.ts +7 -4
- package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-render-pipeline.js +26 -15
- package/dist/adapter/resources/webgpu-render-pipeline.js.map +1 -1
- package/dist/adapter/resources/webgpu-sampler.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-sampler.js +4 -0
- package/dist/adapter/resources/webgpu-sampler.js.map +1 -1
- package/dist/adapter/resources/webgpu-texture-view.d.ts +6 -0
- package/dist/adapter/resources/webgpu-texture-view.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-texture-view.js +47 -11
- package/dist/adapter/resources/webgpu-texture-view.js.map +1 -1
- package/dist/adapter/resources/webgpu-texture.d.ts +10 -4
- package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
- package/dist/adapter/resources/webgpu-texture.js +116 -57
- package/dist/adapter/resources/webgpu-texture.js.map +1 -1
- package/dist/adapter/resources/webgpu-vertex-array.js +1 -1
- package/dist/adapter/resources/webgpu-vertex-array.js.map +1 -1
- package/dist/adapter/webgpu-canvas-context.d.ts +2 -0
- package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
- package/dist/adapter/webgpu-canvas-context.js +78 -19
- package/dist/adapter/webgpu-canvas-context.js.map +1 -1
- package/dist/adapter/webgpu-device.d.ts +5 -1
- package/dist/adapter/webgpu-device.d.ts.map +1 -1
- package/dist/adapter/webgpu-device.js +113 -9
- package/dist/adapter/webgpu-device.js.map +1 -1
- package/dist/adapter/webgpu-presentation-context.d.ts +25 -0
- package/dist/adapter/webgpu-presentation-context.d.ts.map +1 -0
- package/dist/adapter/webgpu-presentation-context.js +144 -0
- package/dist/adapter/webgpu-presentation-context.js.map +1 -0
- package/dist/dist.dev.js +2864 -1702
- package/dist/dist.min.js +167 -8
- package/dist/index.cjs +1363 -225
- package/dist/index.cjs.map +4 -4
- package/package.json +5 -5
- package/src/adapter/helpers/cpu-hotspot-profiler.ts +70 -0
- package/src/adapter/helpers/generate-mipmaps-webgpu.ts +583 -0
- package/src/adapter/helpers/get-bind-group.ts +37 -22
- package/src/adapter/resources/webgpu-buffer.ts +61 -15
- package/src/adapter/resources/webgpu-command-buffer.ts +1 -1
- package/src/adapter/resources/webgpu-command-encoder.ts +32 -6
- package/src/adapter/resources/webgpu-compute-pass.ts +14 -6
- package/src/adapter/resources/webgpu-compute-pipeline.ts +21 -3
- package/src/adapter/resources/webgpu-framebuffer.ts +21 -0
- package/src/adapter/resources/webgpu-pipeline-layout.ts +1 -2
- package/src/adapter/resources/webgpu-query-set.ts +185 -9
- package/src/adapter/resources/webgpu-render-pass.ts +82 -34
- package/src/adapter/resources/webgpu-render-pipeline.ts +36 -19
- package/src/adapter/resources/webgpu-sampler.ts +5 -0
- package/src/adapter/resources/webgpu-texture-view.ts +51 -11
- package/src/adapter/resources/webgpu-texture.ts +142 -93
- package/src/adapter/resources/webgpu-vertex-array.ts +1 -1
- package/src/adapter/webgpu-canvas-context.ts +91 -26
- package/src/adapter/webgpu-device.ts +128 -9
- package/src/adapter/webgpu-presentation-context.ts +180 -0
|
@@ -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
|
}
|
|
@@ -11,59 +11,107 @@ import {WebGPUBuffer} from './webgpu-buffer';
|
|
|
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
|
|
|
24
|
+
/** Latest bindings applied to this pass */
|
|
25
|
+
bindings: Record<string, Binding> = {};
|
|
26
|
+
|
|
22
27
|
constructor(device: WebGPUDevice, props: RenderPassProps = {}) {
|
|
23
28
|
super(device, props);
|
|
24
29
|
this.device = device;
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
const {props: renderPassProps} = this;
|
|
31
|
+
this.framebuffer =
|
|
32
|
+
(renderPassProps.framebuffer as WebGPUFramebuffer) ||
|
|
33
|
+
device.getCanvasContext().getCurrentFramebuffer();
|
|
29
34
|
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
|
|
35
|
+
const profiler = getCpuHotspotProfiler(this.device);
|
|
36
|
+
if (profiler) {
|
|
37
|
+
const counterName:
|
|
38
|
+
| 'explicitFramebufferRenderPassCount'
|
|
39
|
+
| 'defaultFramebufferRenderPassCount' = renderPassProps.framebuffer
|
|
40
|
+
? 'explicitFramebufferRenderPassCount'
|
|
41
|
+
: 'defaultFramebufferRenderPassCount';
|
|
42
|
+
profiler[counterName] = (profiler[counterName] || 0) + 1;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
querySet: webgpuTSQuerySet.handle,
|
|
40
|
-
beginningOfPassWriteIndex: props.beginTimestampIndex,
|
|
41
|
-
endOfPassWriteIndex: props.endTimestampIndex
|
|
42
|
-
} as GPUComputePassTimestampWrites)
|
|
43
|
-
: undefined;
|
|
44
|
-
}
|
|
45
|
+
const startTime = profiler ? getTimestamp() : 0;
|
|
46
|
+
try {
|
|
47
|
+
const descriptorAssemblyStartTime = profiler ? getTimestamp() : 0;
|
|
48
|
+
const renderPassDescriptor = this.getRenderPassDescriptor(this.framebuffer);
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
if (renderPassProps.occlusionQuerySet) {
|
|
51
|
+
renderPassDescriptor.occlusionQuerySet = (
|
|
52
|
+
renderPassProps.occlusionQuerySet as WebGPUQuerySet
|
|
53
|
+
).handle;
|
|
54
|
+
}
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
if (renderPassProps.timestampQuerySet) {
|
|
57
|
+
const webgpuTSQuerySet = renderPassProps.timestampQuerySet as WebGPUQuerySet;
|
|
58
|
+
webgpuTSQuerySet?._invalidateResults();
|
|
59
|
+
renderPassDescriptor.timestampWrites = webgpuTSQuerySet
|
|
60
|
+
? ({
|
|
61
|
+
querySet: webgpuTSQuerySet.handle,
|
|
62
|
+
beginningOfPassWriteIndex: renderPassProps.beginTimestampIndex,
|
|
63
|
+
endOfPassWriteIndex: renderPassProps.endTimestampIndex
|
|
64
|
+
} as GPURenderPassTimestampWrites)
|
|
65
|
+
: undefined;
|
|
66
|
+
}
|
|
67
|
+
if (profiler) {
|
|
68
|
+
profiler.renderPassDescriptorAssemblyCount =
|
|
69
|
+
(profiler.renderPassDescriptorAssemblyCount || 0) + 1;
|
|
70
|
+
profiler.renderPassDescriptorAssemblyTimeMs =
|
|
71
|
+
(profiler.renderPassDescriptorAssemblyTimeMs || 0) +
|
|
72
|
+
(getTimestamp() - descriptorAssemblyStartTime);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!device.commandEncoder) {
|
|
76
|
+
throw new Error('commandEncoder not available');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.device.pushErrorScope('validation');
|
|
80
|
+
const beginRenderPassStartTime = profiler ? getTimestamp() : 0;
|
|
81
|
+
this.handle =
|
|
82
|
+
this.props.handle || device.commandEncoder.handle.beginRenderPass(renderPassDescriptor);
|
|
83
|
+
if (profiler) {
|
|
84
|
+
profiler.renderPassBeginCount = (profiler.renderPassBeginCount || 0) + 1;
|
|
85
|
+
profiler.renderPassBeginTimeMs =
|
|
86
|
+
(profiler.renderPassBeginTimeMs || 0) + (getTimestamp() - beginRenderPassStartTime);
|
|
87
|
+
}
|
|
88
|
+
this.device.popErrorScope((error: GPUError) => {
|
|
89
|
+
this.device.reportError(new Error(`${this} creation failed:\n"${error.message}"`), this)();
|
|
90
|
+
this.device.debug();
|
|
91
|
+
});
|
|
92
|
+
this.handle.label = this.props.id;
|
|
93
|
+
log.groupCollapsed(3, `new WebGPURenderPass(${this.id})`)();
|
|
94
|
+
log.probe(3, JSON.stringify(renderPassDescriptor, null, 2))();
|
|
95
|
+
log.groupEnd(3)();
|
|
96
|
+
} finally {
|
|
97
|
+
if (profiler) {
|
|
98
|
+
profiler.renderPassSetupCount = (profiler.renderPassSetupCount || 0) + 1;
|
|
99
|
+
profiler.renderPassSetupTimeMs =
|
|
100
|
+
(profiler.renderPassSetupTimeMs || 0) + (getTimestamp() - startTime);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
61
103
|
}
|
|
62
104
|
|
|
63
|
-
override destroy(): void {
|
|
105
|
+
override destroy(): void {
|
|
106
|
+
this.destroyResource();
|
|
107
|
+
}
|
|
64
108
|
|
|
65
109
|
end(): void {
|
|
110
|
+
if (this.destroyed) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
66
113
|
this.handle.end();
|
|
114
|
+
this.destroy();
|
|
67
115
|
}
|
|
68
116
|
|
|
69
117
|
setPipeline(pipeline: RenderPipeline): void {
|
|
@@ -78,8 +126,8 @@ export class WebGPURenderPass extends RenderPass {
|
|
|
78
126
|
|
|
79
127
|
/** Sets an array of bindings (uniform buffers, samplers, textures, ...) */
|
|
80
128
|
setBindings(bindings: Record<string, Binding>): void {
|
|
81
|
-
this.
|
|
82
|
-
const bindGroup = this.pipeline?._getBindGroup();
|
|
129
|
+
this.bindings = bindings;
|
|
130
|
+
const bindGroup = this.pipeline?._getBindGroup(bindings);
|
|
83
131
|
if (bindGroup) {
|
|
84
132
|
this.handle.setBindGroup(0, bindGroup);
|
|
85
133
|
}
|
|
@@ -15,6 +15,8 @@ import type {WebGPUDevice} from '../webgpu-device';
|
|
|
15
15
|
import type {WebGPUShader} from './webgpu-shader';
|
|
16
16
|
import type {WebGPURenderPass} from './webgpu-render-pass';
|
|
17
17
|
|
|
18
|
+
const EMPTY_BINDINGS: Record<string, Binding> = {};
|
|
19
|
+
|
|
18
20
|
// RENDER PIPELINE
|
|
19
21
|
|
|
20
22
|
/** Creates a new render pipeline when parameters change */
|
|
@@ -25,8 +27,9 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
25
27
|
readonly vs: WebGPUShader;
|
|
26
28
|
readonly fs: WebGPUShader | null = null;
|
|
27
29
|
|
|
28
|
-
/**
|
|
30
|
+
/** Compatibility path for direct pipeline.setBindings() usage */
|
|
29
31
|
private _bindings: Record<string, Binding>;
|
|
32
|
+
/** For internal use to create BindGroups */
|
|
30
33
|
private _bindGroupLayout: GPUBindGroupLayout | null = null;
|
|
31
34
|
private _bindGroup: GPUBindGroup | null = null;
|
|
32
35
|
|
|
@@ -56,8 +59,7 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
56
59
|
// Note: Often the same shader in WebGPU
|
|
57
60
|
this.vs = props.vs as WebGPUShader;
|
|
58
61
|
this.fs = props.fs as WebGPUShader;
|
|
59
|
-
|
|
60
|
-
this._bindings = {...this.props.bindings};
|
|
62
|
+
this._bindings = props.bindings || EMPTY_BINDINGS;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
override destroy(): void {
|
|
@@ -67,17 +69,25 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
+
* Compatibility shim for code paths that still set bindings on the pipeline.
|
|
73
|
+
* The shared-model path passes bindings per draw and does not rely on this state.
|
|
72
74
|
*/
|
|
73
75
|
setBindings(bindings: Record<string, Binding>): void {
|
|
74
|
-
|
|
76
|
+
let bindingsChanged = false;
|
|
75
77
|
for (const [name, binding] of Object.entries(bindings)) {
|
|
76
78
|
if (this._bindings[name] !== binding) {
|
|
77
|
-
|
|
79
|
+
if (!bindingsChanged) {
|
|
80
|
+
if (this._bindings === this.props.bindings || this._bindings === EMPTY_BINDINGS) {
|
|
81
|
+
this._bindings = {...this._bindings};
|
|
82
|
+
}
|
|
83
|
+
bindingsChanged = true;
|
|
84
|
+
}
|
|
85
|
+
this._bindings[name] = binding;
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
|
-
|
|
88
|
+
if (bindingsChanged) {
|
|
89
|
+
this._bindGroup = null;
|
|
90
|
+
}
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
/** @todo - should this be moved to renderpass? */
|
|
@@ -91,8 +101,12 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
91
101
|
firstIndex?: number;
|
|
92
102
|
firstInstance?: number;
|
|
93
103
|
baseVertex?: number;
|
|
104
|
+
bindings?: Record<string, Binding>;
|
|
105
|
+
uniforms?: Record<string, unknown>;
|
|
94
106
|
}): boolean {
|
|
95
107
|
const webgpuRenderPass = options.renderPass as WebGPURenderPass;
|
|
108
|
+
const instanceCount =
|
|
109
|
+
options.instanceCount && options.instanceCount > 0 ? options.instanceCount : 1;
|
|
96
110
|
|
|
97
111
|
// Set pipeline
|
|
98
112
|
this.device.pushErrorScope('validation');
|
|
@@ -103,7 +117,7 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
103
117
|
});
|
|
104
118
|
|
|
105
119
|
// Set bindings (uniform buffers, textures etc)
|
|
106
|
-
const bindGroup = this._getBindGroup();
|
|
120
|
+
const bindGroup = this._getBindGroup(options.bindings);
|
|
107
121
|
if (bindGroup) {
|
|
108
122
|
webgpuRenderPass.handle.setBindGroup(0, bindGroup);
|
|
109
123
|
}
|
|
@@ -116,16 +130,17 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
116
130
|
if (options.indexCount) {
|
|
117
131
|
webgpuRenderPass.handle.drawIndexed(
|
|
118
132
|
options.indexCount,
|
|
119
|
-
|
|
120
|
-
options.firstIndex,
|
|
121
|
-
options.baseVertex,
|
|
122
|
-
options.firstInstance
|
|
133
|
+
instanceCount,
|
|
134
|
+
options.firstIndex || 0,
|
|
135
|
+
options.baseVertex || 0,
|
|
136
|
+
options.firstInstance || 0
|
|
123
137
|
);
|
|
124
138
|
} else {
|
|
125
139
|
webgpuRenderPass.handle.draw(
|
|
126
140
|
options.vertexCount || 0,
|
|
127
|
-
|
|
128
|
-
options.
|
|
141
|
+
instanceCount,
|
|
142
|
+
options.firstVertex || 0,
|
|
143
|
+
options.firstInstance || 0
|
|
129
144
|
);
|
|
130
145
|
}
|
|
131
146
|
|
|
@@ -136,7 +151,7 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
136
151
|
}
|
|
137
152
|
|
|
138
153
|
/** Return a bind group created by setBindings */
|
|
139
|
-
_getBindGroup() {
|
|
154
|
+
_getBindGroup(bindings?: Record<string, Binding>) {
|
|
140
155
|
if (this.shaderLayout.bindings.length === 0) {
|
|
141
156
|
return null;
|
|
142
157
|
}
|
|
@@ -144,11 +159,13 @@ export class WebGPURenderPipeline extends RenderPipeline {
|
|
|
144
159
|
// 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
160
|
this._bindGroupLayout = this._bindGroupLayout || this.handle.getBindGroupLayout(0);
|
|
146
161
|
|
|
147
|
-
|
|
148
|
-
|
|
162
|
+
if (bindings) {
|
|
163
|
+
return getBindGroup(this.device, this._bindGroupLayout, this.shaderLayout, bindings);
|
|
164
|
+
}
|
|
165
|
+
|
|
149
166
|
this._bindGroup =
|
|
150
167
|
this._bindGroup ||
|
|
151
|
-
getBindGroup(this.device
|
|
168
|
+
getBindGroup(this.device, this._bindGroupLayout, this.shaderLayout, this._bindings);
|
|
152
169
|
|
|
153
170
|
return this._bindGroup;
|
|
154
171
|
}
|
|
@@ -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
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import {TextureView, TextureViewProps} from '@luma.gl/core';
|
|
6
6
|
import type {WebGPUDevice} from '../webgpu-device';
|
|
7
7
|
import type {WebGPUTexture} from './webgpu-texture';
|
|
8
|
+
import {getCpuHotspotProfiler, getTimestamp} from '../helpers/cpu-hotspot-profiler';
|
|
8
9
|
|
|
9
10
|
/*
|
|
10
11
|
// type = sampler
|
|
@@ -39,17 +40,15 @@ export class WebGPUTextureView extends TextureView {
|
|
|
39
40
|
this.texture = props.texture;
|
|
40
41
|
|
|
41
42
|
this.device.pushErrorScope('validation');
|
|
42
|
-
this.handle =
|
|
43
|
-
|
|
44
|
-
this.texture.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
arrayLayerCount: this.props.arrayLayerCount
|
|
52
|
-
});
|
|
43
|
+
this.handle = this.texture.handle.createView({
|
|
44
|
+
format: (this.props.format || this.texture.format) as GPUTextureFormat,
|
|
45
|
+
dimension: this.props.dimension || this.texture.dimension,
|
|
46
|
+
aspect: this.props.aspect,
|
|
47
|
+
baseMipLevel: this.props.baseMipLevel,
|
|
48
|
+
mipLevelCount: this.props.mipLevelCount,
|
|
49
|
+
baseArrayLayer: this.props.baseArrayLayer,
|
|
50
|
+
arrayLayerCount: this.props.arrayLayerCount
|
|
51
|
+
});
|
|
53
52
|
this.device.popErrorScope((error: GPUError) => {
|
|
54
53
|
this.device.reportError(new Error(`TextureView constructor: ${error.message}`), this)();
|
|
55
54
|
this.device.debug();
|
|
@@ -59,9 +58,50 @@ export class WebGPUTextureView extends TextureView {
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
override destroy(): void {
|
|
61
|
+
if (this.destroyed) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.destroyResource();
|
|
62
66
|
// GPUTextureView does not have a destroy method
|
|
63
67
|
// this.handle.destroy();
|
|
64
68
|
// @ts-expect-error readonly
|
|
65
69
|
this.handle = null;
|
|
66
70
|
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Internal-only hook for the cached CanvasContext/PresentationContext swapchain path.
|
|
74
|
+
* Rebuilds the default view when the per-frame canvas texture handle changes, without
|
|
75
|
+
* replacing the long-lived luma.gl wrapper object.
|
|
76
|
+
*/
|
|
77
|
+
_reinitialize(texture: WebGPUTexture): void {
|
|
78
|
+
// @ts-expect-error readonly
|
|
79
|
+
this.texture = texture;
|
|
80
|
+
|
|
81
|
+
const profiler = getCpuHotspotProfiler(this.device);
|
|
82
|
+
this.device.pushErrorScope('validation');
|
|
83
|
+
const createViewStartTime = profiler ? getTimestamp() : 0;
|
|
84
|
+
const handle = this.texture.handle.createView({
|
|
85
|
+
format: (this.props.format || this.texture.format) as GPUTextureFormat,
|
|
86
|
+
dimension: this.props.dimension || this.texture.dimension,
|
|
87
|
+
aspect: this.props.aspect,
|
|
88
|
+
baseMipLevel: this.props.baseMipLevel,
|
|
89
|
+
mipLevelCount: this.props.mipLevelCount,
|
|
90
|
+
baseArrayLayer: this.props.baseArrayLayer,
|
|
91
|
+
arrayLayerCount: this.props.arrayLayerCount
|
|
92
|
+
});
|
|
93
|
+
if (profiler) {
|
|
94
|
+
profiler.textureViewReinitializeCount = (profiler.textureViewReinitializeCount || 0) + 1;
|
|
95
|
+
profiler.textureViewReinitializeTimeMs =
|
|
96
|
+
(profiler.textureViewReinitializeTimeMs || 0) + (getTimestamp() - createViewStartTime);
|
|
97
|
+
}
|
|
98
|
+
this.device.popErrorScope((error: GPUError) => {
|
|
99
|
+
this.device.reportError(new Error(`TextureView constructor: ${error.message}`), this)();
|
|
100
|
+
this.device.debug();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
handle.label = this.props.id;
|
|
104
|
+
// @ts-expect-error readonly
|
|
105
|
+
this.handle = handle;
|
|
106
|
+
}
|
|
67
107
|
}
|