@forgeax/engine-rhi-debug 0.1.2
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 +139 -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__/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 +126 -0
- package/dist/frame-model.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +5810 -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/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 +25 -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 +32 -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 +34 -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 +704 -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 +106 -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 +234 -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 +8 -0
- package/src/__tests__/frame-model-parity.unit.test.ts +38 -0
- package/src/__tests__/guard-gates.test.ts +144 -0
- package/src/__tests__/public-surface.integration.test.ts +30 -0
- package/src/__tests__/readback-fault-cleanup.unit.test.ts +66 -0
- package/src/__tests__/readback-format-matrix.dawn.test.ts +155 -0
- package/src/__tests__/readback-format-matrix.unit.test.ts +96 -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 +143 -0
- package/src/__tests__/replay-session.dawn.test.ts +217 -0
- package/src/__tests__/replay-session.test-d.ts +21 -0
- package/src/__tests__/replay-session.unit.test.ts +155 -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 +176 -0
- package/src/__tests__/tape-index.unit.test.ts +53 -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 +127 -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 +455 -0
- package/src/index.ts +85 -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 +456 -0
- package/src/recorder/encoder.ts +234 -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/execute-support.ts +243 -0
- package/src/replay/execute.ts +870 -0
- package/src/replay/readback.ts +583 -0
- package/src/replay/resources.ts +154 -0
- package/src/replay/session.ts +464 -0
- package/src/replay/texture-format.ts +99 -0
- package/src/texel-decode.ts +255 -0
- package/src/texel-layout.ts +324 -0
- package/src/types.ts +864 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
2
|
+
import { createRhiDebugError, type RhiDebugError } from '../errors';
|
|
3
|
+
import type { DebugRhiInstance } from '../recorder';
|
|
4
|
+
import type { ResourceRegistry } from './resource-registry';
|
|
5
|
+
|
|
6
|
+
export interface SnapshotOptions {
|
|
7
|
+
readonly snapshotTimeoutMs: number;
|
|
8
|
+
readonly byteBudget: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function snapshotFrame(
|
|
12
|
+
recorder: DebugRhiInstance,
|
|
13
|
+
registry: ResourceRegistry,
|
|
14
|
+
options: SnapshotOptions,
|
|
15
|
+
): Promise<Result<void, RhiDebugError>> {
|
|
16
|
+
const estimatedBytes = registry.estimateSnapshotBytes();
|
|
17
|
+
const requiredBytes = Math.max(1, estimatedBytes);
|
|
18
|
+
if (requiredBytes > options.byteBudget) {
|
|
19
|
+
return err(
|
|
20
|
+
createRhiDebugError('capture-snapshot-failed', {
|
|
21
|
+
stage: 'snapshot',
|
|
22
|
+
cause: `snapshot byte budget ${options.byteBudget} is smaller than the ${requiredBytes}-byte capture minimum`,
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const result = await recorder.snapshotAllLiveResources(options.snapshotTimeoutMs);
|
|
28
|
+
if (result.ok) return ok(undefined);
|
|
29
|
+
return err(result.error);
|
|
30
|
+
} catch (cause) {
|
|
31
|
+
return err(
|
|
32
|
+
createRhiDebugError('capture-snapshot-failed', {
|
|
33
|
+
stage: 'snapshot',
|
|
34
|
+
cause: String(cause),
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/recorder/wrap -- RHI instance assembly.
|
|
2
|
+
|
|
3
|
+
/// <reference types="@webgpu/types" />
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
RequestAdapterOptions,
|
|
7
|
+
RequestDeviceOptions,
|
|
8
|
+
Result,
|
|
9
|
+
RhiAdapter,
|
|
10
|
+
RhiDevice,
|
|
11
|
+
RhiInstance,
|
|
12
|
+
ShaderModule,
|
|
13
|
+
} from '@forgeax/engine-rhi';
|
|
14
|
+
import { ok as makeOk } from '@forgeax/engine-types';
|
|
15
|
+
import type { HandleId, RhiCallEvent } from '../types';
|
|
16
|
+
import {
|
|
17
|
+
type DebugRhiInstance,
|
|
18
|
+
pushEvent,
|
|
19
|
+
type RecorderInternal,
|
|
20
|
+
RecorderState,
|
|
21
|
+
registerHandle,
|
|
22
|
+
} from './core';
|
|
23
|
+
import { createDeviceProxy } from './device';
|
|
24
|
+
import { createRecorderLifecycle } from './lifecycle';
|
|
25
|
+
|
|
26
|
+
export function wrap(instance: RhiInstance): DebugRhiInstance {
|
|
27
|
+
const s: RecorderInternal = {
|
|
28
|
+
state: RecorderState.Idle,
|
|
29
|
+
requestedFrames: 0,
|
|
30
|
+
recordedFrames: 0,
|
|
31
|
+
events: [],
|
|
32
|
+
blobPool: new Map(),
|
|
33
|
+
handleMap: new WeakMap(),
|
|
34
|
+
textureViewHandleMap: new WeakMap(),
|
|
35
|
+
bootstrapCreates: new Map(),
|
|
36
|
+
snapshotSeededHandles: new Set(),
|
|
37
|
+
snapshotGeneration: 0,
|
|
38
|
+
snapshotProgress: undefined,
|
|
39
|
+
descriptorTable: new Map(),
|
|
40
|
+
_skipRecord: false,
|
|
41
|
+
frameIdx: 0,
|
|
42
|
+
bootstrap: true,
|
|
43
|
+
recordedCaps: undefined,
|
|
44
|
+
valid: true,
|
|
45
|
+
capturedDevice: undefined,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const lifecycle = createRecorderLifecycle(s);
|
|
49
|
+
const {
|
|
50
|
+
arm,
|
|
51
|
+
onFrameEnd,
|
|
52
|
+
getTape,
|
|
53
|
+
getState,
|
|
54
|
+
getEvents,
|
|
55
|
+
getBlobPool,
|
|
56
|
+
transitionToError,
|
|
57
|
+
disposeError,
|
|
58
|
+
snapshotResource,
|
|
59
|
+
snapshotAllLiveResources,
|
|
60
|
+
} = lifecycle;
|
|
61
|
+
|
|
62
|
+
const debugInst: DebugRhiInstance = {
|
|
63
|
+
arm,
|
|
64
|
+
onFrameEnd,
|
|
65
|
+
getTape,
|
|
66
|
+
getState,
|
|
67
|
+
getEvents,
|
|
68
|
+
getBlobPool,
|
|
69
|
+
transitionToError,
|
|
70
|
+
disposeError,
|
|
71
|
+
snapshotResource,
|
|
72
|
+
snapshotAllLiveResources,
|
|
73
|
+
pushExternalEvent(event: RhiCallEvent): void {
|
|
74
|
+
pushEvent(s, event);
|
|
75
|
+
},
|
|
76
|
+
registerShaderModule(handle: ShaderModule, handleId: HandleId): void {
|
|
77
|
+
s.handleMap.set(handle as object, handleId);
|
|
78
|
+
},
|
|
79
|
+
pushExternalCreateEvent(handle: object, kind: string, event: RhiCallEvent): HandleId {
|
|
80
|
+
const hId = registerHandle(s, handle, kind, event);
|
|
81
|
+
pushEvent(s, event);
|
|
82
|
+
return hId;
|
|
83
|
+
},
|
|
84
|
+
resetForDeviceLoss(): void {
|
|
85
|
+
// A lost GPU device invalidates every opaque handle and every descriptor
|
|
86
|
+
// entry. Keep the recorder usable for the renderer's subsequent rebuild:
|
|
87
|
+
// resource and shader creation calls during recovery repopulate these
|
|
88
|
+
// registries against the fresh device before the next capture arms.
|
|
89
|
+
s.state = RecorderState.Idle;
|
|
90
|
+
s.requestedFrames = 0;
|
|
91
|
+
s.recordedFrames = 0;
|
|
92
|
+
s.events = [];
|
|
93
|
+
s.blobPool = new Map();
|
|
94
|
+
s.handleMap = new WeakMap();
|
|
95
|
+
s.textureViewHandleMap = new WeakMap();
|
|
96
|
+
s.bootstrapCreates = new Map();
|
|
97
|
+
s.descriptorTable = new Map();
|
|
98
|
+
s.snapshotSeededHandles = new Set();
|
|
99
|
+
s.snapshotProgress = undefined;
|
|
100
|
+
s.frameIdx = 0;
|
|
101
|
+
s.bootstrap = true;
|
|
102
|
+
s.recordedCaps = undefined;
|
|
103
|
+
s.valid = true;
|
|
104
|
+
s.capturedDevice = undefined;
|
|
105
|
+
},
|
|
106
|
+
valid(): boolean {
|
|
107
|
+
return s.valid;
|
|
108
|
+
},
|
|
109
|
+
bootstrapCreatesSize(): number {
|
|
110
|
+
return s.bootstrapCreates.size;
|
|
111
|
+
},
|
|
112
|
+
bootstrapEvents(): readonly RhiCallEvent[] {
|
|
113
|
+
return Array.from(s.bootstrapCreates.values());
|
|
114
|
+
},
|
|
115
|
+
descriptorTable() {
|
|
116
|
+
return s.descriptorTable;
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
async requestAdapter(
|
|
120
|
+
opts?: RequestAdapterOptions | undefined,
|
|
121
|
+
compatibleSurface?: HTMLCanvasElement | OffscreenCanvas | undefined,
|
|
122
|
+
) {
|
|
123
|
+
const res = await instance.requestAdapter(opts, compatibleSurface);
|
|
124
|
+
if (!res.ok) return res;
|
|
125
|
+
|
|
126
|
+
const realAdapter = res.value;
|
|
127
|
+
const proxyAdapter: RhiAdapter = {
|
|
128
|
+
features: realAdapter.features,
|
|
129
|
+
limits: realAdapter.limits,
|
|
130
|
+
async requestDevice(devOpts?: RequestDeviceOptions | undefined) {
|
|
131
|
+
const devRes = await realAdapter.requestDevice(devOpts);
|
|
132
|
+
if (!devRes.ok) return devRes;
|
|
133
|
+
// Keep the proxied device available for recorder-owned readback.
|
|
134
|
+
const proxied = createDeviceProxy(s, devRes.value);
|
|
135
|
+
s.capturedDevice = proxied;
|
|
136
|
+
s.recordedCaps = {
|
|
137
|
+
// RhiDevice does not own a canvas, so retain the existing tape
|
|
138
|
+
// default for this informational field while deriving every
|
|
139
|
+
// device-backed capability from the captured device.
|
|
140
|
+
canvasFormat: 'bgra8unorm' as GPUTextureFormat,
|
|
141
|
+
rgba16floatRenderable: proxied.caps.rgba16floatRenderable,
|
|
142
|
+
float32Filterable: proxied.caps.float32Filterable,
|
|
143
|
+
textureCompressionBc: proxied.caps.textureCompressionBc,
|
|
144
|
+
textureCompressionEtc2: proxied.caps.textureCompressionEtc2,
|
|
145
|
+
textureCompressionAstc: proxied.caps.textureCompressionAstc,
|
|
146
|
+
storageBuffer: proxied.caps.storageBuffer,
|
|
147
|
+
timestampQuery: proxied.caps.timestampQuery,
|
|
148
|
+
};
|
|
149
|
+
return makeOk(proxied) as Result<RhiDevice, import('@forgeax/engine-rhi').RhiError>;
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
return makeOk(proxyAdapter) as Result<RhiAdapter, import('@forgeax/engine-rhi').RhiError>;
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
return debugInst;
|
|
157
|
+
}
|
package/src/recorder.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/recorder -- split recorder public seam.
|
|
2
|
+
|
|
3
|
+
export type { CreateShaderModuleFn, DebugRhiInstance } from './recorder/core';
|
|
4
|
+
export { PER_EVENT_OVERHEAD, TAPE_FORMAT_VERSION } from './recorder/core';
|
|
5
|
+
export type {
|
|
6
|
+
CaptureFrameOptions,
|
|
7
|
+
EncodedTape,
|
|
8
|
+
RecordableBackend,
|
|
9
|
+
RecorderAttachment,
|
|
10
|
+
RecorderBackend,
|
|
11
|
+
RecorderOptions,
|
|
12
|
+
} from './recorder/session';
|
|
13
|
+
export { attachRecorder } from './recorder/session';
|
|
14
|
+
export { wrapCreateShaderModule } from './recorder/shader';
|
|
15
|
+
export { wrap } from './recorder/wrap';
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/replay/execute-support -- replay event helpers.
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
Buffer,
|
|
5
|
+
RhiCommandEncoder,
|
|
6
|
+
RhiComputePassEncoder,
|
|
7
|
+
RhiRenderPassEncoder,
|
|
8
|
+
} from '@forgeax/engine-rhi';
|
|
9
|
+
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
10
|
+
import { createRhiDebugError, type RhiDebugError } from '../errors';
|
|
11
|
+
import type { RhiCallEvent, Tape } from '../protocol/types';
|
|
12
|
+
import type { ReplayExecutionContext } from './execute';
|
|
13
|
+
import type { ReplayResource } from './resources';
|
|
14
|
+
|
|
15
|
+
type ReplayPass = RhiRenderPassEncoder & RhiComputePassEncoder;
|
|
16
|
+
|
|
17
|
+
export function eventRecord(event: RhiCallEvent): Record<string, unknown> {
|
|
18
|
+
return JSON.parse(JSON.stringify(event));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function eventFailure(
|
|
22
|
+
eventIndex: number,
|
|
23
|
+
event: RhiCallEvent,
|
|
24
|
+
stage: 'lookup' | 'create' | 'write' | 'encode' | 'finish' | 'submit',
|
|
25
|
+
cause: unknown,
|
|
26
|
+
): Result<never, RhiDebugError> {
|
|
27
|
+
return err(
|
|
28
|
+
createRhiDebugError('replay-event-failed', {
|
|
29
|
+
eventIndex,
|
|
30
|
+
kind: event.kind,
|
|
31
|
+
stage,
|
|
32
|
+
cause: cause instanceof Error ? cause.message : String(cause),
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function blob(
|
|
38
|
+
tape: Tape,
|
|
39
|
+
hash: string,
|
|
40
|
+
event: RhiCallEvent,
|
|
41
|
+
eventIndex: number,
|
|
42
|
+
): Result<Uint8Array, RhiDebugError> {
|
|
43
|
+
const found = tape.blobs.find((candidate) => candidate.hash === hash);
|
|
44
|
+
return found === undefined
|
|
45
|
+
? eventFailure(eventIndex, event, 'lookup', `blob ${hash} is missing`)
|
|
46
|
+
: ok(found.bytes);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function clearBuffer(
|
|
50
|
+
context: ReplayExecutionContext,
|
|
51
|
+
event: Extract<RhiCallEvent, { kind: 'clearBuffer' }>,
|
|
52
|
+
eventIndex: number,
|
|
53
|
+
): Result<void, RhiDebugError> {
|
|
54
|
+
const encoder = requireResource<RhiCommandEncoder>(
|
|
55
|
+
context,
|
|
56
|
+
event.cmdHandleId,
|
|
57
|
+
'encoder',
|
|
58
|
+
eventIndex,
|
|
59
|
+
event.kind,
|
|
60
|
+
'command',
|
|
61
|
+
);
|
|
62
|
+
const buffer = requireResource<Buffer>(context, event.handleId, 'buffer', eventIndex, event.kind);
|
|
63
|
+
if (!encoder.ok) return encoder;
|
|
64
|
+
if (!buffer.ok) return buffer;
|
|
65
|
+
encoder.value.clearBuffer(buffer.value, event.offset, event.size);
|
|
66
|
+
return ok(undefined);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function commandEncoderCall(
|
|
70
|
+
context: ReplayExecutionContext,
|
|
71
|
+
event: Extract<RhiCallEvent, { kind: 'pushDebugGroup' | 'popDebugGroup' | 'insertDebugMarker' }>,
|
|
72
|
+
eventIndex: number,
|
|
73
|
+
call: (encoder: RhiCommandEncoder) => void,
|
|
74
|
+
): Result<void, RhiDebugError> {
|
|
75
|
+
const encoder = requireResource<RhiCommandEncoder>(
|
|
76
|
+
context,
|
|
77
|
+
event.cmdHandleId,
|
|
78
|
+
'encoder',
|
|
79
|
+
eventIndex,
|
|
80
|
+
event.kind,
|
|
81
|
+
'command',
|
|
82
|
+
);
|
|
83
|
+
if (!encoder.ok) return encoder;
|
|
84
|
+
call(encoder.value);
|
|
85
|
+
return ok(undefined);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function finish(
|
|
89
|
+
context: ReplayExecutionContext,
|
|
90
|
+
event: Extract<RhiCallEvent, { kind: 'finish' }>,
|
|
91
|
+
eventIndex: number,
|
|
92
|
+
): Result<void, RhiDebugError> {
|
|
93
|
+
const encoder = requireResource<RhiCommandEncoder>(
|
|
94
|
+
context,
|
|
95
|
+
event.cmdHandleId,
|
|
96
|
+
'encoder',
|
|
97
|
+
eventIndex,
|
|
98
|
+
event.kind,
|
|
99
|
+
'command',
|
|
100
|
+
);
|
|
101
|
+
if (!encoder.ok) return encoder;
|
|
102
|
+
const result = encoder.value.finish();
|
|
103
|
+
if (!result.ok) return eventFailure(eventIndex, event, 'finish', result.error);
|
|
104
|
+
return context.table.set(
|
|
105
|
+
event.cmdHandleId,
|
|
106
|
+
{ kind: 'encoder', role: 'command-buffer', value: result.value },
|
|
107
|
+
eventRecord(event),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function endPass(
|
|
112
|
+
context: ReplayExecutionContext,
|
|
113
|
+
event: Extract<RhiCallEvent, { kind: 'endRenderPass' | 'endComputePass' }>,
|
|
114
|
+
eventIndex: number,
|
|
115
|
+
role: 'render-pass' | 'compute-pass',
|
|
116
|
+
): Result<void, RhiDebugError> {
|
|
117
|
+
const pass = requireResource<RhiRenderPassEncoder | RhiComputePassEncoder>(
|
|
118
|
+
context,
|
|
119
|
+
event.passHandleId,
|
|
120
|
+
'encoder',
|
|
121
|
+
eventIndex,
|
|
122
|
+
event.kind,
|
|
123
|
+
role,
|
|
124
|
+
);
|
|
125
|
+
if (!pass.ok) return pass;
|
|
126
|
+
pass.value.end();
|
|
127
|
+
context.table.delete(event.passHandleId);
|
|
128
|
+
return ok(undefined);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function seedInitialData(
|
|
132
|
+
context: ReplayExecutionContext,
|
|
133
|
+
event: Extract<RhiCallEvent, { kind: 'initialData' }>,
|
|
134
|
+
eventIndex: number,
|
|
135
|
+
): Result<void, RhiDebugError> {
|
|
136
|
+
const entry = context.table.get(event.handleId);
|
|
137
|
+
const bytes = blob(context.tape, event.dataHash, event, eventIndex);
|
|
138
|
+
if (!bytes.ok) return bytes;
|
|
139
|
+
if (entry?.resource.kind === 'buffer') {
|
|
140
|
+
const result = context.queue.writeBuffer(entry.resource.value, 0, bytes.value);
|
|
141
|
+
return result.ok ? ok(undefined) : eventFailure(eventIndex, event, 'write', result.error);
|
|
142
|
+
}
|
|
143
|
+
return eventFailure(
|
|
144
|
+
eventIndex,
|
|
145
|
+
event,
|
|
146
|
+
'lookup',
|
|
147
|
+
'initialData currently requires a buffer resource',
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function requireResource<T>(
|
|
152
|
+
context: ReplayExecutionContext,
|
|
153
|
+
resourceId: string,
|
|
154
|
+
kind: ReplayResource['kind'] | string,
|
|
155
|
+
eventIndex: number,
|
|
156
|
+
eventKind: RhiCallEvent['kind'],
|
|
157
|
+
role?: string,
|
|
158
|
+
): Result<T, RhiDebugError> {
|
|
159
|
+
const entry = context.table.get(resourceId);
|
|
160
|
+
if (
|
|
161
|
+
entry === undefined ||
|
|
162
|
+
entry.resource.kind !== kind ||
|
|
163
|
+
(role !== undefined && entry.resource.role !== role)
|
|
164
|
+
) {
|
|
165
|
+
return missingResource(
|
|
166
|
+
eventIndex,
|
|
167
|
+
{ kind: eventKind } as RhiCallEvent,
|
|
168
|
+
resourceId,
|
|
169
|
+
`${kind}${role === undefined ? '' : `/${role}`}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return ok(entry.resource.value as T);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function requireReplayResource(
|
|
176
|
+
context: ReplayExecutionContext,
|
|
177
|
+
resourceId: string,
|
|
178
|
+
kind: ReplayResource['kind'] | string,
|
|
179
|
+
eventIndex: number,
|
|
180
|
+
eventKind: RhiCallEvent['kind'],
|
|
181
|
+
role?: string,
|
|
182
|
+
): Result<ReplayResource, RhiDebugError> {
|
|
183
|
+
const entry = context.table.get(resourceId);
|
|
184
|
+
if (
|
|
185
|
+
entry === undefined ||
|
|
186
|
+
entry.resource.kind !== kind ||
|
|
187
|
+
(role !== undefined && entry.resource.role !== role)
|
|
188
|
+
) {
|
|
189
|
+
return missingResource(
|
|
190
|
+
eventIndex,
|
|
191
|
+
{ kind: eventKind } as RhiCallEvent,
|
|
192
|
+
resourceId,
|
|
193
|
+
`${kind}${role === undefined ? '' : `/${role}`}`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return ok(entry.resource);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function passCall(
|
|
200
|
+
context: ReplayExecutionContext,
|
|
201
|
+
event: RhiCallEvent,
|
|
202
|
+
eventIndex: number,
|
|
203
|
+
role: 'render-pass' | 'compute-pass' | 'pass',
|
|
204
|
+
call: (pass: ReplayPass) => Result<void, RhiDebugError>,
|
|
205
|
+
): Result<void, RhiDebugError> {
|
|
206
|
+
const passId = 'passHandleId' in event ? event.passHandleId : undefined;
|
|
207
|
+
if (passId === undefined)
|
|
208
|
+
return eventFailure(eventIndex, event, 'lookup', 'pass handle is missing');
|
|
209
|
+
const entry = context.table.get(passId);
|
|
210
|
+
const valid =
|
|
211
|
+
entry?.resource.kind === 'encoder' && (role === 'pass' || entry.resource.role === role);
|
|
212
|
+
if (!valid || entry === undefined || entry.resource.kind !== 'encoder') {
|
|
213
|
+
return missingResource(eventIndex, event, passId, role);
|
|
214
|
+
}
|
|
215
|
+
return call(entry.resource.value as ReplayPass);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function pipelineError<T>(
|
|
219
|
+
result: Result<T, RhiDebugError>,
|
|
220
|
+
event: RhiCallEvent,
|
|
221
|
+
eventIndex: number,
|
|
222
|
+
): Result<never, RhiDebugError> {
|
|
223
|
+
return result.ok
|
|
224
|
+
? eventFailure(eventIndex, event, 'lookup', 'resource has the wrong role')
|
|
225
|
+
: result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function missingResource(
|
|
229
|
+
eventIndex: number,
|
|
230
|
+
event: RhiCallEvent,
|
|
231
|
+
resourceId: string,
|
|
232
|
+
expected: string,
|
|
233
|
+
): Result<never, RhiDebugError> {
|
|
234
|
+
return eventFailure(eventIndex, event, 'lookup', `resource ${resourceId} is not a ${expected}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function unsupportedEvent(
|
|
238
|
+
eventIndex: number,
|
|
239
|
+
event: RhiCallEvent,
|
|
240
|
+
cause: string,
|
|
241
|
+
): Result<never, RhiDebugError> {
|
|
242
|
+
return eventFailure(eventIndex, event, 'lookup', cause);
|
|
243
|
+
}
|