@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/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,372 @@ 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
+ const bounceHistogram = Array.from({ length: Math.max(0, ...bounceIndices) + 1 }, () => 0);
6349
+ let observedPrimaryRays = 0;
6350
+ let secondaryRays = 0;
6351
+ let totalPathSegments = 0;
6352
+ for (let index = 0; index < rayCounts.length; index += 1) {
6353
+ const count = Number(rayCounts[index] ?? 0);
6354
+ const bounceIndex = bounceIndices[index] ?? 0;
6355
+ bounceHistogram[bounceIndex] = (bounceHistogram[bounceIndex] ?? 0) + count;
6356
+ totalPathSegments += count;
6357
+ if (bounceIndex === 0) {
6358
+ observedPrimaryRays += count;
6359
+ } else {
6360
+ secondaryRays += count;
6361
+ }
6362
+ }
6363
+ const primaryCountsMatch = observedPrimaryRays === expectedPrimaryRays;
6364
+ const rayTelemetry = freezeRayCountTelemetry({
6365
+ status: primaryCountsMatch ? "available" : "failed",
6366
+ source: "gpu-active-queue-readback",
6367
+ expectedPrimaryRays,
6368
+ observedPrimaryRays,
6369
+ secondaryRays: primaryCountsMatch ? secondaryRays : null,
6370
+ totalPathSegments: primaryCountsMatch ? totalPathSegments : null,
6371
+ bounceHistogram,
6372
+ capturedRayCounts: recordCount,
6373
+ expectedRayCounts,
6374
+ reason: primaryCountsMatch ? null : `primary-ray-count-mismatch:${observedPrimaryRays}/${expectedPrimaryRays}`
6375
+ });
6376
+ let timestampQueryStatus = timestampQuerySet ? "failed" : timestampUnavailableStatus;
6377
+ let totalGpuTimeMs = null;
6378
+ let timestampReason = timestampSetupReason;
6379
+ if (canResolveTimestamps) {
6380
+ try {
6381
+ await timestampReadbackBuffer.mapAsync(mapMode.READ);
6382
+ timestampReadbackMapped = true;
6383
+ const timestampCopy = timestampReadbackBuffer.getMappedRange().slice(0, TIMESTAMP_BUFFER_BYTES);
6384
+ const timestamps = new BigUint64Array(timestampCopy);
6385
+ timestampReadbackBuffer.unmap();
6386
+ timestampReadbackMapped = false;
6387
+ totalGpuTimeMs = timestampDurationMs(timestamps);
6388
+ timestampQueryStatus = totalGpuTimeMs === null ? "failed" : "available";
6389
+ timestampReason = totalGpuTimeMs === null ? "timestamp-query-returned-invalid-range" : null;
6390
+ } catch (error) {
6391
+ if (timestampReadbackMapped) {
6392
+ timestampReadbackBuffer.unmap();
6393
+ timestampReadbackMapped = false;
6394
+ }
6395
+ timestampQueryStatus = "failed";
6396
+ timestampReason = `timestamp-query-readback-failed:${safeErrorReason(error)}`;
6397
+ }
6398
+ } else if (timestampQuerySet) {
6399
+ timestampReason = "timestamp-query-span-incomplete";
6400
+ }
6401
+ return Object.freeze({
6402
+ rayCounts: rayTelemetry,
6403
+ timestampQueryStatus,
6404
+ totalGpuTimeMs,
6405
+ reason: timestampReason
6406
+ });
6407
+ } catch (error) {
6408
+ if (rayReadbackMapped) {
6409
+ rayCountReadbackBuffer.unmap();
6410
+ rayReadbackMapped = false;
6411
+ }
6412
+ if (timestampReadbackMapped) {
6413
+ timestampReadbackBuffer?.unmap();
6414
+ timestampReadbackMapped = false;
6415
+ }
6416
+ const reason = safeErrorReason(error);
6417
+ return Object.freeze({
6418
+ rayCounts: freezeRayCountTelemetry({
6419
+ status: "failed",
6420
+ expectedPrimaryRays,
6421
+ capturedRayCounts: recordCount,
6422
+ expectedRayCounts,
6423
+ reason: `ray-count-readback-failed:${reason}`
6424
+ }),
6425
+ timestampQueryStatus: timestampQuerySet ? "failed" : timestampUnavailableStatus,
6426
+ totalGpuTimeMs: null,
6427
+ reason: `telemetry-readback-failed:${reason}`
6428
+ });
6429
+ }
6430
+ }
6431
+ function destroy() {
6432
+ active = false;
6433
+ rayCountBuffer.destroy?.();
6434
+ rayCountReadbackBuffer.destroy?.();
6435
+ timestampQuerySet?.destroy?.();
6436
+ timestampResolveBuffer?.destroy?.();
6437
+ timestampReadbackBuffer?.destroy?.();
6438
+ }
6439
+ return Object.freeze({
6440
+ available: true,
6441
+ reason: null,
6442
+ memoryBytes,
6443
+ beginFrame,
6444
+ recordActiveRayCount,
6445
+ decorateFirstPass,
6446
+ decorateFinalPass,
6447
+ readFrame,
6448
+ destroy
6449
+ });
6450
+ }
6451
+
6055
6452
  // src/wavefront-frame-stats.js
6056
6453
  function resolveWavefrontRenderedSamplesPerPixel({
6057
6454
  config,
@@ -6149,6 +6546,11 @@ function createWavefrontFrameStats({
6149
6546
  maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
6150
6547
  screenRays: config.width * config.height,
6151
6548
  primaryRays: config.width * config.height * renderedSamplesPerPixel,
6549
+ secondaryRays: null,
6550
+ totalPathSegments: null,
6551
+ rayCounts: createWavefrontRayCountTelemetry(),
6552
+ timings: createWavefrontFrameTimingTelemetry(),
6553
+ telemetryMemoryBytes: 0,
6152
6554
  sceneObjectCount: config.sceneObjectCount,
6153
6555
  triangleCount: config.triangleCount,
6154
6556
  emissiveTriangleCount: config.emissiveTriangleCount,
@@ -6191,7 +6593,8 @@ function createWavefrontFrameEncoder({
6191
6593
  denoiseDirectResolveBindGroup,
6192
6594
  presentPipeline,
6193
6595
  presentBindGroup,
6194
- context
6596
+ context,
6597
+ getFrameTelemetry = () => null
6195
6598
  }) {
6196
6599
  const resolveConfig = resolveGetter(getConfig);
6197
6600
  const resolveBindGroups = resolveGetter(getBindGroups);
@@ -6199,9 +6602,12 @@ function createWavefrontFrameEncoder({
6199
6602
  encodeTileSample(encoder, tile, configOffset, parallelism) {
6200
6603
  const config = resolveConfig();
6201
6604
  const bindGroups = resolveBindGroups();
6202
- const generatePass = encoder.beginComputePass({
6203
- label: "plasius.wavefront.generatePrimaryRaysPass"
6204
- });
6605
+ const frameTelemetry = getFrameTelemetry();
6606
+ const generatePass = encoder.beginComputePass(
6607
+ frameTelemetry?.decorateFirstPass({
6608
+ label: "plasius.wavefront.generatePrimaryRaysPass"
6609
+ }) ?? { label: "plasius.wavefront.generatePrimaryRaysPass" }
6610
+ );
6205
6611
  const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
6206
6612
  generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
6207
6613
  generatePass.setPipeline(pipelines.generatePrimaryRays);
@@ -6209,6 +6615,7 @@ function createWavefrontFrameEncoder({
6209
6615
  recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
6210
6616
  generatePass.end();
6211
6617
  for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
6618
+ frameTelemetry?.recordActiveRayCount(encoder, counterBuffer, bounceIndex);
6212
6619
  encoder.copyBufferToBuffer(
6213
6620
  counterBuffer,
6214
6621
  COUNTER_DISPATCH_ARGS_OFFSET,
@@ -6285,7 +6692,7 @@ function createWavefrontFrameEncoder({
6285
6692
  },
6286
6693
  encodePresent(encoder) {
6287
6694
  const texture = context.getCurrentTexture();
6288
- const passEncoder = encoder.beginRenderPass({
6695
+ const descriptor = {
6289
6696
  label: "plasius.wavefront.presentPass",
6290
6697
  colorAttachments: [
6291
6698
  {
@@ -6295,7 +6702,10 @@ function createWavefrontFrameEncoder({
6295
6702
  storeOp: "store"
6296
6703
  }
6297
6704
  ]
6298
- });
6705
+ };
6706
+ const passEncoder = encoder.beginRenderPass(
6707
+ getFrameTelemetry()?.decorateFinalPass(descriptor) ?? descriptor
6708
+ );
6299
6709
  passEncoder.setPipeline(presentPipeline);
6300
6710
  passEncoder.setBindGroup(0, presentBindGroup);
6301
6711
  passEncoder.draw(3);
@@ -6595,36 +7005,49 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
6595
7005
  let uv1 = select(vec2<f32>(0.0), vertex1.uv.xy, vertex1.uv.z > 0.5);
6596
7006
  let uv2 = select(vec2<f32>(0.0), vertex2.uv.xy, vertex2.uv.z > 0.5);
6597
7007
 
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
- );
7008
+ var preparedTriangle = TriangleRecord();
7009
+ preparedTriangle.triangleId = triangleIndex;
7010
+ preparedTriangle.meshId = mesh.meshId;
7011
+ preparedTriangle.materialKind = mesh.materialKind;
7012
+ preparedTriangle.flags = mesh.flags;
7013
+ preparedTriangle.materialRefId = mesh.materialRefId;
7014
+ preparedTriangle.mediumRefId = mesh.mediumRefId;
7015
+ preparedTriangle.materialSlot = mesh.materialSlot;
7016
+ preparedTriangle.v0 = vec4<f32>(vertex0.position.xyz, 0.0);
7017
+ preparedTriangle.v1 = vec4<f32>(vertex1.position.xyz, 0.0);
7018
+ preparedTriangle.v2 = vec4<f32>(vertex2.position.xyz, 0.0);
7019
+ preparedTriangle.n0 = vec4<f32>(n0, 0.0);
7020
+ preparedTriangle.n1 = vec4<f32>(n1, 0.0);
7021
+ preparedTriangle.n2 = vec4<f32>(n2, 0.0);
7022
+ preparedTriangle.uv0uv1 = vec4<f32>(uv0, uv1);
7023
+ preparedTriangle.uv2Pad = vec4<f32>(uv2, 0.0, 0.0);
7024
+ preparedTriangle.color = mesh.color;
7025
+ preparedTriangle.emission = mesh.emission;
7026
+ preparedTriangle.material = mesh.material;
7027
+ preparedTriangle.materialResponse = mesh.materialResponse;
7028
+ preparedTriangle.materialExtension = mesh.materialExtension;
7029
+ preparedTriangle.specularColor = mesh.specularColor;
7030
+ preparedTriangle.baseColorAtlas = mesh.baseColorAtlas;
7031
+ preparedTriangle.metallicRoughnessAtlas = mesh.metallicRoughnessAtlas;
7032
+ preparedTriangle.normalAtlas = mesh.normalAtlas;
7033
+ preparedTriangle.occlusionAtlas = mesh.occlusionAtlas;
7034
+ preparedTriangle.emissiveAtlas = mesh.emissiveAtlas;
7035
+ preparedTriangle.textureSettings = mesh.textureSettings;
7036
+ preparedTriangle.materialExtension2 = vec4<f32>(0.0, 0.0, 1.3, 0.0);
7037
+ preparedTriangle.materialExtension3 = vec4<f32>(100.0, 400.0, 0.0, 0.0);
7038
+ preparedTriangle.clearcoatAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7039
+ preparedTriangle.clearcoatRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7040
+ preparedTriangle.clearcoatNormalAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7041
+ preparedTriangle.transmissionAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7042
+ preparedTriangle.thicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7043
+ preparedTriangle.sheenColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7044
+ preparedTriangle.sheenRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7045
+ preparedTriangle.specularAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7046
+ preparedTriangle.specularColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7047
+ preparedTriangle.iridescenceAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7048
+ preparedTriangle.iridescenceThicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7049
+ preparedTriangle.anisotropyAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
7050
+ triangles[triangleIndex] = preparedTriangle;
6628
7051
 
6629
7052
  let leafBase = config.triangleCount - 1u;
6630
7053
  let nodeIndex = leafBase + triangleIndex;
@@ -7294,36 +7717,7 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
7294
7717
  vec4<f32>(1.0, 1.0, 1.0, 1.0)
7295
7718
  );
7296
7719
  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
- );
7720
+ var hitTriangle = TriangleRecord();
7327
7721
 
7328
7722
  for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
7329
7723
  let object = sceneObjects[objectIndex];
@@ -10198,7 +10592,19 @@ function createWavefrontBindGroupLayouts(device, constants) {
10198
10592
  { binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10199
10593
  { binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
10200
10594
  { binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10201
- { binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
10595
+ { binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10596
+ { binding: 33, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10597
+ { binding: 34, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10598
+ { binding: 35, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10599
+ { binding: 36, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10600
+ { binding: 37, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10601
+ { binding: 38, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10602
+ { binding: 39, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10603
+ { binding: 40, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10604
+ { binding: 41, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10605
+ { binding: 42, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10606
+ { binding: 43, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10607
+ { binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
10202
10608
  ]
10203
10609
  });
10204
10610
  const acceleration = device.createBindGroupLayout({
@@ -11186,6 +11592,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11186
11592
  outputView,
11187
11593
  sampler
11188
11594
  });
11595
+ let activeFrameTelemetry = null;
11596
+ let frameTelemetryResources = null;
11189
11597
  const frameEncoder = createWavefrontFrameEncoder({
11190
11598
  getConfig: () => config,
11191
11599
  getBindGroups: () => bindGroups,
@@ -11197,7 +11605,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11197
11605
  denoiseDirectResolveBindGroup,
11198
11606
  presentPipeline,
11199
11607
  presentBindGroup,
11200
- context
11608
+ context,
11609
+ getFrameTelemetry: () => activeFrameTelemetry
11201
11610
  });
11202
11611
  let frame = 0;
11203
11612
  let accelerationBuilt = !config.gpuAccelerationBuildRequired;
@@ -11210,6 +11619,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11210
11619
  createGpuParallelismCounters()
11211
11620
  );
11212
11621
  const waitForSubmittedGpuWork2 = (waitOptions = {}) => waitForSubmittedGpuWork(device, waitOptions);
11622
+ function ensureFrameTelemetryResources() {
11623
+ if (!frameTelemetryResources) {
11624
+ frameTelemetryResources = createWavefrontFrameTelemetryResources({
11625
+ device,
11626
+ constants,
11627
+ maxRayCountRecords: tiles.length * config.samplesPerPixel * config.maxDepth
11628
+ });
11629
+ }
11630
+ return frameTelemetryResources;
11631
+ }
11213
11632
  function resolveRenderedSamplesPerPixel(renderOptions = {}, awaitGPUCompletion = true) {
11214
11633
  return resolveWavefrontRenderedSamplesPerPixel({
11215
11634
  config,
@@ -11337,7 +11756,13 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11337
11756
  frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
11338
11757
  frameTimeMs,
11339
11758
  false
11340
- )
11759
+ ),
11760
+ timings: createWavefrontFrameTimingTelemetry({
11761
+ status: "not-requested",
11762
+ source: "cpu-submit",
11763
+ timestampQueryStatus: "not-recorded",
11764
+ totalRenderJobTimeMs: frameTimeMs
11765
+ })
11341
11766
  });
11342
11767
  }
11343
11768
  function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel, optionsForFrame = {}) {
@@ -11367,98 +11792,151 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11367
11792
  const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
11368
11793
  const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
11369
11794
  const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
11795
+ const telemetryRequested = renderOptions.readStats === true;
11796
+ const telemetryCanRecord = telemetryRequested && awaitGPUCompletion;
11797
+ const telemetryResourcesForFrame = telemetryCanRecord ? ensureFrameTelemetryResources() : null;
11798
+ if (telemetryResourcesForFrame?.available) {
11799
+ telemetryResourcesForFrame.beginFrame();
11800
+ activeFrameTelemetry = telemetryResourcesForFrame;
11801
+ }
11370
11802
  const frameStartTimeMs = nowMs3();
11371
11803
  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,
11804
+ try {
11805
+ if (useThrottledHighSamplePath) {
11806
+ frame += 1;
11807
+ const frameIndex = frame + config.frameIndex;
11808
+ const parallelismCounters = createGpuParallelismCounters();
11809
+ const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
11810
+ let frameSubmissionCount = 0;
11811
+ let frameConfigSlot = 0;
11812
+ if (accelerationBuildSubmitted) {
11813
+ const accelerationWaitOptions = {
11814
+ ...estimateSubmittedGpuWorkTiming(
11815
+ { ...config, renderedSamplesPerPixel: 1 },
11816
+ 1,
11817
+ renderOptions.submittedWorkTimeoutMs,
11818
+ { includeAccelerationBuild: true }
11819
+ ),
11820
+ allowTimeout: false
11821
+ };
11822
+ await waitForSubmittedGpuWork2(accelerationWaitOptions);
11823
+ }
11824
+ for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
11825
+ const tileRangeDispatch = dispatchFrameAwaitingGpu(
11826
+ frameIndex,
11827
+ parallelismCounters,
11828
+ samplingPlan.renderedSamplesPerPixel,
11414
11829
  {
11415
- includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
11830
+ sampleRangeStart: 0,
11831
+ sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
11832
+ tileStartIndex: tileIndex,
11833
+ tileEndIndex: tileIndex + 1,
11834
+ startingSubmissionCount: frameSubmissionCount,
11835
+ startingSlot: frameConfigSlot,
11836
+ includeDenoise: tileIndex + 1 >= tiles.length,
11416
11837
  includePresent: tileIndex + 1 >= tiles.length
11417
11838
  }
11418
- ),
11839
+ );
11840
+ frameSubmissionCount = tileRangeDispatch.submissionCount;
11841
+ frameConfigSlot = tileRangeDispatch.slot;
11842
+ const tileWaitOptions = {
11843
+ ...estimateSubmittedGpuWorkTiming(
11844
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11845
+ 1,
11846
+ renderOptions.submittedWorkTimeoutMs,
11847
+ {
11848
+ includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
11849
+ includePresent: tileIndex + 1 >= tiles.length
11850
+ }
11851
+ ),
11852
+ allowTimeout: false
11853
+ };
11854
+ await waitForSubmittedGpuWork2(tileWaitOptions);
11855
+ }
11856
+ frameStats = createFrameStats({
11857
+ frameIndex,
11858
+ accelerationBuildSubmitted,
11859
+ frameSubmissionCount,
11860
+ parallelismCounters,
11861
+ renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
11862
+ targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
11863
+ frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
11864
+ budgetConstrained: samplingPlan.budgetConstrained
11865
+ });
11866
+ } else {
11867
+ const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
11868
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11869
+ tiles.length,
11870
+ renderOptions.submittedWorkTimeoutMs,
11871
+ { includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
11872
+ );
11873
+ const submissionWaitOptions = awaitGPUCompletion ? {
11874
+ timeoutMs: submittedWorkTiming.timeoutMs,
11875
+ maxWaitMs: submittedWorkTiming.maxWaitMs,
11419
11876
  allowTimeout: false
11877
+ } : {
11878
+ timeoutMs: submittedWorkTiming.timeoutMs,
11879
+ maxWaitMs: submittedWorkTiming.maxWaitMs
11420
11880
  };
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);
11881
+ frameStats = renderOnce(renderOptions, samplingPlan);
11882
+ if (awaitGPUCompletion) {
11883
+ await waitForSubmittedGpuWork2(submissionWaitOptions);
11884
+ }
11451
11885
  }
11886
+ } finally {
11887
+ activeFrameTelemetry = null;
11452
11888
  }
11453
11889
  const frameTimeMs = Math.max(0, nowMs3() - frameStartTimeMs);
11454
11890
  if (awaitGPUCompletion) {
11455
11891
  lastCompletedFrameTimeMs = frameTimeMs;
11456
11892
  lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
11457
11893
  }
11894
+ const expectedPrimaryRays = config.width * config.height * samplingPlan.renderedSamplesPerPixel;
11895
+ const expectedRayCounts = tiles.length * samplingPlan.renderedSamplesPerPixel * config.maxDepth;
11896
+ const telemetryReadback = telemetryCanRecord ? await telemetryResourcesForFrame.readFrame({
11897
+ expectedPrimaryRays,
11898
+ expectedRayCounts,
11899
+ waitForSubmittedGpuWork: waitForSubmittedGpuWork2
11900
+ }) : null;
11901
+ const rayCounts = !telemetryRequested ? createWavefrontRayCountTelemetry() : !awaitGPUCompletion ? createWavefrontRayCountTelemetry({
11902
+ status: "unavailable",
11903
+ expectedPrimaryRays,
11904
+ expectedRayCounts,
11905
+ reason: "gpu-work-not-awaited"
11906
+ }) : telemetryReadback.rayCounts;
11907
+ const timings = !telemetryRequested ? createWavefrontFrameTimingTelemetry({
11908
+ status: "not-requested",
11909
+ source: awaitGPUCompletion ? "queue-completion" : "cpu-submit",
11910
+ timestampQueryStatus: "not-recorded",
11911
+ totalRenderJobTimeMs: frameTimeMs
11912
+ }) : !awaitGPUCompletion ? createWavefrontFrameTimingTelemetry({
11913
+ status: "unavailable",
11914
+ source: "cpu-submit",
11915
+ timestampQueryStatus: "not-recorded",
11916
+ totalRenderJobTimeMs: frameTimeMs,
11917
+ reason: "gpu-work-not-awaited"
11918
+ }) : telemetryReadback.timestampQueryStatus === "available" ? createWavefrontFrameTimingTelemetry({
11919
+ status: "available",
11920
+ source: "timestamp-query",
11921
+ timestampQueryStatus: "available",
11922
+ totalGpuTimeMs: telemetryReadback.totalGpuTimeMs,
11923
+ totalRenderJobTimeMs: frameTimeMs
11924
+ }) : createWavefrontFrameTimingTelemetry({
11925
+ status: "fallback",
11926
+ source: "queue-completion",
11927
+ timestampQueryStatus: telemetryReadback.timestampQueryStatus,
11928
+ totalRenderJobTimeMs: frameTimeMs,
11929
+ reason: telemetryReadback.reason
11930
+ });
11458
11931
  const deviceLossStatus = awaitGPUCompletion ? "not-detected" : typeof device.lost?.then === "function" ? "pending" : "not-exposed";
11459
11932
  frameStats = Object.freeze({
11460
11933
  ...frameStats,
11461
11934
  deviceLossStatus,
11935
+ secondaryRays: rayCounts.status === "available" ? rayCounts.secondaryRays : null,
11936
+ totalPathSegments: rayCounts.status === "available" ? rayCounts.totalPathSegments : null,
11937
+ rayCounts,
11938
+ timings,
11939
+ telemetryMemoryBytes: telemetryResourcesForFrame?.memoryBytes ?? 0,
11462
11940
  gpuWorkerJobs: createGpuWorkerJobDiagnostics(
11463
11941
  frameStats.gpuParallelism,
11464
11942
  frameStats.commandSubmissions,
@@ -11567,6 +12045,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11567
12045
  });
11568
12046
  }
11569
12047
  function destroy() {
12048
+ frameTelemetryResources?.destroy?.();
11570
12049
  activeQueue.destroy?.();
11571
12050
  nextQueue.destroy?.();
11572
12051
  hitBuffer.destroy?.();