@zenfg/inspector 0.1.0-beta.2 → 0.1.0-beta.4

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 (172) hide show
  1. package/GUIDE.md +182 -0
  2. package/README.md +60 -26
  3. package/THEMING.md +124 -0
  4. package/dist/FrameGraphInspector.d.ts +24 -8
  5. package/dist/FrameGraphInspector.d.ts.map +1 -1
  6. package/dist/FrameGraphInspector.js +151 -65
  7. package/dist/FrameGraphInspector.js.map +1 -1
  8. package/dist/capture.d.ts +5 -0
  9. package/dist/capture.d.ts.map +1 -0
  10. package/dist/capture.js +2 -0
  11. package/dist/capture.js.map +1 -0
  12. package/dist/debugCaptureModel.d.ts +28 -4
  13. package/dist/debugCaptureModel.d.ts.map +1 -1
  14. package/dist/debugCaptureModel.js +72 -16
  15. package/dist/debugCaptureModel.js.map +1 -1
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/panelAliasAnalysis.d.ts +1 -0
  20. package/dist/panelAliasAnalysis.d.ts.map +1 -1
  21. package/dist/panelAliasAnalysis.js +23 -54
  22. package/dist/panelAliasAnalysis.js.map +1 -1
  23. package/dist/panelCytoscapeGraphRenderer.d.ts +18 -3
  24. package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -1
  25. package/dist/panelCytoscapeGraphRenderer.js +183 -41
  26. package/dist/panelCytoscapeGraphRenderer.js.map +1 -1
  27. package/dist/panelDetailLayout.d.ts +27 -0
  28. package/dist/panelDetailLayout.d.ts.map +1 -0
  29. package/dist/panelDetailLayout.js +174 -0
  30. package/dist/panelDetailLayout.js.map +1 -0
  31. package/dist/panelDetailStyles.d.ts +2 -0
  32. package/dist/panelDetailStyles.d.ts.map +1 -0
  33. package/dist/panelDetailStyles.js +57 -0
  34. package/dist/panelDetailStyles.js.map +1 -0
  35. package/dist/panelDiagnosticsView.d.ts +10 -1
  36. package/dist/panelDiagnosticsView.d.ts.map +1 -1
  37. package/dist/panelDiagnosticsView.js +167 -58
  38. package/dist/panelDiagnosticsView.js.map +1 -1
  39. package/dist/panelDomHelpers.d.ts.map +1 -1
  40. package/dist/panelDomHelpers.js +6 -5
  41. package/dist/panelDomHelpers.js.map +1 -1
  42. package/dist/panelGraphLayout.d.ts +3 -2
  43. package/dist/panelGraphLayout.d.ts.map +1 -1
  44. package/dist/panelGraphLayout.js +20 -5
  45. package/dist/panelGraphLayout.js.map +1 -1
  46. package/dist/panelGraphRenderer.d.ts +10 -0
  47. package/dist/panelGraphRenderer.d.ts.map +1 -1
  48. package/dist/panelGraphRenderer.js.map +1 -1
  49. package/dist/panelGraphScene.d.ts +15 -26
  50. package/dist/panelGraphScene.d.ts.map +1 -1
  51. package/dist/panelGraphScene.js +191 -208
  52. package/dist/panelGraphScene.js.map +1 -1
  53. package/dist/panelGraphSearch.d.ts +16 -0
  54. package/dist/panelGraphSearch.d.ts.map +1 -0
  55. package/dist/panelGraphSearch.js +93 -0
  56. package/dist/panelGraphSearch.js.map +1 -0
  57. package/dist/panelGraphView.d.ts +82 -0
  58. package/dist/panelGraphView.d.ts.map +1 -1
  59. package/dist/panelGraphView.js +42 -9
  60. package/dist/panelGraphView.js.map +1 -1
  61. package/dist/panelGraphVisuals.d.ts +18 -6
  62. package/dist/panelGraphVisuals.d.ts.map +1 -1
  63. package/dist/panelGraphVisuals.js +156 -116
  64. package/dist/panelGraphVisuals.js.map +1 -1
  65. package/dist/panelInspectorView.d.ts +13 -0
  66. package/dist/panelInspectorView.d.ts.map +1 -1
  67. package/dist/panelInspectorView.js +326 -46
  68. package/dist/panelInspectorView.js.map +1 -1
  69. package/dist/panelListStyles.d.ts +3 -0
  70. package/dist/panelListStyles.d.ts.map +1 -0
  71. package/dist/panelListStyles.js +155 -0
  72. package/dist/panelListStyles.js.map +1 -0
  73. package/dist/panelMemoryStyles.d.ts +3 -0
  74. package/dist/panelMemoryStyles.d.ts.map +1 -0
  75. package/dist/panelMemoryStyles.js +42 -0
  76. package/dist/panelMemoryStyles.js.map +1 -0
  77. package/dist/panelMemoryView.d.ts +13 -1
  78. package/dist/panelMemoryView.d.ts.map +1 -1
  79. package/dist/panelMemoryView.js +161 -41
  80. package/dist/panelMemoryView.js.map +1 -1
  81. package/dist/panelPassesView.d.ts +15 -0
  82. package/dist/panelPassesView.d.ts.map +1 -1
  83. package/dist/panelPassesView.js +202 -72
  84. package/dist/panelPassesView.js.map +1 -1
  85. package/dist/panelRawView.d.ts +15 -0
  86. package/dist/panelRawView.d.ts.map +1 -0
  87. package/dist/panelRawView.js +105 -0
  88. package/dist/panelRawView.js.map +1 -0
  89. package/dist/panelResourcesView.d.ts +8 -0
  90. package/dist/panelResourcesView.d.ts.map +1 -1
  91. package/dist/panelResourcesView.js +62 -31
  92. package/dist/panelResourcesView.js.map +1 -1
  93. package/dist/panelSelection.d.ts +9 -1
  94. package/dist/panelSelection.d.ts.map +1 -1
  95. package/dist/panelSelection.js +36 -6
  96. package/dist/panelSelection.js.map +1 -1
  97. package/dist/panelTheme.d.ts +13 -0
  98. package/dist/panelTheme.d.ts.map +1 -0
  99. package/dist/panelTheme.js +104 -0
  100. package/dist/panelTheme.js.map +1 -0
  101. package/dist/panelTypes.d.ts +18 -4
  102. package/dist/panelTypes.d.ts.map +1 -1
  103. package/dist/panelTypes.js.map +1 -1
  104. package/dist/panelVisualTheme.d.ts +176 -71
  105. package/dist/panelVisualTheme.d.ts.map +1 -1
  106. package/dist/panelVisualTheme.js +34 -36
  107. package/dist/panelVisualTheme.js.map +1 -1
  108. package/dist/panelWorkbenchHelpers.d.ts +12 -1
  109. package/dist/panelWorkbenchHelpers.d.ts.map +1 -1
  110. package/dist/panelWorkbenchHelpers.js +63 -2
  111. package/dist/panelWorkbenchHelpers.js.map +1 -1
  112. package/dist/panelWorkbenchStyles.d.ts +2 -0
  113. package/dist/panelWorkbenchStyles.d.ts.map +1 -0
  114. package/dist/panelWorkbenchStyles.js +101 -0
  115. package/dist/panelWorkbenchStyles.js.map +1 -0
  116. package/dist/panelWorkbenchView.d.ts +16 -0
  117. package/dist/panelWorkbenchView.d.ts.map +1 -1
  118. package/dist/panelWorkbenchView.js +228 -50
  119. package/dist/panelWorkbenchView.js.map +1 -1
  120. package/dist/query.js.map +1 -1
  121. package/dist/styles.d.ts.map +1 -1
  122. package/dist/styles.js +161 -205
  123. package/dist/styles.js.map +1 -1
  124. package/dist/theme.d.ts +22 -0
  125. package/dist/theme.d.ts.map +1 -0
  126. package/dist/theme.js +10 -0
  127. package/dist/theme.js.map +1 -0
  128. package/dist/themeDefinitions.d.ts +251 -0
  129. package/dist/themeDefinitions.d.ts.map +1 -0
  130. package/dist/themeDefinitions.js +96 -0
  131. package/dist/themeDefinitions.js.map +1 -0
  132. package/dist/themePalette.d.ts +66 -0
  133. package/dist/themePalette.d.ts.map +1 -0
  134. package/dist/themePalette.js +37 -0
  135. package/dist/themePalette.js.map +1 -0
  136. package/dist/themes.css +165 -0
  137. package/package.json +44 -10
  138. package/src/FrameGraphInspector.ts +145 -68
  139. package/src/capture.ts +4 -0
  140. package/src/debugCaptureModel.ts +97 -21
  141. package/src/index.ts +6 -2
  142. package/src/panelAliasAnalysis.ts +24 -70
  143. package/src/panelCytoscapeGraphRenderer.ts +177 -40
  144. package/src/panelDetailLayout.ts +155 -0
  145. package/src/panelDetailStyles.ts +56 -0
  146. package/src/panelDiagnosticsView.ts +167 -86
  147. package/src/panelDomHelpers.ts +4 -5
  148. package/src/panelGraphLayout.ts +20 -5
  149. package/src/panelGraphRenderer.ts +7 -0
  150. package/src/panelGraphScene.ts +204 -292
  151. package/src/panelGraphSearch.ts +96 -0
  152. package/src/panelGraphView.ts +41 -9
  153. package/src/panelGraphVisuals.ts +152 -118
  154. package/src/panelInspectorView.ts +315 -48
  155. package/src/panelListStyles.ts +154 -0
  156. package/src/panelMemoryStyles.ts +41 -0
  157. package/src/panelMemoryView.ts +178 -53
  158. package/src/panelPassesView.ts +190 -137
  159. package/src/panelRawView.ts +99 -0
  160. package/src/panelResourcesView.ts +69 -56
  161. package/src/panelSelection.ts +39 -7
  162. package/src/panelTheme.ts +85 -0
  163. package/src/panelTypes.ts +16 -4
  164. package/src/panelVisualTheme.ts +38 -38
  165. package/src/panelWorkbenchHelpers.ts +62 -4
  166. package/src/panelWorkbenchStyles.ts +100 -0
  167. package/src/panelWorkbenchView.ts +201 -52
  168. package/src/query.ts +11 -11
  169. package/src/styles.ts +162 -205
  170. package/src/theme.ts +24 -0
  171. package/src/themeDefinitions.ts +103 -0
  172. package/src/themePalette.ts +44 -0
@@ -6,7 +6,7 @@ import type {
6
6
  FrameGraphSnapshotNodeKind,
7
7
  FrameGraphSnapshotResourceKind,
8
8
  FrameGraphSnapshotResourceOrigin,
9
- FrameGraphSnapshotRootReason,
9
+ FrameGraphSnapshotRoot,
10
10
  FrameGraphSnapshotSegment,
11
11
  FrameGraphSnapshotTextureRegion,
12
12
  FrameGraphSnapshotWriteContents,
@@ -59,12 +59,14 @@ export type FrameGraphDebugNode = {
59
59
  readonly sideEffect: boolean;
60
60
  readonly debugGroupId?: string;
61
61
  readonly gpuDurationMicros?: number;
62
+ readonly cpuDurationMicros?: number;
62
63
  readonly reads: readonly FrameGraphDebugAccess[];
63
64
  readonly writes: readonly FrameGraphDebugAccess[];
64
65
  };
65
66
 
66
67
  export type FrameGraphDebugResource = FrameGraphDebugResourceRef & {
67
68
  readonly origin: FrameGraphSnapshotResourceOrigin;
69
+ readonly initialContents?: 'defined' | 'undefined';
68
70
  readonly debugGroupId?: string;
69
71
  readonly usageFlags: readonly string[];
70
72
  readonly lifetime?: { readonly firstUse: number; readonly lastUse: number };
@@ -80,12 +82,19 @@ export type FrameGraphDebugEdge = {
80
82
  readonly kind: 'value' | 'ordering';
81
83
  };
82
84
 
83
- export type FrameGraphDebugRoot = {
84
- readonly reason: FrameGraphSnapshotRootReason;
85
- readonly nodeId?: string;
85
+ export type FrameGraphDebugRoot = FrameGraphSnapshotRoot & {
86
+ readonly key: string;
86
87
  readonly resource?: FrameGraphDebugResourceRef;
87
88
  };
88
89
 
90
+ /** Semantic identity within one recording, independent of root array order. */
91
+ export function rootKey(root: FrameGraphSnapshotRoot): string {
92
+ return root.reason === 'side-effect' ? JSON.stringify([root.reason, root.nodeId])
93
+ : JSON.stringify([root.reason, root.resourceId, root.range?.kind === 'buffer'
94
+ ? ['buffer', root.range.offset, root.range.size]
95
+ : root.range?.regions.map((r) => [r.baseMipLevel, r.mipLevelCount, r.aspect, r.baseArrayLayer, r.arrayLayerCount, r.baseDepthSlice, r.depthSliceCount]) ?? null]);
96
+ }
97
+
89
98
  export type FrameGraphDebugCulledNode = {
90
99
  readonly node: Omit<FrameGraphDebugNode, 'order'>;
91
100
  readonly reason: string;
@@ -105,6 +114,7 @@ export type FrameGraphDebugGroupSummary = {
105
114
  readonly nodeKindCounts: Readonly<Partial<Record<FrameGraphSnapshotNodeKind, number>>>;
106
115
  readonly inputResources: readonly FrameGraphDebugResourceRef[];
107
116
  readonly outputResources: readonly FrameGraphDebugResourceRef[];
117
+ readonly outputRoots: readonly FrameGraphDebugRoot[];
108
118
  readonly registeredTransientResourceCount: number;
109
119
  readonly accessedTransientResourceCount: number;
110
120
  readonly physicalAllocationCount: number;
@@ -112,6 +122,8 @@ export type FrameGraphDebugGroupSummary = {
112
122
  readonly externalSubmissionCount: number;
113
123
  readonly gpuWorkDurationMicros: number;
114
124
  readonly timedNodeCount: number;
125
+ readonly cpuTimedNodeCount: number;
126
+ readonly cpuWorkDurationMicros: number;
115
127
  readonly timingEligibleNodeCount: number;
116
128
  };
117
129
 
@@ -131,14 +143,23 @@ export type FrameGraphDebugGroup = {
131
143
  export type FrameGraphDebugMetrics = {
132
144
  readonly timingEligibleNodeCount: number;
133
145
  readonly timedNodeCount: number;
146
+ readonly cpuTimedNodeCount: number;
147
+ readonly cpuWorkDurationMicros: number;
134
148
  readonly slowestNode?: FrameGraphDebugNode;
135
149
  readonly transientEstimatedByteSize?: number;
136
150
  readonly logicalCapacityBytes?: number;
137
151
  readonly physicalEstimatedBytes?: number;
138
152
  readonly aliasReuseBytes?: number;
139
153
  readonly aliasedAllocationCount: number;
154
+ readonly estimatedCoverage: {
155
+ readonly transient: EstimateCoverage;
156
+ readonly logical: EstimateCoverage;
157
+ readonly physical: EstimateCoverage;
158
+ };
140
159
  };
141
160
 
161
+ export type EstimateCoverage = { readonly known: number; readonly total: number };
162
+
142
163
  export type FrameGraphDebugSnapshotSource = {
143
164
  readonly kind: 'live' | 'file' | 'programmatic';
144
165
  readonly label: string;
@@ -160,6 +181,10 @@ export type FrameGraphDebugViewModel = {
160
181
  readonly executionSegments: readonly FrameGraphDebugExecutionSegment[];
161
182
  readonly textureViewById: ReadonlyMap<string, FrameGraphSnapshot['graph']['textureViews'][number]>;
162
183
  readonly nodeById: ReadonlyMap<string, FrameGraphDebugNode>;
184
+ readonly canonicalNodeById: ReadonlyMap<string, FrameGraphSnapshot['graph']['nodes'][number]>;
185
+ readonly culledById: ReadonlyMap<string, FrameGraphDebugCulledNode>;
186
+ readonly diagnosticsByNodeId: ReadonlyMap<string, readonly FrameGraphSnapshot['diagnostics'][number][]>;
187
+ readonly diagnosticsByResourceId: ReadonlyMap<string, readonly FrameGraphSnapshot['diagnostics'][number][]>;
163
188
  readonly resourceById: ReadonlyMap<string, FrameGraphDebugResource>;
164
189
  readonly groupById: ReadonlyMap<string, FrameGraphDebugGroup>;
165
190
  readonly groupByPathKey: ReadonlyMap<string, FrameGraphDebugGroup>;
@@ -169,6 +194,7 @@ export type FrameGraphDebugViewModel = {
169
194
  readonly segmentByNodeId: ReadonlyMap<string, FrameGraphDebugExecutionSegment>;
170
195
  readonly segmentByIndex: ReadonlyMap<number, FrameGraphDebugExecutionSegment>;
171
196
  readonly metrics: FrameGraphDebugMetrics;
197
+ readonly cpuProfiling: FrameGraphSnapshot['timings']['cpu'];
172
198
  readonly profiling:
173
199
  | {
174
200
  readonly status: 'available';
@@ -207,6 +233,7 @@ export function createDebugViewModel(
207
233
  ? { ...source, migratedFromLegacy: true }
208
234
  : source;
209
235
  const gpuTiming = snapshot.timings.gpu;
236
+ const cpuTimings = new Map(snapshot.timings.cpu.status === 'available' ? snapshot.timings.cpu.nodes.map(node => [node.nodeId, node.durationMicros]) : []);
210
237
  const resourcePool = snapshot.memory.poolReport;
211
238
  const normalizedGroups = normalizeDebugGroups(compilation);
212
239
  const knownGroupIds = new Set(normalizedGroups.map((group) => group.id));
@@ -269,6 +296,7 @@ export function createDebugViewModel(
269
296
  sideEffect: node.sideEffect,
270
297
  debugGroupId: node.groupId,
271
298
  gpuDurationMicros: timings.get(node.id),
299
+ cpuDurationMicros: cpuTimings.get(node.id),
272
300
  reads: nodeAccesses(node.id, 'read'),
273
301
  writes: nodeAccesses(node.id, 'write'),
274
302
  }));
@@ -277,6 +305,7 @@ export function createDebugViewModel(
277
305
  kind: resource.kind,
278
306
  label: resource.label,
279
307
  origin: resource.origin,
308
+ initialContents: resource.initialContents,
280
309
  debugGroupId: resource.groupId,
281
310
  usageFlags: resource.usageFlags,
282
311
  lifetime: resource.lifetime,
@@ -289,8 +318,8 @@ export function createDebugViewModel(
289
318
  resource: resourceRef(dependency.resourceId),
290
319
  }));
291
320
  const roots: FrameGraphDebugRoot[] = compilation.roots.map((root) => ({
292
- reason: root.reason,
293
- nodeId: root.nodeId,
321
+ ...root,
322
+ key: rootKey(root),
294
323
  resource: root.resourceId === undefined ? undefined : resourceRef(root.resourceId),
295
324
  }));
296
325
  const culledNodes: FrameGraphDebugCulledNode[] = compilation.nodes
@@ -335,6 +364,18 @@ export function createDebugViewModel(
335
364
  executionSegments,
336
365
  });
337
366
  const nodeById = new Map(nodes.map((node) => [node.id, node]));
367
+ const canonicalNodeById = new Map(compilation.nodes.map((node) => [node.id, node]));
368
+ const culledById = new Map(culledNodes.map((entry) => [entry.node.id, entry]));
369
+ const diagnosticsByNodeId = new Map<string, FrameGraphSnapshot['diagnostics'][number][]>();
370
+ const diagnosticsByResourceId = new Map<string, FrameGraphSnapshot['diagnostics'][number][]>();
371
+ for (const diagnostic of snapshot.diagnostics) {
372
+ for (const [id, index] of [[diagnostic.nodeId, diagnosticsByNodeId], [diagnostic.resourceId, diagnosticsByResourceId]] as const) {
373
+ if (id === undefined) continue;
374
+ const entries = index.get(id) ?? [];
375
+ entries.push(diagnostic);
376
+ index.set(id, entries);
377
+ }
378
+ }
338
379
  const resourceById = new Map(resources.map((resource) => [resource.id, resource]));
339
380
  const groupById = new Map(debugGroups.map((group) => [group.id, group]));
340
381
  const groupByPathKey = new Map(debugGroups.map((group) => [group.pathKey, group]));
@@ -352,14 +393,18 @@ export function createDebugViewModel(
352
393
  ), undefined);
353
394
  const transientResources = resources.filter((resource) => resource.origin === 'transient');
354
395
  const transientEstimatedByteSize = sumKnown(transientResources.map((resource) => resource.estimatedByteSize));
355
- const physicalEstimatedBytes = sumKnown(physicalAllocations.map((allocation) => allocation.estimatedByteSize));
396
+ const allocationAvailable = snapshot.memory.allocationReport.status === 'available';
397
+ const physicalSizes = physicalAllocations.map((allocation) => allocation.estimatedByteSize);
398
+ const physicalEstimatedBytes = allocationAvailable ? sumKnown(physicalSizes) : undefined;
356
399
  const logicalAllocationSizes = transientResources
357
400
  .filter((resource) => resource.physicalResourceId !== undefined)
358
401
  .map((resource) => allocationById.get(resource.physicalResourceId!)?.estimatedByteSize);
359
- const logicalCapacityBytes = sumKnown(logicalAllocationSizes);
402
+ const logicalCapacityBytes = allocationAvailable ? sumKnown(logicalAllocationSizes) : undefined;
360
403
  const metrics: FrameGraphDebugMetrics = {
361
404
  timingEligibleNodeCount: timingEligibleNodes.length,
362
405
  timedNodeCount: timedNodes.length,
406
+ cpuTimedNodeCount: nodes.filter(node => node.cpuDurationMicros !== undefined).length,
407
+ cpuWorkDurationMicros: nodes.reduce((sum, node) => sum + (node.cpuDurationMicros ?? 0), 0),
363
408
  slowestNode,
364
409
  transientEstimatedByteSize,
365
410
  logicalCapacityBytes,
@@ -368,6 +413,11 @@ export function createDebugViewModel(
368
413
  ? undefined
369
414
  : Math.max(0, logicalCapacityBytes - physicalEstimatedBytes),
370
415
  aliasedAllocationCount: physicalAllocations.filter((allocation) => allocation.resourceIds.length > 1).length,
416
+ estimatedCoverage: {
417
+ transient: estimateCoverage(transientResources.map((resource) => resource.estimatedByteSize)),
418
+ logical: estimateCoverage(logicalAllocationSizes),
419
+ physical: estimateCoverage(physicalSizes),
420
+ },
371
421
  };
372
422
 
373
423
  return {
@@ -385,6 +435,10 @@ export function createDebugViewModel(
385
435
  executionSegments,
386
436
  textureViewById,
387
437
  nodeById,
438
+ canonicalNodeById,
439
+ culledById,
440
+ diagnosticsByNodeId,
441
+ diagnosticsByResourceId,
388
442
  resourceById,
389
443
  groupById,
390
444
  groupByPathKey,
@@ -394,6 +448,7 @@ export function createDebugViewModel(
394
448
  segmentByNodeId,
395
449
  segmentByIndex,
396
450
  metrics,
451
+ cpuProfiling: snapshot.timings.cpu,
397
452
  profiling: gpuTiming.status === 'available'
398
453
  ? {
399
454
  status: 'available',
@@ -415,6 +470,10 @@ export function createDebugViewModel(
415
470
  };
416
471
  }
417
472
 
473
+ function estimateCoverage(values: readonly (number | undefined)[]): EstimateCoverage {
474
+ return { known: values.filter((value) => value !== undefined).length, total: values.length };
475
+ }
476
+
418
477
  function sumKnown(values: readonly (number | undefined)[]): number | undefined {
419
478
  let total = 0;
420
479
  for (const value of values) {
@@ -492,6 +551,23 @@ function validateDebugGroupReferences(
492
551
  }
493
552
  }
494
553
 
554
+ /** Declaration boundaries are topological, not per-range initial-value provenance. */
555
+ export function declarationEntrances(
556
+ nodes: readonly FrameGraphDebugNode[],
557
+ accesses: readonly FrameGraphDebugAccessEdge[],
558
+ edges: readonly FrameGraphDebugEdge[],
559
+ ): readonly FrameGraphDebugAccessEdge[] {
560
+ const retained = new Set(nodes.map((node) => node.id));
561
+ const incoming = new Set(edges.map((edge) => JSON.stringify([edge.resource.id, edge.toNodeId])));
562
+ const seen = new Set<string>();
563
+ return accesses.filter((access) => {
564
+ const key = JSON.stringify([access.resource.id, access.nodeId]);
565
+ if (!retained.has(access.nodeId) || incoming.has(key) || seen.has(key)) return false;
566
+ seen.add(key);
567
+ return true;
568
+ });
569
+ }
570
+
495
571
  function buildDebugGroupSummaries(input: {
496
572
  readonly groups: readonly NormalizedDebugGroup[];
497
573
  readonly nodes: readonly FrameGraphDebugNode[];
@@ -504,6 +580,7 @@ function buildDebugGroupSummaries(input: {
504
580
  }): FrameGraphDebugGroup[] {
505
581
  const groupsById = new Map(input.groups.map((group) => [group.id, group]));
506
582
  const resourcesById = new Map(input.resources.map((resource) => [resource.id, resource]));
583
+ const entrances = declarationEntrances(input.nodes, input.accessEdges, input.edges);
507
584
  const isInGroup = (candidateId: string | undefined, groupId: string) => (
508
585
  candidateId !== undefined && groupsById.get(candidateId)?.ancestorIds.includes(groupId) === true
509
586
  );
@@ -526,23 +603,19 @@ function buildDebugGroupSummaries(input: {
526
603
  if (!fromInside && toInside) inputIds.add(edge.resource.id);
527
604
  if (fromInside && !toInside) outputIds.add(edge.resource.id);
528
605
  }
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
- }
606
+ for (const access of entrances) {
607
+ const fromInside = isInGroup(resourcesById.get(access.resource.id)?.debugGroupId, group.id);
608
+ const toInside = retainedIds.has(access.nodeId);
609
+ if (!fromInside && toInside) inputIds.add(access.resource.id);
610
+ if (fromInside && !toInside) outputIds.add(access.resource.id);
540
611
  }
612
+ const outputRoots: FrameGraphDebugRoot[] = [];
541
613
  for (const root of input.roots) {
542
614
  if (!root.resource) continue;
543
- if (input.accessEdges.some((access) => retainedIds.has(access.nodeId)
544
- && access.resource.id === root.resource!.id && access.mode === 'write')) {
615
+ if (root.resolution?.producerNodeIds.some((id) => retainedIds.has(id))
616
+ || root.resolution?.usesInitialContents && isInGroup(resourcesById.get(root.resource.id)?.debugGroupId, group.id)) {
545
617
  outputIds.add(root.resource.id);
618
+ outputRoots.push(root);
546
619
  }
547
620
  }
548
621
 
@@ -569,6 +642,7 @@ function buildDebugGroupSummaries(input: {
569
642
  nodeKindCounts,
570
643
  inputResources: [...inputIds].map((id) => resourcesById.get(id)!).filter(Boolean),
571
644
  outputResources: [...outputIds].map((id) => resourcesById.get(id)!).filter(Boolean),
645
+ outputRoots,
572
646
  registeredTransientResourceCount: registeredTransient.length,
573
647
  accessedTransientResourceCount: accessedTransientIds.size,
574
648
  physicalAllocationCount: allocationIds.size,
@@ -576,6 +650,8 @@ function buildDebugGroupSummaries(input: {
576
650
  externalSubmissionCount: retained.filter((node) => node.kind === 'external-submission').length,
577
651
  gpuWorkDurationMicros: timed.reduce((sum, node) => sum + node.gpuDurationMicros!, 0),
578
652
  timedNodeCount: timed.length,
653
+ cpuTimedNodeCount: retained.filter(node => node.cpuDurationMicros !== undefined).length,
654
+ cpuWorkDurationMicros: retained.reduce((sum, node) => sum + (node.cpuDurationMicros ?? 0), 0),
579
655
  timingEligibleNodeCount: timingEligible.length,
580
656
  },
581
657
  };
package/src/index.ts CHANGED
@@ -17,5 +17,9 @@ export type {
17
17
  } from '@zenfg/snapshot';
18
18
  export { FrameGraphInspector, mountFrameGraphInspector } from './FrameGraphInspector.ts';
19
19
  export type { FrameGraphInspectorOptions } from './FrameGraphInspector.ts';
20
- export { ZENFG_INSPECTOR_QUERY_PARAM, isZenFGInspectorRequested } from './query.ts';
21
- export { ensureFrameGraphInspectorStyles } from './styles.ts';
20
+ export { ZENFG_INSPECTOR_QUERY_PARAM, isZenFGInspectorRequested } from './query.ts';
21
+ export { ensureFrameGraphInspectorStyles } from './styles.ts';
22
+
23
+ export type { InspectorTheme, InspectorThemeVariables } from './theme.ts';
24
+
25
+ export type { FrameGraphCaptureRequest } from './capture.ts';
@@ -22,9 +22,16 @@ export type AliasAnalysis = {
22
22
  readonly resources: ReadonlyMap<string, AliasAnalysisResource>;
23
23
  readonly minUse: number;
24
24
  readonly maxUse: number;
25
+ readonly hasLifetimes: boolean;
25
26
  };
26
27
 
28
+ // View models are immutable for the lifetime of a capture. Search and selection
29
+ // changes should not rebuild allocation membership or the execution domain.
30
+ const analysisBySnapshot = new WeakMap<FrameGraphDebugViewModel, AliasAnalysis>();
31
+
27
32
  export function analyzeSnapshotAliases(snapshot: FrameGraphDebugViewModel): AliasAnalysis {
33
+ const cached = analysisBySnapshot.get(snapshot);
34
+ if (cached) return cached;
28
35
  const allocationById = snapshot.allocationById;
29
36
  const resourcesById = snapshot.resourceById;
30
37
  const allocationGroups = snapshot.physicalAllocations.map((allocation): AliasAnalysisAllocation => {
@@ -43,13 +50,14 @@ export function analyzeSnapshotAliases(snapshot: FrameGraphDebugViewModel): Alia
43
50
  let minUse = Number.POSITIVE_INFINITY;
44
51
  let maxUse = Number.NEGATIVE_INFINITY;
45
52
  for (const resource of snapshot.resources) {
46
- if (!resource.lifetime) {
53
+ if (resource.origin !== 'transient' || !resource.lifetime) {
47
54
  continue;
48
55
  }
49
56
  minUse = Math.min(minUse, resource.lifetime.firstUse);
50
57
  maxUse = Math.max(maxUse, resource.lifetime.lastUse);
51
58
  }
52
- if (!Number.isFinite(minUse) || !Number.isFinite(maxUse)) {
59
+ const hasLifetimes = Number.isFinite(minUse) && Number.isFinite(maxUse);
60
+ if (!hasLifetimes) {
53
61
  minUse = 0;
54
62
  maxUse = 0;
55
63
  }
@@ -65,16 +73,19 @@ export function analyzeSnapshotAliases(snapshot: FrameGraphDebugViewModel): Alia
65
73
  resource,
66
74
  allocation,
67
75
  aliasStatus,
68
- nonAliasReasons: resolveNonAliasReasons(resource, allocation, allocationGroups),
76
+ nonAliasReasons: describeAliasStatus(aliasStatus),
69
77
  });
70
78
  }
71
79
 
72
- return {
80
+ const analysis = {
73
81
  allocations: allocationGroups,
74
82
  resources: resourceAnalysis,
75
83
  minUse,
76
84
  maxUse,
85
+ hasLifetimes,
77
86
  };
87
+ analysisBySnapshot.set(snapshot, analysis);
88
+ return analysis;
78
89
  }
79
90
 
80
91
  function resolveAliasStatus(
@@ -94,71 +105,14 @@ function resolveAliasStatus(
94
105
  return allocationResources.length > 1 ? 'aliased' : 'single';
95
106
  }
96
107
 
97
- function resolveNonAliasReasons(
98
- resource: FrameGraphDebugResource,
99
- allocation: FrameGraphDebugPhysicalAllocation | undefined,
100
- allocations: readonly AliasAnalysisAllocation[],
101
- ): readonly string[] {
102
- if (resource.origin !== 'transient') {
103
- return ['Imported or swapchain resources do not participate in transient aliasing.'];
104
- }
105
- if (!resource.lifetime) {
106
- return ['No lifetime is available for this resource in the retained graph.'];
107
- }
108
- if (!allocation) {
109
- return ['No physical allocation is associated with this transient resource.'];
110
- }
111
-
112
- const reasons = new Set<string>();
113
- for (const group of allocations) {
114
- if (group.allocation.id === allocation.id) {
115
- continue;
116
- }
117
- for (const other of group.resources) {
118
- if (other.id === resource.id || other.origin !== 'transient') {
119
- continue;
120
- }
121
- if (other.kind !== resource.kind) {
122
- reasons.add(`Different kind from ${labelDebugResource(other)}.`);
123
- continue;
124
- }
125
- if (group.allocation.compatibilityClassId !== allocation.compatibilityClassId) {
126
- reasons.add(`Different compatibility class from ${labelDebugResource(other)}.`);
127
- continue;
128
- }
129
- if (!other.lifetime) {
130
- reasons.add(`Missing lifetime for ${labelDebugResource(other)}.`);
131
- continue;
132
- }
133
- if (lifetimesOverlap(resource.lifetime, other.lifetime)) {
134
- reasons.add(`Lifetime overlaps ${labelDebugResource(other)} (${other.lifetime.firstUse}-${other.lifetime.lastUse}).`);
135
- }
136
- }
137
- }
138
-
139
- if (reasons.size === 0) {
140
- return allocationResourcesFor(allocations, allocation.id).length > 1
141
- ? ['Aliases with resources in the same physical allocation.']
142
- : ['No compatible non-overlapping transient resource was found in this snapshot.'];
108
+ function describeAliasStatus(status: AliasAnalysisResource['aliasStatus']): readonly string[] {
109
+ switch (status) {
110
+ case 'not-transient': return ['Imported and surface resources do not participate in transient aliasing.'];
111
+ case 'no-lifetime': return ['No lifetime is available for this resource in the retained graph.'];
112
+ case 'not-allocated': return ['No physical allocation is associated with this transient resource.'];
113
+ case 'aliased': return ['Shares a physical allocation with other logical resources.'];
114
+ // The snapshot states allocation membership; it cannot explain allocator
115
+ // decisions by comparing every pair of logical resources.
116
+ case 'single': return ['This physical allocation contains one logical resource.'];
143
117
  }
144
-
145
- return Array.from(reasons).slice(0, 6);
146
- }
147
-
148
- function allocationResourcesFor(
149
- allocations: readonly AliasAnalysisAllocation[],
150
- allocationId: string,
151
- ): readonly FrameGraphDebugResource[] {
152
- return allocations.find((group) => group.allocation.id === allocationId)?.resources ?? [];
153
- }
154
-
155
- function lifetimesOverlap(
156
- a: NonNullable<FrameGraphDebugResource['lifetime']>,
157
- b: NonNullable<FrameGraphDebugResource['lifetime']>,
158
- ): boolean {
159
- return a.firstUse <= b.lastUse && b.firstUse <= a.lastUse;
160
- }
161
-
162
- function labelDebugResource(resource: FrameGraphDebugResource): string {
163
- return resource.label ?? `${resource.kind}-${resource.id}`;
164
118
  }