@forgeax/engine-rhi-debug 0.1.29 → 0.1.30
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 +20 -8
- package/dist/__tests__/timestamp-query-capture.dawn.test.d.ts +2 -0
- package/dist/__tests__/timestamp-query-capture.dawn.test.d.ts.map +1 -0
- package/dist/index.mjs +63 -6
- package/dist/index.mjs.map +1 -1
- package/dist/recorder/closure.d.ts.map +1 -1
- package/dist/recorder/encoder.d.ts.map +1 -1
- package/dist/replay/session.d.ts.map +1 -1
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/readback-format-matrix.dawn.test.ts +2 -2
- package/src/__tests__/replay-session.dawn.test.ts +122 -0
- package/src/__tests__/timestamp-query-capture.dawn.test.ts +61 -0
- package/src/protocol/event-semantics.ts +1 -0
- package/src/protocol/validation.ts +6 -1
- package/src/recorder/closure.ts +4 -1
- package/src/recorder/encoder.ts +39 -3
- package/src/replay/execute.ts +32 -1
- package/src/replay/readback.ts +7 -0
- package/src/replay/session.ts +7 -2
- package/src/types.ts +8 -2
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 @@
|
|
|
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 {
|
|
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
|
|
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
|
|
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", {
|