@zenfg/inspector 0.1.0-beta.1

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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/dist/FrameGraphInspector.d.ts +153 -0
  4. package/dist/FrameGraphInspector.d.ts.map +1 -0
  5. package/dist/FrameGraphInspector.js +580 -0
  6. package/dist/FrameGraphInspector.js.map +1 -0
  7. package/dist/debugCaptureModel.d.ts +166 -0
  8. package/dist/debugCaptureModel.d.ts.map +1 -0
  9. package/dist/debugCaptureModel.js +364 -0
  10. package/dist/debugCaptureModel.js.map +1 -0
  11. package/dist/index.d.ts +17 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +15 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/panelAliasAnalysis.d.ts +20 -0
  16. package/dist/panelAliasAnalysis.d.ts.map +1 -0
  17. package/dist/panelAliasAnalysis.js +113 -0
  18. package/dist/panelAliasAnalysis.js.map +1 -0
  19. package/dist/panelCytoscapeGraphRenderer.d.ts +71 -0
  20. package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -0
  21. package/dist/panelCytoscapeGraphRenderer.js +559 -0
  22. package/dist/panelCytoscapeGraphRenderer.js.map +1 -0
  23. package/dist/panelDiagnosticsView.d.ts +22 -0
  24. package/dist/panelDiagnosticsView.d.ts.map +1 -0
  25. package/dist/panelDiagnosticsView.js +123 -0
  26. package/dist/panelDiagnosticsView.js.map +1 -0
  27. package/dist/panelDomHelpers.d.ts +26 -0
  28. package/dist/panelDomHelpers.d.ts.map +1 -0
  29. package/dist/panelDomHelpers.js +129 -0
  30. package/dist/panelDomHelpers.js.map +1 -0
  31. package/dist/panelGraphLayout.d.ts +28 -0
  32. package/dist/panelGraphLayout.d.ts.map +1 -0
  33. package/dist/panelGraphLayout.js +223 -0
  34. package/dist/panelGraphLayout.js.map +1 -0
  35. package/dist/panelGraphRenderer.d.ts +20 -0
  36. package/dist/panelGraphRenderer.d.ts.map +1 -0
  37. package/dist/panelGraphRenderer.js +2 -0
  38. package/dist/panelGraphRenderer.js.map +1 -0
  39. package/dist/panelGraphScene.d.ts +98 -0
  40. package/dist/panelGraphScene.d.ts.map +1 -0
  41. package/dist/panelGraphScene.js +559 -0
  42. package/dist/panelGraphScene.js.map +1 -0
  43. package/dist/panelGraphView.d.ts +9 -0
  44. package/dist/panelGraphView.d.ts.map +1 -0
  45. package/dist/panelGraphView.js +88 -0
  46. package/dist/panelGraphView.js.map +1 -0
  47. package/dist/panelGraphVisuals.d.ts +38 -0
  48. package/dist/panelGraphVisuals.d.ts.map +1 -0
  49. package/dist/panelGraphVisuals.js +330 -0
  50. package/dist/panelGraphVisuals.js.map +1 -0
  51. package/dist/panelIcons.d.ts +4 -0
  52. package/dist/panelIcons.d.ts.map +1 -0
  53. package/dist/panelIcons.js +43 -0
  54. package/dist/panelIcons.js.map +1 -0
  55. package/dist/panelInspectorView.d.ts +33 -0
  56. package/dist/panelInspectorView.d.ts.map +1 -0
  57. package/dist/panelInspectorView.js +357 -0
  58. package/dist/panelInspectorView.js.map +1 -0
  59. package/dist/panelMemoryView.d.ts +21 -0
  60. package/dist/panelMemoryView.d.ts.map +1 -0
  61. package/dist/panelMemoryView.js +134 -0
  62. package/dist/panelMemoryView.js.map +1 -0
  63. package/dist/panelPassesView.d.ts +31 -0
  64. package/dist/panelPassesView.d.ts.map +1 -0
  65. package/dist/panelPassesView.js +183 -0
  66. package/dist/panelPassesView.js.map +1 -0
  67. package/dist/panelResourcesView.d.ts +19 -0
  68. package/dist/panelResourcesView.d.ts.map +1 -0
  69. package/dist/panelResourcesView.js +89 -0
  70. package/dist/panelResourcesView.js.map +1 -0
  71. package/dist/panelSelection.d.ts +5 -0
  72. package/dist/panelSelection.d.ts.map +1 -0
  73. package/dist/panelSelection.js +88 -0
  74. package/dist/panelSelection.js.map +1 -0
  75. package/dist/panelTypes.d.ts +40 -0
  76. package/dist/panelTypes.d.ts.map +1 -0
  77. package/dist/panelTypes.js +2 -0
  78. package/dist/panelTypes.js.map +1 -0
  79. package/dist/panelVisualTheme.d.ts +76 -0
  80. package/dist/panelVisualTheme.d.ts.map +1 -0
  81. package/dist/panelVisualTheme.js +39 -0
  82. package/dist/panelVisualTheme.js.map +1 -0
  83. package/dist/panelWorkbenchHelpers.d.ts +34 -0
  84. package/dist/panelWorkbenchHelpers.d.ts.map +1 -0
  85. package/dist/panelWorkbenchHelpers.js +162 -0
  86. package/dist/panelWorkbenchHelpers.js.map +1 -0
  87. package/dist/panelWorkbenchView.d.ts +77 -0
  88. package/dist/panelWorkbenchView.d.ts.map +1 -0
  89. package/dist/panelWorkbenchView.js +409 -0
  90. package/dist/panelWorkbenchView.js.map +1 -0
  91. package/dist/query.d.ts +11 -0
  92. package/dist/query.d.ts.map +1 -0
  93. package/dist/query.js +21 -0
  94. package/dist/query.js.map +1 -0
  95. package/dist/styles.d.ts +8 -0
  96. package/dist/styles.d.ts.map +1 -0
  97. package/dist/styles.js +1073 -0
  98. package/dist/styles.js.map +1 -0
  99. package/package.json +29 -0
  100. package/src/FrameGraphInspector.ts +632 -0
  101. package/src/debugCaptureModel.ts +583 -0
  102. package/src/index.ts +21 -0
  103. package/src/panelAliasAnalysis.ts +164 -0
  104. package/src/panelCytoscapeGraphRenderer.ts +617 -0
  105. package/src/panelDiagnosticsView.ts +160 -0
  106. package/src/panelDomHelpers.ts +151 -0
  107. package/src/panelGraphLayout.ts +281 -0
  108. package/src/panelGraphRenderer.ts +21 -0
  109. package/src/panelGraphScene.ts +787 -0
  110. package/src/panelGraphView.ts +108 -0
  111. package/src/panelGraphVisuals.ts +364 -0
  112. package/src/panelIcons.ts +56 -0
  113. package/src/panelInspectorView.ts +377 -0
  114. package/src/panelMemoryView.ts +154 -0
  115. package/src/panelPassesView.ts +222 -0
  116. package/src/panelResourcesView.ts +115 -0
  117. package/src/panelSelection.ts +99 -0
  118. package/src/panelTypes.ts +28 -0
  119. package/src/panelVisualTheme.ts +39 -0
  120. package/src/panelWorkbenchHelpers.ts +216 -0
  121. package/src/panelWorkbenchView.ts +465 -0
  122. package/src/query.ts +22 -0
  123. package/src/styles.ts +1076 -0
@@ -0,0 +1,583 @@
1
+ import type {
2
+ FrameGraphSnapshot,
3
+ FrameGraphSnapshotAccessKind,
4
+ FrameGraphSnapshotBufferRange,
5
+ FrameGraphSnapshotGpuTimings,
6
+ FrameGraphSnapshotNodeKind,
7
+ FrameGraphSnapshotResourceKind,
8
+ FrameGraphSnapshotResourceOrigin,
9
+ FrameGraphSnapshotRootReason,
10
+ FrameGraphSnapshotSegment,
11
+ FrameGraphSnapshotTextureRegion,
12
+ FrameGraphSnapshotWriteContents,
13
+ } from '@zenfg/snapshot';
14
+
15
+ type ResourceAccessMode = 'read' | 'write';
16
+
17
+ export type FrameGraphDebugExecutionSegment = FrameGraphSnapshotSegment & {
18
+ readonly index: number;
19
+ };
20
+
21
+ export type FrameGraphDebugResourceRef = {
22
+ readonly id: string;
23
+ readonly kind: FrameGraphSnapshotResourceKind;
24
+ readonly label?: string;
25
+ };
26
+
27
+ type FrameGraphDebugAccessEdgeBase = {
28
+ readonly accessId: string;
29
+ readonly nodeId: string;
30
+ readonly resource: FrameGraphDebugResourceRef;
31
+ readonly access: FrameGraphSnapshotAccessKind;
32
+ readonly textureViewId?: string;
33
+ readonly textureRegion?: FrameGraphSnapshotTextureRegion;
34
+ readonly bufferRange?: FrameGraphSnapshotBufferRange;
35
+ };
36
+
37
+ export type FrameGraphDebugAccessEdge = FrameGraphDebugAccessEdgeBase & (
38
+ | {
39
+ readonly mode: 'read';
40
+ readonly producesValue: false;
41
+ readonly contents?: never;
42
+ }
43
+ | {
44
+ readonly mode: 'write';
45
+ readonly contents: FrameGraphSnapshotWriteContents;
46
+ readonly producesValue: boolean;
47
+ }
48
+ );
49
+
50
+ export type FrameGraphDebugAccess = FrameGraphDebugAccessEdge extends infer TAccess
51
+ ? TAccess extends FrameGraphDebugAccessEdge ? Omit<TAccess, 'nodeId'> : never
52
+ : never;
53
+
54
+ export type FrameGraphDebugNode = {
55
+ readonly id: string;
56
+ readonly order: number;
57
+ readonly kind: FrameGraphSnapshotNodeKind;
58
+ readonly label?: string;
59
+ readonly sideEffect: boolean;
60
+ readonly debugGroupId?: string;
61
+ readonly gpuDurationMicros?: number;
62
+ readonly reads: readonly FrameGraphDebugAccess[];
63
+ readonly writes: readonly FrameGraphDebugAccess[];
64
+ };
65
+
66
+ export type FrameGraphDebugResource = FrameGraphDebugResourceRef & {
67
+ readonly origin: FrameGraphSnapshotResourceOrigin;
68
+ readonly debugGroupId?: string;
69
+ readonly usageFlags: readonly string[];
70
+ readonly lifetime?: { readonly firstUse: number; readonly lastUse: number };
71
+ readonly physicalResourceId?: string;
72
+ readonly descriptor?: FrameGraphSnapshot['graph']['resources'][number]['descriptor'];
73
+ readonly estimatedByteSize?: number;
74
+ };
75
+
76
+ export type FrameGraphDebugEdge = {
77
+ readonly fromNodeId: string;
78
+ readonly toNodeId: string;
79
+ readonly resource: FrameGraphDebugResourceRef;
80
+ readonly kind: 'value' | 'ordering';
81
+ };
82
+
83
+ export type FrameGraphDebugRoot = {
84
+ readonly reason: FrameGraphSnapshotRootReason;
85
+ readonly nodeId?: string;
86
+ readonly resource?: FrameGraphDebugResourceRef;
87
+ };
88
+
89
+ export type FrameGraphDebugCulledNode = {
90
+ readonly node: Omit<FrameGraphDebugNode, 'order'>;
91
+ readonly reason: string;
92
+ };
93
+
94
+ export type FrameGraphDebugPhysicalAllocation = {
95
+ readonly id: string;
96
+ readonly kind: FrameGraphSnapshotResourceKind;
97
+ readonly compatibilityClassId: string;
98
+ readonly estimatedByteSize?: number;
99
+ readonly resourceIds: readonly string[];
100
+ };
101
+
102
+ export type FrameGraphDebugGroupSummary = {
103
+ readonly retainedNodeCount: number;
104
+ readonly culledNodeCount: number;
105
+ readonly nodeKindCounts: Readonly<Partial<Record<FrameGraphSnapshotNodeKind, number>>>;
106
+ readonly inputResources: readonly FrameGraphDebugResourceRef[];
107
+ readonly outputResources: readonly FrameGraphDebugResourceRef[];
108
+ readonly registeredTransientResourceCount: number;
109
+ readonly accessedTransientResourceCount: number;
110
+ readonly physicalAllocationCount: number;
111
+ readonly executionSegmentCount: number;
112
+ readonly externalSubmissionCount: number;
113
+ readonly gpuWorkDurationMicros: number;
114
+ readonly timedNodeCount: number;
115
+ readonly timingEligibleNodeCount: number;
116
+ };
117
+
118
+ export type FrameGraphDebugGroup = {
119
+ readonly id: string;
120
+ readonly parentId?: string;
121
+ readonly label: string;
122
+ readonly path: readonly string[];
123
+ /** Stable while same-named siblings retain their recording-order occurrence. */
124
+ readonly pathKey: string;
125
+ readonly depth: number;
126
+ /** Ordered ancestor ids from root through this group. */
127
+ readonly ancestorIds: readonly string[];
128
+ readonly summary: FrameGraphDebugGroupSummary;
129
+ };
130
+
131
+ export type FrameGraphDebugMetrics = {
132
+ readonly timingEligibleNodeCount: number;
133
+ readonly timedNodeCount: number;
134
+ readonly slowestNode?: FrameGraphDebugNode;
135
+ readonly transientEstimatedByteSize?: number;
136
+ readonly logicalCapacityBytes?: number;
137
+ readonly physicalEstimatedBytes?: number;
138
+ readonly aliasReuseBytes?: number;
139
+ readonly aliasedAllocationCount: number;
140
+ };
141
+
142
+ export type FrameGraphDebugSnapshotSource = {
143
+ readonly kind: 'live' | 'file' | 'programmatic';
144
+ readonly label: string;
145
+ readonly migratedFromLegacy?: boolean;
146
+ };
147
+
148
+ export type FrameGraphDebugViewModel = {
149
+ readonly protocol: FrameGraphSnapshot;
150
+ readonly source: FrameGraphDebugSnapshotSource;
151
+ readonly frameIndex: number;
152
+ readonly nodes: readonly FrameGraphDebugNode[];
153
+ readonly resources: readonly FrameGraphDebugResource[];
154
+ readonly edges: readonly FrameGraphDebugEdge[];
155
+ readonly accessEdges: readonly FrameGraphDebugAccessEdge[];
156
+ readonly roots: readonly FrameGraphDebugRoot[];
157
+ readonly culledNodes: readonly FrameGraphDebugCulledNode[];
158
+ readonly physicalAllocations: readonly FrameGraphDebugPhysicalAllocation[];
159
+ readonly debugGroups: readonly FrameGraphDebugGroup[];
160
+ readonly executionSegments: readonly FrameGraphDebugExecutionSegment[];
161
+ readonly textureViewById: ReadonlyMap<string, FrameGraphSnapshot['graph']['textureViews'][number]>;
162
+ readonly nodeById: ReadonlyMap<string, FrameGraphDebugNode>;
163
+ readonly resourceById: ReadonlyMap<string, FrameGraphDebugResource>;
164
+ readonly groupById: ReadonlyMap<string, FrameGraphDebugGroup>;
165
+ readonly groupByPathKey: ReadonlyMap<string, FrameGraphDebugGroup>;
166
+ readonly allocationById: ReadonlyMap<string, FrameGraphDebugPhysicalAllocation>;
167
+ readonly accessesByNodeId: ReadonlyMap<string, readonly FrameGraphDebugAccessEdge[]>;
168
+ readonly accessesByResourceId: ReadonlyMap<string, readonly FrameGraphDebugAccessEdge[]>;
169
+ readonly segmentByNodeId: ReadonlyMap<string, FrameGraphDebugExecutionSegment>;
170
+ readonly segmentByIndex: ReadonlyMap<number, FrameGraphDebugExecutionSegment>;
171
+ readonly metrics: FrameGraphDebugMetrics;
172
+ readonly profiling:
173
+ | {
174
+ readonly status: 'available';
175
+ readonly frameIndex: number;
176
+ readonly timings: readonly { readonly nodeId: string; readonly gpuDurationMicros: number }[];
177
+ readonly gpuFrameDurationMicros: number;
178
+ }
179
+ | {
180
+ readonly status: 'unavailable';
181
+ readonly frameIndex: number;
182
+ readonly reason: Extract<FrameGraphSnapshotGpuTimings, { readonly status: 'unavailable' }>['reason'];
183
+ readonly timings: readonly [];
184
+ };
185
+ readonly resourcePool: FrameGraphSnapshot['memory']['poolReport'];
186
+ readonly availability: {
187
+ readonly groups: boolean;
188
+ readonly textureViews: boolean;
189
+ readonly recordingOrder: boolean;
190
+ readonly accessRegions: boolean;
191
+ };
192
+ };
193
+
194
+ export function createDebugViewModel(
195
+ snapshot: FrameGraphSnapshot,
196
+ source: FrameGraphDebugSnapshotSource = { kind: 'programmatic', label: 'Programmatic' },
197
+ ): FrameGraphDebugViewModel {
198
+ const compilation = snapshot.graph;
199
+ const unavailable = new Set(snapshot.capture.migration?.unavailableFacts ?? []);
200
+ const availability = {
201
+ groups: !unavailable.has('graph.groups'),
202
+ textureViews: !unavailable.has('graph.textureViews'),
203
+ recordingOrder: !unavailable.has('graph.nodes.recordingOrder'),
204
+ accessRegions: !unavailable.has('graph.accesses.regions'),
205
+ };
206
+ const resolvedSource: FrameGraphDebugSnapshotSource = snapshot.capture.migration?.sourceFormat === 'legacy-v0'
207
+ ? { ...source, migratedFromLegacy: true }
208
+ : source;
209
+ const gpuTiming = snapshot.timings.gpu;
210
+ const resourcePool = snapshot.memory.poolReport;
211
+ const normalizedGroups = normalizeDebugGroups(compilation);
212
+ const knownGroupIds = new Set(normalizedGroups.map((group) => group.id));
213
+ validateDebugGroupReferences(compilation, knownGroupIds);
214
+ const resourcesById = new Map(compilation.resources.map((resource) => [resource.id, resource]));
215
+ const resourceRef = (resourceId: string): FrameGraphDebugResourceRef => {
216
+ const resource = resourcesById.get(resourceId);
217
+ if (!resource) {
218
+ throw new Error(`Compilation report references unknown resource ${resourceId}.`);
219
+ }
220
+ return { id: resource.id, kind: resource.kind, label: resource.label };
221
+ };
222
+ const accessEdges = compilation.accesses.map((access): FrameGraphDebugAccessEdge => {
223
+ const common = {
224
+ accessId: access.id,
225
+ nodeId: access.nodeId,
226
+ resource: resourceRef(access.resourceId),
227
+ access: access.access,
228
+ textureViewId: access.textureViewId,
229
+ textureRegion: access.textureRegion,
230
+ bufferRange: access.bufferRange,
231
+ };
232
+ return access.mode === 'read'
233
+ ? { ...common, mode: 'read', producesValue: false }
234
+ : {
235
+ ...common,
236
+ mode: 'write',
237
+ contents: access.contents,
238
+ producesValue: access.producesValue,
239
+ };
240
+ });
241
+ const accessesByNode = new Map<string, FrameGraphDebugAccessEdge[]>();
242
+ const accessesByResource = new Map<string, FrameGraphDebugAccessEdge[]>();
243
+ for (const access of accessEdges) {
244
+ const nodeAccesses = accessesByNode.get(access.nodeId) ?? [];
245
+ nodeAccesses.push(access);
246
+ accessesByNode.set(access.nodeId, nodeAccesses);
247
+ const resourceAccesses = accessesByResource.get(access.resource.id) ?? [];
248
+ resourceAccesses.push(access);
249
+ accessesByResource.set(access.resource.id, resourceAccesses);
250
+ }
251
+ const timings = gpuTiming.status === 'available'
252
+ ? new Map(gpuTiming.nodes.map((timing) => [timing.nodeId, timing.durationMicros]))
253
+ : new Map<string, number>();
254
+ const nodeAccesses = (nodeId: string, mode: ResourceAccessMode): FrameGraphDebugAccess[] => (
255
+ (accessesByNode.get(nodeId) ?? [])
256
+ .filter((access) => access.mode === mode)
257
+ .map(({ nodeId: _nodeId, ...access }) => access)
258
+ );
259
+ const nodes: FrameGraphDebugNode[] = compilation.nodes
260
+ .filter((node) => node.compileState.status === 'retained')
261
+ .sort((a, b) => a.compileState.status === 'retained' && b.compileState.status === 'retained'
262
+ ? a.compileState.executionOrder - b.compileState.executionOrder
263
+ : 0)
264
+ .map((node) => ({
265
+ id: node.id,
266
+ order: node.compileState.status === 'retained' ? node.compileState.executionOrder : 0,
267
+ kind: node.kind,
268
+ label: node.label,
269
+ sideEffect: node.sideEffect,
270
+ debugGroupId: node.groupId,
271
+ gpuDurationMicros: timings.get(node.id),
272
+ reads: nodeAccesses(node.id, 'read'),
273
+ writes: nodeAccesses(node.id, 'write'),
274
+ }));
275
+ const resources: FrameGraphDebugResource[] = compilation.resources.map((resource) => ({
276
+ id: resource.id,
277
+ kind: resource.kind,
278
+ label: resource.label,
279
+ origin: resource.origin,
280
+ debugGroupId: resource.groupId,
281
+ usageFlags: resource.usageFlags,
282
+ lifetime: resource.lifetime,
283
+ physicalResourceId: resource.allocationId,
284
+ descriptor: resource.descriptor,
285
+ estimatedByteSize: resource.estimatedByteSize,
286
+ }));
287
+ const edges: FrameGraphDebugEdge[] = compilation.dependencies.map((dependency) => ({
288
+ ...dependency,
289
+ resource: resourceRef(dependency.resourceId),
290
+ }));
291
+ const roots: FrameGraphDebugRoot[] = compilation.roots.map((root) => ({
292
+ reason: root.reason,
293
+ nodeId: root.nodeId,
294
+ resource: root.resourceId === undefined ? undefined : resourceRef(root.resourceId),
295
+ }));
296
+ const culledNodes: FrameGraphDebugCulledNode[] = compilation.nodes
297
+ .filter((node) => node.compileState.status === 'culled')
298
+ .map((node) => ({
299
+ node: {
300
+ id: node.id,
301
+ kind: node.kind,
302
+ label: node.label,
303
+ sideEffect: node.sideEffect,
304
+ debugGroupId: node.groupId,
305
+ reads: nodeAccesses(node.id, 'read'),
306
+ writes: nodeAccesses(node.id, 'write'),
307
+ },
308
+ reason: node.compileState.status === 'culled' ? node.compileState.reason : 'unknown',
309
+ }));
310
+ const resourceIdsByAllocation = new Map<string, string[]>();
311
+ for (const resource of compilation.resources) {
312
+ if (resource.allocationId === undefined) continue;
313
+ const resourceIds = resourceIdsByAllocation.get(resource.allocationId) ?? [];
314
+ resourceIds.push(resource.id);
315
+ resourceIdsByAllocation.set(resource.allocationId, resourceIds);
316
+ }
317
+ const physicalAllocations: FrameGraphDebugPhysicalAllocation[] = snapshot.memory.allocationReport.status === 'available'
318
+ ? snapshot.memory.allocationReport.allocations.map((allocation) => ({
319
+ ...allocation,
320
+ resourceIds: resourceIdsByAllocation.get(allocation.id) ?? [],
321
+ }))
322
+ : [];
323
+ const executionSegments: FrameGraphDebugExecutionSegment[] = compilation.segments.map((segment) => ({
324
+ ...segment,
325
+ index: segment.order,
326
+ }));
327
+ const debugGroups = buildDebugGroupSummaries({
328
+ groups: normalizedGroups,
329
+ nodes,
330
+ culledNodes,
331
+ resources,
332
+ accessEdges,
333
+ edges,
334
+ roots,
335
+ executionSegments,
336
+ });
337
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
338
+ const resourceById = new Map(resources.map((resource) => [resource.id, resource]));
339
+ const groupById = new Map(debugGroups.map((group) => [group.id, group]));
340
+ const groupByPathKey = new Map(debugGroups.map((group) => [group.pathKey, group]));
341
+ const allocationById = new Map(physicalAllocations.map((allocation) => [allocation.id, allocation]));
342
+ const textureViewById = new Map(compilation.textureViews.map((view) => [view.id, view]));
343
+ const segmentByNodeId = new Map<string, FrameGraphDebugExecutionSegment>();
344
+ const segmentByIndex = new Map(executionSegments.map((segment) => [segment.index, segment]));
345
+ for (const segment of executionSegments) {
346
+ for (const nodeId of segment.nodeIds) segmentByNodeId.set(nodeId, segment);
347
+ }
348
+ const timingEligibleNodes = nodes.filter((node) => node.kind === 'render' || node.kind === 'compute');
349
+ const timedNodes = timingEligibleNodes.filter((node) => node.gpuDurationMicros !== undefined);
350
+ const slowestNode = timedNodes.reduce<FrameGraphDebugNode | undefined>((slowest, node) => (
351
+ !slowest || node.gpuDurationMicros! > slowest.gpuDurationMicros! ? node : slowest
352
+ ), undefined);
353
+ const transientResources = resources.filter((resource) => resource.origin === 'transient');
354
+ const transientEstimatedByteSize = sumKnown(transientResources.map((resource) => resource.estimatedByteSize));
355
+ const physicalEstimatedBytes = sumKnown(physicalAllocations.map((allocation) => allocation.estimatedByteSize));
356
+ const logicalAllocationSizes = transientResources
357
+ .filter((resource) => resource.physicalResourceId !== undefined)
358
+ .map((resource) => allocationById.get(resource.physicalResourceId!)?.estimatedByteSize);
359
+ const logicalCapacityBytes = sumKnown(logicalAllocationSizes);
360
+ const metrics: FrameGraphDebugMetrics = {
361
+ timingEligibleNodeCount: timingEligibleNodes.length,
362
+ timedNodeCount: timedNodes.length,
363
+ slowestNode,
364
+ transientEstimatedByteSize,
365
+ logicalCapacityBytes,
366
+ physicalEstimatedBytes,
367
+ aliasReuseBytes: logicalCapacityBytes === undefined || physicalEstimatedBytes === undefined
368
+ ? undefined
369
+ : Math.max(0, logicalCapacityBytes - physicalEstimatedBytes),
370
+ aliasedAllocationCount: physicalAllocations.filter((allocation) => allocation.resourceIds.length > 1).length,
371
+ };
372
+
373
+ return {
374
+ protocol: snapshot,
375
+ source: resolvedSource,
376
+ frameIndex: snapshot.capture.frameIndex,
377
+ nodes,
378
+ resources,
379
+ edges,
380
+ accessEdges,
381
+ roots,
382
+ culledNodes,
383
+ physicalAllocations,
384
+ debugGroups,
385
+ executionSegments,
386
+ textureViewById,
387
+ nodeById,
388
+ resourceById,
389
+ groupById,
390
+ groupByPathKey,
391
+ allocationById,
392
+ accessesByNodeId: accessesByNode,
393
+ accessesByResourceId: accessesByResource,
394
+ segmentByNodeId,
395
+ segmentByIndex,
396
+ metrics,
397
+ profiling: gpuTiming.status === 'available'
398
+ ? {
399
+ status: 'available',
400
+ frameIndex: snapshot.capture.frameIndex,
401
+ timings: gpuTiming.nodes.map((timing) => ({
402
+ nodeId: timing.nodeId,
403
+ gpuDurationMicros: timing.durationMicros,
404
+ })),
405
+ gpuFrameDurationMicros: gpuTiming.frameSpanMicros,
406
+ }
407
+ : {
408
+ status: 'unavailable',
409
+ frameIndex: snapshot.capture.frameIndex,
410
+ timings: [],
411
+ reason: gpuTiming.reason,
412
+ },
413
+ resourcePool,
414
+ availability,
415
+ };
416
+ }
417
+
418
+ function sumKnown(values: readonly (number | undefined)[]): number | undefined {
419
+ let total = 0;
420
+ for (const value of values) {
421
+ if (value === undefined) return undefined;
422
+ total += value;
423
+ }
424
+ return total;
425
+ }
426
+
427
+ type NormalizedDebugGroup = Omit<FrameGraphDebugGroup, 'summary'>;
428
+
429
+ function normalizeDebugGroups(compilation: FrameGraphSnapshot['graph']): NormalizedDebugGroup[] {
430
+ const rawGroups = compilation.groups;
431
+ const rawById = new Map<string, FrameGraphSnapshot['graph']['groups'][number]>();
432
+ const siblingOccurrences = new Map<string, number>();
433
+ const nextOccurrenceByParent = new Map<string | undefined, Map<string, number>>();
434
+ for (const group of rawGroups) {
435
+ if (rawById.has(group.id)) {
436
+ throw new Error(`Compilation report contains duplicate debug group id ${group.id}.`);
437
+ }
438
+ const label = group.label.trim();
439
+ if (label.length === 0) {
440
+ throw new Error(`Compilation report debug group ${group.id} has an empty label.`);
441
+ }
442
+ rawById.set(group.id, { ...group, label });
443
+ const nextOccurrenceByLabel = nextOccurrenceByParent.get(group.parentId) ?? new Map<string, number>();
444
+ const occurrence = nextOccurrenceByLabel.get(label) ?? 0;
445
+ siblingOccurrences.set(group.id, occurrence);
446
+ nextOccurrenceByLabel.set(label, occurrence + 1);
447
+ nextOccurrenceByParent.set(group.parentId, nextOccurrenceByLabel);
448
+ }
449
+
450
+ const normalizedById = new Map<string, NormalizedDebugGroup>();
451
+ const identityPathById = new Map<string, readonly (readonly [label: string, siblingOccurrence: number])[]>();
452
+ const visiting = new Set<string>();
453
+ const resolve = (id: string): NormalizedDebugGroup => {
454
+ const existing = normalizedById.get(id);
455
+ if (existing) return existing;
456
+ const raw = rawById.get(id);
457
+ if (!raw) throw new Error(`Compilation report references unknown debug group ${id}.`);
458
+ if (visiting.has(id)) throw new Error(`Compilation report debug group ${id} has a parent cycle.`);
459
+ visiting.add(id);
460
+ const parent = raw.parentId === undefined ? undefined : resolve(raw.parentId);
461
+ const path = [...(parent?.path ?? []), raw.label];
462
+ const identityPath = [
463
+ ...(raw.parentId === undefined ? [] : identityPathById.get(raw.parentId)!),
464
+ [raw.label, siblingOccurrences.get(raw.id)!] as const,
465
+ ];
466
+ const ancestorIds = [...(parent?.ancestorIds ?? []), raw.id];
467
+ const normalized: NormalizedDebugGroup = {
468
+ id: raw.id,
469
+ parentId: raw.parentId,
470
+ label: raw.label,
471
+ path,
472
+ pathKey: JSON.stringify(identityPath),
473
+ depth: path.length - 1,
474
+ ancestorIds,
475
+ };
476
+ visiting.delete(id);
477
+ identityPathById.set(id, identityPath);
478
+ normalizedById.set(id, normalized);
479
+ return normalized;
480
+ };
481
+ return rawGroups.map((group) => resolve(group.id));
482
+ }
483
+
484
+ function validateDebugGroupReferences(
485
+ compilation: FrameGraphSnapshot['graph'],
486
+ knownGroupIds: ReadonlySet<string>,
487
+ ): void {
488
+ for (const entry of [...compilation.nodes, ...compilation.resources]) {
489
+ if (entry.groupId !== undefined && !knownGroupIds.has(entry.groupId)) {
490
+ throw new Error(`Compilation report references unknown debug group ${entry.groupId}.`);
491
+ }
492
+ }
493
+ }
494
+
495
+ function buildDebugGroupSummaries(input: {
496
+ readonly groups: readonly NormalizedDebugGroup[];
497
+ readonly nodes: readonly FrameGraphDebugNode[];
498
+ readonly culledNodes: readonly FrameGraphDebugCulledNode[];
499
+ readonly resources: readonly FrameGraphDebugResource[];
500
+ readonly accessEdges: readonly FrameGraphDebugAccessEdge[];
501
+ readonly edges: readonly FrameGraphDebugEdge[];
502
+ readonly roots: readonly FrameGraphDebugRoot[];
503
+ readonly executionSegments: readonly FrameGraphDebugExecutionSegment[];
504
+ }): FrameGraphDebugGroup[] {
505
+ const groupsById = new Map(input.groups.map((group) => [group.id, group]));
506
+ const resourcesById = new Map(input.resources.map((resource) => [resource.id, resource]));
507
+ const isInGroup = (candidateId: string | undefined, groupId: string) => (
508
+ candidateId !== undefined && groupsById.get(candidateId)?.ancestorIds.includes(groupId) === true
509
+ );
510
+
511
+ return input.groups.map((group): FrameGraphDebugGroup => {
512
+ const retained = input.nodes.filter((node) => isInGroup(node.debugGroupId, group.id));
513
+ const culled = input.culledNodes.filter((entry) => isInGroup(entry.node.debugGroupId, group.id));
514
+ const retainedIds = new Set(retained.map((node) => node.id));
515
+ const allIds = new Set([...retainedIds, ...culled.map((entry) => entry.node.id)]);
516
+ const nodeKindCounts: Partial<Record<FrameGraphSnapshotNodeKind, number>> = {};
517
+ for (const node of [...retained, ...culled.map((entry) => entry.node)]) {
518
+ nodeKindCounts[node.kind] = (nodeKindCounts[node.kind] ?? 0) + 1;
519
+ }
520
+
521
+ const inputIds = new Set<string>();
522
+ const outputIds = new Set<string>();
523
+ for (const edge of input.edges) {
524
+ const fromInside = retainedIds.has(edge.fromNodeId);
525
+ const toInside = retainedIds.has(edge.toNodeId);
526
+ if (!fromInside && toInside) inputIds.add(edge.resource.id);
527
+ if (fromInside && !toInside) outputIds.add(edge.resource.id);
528
+ }
529
+ for (const access of input.accessEdges) {
530
+ if (!retainedIds.has(access.nodeId)) continue;
531
+ const resource = resourcesById.get(access.resource.id);
532
+ const hasRetainedValueProducer = input.edges.some((edge) => (
533
+ edge.toNodeId === access.nodeId
534
+ && edge.resource.id === access.resource.id
535
+ && edge.kind === 'value'
536
+ ));
537
+ if (access.mode === 'read' && resource && resource.origin !== 'transient' && !hasRetainedValueProducer) {
538
+ inputIds.add(resource.id);
539
+ }
540
+ }
541
+ for (const root of input.roots) {
542
+ if (!root.resource) continue;
543
+ if (input.accessEdges.some((access) => retainedIds.has(access.nodeId)
544
+ && access.resource.id === root.resource!.id && access.mode === 'write')) {
545
+ outputIds.add(root.resource.id);
546
+ }
547
+ }
548
+
549
+ const registeredTransient = input.resources.filter((resource) => resource.origin === 'transient'
550
+ && isInGroup(resource.debugGroupId, group.id));
551
+ const registeredTransientIds = new Set(registeredTransient.map((resource) => resource.id));
552
+ const accessedTransientIds = new Set(input.accessEdges
553
+ .filter((access) => allIds.has(access.nodeId) && registeredTransientIds.has(access.resource.id))
554
+ .map((access) => access.resource.id));
555
+ const allocationIds = new Set(registeredTransient.flatMap((resource) => (
556
+ resource.physicalResourceId === undefined ? [] : [resource.physicalResourceId]
557
+ )));
558
+ const segmentIds = new Set(input.executionSegments
559
+ .filter((segment) => segment.nodeIds.some((id) => retainedIds.has(id)))
560
+ .map((segment) => segment.index));
561
+ const timingEligible = retained.filter((node) => node.kind === 'render' || node.kind === 'compute');
562
+ const timed = timingEligible.filter((node) => node.gpuDurationMicros !== undefined);
563
+
564
+ return {
565
+ ...group,
566
+ summary: {
567
+ retainedNodeCount: retained.length,
568
+ culledNodeCount: culled.length,
569
+ nodeKindCounts,
570
+ inputResources: [...inputIds].map((id) => resourcesById.get(id)!).filter(Boolean),
571
+ outputResources: [...outputIds].map((id) => resourcesById.get(id)!).filter(Boolean),
572
+ registeredTransientResourceCount: registeredTransient.length,
573
+ accessedTransientResourceCount: accessedTransientIds.size,
574
+ physicalAllocationCount: allocationIds.size,
575
+ executionSegmentCount: segmentIds.size,
576
+ externalSubmissionCount: retained.filter((node) => node.kind === 'external-submission').length,
577
+ gpuWorkDurationMicros: timed.reduce((sum, node) => sum + node.gpuDurationMicros!, 0),
578
+ timedNodeCount: timed.length,
579
+ timingEligibleNodeCount: timingEligible.length,
580
+ },
581
+ };
582
+ });
583
+ }
package/src/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Renders a host-sized interactive workbench for live or imported ZenFG
3
+ * FrameGraph Snapshot documents. The same component supports full-page and
4
+ * embedded layouts.
5
+ *
6
+ * {@link mountFrameGraphInspector} appends the inspector to a host element and
7
+ * returns its lifecycle controller. Call {@link FrameGraphInspector.destroy}
8
+ * before discarding the host or replacing the inspector.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+
13
+ export type {
14
+ FrameGraphSnapshot,
15
+ FrameGraphSnapshotDecodeResult,
16
+ FrameGraphSnapshotIssue,
17
+ } from '@zenfg/snapshot';
18
+ export { FrameGraphInspector, mountFrameGraphInspector } from './FrameGraphInspector.ts';
19
+ export type { FrameGraphInspectorOptions } from './FrameGraphInspector.ts';
20
+ export { ZENFG_INSPECTOR_QUERY_PARAM, isZenFGInspectorRequested } from './query.ts';
21
+ export { ensureFrameGraphInspectorStyles } from './styles.ts';