@forgeax/engine-rhi-debug 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +178 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/bootstrap-integration.test.d.ts +2 -0
- package/dist/__tests__/bootstrap-integration.test.d.ts.map +1 -0
- package/dist/__tests__/consumer-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/consumer-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/copy-src-usage-validity.dawn.test.d.ts +15 -0
- package/dist/__tests__/copy-src-usage-validity.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/coverage-invariant.test-d.d.ts +2 -0
- package/dist/__tests__/coverage-invariant.test-d.d.ts.map +1 -0
- package/dist/__tests__/coverage-invariant.unit.test.d.ts +2 -0
- package/dist/__tests__/coverage-invariant.unit.test.d.ts.map +1 -0
- package/dist/__tests__/e2e.browser.test.d.ts +2 -0
- package/dist/__tests__/e2e.browser.test.d.ts.map +1 -0
- package/dist/__tests__/error-code-narrowing.test-d.d.ts +2 -0
- package/dist/__tests__/error-code-narrowing.test-d.d.ts.map +1 -0
- package/dist/__tests__/errors-v7.test-d.d.ts +2 -0
- package/dist/__tests__/errors-v7.test-d.d.ts.map +1 -0
- package/dist/__tests__/errors-v7.unit.test.d.ts +2 -0
- package/dist/__tests__/errors-v7.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/event-semantics-coverage.unit.test.d.ts +2 -0
- package/dist/__tests__/event-semantics-coverage.unit.test.d.ts.map +1 -0
- package/dist/__tests__/format-info.unit.test.d.ts +2 -0
- package/dist/__tests__/format-info.unit.test.d.ts.map +1 -0
- package/dist/__tests__/frame-model-parity.test-d.d.ts +2 -0
- package/dist/__tests__/frame-model-parity.test-d.d.ts.map +1 -0
- package/dist/__tests__/frame-model-parity.unit.test.d.ts +2 -0
- package/dist/__tests__/frame-model-parity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guard-gates.test.d.ts +2 -0
- package/dist/__tests__/guard-gates.test.d.ts.map +1 -0
- package/dist/__tests__/public-surface.integration.test.d.ts +2 -0
- package/dist/__tests__/public-surface.integration.test.d.ts.map +1 -0
- package/dist/__tests__/readback-fault-cleanup.unit.test.d.ts +2 -0
- package/dist/__tests__/readback-fault-cleanup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/readback-format-matrix.dawn.test.d.ts +2 -0
- package/dist/__tests__/readback-format-matrix.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/readback-format-matrix.unit.test.d.ts +2 -0
- package/dist/__tests__/readback-format-matrix.unit.test.d.ts.map +1 -0
- package/dist/__tests__/readback-matrix-fixture.d.ts +32 -0
- package/dist/__tests__/readback-matrix-fixture.d.ts.map +1 -0
- package/dist/__tests__/recorder-bootstrap.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-bootstrap.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-budget.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-budget.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-fail-fast.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-fail-fast.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-push-event.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-push-event.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-session.integration.test.d.ts +2 -0
- package/dist/__tests__/recorder-session.integration.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-session.unit.test.d.ts +2 -0
- package/dist/__tests__/recorder-session.unit.test.d.ts.map +1 -0
- package/dist/__tests__/recorder-steady-frame.dawn.test.d.ts +2 -0
- package/dist/__tests__/recorder-steady-frame.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/replay-fail-fast.unit.test.d.ts +2 -0
- package/dist/__tests__/replay-fail-fast.unit.test.d.ts.map +1 -0
- package/dist/__tests__/replay-session.dawn.test.d.ts +2 -0
- package/dist/__tests__/replay-session.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/replay-session.test-d.d.ts +2 -0
- package/dist/__tests__/replay-session.test-d.d.ts.map +1 -0
- package/dist/__tests__/replay-session.unit.test.d.ts +2 -0
- package/dist/__tests__/replay-session.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-texture-descriptor.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-texture-descriptor.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resource-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/resource-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-debug-fresh-replay.dawn.test.d.ts +2 -0
- package/dist/__tests__/rhi-debug-fresh-replay.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/tape-index.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-index.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tape-v7-invalid.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-v7-invalid.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tape-v7-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-v7-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tape-v7-version.unit.test.d.ts +2 -0
- package/dist/__tests__/tape-v7-version.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tree-shake.unit.test.d.ts +2 -0
- package/dist/__tests__/tree-shake.unit.test.d.ts.map +1 -0
- package/dist/__tests__/types.test.d.ts +2 -0
- package/dist/__tests__/types.test.d.ts.map +1 -0
- package/dist/browser.d.ts +45 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.mjs +83 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/frame-model.d.ts +158 -0
- package/dist/frame-model.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +10415 -0
- package/dist/index.mjs.map +1 -0
- package/dist/protocol/codec.d.ts +6 -0
- package/dist/protocol/codec.d.ts.map +1 -0
- package/dist/protocol/event-semantics.d.ts +15 -0
- package/dist/protocol/event-semantics.d.ts.map +1 -0
- package/dist/protocol/tape-index.d.ts +30 -0
- package/dist/protocol/tape-index.d.ts.map +1 -0
- package/dist/protocol/types.d.ts +44 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/validation.d.ts +7 -0
- package/dist/protocol/validation.d.ts.map +1 -0
- package/dist/readback.d.ts +156 -0
- package/dist/readback.d.ts.map +1 -0
- package/dist/recorder/assemble.d.ts +11 -0
- package/dist/recorder/assemble.d.ts.map +1 -0
- package/dist/recorder/closure.d.ts +39 -0
- package/dist/recorder/closure.d.ts.map +1 -0
- package/dist/recorder/core.d.ts +296 -0
- package/dist/recorder/core.d.ts.map +1 -0
- package/dist/recorder/device.d.ts +4 -0
- package/dist/recorder/device.d.ts.map +1 -0
- package/dist/recorder/encoder.d.ts +5 -0
- package/dist/recorder/encoder.d.ts.map +1 -0
- package/dist/recorder/lifecycle.d.ts +20 -0
- package/dist/recorder/lifecycle.d.ts.map +1 -0
- package/dist/recorder/pass.d.ts +6 -0
- package/dist/recorder/pass.d.ts.map +1 -0
- package/dist/recorder/proxy.d.ts +19 -0
- package/dist/recorder/proxy.d.ts.map +1 -0
- package/dist/recorder/queue.d.ts +4 -0
- package/dist/recorder/queue.d.ts.map +1 -0
- package/dist/recorder/resource-registry.d.ts +15 -0
- package/dist/recorder/resource-registry.d.ts.map +1 -0
- package/dist/recorder/session.d.ts +25 -0
- package/dist/recorder/session.d.ts.map +1 -0
- package/dist/recorder/shader.d.ts +3 -0
- package/dist/recorder/shader.d.ts.map +1 -0
- package/dist/recorder/snapshot.d.ts +10 -0
- package/dist/recorder/snapshot.d.ts.map +1 -0
- package/dist/recorder/wrap.d.ts +4 -0
- package/dist/recorder/wrap.d.ts.map +1 -0
- package/dist/recorder.d.ts +7 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/replay/device-request.d.ts +8 -0
- package/dist/replay/device-request.d.ts.map +1 -0
- package/dist/replay/execute-support.d.ts +33 -0
- package/dist/replay/execute-support.d.ts.map +1 -0
- package/dist/replay/execute.d.ts +18 -0
- package/dist/replay/execute.d.ts.map +1 -0
- package/dist/replay/readback.d.ts +32 -0
- package/dist/replay/readback.d.ts.map +1 -0
- package/dist/replay/resources.d.ts +57 -0
- package/dist/replay/resources.d.ts.map +1 -0
- package/dist/replay/session.d.ts +49 -0
- package/dist/replay/session.d.ts.map +1 -0
- package/dist/replay/texture-format.d.ts +21 -0
- package/dist/replay/texture-format.d.ts.map +1 -0
- package/dist/texel-decode.d.ts +35 -0
- package/dist/texel-decode.d.ts.map +1 -0
- package/dist/texel-layout.d.ts +76 -0
- package/dist/texel-layout.d.ts.map +1 -0
- package/dist/types.d.ts +722 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +72 -0
- package/src/__tests__/bootstrap-integration.test.ts +268 -0
- package/src/__tests__/consumer-inventory.unit.test.ts +154 -0
- package/src/__tests__/copy-src-usage-validity.dawn.test.ts +117 -0
- package/src/__tests__/coverage-invariant.test-d.ts +115 -0
- package/src/__tests__/coverage-invariant.unit.test.ts +45 -0
- package/src/__tests__/e2e.browser.test.ts +239 -0
- package/src/__tests__/error-code-narrowing.test-d.ts +32 -0
- package/src/__tests__/errors-v7.test-d.ts +19 -0
- package/src/__tests__/errors-v7.unit.test.ts +15 -0
- package/src/__tests__/errors.unit.test.ts +83 -0
- package/src/__tests__/event-semantics-coverage.unit.test.ts +22 -0
- package/src/__tests__/format-info.unit.test.ts +85 -0
- package/src/__tests__/frame-model-parity.test-d.ts +20 -0
- package/src/__tests__/frame-model-parity.unit.test.ts +285 -0
- package/src/__tests__/guard-gates.test.ts +317 -0
- package/src/__tests__/public-surface.integration.test.ts +38 -0
- package/src/__tests__/readback-fault-cleanup.unit.test.ts +66 -0
- package/src/__tests__/readback-format-matrix.dawn.test.ts +378 -0
- package/src/__tests__/readback-format-matrix.unit.test.ts +120 -0
- package/src/__tests__/readback-matrix-fixture.ts +20 -0
- package/src/__tests__/recorder-bootstrap.unit.test.ts +302 -0
- package/src/__tests__/recorder-budget.unit.test.ts +42 -0
- package/src/__tests__/recorder-fail-fast.unit.test.ts +434 -0
- package/src/__tests__/recorder-lifecycle.unit.test.ts +42 -0
- package/src/__tests__/recorder-push-event.unit.test.ts +573 -0
- package/src/__tests__/recorder-session.integration.test.ts +32 -0
- package/src/__tests__/recorder-session.unit.test.ts +63 -0
- package/src/__tests__/recorder-steady-frame.dawn.test.ts +52 -0
- package/src/__tests__/replay-fail-fast.unit.test.ts +155 -0
- package/src/__tests__/replay-session.dawn.test.ts +220 -0
- package/src/__tests__/replay-session.test-d.ts +27 -0
- package/src/__tests__/replay-session.unit.test.ts +171 -0
- package/src/__tests__/resolve-texture-descriptor.unit.test.ts +144 -0
- package/src/__tests__/resource-lifecycle.unit.test.ts +114 -0
- package/src/__tests__/rhi-debug-fresh-replay.dawn.test.ts +178 -0
- package/src/__tests__/tape-index.unit.test.ts +56 -0
- package/src/__tests__/tape-v7-invalid.unit.test.ts +45 -0
- package/src/__tests__/tape-v7-roundtrip.unit.test.ts +66 -0
- package/src/__tests__/tape-v7-version.unit.test.ts +13 -0
- package/src/__tests__/tree-shake.unit.test.ts +151 -0
- package/src/__tests__/types.test.ts +257 -0
- package/src/browser.ts +146 -0
- package/src/errors.ts +173 -0
- package/src/frame-model.ts +753 -0
- package/src/index.ts +92 -0
- package/src/pako.d.ts +8 -0
- package/src/protocol/codec.ts +220 -0
- package/src/protocol/event-semantics.ts +180 -0
- package/src/protocol/tape-index.ts +120 -0
- package/src/protocol/types.ts +60 -0
- package/src/protocol/validation.ts +173 -0
- package/src/readback.ts +804 -0
- package/src/recorder/assemble.ts +192 -0
- package/src/recorder/closure.ts +408 -0
- package/src/recorder/core.ts +587 -0
- package/src/recorder/device.ts +474 -0
- package/src/recorder/encoder.ts +251 -0
- package/src/recorder/lifecycle.ts +847 -0
- package/src/recorder/pass.ts +241 -0
- package/src/recorder/proxy.ts +59 -0
- package/src/recorder/queue.ts +109 -0
- package/src/recorder/resource-registry.ts +53 -0
- package/src/recorder/session.ts +152 -0
- package/src/recorder/shader.ts +47 -0
- package/src/recorder/snapshot.ts +38 -0
- package/src/recorder/wrap.ts +157 -0
- package/src/recorder.ts +15 -0
- package/src/replay/device-request.ts +38 -0
- package/src/replay/execute-support.ts +243 -0
- package/src/replay/execute.ts +870 -0
- package/src/replay/readback.ts +671 -0
- package/src/replay/resources.ts +154 -0
- package/src/replay/session.ts +509 -0
- package/src/replay/texture-format.ts +99 -0
- package/src/texel-decode.ts +289 -0
- package/src/texel-layout.ts +324 -0
- package/src/types.ts +888 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/index.ts -- v7 public contract.
|
|
2
|
+
//
|
|
3
|
+
// AI cold-start path: one .rhitape ArtifactRef, one FrameModel workIndex, and one
|
|
4
|
+
// fresh ReplaySession. Consumers should use structured Result errors and keep
|
|
5
|
+
// browser, Node, and backend ownership at their host boundaries.
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
createRhiDebugError,
|
|
9
|
+
type RhiDebugError,
|
|
10
|
+
type RhiDebugErrorCode,
|
|
11
|
+
type RhiDebugErrorDetail,
|
|
12
|
+
type RhiDebugErrorFor,
|
|
13
|
+
} from './errors';
|
|
14
|
+
export type {
|
|
15
|
+
CommandEntry,
|
|
16
|
+
FrameModel,
|
|
17
|
+
FramePass,
|
|
18
|
+
JsonValue,
|
|
19
|
+
ResourceConsumer,
|
|
20
|
+
ResourceEntry,
|
|
21
|
+
WorkBinding,
|
|
22
|
+
WorkEntry,
|
|
23
|
+
WorkPipeline,
|
|
24
|
+
} from './frame-model';
|
|
25
|
+
export {
|
|
26
|
+
buildFrameModel,
|
|
27
|
+
buildResourceLifecycle,
|
|
28
|
+
type ResourceByteEstimate,
|
|
29
|
+
type ResourceKind,
|
|
30
|
+
type ResourceLifecycleEntry,
|
|
31
|
+
type ResourceLifecycleSummary,
|
|
32
|
+
} from './frame-model';
|
|
33
|
+
export { decodeTape, encodeTape } from './protocol/codec';
|
|
34
|
+
export type { EventCategory, EventSemantics } from './protocol/event-semantics';
|
|
35
|
+
export {
|
|
36
|
+
EVENT_SEMANTICS,
|
|
37
|
+
eventKinds,
|
|
38
|
+
isWorkEvent,
|
|
39
|
+
resourceKindForEvent,
|
|
40
|
+
workEventKinds,
|
|
41
|
+
} from './protocol/event-semantics';
|
|
42
|
+
export type {
|
|
43
|
+
TapeIndex,
|
|
44
|
+
TapePassEntry,
|
|
45
|
+
TapeResourceEntry,
|
|
46
|
+
TapeWorkEntry,
|
|
47
|
+
} from './protocol/tape-index';
|
|
48
|
+
export { buildTapeIndex } from './protocol/tape-index';
|
|
49
|
+
export type {
|
|
50
|
+
BootstrapResource,
|
|
51
|
+
InitialDataSlice,
|
|
52
|
+
RhiCallEvent as V7RhiCallEvent,
|
|
53
|
+
RhiCapsRecorded as V7RhiCapsRecorded,
|
|
54
|
+
RhiDebugResult,
|
|
55
|
+
Tape as V7Tape,
|
|
56
|
+
TapeBlob as V7TapeBlob,
|
|
57
|
+
TapeBlobCompression,
|
|
58
|
+
TapeEncodeOptions,
|
|
59
|
+
} from './protocol/types';
|
|
60
|
+
export {
|
|
61
|
+
TAPE_FORMAT_VERSION as V7_TAPE_FORMAT_VERSION,
|
|
62
|
+
TAPE_MAGIC,
|
|
63
|
+
} from './protocol/types';
|
|
64
|
+
export { readbackTexturePixels } from './readback';
|
|
65
|
+
export {
|
|
66
|
+
attachRecorder,
|
|
67
|
+
type CaptureFrameOptions,
|
|
68
|
+
type CreateShaderModuleFn,
|
|
69
|
+
type EncodedTape,
|
|
70
|
+
type RecordableBackend,
|
|
71
|
+
type RecorderAttachment,
|
|
72
|
+
type RecorderBackend,
|
|
73
|
+
type RecorderOptions,
|
|
74
|
+
} from './recorder/session';
|
|
75
|
+
export { replayDeviceRequest } from './replay/device-request';
|
|
76
|
+
export {
|
|
77
|
+
type InspectField,
|
|
78
|
+
openReplay,
|
|
79
|
+
type ReadbackSubresource,
|
|
80
|
+
type ReplayBackend,
|
|
81
|
+
type ReplayReadbackResult,
|
|
82
|
+
type ReplaySession,
|
|
83
|
+
type TextureSubresource,
|
|
84
|
+
type WorkInspection,
|
|
85
|
+
} from './replay/session';
|
|
86
|
+
export { decodeTexelRaw, decodeToRgba8, halfToFloat } from './texel-decode';
|
|
87
|
+
export {
|
|
88
|
+
bytesPerTexel,
|
|
89
|
+
type ChannelType,
|
|
90
|
+
type FormatInfo,
|
|
91
|
+
formatInfo,
|
|
92
|
+
} from './texel-layout';
|
package/src/pako.d.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
3
|
+
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
4
|
+
import pako from 'pako';
|
|
5
|
+
import { createRhiDebugError } from '../errors';
|
|
6
|
+
import {
|
|
7
|
+
type RhiCallEvent,
|
|
8
|
+
TAPE_FORMAT_VERSION,
|
|
9
|
+
TAPE_MAGIC,
|
|
10
|
+
type Tape,
|
|
11
|
+
type TapeBlobCompression,
|
|
12
|
+
type TapeEncodeOptions,
|
|
13
|
+
} from './types';
|
|
14
|
+
import { validateTape } from './validation';
|
|
15
|
+
|
|
16
|
+
const MAGIC_BYTES = new TextEncoder().encode(TAPE_MAGIC);
|
|
17
|
+
const HEADER_BYTES = MAGIC_BYTES.byteLength + 12;
|
|
18
|
+
|
|
19
|
+
interface WireBlob {
|
|
20
|
+
readonly hash: string;
|
|
21
|
+
readonly offset: number;
|
|
22
|
+
readonly length: number;
|
|
23
|
+
readonly compression: TapeBlobCompression;
|
|
24
|
+
readonly digest: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface WireTape {
|
|
28
|
+
readonly header: Tape['header'];
|
|
29
|
+
readonly bootstrap: Tape['bootstrap'];
|
|
30
|
+
readonly events: readonly RhiCallEvent[];
|
|
31
|
+
readonly blobs: readonly WireBlob[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function encodeTape(
|
|
35
|
+
tape: Tape,
|
|
36
|
+
options: TapeEncodeOptions = {},
|
|
37
|
+
): Result<Uint8Array, ReturnType<typeof createRhiDebugError<'tape-invalid'>>> {
|
|
38
|
+
const validation = validateTape(tape);
|
|
39
|
+
if (!validation.ok) return validation;
|
|
40
|
+
const compression = options.compression ?? tape.blobs[0]?.compression ?? 'none';
|
|
41
|
+
const payloads: Uint8Array[] = [];
|
|
42
|
+
const blobs: WireBlob[] = [];
|
|
43
|
+
let offset = 0;
|
|
44
|
+
for (const blob of tape.blobs) {
|
|
45
|
+
const raw = new Uint8Array(blob.bytes);
|
|
46
|
+
const payload = compression === 'gzip' ? pako.gzip(raw) : raw;
|
|
47
|
+
payloads.push(payload);
|
|
48
|
+
blobs.push({
|
|
49
|
+
hash: blob.hash,
|
|
50
|
+
offset,
|
|
51
|
+
length: payload.byteLength,
|
|
52
|
+
compression,
|
|
53
|
+
digest: digestBytes(payload),
|
|
54
|
+
});
|
|
55
|
+
offset += payload.byteLength;
|
|
56
|
+
}
|
|
57
|
+
const wire: WireTape = {
|
|
58
|
+
header: { ...tape.header, blobCount: blobs.length },
|
|
59
|
+
bootstrap: tape.bootstrap,
|
|
60
|
+
events: tape.events,
|
|
61
|
+
blobs,
|
|
62
|
+
};
|
|
63
|
+
const json = canonicalJson(wire);
|
|
64
|
+
const jsonBytes = new TextEncoder().encode(json);
|
|
65
|
+
const output = new Uint8Array(HEADER_BYTES + jsonBytes.byteLength + offset);
|
|
66
|
+
output.set(MAGIC_BYTES, 0);
|
|
67
|
+
const view = new DataView(output.buffer);
|
|
68
|
+
view.setUint32(MAGIC_BYTES.byteLength, TAPE_FORMAT_VERSION, true);
|
|
69
|
+
view.setUint32(MAGIC_BYTES.byteLength + 4, jsonBytes.byteLength, true);
|
|
70
|
+
view.setUint32(MAGIC_BYTES.byteLength + 8, offset, true);
|
|
71
|
+
output.set(jsonBytes, HEADER_BYTES);
|
|
72
|
+
let cursor = HEADER_BYTES + jsonBytes.byteLength;
|
|
73
|
+
for (const payload of payloads) {
|
|
74
|
+
output.set(payload, cursor);
|
|
75
|
+
cursor += payload.byteLength;
|
|
76
|
+
}
|
|
77
|
+
return ok(output);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function decodeTape(
|
|
81
|
+
bytes: Uint8Array,
|
|
82
|
+
): Result<
|
|
83
|
+
Tape,
|
|
84
|
+
| ReturnType<typeof createRhiDebugError<'tape-invalid'>>
|
|
85
|
+
| ReturnType<typeof createRhiDebugError<'tape-version-unsupported'>>
|
|
86
|
+
> {
|
|
87
|
+
const legacy = decodeLegacyVersion(bytes);
|
|
88
|
+
if (legacy !== undefined)
|
|
89
|
+
return err(
|
|
90
|
+
createRhiDebugError('tape-version-unsupported', { foundVersion: legacy, expectedVersion: 7 }),
|
|
91
|
+
);
|
|
92
|
+
if (bytes.byteLength < HEADER_BYTES)
|
|
93
|
+
return err(
|
|
94
|
+
createRhiDebugError('tape-invalid', { stage: 'decode', cause: 'container is truncated' }),
|
|
95
|
+
);
|
|
96
|
+
for (let i = 0; i < MAGIC_BYTES.byteLength; i++) {
|
|
97
|
+
if (bytes[i] !== MAGIC_BYTES[i])
|
|
98
|
+
return err(
|
|
99
|
+
createRhiDebugError('tape-invalid', {
|
|
100
|
+
stage: 'decode',
|
|
101
|
+
cause: 'magic does not match RHITAPE',
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
106
|
+
const version = view.getUint32(MAGIC_BYTES.byteLength, true);
|
|
107
|
+
if (version !== TAPE_FORMAT_VERSION)
|
|
108
|
+
return err(
|
|
109
|
+
createRhiDebugError('tape-version-unsupported', {
|
|
110
|
+
foundVersion: version,
|
|
111
|
+
expectedVersion: 7,
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
const jsonLength = view.getUint32(MAGIC_BYTES.byteLength + 4, true);
|
|
115
|
+
const payloadLength = view.getUint32(MAGIC_BYTES.byteLength + 8, true);
|
|
116
|
+
const payloadStart = HEADER_BYTES + jsonLength;
|
|
117
|
+
if (payloadStart > bytes.byteLength || payloadStart + payloadLength !== bytes.byteLength) {
|
|
118
|
+
return err(
|
|
119
|
+
createRhiDebugError('tape-invalid', {
|
|
120
|
+
stage: 'decode',
|
|
121
|
+
cause: 'container length is out of bounds',
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
let wire: WireTape;
|
|
126
|
+
try {
|
|
127
|
+
wire = JSON.parse(
|
|
128
|
+
new TextDecoder().decode(bytes.subarray(HEADER_BYTES, payloadStart)),
|
|
129
|
+
) as WireTape;
|
|
130
|
+
} catch {
|
|
131
|
+
return err(
|
|
132
|
+
createRhiDebugError('tape-invalid', { stage: 'decode', cause: 'canonical JSON is invalid' }),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (
|
|
136
|
+
wire.header?.formatVersion !== TAPE_FORMAT_VERSION ||
|
|
137
|
+
!Array.isArray(wire.events) ||
|
|
138
|
+
!Array.isArray(wire.blobs)
|
|
139
|
+
) {
|
|
140
|
+
return err(
|
|
141
|
+
createRhiDebugError('tape-invalid', {
|
|
142
|
+
stage: 'validate',
|
|
143
|
+
cause: 'required v7 fields are missing',
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const payload = bytes.subarray(payloadStart);
|
|
148
|
+
const decodedBlobs = [];
|
|
149
|
+
for (const blob of wire.blobs) {
|
|
150
|
+
if (
|
|
151
|
+
!Number.isInteger(blob.offset) ||
|
|
152
|
+
!Number.isInteger(blob.length) ||
|
|
153
|
+
blob.offset < 0 ||
|
|
154
|
+
blob.length < 0 ||
|
|
155
|
+
blob.offset + blob.length > payload.byteLength
|
|
156
|
+
) {
|
|
157
|
+
return err(
|
|
158
|
+
createRhiDebugError('tape-invalid', {
|
|
159
|
+
stage: 'validate',
|
|
160
|
+
cause: 'blob table entry is out of bounds',
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const stored = payload.slice(blob.offset, blob.offset + blob.length);
|
|
165
|
+
if (digestBytes(stored) !== blob.digest)
|
|
166
|
+
return err(
|
|
167
|
+
createRhiDebugError('tape-invalid', {
|
|
168
|
+
stage: 'validate',
|
|
169
|
+
cause: `blob digest mismatch for ${blob.hash}`,
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
let raw: Uint8Array;
|
|
173
|
+
try {
|
|
174
|
+
raw = blob.compression === 'gzip' ? new Uint8Array(pako.ungzip(stored)) : stored;
|
|
175
|
+
} catch {
|
|
176
|
+
return err(
|
|
177
|
+
createRhiDebugError('tape-invalid', {
|
|
178
|
+
stage: 'decode',
|
|
179
|
+
cause: `blob decompression failed for ${blob.hash}`,
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
decodedBlobs.push({ hash: blob.hash, bytes: raw, compression: blob.compression });
|
|
184
|
+
}
|
|
185
|
+
const tape: Tape = {
|
|
186
|
+
header: { ...wire.header, formatVersion: TAPE_FORMAT_VERSION, blobCount: decodedBlobs.length },
|
|
187
|
+
bootstrap: wire.bootstrap ?? [],
|
|
188
|
+
events: wire.events,
|
|
189
|
+
blobs: decodedBlobs,
|
|
190
|
+
};
|
|
191
|
+
const validation = validateTape(tape);
|
|
192
|
+
if (!validation.ok) return validation;
|
|
193
|
+
return ok(tape);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function decodeLegacyVersion(bytes: Uint8Array): number | undefined {
|
|
197
|
+
if (bytes.length === 0 || bytes[0] !== 123) return undefined;
|
|
198
|
+
try {
|
|
199
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes)) as { formatVersion?: unknown };
|
|
200
|
+
return typeof parsed.formatVersion === 'number' && parsed.formatVersion !== 7
|
|
201
|
+
? parsed.formatVersion
|
|
202
|
+
: undefined;
|
|
203
|
+
} catch {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function canonicalJson(value: unknown): string {
|
|
209
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
210
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
211
|
+
const object = value as Record<string, unknown>;
|
|
212
|
+
return `{${Object.keys(object)
|
|
213
|
+
.sort()
|
|
214
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`)
|
|
215
|
+
.join(',')}}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function digestBytes(bytes: Uint8Array): string {
|
|
219
|
+
return `sha256:${bytesToHex(sha256(bytes))}`;
|
|
220
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { EventKind, ResourceKind, RhiCallEvent } from './types';
|
|
2
|
+
|
|
3
|
+
export type EventCategory = 'resource' | 'pass' | 'state' | 'work' | 'copy' | 'submit' | 'marker';
|
|
4
|
+
|
|
5
|
+
export interface EventSemantics {
|
|
6
|
+
readonly category: EventCategory;
|
|
7
|
+
readonly created: (event: RhiCallEvent) => readonly string[];
|
|
8
|
+
readonly read: (event: RhiCallEvent) => readonly string[];
|
|
9
|
+
readonly written: (event: RhiCallEvent) => readonly string[];
|
|
10
|
+
readonly destroyed: (event: RhiCallEvent) => readonly string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const eventKinds = [
|
|
14
|
+
'frameMark',
|
|
15
|
+
'createBuffer',
|
|
16
|
+
'createTexture',
|
|
17
|
+
'destroyBuffer',
|
|
18
|
+
'destroyTexture',
|
|
19
|
+
'createTextureView',
|
|
20
|
+
'createSampler',
|
|
21
|
+
'createBindGroupLayout',
|
|
22
|
+
'createBindGroup',
|
|
23
|
+
'createPipelineLayout',
|
|
24
|
+
'createRenderPipeline',
|
|
25
|
+
'createComputePipeline',
|
|
26
|
+
'createShaderModule',
|
|
27
|
+
'createCommandEncoder',
|
|
28
|
+
'writeBuffer',
|
|
29
|
+
'writeTexture',
|
|
30
|
+
'copyExternalImageToTexture',
|
|
31
|
+
'submit',
|
|
32
|
+
'beginRenderPass',
|
|
33
|
+
'beginComputePass',
|
|
34
|
+
'copyBufferToBuffer',
|
|
35
|
+
'copyBufferToTexture',
|
|
36
|
+
'copyTextureToBuffer',
|
|
37
|
+
'copyTextureToTexture',
|
|
38
|
+
'clearBuffer',
|
|
39
|
+
'pushDebugGroup',
|
|
40
|
+
'popDebugGroup',
|
|
41
|
+
'insertDebugMarker',
|
|
42
|
+
'finish',
|
|
43
|
+
'setPipeline',
|
|
44
|
+
'setVertexBuffer',
|
|
45
|
+
'setIndexBuffer',
|
|
46
|
+
'setBindGroup',
|
|
47
|
+
'draw',
|
|
48
|
+
'drawIndexed',
|
|
49
|
+
'setViewport',
|
|
50
|
+
'setScissorRect',
|
|
51
|
+
'setStencilReference',
|
|
52
|
+
'endRenderPass',
|
|
53
|
+
'setBlendConstant',
|
|
54
|
+
'drawIndirect',
|
|
55
|
+
'drawIndexedIndirect',
|
|
56
|
+
'passPushDebugGroup',
|
|
57
|
+
'passPopDebugGroup',
|
|
58
|
+
'passInsertDebugMarker',
|
|
59
|
+
'setComputePipeline',
|
|
60
|
+
'dispatchWorkgroups',
|
|
61
|
+
'dispatchWorkgroupsIndirect',
|
|
62
|
+
'endComputePass',
|
|
63
|
+
'initialData',
|
|
64
|
+
] as const satisfies readonly EventKind[];
|
|
65
|
+
|
|
66
|
+
export const workEventKinds = [
|
|
67
|
+
'draw',
|
|
68
|
+
'drawIndexed',
|
|
69
|
+
'drawIndirect',
|
|
70
|
+
'drawIndexedIndirect',
|
|
71
|
+
'dispatchWorkgroups',
|
|
72
|
+
'dispatchWorkgroupsIndirect',
|
|
73
|
+
] as const satisfies readonly EventKind[];
|
|
74
|
+
|
|
75
|
+
export function isWorkEvent(kind: EventKind): boolean {
|
|
76
|
+
return (workEventKinds as readonly string[]).includes(kind);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const EVENT_SEMANTICS: Readonly<Record<EventKind, EventSemantics>> = Object.fromEntries(
|
|
80
|
+
eventKinds.map((kind) => [kind, semanticsFor(kind)]),
|
|
81
|
+
) as Record<EventKind, EventSemantics>;
|
|
82
|
+
|
|
83
|
+
export function resourceKindForEvent(kind: EventKind): ResourceKind | undefined {
|
|
84
|
+
switch (kind) {
|
|
85
|
+
case 'createBuffer':
|
|
86
|
+
return 'buffer';
|
|
87
|
+
case 'createTexture':
|
|
88
|
+
return 'texture';
|
|
89
|
+
case 'createTextureView':
|
|
90
|
+
return 'texture-view';
|
|
91
|
+
case 'createSampler':
|
|
92
|
+
return 'sampler';
|
|
93
|
+
case 'createShaderModule':
|
|
94
|
+
return 'shader-module';
|
|
95
|
+
case 'createRenderPipeline':
|
|
96
|
+
case 'createComputePipeline':
|
|
97
|
+
return 'pipeline';
|
|
98
|
+
case 'createBindGroup':
|
|
99
|
+
case 'createBindGroupLayout':
|
|
100
|
+
case 'createPipelineLayout':
|
|
101
|
+
return 'binding';
|
|
102
|
+
case 'createCommandEncoder':
|
|
103
|
+
return 'encoder';
|
|
104
|
+
default:
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function semanticsFor(kind: EventKind): EventSemantics {
|
|
110
|
+
const category = categoryFor(kind);
|
|
111
|
+
return {
|
|
112
|
+
category,
|
|
113
|
+
created: (event) => createdHandles(event),
|
|
114
|
+
read: (event) => referencedHandles(event),
|
|
115
|
+
written: (event) => writtenHandles(event),
|
|
116
|
+
destroyed: (event) =>
|
|
117
|
+
event.kind === 'destroyBuffer' || event.kind === 'destroyTexture'
|
|
118
|
+
? stringField(event, 'handleId')
|
|
119
|
+
: [],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function categoryFor(kind: EventKind): EventCategory {
|
|
124
|
+
if (isWorkEvent(kind)) return 'work';
|
|
125
|
+
if (kind.startsWith('create') || kind.startsWith('destroy') || kind === 'initialData')
|
|
126
|
+
return 'resource';
|
|
127
|
+
if (kind.includes('Pass')) return 'pass';
|
|
128
|
+
if (kind.startsWith('copy') || kind === 'clearBuffer' || kind.startsWith('write')) return 'copy';
|
|
129
|
+
if (kind === 'submit' || kind === 'finish') return 'submit';
|
|
130
|
+
if (kind.includes('Debug')) return 'marker';
|
|
131
|
+
return 'state';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function createdHandles(event: RhiCallEvent): readonly string[] {
|
|
135
|
+
if (event.kind === 'createTextureView') return [event.resultHandleId];
|
|
136
|
+
if (event.kind === 'createCommandEncoder') return [event.cmdHandleId];
|
|
137
|
+
if (resourceKindForEvent(event.kind) !== undefined) return stringField(event, 'handleId');
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function referencedHandles(event: RhiCallEvent): readonly string[] {
|
|
142
|
+
const keys = [
|
|
143
|
+
'sourceHandleId',
|
|
144
|
+
'layoutHandleId',
|
|
145
|
+
'vertexShaderModuleHandleId',
|
|
146
|
+
'fragmentShaderModuleHandleId',
|
|
147
|
+
'computeShaderModuleHandleId',
|
|
148
|
+
'handleId',
|
|
149
|
+
'bufferHandleId',
|
|
150
|
+
'textureHandleId',
|
|
151
|
+
'source',
|
|
152
|
+
'destination',
|
|
153
|
+
'resourceHandleIds',
|
|
154
|
+
'bindGroupHandleId',
|
|
155
|
+
'pipelineHandleId',
|
|
156
|
+
'indexBufferHandleId',
|
|
157
|
+
'vertexBufferHandleId',
|
|
158
|
+
];
|
|
159
|
+
return keys.flatMap((key) => stringField(event, key));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function writtenHandles(event: RhiCallEvent): readonly string[] {
|
|
163
|
+
if (event.kind === 'writeBuffer' || event.kind === 'writeTexture')
|
|
164
|
+
return stringField(event, 'handleId');
|
|
165
|
+
if (
|
|
166
|
+
event.kind === 'copyBufferToBuffer' ||
|
|
167
|
+
event.kind === 'copyBufferToTexture' ||
|
|
168
|
+
event.kind === 'copyTextureToBuffer' ||
|
|
169
|
+
event.kind === 'copyTextureToTexture'
|
|
170
|
+
)
|
|
171
|
+
return stringField(event, 'destination');
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function stringField(event: object, key: string): readonly string[] {
|
|
176
|
+
const value = (event as Record<string, unknown>)[key];
|
|
177
|
+
if (typeof value === 'string') return [value];
|
|
178
|
+
if (Array.isArray(value)) return value.filter((item): item is string => typeof item === 'string');
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { EVENT_SEMANTICS, eventKinds, isWorkEvent, resourceKindForEvent } from './event-semantics';
|
|
2
|
+
import type { EventKind, ResourceKind, Tape } from './types';
|
|
3
|
+
|
|
4
|
+
export interface TapePassEntry {
|
|
5
|
+
readonly passIndex: number;
|
|
6
|
+
readonly kind: 'render' | 'compute';
|
|
7
|
+
readonly beginEventIndex: number;
|
|
8
|
+
readonly endEventIndex: number | undefined;
|
|
9
|
+
readonly workIndices: readonly number[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface TapeWorkEntry {
|
|
13
|
+
readonly workIndex: number;
|
|
14
|
+
readonly eventIndex: number;
|
|
15
|
+
readonly passIndex: number;
|
|
16
|
+
readonly kind: Extract<
|
|
17
|
+
EventKind,
|
|
18
|
+
| 'draw'
|
|
19
|
+
| 'drawIndexed'
|
|
20
|
+
| 'drawIndirect'
|
|
21
|
+
| 'drawIndexedIndirect'
|
|
22
|
+
| 'dispatchWorkgroups'
|
|
23
|
+
| 'dispatchWorkgroupsIndirect'
|
|
24
|
+
>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TapeResourceEntry {
|
|
28
|
+
readonly resourceId: string;
|
|
29
|
+
readonly kind: ResourceKind;
|
|
30
|
+
readonly origin: 'bootstrap' | 'frame';
|
|
31
|
+
readonly createEventIndex: number | undefined;
|
|
32
|
+
readonly destroyEventIndex: number | undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TapeIndex {
|
|
36
|
+
readonly passes: readonly TapePassEntry[];
|
|
37
|
+
readonly works: readonly TapeWorkEntry[];
|
|
38
|
+
readonly resources: readonly TapeResourceEntry[];
|
|
39
|
+
readonly eventKinds: readonly EventKind[];
|
|
40
|
+
readonly passIndexByEvent: readonly number[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildTapeIndex(tape: Tape): TapeIndex {
|
|
44
|
+
const passes: TapePassEntry[] = [];
|
|
45
|
+
const works: TapeWorkEntry[] = [];
|
|
46
|
+
const resources = new Map<string, TapeResourceEntry>();
|
|
47
|
+
const passIndexByEvent: number[] = [];
|
|
48
|
+
let passIndex = -1;
|
|
49
|
+
let currentPass:
|
|
50
|
+
| { kind: 'render' | 'compute'; beginEventIndex: number; workIndices: number[] }
|
|
51
|
+
| undefined;
|
|
52
|
+
for (const [eventIndex, event] of tape.events.entries()) {
|
|
53
|
+
passIndexByEvent[eventIndex] = passIndex;
|
|
54
|
+
const semantics = EVENT_SEMANTICS[event.kind];
|
|
55
|
+
if (event.kind === 'beginRenderPass' || event.kind === 'beginComputePass') {
|
|
56
|
+
passIndex++;
|
|
57
|
+
passIndexByEvent[eventIndex] = passIndex;
|
|
58
|
+
currentPass = {
|
|
59
|
+
kind: event.kind === 'beginRenderPass' ? 'render' : 'compute',
|
|
60
|
+
beginEventIndex: eventIndex,
|
|
61
|
+
workIndices: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (isWorkEvent(event.kind)) {
|
|
65
|
+
const workIndex = works.length;
|
|
66
|
+
works.push({ workIndex, eventIndex, passIndex, kind: event.kind as TapeWorkEntry['kind'] });
|
|
67
|
+
currentPass?.workIndices.push(workIndex);
|
|
68
|
+
}
|
|
69
|
+
if (event.kind === 'endRenderPass' || event.kind === 'endComputePass') {
|
|
70
|
+
if (currentPass) {
|
|
71
|
+
passes.push({
|
|
72
|
+
passIndex,
|
|
73
|
+
kind: currentPass.kind,
|
|
74
|
+
beginEventIndex: currentPass.beginEventIndex,
|
|
75
|
+
endEventIndex: eventIndex,
|
|
76
|
+
workIndices: currentPass.workIndices,
|
|
77
|
+
});
|
|
78
|
+
currentPass = undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const kind = resourceKindForEvent(event.kind);
|
|
82
|
+
for (const resourceId of semantics.created(event)) {
|
|
83
|
+
if (kind)
|
|
84
|
+
resources.set(resourceId, {
|
|
85
|
+
resourceId,
|
|
86
|
+
kind,
|
|
87
|
+
origin: 'frame',
|
|
88
|
+
createEventIndex: eventIndex,
|
|
89
|
+
destroyEventIndex: undefined,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
for (const resourceId of semantics.destroyed(event)) {
|
|
93
|
+
const previous = resources.get(resourceId);
|
|
94
|
+
if (previous) resources.set(resourceId, { ...previous, destroyEventIndex: eventIndex });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (currentPass)
|
|
98
|
+
passes.push({
|
|
99
|
+
passIndex,
|
|
100
|
+
kind: currentPass.kind,
|
|
101
|
+
beginEventIndex: currentPass.beginEventIndex,
|
|
102
|
+
endEventIndex: undefined,
|
|
103
|
+
workIndices: currentPass.workIndices,
|
|
104
|
+
});
|
|
105
|
+
for (const resource of tape.bootstrap)
|
|
106
|
+
resources.set(resource.handleId, {
|
|
107
|
+
resourceId: resource.handleId,
|
|
108
|
+
kind: resource.kind,
|
|
109
|
+
origin: 'bootstrap',
|
|
110
|
+
createEventIndex: undefined,
|
|
111
|
+
destroyEventIndex: undefined,
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
passes,
|
|
115
|
+
works,
|
|
116
|
+
resources: [...resources.values()],
|
|
117
|
+
eventKinds: [...eventKinds],
|
|
118
|
+
passIndexByEvent,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Result } from '@forgeax/engine-types';
|
|
2
|
+
import type { RhiDebugError } from '../errors';
|
|
3
|
+
import type { RhiCallEvent as LegacyRhiCallEvent } from '../types';
|
|
4
|
+
|
|
5
|
+
export const TAPE_FORMAT_VERSION = 7 as const;
|
|
6
|
+
export const TAPE_MAGIC = 'RHITAPE' as const;
|
|
7
|
+
|
|
8
|
+
export type RhiCallEvent = LegacyRhiCallEvent;
|
|
9
|
+
export type EventKind = RhiCallEvent['kind'];
|
|
10
|
+
export type TapeBlobCompression = 'none' | 'gzip';
|
|
11
|
+
export type ResourceKind =
|
|
12
|
+
| 'buffer'
|
|
13
|
+
| 'texture'
|
|
14
|
+
| 'texture-view'
|
|
15
|
+
| 'sampler'
|
|
16
|
+
| 'shader-module'
|
|
17
|
+
| 'pipeline'
|
|
18
|
+
| 'binding'
|
|
19
|
+
| 'encoder';
|
|
20
|
+
|
|
21
|
+
export interface RhiCapsRecorded {
|
|
22
|
+
readonly [key: string]: boolean | number | string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface InitialDataSlice {
|
|
26
|
+
readonly hash: string;
|
|
27
|
+
readonly byteOffset: number;
|
|
28
|
+
readonly byteLength: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface BootstrapResource {
|
|
32
|
+
readonly handleId: string;
|
|
33
|
+
readonly kind: ResourceKind;
|
|
34
|
+
readonly create: Record<string, unknown>;
|
|
35
|
+
readonly initialData: readonly InitialDataSlice[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TapeBlob {
|
|
39
|
+
readonly hash: string;
|
|
40
|
+
readonly bytes: Uint8Array;
|
|
41
|
+
readonly compression: TapeBlobCompression;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Tape {
|
|
45
|
+
readonly header: {
|
|
46
|
+
readonly formatVersion: typeof TAPE_FORMAT_VERSION;
|
|
47
|
+
readonly rhiCaps: RhiCapsRecorded;
|
|
48
|
+
readonly eventCount: number;
|
|
49
|
+
readonly blobCount: number;
|
|
50
|
+
};
|
|
51
|
+
readonly bootstrap: readonly BootstrapResource[];
|
|
52
|
+
readonly events: readonly RhiCallEvent[];
|
|
53
|
+
readonly blobs: readonly TapeBlob[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TapeEncodeOptions {
|
|
57
|
+
readonly compression?: TapeBlobCompression;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type RhiDebugResult<T> = Result<T, RhiDebugError>;
|