@zenfg/inspector 0.1.0-beta.3 → 0.1.0
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/GUIDE.md +182 -0
- package/README.md +61 -89
- package/THEMING.md +124 -0
- package/dist/FrameGraphInspector.d.ts +23 -4
- package/dist/FrameGraphInspector.d.ts.map +1 -1
- package/dist/FrameGraphInspector.js +137 -29
- package/dist/FrameGraphInspector.js.map +1 -1
- package/dist/capture.d.ts +5 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +2 -0
- package/dist/capture.js.map +1 -0
- package/dist/debugCaptureModel.d.ts +19 -0
- package/dist/debugCaptureModel.d.ts.map +1 -1
- package/dist/debugCaptureModel.js +36 -2
- package/dist/debugCaptureModel.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/panelAliasAnalysis.d.ts +1 -0
- package/dist/panelAliasAnalysis.d.ts.map +1 -1
- package/dist/panelAliasAnalysis.js +23 -54
- package/dist/panelAliasAnalysis.js.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.d.ts +15 -2
- package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.js +145 -23
- package/dist/panelCytoscapeGraphRenderer.js.map +1 -1
- package/dist/panelDetailLayout.d.ts +27 -0
- package/dist/panelDetailLayout.d.ts.map +1 -0
- package/dist/panelDetailLayout.js +174 -0
- package/dist/panelDetailLayout.js.map +1 -0
- package/dist/panelDetailStyles.d.ts +2 -0
- package/dist/panelDetailStyles.d.ts.map +1 -0
- package/dist/panelDetailStyles.js +57 -0
- package/dist/panelDetailStyles.js.map +1 -0
- package/dist/panelDiagnosticsView.d.ts +10 -1
- package/dist/panelDiagnosticsView.d.ts.map +1 -1
- package/dist/panelDiagnosticsView.js +166 -57
- package/dist/panelDiagnosticsView.js.map +1 -1
- package/dist/panelDomHelpers.d.ts.map +1 -1
- package/dist/panelDomHelpers.js +6 -5
- package/dist/panelDomHelpers.js.map +1 -1
- package/dist/panelGraphLayout.d.ts +3 -2
- package/dist/panelGraphLayout.d.ts.map +1 -1
- package/dist/panelGraphLayout.js +6 -5
- package/dist/panelGraphLayout.js.map +1 -1
- package/dist/panelGraphRenderer.d.ts +10 -0
- package/dist/panelGraphRenderer.d.ts.map +1 -1
- package/dist/panelGraphRenderer.js.map +1 -1
- package/dist/panelGraphScene.d.ts +1 -0
- package/dist/panelGraphScene.d.ts.map +1 -1
- package/dist/panelGraphScene.js +69 -32
- package/dist/panelGraphScene.js.map +1 -1
- package/dist/panelGraphSearch.d.ts +16 -0
- package/dist/panelGraphSearch.d.ts.map +1 -0
- package/dist/panelGraphSearch.js +93 -0
- package/dist/panelGraphSearch.js.map +1 -0
- package/dist/panelGraphView.d.ts +82 -0
- package/dist/panelGraphView.d.ts.map +1 -1
- package/dist/panelGraphView.js +15 -4
- package/dist/panelGraphView.js.map +1 -1
- package/dist/panelGraphVisuals.d.ts +14 -6
- package/dist/panelGraphVisuals.d.ts.map +1 -1
- package/dist/panelGraphVisuals.js +126 -51
- package/dist/panelGraphVisuals.js.map +1 -1
- package/dist/panelInspectorView.d.ts +8 -0
- package/dist/panelInspectorView.d.ts.map +1 -1
- package/dist/panelInspectorView.js +242 -48
- package/dist/panelInspectorView.js.map +1 -1
- package/dist/panelListStyles.d.ts +3 -0
- package/dist/panelListStyles.d.ts.map +1 -0
- package/dist/panelListStyles.js +155 -0
- package/dist/panelListStyles.js.map +1 -0
- package/dist/panelMemoryStyles.d.ts +3 -0
- package/dist/panelMemoryStyles.d.ts.map +1 -0
- package/dist/panelMemoryStyles.js +42 -0
- package/dist/panelMemoryStyles.js.map +1 -0
- package/dist/panelMemoryView.d.ts +13 -1
- package/dist/panelMemoryView.d.ts.map +1 -1
- package/dist/panelMemoryView.js +161 -41
- package/dist/panelMemoryView.js.map +1 -1
- package/dist/panelPassesView.d.ts +15 -0
- package/dist/panelPassesView.d.ts.map +1 -1
- package/dist/panelPassesView.js +202 -72
- package/dist/panelPassesView.js.map +1 -1
- package/dist/panelRawView.d.ts +15 -0
- package/dist/panelRawView.d.ts.map +1 -0
- package/dist/panelRawView.js +105 -0
- package/dist/panelRawView.js.map +1 -0
- package/dist/panelResourcesView.d.ts +8 -0
- package/dist/panelResourcesView.d.ts.map +1 -1
- package/dist/panelResourcesView.js +62 -31
- package/dist/panelResourcesView.js.map +1 -1
- package/dist/panelSelection.d.ts +9 -1
- package/dist/panelSelection.d.ts.map +1 -1
- package/dist/panelSelection.js +34 -4
- package/dist/panelSelection.js.map +1 -1
- package/dist/panelTheme.d.ts +13 -0
- package/dist/panelTheme.d.ts.map +1 -0
- package/dist/panelTheme.js +104 -0
- package/dist/panelTheme.js.map +1 -0
- package/dist/panelTypes.d.ts +10 -1
- package/dist/panelTypes.d.ts.map +1 -1
- package/dist/panelTypes.js.map +1 -1
- package/dist/panelVisualTheme.d.ts +147 -50
- package/dist/panelVisualTheme.d.ts.map +1 -1
- package/dist/panelVisualTheme.js +34 -44
- package/dist/panelVisualTheme.js.map +1 -1
- package/dist/panelWorkbenchHelpers.d.ts +12 -1
- package/dist/panelWorkbenchHelpers.d.ts.map +1 -1
- package/dist/panelWorkbenchHelpers.js +62 -1
- package/dist/panelWorkbenchHelpers.js.map +1 -1
- package/dist/panelWorkbenchStyles.d.ts +2 -0
- package/dist/panelWorkbenchStyles.d.ts.map +1 -0
- package/dist/panelWorkbenchStyles.js +101 -0
- package/dist/panelWorkbenchStyles.js.map +1 -0
- package/dist/panelWorkbenchView.d.ts +16 -0
- package/dist/panelWorkbenchView.d.ts.map +1 -1
- package/dist/panelWorkbenchView.js +228 -50
- package/dist/panelWorkbenchView.js.map +1 -1
- package/dist/query.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +147 -195
- package/dist/styles.js.map +1 -1
- package/dist/theme.d.ts +22 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +10 -0
- package/dist/theme.js.map +1 -0
- package/dist/themeDefinitions.d.ts +251 -0
- package/dist/themeDefinitions.d.ts.map +1 -0
- package/dist/themeDefinitions.js +96 -0
- package/dist/themeDefinitions.js.map +1 -0
- package/dist/themePalette.d.ts +66 -0
- package/dist/themePalette.d.ts.map +1 -0
- package/dist/themePalette.js +37 -0
- package/dist/themePalette.js.map +1 -0
- package/dist/themes.css +165 -0
- package/package.json +44 -10
- package/src/FrameGraphInspector.ts +130 -31
- package/src/capture.ts +4 -0
- package/src/debugCaptureModel.ts +53 -2
- package/src/index.ts +6 -2
- package/src/panelAliasAnalysis.ts +24 -70
- package/src/panelCytoscapeGraphRenderer.ts +140 -23
- package/src/panelDetailLayout.ts +155 -0
- package/src/panelDetailStyles.ts +56 -0
- package/src/panelDiagnosticsView.ts +166 -85
- package/src/panelDomHelpers.ts +4 -5
- package/src/panelGraphLayout.ts +6 -5
- package/src/panelGraphRenderer.ts +7 -0
- package/src/panelGraphScene.ts +68 -30
- package/src/panelGraphSearch.ts +96 -0
- package/src/panelGraphView.ts +14 -4
- package/src/panelGraphVisuals.ts +122 -52
- package/src/panelInspectorView.ts +227 -48
- package/src/panelListStyles.ts +154 -0
- package/src/panelMemoryStyles.ts +41 -0
- package/src/panelMemoryView.ts +178 -53
- package/src/panelPassesView.ts +190 -137
- package/src/panelRawView.ts +99 -0
- package/src/panelResourcesView.ts +69 -56
- package/src/panelSelection.ts +37 -5
- package/src/panelTheme.ts +85 -0
- package/src/panelTypes.ts +7 -1
- package/src/panelVisualTheme.ts +38 -49
- package/src/panelWorkbenchHelpers.ts +60 -2
- package/src/panelWorkbenchStyles.ts +100 -0
- package/src/panelWorkbenchView.ts +201 -52
- package/src/query.ts +11 -11
- package/src/styles.ts +148 -195
- package/src/theme.ts +24 -0
- package/src/themeDefinitions.ts +103 -0
- package/src/themePalette.ts +44 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { FrameGraphCaptureRequest } from './capture.ts';
|
|
2
|
+
import type { InspectorTheme } from './theme.ts';
|
|
3
|
+
import { InspectorThemeController, resolveGraphTheme } from './panelTheme.ts';
|
|
1
4
|
import {
|
|
2
5
|
FrameGraphSnapshotValidationError,
|
|
3
6
|
decodeFrameGraphSnapshot,
|
|
@@ -11,20 +14,23 @@ import {
|
|
|
11
14
|
type FrameGraphDebugViewModel,
|
|
12
15
|
} from './debugCaptureModel.ts';
|
|
13
16
|
import { createToolbarButton } from './panelDomHelpers.ts';
|
|
14
|
-
import { createPanelIcon } from './panelIcons.ts';
|
|
15
17
|
import {
|
|
16
18
|
destroyGraph,
|
|
17
19
|
fitGraph,
|
|
20
|
+
resolveGraphScene,
|
|
21
|
+
renderGraphLegend,
|
|
18
22
|
} from './panelGraphView.ts';
|
|
19
|
-
import { graphGroupElementId } from './panelGraphScene.ts';
|
|
20
|
-
import { selectionExists } from './panelSelection.ts';
|
|
21
|
-
import type { GraphViewState, Selection } from './panelTypes.ts';
|
|
23
|
+
import { graphGroupElementId, selectionKey } from './panelGraphScene.ts';
|
|
24
|
+
import { resolveNodeSelection, selectionExists } from './panelSelection.ts';
|
|
25
|
+
import type { GraphViewState, Selection, WorkbenchTab } from './panelTypes.ts';
|
|
22
26
|
import { ensureFrameGraphInspectorStyles } from './styles.ts';
|
|
23
27
|
import { sameSelection, type WorkbenchCallbacks } from './panelWorkbenchHelpers.ts';
|
|
24
28
|
import { FrameGraphDebugWorkbench } from './panelWorkbenchView.ts';
|
|
25
29
|
|
|
26
30
|
/** Construction and safety limits for an embedded {@link FrameGraphInspector}. */
|
|
27
31
|
export type FrameGraphInspectorOptions = {
|
|
32
|
+
/** Optional CSS theme. Without one, host variables inherit over the Storm defaults. */
|
|
33
|
+
theme?: InspectorTheme;
|
|
28
34
|
/**
|
|
29
35
|
* Product label shown in the workbench command bar. Pass `false` to hide
|
|
30
36
|
* visible branding while retaining the inspector's accessible name.
|
|
@@ -36,7 +42,7 @@ export type FrameGraphInspectorOptions = {
|
|
|
36
42
|
* Produces a live snapshot when capture is requested. The inspector awaits
|
|
37
43
|
* promises and displays thrown or rejected errors in its status area.
|
|
38
44
|
*/
|
|
39
|
-
captureSnapshot?: () => FrameGraphSnapshot | undefined | Promise<FrameGraphSnapshot | undefined>;
|
|
45
|
+
captureSnapshot?: (request: FrameGraphCaptureRequest) => FrameGraphSnapshot | undefined | Promise<FrameGraphSnapshot | undefined>;
|
|
40
46
|
/**
|
|
41
47
|
* Maximum accepted import size in bytes.
|
|
42
48
|
*
|
|
@@ -74,11 +80,13 @@ const DEFAULT_BRANDING = 'ZenFG Inspector';
|
|
|
74
80
|
export class FrameGraphInspector {
|
|
75
81
|
/** Root element owned by this inspector instance. */
|
|
76
82
|
readonly dom = document.createElement('section');
|
|
83
|
+
private readonly themeController = new InspectorThemeController(this.dom);
|
|
77
84
|
|
|
78
85
|
private readonly body = document.createElement('div');
|
|
79
86
|
private readonly content = document.createElement('div');
|
|
80
87
|
private readonly dropOverlay = document.createElement('div');
|
|
81
88
|
private readonly groupsButton: HTMLButtonElement;
|
|
89
|
+
private readonly declarationsButton: HTMLButtonElement;
|
|
82
90
|
private readonly collapseGroupsButton: HTMLButtonElement;
|
|
83
91
|
private readonly graphView: GraphViewState;
|
|
84
92
|
private readonly workbench: FrameGraphDebugWorkbench;
|
|
@@ -99,6 +107,7 @@ export class FrameGraphInspector {
|
|
|
99
107
|
private copyFeedbackTimeout: number | undefined;
|
|
100
108
|
private readonly maxImportBytes: number;
|
|
101
109
|
private dragDepth = 0;
|
|
110
|
+
private revealRevision = 0;
|
|
102
111
|
private readonly handleDragEnter = (event: DragEvent): void => {
|
|
103
112
|
if (!isFileDrag(event)) return;
|
|
104
113
|
event.preventDefault();
|
|
@@ -158,8 +167,10 @@ export class FrameGraphInspector {
|
|
|
158
167
|
host: document.createElement('div'),
|
|
159
168
|
toolbar: document.createElement('div'),
|
|
160
169
|
legend: graphLegend,
|
|
170
|
+
refreshTheme: () => this.refreshTheme(),
|
|
161
171
|
layoutElementBudget: normalizeLimit(options.maxGraphElements, DEFAULT_MAX_GRAPH_ELEMENTS, 'maxGraphElements'),
|
|
162
172
|
groupsEnabled: true,
|
|
173
|
+
showResourceDeclarations: true,
|
|
163
174
|
expandedGroupPaths: new Set(),
|
|
164
175
|
fitOnNextRender: true,
|
|
165
176
|
};
|
|
@@ -168,23 +179,27 @@ export class FrameGraphInspector {
|
|
|
168
179
|
this.graphView.toolbar.setAttribute('role', 'toolbar');
|
|
169
180
|
this.graphView.toolbar.setAttribute('aria-label', 'Frame graph view controls');
|
|
170
181
|
|
|
182
|
+
this.declarationsButton = createToolbarButton('Declarations', 'Show resource declaration nodes', () => this.toggleDeclarations());
|
|
171
183
|
this.groupsButton = createToolbarButton('Groups', 'Toggle diagnostic group projection', () => this.toggleGroups());
|
|
172
184
|
this.collapseGroupsButton = createToolbarButton('Collapse All', 'Collapse every diagnostic group', () => this.collapseAllGroups());
|
|
173
185
|
|
|
174
186
|
const actionControls = document.createElement('div');
|
|
175
187
|
actionControls.className = 'zenfg-inspector-graph-action-controls';
|
|
176
188
|
actionControls.append(
|
|
189
|
+
this.declarationsButton,
|
|
177
190
|
this.groupsButton,
|
|
178
191
|
this.collapseGroupsButton,
|
|
179
|
-
|
|
192
|
+
createToolbarButton('Fit', 'Fit graph to view', () => fitGraph(this.graphView)),
|
|
180
193
|
);
|
|
181
|
-
this.graphView.toolbar.append(
|
|
194
|
+
this.graphView.toolbar.append(actionControls);
|
|
182
195
|
|
|
183
196
|
const callbacks: WorkbenchCallbacks = {
|
|
184
197
|
onSelect: (selection) => this.handleSelect(selection),
|
|
185
198
|
onHover: (selection) => this.handleHover(selection),
|
|
186
199
|
onGroupToggle: (pathKey) => this.toggleGroup(pathKey),
|
|
187
200
|
isGroupExpanded: (pathKey) => this.graphView.expandedGroupPaths.has(pathKey),
|
|
201
|
+
onReveal: (selection, tab) => this.handleReveal(selection, tab),
|
|
202
|
+
onNavigate: (tab, filter) => this.workbench.navigate(tab, filter),
|
|
188
203
|
};
|
|
189
204
|
this.workbench = new FrameGraphDebugWorkbench(this.graphView, callbacks, {
|
|
190
205
|
onCapture: () => { void this.captureSnapshot(); },
|
|
@@ -206,20 +221,47 @@ export class FrameGraphInspector {
|
|
|
206
221
|
this.updateCaptureActions();
|
|
207
222
|
this.updateGraphControls();
|
|
208
223
|
this.showEmptyState();
|
|
209
|
-
|
|
224
|
+
if (options.theme) this.setTheme(options.theme);
|
|
225
|
+
queueMicrotask(() => { if (!this.destroyed) { this.refreshTheme(); this.maybeAutoCapture(); } });
|
|
210
226
|
}
|
|
211
227
|
|
|
228
|
+
/** Replace API-applied variables on this instance, then synchronize graph styles.
|
|
229
|
+
* Pass null to restore host CSS and defaults. Does not change capture or interaction state.
|
|
230
|
+
*/
|
|
231
|
+
setTheme(theme: InspectorTheme | null): void {
|
|
232
|
+
if (this.destroyed) return;
|
|
233
|
+
this.themeController.apply(theme);
|
|
234
|
+
this.refreshTheme();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Re-read final CSS values after external CSS changes and refresh graph and legend.
|
|
238
|
+
* Does not write theme variables, recapture, fit, or rebuild the component.
|
|
239
|
+
*/
|
|
240
|
+
refreshTheme(): void {
|
|
241
|
+
if (this.destroyed) return;
|
|
242
|
+
const theme = resolveGraphTheme(this.dom);
|
|
243
|
+
this.graphView.theme = theme;
|
|
244
|
+
this.graphView.renderer?.setTheme?.(theme);
|
|
245
|
+
if (this.viewModel) renderGraphLegend(this.graphView.legend, this.viewModel, theme, this.graphView.showResourceDeclarations);
|
|
246
|
+
}
|
|
247
|
+
|
|
212
248
|
/**
|
|
213
249
|
* Replaces or removes the live-capture provider.
|
|
214
250
|
*
|
|
215
251
|
* @remarks Installing a provider may immediately begin an asynchronous
|
|
216
|
-
* capture when no snapshot is displayed.
|
|
252
|
+
* capture when no snapshot is displayed. Replacing or removing a provider
|
|
253
|
+
* invalidates its pending capture results without cancelling the provider
|
|
254
|
+
* promise. An already displayed snapshot and any pending import are preserved.
|
|
217
255
|
*/
|
|
218
256
|
setCaptureSnapshotProvider(provider: FrameGraphInspectorOptions['captureSnapshot']): void {
|
|
219
|
-
|
|
257
|
+
if (this.destroyed || this.captureSnapshotCallback === provider) return;
|
|
258
|
+
if (this.capturing) {
|
|
259
|
+
this.operationRevision += 1;
|
|
260
|
+
this.capturing = false;
|
|
261
|
+
}
|
|
220
262
|
this.captureSnapshotCallback = provider;
|
|
221
|
-
if (
|
|
222
|
-
if (!provider && !this.viewModel) this.showEmptyState();
|
|
263
|
+
if (!this.viewModel) this.initialAutoCaptureAttempted = false;
|
|
264
|
+
if (!provider && !this.viewModel && !this.importing) this.showEmptyState();
|
|
223
265
|
this.updateCaptureActions();
|
|
224
266
|
this.maybeAutoCapture();
|
|
225
267
|
}
|
|
@@ -245,7 +287,8 @@ export class FrameGraphInspector {
|
|
|
245
287
|
if (!this.viewModel) this.workbench.showEmptyState('capturing', 'Capturing the next rendered frame…');
|
|
246
288
|
this.updateCaptureActions();
|
|
247
289
|
try {
|
|
248
|
-
const
|
|
290
|
+
const request: FrameGraphCaptureRequest = { timing: 'both' };
|
|
291
|
+
const snapshot = await this.captureSnapshotCallback(request);
|
|
249
292
|
if (this.destroyed || revision !== this.operationRevision) return;
|
|
250
293
|
if (!snapshot) {
|
|
251
294
|
this.reportCaptureIssue('No snapshot was produced. Capture again when rendering is active.');
|
|
@@ -270,9 +313,9 @@ export class FrameGraphInspector {
|
|
|
270
313
|
}
|
|
271
314
|
|
|
272
315
|
/**
|
|
273
|
-
* Validates and synchronously displays a programmatic Snapshot 1.
|
|
316
|
+
* Validates and synchronously displays a programmatic Snapshot 1.2 value.
|
|
274
317
|
*
|
|
275
|
-
* @throws {@link FrameGraphSnapshotValidationError} if `snapshot` is invalid.
|
|
318
|
+
* @throws {@link @zenfg/snapshot!index.FrameGraphSnapshotValidationError | FrameGraphSnapshotValidationError} if `snapshot` is invalid.
|
|
276
319
|
*/
|
|
277
320
|
setSnapshot(snapshot: FrameGraphSnapshot): void {
|
|
278
321
|
if (this.destroyed) return;
|
|
@@ -322,10 +365,17 @@ export class FrameGraphInspector {
|
|
|
322
365
|
});
|
|
323
366
|
const sourceFormat = decoded.snapshot.capture.migration?.sourceFormat;
|
|
324
367
|
if (sourceFormat) {
|
|
325
|
-
|
|
368
|
+
// The current import format can differ from retained historical provenance.
|
|
369
|
+
const sourceLabel = {
|
|
370
|
+
'legacy-v0': 'Legacy V0',
|
|
371
|
+
'legacy-candidate-v1': 'Legacy Candidate V1',
|
|
372
|
+
'snapshot-v1.1': 'Snapshot 1.1',
|
|
373
|
+
}[decoded.migrated && decoded.source !== 'v1' ? decoded.source : sourceFormat];
|
|
374
|
+
const version = decoded.snapshot.version;
|
|
375
|
+
const targetLabel = `Snapshot ${version.major}.${version.minor}`;
|
|
326
376
|
this.statusMessage = decoded.migrated
|
|
327
|
-
? `Imported ${sourceLabel} and migrated it to ZenFG
|
|
328
|
-
: `Imported
|
|
377
|
+
? `Imported ${sourceLabel} and migrated it to ZenFG ${targetLabel}.`
|
|
378
|
+
: `Imported ${targetLabel} with ${sourceLabel} migration provenance.`;
|
|
329
379
|
this.statusTone = 'neutral';
|
|
330
380
|
}
|
|
331
381
|
}
|
|
@@ -401,15 +451,15 @@ export class FrameGraphInspector {
|
|
|
401
451
|
this.protocolSnapshot = snapshot;
|
|
402
452
|
this.viewModel = viewModel;
|
|
403
453
|
this.graphView.fitOnNextRender = true;
|
|
454
|
+
this.graphView.captureRevision = (this.graphView.captureRevision ?? 0) + 1;
|
|
455
|
+
this.graphView.revealOnNextRender = undefined;
|
|
456
|
+
this.graphView.renderer?.cancelReveal?.();
|
|
404
457
|
this.hovered = undefined;
|
|
405
458
|
this.initialAutoCaptureAttempted = true;
|
|
406
459
|
this.statusMessage = undefined;
|
|
407
460
|
this.statusTone = 'neutral';
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
? { kind: 'node', id: viewModel.nodes[0].id }
|
|
411
|
-
: undefined;
|
|
412
|
-
}
|
|
461
|
+
if (this.selected?.kind === 'node' || this.selected?.kind === 'culled') this.selected = resolveNodeSelection(viewModel, this.selected.id);
|
|
462
|
+
else if (this.selected && !selectionExists(viewModel, this.selected)) this.selected = undefined;
|
|
413
463
|
this.workbench.setSnapshot(viewModel, this.selected);
|
|
414
464
|
this.updateCaptureActions();
|
|
415
465
|
this.updateGraphControls();
|
|
@@ -430,7 +480,9 @@ export class FrameGraphInspector {
|
|
|
430
480
|
this.dom.removeEventListener('dragleave', this.handleDragLeave);
|
|
431
481
|
this.dom.removeEventListener('dragend', this.handleDragEnd);
|
|
432
482
|
this.dom.removeEventListener('drop', this.handleDrop);
|
|
483
|
+
this.themeController.destroy();
|
|
433
484
|
destroyGraph(this.graphView);
|
|
485
|
+
this.workbench.destroy();
|
|
434
486
|
this.dom.remove();
|
|
435
487
|
}
|
|
436
488
|
|
|
@@ -460,10 +512,47 @@ export class FrameGraphInspector {
|
|
|
460
512
|
void this.captureSnapshot();
|
|
461
513
|
}
|
|
462
514
|
private handleSelect(selection: Selection): void {
|
|
515
|
+
if (!this.viewModel || !selectionExists(this.viewModel, selection)) return;
|
|
463
516
|
this.selected = selection;
|
|
464
517
|
this.workbench.setSelection(selection);
|
|
465
518
|
}
|
|
466
519
|
|
|
520
|
+
private handleReveal(selection: Selection, tab: WorkbenchTab): void {
|
|
521
|
+
const snapshot = this.viewModel;
|
|
522
|
+
if (!snapshot || !selectionExists(snapshot, selection)) return;
|
|
523
|
+
if (tab === 'graph') {
|
|
524
|
+
const expanded = new Set(this.graphView.expandedGroupPaths);
|
|
525
|
+
const groupsEnabled = this.graphView.groupsEnabled;
|
|
526
|
+
const showResourceDeclarations = this.graphView.showResourceDeclarations;
|
|
527
|
+
if (selection.kind === 'resource') this.graphView.showResourceDeclarations = true;
|
|
528
|
+
if (selection.kind === 'group') this.graphView.groupsEnabled = true;
|
|
529
|
+
let groupId: string | undefined;
|
|
530
|
+
if (selection.kind === 'node') groupId = snapshot.nodeById.get(selection.id)?.debugGroupId;
|
|
531
|
+
else if (selection.kind === 'resource') groupId = snapshot.resourceById.get(selection.id)?.debugGroupId;
|
|
532
|
+
else if (selection.kind === 'group') groupId = snapshot.groupByPathKey.get(selection.pathKey)?.parentId;
|
|
533
|
+
if (this.graphView.groupsEnabled && groupId) {
|
|
534
|
+
for (const id of snapshot.groupById.get(groupId)?.ancestorIds ?? []) {
|
|
535
|
+
const group = snapshot.groupById.get(id);
|
|
536
|
+
if (group) this.graphView.expandedGroupPaths.add(group.pathKey);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const scene = resolveGraphScene(this.graphView, snapshot);
|
|
540
|
+
const availableIds = new Set([...scene.nodes, ...scene.edges].map((entry) => entry.id));
|
|
541
|
+
if (!(scene.interaction.primaryElementIdsBySelection.get(selectionKey(selection))?.some((id) => availableIds.has(id)))) {
|
|
542
|
+
this.graphView.groupsEnabled = groupsEnabled;
|
|
543
|
+
this.graphView.showResourceDeclarations = showResourceDeclarations;
|
|
544
|
+
this.graphView.expandedGroupPaths.clear();
|
|
545
|
+
for (const path of expanded) this.graphView.expandedGroupPaths.add(path);
|
|
546
|
+
this.workbench.showNavigationIssue(selection.kind === 'culled' ? 'Culled passes are not part of Frame Flow.' : 'This object has no representation in Frame Flow.', selection);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
this.graphView.revealOnNextRender = { selection, revision: ++this.revealRevision };
|
|
550
|
+
this.updateGraphControls();
|
|
551
|
+
}
|
|
552
|
+
this.handleSelect(selection);
|
|
553
|
+
this.workbench.reveal(selection, tab);
|
|
554
|
+
}
|
|
555
|
+
|
|
467
556
|
private handleHover(selection: Selection | undefined): void {
|
|
468
557
|
if (!this.viewModel || sameSelection(this.hovered, selection)) return;
|
|
469
558
|
this.hovered = selection;
|
|
@@ -471,14 +560,30 @@ export class FrameGraphInspector {
|
|
|
471
560
|
}
|
|
472
561
|
|
|
473
562
|
private updateGraphControls(): void {
|
|
563
|
+
const showDeclarations = this.graphView.showResourceDeclarations ?? true;
|
|
564
|
+
this.declarationsButton.classList.toggle('active', showDeclarations);
|
|
565
|
+
this.declarationsButton.setAttribute('aria-pressed', String(showDeclarations));
|
|
474
566
|
const hasGroups = (this.viewModel?.debugGroups.length ?? 0) > 0;
|
|
475
567
|
this.groupsButton.disabled = !hasGroups;
|
|
568
|
+
this.groupsButton.hidden = !hasGroups;
|
|
569
|
+
this.collapseGroupsButton.hidden = !hasGroups;
|
|
476
570
|
this.groupsButton.classList.toggle('active', hasGroups && this.graphView.groupsEnabled);
|
|
477
571
|
this.groupsButton.setAttribute('aria-pressed', hasGroups && this.graphView.groupsEnabled ? 'true' : 'false');
|
|
478
572
|
const hasExpanded = this.viewModel?.debugGroups.some((group) => this.graphView.expandedGroupPaths.has(group.pathKey)) ?? false;
|
|
479
573
|
this.collapseGroupsButton.disabled = !hasGroups || !this.graphView.groupsEnabled || !hasExpanded;
|
|
480
574
|
}
|
|
481
575
|
|
|
576
|
+
private toggleDeclarations(): void {
|
|
577
|
+
this.handleHover(undefined);
|
|
578
|
+
this.graphView.showResourceDeclarations = !(this.graphView.showResourceDeclarations ?? true);
|
|
579
|
+
this.graphView.renderer?.cancelReveal?.();
|
|
580
|
+
this.graphView.revealOnNextRender = undefined;
|
|
581
|
+
this.graphView.anchorElementIdOnNextRender = undefined;
|
|
582
|
+
this.graphView.fitOnNextRender = true;
|
|
583
|
+
this.updateGraphControls();
|
|
584
|
+
this.workbench.refreshGraphStructure();
|
|
585
|
+
}
|
|
586
|
+
|
|
482
587
|
private toggleGroups(): void {
|
|
483
588
|
if (!this.viewModel?.debugGroups.length) return;
|
|
484
589
|
this.handleHover(undefined);
|
|
@@ -546,16 +651,10 @@ export class FrameGraphInspector {
|
|
|
546
651
|
export function mountFrameGraphInspector(host: HTMLElement, options?: FrameGraphInspectorOptions): FrameGraphInspector {
|
|
547
652
|
const inspector = new FrameGraphInspector(options);
|
|
548
653
|
host.appendChild(inspector.dom);
|
|
654
|
+
inspector.refreshTheme();
|
|
549
655
|
return inspector;
|
|
550
656
|
}
|
|
551
657
|
|
|
552
|
-
function createGraphIconButton(icon: 'fit', title: string, onClick: () => void): HTMLButtonElement {
|
|
553
|
-
const button = createToolbarButton('', title, onClick);
|
|
554
|
-
button.classList.add('zenfg-inspector-icon-button');
|
|
555
|
-
button.appendChild(createPanelIcon(icon));
|
|
556
|
-
return button;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
658
|
async function writeTextToClipboard(text: string): Promise<void> {
|
|
560
659
|
if (navigator.clipboard?.writeText) {
|
|
561
660
|
await navigator.clipboard.writeText(text);
|
|
@@ -596,7 +695,7 @@ function formatImportLimit(bytes: number): string {
|
|
|
596
695
|
}
|
|
597
696
|
|
|
598
697
|
function formatIssues(issues: readonly { readonly path: string; readonly message: string }[]): string {
|
|
599
|
-
return issues.
|
|
698
|
+
return issues.map((issue) => `${issue.path || '/'}: ${issue.message}`).join('\n');
|
|
600
699
|
}
|
|
601
700
|
|
|
602
701
|
function isFileDrag(event: DragEvent): boolean {
|
package/src/capture.ts
ADDED
package/src/debugCaptureModel.ts
CHANGED
|
@@ -59,6 +59,7 @@ 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
|
};
|
|
@@ -121,6 +122,8 @@ export type FrameGraphDebugGroupSummary = {
|
|
|
121
122
|
readonly externalSubmissionCount: number;
|
|
122
123
|
readonly gpuWorkDurationMicros: number;
|
|
123
124
|
readonly timedNodeCount: number;
|
|
125
|
+
readonly cpuTimedNodeCount: number;
|
|
126
|
+
readonly cpuWorkDurationMicros: number;
|
|
124
127
|
readonly timingEligibleNodeCount: number;
|
|
125
128
|
};
|
|
126
129
|
|
|
@@ -140,14 +143,23 @@ export type FrameGraphDebugGroup = {
|
|
|
140
143
|
export type FrameGraphDebugMetrics = {
|
|
141
144
|
readonly timingEligibleNodeCount: number;
|
|
142
145
|
readonly timedNodeCount: number;
|
|
146
|
+
readonly cpuTimedNodeCount: number;
|
|
147
|
+
readonly cpuWorkDurationMicros: number;
|
|
143
148
|
readonly slowestNode?: FrameGraphDebugNode;
|
|
144
149
|
readonly transientEstimatedByteSize?: number;
|
|
145
150
|
readonly logicalCapacityBytes?: number;
|
|
146
151
|
readonly physicalEstimatedBytes?: number;
|
|
147
152
|
readonly aliasReuseBytes?: number;
|
|
148
153
|
readonly aliasedAllocationCount: number;
|
|
154
|
+
readonly estimatedCoverage: {
|
|
155
|
+
readonly transient: EstimateCoverage;
|
|
156
|
+
readonly logical: EstimateCoverage;
|
|
157
|
+
readonly physical: EstimateCoverage;
|
|
158
|
+
};
|
|
149
159
|
};
|
|
150
160
|
|
|
161
|
+
export type EstimateCoverage = { readonly known: number; readonly total: number };
|
|
162
|
+
|
|
151
163
|
export type FrameGraphDebugSnapshotSource = {
|
|
152
164
|
readonly kind: 'live' | 'file' | 'programmatic';
|
|
153
165
|
readonly label: string;
|
|
@@ -169,6 +181,10 @@ export type FrameGraphDebugViewModel = {
|
|
|
169
181
|
readonly executionSegments: readonly FrameGraphDebugExecutionSegment[];
|
|
170
182
|
readonly textureViewById: ReadonlyMap<string, FrameGraphSnapshot['graph']['textureViews'][number]>;
|
|
171
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][]>;
|
|
172
188
|
readonly resourceById: ReadonlyMap<string, FrameGraphDebugResource>;
|
|
173
189
|
readonly groupById: ReadonlyMap<string, FrameGraphDebugGroup>;
|
|
174
190
|
readonly groupByPathKey: ReadonlyMap<string, FrameGraphDebugGroup>;
|
|
@@ -178,6 +194,7 @@ export type FrameGraphDebugViewModel = {
|
|
|
178
194
|
readonly segmentByNodeId: ReadonlyMap<string, FrameGraphDebugExecutionSegment>;
|
|
179
195
|
readonly segmentByIndex: ReadonlyMap<number, FrameGraphDebugExecutionSegment>;
|
|
180
196
|
readonly metrics: FrameGraphDebugMetrics;
|
|
197
|
+
readonly cpuProfiling: FrameGraphSnapshot['timings']['cpu'];
|
|
181
198
|
readonly profiling:
|
|
182
199
|
| {
|
|
183
200
|
readonly status: 'available';
|
|
@@ -216,6 +233,7 @@ export function createDebugViewModel(
|
|
|
216
233
|
? { ...source, migratedFromLegacy: true }
|
|
217
234
|
: source;
|
|
218
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]) : []);
|
|
219
237
|
const resourcePool = snapshot.memory.poolReport;
|
|
220
238
|
const normalizedGroups = normalizeDebugGroups(compilation);
|
|
221
239
|
const knownGroupIds = new Set(normalizedGroups.map((group) => group.id));
|
|
@@ -278,6 +296,7 @@ export function createDebugViewModel(
|
|
|
278
296
|
sideEffect: node.sideEffect,
|
|
279
297
|
debugGroupId: node.groupId,
|
|
280
298
|
gpuDurationMicros: timings.get(node.id),
|
|
299
|
+
cpuDurationMicros: cpuTimings.get(node.id),
|
|
281
300
|
reads: nodeAccesses(node.id, 'read'),
|
|
282
301
|
writes: nodeAccesses(node.id, 'write'),
|
|
283
302
|
}));
|
|
@@ -345,6 +364,18 @@ export function createDebugViewModel(
|
|
|
345
364
|
executionSegments,
|
|
346
365
|
});
|
|
347
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
|
+
}
|
|
348
379
|
const resourceById = new Map(resources.map((resource) => [resource.id, resource]));
|
|
349
380
|
const groupById = new Map(debugGroups.map((group) => [group.id, group]));
|
|
350
381
|
const groupByPathKey = new Map(debugGroups.map((group) => [group.pathKey, group]));
|
|
@@ -362,14 +393,18 @@ export function createDebugViewModel(
|
|
|
362
393
|
), undefined);
|
|
363
394
|
const transientResources = resources.filter((resource) => resource.origin === 'transient');
|
|
364
395
|
const transientEstimatedByteSize = sumKnown(transientResources.map((resource) => resource.estimatedByteSize));
|
|
365
|
-
const
|
|
396
|
+
const allocationAvailable = snapshot.memory.allocationReport.status === 'available';
|
|
397
|
+
const physicalSizes = physicalAllocations.map((allocation) => allocation.estimatedByteSize);
|
|
398
|
+
const physicalEstimatedBytes = allocationAvailable ? sumKnown(physicalSizes) : undefined;
|
|
366
399
|
const logicalAllocationSizes = transientResources
|
|
367
400
|
.filter((resource) => resource.physicalResourceId !== undefined)
|
|
368
401
|
.map((resource) => allocationById.get(resource.physicalResourceId!)?.estimatedByteSize);
|
|
369
|
-
const logicalCapacityBytes = sumKnown(logicalAllocationSizes);
|
|
402
|
+
const logicalCapacityBytes = allocationAvailable ? sumKnown(logicalAllocationSizes) : undefined;
|
|
370
403
|
const metrics: FrameGraphDebugMetrics = {
|
|
371
404
|
timingEligibleNodeCount: timingEligibleNodes.length,
|
|
372
405
|
timedNodeCount: timedNodes.length,
|
|
406
|
+
cpuTimedNodeCount: nodes.filter(node => node.cpuDurationMicros !== undefined).length,
|
|
407
|
+
cpuWorkDurationMicros: nodes.reduce((sum, node) => sum + (node.cpuDurationMicros ?? 0), 0),
|
|
373
408
|
slowestNode,
|
|
374
409
|
transientEstimatedByteSize,
|
|
375
410
|
logicalCapacityBytes,
|
|
@@ -378,6 +413,11 @@ export function createDebugViewModel(
|
|
|
378
413
|
? undefined
|
|
379
414
|
: Math.max(0, logicalCapacityBytes - physicalEstimatedBytes),
|
|
380
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
|
+
},
|
|
381
421
|
};
|
|
382
422
|
|
|
383
423
|
return {
|
|
@@ -395,6 +435,10 @@ export function createDebugViewModel(
|
|
|
395
435
|
executionSegments,
|
|
396
436
|
textureViewById,
|
|
397
437
|
nodeById,
|
|
438
|
+
canonicalNodeById,
|
|
439
|
+
culledById,
|
|
440
|
+
diagnosticsByNodeId,
|
|
441
|
+
diagnosticsByResourceId,
|
|
398
442
|
resourceById,
|
|
399
443
|
groupById,
|
|
400
444
|
groupByPathKey,
|
|
@@ -404,6 +448,7 @@ export function createDebugViewModel(
|
|
|
404
448
|
segmentByNodeId,
|
|
405
449
|
segmentByIndex,
|
|
406
450
|
metrics,
|
|
451
|
+
cpuProfiling: snapshot.timings.cpu,
|
|
407
452
|
profiling: gpuTiming.status === 'available'
|
|
408
453
|
? {
|
|
409
454
|
status: 'available',
|
|
@@ -425,6 +470,10 @@ export function createDebugViewModel(
|
|
|
425
470
|
};
|
|
426
471
|
}
|
|
427
472
|
|
|
473
|
+
function estimateCoverage(values: readonly (number | undefined)[]): EstimateCoverage {
|
|
474
|
+
return { known: values.filter((value) => value !== undefined).length, total: values.length };
|
|
475
|
+
}
|
|
476
|
+
|
|
428
477
|
function sumKnown(values: readonly (number | undefined)[]): number | undefined {
|
|
429
478
|
let total = 0;
|
|
430
479
|
for (const value of values) {
|
|
@@ -601,6 +650,8 @@ function buildDebugGroupSummaries(input: {
|
|
|
601
650
|
externalSubmissionCount: retained.filter((node) => node.kind === 'external-submission').length,
|
|
602
651
|
gpuWorkDurationMicros: timed.reduce((sum, node) => sum + node.gpuDurationMicros!, 0),
|
|
603
652
|
timedNodeCount: timed.length,
|
|
653
|
+
cpuTimedNodeCount: retained.filter(node => node.cpuDurationMicros !== undefined).length,
|
|
654
|
+
cpuWorkDurationMicros: retained.reduce((sum, node) => sum + (node.cpuDurationMicros ?? 0), 0),
|
|
604
655
|
timingEligibleNodeCount: timingEligible.length,
|
|
605
656
|
},
|
|
606
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
|
-
|
|
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:
|
|
76
|
+
nonAliasReasons: describeAliasStatus(aliasStatus),
|
|
69
77
|
});
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
}
|