@plasius/gpu-renderer 0.2.40 → 0.2.41
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/CHANGELOG.md +23 -0
- package/README.md +41 -1
- package/dist/index.cjs +626 -147
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +626 -147
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +34 -0
- package/src/wavefront-compute.js +174 -76
- package/src/wavefront-core.js +2 -0
- package/src/wavefront-frame-encoder.js +13 -5
- package/src/wavefront-frame-runtime.js +24 -0
- package/src/wavefront-frame-stats.js +9 -0
- package/src/wavefront-frame-telemetry.js +411 -0
- package/src/wavefront-pipelines.js +12 -0
- package/src/wavefront-runtime-support.js +37 -4
- package/src/wavefront-shader-bvh.js +43 -30
- package/src/wavefront-shader-kernels.js +1 -30
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { GPU_READBACK_COMPLETION_TIMEOUT_MS } from "./wavefront-core.js";
|
|
2
|
+
import { createBuffer } from "./wavefront-gpu-resources.js";
|
|
3
|
+
|
|
4
|
+
const TIMESTAMP_QUERY_COUNT = 2;
|
|
5
|
+
const TIMESTAMP_BUFFER_BYTES = TIMESTAMP_QUERY_COUNT * BigUint64Array.BYTES_PER_ELEMENT;
|
|
6
|
+
|
|
7
|
+
function hasGpuFeature(features, name) {
|
|
8
|
+
if (typeof features?.has === "function") {
|
|
9
|
+
return features.has(name);
|
|
10
|
+
}
|
|
11
|
+
if (features && typeof features[Symbol.iterator] === "function") {
|
|
12
|
+
return Array.from(features).includes(name);
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function freezeRayCountTelemetry(values = {}) {
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
status: values.status ?? "not-requested",
|
|
20
|
+
source: values.source ?? null,
|
|
21
|
+
expectedPrimaryRays: values.expectedPrimaryRays ?? null,
|
|
22
|
+
observedPrimaryRays: values.observedPrimaryRays ?? null,
|
|
23
|
+
secondaryRays: values.secondaryRays ?? null,
|
|
24
|
+
totalPathSegments: values.totalPathSegments ?? null,
|
|
25
|
+
bounceHistogram: Object.freeze([...(values.bounceHistogram ?? [])]),
|
|
26
|
+
capturedRayCounts: values.capturedRayCounts ?? 0,
|
|
27
|
+
expectedRayCounts: values.expectedRayCounts ?? 0,
|
|
28
|
+
reason: values.reason ?? null,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createWavefrontRayCountTelemetry(values = {}) {
|
|
33
|
+
return freezeRayCountTelemetry(values);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createWavefrontFrameTimingTelemetry(values = {}) {
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
status: values.status ?? "not-requested",
|
|
39
|
+
source: values.source ?? null,
|
|
40
|
+
timestampQueryStatus: values.timestampQueryStatus ?? "not-recorded",
|
|
41
|
+
totalGpuTimeMs: values.totalGpuTimeMs ?? null,
|
|
42
|
+
totalRenderJobTimeMs: Math.max(0, Number(values.totalRenderJobTimeMs ?? 0)),
|
|
43
|
+
classificationTimeMs: values.classificationTimeMs ?? null,
|
|
44
|
+
compactionTimeMs: values.compactionTimeMs ?? null,
|
|
45
|
+
samplingTimeMs: values.samplingTimeMs ?? null,
|
|
46
|
+
reason: values.reason ?? null,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function safeErrorReason(error) {
|
|
51
|
+
return error instanceof Error && error.message
|
|
52
|
+
? error.message
|
|
53
|
+
: "unknown-telemetry-readback-failure";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function timestampDurationMs(values) {
|
|
57
|
+
if (values.length < TIMESTAMP_QUERY_COUNT || values[1] < values[0]) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const durationNanoseconds = values[1] - values[0];
|
|
61
|
+
const durationMs = Number(durationNanoseconds) / 1_000_000;
|
|
62
|
+
return Number.isFinite(durationMs) && durationMs >= 0 ? durationMs : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createWavefrontFrameTelemetryResources({
|
|
66
|
+
device,
|
|
67
|
+
constants,
|
|
68
|
+
maxRayCountRecords,
|
|
69
|
+
}) {
|
|
70
|
+
const rayRecordCapacity = Math.max(1, Math.trunc(Number(maxRayCountRecords) || 1));
|
|
71
|
+
const rayBufferBytes = rayRecordCapacity * Uint32Array.BYTES_PER_ELEMENT;
|
|
72
|
+
const mapMode = constants.map;
|
|
73
|
+
if (!mapMode || typeof mapMode.READ !== "number") {
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
available: false,
|
|
76
|
+
reason: "gpu-map-read-unavailable",
|
|
77
|
+
memoryBytes: 0,
|
|
78
|
+
beginFrame() {},
|
|
79
|
+
recordActiveRayCount() {},
|
|
80
|
+
decorateFirstPass(descriptor) {
|
|
81
|
+
return descriptor;
|
|
82
|
+
},
|
|
83
|
+
decorateFinalPass(descriptor) {
|
|
84
|
+
return descriptor;
|
|
85
|
+
},
|
|
86
|
+
async readFrame({ expectedPrimaryRays = 0, expectedRayCounts = 0 } = {}) {
|
|
87
|
+
return Object.freeze({
|
|
88
|
+
rayCounts: freezeRayCountTelemetry({
|
|
89
|
+
status: "unavailable",
|
|
90
|
+
expectedPrimaryRays,
|
|
91
|
+
expectedRayCounts,
|
|
92
|
+
reason: "gpu-map-read-unavailable",
|
|
93
|
+
}),
|
|
94
|
+
timestampQueryStatus: "unsupported",
|
|
95
|
+
totalGpuTimeMs: null,
|
|
96
|
+
reason: "gpu-map-read-unavailable",
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
destroy() {},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const rayCountBuffer = createBuffer(
|
|
104
|
+
device,
|
|
105
|
+
constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
|
|
106
|
+
rayBufferBytes,
|
|
107
|
+
"plasius.wavefront.rayCounts"
|
|
108
|
+
);
|
|
109
|
+
const rayCountReadbackBuffer = createBuffer(
|
|
110
|
+
device,
|
|
111
|
+
constants.buffer.COPY_DST | constants.buffer.MAP_READ,
|
|
112
|
+
rayBufferBytes,
|
|
113
|
+
"plasius.wavefront.rayCounts.readback"
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const timestampFeatureAvailable = hasGpuFeature(device?.features, "timestamp-query");
|
|
117
|
+
const timestampApiAvailable =
|
|
118
|
+
timestampFeatureAvailable &&
|
|
119
|
+
typeof device.createQuerySet === "function" &&
|
|
120
|
+
typeof constants.buffer.QUERY_RESOLVE === "number";
|
|
121
|
+
let timestampQuerySet = null;
|
|
122
|
+
let timestampResolveBuffer = null;
|
|
123
|
+
let timestampReadbackBuffer = null;
|
|
124
|
+
let timestampSetupReason = timestampFeatureAvailable
|
|
125
|
+
? "timestamp-query-api-unavailable"
|
|
126
|
+
: "timestamp-query-unsupported";
|
|
127
|
+
if (timestampApiAvailable) {
|
|
128
|
+
try {
|
|
129
|
+
timestampQuerySet = device.createQuerySet({
|
|
130
|
+
label: "plasius.wavefront.timestamps",
|
|
131
|
+
type: "timestamp",
|
|
132
|
+
count: TIMESTAMP_QUERY_COUNT,
|
|
133
|
+
});
|
|
134
|
+
timestampResolveBuffer = createBuffer(
|
|
135
|
+
device,
|
|
136
|
+
constants.buffer.QUERY_RESOLVE | constants.buffer.COPY_SRC,
|
|
137
|
+
TIMESTAMP_BUFFER_BYTES,
|
|
138
|
+
"plasius.wavefront.timestamps.resolve"
|
|
139
|
+
);
|
|
140
|
+
timestampReadbackBuffer = createBuffer(
|
|
141
|
+
device,
|
|
142
|
+
constants.buffer.COPY_DST | constants.buffer.MAP_READ,
|
|
143
|
+
TIMESTAMP_BUFFER_BYTES,
|
|
144
|
+
"plasius.wavefront.timestamps.readback"
|
|
145
|
+
);
|
|
146
|
+
timestampSetupReason = null;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
timestampQuerySet?.destroy?.();
|
|
149
|
+
timestampQuerySet = null;
|
|
150
|
+
timestampResolveBuffer?.destroy?.();
|
|
151
|
+
timestampResolveBuffer = null;
|
|
152
|
+
timestampReadbackBuffer?.destroy?.();
|
|
153
|
+
timestampReadbackBuffer = null;
|
|
154
|
+
timestampSetupReason = `timestamp-query-setup-failed:${safeErrorReason(error)}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const timestampUnavailableStatus = timestampSetupReason?.startsWith(
|
|
158
|
+
"timestamp-query-setup-failed:"
|
|
159
|
+
)
|
|
160
|
+
? "failed"
|
|
161
|
+
: "unsupported";
|
|
162
|
+
|
|
163
|
+
let active = false;
|
|
164
|
+
let recordCount = 0;
|
|
165
|
+
let recordOverflow = false;
|
|
166
|
+
let bounceIndices = [];
|
|
167
|
+
let timestampStarted = false;
|
|
168
|
+
let timestampEnded = false;
|
|
169
|
+
let rayReadbackMapped = false;
|
|
170
|
+
let timestampReadbackMapped = false;
|
|
171
|
+
|
|
172
|
+
const memoryBytes =
|
|
173
|
+
rayBufferBytes * 2 +
|
|
174
|
+
(timestampQuerySet ? TIMESTAMP_BUFFER_BYTES * 2 : 0);
|
|
175
|
+
|
|
176
|
+
function beginFrame() {
|
|
177
|
+
active = true;
|
|
178
|
+
recordCount = 0;
|
|
179
|
+
recordOverflow = false;
|
|
180
|
+
bounceIndices = [];
|
|
181
|
+
timestampStarted = false;
|
|
182
|
+
timestampEnded = false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function recordActiveRayCount(encoder, counterBuffer, bounceIndex) {
|
|
186
|
+
if (!active) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (recordCount >= rayRecordCapacity) {
|
|
190
|
+
recordOverflow = true;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
encoder.copyBufferToBuffer(
|
|
194
|
+
counterBuffer,
|
|
195
|
+
0,
|
|
196
|
+
rayCountBuffer,
|
|
197
|
+
recordCount * Uint32Array.BYTES_PER_ELEMENT,
|
|
198
|
+
Uint32Array.BYTES_PER_ELEMENT
|
|
199
|
+
);
|
|
200
|
+
bounceIndices.push(Math.max(0, Math.trunc(Number(bounceIndex) || 0)));
|
|
201
|
+
recordCount += 1;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function decorateFirstPass(descriptor) {
|
|
205
|
+
if (!active || !timestampQuerySet || timestampStarted) {
|
|
206
|
+
return descriptor;
|
|
207
|
+
}
|
|
208
|
+
timestampStarted = true;
|
|
209
|
+
return {
|
|
210
|
+
...descriptor,
|
|
211
|
+
timestampWrites: {
|
|
212
|
+
querySet: timestampQuerySet,
|
|
213
|
+
beginningOfPassWriteIndex: 0,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function decorateFinalPass(descriptor) {
|
|
219
|
+
if (!active || !timestampQuerySet || !timestampStarted || timestampEnded) {
|
|
220
|
+
return descriptor;
|
|
221
|
+
}
|
|
222
|
+
timestampEnded = true;
|
|
223
|
+
return {
|
|
224
|
+
...descriptor,
|
|
225
|
+
timestampWrites: {
|
|
226
|
+
querySet: timestampQuerySet,
|
|
227
|
+
endOfPassWriteIndex: 1,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function readFrame({
|
|
233
|
+
expectedPrimaryRays = 0,
|
|
234
|
+
expectedRayCounts = recordCount,
|
|
235
|
+
waitForSubmittedGpuWork,
|
|
236
|
+
} = {}) {
|
|
237
|
+
active = false;
|
|
238
|
+
if (recordOverflow || recordCount !== expectedRayCounts) {
|
|
239
|
+
return Object.freeze({
|
|
240
|
+
rayCounts: freezeRayCountTelemetry({
|
|
241
|
+
status: "failed",
|
|
242
|
+
expectedPrimaryRays,
|
|
243
|
+
capturedRayCounts: recordCount,
|
|
244
|
+
expectedRayCounts,
|
|
245
|
+
reason: recordOverflow
|
|
246
|
+
? "ray-count-record-capacity-exceeded"
|
|
247
|
+
: `ray-count-record-mismatch:${recordCount}/${expectedRayCounts}`,
|
|
248
|
+
}),
|
|
249
|
+
timestampQueryStatus: timestampQuerySet ? "failed" : timestampUnavailableStatus,
|
|
250
|
+
totalGpuTimeMs: null,
|
|
251
|
+
reason: "ray-count-recording-incomplete",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const encoder = device.createCommandEncoder({
|
|
256
|
+
label: "plasius.wavefront.telemetry.readback",
|
|
257
|
+
});
|
|
258
|
+
const rayCopyBytes = Math.max(
|
|
259
|
+
Uint32Array.BYTES_PER_ELEMENT,
|
|
260
|
+
recordCount * Uint32Array.BYTES_PER_ELEMENT
|
|
261
|
+
);
|
|
262
|
+
encoder.copyBufferToBuffer(rayCountBuffer, 0, rayCountReadbackBuffer, 0, rayCopyBytes);
|
|
263
|
+
const canResolveTimestamps =
|
|
264
|
+
timestampQuerySet &&
|
|
265
|
+
timestampStarted &&
|
|
266
|
+
timestampEnded &&
|
|
267
|
+
typeof encoder.resolveQuerySet === "function";
|
|
268
|
+
if (canResolveTimestamps) {
|
|
269
|
+
encoder.resolveQuerySet(
|
|
270
|
+
timestampQuerySet,
|
|
271
|
+
0,
|
|
272
|
+
TIMESTAMP_QUERY_COUNT,
|
|
273
|
+
timestampResolveBuffer,
|
|
274
|
+
0
|
|
275
|
+
);
|
|
276
|
+
encoder.copyBufferToBuffer(
|
|
277
|
+
timestampResolveBuffer,
|
|
278
|
+
0,
|
|
279
|
+
timestampReadbackBuffer,
|
|
280
|
+
0,
|
|
281
|
+
TIMESTAMP_BUFFER_BYTES
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
device.queue.submit([encoder.finish()]);
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
await waitForSubmittedGpuWork({
|
|
288
|
+
timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
289
|
+
allowTimeout: false,
|
|
290
|
+
});
|
|
291
|
+
await rayCountReadbackBuffer.mapAsync(mapMode.READ);
|
|
292
|
+
rayReadbackMapped = true;
|
|
293
|
+
const rayCopy = rayCountReadbackBuffer
|
|
294
|
+
.getMappedRange()
|
|
295
|
+
.slice(0, rayCopyBytes);
|
|
296
|
+
const rayCounts = new Uint32Array(rayCopy, 0, recordCount);
|
|
297
|
+
rayCountReadbackBuffer.unmap();
|
|
298
|
+
rayReadbackMapped = false;
|
|
299
|
+
|
|
300
|
+
const bounceHistogram = Array.from({ length: Math.max(0, ...bounceIndices) + 1 }, () => 0);
|
|
301
|
+
let observedPrimaryRays = 0;
|
|
302
|
+
let secondaryRays = 0;
|
|
303
|
+
let totalPathSegments = 0;
|
|
304
|
+
for (let index = 0; index < rayCounts.length; index += 1) {
|
|
305
|
+
const count = Number(rayCounts[index] ?? 0);
|
|
306
|
+
const bounceIndex = bounceIndices[index] ?? 0;
|
|
307
|
+
bounceHistogram[bounceIndex] = (bounceHistogram[bounceIndex] ?? 0) + count;
|
|
308
|
+
totalPathSegments += count;
|
|
309
|
+
if (bounceIndex === 0) {
|
|
310
|
+
observedPrimaryRays += count;
|
|
311
|
+
} else {
|
|
312
|
+
secondaryRays += count;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const primaryCountsMatch = observedPrimaryRays === expectedPrimaryRays;
|
|
316
|
+
const rayTelemetry = freezeRayCountTelemetry({
|
|
317
|
+
status: primaryCountsMatch ? "available" : "failed",
|
|
318
|
+
source: "gpu-active-queue-readback",
|
|
319
|
+
expectedPrimaryRays,
|
|
320
|
+
observedPrimaryRays,
|
|
321
|
+
secondaryRays: primaryCountsMatch ? secondaryRays : null,
|
|
322
|
+
totalPathSegments: primaryCountsMatch ? totalPathSegments : null,
|
|
323
|
+
bounceHistogram,
|
|
324
|
+
capturedRayCounts: recordCount,
|
|
325
|
+
expectedRayCounts,
|
|
326
|
+
reason: primaryCountsMatch
|
|
327
|
+
? null
|
|
328
|
+
: `primary-ray-count-mismatch:${observedPrimaryRays}/${expectedPrimaryRays}`,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
let timestampQueryStatus = timestampQuerySet ? "failed" : timestampUnavailableStatus;
|
|
332
|
+
let totalGpuTimeMs = null;
|
|
333
|
+
let timestampReason = timestampSetupReason;
|
|
334
|
+
if (canResolveTimestamps) {
|
|
335
|
+
try {
|
|
336
|
+
await timestampReadbackBuffer.mapAsync(mapMode.READ);
|
|
337
|
+
timestampReadbackMapped = true;
|
|
338
|
+
const timestampCopy = timestampReadbackBuffer
|
|
339
|
+
.getMappedRange()
|
|
340
|
+
.slice(0, TIMESTAMP_BUFFER_BYTES);
|
|
341
|
+
const timestamps = new BigUint64Array(timestampCopy);
|
|
342
|
+
timestampReadbackBuffer.unmap();
|
|
343
|
+
timestampReadbackMapped = false;
|
|
344
|
+
totalGpuTimeMs = timestampDurationMs(timestamps);
|
|
345
|
+
timestampQueryStatus = totalGpuTimeMs === null ? "failed" : "available";
|
|
346
|
+
timestampReason =
|
|
347
|
+
totalGpuTimeMs === null ? "timestamp-query-returned-invalid-range" : null;
|
|
348
|
+
} catch (error) {
|
|
349
|
+
if (timestampReadbackMapped) {
|
|
350
|
+
timestampReadbackBuffer.unmap();
|
|
351
|
+
timestampReadbackMapped = false;
|
|
352
|
+
}
|
|
353
|
+
timestampQueryStatus = "failed";
|
|
354
|
+
timestampReason = `timestamp-query-readback-failed:${safeErrorReason(error)}`;
|
|
355
|
+
}
|
|
356
|
+
} else if (timestampQuerySet) {
|
|
357
|
+
timestampReason = "timestamp-query-span-incomplete";
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return Object.freeze({
|
|
361
|
+
rayCounts: rayTelemetry,
|
|
362
|
+
timestampQueryStatus,
|
|
363
|
+
totalGpuTimeMs,
|
|
364
|
+
reason: timestampReason,
|
|
365
|
+
});
|
|
366
|
+
} catch (error) {
|
|
367
|
+
if (rayReadbackMapped) {
|
|
368
|
+
rayCountReadbackBuffer.unmap();
|
|
369
|
+
rayReadbackMapped = false;
|
|
370
|
+
}
|
|
371
|
+
if (timestampReadbackMapped) {
|
|
372
|
+
timestampReadbackBuffer?.unmap();
|
|
373
|
+
timestampReadbackMapped = false;
|
|
374
|
+
}
|
|
375
|
+
const reason = safeErrorReason(error);
|
|
376
|
+
return Object.freeze({
|
|
377
|
+
rayCounts: freezeRayCountTelemetry({
|
|
378
|
+
status: "failed",
|
|
379
|
+
expectedPrimaryRays,
|
|
380
|
+
capturedRayCounts: recordCount,
|
|
381
|
+
expectedRayCounts,
|
|
382
|
+
reason: `ray-count-readback-failed:${reason}`,
|
|
383
|
+
}),
|
|
384
|
+
timestampQueryStatus: timestampQuerySet ? "failed" : timestampUnavailableStatus,
|
|
385
|
+
totalGpuTimeMs: null,
|
|
386
|
+
reason: `telemetry-readback-failed:${reason}`,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function destroy() {
|
|
392
|
+
active = false;
|
|
393
|
+
rayCountBuffer.destroy?.();
|
|
394
|
+
rayCountReadbackBuffer.destroy?.();
|
|
395
|
+
timestampQuerySet?.destroy?.();
|
|
396
|
+
timestampResolveBuffer?.destroy?.();
|
|
397
|
+
timestampReadbackBuffer?.destroy?.();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return Object.freeze({
|
|
401
|
+
available: true,
|
|
402
|
+
reason: null,
|
|
403
|
+
memoryBytes,
|
|
404
|
+
beginFrame,
|
|
405
|
+
recordActiveRayCount,
|
|
406
|
+
decorateFirstPass,
|
|
407
|
+
decorateFinalPass,
|
|
408
|
+
readFrame,
|
|
409
|
+
destroy,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
@@ -47,6 +47,18 @@ export function createWavefrontBindGroupLayouts(device, constants) {
|
|
|
47
47
|
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
48
48
|
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
49
49
|
{ binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
50
|
+
{ binding: 33, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
51
|
+
{ binding: 34, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
52
|
+
{ binding: 35, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
53
|
+
{ binding: 36, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
54
|
+
{ binding: 37, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
55
|
+
{ binding: 38, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
56
|
+
{ binding: 39, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
57
|
+
{ binding: 40, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
58
|
+
{ binding: 41, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
59
|
+
{ binding: 42, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
60
|
+
{ binding: 43, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
61
|
+
{ binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
50
62
|
],
|
|
51
63
|
});
|
|
52
64
|
const acceleration = device.createBindGroupLayout({
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
4
4
|
GPU_READBACK_COMPLETION_TIMEOUT_MS,
|
|
5
5
|
GPU_SUBMITTED_WORK_TIMEOUT_MS,
|
|
6
|
+
TRACE_SAMPLED_TEXTURE_BINDINGS,
|
|
6
7
|
TRACE_STORAGE_BUFFER_BINDINGS,
|
|
7
8
|
WORKGROUP_SIZE,
|
|
8
9
|
readNonNegativeInteger,
|
|
@@ -50,11 +51,11 @@ export function resolveCanvas(canvasOrSelector, documentRef = globalThis.documen
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
async function getPipelineDiagnostics(shaderModule) {
|
|
53
|
-
if (typeof shaderModule?.
|
|
54
|
+
if (typeof shaderModule?.getCompilationInfo !== "function") {
|
|
54
55
|
return "";
|
|
55
56
|
}
|
|
56
57
|
try {
|
|
57
|
-
const info = await shaderModule.
|
|
58
|
+
const info = await shaderModule.getCompilationInfo();
|
|
58
59
|
const messages = info.messages ?? [];
|
|
59
60
|
if (messages.length === 0) {
|
|
60
61
|
return "";
|
|
@@ -96,10 +97,10 @@ export async function createComputePipeline(device, shaderModule, layout, entryP
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
export async function assertShaderModuleCompiles(shaderModule, label) {
|
|
99
|
-
if (typeof shaderModule?.
|
|
100
|
+
if (typeof shaderModule?.getCompilationInfo !== "function") {
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
|
-
const info = await shaderModule.
|
|
103
|
+
const info = await shaderModule.getCompilationInfo();
|
|
103
104
|
const messages = Array.isArray(info?.messages) ? info.messages : [];
|
|
104
105
|
const errors = messages.filter((message) => message?.type === "error");
|
|
105
106
|
if (errors.length <= 0) {
|
|
@@ -138,7 +139,39 @@ export function createWavefrontDeviceDescriptor(adapter, options = {}) {
|
|
|
138
139
|
);
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
const exposedSampledTextureLimit = Number(adapter?.limits?.maxSampledTexturesPerShaderStage);
|
|
143
|
+
if (Number.isFinite(exposedSampledTextureLimit)) {
|
|
144
|
+
if (exposedSampledTextureLimit < TRACE_SAMPLED_TEXTURE_BINDINGS) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Wavefront material tracing requires maxSampledTexturesPerShaderStage>=${TRACE_SAMPLED_TEXTURE_BINDINGS}, ` +
|
|
147
|
+
`but this adapter exposes ${exposedSampledTextureLimit}.`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
requiredLimits.maxSampledTexturesPerShaderStage = Math.max(
|
|
151
|
+
Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
|
|
152
|
+
TRACE_SAMPLED_TEXTURE_BINDINGS
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
141
156
|
const descriptor = { ...(options.deviceDescriptor ?? {}) };
|
|
157
|
+
const requiredFeatures = Array.from(descriptor.requiredFeatures ?? []);
|
|
158
|
+
const adapterFeatures = adapter?.features;
|
|
159
|
+
const timestampQuerySupported =
|
|
160
|
+
typeof adapterFeatures?.has === "function"
|
|
161
|
+
? adapterFeatures.has("timestamp-query")
|
|
162
|
+
: adapterFeatures && typeof adapterFeatures[Symbol.iterator] === "function"
|
|
163
|
+
? Array.from(adapterFeatures).includes("timestamp-query")
|
|
164
|
+
: false;
|
|
165
|
+
if (
|
|
166
|
+
options.gpuTimestamps !== false &&
|
|
167
|
+
timestampQuerySupported &&
|
|
168
|
+
!requiredFeatures.includes("timestamp-query")
|
|
169
|
+
) {
|
|
170
|
+
requiredFeatures.push("timestamp-query");
|
|
171
|
+
}
|
|
172
|
+
if (requiredFeatures.length > 0) {
|
|
173
|
+
descriptor.requiredFeatures = requiredFeatures;
|
|
174
|
+
}
|
|
142
175
|
if (Object.keys(requiredLimits).length > 0) {
|
|
143
176
|
descriptor.requiredLimits = {
|
|
144
177
|
...(descriptor.requiredLimits ?? {}),
|
|
@@ -113,36 +113,49 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
|
|
|
113
113
|
let uv1 = select(vec2<f32>(0.0), vertex1.uv.xy, vertex1.uv.z > 0.5);
|
|
114
114
|
let uv2 = select(vec2<f32>(0.0), vertex2.uv.xy, vertex2.uv.z > 0.5);
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
);
|
|
116
|
+
var preparedTriangle = TriangleRecord();
|
|
117
|
+
preparedTriangle.triangleId = triangleIndex;
|
|
118
|
+
preparedTriangle.meshId = mesh.meshId;
|
|
119
|
+
preparedTriangle.materialKind = mesh.materialKind;
|
|
120
|
+
preparedTriangle.flags = mesh.flags;
|
|
121
|
+
preparedTriangle.materialRefId = mesh.materialRefId;
|
|
122
|
+
preparedTriangle.mediumRefId = mesh.mediumRefId;
|
|
123
|
+
preparedTriangle.materialSlot = mesh.materialSlot;
|
|
124
|
+
preparedTriangle.v0 = vec4<f32>(vertex0.position.xyz, 0.0);
|
|
125
|
+
preparedTriangle.v1 = vec4<f32>(vertex1.position.xyz, 0.0);
|
|
126
|
+
preparedTriangle.v2 = vec4<f32>(vertex2.position.xyz, 0.0);
|
|
127
|
+
preparedTriangle.n0 = vec4<f32>(n0, 0.0);
|
|
128
|
+
preparedTriangle.n1 = vec4<f32>(n1, 0.0);
|
|
129
|
+
preparedTriangle.n2 = vec4<f32>(n2, 0.0);
|
|
130
|
+
preparedTriangle.uv0uv1 = vec4<f32>(uv0, uv1);
|
|
131
|
+
preparedTriangle.uv2Pad = vec4<f32>(uv2, 0.0, 0.0);
|
|
132
|
+
preparedTriangle.color = mesh.color;
|
|
133
|
+
preparedTriangle.emission = mesh.emission;
|
|
134
|
+
preparedTriangle.material = mesh.material;
|
|
135
|
+
preparedTriangle.materialResponse = mesh.materialResponse;
|
|
136
|
+
preparedTriangle.materialExtension = mesh.materialExtension;
|
|
137
|
+
preparedTriangle.specularColor = mesh.specularColor;
|
|
138
|
+
preparedTriangle.baseColorAtlas = mesh.baseColorAtlas;
|
|
139
|
+
preparedTriangle.metallicRoughnessAtlas = mesh.metallicRoughnessAtlas;
|
|
140
|
+
preparedTriangle.normalAtlas = mesh.normalAtlas;
|
|
141
|
+
preparedTriangle.occlusionAtlas = mesh.occlusionAtlas;
|
|
142
|
+
preparedTriangle.emissiveAtlas = mesh.emissiveAtlas;
|
|
143
|
+
preparedTriangle.textureSettings = mesh.textureSettings;
|
|
144
|
+
preparedTriangle.materialExtension2 = vec4<f32>(0.0, 0.0, 1.3, 0.0);
|
|
145
|
+
preparedTriangle.materialExtension3 = vec4<f32>(100.0, 400.0, 0.0, 0.0);
|
|
146
|
+
preparedTriangle.clearcoatAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
147
|
+
preparedTriangle.clearcoatRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
148
|
+
preparedTriangle.clearcoatNormalAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
149
|
+
preparedTriangle.transmissionAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
150
|
+
preparedTriangle.thicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
151
|
+
preparedTriangle.sheenColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
152
|
+
preparedTriangle.sheenRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
153
|
+
preparedTriangle.specularAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
154
|
+
preparedTriangle.specularColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
155
|
+
preparedTriangle.iridescenceAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
156
|
+
preparedTriangle.iridescenceThicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
157
|
+
preparedTriangle.anisotropyAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
158
|
+
triangles[triangleIndex] = preparedTriangle;
|
|
146
159
|
|
|
147
160
|
let leafBase = config.triangleCount - 1u;
|
|
148
161
|
let nodeIndex = leafBase + triangleIndex;
|
|
@@ -224,36 +224,7 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
224
224
|
vec4<f32>(1.0, 1.0, 1.0, 1.0)
|
|
225
225
|
);
|
|
226
226
|
var candidate = no_candidate();
|
|
227
|
-
var hitTriangle = TriangleRecord(
|
|
228
|
-
0u,
|
|
229
|
-
0u,
|
|
230
|
-
0u,
|
|
231
|
-
0u,
|
|
232
|
-
0u,
|
|
233
|
-
0u,
|
|
234
|
-
0u,
|
|
235
|
-
0u,
|
|
236
|
-
vec4<f32>(0.0),
|
|
237
|
-
vec4<f32>(0.0),
|
|
238
|
-
vec4<f32>(0.0),
|
|
239
|
-
vec4<f32>(0.0, 1.0, 0.0, 0.0),
|
|
240
|
-
vec4<f32>(0.0, 1.0, 0.0, 0.0),
|
|
241
|
-
vec4<f32>(0.0, 1.0, 0.0, 0.0),
|
|
242
|
-
vec4<f32>(0.0),
|
|
243
|
-
vec4<f32>(0.0),
|
|
244
|
-
vec4<f32>(0.0),
|
|
245
|
-
vec4<f32>(0.0),
|
|
246
|
-
vec4<f32>(1.0, 0.0, 1.0, 1.0),
|
|
247
|
-
vec4<f32>(0.0, 0.0, 0.0, 0.08),
|
|
248
|
-
vec4<f32>(0.08, 1.0, 0.0, 0.0),
|
|
249
|
-
vec4<f32>(1.0, 1.0, 1.0, 1.0),
|
|
250
|
-
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
251
|
-
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
252
|
-
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
253
|
-
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
254
|
-
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
|
255
|
-
vec4<f32>(1.0, 1.0, 1.0, 0.0)
|
|
256
|
-
);
|
|
227
|
+
var hitTriangle = TriangleRecord();
|
|
257
228
|
|
|
258
229
|
for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
|
|
259
230
|
let object = sceneObjects[objectIndex];
|