@forgeax/engine-rhi-debug 0.1.29 → 0.1.31

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/README.md CHANGED
@@ -108,20 +108,23 @@ views aligned.
108
108
 
109
109
  ## Replay and readback
110
110
 
111
+ `openReplay(tape, backend)` uses the caller's fresh device and shader factory.
112
+ `ResourceTable` owns replay resources and typed handle generations; stale
113
+ generations fail closed. Session disposal releases replay resources; the
114
+ supplied backend remains owned by its caller.
115
+
116
+ `inspectWork(..., ['pixels'])` reads the first color attachment's resolve target
117
+ when present, after finishing the selected work's pass. Direct reads of an
118
+ unresolved multisampled texture return `readback-unsupported` before any copy.
119
+ The remaining readback matrix is explicit: supported color/depth formats use
120
+ the backend path, unsupported formats return `readback-unsupported`, and
121
+ transfer or map failures return `readback-failed`.
111
122
  `readResource(resourceId, subresource)` reads the captured initial state.
112
123
  `readResourceAtWork(resourceId, workIndex, subresource)` replays through the
113
124
  selected work before reading, including compute storage outputs. Its
114
125
  `provenance.selectedWorkIndex` identifies that post-work state. Use this form
115
126
  when checking what a dispatch produced; initial bytes are not output evidence.
116
127
 
117
- `openReplay` requires a `ReplayBackend` factory. The factory is called only for
118
- the requested operation and must return a fresh backend. `ResourceTable` owns
119
- typed handle generations and disposal; stale generations fail closed. The
120
- readback matrix is explicit: supported color/depth formats use the backend
121
- path, unsupported formats return `readback-unsupported`, and transfer or map
122
- failures return `readback-failed`. Every successful session and every failed
123
- setup path releases backend resources before returning.
124
-
125
128
  ## Structured errors
126
129
 
127
130
  The public operation boundary uses `RhiDebugError`. Consumers should narrow
@@ -189,3 +192,12 @@ FORGEAX_SKIP_HARNESS_SYNC=1 pnpm --filter @forgeax/engine-rhi-debug build
189
192
  Browser and Dawn gates belong to the host and application packages. They must
190
193
  exercise the same `.rhitape` bytes and the same `workIndex` used by offline
191
194
  inspection.
195
+
196
+ ### Timestamp-bearing frame capture
197
+
198
+ Render, compute and empty-compute pass timestamps retain their QuerySet handle
199
+ identity in v7 events. Capture closure includes a QuerySet created before the
200
+ capture window; validation and resource lineage treat it as a referenced
201
+ resource. Replay replaces its serialized descriptor object with the fresh
202
+ device's QuerySet while preserving beginning/end indices. A missing identity
203
+ is a structured replay failure, never a request to silently remove timestamps.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=timestamp-query-capture.dawn.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timestamp-query-capture.dawn.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/timestamp-query-capture.dawn.test.ts"],"names":[],"mappings":""}
package/dist/index.mjs CHANGED
@@ -4433,6 +4433,7 @@ function referencedHandles(event) {
4433
4433
  "pipelineHandleId",
4434
4434
  "querySetHandleId",
4435
4435
  "occlusionQuerySetHandleId",
4436
+ "timestampQuerySetHandleId",
4436
4437
  "indexBufferHandleId",
4437
4438
  "vertexBufferHandleId"
4438
4439
  ];
@@ -5340,6 +5341,7 @@ function eventResources(event) {
5340
5341
  reads: handleRefs(event, [
5341
5342
  "cmdHandleId",
5342
5343
  "occlusionQuerySetHandleId",
5344
+ "timestampQuerySetHandleId",
5343
5345
  "colorAttachmentViewHandleIds",
5344
5346
  "colorAttachmentResolveTargetHandleIds",
5345
5347
  "depthStencilViewHandleId"
@@ -5347,7 +5349,11 @@ function eventResources(event) {
5347
5349
  destroyed: []
5348
5350
  };
5349
5351
  case "beginComputePass":
5350
- return { created: [event.passHandleId], reads: [event.cmdHandleId], destroyed: [] };
5352
+ return {
5353
+ created: [event.passHandleId],
5354
+ reads: handleRefs(event, ["cmdHandleId", "timestampQuerySetHandleId"]),
5355
+ destroyed: []
5356
+ };
5351
5357
  case "endRenderPass":
5352
5358
  case "endComputePass":
5353
5359
  return { created: [], reads: [event.passHandleId], destroyed: [event.passHandleId] };
@@ -6310,11 +6316,13 @@ function _collectFrameReferencedHandleIds(events) {
6310
6316
  }
6311
6317
  if (we.depthStencilViewHandleId !== void 0) refs.add(we.depthStencilViewHandleId);
6312
6318
  if (we.occlusionQuerySetHandleId !== void 0) refs.add(we.occlusionQuerySetHandleId);
6319
+ if (we.timestampQuerySetHandleId !== void 0) refs.add(we.timestampQuerySetHandleId);
6313
6320
  break;
6314
6321
  }
6315
6322
  case "beginComputePass": {
6316
6323
  const we = e;
6317
6324
  refs.add(we.cmdHandleId);
6325
+ if (we.timestampQuerySetHandleId !== void 0) refs.add(we.timestampQuerySetHandleId);
6318
6326
  break;
6319
6327
  }
6320
6328
  case "finish": {
@@ -6948,12 +6956,13 @@ function createCommandEncoderProxy(s, realEnc, cmdHId) {
6948
6956
  kind: "beginRenderPass",
6949
6957
  cmdHandleId: cmdHId,
6950
6958
  passHandleId: passHId,
6959
+ timestampQuerySetHandleId: desc?.timestampWrites === void 0 ? void 0 : getHandleId(s, desc.timestampWrites.querySet, "querySet"),
6951
6960
  desc: {
6952
6961
  colorAttachments: Array.from(desc.colorAttachments).map(
6953
6962
  (attachment) => attachment === null || attachment === void 0 ? attachment : { ...attachment }
6954
6963
  ),
6955
6964
  ...desc.depthStencilAttachment === void 0 ? {} : { depthStencilAttachment: { ...desc.depthStencilAttachment } },
6956
- ...desc.timestampWrites === void 0 ? {} : { timestampWrites: desc.timestampWrites },
6965
+ ...desc.timestampWrites === void 0 ? {} : { timestampWrites: recordTimestampWrites(desc.timestampWrites) },
6957
6966
  ...desc.maxDrawCount === void 0 ? {} : { maxDrawCount: desc.maxDrawCount }
6958
6967
  },
6959
6968
  colorAttachmentViewHandleIds,
@@ -6976,7 +6985,11 @@ function createCommandEncoderProxy(s, realEnc, cmdHId) {
6976
6985
  kind: "beginComputePass",
6977
6986
  cmdHandleId: cmdHId,
6978
6987
  passHandleId: passHId,
6979
- desc
6988
+ timestampQuerySetHandleId: desc?.timestampWrites === void 0 ? void 0 : getHandleId(s, desc.timestampWrites.querySet, "querySet"),
6989
+ desc: desc === void 0 ? void 0 : {
6990
+ ...desc.label === void 0 ? {} : { label: desc.label },
6991
+ timestampWrites: recordTimestampWrites(desc.timestampWrites)
6992
+ }
6980
6993
  });
6981
6994
  const realPass = realEnc.beginComputePass(desc);
6982
6995
  return createComputePassProxy(s, realPass, passHId);
@@ -6987,7 +7000,11 @@ function createCommandEncoderProxy(s, realEnc, cmdHId) {
6987
7000
  kind: "beginComputePass",
6988
7001
  cmdHandleId: cmdHId,
6989
7002
  passHandleId: passHId,
6990
- desc
7003
+ timestampQuerySetHandleId: desc?.timestampWrites === void 0 ? void 0 : getHandleId(s, desc.timestampWrites.querySet, "querySet"),
7004
+ desc: {
7005
+ ...desc.label === void 0 ? {} : { label: desc.label },
7006
+ timestampWrites: recordTimestampWrites(desc.timestampWrites)
7007
+ }
6991
7008
  });
6992
7009
  realEnc.encodeEmptyComputePass(desc);
6993
7010
  pushEvent(s, { kind: "endComputePass", passHandleId: passHId });
@@ -7147,6 +7164,12 @@ function createCommandEncoderProxy(s, realEnc, cmdHId) {
7147
7164
  }
7148
7165
  };
7149
7166
  }
7167
+ function recordTimestampWrites(writes) {
7168
+ return writes === void 0 ? void 0 : {
7169
+ ...writes.beginningOfPassWriteIndex === void 0 ? {} : { beginningOfPassWriteIndex: writes.beginningOfPassWriteIndex },
7170
+ ...writes.endOfPassWriteIndex === void 0 ? {} : { endOfPassWriteIndex: writes.endOfPassWriteIndex }
7171
+ };
7172
+ }
7150
7173
 
7151
7174
  // src/recorder/pipeline.ts
7152
7175
  function wrapPipeline(state, pipeline, pipelineHandleId) {
@@ -9406,8 +9429,11 @@ function beginRenderPass(context, event, eventIndex) {
9406
9429
  if (event.occlusionQuerySetHandleId !== void 0 && querySet?.kind !== "query-set") {
9407
9430
  return missingResource(eventIndex, event, event.occlusionQuerySetHandleId, "query-set");
9408
9431
  }
9432
+ const timestamp = replayTimestampWrites(context, event, eventIndex);
9433
+ if (!timestamp.ok) return timestamp;
9409
9434
  const descriptor = {
9410
9435
  ...event.desc,
9436
+ ...timestamp.value === void 0 ? {} : { timestampWrites: timestamp.value },
9411
9437
  colorAttachments: colors,
9412
9438
  ...depth?.kind === "texture-view" ? {
9413
9439
  depthStencilAttachment: {
@@ -9424,6 +9450,24 @@ function beginRenderPass(context, event, eventIndex) {
9424
9450
  eventRecord(event)
9425
9451
  );
9426
9452
  }
9453
+ function replayTimestampWrites(context, event, eventIndex) {
9454
+ if (event.desc?.timestampWrites === void 0) return ok(void 0);
9455
+ const handle = event.timestampQuerySetHandleId;
9456
+ if (handle === void 0)
9457
+ return missingResource(eventIndex, event, "timestamp-query-set", "query-set");
9458
+ const query = requireResource(
9459
+ context,
9460
+ handle,
9461
+ "query-set",
9462
+ eventIndex,
9463
+ event.kind
9464
+ );
9465
+ if (!query.ok) return query;
9466
+ return ok({
9467
+ ...event.desc.timestampWrites,
9468
+ querySet: query.value
9469
+ });
9470
+ }
9427
9471
  function beginComputePass(context, event, eventIndex) {
9428
9472
  const encoder = requireResource(
9429
9473
  context,
@@ -9434,7 +9478,12 @@ function beginComputePass(context, event, eventIndex) {
9434
9478
  "command"
9435
9479
  );
9436
9480
  if (!encoder.ok) return encoder;
9437
- const pass = encoder.value.beginComputePass(JSON.parse(JSON.stringify(event.desc)));
9481
+ const timestamp = replayTimestampWrites(context, event, eventIndex);
9482
+ if (!timestamp.ok) return timestamp;
9483
+ const pass = encoder.value.beginComputePass({
9484
+ ...event.desc?.label === void 0 ? {} : { label: event.desc.label },
9485
+ ...timestamp.value === void 0 ? {} : { timestampWrites: timestamp.value }
9486
+ });
9438
9487
  return context.table.set(
9439
9488
  event.passHandleId,
9440
9489
  { kind: "encoder", role: "compute-pass", value: pass },
@@ -9661,6 +9710,13 @@ async function readTexture(device, entry, resourceId, subresource, createShaderM
9661
9710
  const format = stringField2(descriptor, "format");
9662
9711
  const dimension = stringField2(descriptor, "dimension") ?? "2d";
9663
9712
  if (format === void 0) return readbackFailure(`texture ${resourceId} has no recorded format`);
9713
+ if ((numberField(descriptor, "sampleCount") ?? 1) > 1) {
9714
+ return readbackUnsupported(
9715
+ resourceId,
9716
+ format,
9717
+ "multisampled textures require a single-sample resolve target before readback"
9718
+ );
9719
+ }
9664
9720
  const plan = getTextureReadbackPlan({ format, dimension });
9665
9721
  if (!plan.supported) {
9666
9722
  return err(
@@ -10734,9 +10790,10 @@ async function readWorkAttachment(context, index, work) {
10734
10790
  })
10735
10791
  );
10736
10792
  }
10737
- const viewId = begin.colorAttachmentViewHandleIds.find(
10793
+ const attachmentIndex = begin.colorAttachmentViewHandleIds.findIndex(
10738
10794
  (candidate) => candidate !== void 0
10739
10795
  );
10796
+ const viewId = begin.colorAttachmentResolveTargetHandleIds?.[attachmentIndex] ?? begin.colorAttachmentViewHandleIds[attachmentIndex];
10740
10797
  if (viewId === void 0) {
10741
10798
  return err(
10742
10799
  createRhiDebugError("readback-unsupported", {