@plasius/gpu-renderer 0.2.40 → 0.2.42

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/index.cjs CHANGED
@@ -2441,6 +2441,8 @@ function createWavefrontTransportGuardrailSummary(frameStats, options = {}) {
2441
2441
  0,
2442
2442
  Number(frameStats?.radianceDiagnostics?.legacyClampEquivalentSamples ?? 0)
2443
2443
  );
2444
+ const rayCountStatus = typeof frameStats?.rayCounts?.status === "string" ? frameStats.rayCounts.status : "not-requested";
2445
+ const rayCountReason = typeof frameStats?.rayCounts?.reason === "string" ? frameStats.rayCounts.reason : null;
2444
2446
  const memory = summarizeWavefrontMemory(frameStats?.memory);
2445
2447
  const checks = Object.freeze([
2446
2448
  createTransportGuardrailCheck(
@@ -2462,6 +2464,11 @@ function createWavefrontTransportGuardrailSummary(frameStats, options = {}) {
2462
2464
  "radiance-diagnostics",
2463
2465
  invalidSamples > 0 ? "fail" : legacyClampEquivalentSamples > 0 ? "warn" : "pass",
2464
2466
  invalidSamples > 0 ? `Renderer recorded ${invalidSamples} invalid radiance sample(s); inspect transport math before trusting the frame.` : legacyClampEquivalentSamples > 0 ? `${legacyClampEquivalentSamples} weighted sample(s) exceeded the legacy 4.0 preview clamp threshold; convergence stayed unbiased, but review firefly diagnostics before rollout.` : "No invalid radiance samples or legacy-clamp-equivalent fireflies were recorded."
2467
+ ),
2468
+ createTransportGuardrailCheck(
2469
+ "ray-count-telemetry",
2470
+ rayCountStatus === "failed" ? "fail" : rayCountStatus === "unavailable" ? "warn" : "pass",
2471
+ rayCountStatus === "failed" ? `Ray-count telemetry failed closed: ${rayCountReason ?? "unknown failure"}.` : rayCountStatus === "unavailable" ? `Ray-count telemetry was requested but unavailable: ${rayCountReason ?? "unknown reason"}.` : rayCountStatus === "available" ? "Primary, secondary, and total path-segment counts were captured." : "Ray-count telemetry was not requested for this frame."
2465
2472
  )
2466
2473
  ]);
2467
2474
  const status = checks.some((check) => check.status === "fail") ? "fail" : checks.some((check) => check.status === "warn") ? "warn" : "pass";
@@ -2482,6 +2489,7 @@ function createWavefrontTransportGuardrailSummary(frameStats, options = {}) {
2482
2489
  invalidSamples,
2483
2490
  legacyClampEquivalentSamples
2484
2491
  }),
2492
+ rayCountStatus,
2485
2493
  memory
2486
2494
  }),
2487
2495
  checks
@@ -2592,6 +2600,7 @@ var COUNTER_TRANSPORT_RESIDUAL_LUMINANCE_OFFSET = 22;
2592
2600
  var COUNTER_TRANSPORT_ZERO_TERMINATION_OFFSET = 23;
2593
2601
  var COUNTER_TRANSPORT_CHECKSUM_OFFSET = 24;
2594
2602
  var TRACE_STORAGE_BUFFER_BINDINGS = 10;
2603
+ var TRACE_SAMPLED_TEXTURE_BINDINGS = 21;
2595
2604
  var BRDF_LUT_UPLOAD_CACHE = /* @__PURE__ */ new Map();
2596
2605
  var MATERIAL_DIFFUSE = 0;
2597
2606
  var MATERIAL_METAL = 1;
@@ -2650,6 +2659,7 @@ var EMPTY_TERMINATION_METRICS = Object.freeze({
2650
2659
  var wavefrontPathTracingComputeLimits = Object.freeze({
2651
2660
  workgroupSize: WORKGROUP_SIZE,
2652
2661
  traceStorageBufferBindings: TRACE_STORAGE_BUFFER_BINDINGS,
2662
+ traceSampledTextureBindings: TRACE_SAMPLED_TEXTURE_BINDINGS,
2653
2663
  rayRecordBytes: RAY_RECORD_BYTES,
2654
2664
  hitRecordBytes: HIT_RECORD_BYTES,
2655
2665
  sceneObjectRecordBytes: SCENE_OBJECT_RECORD_BYTES,
@@ -5783,11 +5793,11 @@ function resolveCanvas3(canvasOrSelector, documentRef = globalThis.document) {
5783
5793
  return fallback;
5784
5794
  }
5785
5795
  async function getPipelineDiagnostics(shaderModule) {
5786
- if (typeof shaderModule?.compilationInfo !== "function") {
5796
+ if (typeof shaderModule?.getCompilationInfo !== "function") {
5787
5797
  return "";
5788
5798
  }
5789
5799
  try {
5790
- const info = await shaderModule.compilationInfo();
5800
+ const info = await shaderModule.getCompilationInfo();
5791
5801
  const messages = info.messages ?? [];
5792
5802
  if (messages.length === 0) {
5793
5803
  return "";
@@ -5825,10 +5835,10 @@ ${diagnostics}` : "";
5825
5835
  }
5826
5836
  }
5827
5837
  async function assertShaderModuleCompiles(shaderModule, label) {
5828
- if (typeof shaderModule?.compilationInfo !== "function") {
5838
+ if (typeof shaderModule?.getCompilationInfo !== "function") {
5829
5839
  return;
5830
5840
  }
5831
- const info = await shaderModule.compilationInfo();
5841
+ const info = await shaderModule.getCompilationInfo();
5832
5842
  const messages = Array.isArray(info?.messages) ? info.messages : [];
5833
5843
  const errors = messages.filter((message) => message?.type === "error");
5834
5844
  if (errors.length <= 0) {
@@ -5862,7 +5872,28 @@ function createWavefrontDeviceDescriptor(adapter, options = {}) {
5862
5872
  TRACE_STORAGE_BUFFER_BINDINGS
5863
5873
  );
5864
5874
  }
5875
+ const exposedSampledTextureLimit = Number(adapter?.limits?.maxSampledTexturesPerShaderStage);
5876
+ if (Number.isFinite(exposedSampledTextureLimit)) {
5877
+ if (exposedSampledTextureLimit < TRACE_SAMPLED_TEXTURE_BINDINGS) {
5878
+ throw new Error(
5879
+ `Wavefront material tracing requires maxSampledTexturesPerShaderStage>=${TRACE_SAMPLED_TEXTURE_BINDINGS}, but this adapter exposes ${exposedSampledTextureLimit}.`
5880
+ );
5881
+ }
5882
+ requiredLimits.maxSampledTexturesPerShaderStage = Math.max(
5883
+ Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
5884
+ TRACE_SAMPLED_TEXTURE_BINDINGS
5885
+ );
5886
+ }
5865
5887
  const descriptor = { ...options.deviceDescriptor ?? {} };
5888
+ const requiredFeatures = Array.from(descriptor.requiredFeatures ?? []);
5889
+ const adapterFeatures = adapter?.features;
5890
+ const timestampQuerySupported = typeof adapterFeatures?.has === "function" ? adapterFeatures.has("timestamp-query") : adapterFeatures && typeof adapterFeatures[Symbol.iterator] === "function" ? Array.from(adapterFeatures).includes("timestamp-query") : false;
5891
+ if (options.gpuTimestamps !== false && timestampQuerySupported && !requiredFeatures.includes("timestamp-query")) {
5892
+ requiredFeatures.push("timestamp-query");
5893
+ }
5894
+ if (requiredFeatures.length > 0) {
5895
+ descriptor.requiredFeatures = requiredFeatures;
5896
+ }
5866
5897
  if (Object.keys(requiredLimits).length > 0) {
5867
5898
  descriptor.requiredLimits = {
5868
5899
  ...descriptor.requiredLimits ?? {},
@@ -6052,6 +6083,376 @@ async function waitForSubmittedGpuWork(device, options = {}) {
6052
6083
  }
6053
6084
  }
6054
6085
 
6086
+ // src/wavefront-frame-telemetry.js
6087
+ var TIMESTAMP_QUERY_COUNT = 2;
6088
+ var TIMESTAMP_BUFFER_BYTES = TIMESTAMP_QUERY_COUNT * BigUint64Array.BYTES_PER_ELEMENT;
6089
+ function hasGpuFeature(features, name) {
6090
+ if (typeof features?.has === "function") {
6091
+ return features.has(name);
6092
+ }
6093
+ if (features && typeof features[Symbol.iterator] === "function") {
6094
+ return Array.from(features).includes(name);
6095
+ }
6096
+ return false;
6097
+ }
6098
+ function freezeRayCountTelemetry(values = {}) {
6099
+ return Object.freeze({
6100
+ status: values.status ?? "not-requested",
6101
+ source: values.source ?? null,
6102
+ expectedPrimaryRays: values.expectedPrimaryRays ?? null,
6103
+ observedPrimaryRays: values.observedPrimaryRays ?? null,
6104
+ secondaryRays: values.secondaryRays ?? null,
6105
+ totalPathSegments: values.totalPathSegments ?? null,
6106
+ bounceHistogram: Object.freeze([...values.bounceHistogram ?? []]),
6107
+ capturedRayCounts: values.capturedRayCounts ?? 0,
6108
+ expectedRayCounts: values.expectedRayCounts ?? 0,
6109
+ reason: values.reason ?? null
6110
+ });
6111
+ }
6112
+ function createWavefrontRayCountTelemetry(values = {}) {
6113
+ return freezeRayCountTelemetry(values);
6114
+ }
6115
+ function createWavefrontFrameTimingTelemetry(values = {}) {
6116
+ return Object.freeze({
6117
+ status: values.status ?? "not-requested",
6118
+ source: values.source ?? null,
6119
+ timestampQueryStatus: values.timestampQueryStatus ?? "not-recorded",
6120
+ totalGpuTimeMs: values.totalGpuTimeMs ?? null,
6121
+ totalRenderJobTimeMs: Math.max(0, Number(values.totalRenderJobTimeMs ?? 0)),
6122
+ classificationTimeMs: values.classificationTimeMs ?? null,
6123
+ compactionTimeMs: values.compactionTimeMs ?? null,
6124
+ samplingTimeMs: values.samplingTimeMs ?? null,
6125
+ reason: values.reason ?? null
6126
+ });
6127
+ }
6128
+ function safeErrorReason(error) {
6129
+ return error instanceof Error && error.message ? error.message : "unknown-telemetry-readback-failure";
6130
+ }
6131
+ function timestampDurationMs(values) {
6132
+ if (values.length < TIMESTAMP_QUERY_COUNT || values[1] < values[0]) {
6133
+ return null;
6134
+ }
6135
+ const durationNanoseconds = values[1] - values[0];
6136
+ const durationMs = Number(durationNanoseconds) / 1e6;
6137
+ return Number.isFinite(durationMs) && durationMs >= 0 ? durationMs : null;
6138
+ }
6139
+ function createWavefrontFrameTelemetryResources({
6140
+ device,
6141
+ constants,
6142
+ maxRayCountRecords
6143
+ }) {
6144
+ const rayRecordCapacity = Math.max(1, Math.trunc(Number(maxRayCountRecords) || 1));
6145
+ const rayBufferBytes = rayRecordCapacity * Uint32Array.BYTES_PER_ELEMENT;
6146
+ const mapMode = constants.map;
6147
+ if (!mapMode || typeof mapMode.READ !== "number") {
6148
+ return Object.freeze({
6149
+ available: false,
6150
+ reason: "gpu-map-read-unavailable",
6151
+ memoryBytes: 0,
6152
+ beginFrame() {
6153
+ },
6154
+ recordActiveRayCount() {
6155
+ },
6156
+ decorateFirstPass(descriptor) {
6157
+ return descriptor;
6158
+ },
6159
+ decorateFinalPass(descriptor) {
6160
+ return descriptor;
6161
+ },
6162
+ async readFrame({ expectedPrimaryRays = 0, expectedRayCounts = 0 } = {}) {
6163
+ return Object.freeze({
6164
+ rayCounts: freezeRayCountTelemetry({
6165
+ status: "unavailable",
6166
+ expectedPrimaryRays,
6167
+ expectedRayCounts,
6168
+ reason: "gpu-map-read-unavailable"
6169
+ }),
6170
+ timestampQueryStatus: "unsupported",
6171
+ totalGpuTimeMs: null,
6172
+ reason: "gpu-map-read-unavailable"
6173
+ });
6174
+ },
6175
+ destroy() {
6176
+ }
6177
+ });
6178
+ }
6179
+ const rayCountBuffer = createBuffer(
6180
+ device,
6181
+ constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
6182
+ rayBufferBytes,
6183
+ "plasius.wavefront.rayCounts"
6184
+ );
6185
+ const rayCountReadbackBuffer = createBuffer(
6186
+ device,
6187
+ constants.buffer.COPY_DST | constants.buffer.MAP_READ,
6188
+ rayBufferBytes,
6189
+ "plasius.wavefront.rayCounts.readback"
6190
+ );
6191
+ const timestampFeatureAvailable = hasGpuFeature(device?.features, "timestamp-query");
6192
+ const timestampApiAvailable = timestampFeatureAvailable && typeof device.createQuerySet === "function" && typeof constants.buffer.QUERY_RESOLVE === "number";
6193
+ let timestampQuerySet = null;
6194
+ let timestampResolveBuffer = null;
6195
+ let timestampReadbackBuffer = null;
6196
+ let timestampSetupReason = timestampFeatureAvailable ? "timestamp-query-api-unavailable" : "timestamp-query-unsupported";
6197
+ if (timestampApiAvailable) {
6198
+ try {
6199
+ timestampQuerySet = device.createQuerySet({
6200
+ label: "plasius.wavefront.timestamps",
6201
+ type: "timestamp",
6202
+ count: TIMESTAMP_QUERY_COUNT
6203
+ });
6204
+ timestampResolveBuffer = createBuffer(
6205
+ device,
6206
+ constants.buffer.QUERY_RESOLVE | constants.buffer.COPY_SRC,
6207
+ TIMESTAMP_BUFFER_BYTES,
6208
+ "plasius.wavefront.timestamps.resolve"
6209
+ );
6210
+ timestampReadbackBuffer = createBuffer(
6211
+ device,
6212
+ constants.buffer.COPY_DST | constants.buffer.MAP_READ,
6213
+ TIMESTAMP_BUFFER_BYTES,
6214
+ "plasius.wavefront.timestamps.readback"
6215
+ );
6216
+ timestampSetupReason = null;
6217
+ } catch (error) {
6218
+ timestampQuerySet?.destroy?.();
6219
+ timestampQuerySet = null;
6220
+ timestampResolveBuffer?.destroy?.();
6221
+ timestampResolveBuffer = null;
6222
+ timestampReadbackBuffer?.destroy?.();
6223
+ timestampReadbackBuffer = null;
6224
+ timestampSetupReason = `timestamp-query-setup-failed:${safeErrorReason(error)}`;
6225
+ }
6226
+ }
6227
+ const timestampUnavailableStatus = timestampSetupReason?.startsWith(
6228
+ "timestamp-query-setup-failed:"
6229
+ ) ? "failed" : "unsupported";
6230
+ let active = false;
6231
+ let recordCount = 0;
6232
+ let recordOverflow = false;
6233
+ let bounceIndices = [];
6234
+ let timestampStarted = false;
6235
+ let timestampEnded = false;
6236
+ let rayReadbackMapped = false;
6237
+ let timestampReadbackMapped = false;
6238
+ const memoryBytes = rayBufferBytes * 2 + (timestampQuerySet ? TIMESTAMP_BUFFER_BYTES * 2 : 0);
6239
+ function beginFrame() {
6240
+ active = true;
6241
+ recordCount = 0;
6242
+ recordOverflow = false;
6243
+ bounceIndices = [];
6244
+ timestampStarted = false;
6245
+ timestampEnded = false;
6246
+ }
6247
+ function recordActiveRayCount(encoder, counterBuffer, bounceIndex) {
6248
+ if (!active) {
6249
+ return;
6250
+ }
6251
+ if (recordCount >= rayRecordCapacity) {
6252
+ recordOverflow = true;
6253
+ return;
6254
+ }
6255
+ encoder.copyBufferToBuffer(
6256
+ counterBuffer,
6257
+ 0,
6258
+ rayCountBuffer,
6259
+ recordCount * Uint32Array.BYTES_PER_ELEMENT,
6260
+ Uint32Array.BYTES_PER_ELEMENT
6261
+ );
6262
+ bounceIndices.push(Math.max(0, Math.trunc(Number(bounceIndex) || 0)));
6263
+ recordCount += 1;
6264
+ }
6265
+ function decorateFirstPass(descriptor) {
6266
+ if (!active || !timestampQuerySet || timestampStarted) {
6267
+ return descriptor;
6268
+ }
6269
+ timestampStarted = true;
6270
+ return {
6271
+ ...descriptor,
6272
+ timestampWrites: {
6273
+ querySet: timestampQuerySet,
6274
+ beginningOfPassWriteIndex: 0
6275
+ }
6276
+ };
6277
+ }
6278
+ function decorateFinalPass(descriptor) {
6279
+ if (!active || !timestampQuerySet || !timestampStarted || timestampEnded) {
6280
+ return descriptor;
6281
+ }
6282
+ timestampEnded = true;
6283
+ return {
6284
+ ...descriptor,
6285
+ timestampWrites: {
6286
+ querySet: timestampQuerySet,
6287
+ endOfPassWriteIndex: 1
6288
+ }
6289
+ };
6290
+ }
6291
+ async function readFrame({
6292
+ expectedPrimaryRays = 0,
6293
+ expectedRayCounts = recordCount,
6294
+ waitForSubmittedGpuWork: waitForSubmittedGpuWork2
6295
+ } = {}) {
6296
+ active = false;
6297
+ if (recordOverflow || recordCount !== expectedRayCounts) {
6298
+ return Object.freeze({
6299
+ rayCounts: freezeRayCountTelemetry({
6300
+ status: "failed",
6301
+ expectedPrimaryRays,
6302
+ capturedRayCounts: recordCount,
6303
+ expectedRayCounts,
6304
+ reason: recordOverflow ? "ray-count-record-capacity-exceeded" : `ray-count-record-mismatch:${recordCount}/${expectedRayCounts}`
6305
+ }),
6306
+ timestampQueryStatus: timestampQuerySet ? "failed" : timestampUnavailableStatus,
6307
+ totalGpuTimeMs: null,
6308
+ reason: "ray-count-recording-incomplete"
6309
+ });
6310
+ }
6311
+ const encoder = device.createCommandEncoder({
6312
+ label: "plasius.wavefront.telemetry.readback"
6313
+ });
6314
+ const rayCopyBytes = Math.max(
6315
+ Uint32Array.BYTES_PER_ELEMENT,
6316
+ recordCount * Uint32Array.BYTES_PER_ELEMENT
6317
+ );
6318
+ encoder.copyBufferToBuffer(rayCountBuffer, 0, rayCountReadbackBuffer, 0, rayCopyBytes);
6319
+ const canResolveTimestamps = timestampQuerySet && timestampStarted && timestampEnded && typeof encoder.resolveQuerySet === "function";
6320
+ if (canResolveTimestamps) {
6321
+ encoder.resolveQuerySet(
6322
+ timestampQuerySet,
6323
+ 0,
6324
+ TIMESTAMP_QUERY_COUNT,
6325
+ timestampResolveBuffer,
6326
+ 0
6327
+ );
6328
+ encoder.copyBufferToBuffer(
6329
+ timestampResolveBuffer,
6330
+ 0,
6331
+ timestampReadbackBuffer,
6332
+ 0,
6333
+ TIMESTAMP_BUFFER_BYTES
6334
+ );
6335
+ }
6336
+ device.queue.submit([encoder.finish()]);
6337
+ try {
6338
+ await waitForSubmittedGpuWork2({
6339
+ timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
6340
+ allowTimeout: false
6341
+ });
6342
+ await rayCountReadbackBuffer.mapAsync(mapMode.READ);
6343
+ rayReadbackMapped = true;
6344
+ const rayCopy = rayCountReadbackBuffer.getMappedRange().slice(0, rayCopyBytes);
6345
+ const rayCounts = new Uint32Array(rayCopy, 0, recordCount);
6346
+ rayCountReadbackBuffer.unmap();
6347
+ rayReadbackMapped = false;
6348
+ let maximumBounceIndex = 0;
6349
+ for (const bounceIndex of bounceIndices) {
6350
+ maximumBounceIndex = Math.max(maximumBounceIndex, bounceIndex);
6351
+ }
6352
+ const bounceHistogram = Array.from({ length: maximumBounceIndex + 1 }, () => 0);
6353
+ let observedPrimaryRays = 0;
6354
+ let secondaryRays = 0;
6355
+ let totalPathSegments = 0;
6356
+ for (let index = 0; index < rayCounts.length; index += 1) {
6357
+ const count = Number(rayCounts[index] ?? 0);
6358
+ const bounceIndex = bounceIndices[index] ?? 0;
6359
+ bounceHistogram[bounceIndex] = (bounceHistogram[bounceIndex] ?? 0) + count;
6360
+ totalPathSegments += count;
6361
+ if (bounceIndex === 0) {
6362
+ observedPrimaryRays += count;
6363
+ } else {
6364
+ secondaryRays += count;
6365
+ }
6366
+ }
6367
+ const primaryCountsMatch = observedPrimaryRays === expectedPrimaryRays;
6368
+ const rayTelemetry = freezeRayCountTelemetry({
6369
+ status: primaryCountsMatch ? "available" : "failed",
6370
+ source: "gpu-active-queue-readback",
6371
+ expectedPrimaryRays,
6372
+ observedPrimaryRays,
6373
+ secondaryRays: primaryCountsMatch ? secondaryRays : null,
6374
+ totalPathSegments: primaryCountsMatch ? totalPathSegments : null,
6375
+ bounceHistogram,
6376
+ capturedRayCounts: recordCount,
6377
+ expectedRayCounts,
6378
+ reason: primaryCountsMatch ? null : `primary-ray-count-mismatch:${observedPrimaryRays}/${expectedPrimaryRays}`
6379
+ });
6380
+ let timestampQueryStatus = timestampQuerySet ? "failed" : timestampUnavailableStatus;
6381
+ let totalGpuTimeMs = null;
6382
+ let timestampReason = timestampSetupReason;
6383
+ if (canResolveTimestamps) {
6384
+ try {
6385
+ await timestampReadbackBuffer.mapAsync(mapMode.READ);
6386
+ timestampReadbackMapped = true;
6387
+ const timestampCopy = timestampReadbackBuffer.getMappedRange().slice(0, TIMESTAMP_BUFFER_BYTES);
6388
+ const timestamps = new BigUint64Array(timestampCopy);
6389
+ timestampReadbackBuffer.unmap();
6390
+ timestampReadbackMapped = false;
6391
+ totalGpuTimeMs = timestampDurationMs(timestamps);
6392
+ timestampQueryStatus = totalGpuTimeMs === null ? "failed" : "available";
6393
+ timestampReason = totalGpuTimeMs === null ? "timestamp-query-returned-invalid-range" : null;
6394
+ } catch (error) {
6395
+ if (timestampReadbackMapped) {
6396
+ timestampReadbackBuffer.unmap();
6397
+ timestampReadbackMapped = false;
6398
+ }
6399
+ timestampQueryStatus = "failed";
6400
+ timestampReason = `timestamp-query-readback-failed:${safeErrorReason(error)}`;
6401
+ }
6402
+ } else if (timestampQuerySet) {
6403
+ timestampReason = "timestamp-query-span-incomplete";
6404
+ }
6405
+ return Object.freeze({
6406
+ rayCounts: rayTelemetry,
6407
+ timestampQueryStatus,
6408
+ totalGpuTimeMs,
6409
+ reason: timestampReason
6410
+ });
6411
+ } catch (error) {
6412
+ if (rayReadbackMapped) {
6413
+ rayCountReadbackBuffer.unmap();
6414
+ rayReadbackMapped = false;
6415
+ }
6416
+ if (timestampReadbackMapped) {
6417
+ timestampReadbackBuffer?.unmap();
6418
+ timestampReadbackMapped = false;
6419
+ }
6420
+ const reason = safeErrorReason(error);
6421
+ return Object.freeze({
6422
+ rayCounts: freezeRayCountTelemetry({
6423
+ status: "failed",
6424
+ expectedPrimaryRays,
6425
+ capturedRayCounts: recordCount,
6426
+ expectedRayCounts,
6427
+ reason: `ray-count-readback-failed:${reason}`
6428
+ }),
6429
+ timestampQueryStatus: timestampQuerySet ? "failed" : timestampUnavailableStatus,
6430
+ totalGpuTimeMs: null,
6431
+ reason: `telemetry-readback-failed:${reason}`
6432
+ });
6433
+ }
6434
+ }
6435
+ function destroy() {
6436
+ active = false;
6437
+ rayCountBuffer.destroy?.();
6438
+ rayCountReadbackBuffer.destroy?.();
6439
+ timestampQuerySet?.destroy?.();
6440
+ timestampResolveBuffer?.destroy?.();
6441
+ timestampReadbackBuffer?.destroy?.();
6442
+ }
6443
+ return Object.freeze({
6444
+ available: true,
6445
+ reason: null,
6446
+ memoryBytes,
6447
+ beginFrame,
6448
+ recordActiveRayCount,
6449
+ decorateFirstPass,
6450
+ decorateFinalPass,
6451
+ readFrame,
6452
+ destroy
6453
+ });
6454
+ }
6455
+
6055
6456
  // src/wavefront-frame-stats.js
6056
6457
  function resolveWavefrontRenderedSamplesPerPixel({
6057
6458
  config,
@@ -6149,6 +6550,11 @@ function createWavefrontFrameStats({
6149
6550
  maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
6150
6551
  screenRays: config.width * config.height,
6151
6552
  primaryRays: config.width * config.height * renderedSamplesPerPixel,
6553
+ secondaryRays: null,
6554
+ totalPathSegments: null,
6555
+ rayCounts: createWavefrontRayCountTelemetry(),
6556
+ timings: createWavefrontFrameTimingTelemetry(),
6557
+ telemetryMemoryBytes: 0,
6152
6558
  sceneObjectCount: config.sceneObjectCount,
6153
6559
  triangleCount: config.triangleCount,
6154
6560
  emissiveTriangleCount: config.emissiveTriangleCount,
@@ -6191,7 +6597,8 @@ function createWavefrontFrameEncoder({
6191
6597
  denoiseDirectResolveBindGroup,
6192
6598
  presentPipeline,
6193
6599
  presentBindGroup,
6194
- context
6600
+ context,
6601
+ getFrameTelemetry = () => null
6195
6602
  }) {
6196
6603
  const resolveConfig = resolveGetter(getConfig);
6197
6604
  const resolveBindGroups = resolveGetter(getBindGroups);
@@ -6199,9 +6606,12 @@ function createWavefrontFrameEncoder({
6199
6606
  encodeTileSample(encoder, tile, configOffset, parallelism) {
6200
6607
  const config = resolveConfig();
6201
6608
  const bindGroups = resolveBindGroups();
6202
- const generatePass = encoder.beginComputePass({
6203
- label: "plasius.wavefront.generatePrimaryRaysPass"
6204
- });
6609
+ const frameTelemetry = getFrameTelemetry();
6610
+ const generatePass = encoder.beginComputePass(
6611
+ frameTelemetry?.decorateFirstPass({
6612
+ label: "plasius.wavefront.generatePrimaryRaysPass"
6613
+ }) ?? { label: "plasius.wavefront.generatePrimaryRaysPass" }
6614
+ );
6205
6615
  const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
6206
6616
  generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
6207
6617
  generatePass.setPipeline(pipelines.generatePrimaryRays);
@@ -6209,6 +6619,7 @@ function createWavefrontFrameEncoder({
6209
6619
  recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
6210
6620
  generatePass.end();
6211
6621
  for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
6622
+ frameTelemetry?.recordActiveRayCount(encoder, counterBuffer, bounceIndex);
6212
6623
  encoder.copyBufferToBuffer(
6213
6624
  counterBuffer,
6214
6625
  COUNTER_DISPATCH_ARGS_OFFSET,
@@ -6285,7 +6696,7 @@ function createWavefrontFrameEncoder({
6285
6696
  },
6286
6697
  encodePresent(encoder) {
6287
6698
  const texture = context.getCurrentTexture();
6288
- const passEncoder = encoder.beginRenderPass({
6699
+ const descriptor = {
6289
6700
  label: "plasius.wavefront.presentPass",
6290
6701
  colorAttachments: [
6291
6702
  {
@@ -6295,7 +6706,10 @@ function createWavefrontFrameEncoder({
6295
6706
  storeOp: "store"
6296
6707
  }
6297
6708
  ]
6298
- });
6709
+ };
6710
+ const passEncoder = encoder.beginRenderPass(
6711
+ getFrameTelemetry()?.decorateFinalPass(descriptor) ?? descriptor
6712
+ );
6299
6713
  passEncoder.setPipeline(presentPipeline);
6300
6714
  passEncoder.setBindGroup(0, presentBindGroup);
6301
6715
  passEncoder.draw(3);
@@ -6595,36 +7009,49 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
6595
7009
  let uv1 = select(vec2<f32>(0.0), vertex1.uv.xy, vertex1.uv.z > 0.5);
6596
7010
  let uv2 = select(vec2<f32>(0.0), vertex2.uv.xy, vertex2.uv.z > 0.5);
6597
7011
 
6598
- triangles[triangleIndex] = TriangleRecord(
6599
- triangleIndex,
6600
- mesh.meshId,
6601
- mesh.materialKind,
6602
- mesh.flags,
6603
- mesh.materialRefId,
6604
- mesh.mediumRefId,
6605
- mesh.materialSlot,
6606
- 0u,
6607
- vec4<f32>(vertex0.position.xyz, 0.0),
6608
- vec4<f32>(vertex1.position.xyz, 0.0),
6609
- vec4<f32>(vertex2.position.xyz, 0.0),
6610
- vec4<f32>(n0, 0.0),
6611
- vec4<f32>(n1, 0.0),
6612
- vec4<f32>(n2, 0.0),
6613
- vec4<f32>(uv0, uv1),
6614
- vec4<f32>(uv2, 0.0, 0.0),
6615
- mesh.color,
6616
- mesh.emission,
6617
- mesh.material,
6618
- mesh.materialResponse,
6619
- mesh.materialExtension,
6620
- mesh.specularColor,
6621
- mesh.baseColorAtlas,
6622
- mesh.metallicRoughnessAtlas,
6623
- mesh.normalAtlas,
6624
- mesh.occlusionAtlas,
6625
- mesh.emissiveAtlas,
6626
- mesh.textureSettings
6627
- );
7012
+ var preparedTriangle = TriangleRecord();
7013
+ preparedTriangle.triangleId = triangleIndex;
7014
+ preparedTriangle.meshId = mesh.meshId;
7015
+ preparedTriangle.materialKind = mesh.materialKind;
7016
+ preparedTriangle.flags = mesh.flags;
7017
+ preparedTriangle.materialRefId = mesh.materialRefId;
7018
+ preparedTriangle.mediumRefId = mesh.mediumRefId;
7019
+ preparedTriangle.materialSlot = mesh.materialSlot;
7020
+ preparedTriangle.v0 = vec4<f32>(vertex0.position.xyz, 0.0);
7021
+ preparedTriangle.v1 = vec4<f32>(vertex1.position.xyz, 0.0);
7022
+ preparedTriangle.v2 = vec4<f32>(vertex2.position.xyz, 0.0);
7023
+ preparedTriangle.n0 = vec4<f32>(n0, 0.0);
7024
+ preparedTriangle.n1 = vec4<f32>(n1, 0.0);
7025
+ preparedTriangle.n2 = vec4<f32>(n2, 0.0);
7026
+ preparedTriangle.uv0uv1 = vec4<f32>(uv0, uv1);
7027
+ preparedTriangle.uv2Pad = vec4<f32>(uv2, 0.0, 0.0);
7028
+ preparedTriangle.color = mesh.color;
7029
+ preparedTriangle.emission = mesh.emission;
7030
+ preparedTriangle.material = mesh.material;
7031
+ preparedTriangle.materialResponse = mesh.materialResponse;
7032
+ preparedTriangle.materialExtension = mesh.materialExtension;
7033
+ preparedTriangle.specularColor = mesh.specularColor;
7034
+ preparedTriangle.baseColorAtlas = mesh.baseColorAtlas;
7035
+ preparedTriangle.metallicRoughnessAtlas = mesh.metallicRoughnessAtlas;
7036
+ preparedTriangle.normalAtlas = mesh.normalAtlas;
7037
+ preparedTriangle.occlusionAtlas = mesh.occlusionAtlas;
7038
+ preparedTriangle.emissiveAtlas = mesh.emissiveAtlas;
7039
+ preparedTriangle.textureSettings = mesh.textureSettings;
7040
+ preparedTriangle.materialExtension2 = vec4<f32>(0.0, 0.0, 1.3, 0.0);
7041
+ preparedTriangle.materialExtension3 = vec4<f32>(100.0, 400.0, 0.0, 0.0);
7042
+ preparedTriangle.clearcoatAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7043
+ preparedTriangle.clearcoatRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7044
+ preparedTriangle.clearcoatNormalAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7045
+ preparedTriangle.transmissionAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7046
+ preparedTriangle.thicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7047
+ preparedTriangle.sheenColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7048
+ preparedTriangle.sheenRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7049
+ preparedTriangle.specularAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7050
+ preparedTriangle.specularColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7051
+ preparedTriangle.iridescenceAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7052
+ preparedTriangle.iridescenceThicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7053
+ preparedTriangle.anisotropyAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7054
+ triangles[triangleIndex] = preparedTriangle;
6628
7055
 
6629
7056
  let leafBase = config.triangleCount - 1u;
6630
7057
  let nodeIndex = leafBase + triangleIndex;
@@ -7294,36 +7721,7 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
7294
7721
  vec4<f32>(1.0, 1.0, 1.0, 1.0)
7295
7722
  );
7296
7723
  var candidate = no_candidate();
7297
- var hitTriangle = TriangleRecord(
7298
- 0u,
7299
- 0u,
7300
- 0u,
7301
- 0u,
7302
- 0u,
7303
- 0u,
7304
- 0u,
7305
- 0u,
7306
- vec4<f32>(0.0),
7307
- vec4<f32>(0.0),
7308
- vec4<f32>(0.0),
7309
- vec4<f32>(0.0, 1.0, 0.0, 0.0),
7310
- vec4<f32>(0.0, 1.0, 0.0, 0.0),
7311
- vec4<f32>(0.0, 1.0, 0.0, 0.0),
7312
- vec4<f32>(0.0),
7313
- vec4<f32>(0.0),
7314
- vec4<f32>(0.0),
7315
- vec4<f32>(0.0),
7316
- vec4<f32>(1.0, 0.0, 1.0, 1.0),
7317
- vec4<f32>(0.0, 0.0, 0.0, 0.08),
7318
- vec4<f32>(0.08, 1.0, 0.0, 0.0),
7319
- vec4<f32>(1.0, 1.0, 1.0, 1.0),
7320
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7321
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7322
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7323
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7324
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7325
- vec4<f32>(1.0, 1.0, 1.0, 0.0)
7326
- );
7724
+ var hitTriangle = TriangleRecord();
7327
7725
 
7328
7726
  for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
7329
7727
  let object = sceneObjects[objectIndex];
@@ -10198,7 +10596,19 @@ function createWavefrontBindGroupLayouts(device, constants) {
10198
10596
  { binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10199
10597
  { binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
10200
10598
  { binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10201
- { binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
10599
+ { binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10600
+ { binding: 33, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10601
+ { binding: 34, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10602
+ { binding: 35, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10603
+ { binding: 36, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10604
+ { binding: 37, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10605
+ { binding: 38, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10606
+ { binding: 39, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10607
+ { binding: 40, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10608
+ { binding: 41, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10609
+ { binding: 42, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10610
+ { binding: 43, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10611
+ { binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
10202
10612
  ]
10203
10613
  });
10204
10614
  const acceleration = device.createBindGroupLayout({
@@ -11186,6 +11596,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11186
11596
  outputView,
11187
11597
  sampler
11188
11598
  });
11599
+ let activeFrameTelemetry = null;
11600
+ let frameTelemetryResources = null;
11189
11601
  const frameEncoder = createWavefrontFrameEncoder({
11190
11602
  getConfig: () => config,
11191
11603
  getBindGroups: () => bindGroups,
@@ -11197,7 +11609,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11197
11609
  denoiseDirectResolveBindGroup,
11198
11610
  presentPipeline,
11199
11611
  presentBindGroup,
11200
- context
11612
+ context,
11613
+ getFrameTelemetry: () => activeFrameTelemetry
11201
11614
  });
11202
11615
  let frame = 0;
11203
11616
  let accelerationBuilt = !config.gpuAccelerationBuildRequired;
@@ -11210,6 +11623,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11210
11623
  createGpuParallelismCounters()
11211
11624
  );
11212
11625
  const waitForSubmittedGpuWork2 = (waitOptions = {}) => waitForSubmittedGpuWork(device, waitOptions);
11626
+ function ensureFrameTelemetryResources() {
11627
+ if (!frameTelemetryResources) {
11628
+ frameTelemetryResources = createWavefrontFrameTelemetryResources({
11629
+ device,
11630
+ constants,
11631
+ maxRayCountRecords: tiles.length * config.samplesPerPixel * config.maxDepth
11632
+ });
11633
+ }
11634
+ return frameTelemetryResources;
11635
+ }
11213
11636
  function resolveRenderedSamplesPerPixel(renderOptions = {}, awaitGPUCompletion = true) {
11214
11637
  return resolveWavefrontRenderedSamplesPerPixel({
11215
11638
  config,
@@ -11337,7 +11760,13 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11337
11760
  frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
11338
11761
  frameTimeMs,
11339
11762
  false
11340
- )
11763
+ ),
11764
+ timings: createWavefrontFrameTimingTelemetry({
11765
+ status: "not-requested",
11766
+ source: "cpu-submit",
11767
+ timestampQueryStatus: "not-recorded",
11768
+ totalRenderJobTimeMs: frameTimeMs
11769
+ })
11341
11770
  });
11342
11771
  }
11343
11772
  function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel, optionsForFrame = {}) {
@@ -11367,98 +11796,151 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11367
11796
  const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
11368
11797
  const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
11369
11798
  const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
11799
+ const telemetryRequested = renderOptions.readStats === true;
11800
+ const telemetryCanRecord = telemetryRequested && awaitGPUCompletion;
11801
+ const telemetryResourcesForFrame = telemetryCanRecord ? ensureFrameTelemetryResources() : null;
11802
+ if (telemetryResourcesForFrame?.available) {
11803
+ telemetryResourcesForFrame.beginFrame();
11804
+ activeFrameTelemetry = telemetryResourcesForFrame;
11805
+ }
11370
11806
  const frameStartTimeMs = nowMs3();
11371
11807
  let frameStats;
11372
- if (useThrottledHighSamplePath) {
11373
- frame += 1;
11374
- const frameIndex = frame + config.frameIndex;
11375
- const parallelismCounters = createGpuParallelismCounters();
11376
- const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
11377
- let frameSubmissionCount = 0;
11378
- let frameConfigSlot = 0;
11379
- if (accelerationBuildSubmitted) {
11380
- const accelerationWaitOptions = {
11381
- ...estimateSubmittedGpuWorkTiming(
11382
- { ...config, renderedSamplesPerPixel: 1 },
11383
- 1,
11384
- renderOptions.submittedWorkTimeoutMs,
11385
- { includeAccelerationBuild: true }
11386
- ),
11387
- allowTimeout: false
11388
- };
11389
- await waitForSubmittedGpuWork2(accelerationWaitOptions);
11390
- }
11391
- for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
11392
- const tileRangeDispatch = dispatchFrameAwaitingGpu(
11393
- frameIndex,
11394
- parallelismCounters,
11395
- samplingPlan.renderedSamplesPerPixel,
11396
- {
11397
- sampleRangeStart: 0,
11398
- sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
11399
- tileStartIndex: tileIndex,
11400
- tileEndIndex: tileIndex + 1,
11401
- startingSubmissionCount: frameSubmissionCount,
11402
- startingSlot: frameConfigSlot,
11403
- includeDenoise: tileIndex + 1 >= tiles.length,
11404
- includePresent: tileIndex + 1 >= tiles.length
11405
- }
11406
- );
11407
- frameSubmissionCount = tileRangeDispatch.submissionCount;
11408
- frameConfigSlot = tileRangeDispatch.slot;
11409
- const tileWaitOptions = {
11410
- ...estimateSubmittedGpuWorkTiming(
11411
- { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11412
- 1,
11413
- renderOptions.submittedWorkTimeoutMs,
11808
+ try {
11809
+ if (useThrottledHighSamplePath) {
11810
+ frame += 1;
11811
+ const frameIndex = frame + config.frameIndex;
11812
+ const parallelismCounters = createGpuParallelismCounters();
11813
+ const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
11814
+ let frameSubmissionCount = 0;
11815
+ let frameConfigSlot = 0;
11816
+ if (accelerationBuildSubmitted) {
11817
+ const accelerationWaitOptions = {
11818
+ ...estimateSubmittedGpuWorkTiming(
11819
+ { ...config, renderedSamplesPerPixel: 1 },
11820
+ 1,
11821
+ renderOptions.submittedWorkTimeoutMs,
11822
+ { includeAccelerationBuild: true }
11823
+ ),
11824
+ allowTimeout: false
11825
+ };
11826
+ await waitForSubmittedGpuWork2(accelerationWaitOptions);
11827
+ }
11828
+ for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
11829
+ const tileRangeDispatch = dispatchFrameAwaitingGpu(
11830
+ frameIndex,
11831
+ parallelismCounters,
11832
+ samplingPlan.renderedSamplesPerPixel,
11414
11833
  {
11415
- includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
11834
+ sampleRangeStart: 0,
11835
+ sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
11836
+ tileStartIndex: tileIndex,
11837
+ tileEndIndex: tileIndex + 1,
11838
+ startingSubmissionCount: frameSubmissionCount,
11839
+ startingSlot: frameConfigSlot,
11840
+ includeDenoise: tileIndex + 1 >= tiles.length,
11416
11841
  includePresent: tileIndex + 1 >= tiles.length
11417
11842
  }
11418
- ),
11843
+ );
11844
+ frameSubmissionCount = tileRangeDispatch.submissionCount;
11845
+ frameConfigSlot = tileRangeDispatch.slot;
11846
+ const tileWaitOptions = {
11847
+ ...estimateSubmittedGpuWorkTiming(
11848
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11849
+ 1,
11850
+ renderOptions.submittedWorkTimeoutMs,
11851
+ {
11852
+ includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
11853
+ includePresent: tileIndex + 1 >= tiles.length
11854
+ }
11855
+ ),
11856
+ allowTimeout: false
11857
+ };
11858
+ await waitForSubmittedGpuWork2(tileWaitOptions);
11859
+ }
11860
+ frameStats = createFrameStats({
11861
+ frameIndex,
11862
+ accelerationBuildSubmitted,
11863
+ frameSubmissionCount,
11864
+ parallelismCounters,
11865
+ renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
11866
+ targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
11867
+ frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
11868
+ budgetConstrained: samplingPlan.budgetConstrained
11869
+ });
11870
+ } else {
11871
+ const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
11872
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11873
+ tiles.length,
11874
+ renderOptions.submittedWorkTimeoutMs,
11875
+ { includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
11876
+ );
11877
+ const submissionWaitOptions = awaitGPUCompletion ? {
11878
+ timeoutMs: submittedWorkTiming.timeoutMs,
11879
+ maxWaitMs: submittedWorkTiming.maxWaitMs,
11419
11880
  allowTimeout: false
11881
+ } : {
11882
+ timeoutMs: submittedWorkTiming.timeoutMs,
11883
+ maxWaitMs: submittedWorkTiming.maxWaitMs
11420
11884
  };
11421
- await waitForSubmittedGpuWork2(tileWaitOptions);
11422
- }
11423
- frameStats = createFrameStats({
11424
- frameIndex,
11425
- accelerationBuildSubmitted,
11426
- frameSubmissionCount,
11427
- parallelismCounters,
11428
- renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
11429
- targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
11430
- frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
11431
- budgetConstrained: samplingPlan.budgetConstrained
11432
- });
11433
- } else {
11434
- const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
11435
- { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11436
- tiles.length,
11437
- renderOptions.submittedWorkTimeoutMs,
11438
- { includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
11439
- );
11440
- const submissionWaitOptions = awaitGPUCompletion ? {
11441
- timeoutMs: submittedWorkTiming.timeoutMs,
11442
- maxWaitMs: submittedWorkTiming.maxWaitMs,
11443
- allowTimeout: false
11444
- } : {
11445
- timeoutMs: submittedWorkTiming.timeoutMs,
11446
- maxWaitMs: submittedWorkTiming.maxWaitMs
11447
- };
11448
- frameStats = renderOnce(renderOptions, samplingPlan);
11449
- if (awaitGPUCompletion) {
11450
- await waitForSubmittedGpuWork2(submissionWaitOptions);
11885
+ frameStats = renderOnce(renderOptions, samplingPlan);
11886
+ if (awaitGPUCompletion) {
11887
+ await waitForSubmittedGpuWork2(submissionWaitOptions);
11888
+ }
11451
11889
  }
11890
+ } finally {
11891
+ activeFrameTelemetry = null;
11452
11892
  }
11453
11893
  const frameTimeMs = Math.max(0, nowMs3() - frameStartTimeMs);
11454
11894
  if (awaitGPUCompletion) {
11455
11895
  lastCompletedFrameTimeMs = frameTimeMs;
11456
11896
  lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
11457
11897
  }
11898
+ const expectedPrimaryRays = config.width * config.height * samplingPlan.renderedSamplesPerPixel;
11899
+ const expectedRayCounts = tiles.length * samplingPlan.renderedSamplesPerPixel * config.maxDepth;
11900
+ const telemetryReadback = telemetryCanRecord ? await telemetryResourcesForFrame.readFrame({
11901
+ expectedPrimaryRays,
11902
+ expectedRayCounts,
11903
+ waitForSubmittedGpuWork: waitForSubmittedGpuWork2
11904
+ }) : null;
11905
+ const rayCounts = !telemetryRequested ? createWavefrontRayCountTelemetry() : !awaitGPUCompletion ? createWavefrontRayCountTelemetry({
11906
+ status: "unavailable",
11907
+ expectedPrimaryRays,
11908
+ expectedRayCounts,
11909
+ reason: "gpu-work-not-awaited"
11910
+ }) : telemetryReadback.rayCounts;
11911
+ const timings = !telemetryRequested ? createWavefrontFrameTimingTelemetry({
11912
+ status: "not-requested",
11913
+ source: awaitGPUCompletion ? "queue-completion" : "cpu-submit",
11914
+ timestampQueryStatus: "not-recorded",
11915
+ totalRenderJobTimeMs: frameTimeMs
11916
+ }) : !awaitGPUCompletion ? createWavefrontFrameTimingTelemetry({
11917
+ status: "unavailable",
11918
+ source: "cpu-submit",
11919
+ timestampQueryStatus: "not-recorded",
11920
+ totalRenderJobTimeMs: frameTimeMs,
11921
+ reason: "gpu-work-not-awaited"
11922
+ }) : telemetryReadback.timestampQueryStatus === "available" ? createWavefrontFrameTimingTelemetry({
11923
+ status: "available",
11924
+ source: "timestamp-query",
11925
+ timestampQueryStatus: "available",
11926
+ totalGpuTimeMs: telemetryReadback.totalGpuTimeMs,
11927
+ totalRenderJobTimeMs: frameTimeMs
11928
+ }) : createWavefrontFrameTimingTelemetry({
11929
+ status: "fallback",
11930
+ source: "queue-completion",
11931
+ timestampQueryStatus: telemetryReadback.timestampQueryStatus,
11932
+ totalRenderJobTimeMs: frameTimeMs,
11933
+ reason: telemetryReadback.reason
11934
+ });
11458
11935
  const deviceLossStatus = awaitGPUCompletion ? "not-detected" : typeof device.lost?.then === "function" ? "pending" : "not-exposed";
11459
11936
  frameStats = Object.freeze({
11460
11937
  ...frameStats,
11461
11938
  deviceLossStatus,
11939
+ secondaryRays: rayCounts.status === "available" ? rayCounts.secondaryRays : null,
11940
+ totalPathSegments: rayCounts.status === "available" ? rayCounts.totalPathSegments : null,
11941
+ rayCounts,
11942
+ timings,
11943
+ telemetryMemoryBytes: telemetryResourcesForFrame?.memoryBytes ?? 0,
11462
11944
  gpuWorkerJobs: createGpuWorkerJobDiagnostics(
11463
11945
  frameStats.gpuParallelism,
11464
11946
  frameStats.commandSubmissions,
@@ -11567,6 +12049,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11567
12049
  });
11568
12050
  }
11569
12051
  function destroy() {
12052
+ frameTelemetryResources?.destroy?.();
11570
12053
  activeQueue.destroy?.();
11571
12054
  nextQueue.destroy?.();
11572
12055
  hitBuffer.destroy?.();