@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,455 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/frame-model -- v7 structural tape projection.
|
|
2
|
+
|
|
3
|
+
/// <reference types="@webgpu/types" />
|
|
4
|
+
|
|
5
|
+
import { isWorkEvent } from './protocol/event-semantics';
|
|
6
|
+
import type { TapePassEntry, TapeResourceEntry } from './protocol/tape-index';
|
|
7
|
+
import { buildTapeIndex } from './protocol/tape-index';
|
|
8
|
+
import type { Tape as V7Tape } from './protocol/types';
|
|
9
|
+
import { computeTextureLayout } from './texel-layout';
|
|
10
|
+
import type {
|
|
11
|
+
CreateDescriptor,
|
|
12
|
+
DrawPipelineState,
|
|
13
|
+
HandleId,
|
|
14
|
+
InspectBindingEntry,
|
|
15
|
+
InspectDrawCall,
|
|
16
|
+
RhiCallEvent,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
export type { CreateDescriptor, DrawPipelineState } from './types';
|
|
20
|
+
|
|
21
|
+
export interface PassDrawItem {
|
|
22
|
+
readonly workIndex: number;
|
|
23
|
+
readonly eventKind:
|
|
24
|
+
| 'draw'
|
|
25
|
+
| 'drawIndexed'
|
|
26
|
+
| 'dispatchWorkgroups'
|
|
27
|
+
| 'dispatchWorkgroupsIndirect'
|
|
28
|
+
| 'drawIndirect'
|
|
29
|
+
| 'drawIndexedIndirect';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PassNode {
|
|
33
|
+
readonly kind: 'render' | 'compute';
|
|
34
|
+
readonly passIdx: number;
|
|
35
|
+
readonly draws: readonly PassDrawItem[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DrawEntry {
|
|
39
|
+
readonly frameIdx: number;
|
|
40
|
+
readonly passIdx: number;
|
|
41
|
+
readonly bindings: readonly InspectBindingEntry[];
|
|
42
|
+
readonly drawCall: InspectDrawCall;
|
|
43
|
+
readonly colorAttachmentHandleId: string | undefined;
|
|
44
|
+
readonly pipelineState: DrawPipelineState;
|
|
45
|
+
readonly vertexBuffers: ReadonlyMap<
|
|
46
|
+
number,
|
|
47
|
+
{ readonly handleId: HandleId; readonly offset: number; readonly size: number }
|
|
48
|
+
>;
|
|
49
|
+
readonly indexBuffer?: { handleId: HandleId; format: GPUIndexFormat; offset: number } | undefined;
|
|
50
|
+
readonly depthStencil: DrawDepthStencil;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DrawDepthStencil {
|
|
54
|
+
readonly depthStencilViewHandleId: HandleId | undefined;
|
|
55
|
+
readonly depthStencilAttachment: GPURenderPassDepthStencilAttachment | undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CommandEntry {
|
|
59
|
+
readonly passIdx: number;
|
|
60
|
+
readonly eventIdx: number;
|
|
61
|
+
readonly isDraw: boolean;
|
|
62
|
+
readonly kind: string;
|
|
63
|
+
readonly groupLabel: string | undefined;
|
|
64
|
+
readonly markerLabel: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface FrameModelMeta {
|
|
68
|
+
readonly totalDraws: number;
|
|
69
|
+
readonly totalPasses: number;
|
|
70
|
+
readonly hasCompute: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface WorkEntry {
|
|
74
|
+
readonly workIndex: number;
|
|
75
|
+
readonly eventIndex: number;
|
|
76
|
+
readonly passIndex: number;
|
|
77
|
+
readonly kind: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface FrameModel {
|
|
81
|
+
readonly tree: readonly PassNode[];
|
|
82
|
+
readonly draws: readonly DrawEntry[];
|
|
83
|
+
readonly meta: FrameModelMeta;
|
|
84
|
+
readonly commands: readonly CommandEntry[];
|
|
85
|
+
readonly resources: ReadonlyMap<HandleId, CreateDescriptor>;
|
|
86
|
+
readonly resourceLifecycle: ResourceLifecycleSummary;
|
|
87
|
+
readonly works: readonly WorkEntry[];
|
|
88
|
+
readonly passes: readonly TapePassEntry[];
|
|
89
|
+
readonly resourceEntries: readonly TapeResourceEntry[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildFrameModel(tape: V7Tape): FrameModel {
|
|
93
|
+
const index = buildTapeIndex(tape);
|
|
94
|
+
const works = index.works;
|
|
95
|
+
return {
|
|
96
|
+
tree: [],
|
|
97
|
+
draws: [],
|
|
98
|
+
meta: {
|
|
99
|
+
totalDraws: works.length,
|
|
100
|
+
totalPasses: index.passes.length,
|
|
101
|
+
hasCompute: works.some((work) => work.kind.startsWith('dispatch')),
|
|
102
|
+
},
|
|
103
|
+
commands: tape.events.map((event, eventIndex) => ({
|
|
104
|
+
passIdx: index.passIndexByEvent[eventIndex] ?? -1,
|
|
105
|
+
eventIdx: eventIndex,
|
|
106
|
+
isDraw: isWorkEvent(event.kind),
|
|
107
|
+
kind: event.kind,
|
|
108
|
+
groupLabel: undefined,
|
|
109
|
+
markerLabel: undefined,
|
|
110
|
+
})),
|
|
111
|
+
resources: new Map(),
|
|
112
|
+
resourceLifecycle: buildResourceLifecycle(tape.events),
|
|
113
|
+
works,
|
|
114
|
+
passes: index.passes,
|
|
115
|
+
resourceEntries: index.resources,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A byte estimate that distinguishes known descriptor size from unavailable GPU facts. */
|
|
120
|
+
export type ResourceByteEstimate =
|
|
121
|
+
| {
|
|
122
|
+
readonly status: 'known';
|
|
123
|
+
readonly bytes: number;
|
|
124
|
+
readonly basis: 'buffer-descriptor' | 'texture-tight-layout';
|
|
125
|
+
}
|
|
126
|
+
| {
|
|
127
|
+
readonly status: 'unavailable';
|
|
128
|
+
readonly reason:
|
|
129
|
+
| 'non-memory-resource'
|
|
130
|
+
| 'unsupported-texture-dimension'
|
|
131
|
+
| 'unsupported-texture-format'
|
|
132
|
+
| 'invalid-texture-size';
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export type ResourceKind =
|
|
136
|
+
| 'buffer'
|
|
137
|
+
| 'texture'
|
|
138
|
+
| 'texture-view'
|
|
139
|
+
| 'sampler'
|
|
140
|
+
| 'bind-group-layout'
|
|
141
|
+
| 'bind-group'
|
|
142
|
+
| 'pipeline-layout'
|
|
143
|
+
| 'render-pipeline'
|
|
144
|
+
| 'compute-pipeline'
|
|
145
|
+
| 'shader-module';
|
|
146
|
+
|
|
147
|
+
export type ResourceOrigin = 'engine' | 'swapchain';
|
|
148
|
+
|
|
149
|
+
export interface ResourceLifecycleEntry {
|
|
150
|
+
readonly handleId: HandleId;
|
|
151
|
+
readonly kind: ResourceKind;
|
|
152
|
+
readonly origin: ResourceOrigin;
|
|
153
|
+
readonly state: 'live' | 'destroyed';
|
|
154
|
+
readonly createdEventIndex: number;
|
|
155
|
+
readonly destroyedEventIndex?: number;
|
|
156
|
+
readonly byteEstimate: ResourceByteEstimate;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ResourceLifecycleSummary {
|
|
160
|
+
/** This is the resource closure represented by the tape, not the whole device heap. */
|
|
161
|
+
readonly scope: 'captured-tape-resource-closure';
|
|
162
|
+
readonly counts: {
|
|
163
|
+
readonly created: number;
|
|
164
|
+
readonly destroyed: number;
|
|
165
|
+
readonly live: number;
|
|
166
|
+
readonly destroyEvents: number;
|
|
167
|
+
readonly unknownDestroyEvents: number;
|
|
168
|
+
};
|
|
169
|
+
readonly bytes: {
|
|
170
|
+
readonly knownCreated: number;
|
|
171
|
+
readonly knownDestroyed: number;
|
|
172
|
+
readonly knownLive: number;
|
|
173
|
+
readonly unavailableCreated: number;
|
|
174
|
+
readonly unavailableDestroyed: number;
|
|
175
|
+
readonly unavailableLive: number;
|
|
176
|
+
};
|
|
177
|
+
readonly originBreakdown: Readonly<
|
|
178
|
+
Record<
|
|
179
|
+
ResourceOrigin,
|
|
180
|
+
{
|
|
181
|
+
readonly created: number;
|
|
182
|
+
readonly destroyed: number;
|
|
183
|
+
readonly live: number;
|
|
184
|
+
readonly knownCreated: number;
|
|
185
|
+
readonly knownDestroyed: number;
|
|
186
|
+
readonly knownLive: number;
|
|
187
|
+
readonly unavailableCreated: number;
|
|
188
|
+
readonly unavailableDestroyed: number;
|
|
189
|
+
readonly unavailableLive: number;
|
|
190
|
+
}
|
|
191
|
+
>
|
|
192
|
+
>;
|
|
193
|
+
readonly availability: {
|
|
194
|
+
readonly destroy: 'observed-buffer-texture';
|
|
195
|
+
readonly retire: 'unavailable';
|
|
196
|
+
readonly driverAllocation: 'unavailable';
|
|
197
|
+
};
|
|
198
|
+
readonly resources: readonly ResourceLifecycleEntry[];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function asPositiveInteger(value: unknown): number | undefined {
|
|
202
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizeTextureSize(
|
|
206
|
+
size: unknown,
|
|
207
|
+
):
|
|
208
|
+
| { readonly width: number; readonly height: number; readonly depthOrArrayLayers: number }
|
|
209
|
+
| undefined {
|
|
210
|
+
if (typeof size === 'number') {
|
|
211
|
+
const width = asPositiveInteger(size);
|
|
212
|
+
return width === undefined ? undefined : { width, height: 1, depthOrArrayLayers: 1 };
|
|
213
|
+
}
|
|
214
|
+
if (Array.isArray(size)) {
|
|
215
|
+
const width = asPositiveInteger(size[0]);
|
|
216
|
+
const height = asPositiveInteger(size[1] ?? 1);
|
|
217
|
+
const depthOrArrayLayers = asPositiveInteger(size[2] ?? 1);
|
|
218
|
+
return width === undefined || height === undefined || depthOrArrayLayers === undefined
|
|
219
|
+
? undefined
|
|
220
|
+
: { width, height, depthOrArrayLayers };
|
|
221
|
+
}
|
|
222
|
+
if (size !== null && typeof size === 'object') {
|
|
223
|
+
const raw = size as {
|
|
224
|
+
readonly width?: unknown;
|
|
225
|
+
readonly height?: unknown;
|
|
226
|
+
readonly depthOrArrayLayers?: unknown;
|
|
227
|
+
};
|
|
228
|
+
const width = asPositiveInteger(raw.width);
|
|
229
|
+
const height = asPositiveInteger(raw.height ?? 1);
|
|
230
|
+
const depthOrArrayLayers = asPositiveInteger(raw.depthOrArrayLayers ?? 1);
|
|
231
|
+
return width === undefined || height === undefined || depthOrArrayLayers === undefined
|
|
232
|
+
? undefined
|
|
233
|
+
: { width, height, depthOrArrayLayers };
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function estimateBytes(event: RhiCallEvent): ResourceByteEstimate {
|
|
239
|
+
if (event.kind === 'createBuffer') {
|
|
240
|
+
return { status: 'known', bytes: event.desc.size, basis: 'buffer-descriptor' };
|
|
241
|
+
}
|
|
242
|
+
if (event.kind !== 'createTexture') {
|
|
243
|
+
return { status: 'unavailable', reason: 'non-memory-resource' };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const dimension = event.desc.dimension ?? '2d';
|
|
247
|
+
if (dimension === '3d') {
|
|
248
|
+
return { status: 'unavailable', reason: 'unsupported-texture-dimension' };
|
|
249
|
+
}
|
|
250
|
+
const size = normalizeTextureSize(event.desc.size);
|
|
251
|
+
if (size === undefined) return { status: 'unavailable', reason: 'invalid-texture-size' };
|
|
252
|
+
const layers = dimension === '1d' ? 1 : size.depthOrArrayLayers;
|
|
253
|
+
const layout = computeTextureLayout(
|
|
254
|
+
event.desc.format,
|
|
255
|
+
size.width,
|
|
256
|
+
size.height,
|
|
257
|
+
layers,
|
|
258
|
+
event.desc.mipLevelCount ?? 1,
|
|
259
|
+
);
|
|
260
|
+
if (layout === undefined) return { status: 'unavailable', reason: 'unsupported-texture-format' };
|
|
261
|
+
return {
|
|
262
|
+
status: 'known',
|
|
263
|
+
bytes: layout.totalBytes * (event.desc.sampleCount ?? 1),
|
|
264
|
+
basis: 'texture-tight-layout',
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function resourceIdentity(
|
|
269
|
+
event: RhiCallEvent,
|
|
270
|
+
): { readonly kind: ResourceKind; readonly handleId: HandleId } | undefined {
|
|
271
|
+
switch (event.kind) {
|
|
272
|
+
case 'createBuffer':
|
|
273
|
+
return { kind: 'buffer', handleId: event.handleId };
|
|
274
|
+
case 'createTexture':
|
|
275
|
+
return { kind: 'texture', handleId: event.handleId };
|
|
276
|
+
case 'createTextureView':
|
|
277
|
+
return { kind: 'texture-view', handleId: event.resultHandleId };
|
|
278
|
+
case 'createSampler':
|
|
279
|
+
return { kind: 'sampler', handleId: event.handleId };
|
|
280
|
+
case 'createBindGroupLayout':
|
|
281
|
+
return { kind: 'bind-group-layout', handleId: event.handleId };
|
|
282
|
+
case 'createBindGroup':
|
|
283
|
+
return { kind: 'bind-group', handleId: event.handleId };
|
|
284
|
+
case 'createPipelineLayout':
|
|
285
|
+
return { kind: 'pipeline-layout', handleId: event.handleId };
|
|
286
|
+
case 'createRenderPipeline':
|
|
287
|
+
return { kind: 'render-pipeline', handleId: event.handleId };
|
|
288
|
+
case 'createComputePipeline':
|
|
289
|
+
return { kind: 'compute-pipeline', handleId: event.handleId };
|
|
290
|
+
case 'createShaderModule':
|
|
291
|
+
return { kind: 'shader-module', handleId: event.handleId };
|
|
292
|
+
default:
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function addBytes(
|
|
298
|
+
bytes: { known: number; unavailable: number },
|
|
299
|
+
estimate: ResourceByteEstimate,
|
|
300
|
+
): void {
|
|
301
|
+
if (estimate.status === 'known') bytes.known += estimate.bytes;
|
|
302
|
+
else bytes.unavailable++;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Build a pure lifecycle ledger from the ordered tape events. */
|
|
306
|
+
export function buildResourceLifecycle(events: readonly RhiCallEvent[]): ResourceLifecycleSummary {
|
|
307
|
+
const records = new Map<
|
|
308
|
+
HandleId,
|
|
309
|
+
{
|
|
310
|
+
readonly kind: ResourceKind;
|
|
311
|
+
readonly origin: ResourceOrigin;
|
|
312
|
+
readonly createdEventIndex: number;
|
|
313
|
+
readonly byteEstimate: ResourceByteEstimate;
|
|
314
|
+
destroyedEventIndex?: number;
|
|
315
|
+
}
|
|
316
|
+
>();
|
|
317
|
+
const origins = new Map<HandleId, ResourceOrigin>();
|
|
318
|
+
let destroyEvents = 0;
|
|
319
|
+
let unknownDestroyEvents = 0;
|
|
320
|
+
|
|
321
|
+
for (let eventIndex = 0; eventIndex < events.length; eventIndex++) {
|
|
322
|
+
const event = events[eventIndex];
|
|
323
|
+
if (event === undefined) continue;
|
|
324
|
+
const identity = resourceIdentity(event);
|
|
325
|
+
if (identity !== undefined) {
|
|
326
|
+
const origin: ResourceOrigin =
|
|
327
|
+
event.kind === 'createTexture' && event.origin === 'swapchain'
|
|
328
|
+
? 'swapchain'
|
|
329
|
+
: event.kind === 'createTextureView'
|
|
330
|
+
? (origins.get(event.sourceHandleId) ?? 'engine')
|
|
331
|
+
: 'engine';
|
|
332
|
+
origins.set(identity.handleId, origin);
|
|
333
|
+
records.set(identity.handleId, {
|
|
334
|
+
kind: identity.kind,
|
|
335
|
+
origin,
|
|
336
|
+
createdEventIndex: eventIndex,
|
|
337
|
+
byteEstimate: estimateBytes(event),
|
|
338
|
+
});
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (event.kind !== 'destroyBuffer' && event.kind !== 'destroyTexture') continue;
|
|
342
|
+
destroyEvents++;
|
|
343
|
+
const record = records.get(event.handleId);
|
|
344
|
+
const expectedKind = event.kind === 'destroyBuffer' ? 'buffer' : 'texture';
|
|
345
|
+
if (
|
|
346
|
+
record === undefined ||
|
|
347
|
+
record.kind !== expectedKind ||
|
|
348
|
+
record.destroyedEventIndex !== undefined
|
|
349
|
+
) {
|
|
350
|
+
unknownDestroyEvents++;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
record.destroyedEventIndex = eventIndex;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const resources: ResourceLifecycleEntry[] = [];
|
|
357
|
+
const createdBytes = { known: 0, unavailable: 0 };
|
|
358
|
+
const destroyedBytes = { known: 0, unavailable: 0 };
|
|
359
|
+
const liveBytes = { known: 0, unavailable: 0 };
|
|
360
|
+
const originBreakdown: Record<
|
|
361
|
+
ResourceOrigin,
|
|
362
|
+
{
|
|
363
|
+
created: number;
|
|
364
|
+
destroyed: number;
|
|
365
|
+
live: number;
|
|
366
|
+
knownCreated: number;
|
|
367
|
+
knownDestroyed: number;
|
|
368
|
+
knownLive: number;
|
|
369
|
+
unavailableCreated: number;
|
|
370
|
+
unavailableDestroyed: number;
|
|
371
|
+
unavailableLive: number;
|
|
372
|
+
}
|
|
373
|
+
> = {
|
|
374
|
+
engine: {
|
|
375
|
+
created: 0,
|
|
376
|
+
destroyed: 0,
|
|
377
|
+
live: 0,
|
|
378
|
+
knownCreated: 0,
|
|
379
|
+
knownDestroyed: 0,
|
|
380
|
+
knownLive: 0,
|
|
381
|
+
unavailableCreated: 0,
|
|
382
|
+
unavailableDestroyed: 0,
|
|
383
|
+
unavailableLive: 0,
|
|
384
|
+
},
|
|
385
|
+
swapchain: {
|
|
386
|
+
created: 0,
|
|
387
|
+
destroyed: 0,
|
|
388
|
+
live: 0,
|
|
389
|
+
knownCreated: 0,
|
|
390
|
+
knownDestroyed: 0,
|
|
391
|
+
knownLive: 0,
|
|
392
|
+
unavailableCreated: 0,
|
|
393
|
+
unavailableDestroyed: 0,
|
|
394
|
+
unavailableLive: 0,
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
let destroyed = 0;
|
|
398
|
+
|
|
399
|
+
for (const [handleId, record] of records) {
|
|
400
|
+
const state = record.destroyedEventIndex === undefined ? 'live' : 'destroyed';
|
|
401
|
+
if (state === 'destroyed') destroyed++;
|
|
402
|
+
addBytes(createdBytes, record.byteEstimate);
|
|
403
|
+
addBytes(state === 'live' ? liveBytes : destroyedBytes, record.byteEstimate);
|
|
404
|
+
const byOrigin = originBreakdown[record.origin];
|
|
405
|
+
byOrigin.created++;
|
|
406
|
+
if (state === 'destroyed') byOrigin.destroyed++;
|
|
407
|
+
else byOrigin.live++;
|
|
408
|
+
if (record.byteEstimate.status === 'known') {
|
|
409
|
+
byOrigin.knownCreated += record.byteEstimate.bytes;
|
|
410
|
+
if (state === 'destroyed') byOrigin.knownDestroyed += record.byteEstimate.bytes;
|
|
411
|
+
else byOrigin.knownLive += record.byteEstimate.bytes;
|
|
412
|
+
} else {
|
|
413
|
+
byOrigin.unavailableCreated++;
|
|
414
|
+
if (state === 'destroyed') byOrigin.unavailableDestroyed++;
|
|
415
|
+
else byOrigin.unavailableLive++;
|
|
416
|
+
}
|
|
417
|
+
resources.push({
|
|
418
|
+
handleId,
|
|
419
|
+
kind: record.kind,
|
|
420
|
+
origin: record.origin,
|
|
421
|
+
state,
|
|
422
|
+
createdEventIndex: record.createdEventIndex,
|
|
423
|
+
...(record.destroyedEventIndex === undefined
|
|
424
|
+
? {}
|
|
425
|
+
: { destroyedEventIndex: record.destroyedEventIndex }),
|
|
426
|
+
byteEstimate: record.byteEstimate,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
scope: 'captured-tape-resource-closure',
|
|
432
|
+
counts: {
|
|
433
|
+
created: resources.length,
|
|
434
|
+
destroyed,
|
|
435
|
+
live: resources.length - destroyed,
|
|
436
|
+
destroyEvents,
|
|
437
|
+
unknownDestroyEvents,
|
|
438
|
+
},
|
|
439
|
+
bytes: {
|
|
440
|
+
knownCreated: createdBytes.known,
|
|
441
|
+
knownDestroyed: destroyedBytes.known,
|
|
442
|
+
knownLive: liveBytes.known,
|
|
443
|
+
unavailableCreated: createdBytes.unavailable,
|
|
444
|
+
unavailableDestroyed: destroyedBytes.unavailable,
|
|
445
|
+
unavailableLive: liveBytes.unavailable,
|
|
446
|
+
},
|
|
447
|
+
originBreakdown,
|
|
448
|
+
availability: {
|
|
449
|
+
destroy: 'observed-buffer-texture',
|
|
450
|
+
retire: 'unavailable',
|
|
451
|
+
driverAllocation: 'unavailable',
|
|
452
|
+
},
|
|
453
|
+
resources,
|
|
454
|
+
};
|
|
455
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-debug/src/index.ts -- v7 public contract.
|
|
2
|
+
//
|
|
3
|
+
// The public path is one capture artifact, one FrameModel workIndex, and one
|
|
4
|
+
// fresh replay session. 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
|
+
CreateDescriptor,
|
|
17
|
+
DrawDepthStencil,
|
|
18
|
+
DrawEntry,
|
|
19
|
+
DrawPipelineState,
|
|
20
|
+
FrameModel,
|
|
21
|
+
FrameModelMeta,
|
|
22
|
+
PassDrawItem,
|
|
23
|
+
PassNode,
|
|
24
|
+
WorkEntry,
|
|
25
|
+
} from './frame-model';
|
|
26
|
+
export { buildFrameModel } from './frame-model';
|
|
27
|
+
export { decodeTape, encodeTape } from './protocol/codec';
|
|
28
|
+
export type { EventCategory, EventSemantics } from './protocol/event-semantics';
|
|
29
|
+
export {
|
|
30
|
+
EVENT_SEMANTICS,
|
|
31
|
+
eventKinds,
|
|
32
|
+
isWorkEvent,
|
|
33
|
+
resourceKindForEvent,
|
|
34
|
+
workEventKinds,
|
|
35
|
+
} from './protocol/event-semantics';
|
|
36
|
+
export type {
|
|
37
|
+
TapeIndex,
|
|
38
|
+
TapePassEntry,
|
|
39
|
+
TapeResourceEntry,
|
|
40
|
+
TapeWorkEntry,
|
|
41
|
+
} from './protocol/tape-index';
|
|
42
|
+
export { buildTapeIndex } from './protocol/tape-index';
|
|
43
|
+
export type {
|
|
44
|
+
BootstrapResource,
|
|
45
|
+
InitialDataSlice,
|
|
46
|
+
RhiCallEvent as V7RhiCallEvent,
|
|
47
|
+
RhiCapsRecorded as V7RhiCapsRecorded,
|
|
48
|
+
RhiDebugResult,
|
|
49
|
+
Tape as V7Tape,
|
|
50
|
+
TapeBlob as V7TapeBlob,
|
|
51
|
+
TapeBlobCompression,
|
|
52
|
+
TapeEncodeOptions,
|
|
53
|
+
} from './protocol/types';
|
|
54
|
+
export {
|
|
55
|
+
TAPE_FORMAT_VERSION as V7_TAPE_FORMAT_VERSION,
|
|
56
|
+
TAPE_MAGIC,
|
|
57
|
+
} from './protocol/types';
|
|
58
|
+
export { readbackTexturePixels } from './readback';
|
|
59
|
+
export {
|
|
60
|
+
attachRecorder,
|
|
61
|
+
type CaptureFrameOptions,
|
|
62
|
+
type CreateShaderModuleFn,
|
|
63
|
+
type EncodedTape,
|
|
64
|
+
type RecordableBackend,
|
|
65
|
+
type RecorderAttachment,
|
|
66
|
+
type RecorderBackend,
|
|
67
|
+
type RecorderOptions,
|
|
68
|
+
} from './recorder/session';
|
|
69
|
+
export {
|
|
70
|
+
type InspectField,
|
|
71
|
+
openReplay,
|
|
72
|
+
type ReadbackSubresource,
|
|
73
|
+
type ReplayBackend,
|
|
74
|
+
type ReplayReadbackResult,
|
|
75
|
+
type ReplaySession,
|
|
76
|
+
type TextureSubresource,
|
|
77
|
+
type WorkInspection,
|
|
78
|
+
} from './replay/session';
|
|
79
|
+
export { decodeTexelRaw, decodeToRgba8, halfToFloat } from './texel-decode';
|
|
80
|
+
export {
|
|
81
|
+
bytesPerTexel,
|
|
82
|
+
type ChannelType,
|
|
83
|
+
type FormatInfo,
|
|
84
|
+
formatInfo,
|
|
85
|
+
} from './texel-layout';
|