@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.js CHANGED
@@ -2352,6 +2352,8 @@ function createWavefrontTransportGuardrailSummary(frameStats, options = {}) {
2352
2352
  0,
2353
2353
  Number(frameStats?.radianceDiagnostics?.legacyClampEquivalentSamples ?? 0)
2354
2354
  );
2355
+ const rayCountStatus = typeof frameStats?.rayCounts?.status === "string" ? frameStats.rayCounts.status : "not-requested";
2356
+ const rayCountReason = typeof frameStats?.rayCounts?.reason === "string" ? frameStats.rayCounts.reason : null;
2355
2357
  const memory = summarizeWavefrontMemory(frameStats?.memory);
2356
2358
  const checks = Object.freeze([
2357
2359
  createTransportGuardrailCheck(
@@ -2373,6 +2375,11 @@ function createWavefrontTransportGuardrailSummary(frameStats, options = {}) {
2373
2375
  "radiance-diagnostics",
2374
2376
  invalidSamples > 0 ? "fail" : legacyClampEquivalentSamples > 0 ? "warn" : "pass",
2375
2377
  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."
2378
+ ),
2379
+ createTransportGuardrailCheck(
2380
+ "ray-count-telemetry",
2381
+ rayCountStatus === "failed" ? "fail" : rayCountStatus === "unavailable" ? "warn" : "pass",
2382
+ 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."
2376
2383
  )
2377
2384
  ]);
2378
2385
  const status = checks.some((check) => check.status === "fail") ? "fail" : checks.some((check) => check.status === "warn") ? "warn" : "pass";
@@ -2393,6 +2400,7 @@ function createWavefrontTransportGuardrailSummary(frameStats, options = {}) {
2393
2400
  invalidSamples,
2394
2401
  legacyClampEquivalentSamples
2395
2402
  }),
2403
+ rayCountStatus,
2396
2404
  memory
2397
2405
  }),
2398
2406
  checks
@@ -2503,6 +2511,7 @@ var COUNTER_TRANSPORT_RESIDUAL_LUMINANCE_OFFSET = 22;
2503
2511
  var COUNTER_TRANSPORT_ZERO_TERMINATION_OFFSET = 23;
2504
2512
  var COUNTER_TRANSPORT_CHECKSUM_OFFSET = 24;
2505
2513
  var TRACE_STORAGE_BUFFER_BINDINGS = 10;
2514
+ var TRACE_SAMPLED_TEXTURE_BINDINGS = 21;
2506
2515
  var BRDF_LUT_UPLOAD_CACHE = /* @__PURE__ */ new Map();
2507
2516
  var MATERIAL_DIFFUSE = 0;
2508
2517
  var MATERIAL_METAL = 1;
@@ -2561,6 +2570,7 @@ var EMPTY_TERMINATION_METRICS = Object.freeze({
2561
2570
  var wavefrontPathTracingComputeLimits = Object.freeze({
2562
2571
  workgroupSize: WORKGROUP_SIZE,
2563
2572
  traceStorageBufferBindings: TRACE_STORAGE_BUFFER_BINDINGS,
2573
+ traceSampledTextureBindings: TRACE_SAMPLED_TEXTURE_BINDINGS,
2564
2574
  rayRecordBytes: RAY_RECORD_BYTES,
2565
2575
  hitRecordBytes: HIT_RECORD_BYTES,
2566
2576
  sceneObjectRecordBytes: SCENE_OBJECT_RECORD_BYTES,
@@ -5694,11 +5704,11 @@ function resolveCanvas3(canvasOrSelector, documentRef = globalThis.document) {
5694
5704
  return fallback;
5695
5705
  }
5696
5706
  async function getPipelineDiagnostics(shaderModule) {
5697
- if (typeof shaderModule?.compilationInfo !== "function") {
5707
+ if (typeof shaderModule?.getCompilationInfo !== "function") {
5698
5708
  return "";
5699
5709
  }
5700
5710
  try {
5701
- const info = await shaderModule.compilationInfo();
5711
+ const info = await shaderModule.getCompilationInfo();
5702
5712
  const messages = info.messages ?? [];
5703
5713
  if (messages.length === 0) {
5704
5714
  return "";
@@ -5736,10 +5746,10 @@ ${diagnostics}` : "";
5736
5746
  }
5737
5747
  }
5738
5748
  async function assertShaderModuleCompiles(shaderModule, label) {
5739
- if (typeof shaderModule?.compilationInfo !== "function") {
5749
+ if (typeof shaderModule?.getCompilationInfo !== "function") {
5740
5750
  return;
5741
5751
  }
5742
- const info = await shaderModule.compilationInfo();
5752
+ const info = await shaderModule.getCompilationInfo();
5743
5753
  const messages = Array.isArray(info?.messages) ? info.messages : [];
5744
5754
  const errors = messages.filter((message) => message?.type === "error");
5745
5755
  if (errors.length <= 0) {
@@ -5773,7 +5783,28 @@ function createWavefrontDeviceDescriptor(adapter, options = {}) {
5773
5783
  TRACE_STORAGE_BUFFER_BINDINGS
5774
5784
  );
5775
5785
  }
5786
+ const exposedSampledTextureLimit = Number(adapter?.limits?.maxSampledTexturesPerShaderStage);
5787
+ if (Number.isFinite(exposedSampledTextureLimit)) {
5788
+ if (exposedSampledTextureLimit < TRACE_SAMPLED_TEXTURE_BINDINGS) {
5789
+ throw new Error(
5790
+ `Wavefront material tracing requires maxSampledTexturesPerShaderStage>=${TRACE_SAMPLED_TEXTURE_BINDINGS}, but this adapter exposes ${exposedSampledTextureLimit}.`
5791
+ );
5792
+ }
5793
+ requiredLimits.maxSampledTexturesPerShaderStage = Math.max(
5794
+ Number(requiredLimits.maxSampledTexturesPerShaderStage ?? 0),
5795
+ TRACE_SAMPLED_TEXTURE_BINDINGS
5796
+ );
5797
+ }
5776
5798
  const descriptor = { ...options.deviceDescriptor ?? {} };
5799
+ const requiredFeatures = Array.from(descriptor.requiredFeatures ?? []);
5800
+ const adapterFeatures = adapter?.features;
5801
+ const timestampQuerySupported = typeof adapterFeatures?.has === "function" ? adapterFeatures.has("timestamp-query") : adapterFeatures && typeof adapterFeatures[Symbol.iterator] === "function" ? Array.from(adapterFeatures).includes("timestamp-query") : false;
5802
+ if (options.gpuTimestamps !== false && timestampQuerySupported && !requiredFeatures.includes("timestamp-query")) {
5803
+ requiredFeatures.push("timestamp-query");
5804
+ }
5805
+ if (requiredFeatures.length > 0) {
5806
+ descriptor.requiredFeatures = requiredFeatures;
5807
+ }
5777
5808
  if (Object.keys(requiredLimits).length > 0) {
5778
5809
  descriptor.requiredLimits = {
5779
5810
  ...descriptor.requiredLimits ?? {},
@@ -5963,6 +5994,372 @@ async function waitForSubmittedGpuWork(device, options = {}) {
5963
5994
  }
5964
5995
  }
5965
5996
 
5997
+ // src/wavefront-frame-telemetry.js
5998
+ var TIMESTAMP_QUERY_COUNT = 2;
5999
+ var TIMESTAMP_BUFFER_BYTES = TIMESTAMP_QUERY_COUNT * BigUint64Array.BYTES_PER_ELEMENT;
6000
+ function hasGpuFeature(features, name) {
6001
+ if (typeof features?.has === "function") {
6002
+ return features.has(name);
6003
+ }
6004
+ if (features && typeof features[Symbol.iterator] === "function") {
6005
+ return Array.from(features).includes(name);
6006
+ }
6007
+ return false;
6008
+ }
6009
+ function freezeRayCountTelemetry(values = {}) {
6010
+ return Object.freeze({
6011
+ status: values.status ?? "not-requested",
6012
+ source: values.source ?? null,
6013
+ expectedPrimaryRays: values.expectedPrimaryRays ?? null,
6014
+ observedPrimaryRays: values.observedPrimaryRays ?? null,
6015
+ secondaryRays: values.secondaryRays ?? null,
6016
+ totalPathSegments: values.totalPathSegments ?? null,
6017
+ bounceHistogram: Object.freeze([...values.bounceHistogram ?? []]),
6018
+ capturedRayCounts: values.capturedRayCounts ?? 0,
6019
+ expectedRayCounts: values.expectedRayCounts ?? 0,
6020
+ reason: values.reason ?? null
6021
+ });
6022
+ }
6023
+ function createWavefrontRayCountTelemetry(values = {}) {
6024
+ return freezeRayCountTelemetry(values);
6025
+ }
6026
+ function createWavefrontFrameTimingTelemetry(values = {}) {
6027
+ return Object.freeze({
6028
+ status: values.status ?? "not-requested",
6029
+ source: values.source ?? null,
6030
+ timestampQueryStatus: values.timestampQueryStatus ?? "not-recorded",
6031
+ totalGpuTimeMs: values.totalGpuTimeMs ?? null,
6032
+ totalRenderJobTimeMs: Math.max(0, Number(values.totalRenderJobTimeMs ?? 0)),
6033
+ classificationTimeMs: values.classificationTimeMs ?? null,
6034
+ compactionTimeMs: values.compactionTimeMs ?? null,
6035
+ samplingTimeMs: values.samplingTimeMs ?? null,
6036
+ reason: values.reason ?? null
6037
+ });
6038
+ }
6039
+ function safeErrorReason(error) {
6040
+ return error instanceof Error && error.message ? error.message : "unknown-telemetry-readback-failure";
6041
+ }
6042
+ function timestampDurationMs(values) {
6043
+ if (values.length < TIMESTAMP_QUERY_COUNT || values[1] < values[0]) {
6044
+ return null;
6045
+ }
6046
+ const durationNanoseconds = values[1] - values[0];
6047
+ const durationMs = Number(durationNanoseconds) / 1e6;
6048
+ return Number.isFinite(durationMs) && durationMs >= 0 ? durationMs : null;
6049
+ }
6050
+ function createWavefrontFrameTelemetryResources({
6051
+ device,
6052
+ constants,
6053
+ maxRayCountRecords
6054
+ }) {
6055
+ const rayRecordCapacity = Math.max(1, Math.trunc(Number(maxRayCountRecords) || 1));
6056
+ const rayBufferBytes = rayRecordCapacity * Uint32Array.BYTES_PER_ELEMENT;
6057
+ const mapMode = constants.map;
6058
+ if (!mapMode || typeof mapMode.READ !== "number") {
6059
+ return Object.freeze({
6060
+ available: false,
6061
+ reason: "gpu-map-read-unavailable",
6062
+ memoryBytes: 0,
6063
+ beginFrame() {
6064
+ },
6065
+ recordActiveRayCount() {
6066
+ },
6067
+ decorateFirstPass(descriptor) {
6068
+ return descriptor;
6069
+ },
6070
+ decorateFinalPass(descriptor) {
6071
+ return descriptor;
6072
+ },
6073
+ async readFrame({ expectedPrimaryRays = 0, expectedRayCounts = 0 } = {}) {
6074
+ return Object.freeze({
6075
+ rayCounts: freezeRayCountTelemetry({
6076
+ status: "unavailable",
6077
+ expectedPrimaryRays,
6078
+ expectedRayCounts,
6079
+ reason: "gpu-map-read-unavailable"
6080
+ }),
6081
+ timestampQueryStatus: "unsupported",
6082
+ totalGpuTimeMs: null,
6083
+ reason: "gpu-map-read-unavailable"
6084
+ });
6085
+ },
6086
+ destroy() {
6087
+ }
6088
+ });
6089
+ }
6090
+ const rayCountBuffer = createBuffer(
6091
+ device,
6092
+ constants.buffer.COPY_DST | constants.buffer.COPY_SRC,
6093
+ rayBufferBytes,
6094
+ "plasius.wavefront.rayCounts"
6095
+ );
6096
+ const rayCountReadbackBuffer = createBuffer(
6097
+ device,
6098
+ constants.buffer.COPY_DST | constants.buffer.MAP_READ,
6099
+ rayBufferBytes,
6100
+ "plasius.wavefront.rayCounts.readback"
6101
+ );
6102
+ const timestampFeatureAvailable = hasGpuFeature(device?.features, "timestamp-query");
6103
+ const timestampApiAvailable = timestampFeatureAvailable && typeof device.createQuerySet === "function" && typeof constants.buffer.QUERY_RESOLVE === "number";
6104
+ let timestampQuerySet = null;
6105
+ let timestampResolveBuffer = null;
6106
+ let timestampReadbackBuffer = null;
6107
+ let timestampSetupReason = timestampFeatureAvailable ? "timestamp-query-api-unavailable" : "timestamp-query-unsupported";
6108
+ if (timestampApiAvailable) {
6109
+ try {
6110
+ timestampQuerySet = device.createQuerySet({
6111
+ label: "plasius.wavefront.timestamps",
6112
+ type: "timestamp",
6113
+ count: TIMESTAMP_QUERY_COUNT
6114
+ });
6115
+ timestampResolveBuffer = createBuffer(
6116
+ device,
6117
+ constants.buffer.QUERY_RESOLVE | constants.buffer.COPY_SRC,
6118
+ TIMESTAMP_BUFFER_BYTES,
6119
+ "plasius.wavefront.timestamps.resolve"
6120
+ );
6121
+ timestampReadbackBuffer = createBuffer(
6122
+ device,
6123
+ constants.buffer.COPY_DST | constants.buffer.MAP_READ,
6124
+ TIMESTAMP_BUFFER_BYTES,
6125
+ "plasius.wavefront.timestamps.readback"
6126
+ );
6127
+ timestampSetupReason = null;
6128
+ } catch (error) {
6129
+ timestampQuerySet?.destroy?.();
6130
+ timestampQuerySet = null;
6131
+ timestampResolveBuffer?.destroy?.();
6132
+ timestampResolveBuffer = null;
6133
+ timestampReadbackBuffer?.destroy?.();
6134
+ timestampReadbackBuffer = null;
6135
+ timestampSetupReason = `timestamp-query-setup-failed:${safeErrorReason(error)}`;
6136
+ }
6137
+ }
6138
+ const timestampUnavailableStatus = timestampSetupReason?.startsWith(
6139
+ "timestamp-query-setup-failed:"
6140
+ ) ? "failed" : "unsupported";
6141
+ let active = false;
6142
+ let recordCount = 0;
6143
+ let recordOverflow = false;
6144
+ let bounceIndices = [];
6145
+ let timestampStarted = false;
6146
+ let timestampEnded = false;
6147
+ let rayReadbackMapped = false;
6148
+ let timestampReadbackMapped = false;
6149
+ const memoryBytes = rayBufferBytes * 2 + (timestampQuerySet ? TIMESTAMP_BUFFER_BYTES * 2 : 0);
6150
+ function beginFrame() {
6151
+ active = true;
6152
+ recordCount = 0;
6153
+ recordOverflow = false;
6154
+ bounceIndices = [];
6155
+ timestampStarted = false;
6156
+ timestampEnded = false;
6157
+ }
6158
+ function recordActiveRayCount(encoder, counterBuffer, bounceIndex) {
6159
+ if (!active) {
6160
+ return;
6161
+ }
6162
+ if (recordCount >= rayRecordCapacity) {
6163
+ recordOverflow = true;
6164
+ return;
6165
+ }
6166
+ encoder.copyBufferToBuffer(
6167
+ counterBuffer,
6168
+ 0,
6169
+ rayCountBuffer,
6170
+ recordCount * Uint32Array.BYTES_PER_ELEMENT,
6171
+ Uint32Array.BYTES_PER_ELEMENT
6172
+ );
6173
+ bounceIndices.push(Math.max(0, Math.trunc(Number(bounceIndex) || 0)));
6174
+ recordCount += 1;
6175
+ }
6176
+ function decorateFirstPass(descriptor) {
6177
+ if (!active || !timestampQuerySet || timestampStarted) {
6178
+ return descriptor;
6179
+ }
6180
+ timestampStarted = true;
6181
+ return {
6182
+ ...descriptor,
6183
+ timestampWrites: {
6184
+ querySet: timestampQuerySet,
6185
+ beginningOfPassWriteIndex: 0
6186
+ }
6187
+ };
6188
+ }
6189
+ function decorateFinalPass(descriptor) {
6190
+ if (!active || !timestampQuerySet || !timestampStarted || timestampEnded) {
6191
+ return descriptor;
6192
+ }
6193
+ timestampEnded = true;
6194
+ return {
6195
+ ...descriptor,
6196
+ timestampWrites: {
6197
+ querySet: timestampQuerySet,
6198
+ endOfPassWriteIndex: 1
6199
+ }
6200
+ };
6201
+ }
6202
+ async function readFrame({
6203
+ expectedPrimaryRays = 0,
6204
+ expectedRayCounts = recordCount,
6205
+ waitForSubmittedGpuWork: waitForSubmittedGpuWork2
6206
+ } = {}) {
6207
+ active = false;
6208
+ if (recordOverflow || recordCount !== expectedRayCounts) {
6209
+ return Object.freeze({
6210
+ rayCounts: freezeRayCountTelemetry({
6211
+ status: "failed",
6212
+ expectedPrimaryRays,
6213
+ capturedRayCounts: recordCount,
6214
+ expectedRayCounts,
6215
+ reason: recordOverflow ? "ray-count-record-capacity-exceeded" : `ray-count-record-mismatch:${recordCount}/${expectedRayCounts}`
6216
+ }),
6217
+ timestampQueryStatus: timestampQuerySet ? "failed" : timestampUnavailableStatus,
6218
+ totalGpuTimeMs: null,
6219
+ reason: "ray-count-recording-incomplete"
6220
+ });
6221
+ }
6222
+ const encoder = device.createCommandEncoder({
6223
+ label: "plasius.wavefront.telemetry.readback"
6224
+ });
6225
+ const rayCopyBytes = Math.max(
6226
+ Uint32Array.BYTES_PER_ELEMENT,
6227
+ recordCount * Uint32Array.BYTES_PER_ELEMENT
6228
+ );
6229
+ encoder.copyBufferToBuffer(rayCountBuffer, 0, rayCountReadbackBuffer, 0, rayCopyBytes);
6230
+ const canResolveTimestamps = timestampQuerySet && timestampStarted && timestampEnded && typeof encoder.resolveQuerySet === "function";
6231
+ if (canResolveTimestamps) {
6232
+ encoder.resolveQuerySet(
6233
+ timestampQuerySet,
6234
+ 0,
6235
+ TIMESTAMP_QUERY_COUNT,
6236
+ timestampResolveBuffer,
6237
+ 0
6238
+ );
6239
+ encoder.copyBufferToBuffer(
6240
+ timestampResolveBuffer,
6241
+ 0,
6242
+ timestampReadbackBuffer,
6243
+ 0,
6244
+ TIMESTAMP_BUFFER_BYTES
6245
+ );
6246
+ }
6247
+ device.queue.submit([encoder.finish()]);
6248
+ try {
6249
+ await waitForSubmittedGpuWork2({
6250
+ timeoutMs: GPU_READBACK_COMPLETION_TIMEOUT_MS,
6251
+ allowTimeout: false
6252
+ });
6253
+ await rayCountReadbackBuffer.mapAsync(mapMode.READ);
6254
+ rayReadbackMapped = true;
6255
+ const rayCopy = rayCountReadbackBuffer.getMappedRange().slice(0, rayCopyBytes);
6256
+ const rayCounts = new Uint32Array(rayCopy, 0, recordCount);
6257
+ rayCountReadbackBuffer.unmap();
6258
+ rayReadbackMapped = false;
6259
+ const bounceHistogram = Array.from({ length: Math.max(0, ...bounceIndices) + 1 }, () => 0);
6260
+ let observedPrimaryRays = 0;
6261
+ let secondaryRays = 0;
6262
+ let totalPathSegments = 0;
6263
+ for (let index = 0; index < rayCounts.length; index += 1) {
6264
+ const count = Number(rayCounts[index] ?? 0);
6265
+ const bounceIndex = bounceIndices[index] ?? 0;
6266
+ bounceHistogram[bounceIndex] = (bounceHistogram[bounceIndex] ?? 0) + count;
6267
+ totalPathSegments += count;
6268
+ if (bounceIndex === 0) {
6269
+ observedPrimaryRays += count;
6270
+ } else {
6271
+ secondaryRays += count;
6272
+ }
6273
+ }
6274
+ const primaryCountsMatch = observedPrimaryRays === expectedPrimaryRays;
6275
+ const rayTelemetry = freezeRayCountTelemetry({
6276
+ status: primaryCountsMatch ? "available" : "failed",
6277
+ source: "gpu-active-queue-readback",
6278
+ expectedPrimaryRays,
6279
+ observedPrimaryRays,
6280
+ secondaryRays: primaryCountsMatch ? secondaryRays : null,
6281
+ totalPathSegments: primaryCountsMatch ? totalPathSegments : null,
6282
+ bounceHistogram,
6283
+ capturedRayCounts: recordCount,
6284
+ expectedRayCounts,
6285
+ reason: primaryCountsMatch ? null : `primary-ray-count-mismatch:${observedPrimaryRays}/${expectedPrimaryRays}`
6286
+ });
6287
+ let timestampQueryStatus = timestampQuerySet ? "failed" : timestampUnavailableStatus;
6288
+ let totalGpuTimeMs = null;
6289
+ let timestampReason = timestampSetupReason;
6290
+ if (canResolveTimestamps) {
6291
+ try {
6292
+ await timestampReadbackBuffer.mapAsync(mapMode.READ);
6293
+ timestampReadbackMapped = true;
6294
+ const timestampCopy = timestampReadbackBuffer.getMappedRange().slice(0, TIMESTAMP_BUFFER_BYTES);
6295
+ const timestamps = new BigUint64Array(timestampCopy);
6296
+ timestampReadbackBuffer.unmap();
6297
+ timestampReadbackMapped = false;
6298
+ totalGpuTimeMs = timestampDurationMs(timestamps);
6299
+ timestampQueryStatus = totalGpuTimeMs === null ? "failed" : "available";
6300
+ timestampReason = totalGpuTimeMs === null ? "timestamp-query-returned-invalid-range" : null;
6301
+ } catch (error) {
6302
+ if (timestampReadbackMapped) {
6303
+ timestampReadbackBuffer.unmap();
6304
+ timestampReadbackMapped = false;
6305
+ }
6306
+ timestampQueryStatus = "failed";
6307
+ timestampReason = `timestamp-query-readback-failed:${safeErrorReason(error)}`;
6308
+ }
6309
+ } else if (timestampQuerySet) {
6310
+ timestampReason = "timestamp-query-span-incomplete";
6311
+ }
6312
+ return Object.freeze({
6313
+ rayCounts: rayTelemetry,
6314
+ timestampQueryStatus,
6315
+ totalGpuTimeMs,
6316
+ reason: timestampReason
6317
+ });
6318
+ } catch (error) {
6319
+ if (rayReadbackMapped) {
6320
+ rayCountReadbackBuffer.unmap();
6321
+ rayReadbackMapped = false;
6322
+ }
6323
+ if (timestampReadbackMapped) {
6324
+ timestampReadbackBuffer?.unmap();
6325
+ timestampReadbackMapped = false;
6326
+ }
6327
+ const reason = safeErrorReason(error);
6328
+ return Object.freeze({
6329
+ rayCounts: freezeRayCountTelemetry({
6330
+ status: "failed",
6331
+ expectedPrimaryRays,
6332
+ capturedRayCounts: recordCount,
6333
+ expectedRayCounts,
6334
+ reason: `ray-count-readback-failed:${reason}`
6335
+ }),
6336
+ timestampQueryStatus: timestampQuerySet ? "failed" : timestampUnavailableStatus,
6337
+ totalGpuTimeMs: null,
6338
+ reason: `telemetry-readback-failed:${reason}`
6339
+ });
6340
+ }
6341
+ }
6342
+ function destroy() {
6343
+ active = false;
6344
+ rayCountBuffer.destroy?.();
6345
+ rayCountReadbackBuffer.destroy?.();
6346
+ timestampQuerySet?.destroy?.();
6347
+ timestampResolveBuffer?.destroy?.();
6348
+ timestampReadbackBuffer?.destroy?.();
6349
+ }
6350
+ return Object.freeze({
6351
+ available: true,
6352
+ reason: null,
6353
+ memoryBytes,
6354
+ beginFrame,
6355
+ recordActiveRayCount,
6356
+ decorateFirstPass,
6357
+ decorateFinalPass,
6358
+ readFrame,
6359
+ destroy
6360
+ });
6361
+ }
6362
+
5966
6363
  // src/wavefront-frame-stats.js
5967
6364
  function resolveWavefrontRenderedSamplesPerPixel({
5968
6365
  config,
@@ -6060,6 +6457,11 @@ function createWavefrontFrameStats({
6060
6457
  maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
6061
6458
  screenRays: config.width * config.height,
6062
6459
  primaryRays: config.width * config.height * renderedSamplesPerPixel,
6460
+ secondaryRays: null,
6461
+ totalPathSegments: null,
6462
+ rayCounts: createWavefrontRayCountTelemetry(),
6463
+ timings: createWavefrontFrameTimingTelemetry(),
6464
+ telemetryMemoryBytes: 0,
6063
6465
  sceneObjectCount: config.sceneObjectCount,
6064
6466
  triangleCount: config.triangleCount,
6065
6467
  emissiveTriangleCount: config.emissiveTriangleCount,
@@ -6102,7 +6504,8 @@ function createWavefrontFrameEncoder({
6102
6504
  denoiseDirectResolveBindGroup,
6103
6505
  presentPipeline,
6104
6506
  presentBindGroup,
6105
- context
6507
+ context,
6508
+ getFrameTelemetry = () => null
6106
6509
  }) {
6107
6510
  const resolveConfig = resolveGetter(getConfig);
6108
6511
  const resolveBindGroups = resolveGetter(getBindGroups);
@@ -6110,9 +6513,12 @@ function createWavefrontFrameEncoder({
6110
6513
  encodeTileSample(encoder, tile, configOffset, parallelism) {
6111
6514
  const config = resolveConfig();
6112
6515
  const bindGroups = resolveBindGroups();
6113
- const generatePass = encoder.beginComputePass({
6114
- label: "plasius.wavefront.generatePrimaryRaysPass"
6115
- });
6516
+ const frameTelemetry = getFrameTelemetry();
6517
+ const generatePass = encoder.beginComputePass(
6518
+ frameTelemetry?.decorateFirstPass({
6519
+ label: "plasius.wavefront.generatePrimaryRaysPass"
6520
+ }) ?? { label: "plasius.wavefront.generatePrimaryRaysPass" }
6521
+ );
6116
6522
  const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
6117
6523
  generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
6118
6524
  generatePass.setPipeline(pipelines.generatePrimaryRays);
@@ -6120,6 +6526,7 @@ function createWavefrontFrameEncoder({
6120
6526
  recordDirectDispatch(parallelism, [tileWorkgroups], WORKGROUP_SIZE);
6121
6527
  generatePass.end();
6122
6528
  for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
6529
+ frameTelemetry?.recordActiveRayCount(encoder, counterBuffer, bounceIndex);
6123
6530
  encoder.copyBufferToBuffer(
6124
6531
  counterBuffer,
6125
6532
  COUNTER_DISPATCH_ARGS_OFFSET,
@@ -6196,7 +6603,7 @@ function createWavefrontFrameEncoder({
6196
6603
  },
6197
6604
  encodePresent(encoder) {
6198
6605
  const texture = context.getCurrentTexture();
6199
- const passEncoder = encoder.beginRenderPass({
6606
+ const descriptor = {
6200
6607
  label: "plasius.wavefront.presentPass",
6201
6608
  colorAttachments: [
6202
6609
  {
@@ -6206,7 +6613,10 @@ function createWavefrontFrameEncoder({
6206
6613
  storeOp: "store"
6207
6614
  }
6208
6615
  ]
6209
- });
6616
+ };
6617
+ const passEncoder = encoder.beginRenderPass(
6618
+ getFrameTelemetry()?.decorateFinalPass(descriptor) ?? descriptor
6619
+ );
6210
6620
  passEncoder.setPipeline(presentPipeline);
6211
6621
  passEncoder.setBindGroup(0, presentBindGroup);
6212
6622
  passEncoder.draw(3);
@@ -6506,36 +6916,49 @@ fn prepareMeshTrianglesAndLeaves(@builtin(global_invocation_id) globalId: vec3<u
6506
6916
  let uv1 = select(vec2<f32>(0.0), vertex1.uv.xy, vertex1.uv.z > 0.5);
6507
6917
  let uv2 = select(vec2<f32>(0.0), vertex2.uv.xy, vertex2.uv.z > 0.5);
6508
6918
 
6509
- triangles[triangleIndex] = TriangleRecord(
6510
- triangleIndex,
6511
- mesh.meshId,
6512
- mesh.materialKind,
6513
- mesh.flags,
6514
- mesh.materialRefId,
6515
- mesh.mediumRefId,
6516
- mesh.materialSlot,
6517
- 0u,
6518
- vec4<f32>(vertex0.position.xyz, 0.0),
6519
- vec4<f32>(vertex1.position.xyz, 0.0),
6520
- vec4<f32>(vertex2.position.xyz, 0.0),
6521
- vec4<f32>(n0, 0.0),
6522
- vec4<f32>(n1, 0.0),
6523
- vec4<f32>(n2, 0.0),
6524
- vec4<f32>(uv0, uv1),
6525
- vec4<f32>(uv2, 0.0, 0.0),
6526
- mesh.color,
6527
- mesh.emission,
6528
- mesh.material,
6529
- mesh.materialResponse,
6530
- mesh.materialExtension,
6531
- mesh.specularColor,
6532
- mesh.baseColorAtlas,
6533
- mesh.metallicRoughnessAtlas,
6534
- mesh.normalAtlas,
6535
- mesh.occlusionAtlas,
6536
- mesh.emissiveAtlas,
6537
- mesh.textureSettings
6538
- );
6919
+ var preparedTriangle = TriangleRecord();
6920
+ preparedTriangle.triangleId = triangleIndex;
6921
+ preparedTriangle.meshId = mesh.meshId;
6922
+ preparedTriangle.materialKind = mesh.materialKind;
6923
+ preparedTriangle.flags = mesh.flags;
6924
+ preparedTriangle.materialRefId = mesh.materialRefId;
6925
+ preparedTriangle.mediumRefId = mesh.mediumRefId;
6926
+ preparedTriangle.materialSlot = mesh.materialSlot;
6927
+ preparedTriangle.v0 = vec4<f32>(vertex0.position.xyz, 0.0);
6928
+ preparedTriangle.v1 = vec4<f32>(vertex1.position.xyz, 0.0);
6929
+ preparedTriangle.v2 = vec4<f32>(vertex2.position.xyz, 0.0);
6930
+ preparedTriangle.n0 = vec4<f32>(n0, 0.0);
6931
+ preparedTriangle.n1 = vec4<f32>(n1, 0.0);
6932
+ preparedTriangle.n2 = vec4<f32>(n2, 0.0);
6933
+ preparedTriangle.uv0uv1 = vec4<f32>(uv0, uv1);
6934
+ preparedTriangle.uv2Pad = vec4<f32>(uv2, 0.0, 0.0);
6935
+ preparedTriangle.color = mesh.color;
6936
+ preparedTriangle.emission = mesh.emission;
6937
+ preparedTriangle.material = mesh.material;
6938
+ preparedTriangle.materialResponse = mesh.materialResponse;
6939
+ preparedTriangle.materialExtension = mesh.materialExtension;
6940
+ preparedTriangle.specularColor = mesh.specularColor;
6941
+ preparedTriangle.baseColorAtlas = mesh.baseColorAtlas;
6942
+ preparedTriangle.metallicRoughnessAtlas = mesh.metallicRoughnessAtlas;
6943
+ preparedTriangle.normalAtlas = mesh.normalAtlas;
6944
+ preparedTriangle.occlusionAtlas = mesh.occlusionAtlas;
6945
+ preparedTriangle.emissiveAtlas = mesh.emissiveAtlas;
6946
+ preparedTriangle.textureSettings = mesh.textureSettings;
6947
+ preparedTriangle.materialExtension2 = vec4<f32>(0.0, 0.0, 1.3, 0.0);
6948
+ preparedTriangle.materialExtension3 = vec4<f32>(100.0, 400.0, 0.0, 0.0);
6949
+ preparedTriangle.clearcoatAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6950
+ preparedTriangle.clearcoatRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6951
+ preparedTriangle.clearcoatNormalAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6952
+ preparedTriangle.transmissionAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6953
+ preparedTriangle.thicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6954
+ preparedTriangle.sheenColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6955
+ preparedTriangle.sheenRoughnessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6956
+ preparedTriangle.specularAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6957
+ preparedTriangle.specularColorAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6958
+ preparedTriangle.iridescenceAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6959
+ preparedTriangle.iridescenceThicknessAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6960
+ preparedTriangle.anisotropyAtlas = vec4<f32>(0.0, 0.0, 1.0, 1.0);
6961
+ triangles[triangleIndex] = preparedTriangle;
6539
6962
 
6540
6963
  let leafBase = config.triangleCount - 1u;
6541
6964
  let nodeIndex = leafBase + triangleIndex;
@@ -7205,36 +7628,7 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
7205
7628
  vec4<f32>(1.0, 1.0, 1.0, 1.0)
7206
7629
  );
7207
7630
  var candidate = no_candidate();
7208
- var hitTriangle = TriangleRecord(
7209
- 0u,
7210
- 0u,
7211
- 0u,
7212
- 0u,
7213
- 0u,
7214
- 0u,
7215
- 0u,
7216
- 0u,
7217
- vec4<f32>(0.0),
7218
- vec4<f32>(0.0),
7219
- vec4<f32>(0.0),
7220
- vec4<f32>(0.0, 1.0, 0.0, 0.0),
7221
- vec4<f32>(0.0, 1.0, 0.0, 0.0),
7222
- vec4<f32>(0.0, 1.0, 0.0, 0.0),
7223
- vec4<f32>(0.0),
7224
- vec4<f32>(0.0),
7225
- vec4<f32>(0.0),
7226
- vec4<f32>(0.0),
7227
- vec4<f32>(1.0, 0.0, 1.0, 1.0),
7228
- vec4<f32>(0.0, 0.0, 0.0, 0.08),
7229
- vec4<f32>(0.08, 1.0, 0.0, 0.0),
7230
- vec4<f32>(1.0, 1.0, 1.0, 1.0),
7231
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7232
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7233
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7234
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7235
- vec4<f32>(0.0, 0.0, 1.0, 1.0),
7236
- vec4<f32>(1.0, 1.0, 1.0, 0.0)
7237
- );
7631
+ var hitTriangle = TriangleRecord();
7238
7632
 
7239
7633
  for (var objectIndex = 0u; objectIndex < config.sceneObjectCount; objectIndex = objectIndex + 1u) {
7240
7634
  let object = sceneObjects[objectIndex];
@@ -10109,7 +10503,19 @@ function createWavefrontBindGroupLayouts(device, constants) {
10109
10503
  { binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10110
10504
  { binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
10111
10505
  { binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10112
- { binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
10506
+ { binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10507
+ { binding: 33, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10508
+ { binding: 34, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10509
+ { binding: 35, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10510
+ { binding: 36, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10511
+ { binding: 37, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10512
+ { binding: 38, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10513
+ { binding: 39, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10514
+ { binding: 40, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10515
+ { binding: 41, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10516
+ { binding: 42, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10517
+ { binding: 43, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
10518
+ { binding: 44, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } }
10113
10519
  ]
10114
10520
  });
10115
10521
  const acceleration = device.createBindGroupLayout({
@@ -11097,6 +11503,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11097
11503
  outputView,
11098
11504
  sampler
11099
11505
  });
11506
+ let activeFrameTelemetry = null;
11507
+ let frameTelemetryResources = null;
11100
11508
  const frameEncoder = createWavefrontFrameEncoder({
11101
11509
  getConfig: () => config,
11102
11510
  getBindGroups: () => bindGroups,
@@ -11108,7 +11516,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11108
11516
  denoiseDirectResolveBindGroup,
11109
11517
  presentPipeline,
11110
11518
  presentBindGroup,
11111
- context
11519
+ context,
11520
+ getFrameTelemetry: () => activeFrameTelemetry
11112
11521
  });
11113
11522
  let frame = 0;
11114
11523
  let accelerationBuilt = !config.gpuAccelerationBuildRequired;
@@ -11121,6 +11530,16 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11121
11530
  createGpuParallelismCounters()
11122
11531
  );
11123
11532
  const waitForSubmittedGpuWork2 = (waitOptions = {}) => waitForSubmittedGpuWork(device, waitOptions);
11533
+ function ensureFrameTelemetryResources() {
11534
+ if (!frameTelemetryResources) {
11535
+ frameTelemetryResources = createWavefrontFrameTelemetryResources({
11536
+ device,
11537
+ constants,
11538
+ maxRayCountRecords: tiles.length * config.samplesPerPixel * config.maxDepth
11539
+ });
11540
+ }
11541
+ return frameTelemetryResources;
11542
+ }
11124
11543
  function resolveRenderedSamplesPerPixel(renderOptions = {}, awaitGPUCompletion = true) {
11125
11544
  return resolveWavefrontRenderedSamplesPerPixel({
11126
11545
  config,
@@ -11248,7 +11667,13 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11248
11667
  frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
11249
11668
  frameTimeMs,
11250
11669
  false
11251
- )
11670
+ ),
11671
+ timings: createWavefrontFrameTimingTelemetry({
11672
+ status: "not-requested",
11673
+ source: "cpu-submit",
11674
+ timestampQueryStatus: "not-recorded",
11675
+ totalRenderJobTimeMs: frameTimeMs
11676
+ })
11252
11677
  });
11253
11678
  }
11254
11679
  function dispatchFrameAwaitingGpu(frameIndex, parallelism, renderedSamplesPerPixel = config.samplesPerPixel, optionsForFrame = {}) {
@@ -11278,98 +11703,151 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11278
11703
  const awaitGPUCompletion = renderOptions.awaitGPUCompletion !== false;
11279
11704
  const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
11280
11705
  const useThrottledHighSamplePath = awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
11706
+ const telemetryRequested = renderOptions.readStats === true;
11707
+ const telemetryCanRecord = telemetryRequested && awaitGPUCompletion;
11708
+ const telemetryResourcesForFrame = telemetryCanRecord ? ensureFrameTelemetryResources() : null;
11709
+ if (telemetryResourcesForFrame?.available) {
11710
+ telemetryResourcesForFrame.beginFrame();
11711
+ activeFrameTelemetry = telemetryResourcesForFrame;
11712
+ }
11281
11713
  const frameStartTimeMs = nowMs3();
11282
11714
  let frameStats;
11283
- if (useThrottledHighSamplePath) {
11284
- frame += 1;
11285
- const frameIndex = frame + config.frameIndex;
11286
- const parallelismCounters = createGpuParallelismCounters();
11287
- const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
11288
- let frameSubmissionCount = 0;
11289
- let frameConfigSlot = 0;
11290
- if (accelerationBuildSubmitted) {
11291
- const accelerationWaitOptions = {
11292
- ...estimateSubmittedGpuWorkTiming(
11293
- { ...config, renderedSamplesPerPixel: 1 },
11294
- 1,
11295
- renderOptions.submittedWorkTimeoutMs,
11296
- { includeAccelerationBuild: true }
11297
- ),
11298
- allowTimeout: false
11299
- };
11300
- await waitForSubmittedGpuWork2(accelerationWaitOptions);
11301
- }
11302
- for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
11303
- const tileRangeDispatch = dispatchFrameAwaitingGpu(
11304
- frameIndex,
11305
- parallelismCounters,
11306
- samplingPlan.renderedSamplesPerPixel,
11307
- {
11308
- sampleRangeStart: 0,
11309
- sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
11310
- tileStartIndex: tileIndex,
11311
- tileEndIndex: tileIndex + 1,
11312
- startingSubmissionCount: frameSubmissionCount,
11313
- startingSlot: frameConfigSlot,
11314
- includeDenoise: tileIndex + 1 >= tiles.length,
11315
- includePresent: tileIndex + 1 >= tiles.length
11316
- }
11317
- );
11318
- frameSubmissionCount = tileRangeDispatch.submissionCount;
11319
- frameConfigSlot = tileRangeDispatch.slot;
11320
- const tileWaitOptions = {
11321
- ...estimateSubmittedGpuWorkTiming(
11322
- { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11323
- 1,
11324
- renderOptions.submittedWorkTimeoutMs,
11715
+ try {
11716
+ if (useThrottledHighSamplePath) {
11717
+ frame += 1;
11718
+ const frameIndex = frame + config.frameIndex;
11719
+ const parallelismCounters = createGpuParallelismCounters();
11720
+ const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
11721
+ let frameSubmissionCount = 0;
11722
+ let frameConfigSlot = 0;
11723
+ if (accelerationBuildSubmitted) {
11724
+ const accelerationWaitOptions = {
11725
+ ...estimateSubmittedGpuWorkTiming(
11726
+ { ...config, renderedSamplesPerPixel: 1 },
11727
+ 1,
11728
+ renderOptions.submittedWorkTimeoutMs,
11729
+ { includeAccelerationBuild: true }
11730
+ ),
11731
+ allowTimeout: false
11732
+ };
11733
+ await waitForSubmittedGpuWork2(accelerationWaitOptions);
11734
+ }
11735
+ for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
11736
+ const tileRangeDispatch = dispatchFrameAwaitingGpu(
11737
+ frameIndex,
11738
+ parallelismCounters,
11739
+ samplingPlan.renderedSamplesPerPixel,
11325
11740
  {
11326
- includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
11741
+ sampleRangeStart: 0,
11742
+ sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
11743
+ tileStartIndex: tileIndex,
11744
+ tileEndIndex: tileIndex + 1,
11745
+ startingSubmissionCount: frameSubmissionCount,
11746
+ startingSlot: frameConfigSlot,
11747
+ includeDenoise: tileIndex + 1 >= tiles.length,
11327
11748
  includePresent: tileIndex + 1 >= tiles.length
11328
11749
  }
11329
- ),
11750
+ );
11751
+ frameSubmissionCount = tileRangeDispatch.submissionCount;
11752
+ frameConfigSlot = tileRangeDispatch.slot;
11753
+ const tileWaitOptions = {
11754
+ ...estimateSubmittedGpuWorkTiming(
11755
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11756
+ 1,
11757
+ renderOptions.submittedWorkTimeoutMs,
11758
+ {
11759
+ includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
11760
+ includePresent: tileIndex + 1 >= tiles.length
11761
+ }
11762
+ ),
11763
+ allowTimeout: false
11764
+ };
11765
+ await waitForSubmittedGpuWork2(tileWaitOptions);
11766
+ }
11767
+ frameStats = createFrameStats({
11768
+ frameIndex,
11769
+ accelerationBuildSubmitted,
11770
+ frameSubmissionCount,
11771
+ parallelismCounters,
11772
+ renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
11773
+ targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
11774
+ frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
11775
+ budgetConstrained: samplingPlan.budgetConstrained
11776
+ });
11777
+ } else {
11778
+ const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
11779
+ { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11780
+ tiles.length,
11781
+ renderOptions.submittedWorkTimeoutMs,
11782
+ { includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
11783
+ );
11784
+ const submissionWaitOptions = awaitGPUCompletion ? {
11785
+ timeoutMs: submittedWorkTiming.timeoutMs,
11786
+ maxWaitMs: submittedWorkTiming.maxWaitMs,
11330
11787
  allowTimeout: false
11788
+ } : {
11789
+ timeoutMs: submittedWorkTiming.timeoutMs,
11790
+ maxWaitMs: submittedWorkTiming.maxWaitMs
11331
11791
  };
11332
- await waitForSubmittedGpuWork2(tileWaitOptions);
11333
- }
11334
- frameStats = createFrameStats({
11335
- frameIndex,
11336
- accelerationBuildSubmitted,
11337
- frameSubmissionCount,
11338
- parallelismCounters,
11339
- renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel,
11340
- targetSamplesPerPixel: samplingPlan.targetSamplesPerPixel,
11341
- frameTimeBudgetMs: samplingPlan.frameTimeBudgetMs,
11342
- budgetConstrained: samplingPlan.budgetConstrained
11343
- });
11344
- } else {
11345
- const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
11346
- { ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
11347
- tiles.length,
11348
- renderOptions.submittedWorkTimeoutMs,
11349
- { includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
11350
- );
11351
- const submissionWaitOptions = awaitGPUCompletion ? {
11352
- timeoutMs: submittedWorkTiming.timeoutMs,
11353
- maxWaitMs: submittedWorkTiming.maxWaitMs,
11354
- allowTimeout: false
11355
- } : {
11356
- timeoutMs: submittedWorkTiming.timeoutMs,
11357
- maxWaitMs: submittedWorkTiming.maxWaitMs
11358
- };
11359
- frameStats = renderOnce(renderOptions, samplingPlan);
11360
- if (awaitGPUCompletion) {
11361
- await waitForSubmittedGpuWork2(submissionWaitOptions);
11792
+ frameStats = renderOnce(renderOptions, samplingPlan);
11793
+ if (awaitGPUCompletion) {
11794
+ await waitForSubmittedGpuWork2(submissionWaitOptions);
11795
+ }
11362
11796
  }
11797
+ } finally {
11798
+ activeFrameTelemetry = null;
11363
11799
  }
11364
11800
  const frameTimeMs = Math.max(0, nowMs3() - frameStartTimeMs);
11365
11801
  if (awaitGPUCompletion) {
11366
11802
  lastCompletedFrameTimeMs = frameTimeMs;
11367
11803
  lastCompletedSamplesPerPixel = frameStats.renderedSamplesPerPixel ?? frameStats.samplesPerPixel;
11368
11804
  }
11805
+ const expectedPrimaryRays = config.width * config.height * samplingPlan.renderedSamplesPerPixel;
11806
+ const expectedRayCounts = tiles.length * samplingPlan.renderedSamplesPerPixel * config.maxDepth;
11807
+ const telemetryReadback = telemetryCanRecord ? await telemetryResourcesForFrame.readFrame({
11808
+ expectedPrimaryRays,
11809
+ expectedRayCounts,
11810
+ waitForSubmittedGpuWork: waitForSubmittedGpuWork2
11811
+ }) : null;
11812
+ const rayCounts = !telemetryRequested ? createWavefrontRayCountTelemetry() : !awaitGPUCompletion ? createWavefrontRayCountTelemetry({
11813
+ status: "unavailable",
11814
+ expectedPrimaryRays,
11815
+ expectedRayCounts,
11816
+ reason: "gpu-work-not-awaited"
11817
+ }) : telemetryReadback.rayCounts;
11818
+ const timings = !telemetryRequested ? createWavefrontFrameTimingTelemetry({
11819
+ status: "not-requested",
11820
+ source: awaitGPUCompletion ? "queue-completion" : "cpu-submit",
11821
+ timestampQueryStatus: "not-recorded",
11822
+ totalRenderJobTimeMs: frameTimeMs
11823
+ }) : !awaitGPUCompletion ? createWavefrontFrameTimingTelemetry({
11824
+ status: "unavailable",
11825
+ source: "cpu-submit",
11826
+ timestampQueryStatus: "not-recorded",
11827
+ totalRenderJobTimeMs: frameTimeMs,
11828
+ reason: "gpu-work-not-awaited"
11829
+ }) : telemetryReadback.timestampQueryStatus === "available" ? createWavefrontFrameTimingTelemetry({
11830
+ status: "available",
11831
+ source: "timestamp-query",
11832
+ timestampQueryStatus: "available",
11833
+ totalGpuTimeMs: telemetryReadback.totalGpuTimeMs,
11834
+ totalRenderJobTimeMs: frameTimeMs
11835
+ }) : createWavefrontFrameTimingTelemetry({
11836
+ status: "fallback",
11837
+ source: "queue-completion",
11838
+ timestampQueryStatus: telemetryReadback.timestampQueryStatus,
11839
+ totalRenderJobTimeMs: frameTimeMs,
11840
+ reason: telemetryReadback.reason
11841
+ });
11369
11842
  const deviceLossStatus = awaitGPUCompletion ? "not-detected" : typeof device.lost?.then === "function" ? "pending" : "not-exposed";
11370
11843
  frameStats = Object.freeze({
11371
11844
  ...frameStats,
11372
11845
  deviceLossStatus,
11846
+ secondaryRays: rayCounts.status === "available" ? rayCounts.secondaryRays : null,
11847
+ totalPathSegments: rayCounts.status === "available" ? rayCounts.totalPathSegments : null,
11848
+ rayCounts,
11849
+ timings,
11850
+ telemetryMemoryBytes: telemetryResourcesForFrame?.memoryBytes ?? 0,
11373
11851
  gpuWorkerJobs: createGpuWorkerJobDiagnostics(
11374
11852
  frameStats.gpuParallelism,
11375
11853
  frameStats.commandSubmissions,
@@ -11478,6 +11956,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
11478
11956
  });
11479
11957
  }
11480
11958
  function destroy() {
11959
+ frameTelemetryResources?.destroy?.();
11481
11960
  activeQueue.destroy?.();
11482
11961
  nextQueue.destroy?.();
11483
11962
  hitBuffer.destroy?.();