@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
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/__tests__/recorder-bootstrap.unit.test.ts
|
|
2
|
+
//
|
|
3
|
+
// M1 bug-20260624: getTape() inFrameHandleIds backward-refs removal (fix 2).
|
|
4
|
+
// M4 fix-up: revert M1 _realDevice hook — _realDevice must stay the original
|
|
5
|
+
// RhiDevice for RAW_DEVICE_MAP reverse-lookup (rhi-webgpu createShaderModule,
|
|
6
|
+
// configureSurface etc.) to succeed. _realDevice is the raw device, NOT a
|
|
7
|
+
// hooked wrapper; bootstrap registration is the proxy path's responsibility.
|
|
8
|
+
//
|
|
9
|
+
// AC-01: tape self-containment (findDanglingHandleId=null after fix).
|
|
10
|
+
// AC-04: closure fully populated via proxy path.
|
|
11
|
+
|
|
12
|
+
// biome-ignore-all lint/suspicious/noExplicitAny: stub RHI mock objects at test boundary
|
|
13
|
+
// biome-ignore-all lint/style/noNonNullAssertion: test assertions on mock stubs guarded by expect
|
|
14
|
+
|
|
15
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
16
|
+
import { type DebugRhiInstance, wrap } from '../recorder';
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------
|
|
19
|
+
// Minimal Result helpers
|
|
20
|
+
// ---------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
function rOk<T>(value: T) {
|
|
23
|
+
return { ok: true as const, value };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function h(): any {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------
|
|
31
|
+
// Build a mock RhiInstance with a real device for bypass simulation
|
|
32
|
+
// ---------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
function buildMockInstanceWithRealDevice(): {
|
|
35
|
+
inst: any;
|
|
36
|
+
realDeviceSpies: Record<string, ReturnType<typeof vi.fn>>;
|
|
37
|
+
} {
|
|
38
|
+
const realCreateBuffer = vi.fn((_desc: any) => rOk(h()));
|
|
39
|
+
const realCreateTexture = vi.fn(() => rOk(h()));
|
|
40
|
+
const realCreateSampler = vi.fn(() => rOk(h()));
|
|
41
|
+
const realCreateBindGroupLayout = vi.fn(() => rOk(h()));
|
|
42
|
+
const realCreateBindGroup = vi.fn(() => rOk(h()));
|
|
43
|
+
const realCreatePipelineLayout = vi.fn(() => rOk(h()));
|
|
44
|
+
const realCreateRenderPipeline = vi.fn(() => rOk(h()));
|
|
45
|
+
const realCreateComputePipeline = vi.fn(() => rOk(h()));
|
|
46
|
+
|
|
47
|
+
const realDevice = {
|
|
48
|
+
caps: { rgba16floatRenderable: false },
|
|
49
|
+
features: new Set(),
|
|
50
|
+
limits: {},
|
|
51
|
+
queue: {
|
|
52
|
+
writeBuffer: vi.fn(() => rOk(undefined)),
|
|
53
|
+
submit: vi.fn(() => rOk(undefined)),
|
|
54
|
+
writeTexture: vi.fn(() => rOk(undefined)),
|
|
55
|
+
copyExternalImageToTexture: vi.fn(() => rOk(undefined)),
|
|
56
|
+
onSubmittedWorkDone: vi.fn(() => Promise.resolve(undefined)),
|
|
57
|
+
},
|
|
58
|
+
lost: new Promise(() => {}),
|
|
59
|
+
createBuffer: realCreateBuffer,
|
|
60
|
+
createTexture: realCreateTexture,
|
|
61
|
+
createTextureView: vi.fn(() => rOk(h())),
|
|
62
|
+
createSampler: realCreateSampler,
|
|
63
|
+
createBindGroupLayout: realCreateBindGroupLayout,
|
|
64
|
+
createBindGroup: realCreateBindGroup,
|
|
65
|
+
createPipelineLayout: realCreatePipelineLayout,
|
|
66
|
+
createRenderPipeline: realCreateRenderPipeline,
|
|
67
|
+
createComputePipeline: realCreateComputePipeline,
|
|
68
|
+
createCommandEncoder: vi.fn(() => rOk(h())),
|
|
69
|
+
createQuerySet: vi.fn(() => rOk(h())),
|
|
70
|
+
destroyBuffer: vi.fn(),
|
|
71
|
+
destroyTexture: vi.fn(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const adapter = {
|
|
75
|
+
features: new Set(),
|
|
76
|
+
limits: {},
|
|
77
|
+
requestDevice: vi.fn(() => Promise.resolve(rOk(realDevice))),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
inst: { requestAdapter: vi.fn(() => Promise.resolve(rOk(adapter))) },
|
|
82
|
+
realDeviceSpies: {
|
|
83
|
+
createBuffer: realCreateBuffer,
|
|
84
|
+
createTexture: realCreateTexture,
|
|
85
|
+
createSampler: realCreateSampler,
|
|
86
|
+
createBindGroupLayout: realCreateBindGroupLayout,
|
|
87
|
+
createBindGroup: realCreateBindGroup,
|
|
88
|
+
createPipelineLayout: realCreatePipelineLayout,
|
|
89
|
+
createRenderPipeline: realCreateRenderPipeline,
|
|
90
|
+
createComputePipeline: realCreateComputePipeline,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function bootstrapWithRealDevice(): Promise<{
|
|
96
|
+
debugInst: DebugRhiInstance;
|
|
97
|
+
proxyDevice: any;
|
|
98
|
+
realDevice: any;
|
|
99
|
+
}> {
|
|
100
|
+
const { inst } = buildMockInstanceWithRealDevice();
|
|
101
|
+
const debugInst = wrap(inst);
|
|
102
|
+
const adapterRes = await debugInst.requestAdapter();
|
|
103
|
+
if (!adapterRes.ok) throw new Error('adapter');
|
|
104
|
+
const adapter = (adapterRes as any).value;
|
|
105
|
+
const devRes = await adapter.requestDevice();
|
|
106
|
+
if (!devRes.ok) throw new Error('device');
|
|
107
|
+
const proxyDevice = (devRes as any).value;
|
|
108
|
+
const realDevice = proxyDevice._realDevice;
|
|
109
|
+
return { debugInst, proxyDevice, realDevice };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ================================================================
|
|
113
|
+
// _realDevice RAW_DEVICE_MAP contract (M4 fix-up)
|
|
114
|
+
// ================================================================
|
|
115
|
+
//
|
|
116
|
+
// M4 fix-up: M1 broke the RAW_DEVICE_MAP contract by replacing _realDevice
|
|
117
|
+
// with a hooked wrapper. RAW_DEVICE_MAP (rhi-webgpu WeakMap) is keyed on the
|
|
118
|
+
// original RhiDevice that makeRhiDevice registered. _realDevice must stay
|
|
119
|
+
// reference-equal to that object so reverse-lookup succeeds.
|
|
120
|
+
//
|
|
121
|
+
// Resources created through _realDevice directly bypass the recorder — this
|
|
122
|
+
// is correct: _realDevice exists only for cross-module device identity lookup.
|
|
123
|
+
|
|
124
|
+
describe('B1: _realDevice RAW_DEVICE_MAP contract (M4 fix-up)', () => {
|
|
125
|
+
it('_realDevice is reference-equal to the original real device (not hooked)', async () => {
|
|
126
|
+
const { proxyDevice } = await bootstrapWithRealDevice();
|
|
127
|
+
|
|
128
|
+
// After M4 fix-up: _realDevice must be the untampered RhiDevice so
|
|
129
|
+
// RAW_DEVICE_MAP.get(_realDevice) returns the real GPUDevice.
|
|
130
|
+
const rd = proxyDevice._realDevice;
|
|
131
|
+
expect(rd).toBeDefined();
|
|
132
|
+
expect(rd).toHaveProperty('caps');
|
|
133
|
+
expect(rd).toHaveProperty('createBuffer');
|
|
134
|
+
expect(rd).toHaveProperty('queue');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('proxy path registers resources in bootstrapCreates; _realDevice bypass does not', async () => {
|
|
138
|
+
const { debugInst, proxyDevice } = await bootstrapWithRealDevice();
|
|
139
|
+
|
|
140
|
+
// Create through proxy: enters handleMap + bootstrapCreates.
|
|
141
|
+
proxyDevice.createBuffer({ size: 64, usage: 16 });
|
|
142
|
+
expect(debugInst.bootstrapCreatesSize()).toBe(1);
|
|
143
|
+
|
|
144
|
+
// Create through raw _realDevice (not hooked anymore): bypasses recorder.
|
|
145
|
+
// This is correct — _realDevice is only for RAW_DEVICE_MAP identity lookup.
|
|
146
|
+
const realDevice = proxyDevice._realDevice;
|
|
147
|
+
realDevice.createBuffer({ size: 128, usage: 16 });
|
|
148
|
+
expect(debugInst.bootstrapCreatesSize()).toBe(1);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('proxy-only path produces self-contained tape', async () => {
|
|
152
|
+
const { debugInst, proxyDevice } = await bootstrapWithRealDevice();
|
|
153
|
+
|
|
154
|
+
const bufRes = proxyDevice.createBuffer({ size: 64, usage: 16 });
|
|
155
|
+
expect(bufRes.ok).toBe(true);
|
|
156
|
+
const buf = (bufRes as any).value;
|
|
157
|
+
|
|
158
|
+
debugInst.arm(1);
|
|
159
|
+
proxyDevice.queue.writeBuffer(buf, 0, new Uint8Array([1]));
|
|
160
|
+
debugInst.onFrameEnd();
|
|
161
|
+
|
|
162
|
+
const tape = debugInst.getTape() as any;
|
|
163
|
+
expect(tape).toBeDefined();
|
|
164
|
+
expect(tape).toHaveProperty('formatVersion');
|
|
165
|
+
// Self-contained: no dangling handleIds.
|
|
166
|
+
expect(tape).not.toHaveProperty('code');
|
|
167
|
+
|
|
168
|
+
const prefixEvents = tape.events.filter((e: any) => e.kind === 'createBuffer');
|
|
169
|
+
expect(prefixEvents.length).toBeGreaterThanOrEqual(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('bootstrapCreates preserved across arm cycles (proxy-only path)', async () => {
|
|
173
|
+
const { debugInst, proxyDevice } = await bootstrapWithRealDevice();
|
|
174
|
+
|
|
175
|
+
proxyDevice.createBuffer({ size: 64, usage: 16 });
|
|
176
|
+
expect(debugInst.bootstrapCreatesSize()).toBe(1);
|
|
177
|
+
|
|
178
|
+
// Arm + frameMark preserves bootstrapCreates.
|
|
179
|
+
debugInst.arm(1);
|
|
180
|
+
debugInst.onFrameEnd();
|
|
181
|
+
expect(debugInst.bootstrapCreatesSize()).toBe(1);
|
|
182
|
+
|
|
183
|
+
// Re-arm, create more via proxy.
|
|
184
|
+
debugInst.arm(1);
|
|
185
|
+
proxyDevice.createBuffer({ size: 128, usage: 16 });
|
|
186
|
+
debugInst.onFrameEnd();
|
|
187
|
+
expect(debugInst.bootstrapCreatesSize()).toBe(2);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('adds a requested view format to a synthetic swapchain texture event', async () => {
|
|
191
|
+
const { debugInst, proxyDevice } = await bootstrapWithRealDevice();
|
|
192
|
+
debugInst.arm(1);
|
|
193
|
+
|
|
194
|
+
const swapchainTexture = {
|
|
195
|
+
width: 1280,
|
|
196
|
+
height: 720,
|
|
197
|
+
depthOrArrayLayers: 1,
|
|
198
|
+
format: 'bgra8unorm',
|
|
199
|
+
usage: 23,
|
|
200
|
+
};
|
|
201
|
+
const view = proxyDevice.createTextureView(swapchainTexture, {
|
|
202
|
+
format: 'bgra8unorm-srgb',
|
|
203
|
+
});
|
|
204
|
+
expect(view.ok).toBe(true);
|
|
205
|
+
|
|
206
|
+
const create = debugInst
|
|
207
|
+
.bootstrapEvents()
|
|
208
|
+
.find((event: any) => event.kind === 'createTexture' && event.origin === 'swapchain') as any;
|
|
209
|
+
expect(create?.desc.viewFormats).toEqual(['bgra8unorm-srgb']);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ================================================================
|
|
214
|
+
// getTape: inFrameHandleIds no longer includes backward-refs
|
|
215
|
+
// ================================================================
|
|
216
|
+
|
|
217
|
+
describe('B1: getTape inFrameHandleIds backward-refs (fix 2)', () => {
|
|
218
|
+
it('persistent resource referenced by in-frame createBG gets bootstrap prefix', async () => {
|
|
219
|
+
const { debugInst, proxyDevice } = await bootstrapWithRealDevice();
|
|
220
|
+
|
|
221
|
+
// Create persistent buffer before arm
|
|
222
|
+
const bufRes = proxyDevice.createBuffer({ size: 64, usage: 16 });
|
|
223
|
+
expect(bufRes.ok).toBe(true);
|
|
224
|
+
const buf = (bufRes as any).value;
|
|
225
|
+
|
|
226
|
+
// Create a BGL and sampler
|
|
227
|
+
const bglRes = proxyDevice.createBindGroupLayout({ entries: [] });
|
|
228
|
+
expect(bglRes.ok).toBe(true);
|
|
229
|
+
const bgl = (bglRes as any).value;
|
|
230
|
+
|
|
231
|
+
// Arm and create a bindGroup that references the pre-arm buffer + BGL
|
|
232
|
+
debugInst.arm(1);
|
|
233
|
+
const bgRes = proxyDevice.createBindGroup({
|
|
234
|
+
layout: bgl,
|
|
235
|
+
entries: [
|
|
236
|
+
{
|
|
237
|
+
binding: 0,
|
|
238
|
+
resource: { kind: 'buffer' as const, value: { buffer: buf, offset: 0, size: 64 } },
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
expect(bgRes.ok).toBe(true);
|
|
243
|
+
|
|
244
|
+
proxyDevice.queue.writeBuffer(buf, 0, new Uint8Array([1]));
|
|
245
|
+
debugInst.onFrameEnd();
|
|
246
|
+
|
|
247
|
+
const tape = debugInst.getTape() as any;
|
|
248
|
+
expect(tape).toBeDefined();
|
|
249
|
+
expect(tape).not.toHaveProperty('code');
|
|
250
|
+
|
|
251
|
+
// With fix: the persistent buffer (created before arm) is NOT in
|
|
252
|
+
// inFrameHandleIds (backward-refs removed), so it enters prefixSeedIds
|
|
253
|
+
// and gets bootstrap prefixing. The tape is self-contained.
|
|
254
|
+
const prefixBufferCreates = tape.events.filter((e: any) => e.kind === 'createBuffer');
|
|
255
|
+
expect(prefixBufferCreates.length).toBeGreaterThanOrEqual(1);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// bug #8 (batch-3 CSM): a pre-arm resource reachable ONLY through an in-frame
|
|
259
|
+
// createBindGroup's backward edge -- with NO direct usage event naming it --
|
|
260
|
+
// must still be pulled into the bootstrap prefix. The test above passed for
|
|
261
|
+
// the wrong reason: its pre-arm buffer was ALSO hit by an in-frame writeBuffer,
|
|
262
|
+
// so _collectFrameReferencedHandleIds seeded it via the writeBuffer case. CSM's
|
|
263
|
+
// composite-pass bind group sampled a pre-arm TextureView that no usage event
|
|
264
|
+
// referenced directly, so the closure never seeded it and the tape deserialized
|
|
265
|
+
// as tape-invalid. This case isolates that path: a pre-arm buffer
|
|
266
|
+
// bound by an in-frame createBindGroup with NO writeBuffer/setBindGroup usage.
|
|
267
|
+
it('pre-arm resource referenced ONLY by in-frame createBindGroup is prefixed', async () => {
|
|
268
|
+
const { debugInst, proxyDevice } = await bootstrapWithRealDevice();
|
|
269
|
+
|
|
270
|
+
// Pre-arm buffer + BGL (created before arm; NOT touched by any usage event).
|
|
271
|
+
const bufRes = proxyDevice.createBuffer({ size: 64, usage: 16 });
|
|
272
|
+
expect(bufRes.ok).toBe(true);
|
|
273
|
+
const buf = (bufRes as any).value;
|
|
274
|
+
const bglRes = proxyDevice.createBindGroupLayout({ entries: [] });
|
|
275
|
+
expect(bglRes.ok).toBe(true);
|
|
276
|
+
const bgl = (bglRes as any).value;
|
|
277
|
+
|
|
278
|
+
// Arm, then build a bind group referencing the pre-arm buffer. Crucially:
|
|
279
|
+
// NO writeBuffer, NO setBindGroup -- the only edge to `buf` is this in-frame
|
|
280
|
+
// createBindGroup's resourceHandleIds backward ref.
|
|
281
|
+
debugInst.arm(1);
|
|
282
|
+
const bgRes = proxyDevice.createBindGroup({
|
|
283
|
+
layout: bgl,
|
|
284
|
+
entries: [
|
|
285
|
+
{
|
|
286
|
+
binding: 0,
|
|
287
|
+
resource: { kind: 'buffer' as const, value: { buffer: buf, offset: 0, size: 64 } },
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
});
|
|
291
|
+
expect(bgRes.ok).toBe(true);
|
|
292
|
+
debugInst.onFrameEnd();
|
|
293
|
+
|
|
294
|
+
const tape = debugInst.getTape() as any;
|
|
295
|
+
expect(tape).toBeDefined();
|
|
296
|
+
// Must NOT be an unstructured error (tape-invalid before the fix).
|
|
297
|
+
expect(tape).not.toHaveProperty('code');
|
|
298
|
+
// The pre-arm buffer's createBuffer must land in the prefix for self-containment.
|
|
299
|
+
const bufferCreates = tape.events.filter((e: any) => e.kind === 'createBuffer');
|
|
300
|
+
expect(bufferCreates.length).toBeGreaterThanOrEqual(1);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RhiInstance } from '@forgeax/engine-rhi';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { attachRecorder, type RecordableBackend } from '../recorder/session';
|
|
4
|
+
|
|
5
|
+
function backend(): RecordableBackend {
|
|
6
|
+
return {
|
|
7
|
+
rhi: {
|
|
8
|
+
requestAdapter: vi.fn(() => Promise.resolve({ ok: false as const, error: {} })),
|
|
9
|
+
} as unknown as RhiInstance,
|
|
10
|
+
createShaderModule: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('RecorderSession bounded options', () => {
|
|
15
|
+
it('fails before snapshot allocation when the byte budget is exhausted', async () => {
|
|
16
|
+
const attached = attachRecorder(backend());
|
|
17
|
+
expect(attached.ok).toBe(true);
|
|
18
|
+
if (!attached.ok) return;
|
|
19
|
+
|
|
20
|
+
const capture = attached.value.captureFrame({ byteBudget: 0 });
|
|
21
|
+
const boundary = await attached.value.frameBoundary();
|
|
22
|
+
expect(boundary).toMatchObject({ ok: false, error: { code: 'capture-snapshot-failed' } });
|
|
23
|
+
expect(await capture).toMatchObject({ ok: false, error: { code: 'capture-snapshot-failed' } });
|
|
24
|
+
|
|
25
|
+
const retry = attached.value.captureFrame();
|
|
26
|
+
expect((await attached.value.frameBoundary()).ok).toBe(true);
|
|
27
|
+
await attached.value.frameBoundary();
|
|
28
|
+
expect((await retry).ok).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('keeps timeout and budget as bounded options instead of frame-count controls', async () => {
|
|
32
|
+
const attached = attachRecorder(backend());
|
|
33
|
+
expect(attached.ok).toBe(true);
|
|
34
|
+
if (!attached.ok) return;
|
|
35
|
+
|
|
36
|
+
const options = { snapshotTimeoutMs: 1, byteBudget: 1024 };
|
|
37
|
+
const capture = attached.value.captureFrame(options);
|
|
38
|
+
expect((await attached.value.frameBoundary()).ok).toBe(true);
|
|
39
|
+
await attached.value.frameBoundary();
|
|
40
|
+
expect((await capture).ok).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|