@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
|
@@ -12,8 +12,10 @@ import { MemoryView } from './panelMemoryView.ts';
|
|
|
12
12
|
import { PassesView } from './panelPassesView.ts';
|
|
13
13
|
import { ResourcesView } from './panelResourcesView.ts';
|
|
14
14
|
import { renderGraphView, resizeGraph } from './panelGraphView.ts';
|
|
15
|
+
import { DetailLayout } from './panelDetailLayout.ts';
|
|
16
|
+
import { GraphSearch } from './panelGraphSearch.ts';
|
|
15
17
|
import type { GraphViewState, Selection, WorkbenchTab } from './panelTypes.ts';
|
|
16
|
-
import { formatEstimatedBytes, type WorkbenchCallbacks } from './panelWorkbenchHelpers.ts';
|
|
18
|
+
import { enableTabKeyboard, formatEstimatedBytes, formatEstimateCoverage, formatTimingCoverage, type WorkbenchCallbacks } from './panelWorkbenchHelpers.ts';
|
|
17
19
|
|
|
18
20
|
export type FrameGraphDebugWorkbenchActions = {
|
|
19
21
|
onCapture(): void;
|
|
@@ -47,6 +49,8 @@ export class FrameGraphDebugWorkbench {
|
|
|
47
49
|
private readonly tabList = document.createElement('div');
|
|
48
50
|
private readonly commandActions = document.createElement('div');
|
|
49
51
|
private readonly commandStatus = document.createElement('span');
|
|
52
|
+
private readonly feedback = document.createElement('details');
|
|
53
|
+
private readonly feedbackTitle = document.createElement('summary');
|
|
50
54
|
private readonly workspace = document.createElement('div');
|
|
51
55
|
private readonly main = document.createElement('main');
|
|
52
56
|
private readonly emptyHost = document.createElement('div');
|
|
@@ -57,6 +61,10 @@ export class FrameGraphDebugWorkbench {
|
|
|
57
61
|
private readonly memory: MemoryView;
|
|
58
62
|
private readonly diagnostics: DiagnosticsView;
|
|
59
63
|
private readonly inspector: InspectorView;
|
|
64
|
+
private readonly detailLayout: DetailLayout;
|
|
65
|
+
private readonly graphSearch: GraphSearch;
|
|
66
|
+
private readonly dirtyViews = new Set<WorkbenchTab>();
|
|
67
|
+
private readonly captureDetails = document.createElement('details');
|
|
60
68
|
private readonly inspectorOpenButton = document.createElement('button');
|
|
61
69
|
private readonly captureButton = document.createElement('button');
|
|
62
70
|
private readonly importButton = document.createElement('button');
|
|
@@ -72,6 +80,7 @@ export class FrameGraphDebugWorkbench {
|
|
|
72
80
|
private snapshot: FrameGraphDebugViewModel | undefined;
|
|
73
81
|
private selected: Selection | undefined;
|
|
74
82
|
private hovered: Selection | undefined;
|
|
83
|
+
private destroyed = false;
|
|
75
84
|
|
|
76
85
|
constructor(
|
|
77
86
|
private readonly graphView: GraphViewState,
|
|
@@ -94,7 +103,7 @@ export class FrameGraphDebugWorkbench {
|
|
|
94
103
|
this.commandStatus.className = 'zenfg-inspector-command-status';
|
|
95
104
|
this.commandStatus.setAttribute('role', 'status');
|
|
96
105
|
this.commandStatus.setAttribute('aria-live', 'polite');
|
|
97
|
-
this.workspace.className = 'zenfg-inspector-workspace
|
|
106
|
+
this.workspace.className = 'zenfg-inspector-workspace';
|
|
98
107
|
this.main.className = 'zenfg-inspector-main';
|
|
99
108
|
this.emptyHost.className = 'zenfg-inspector-workbench-empty';
|
|
100
109
|
this.emptyHost.setAttribute('role', 'status');
|
|
@@ -107,12 +116,25 @@ export class FrameGraphDebugWorkbench {
|
|
|
107
116
|
this.graphRoot.className = 'zenfg-inspector-view zenfg-inspector-graph-view';
|
|
108
117
|
this.graphRoot.id = `${options.idPrefix}-view-graph`;
|
|
109
118
|
this.graphRoot.setAttribute('role', 'tabpanel');
|
|
110
|
-
|
|
119
|
+
const graphViewport = document.createElement('div');
|
|
120
|
+
graphViewport.className = 'zenfg-inspector-graph-viewport';
|
|
121
|
+
graphViewport.append(this.graphView.host, this.graphView.toolbar);
|
|
122
|
+
if (this.graphView.legend) {
|
|
123
|
+
const legend = document.createElement('details');
|
|
124
|
+
legend.className = 'zenfg-inspector-legend-details';
|
|
125
|
+
const title = document.createElement('summary');
|
|
126
|
+
title.textContent = 'Legend';
|
|
127
|
+
legend.append(title, this.graphView.legend);
|
|
128
|
+
graphViewport.append(legend);
|
|
129
|
+
}
|
|
130
|
+
this.graphRoot.append(graphViewport);
|
|
111
131
|
this.passes = new PassesView(callbacks, options.idPrefix);
|
|
112
132
|
this.resources = new ResourcesView(callbacks, options.idPrefix);
|
|
113
133
|
this.memory = new MemoryView(callbacks, options.idPrefix);
|
|
114
134
|
this.diagnostics = new DiagnosticsView(callbacks, options.idPrefix);
|
|
115
135
|
this.inspector = new InspectorView(callbacks, (open) => this.handleInspectorOpenChange(open), options.idPrefix);
|
|
136
|
+
this.graphSearch = new GraphSearch((selection) => callbacks.onReveal?.(selection, 'graph'), `${options.idPrefix}-graph-search`);
|
|
137
|
+
this.graphView.toolbar.prepend(this.graphSearch.root);
|
|
116
138
|
|
|
117
139
|
this.views.set('overview', this.overviewRoot);
|
|
118
140
|
this.views.set('graph', this.graphRoot);
|
|
@@ -138,6 +160,7 @@ export class FrameGraphDebugWorkbench {
|
|
|
138
160
|
this.tabButtons.set(tab, button);
|
|
139
161
|
this.tabList.appendChild(button);
|
|
140
162
|
}
|
|
163
|
+
enableTabKeyboard(this.tabList);
|
|
141
164
|
|
|
142
165
|
this.inspectorOpenButton.type = 'button';
|
|
143
166
|
this.inspectorOpenButton.hidden = true;
|
|
@@ -191,7 +214,6 @@ export class FrameGraphDebugWorkbench {
|
|
|
191
214
|
this.exportMenu.append(this.downloadButton, this.copyButton);
|
|
192
215
|
|
|
193
216
|
this.commandActions.append(
|
|
194
|
-
this.commandStatus,
|
|
195
217
|
this.inspectorOpenButton,
|
|
196
218
|
this.captureButton,
|
|
197
219
|
this.importButton,
|
|
@@ -206,14 +228,15 @@ export class FrameGraphDebugWorkbench {
|
|
|
206
228
|
const targetNode = target as Node;
|
|
207
229
|
if (!this.exportButton.contains(targetNode) && !this.exportMenu.contains(targetNode)) this.setExportMenuOpen(false);
|
|
208
230
|
});
|
|
209
|
-
this.root.addEventListener('keydown', (event) =>
|
|
210
|
-
if (event.key !== 'Escape' || this.exportMenu.hidden) return;
|
|
211
|
-
this.setExportMenuOpen(false);
|
|
212
|
-
this.exportButton.focus();
|
|
213
|
-
});
|
|
231
|
+
this.root.addEventListener('keydown', (event) => this.handleMenuKey(event));
|
|
214
232
|
this.main.append(this.emptyHost, ...this.views.values());
|
|
215
233
|
this.workspace.append(this.main, this.inspector.root);
|
|
216
|
-
this.
|
|
234
|
+
this.feedback.className = 'zenfg-inspector-feedback';
|
|
235
|
+
this.feedback.hidden = true;
|
|
236
|
+
this.feedback.append(this.feedbackTitle, this.commandStatus);
|
|
237
|
+
this.root.append(this.commandBar, this.feedback, this.workspace);
|
|
238
|
+
this.detailLayout = new DetailLayout(this.workspace, this.main, this.inspector.root, this.commandBar, () => this.inspector.setOpen(false));
|
|
239
|
+
this.workspace.addEventListener('detail-layout-change', () => this.resizeGraph());
|
|
217
240
|
this.showEmptyState('empty', 'Drop a ZenFG Snapshot here or choose Import.', 'Files are processed locally in your browser.');
|
|
218
241
|
this.setSnapshotActionState({
|
|
219
242
|
providerAvailable: false,
|
|
@@ -230,18 +253,13 @@ export class FrameGraphDebugWorkbench {
|
|
|
230
253
|
this.snapshot = snapshot;
|
|
231
254
|
this.selected = selected;
|
|
232
255
|
this.hovered = undefined;
|
|
233
|
-
this.
|
|
234
|
-
this.
|
|
235
|
-
this.
|
|
236
|
-
this.memory.setSnapshot(snapshot);
|
|
237
|
-
this.diagnostics.setSnapshot(snapshot);
|
|
256
|
+
for (const tab of this.views.keys()) this.dirtyViews.add(tab);
|
|
257
|
+
this.renderDiagnosticBadge(snapshot);
|
|
258
|
+
this.graphSearch.setSnapshot(snapshot);
|
|
238
259
|
this.inspector.setSnapshot(snapshot);
|
|
239
|
-
this.passes.setSelection(selected);
|
|
240
|
-
this.resources.setSelection(selected);
|
|
241
|
-
this.memory.setSelection(selected);
|
|
242
|
-
this.diagnostics.setSelection(selected);
|
|
243
260
|
this.inspector.setSelection(selected, false);
|
|
244
261
|
this.emptyHost.hidden = true;
|
|
262
|
+
this.ensureActiveView();
|
|
245
263
|
this.updateActiveTab();
|
|
246
264
|
this.updateWorkspaceState();
|
|
247
265
|
if (this.activeTab === 'graph') this.renderGraph();
|
|
@@ -303,14 +321,14 @@ export class FrameGraphDebugWorkbench {
|
|
|
303
321
|
this.commandStatus.dataset.tone = state.message ? state.messageTone ?? 'error' : 'neutral';
|
|
304
322
|
if (state.message) this.commandStatus.title = state.message;
|
|
305
323
|
else this.commandStatus.removeAttribute('title');
|
|
306
|
-
|
|
324
|
+
this.feedback.hidden = !state.message;
|
|
325
|
+
this.feedback.dataset.tone = state.messageTone ?? 'neutral';
|
|
326
|
+
this.feedbackTitle.textContent = state.message ? state.messageTone === 'error' ? 'Operation failed · show details' : 'Operation feedback · show details' : '';
|
|
327
|
+
}
|
|
307
328
|
|
|
308
329
|
setSelection(selected: Selection | undefined): void {
|
|
309
330
|
this.selected = selected;
|
|
310
|
-
this.
|
|
311
|
-
this.resources.setSelection(selected);
|
|
312
|
-
this.memory.setSelection(selected);
|
|
313
|
-
this.diagnostics.setSelection(selected);
|
|
331
|
+
this.updateActiveSelection();
|
|
314
332
|
this.inspector.setSelection(selected);
|
|
315
333
|
if (this.activeTab === 'graph') this.renderGraph();
|
|
316
334
|
}
|
|
@@ -321,11 +339,19 @@ export class FrameGraphDebugWorkbench {
|
|
|
321
339
|
}
|
|
322
340
|
|
|
323
341
|
setActiveTab(tab: WorkbenchTab): void {
|
|
342
|
+
if (this.destroyed) return;
|
|
324
343
|
if (this.activeTab === tab) return;
|
|
344
|
+
if (this.activeTab === 'graph') {
|
|
345
|
+
this.graphView.revealOnNextRender = undefined;
|
|
346
|
+
this.graphView.renderer?.cancelReveal?.();
|
|
347
|
+
}
|
|
325
348
|
this.activeTab = tab;
|
|
326
349
|
this.updateActiveTab();
|
|
350
|
+
this.ensureActiveView();
|
|
351
|
+
this.updateWorkspaceState();
|
|
327
352
|
if (tab === 'graph') {
|
|
328
353
|
window.requestAnimationFrame(() => {
|
|
354
|
+
if (this.destroyed || this.activeTab !== 'graph') return;
|
|
329
355
|
resizeGraph(this.graphView);
|
|
330
356
|
this.renderGraph();
|
|
331
357
|
});
|
|
@@ -334,13 +360,13 @@ export class FrameGraphDebugWorkbench {
|
|
|
334
360
|
|
|
335
361
|
refreshGraphStructure(): void {
|
|
336
362
|
if (!this.snapshot) return;
|
|
337
|
-
this.passes.setSnapshot(this.snapshot);
|
|
338
363
|
if (this.activeTab === 'graph') this.renderGraph();
|
|
339
364
|
}
|
|
340
365
|
|
|
341
366
|
resizeGraph(fit = false): void {
|
|
342
367
|
if (fit) this.graphView.fitOnNextRender = true;
|
|
343
368
|
window.requestAnimationFrame(() => {
|
|
369
|
+
if (this.destroyed) return;
|
|
344
370
|
resizeGraph(this.graphView);
|
|
345
371
|
if (this.activeTab === 'graph') this.renderGraph();
|
|
346
372
|
});
|
|
@@ -352,6 +378,7 @@ export class FrameGraphDebugWorkbench {
|
|
|
352
378
|
button.disabled = !this.hasSnapshot;
|
|
353
379
|
button.classList.toggle('active', active);
|
|
354
380
|
button.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
381
|
+
button.tabIndex = active ? 0 : -1;
|
|
355
382
|
const view = this.views.get(tab)!;
|
|
356
383
|
view.hidden = !this.hasSnapshot || !active;
|
|
357
384
|
view.setAttribute('aria-labelledby', button.id);
|
|
@@ -375,9 +402,7 @@ export class FrameGraphDebugWorkbench {
|
|
|
375
402
|
const frameGraphSegments = snapshot.executionSegments.filter((segment) => segment.kind === 'frame-graph').length;
|
|
376
403
|
const opaqueIntervals = snapshot.executionSegments.length - frameGraphSegments;
|
|
377
404
|
const metrics = snapshot.metrics;
|
|
378
|
-
const coverage = metrics.timingEligibleNodeCount
|
|
379
|
-
? 'No eligible passes'
|
|
380
|
-
: `${metrics.timedNodeCount}/${metrics.timingEligibleNodeCount} timed`;
|
|
405
|
+
const coverage = formatTimingCoverage(metrics.timedNodeCount, metrics.timingEligibleNodeCount);
|
|
381
406
|
const slowest = metrics.slowestNode
|
|
382
407
|
? `${labelNode(metrics.slowestNode)} · ${(metrics.slowestNode.gpuDurationMicros! / 1000).toFixed(3)} ms`
|
|
383
408
|
: 'Unknown';
|
|
@@ -394,41 +419,55 @@ export class FrameGraphDebugWorkbench {
|
|
|
394
419
|
const poolRetained = snapshot.resourcePool.status === 'available'
|
|
395
420
|
? `${snapshot.resourcePool.estimatedRetainedBytes === undefined ? 'Unknown' : formatBytes(snapshot.resourcePool.estimatedRetainedBytes)} · ${snapshot.resourcePool.retainedCount} allocations`
|
|
396
421
|
: 'Unavailable';
|
|
422
|
+
const counts = this.diagnosticCounts(snapshot);
|
|
397
423
|
this.summary.replaceChildren(
|
|
398
|
-
this.createSummaryGroup('
|
|
399
|
-
['
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
['
|
|
403
|
-
['
|
|
404
|
-
|
|
405
|
-
['
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
['Coverage', coverage],
|
|
410
|
-
['Slowest', slowest],
|
|
411
|
-
]),
|
|
424
|
+
this.createSummaryGroup('Diagnostics', [
|
|
425
|
+
['Errors', String(counts.error)], ['Warnings', String(counts.warning)], ['Information', String(counts.info)],
|
|
426
|
+
], () => this.setActiveTab('diagnostics')),
|
|
427
|
+
this.createSummaryGroup('Execution timing', [
|
|
428
|
+
['CPU execute', snapshot.cpuProfiling.status === 'available' ? (snapshot.cpuProfiling.executionDurationMicros / 1000).toFixed(3) + ' ms' : 'Not collected'],
|
|
429
|
+
['CPU pass sum', snapshot.metrics.cpuTimedNodeCount ? (snapshot.metrics.cpuWorkDurationMicros / 1000).toFixed(3) + ' ms' : 'Not collected'],
|
|
430
|
+
['CPU coverage', formatTimingCoverage(snapshot.metrics.cpuTimedNodeCount, snapshot.nodes.length)],
|
|
431
|
+
['GPU span', snapshot.profiling.status === 'available' ? `${formatGpuFrameDuration(snapshot)} ms` : 'Not collected'],
|
|
432
|
+
['GPU coverage', coverage],
|
|
433
|
+
['Slowest GPU pass', slowest],
|
|
434
|
+
], () => metrics.slowestNode ? this.callbacks.onReveal?.({ kind: 'node', id: metrics.slowestNode.id }, 'passes') : this.setActiveTab('passes')),
|
|
412
435
|
this.createSummaryGroup('Work', [
|
|
413
436
|
['Nodes', `${snapshot.nodes.length} retained · ${snapshot.culledNodes.length} culled`],
|
|
414
437
|
['Segments', `${frameGraphSegments} FG · ${opaqueIntervals} opaque`],
|
|
415
438
|
['Groups', snapshot.availability.groups ? String(snapshot.debugGroups.length) : 'Unknown'],
|
|
416
439
|
['Recording order', snapshot.availability.recordingOrder ? 'Available' : 'Unknown'],
|
|
417
|
-
]),
|
|
440
|
+
], () => this.setActiveTab('passes')),
|
|
418
441
|
this.createSummaryGroup('Resources', [
|
|
419
|
-
['Logical / physical', `${snapshot.resources.length} / ${snapshot.physicalAllocations.length}`],
|
|
442
|
+
['Logical / physical', `${snapshot.resources.length} / ${protocol.memory.allocationReport.status === 'available' ? snapshot.physicalAllocations.length : 'Unavailable'}`],
|
|
420
443
|
['Texture views', snapshot.availability.textureViews ? String(snapshot.textureViewById.size) : 'Unknown'],
|
|
421
444
|
['Access regions', snapshot.availability.accessRegions ? 'Available' : 'Unknown'],
|
|
422
|
-
['Transient estimate',
|
|
423
|
-
]),
|
|
445
|
+
['Transient estimate', formatEstimateCoverage(metrics.transientEstimatedByteSize, metrics.estimatedCoverage.transient)],
|
|
446
|
+
], () => this.setActiveTab('resources')),
|
|
447
|
+
this.createSummaryGroup('Memory estimates', [
|
|
448
|
+
['Physical allocations', protocol.memory.allocationReport.status === 'available' ? formatEstimateCoverage(metrics.physicalEstimatedBytes, metrics.estimatedCoverage.physical) : 'Unavailable'],
|
|
449
|
+
['Alias reuse', protocol.memory.allocationReport.status === 'available' ? formatEstimatedBytes(metrics.aliasReuseBytes) : 'Unavailable'],
|
|
450
|
+
], () => this.setActiveTab('memory')),
|
|
424
451
|
this.createSummaryGroup('Pool', [
|
|
425
452
|
['Retained', poolRetained],
|
|
426
453
|
['Reuse', formatPoolHitRate(snapshot)],
|
|
427
|
-
]),
|
|
454
|
+
], () => this.setActiveTab('memory')),
|
|
428
455
|
);
|
|
456
|
+
const title = document.createElement('summary');
|
|
457
|
+
title.textContent = 'Capture information';
|
|
458
|
+
this.captureDetails.className = 'zenfg-inspector-capture-details';
|
|
459
|
+
this.captureDetails.replaceChildren(title, this.createSummaryGroup('Capture', [
|
|
460
|
+
['Source', snapshot.source.label], ['Frame', String(snapshot.frameIndex)],
|
|
461
|
+
['Captured at', protocol.capture.capturedAt ?? 'Unknown'],
|
|
462
|
+
['Schema', `${protocol.format} v${protocol.version.major}.${protocol.version.minor}`],
|
|
463
|
+
['Producer', producer], ['Runtime', runtimeLabel],
|
|
464
|
+
...(protocol.capture.migration ? [['Migration', `${protocol.capture.migration.sourceFormat} → canonical v${protocol.version.major}.${protocol.version.minor}`] as const] : []),
|
|
465
|
+
['Timing', snapshot.profiling.status === 'available' ? coverage : snapshot.profiling.reason],
|
|
466
|
+
]));
|
|
467
|
+
this.summary.append(this.captureDetails);
|
|
429
468
|
}
|
|
430
469
|
|
|
431
|
-
private createSummaryGroup(title: string, rows: readonly (readonly [string, string])[]): HTMLElement {
|
|
470
|
+
private createSummaryGroup(title: string, rows: readonly (readonly [string, string])[], onNavigate?: () => void): HTMLElement {
|
|
432
471
|
const group = document.createElement('section');
|
|
433
472
|
const heading = document.createElement('h2');
|
|
434
473
|
heading.textContent = title;
|
|
@@ -443,12 +482,21 @@ export class FrameGraphDebugWorkbench {
|
|
|
443
482
|
row.append(term, description);
|
|
444
483
|
group.appendChild(row);
|
|
445
484
|
}
|
|
485
|
+
if (onNavigate) {
|
|
486
|
+
const action = document.createElement('button');
|
|
487
|
+
action.type = 'button';
|
|
488
|
+
action.className = 'zenfg-inspector-relation-button';
|
|
489
|
+
action.textContent = `Explore ${title.toLowerCase()}`;
|
|
490
|
+
action.addEventListener('click', onNavigate);
|
|
491
|
+
group.append(action);
|
|
492
|
+
}
|
|
446
493
|
return group;
|
|
447
494
|
}
|
|
448
495
|
|
|
449
496
|
private setExportMenuOpen(open: boolean): void {
|
|
450
497
|
this.exportMenu.hidden = !open;
|
|
451
498
|
this.exportButton.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
499
|
+
if (open) this.downloadButton.focus();
|
|
452
500
|
}
|
|
453
501
|
|
|
454
502
|
private handleInspectorOpenChange(_open: boolean): void {
|
|
@@ -457,10 +505,111 @@ export class FrameGraphDebugWorkbench {
|
|
|
457
505
|
}
|
|
458
506
|
|
|
459
507
|
private updateWorkspaceState(): void {
|
|
460
|
-
const inspectorOpen = this.hasSnapshot && this.inspector.isOpen;
|
|
508
|
+
const inspectorOpen = this.hasSnapshot && this.inspector.isOpen && this.activeTab !== 'overview';
|
|
461
509
|
this.workspace.classList.toggle('has-capture', this.hasSnapshot);
|
|
462
|
-
this.
|
|
463
|
-
this.
|
|
464
|
-
this.
|
|
510
|
+
this.inspector.root.classList.toggle('unavailable', !inspectorOpen);
|
|
511
|
+
this.inspectorOpenButton.hidden = !this.hasSnapshot || !this.selected || inspectorOpen || this.activeTab === 'overview';
|
|
512
|
+
this.detailLayout.update(inspectorOpen);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
reveal(selection: Selection, tab: WorkbenchTab): void {
|
|
516
|
+
if (this.feedbackTitle.textContent === 'Graph location unavailable') {
|
|
517
|
+
this.feedback.hidden = true;
|
|
518
|
+
this.commandStatus.replaceChildren();
|
|
519
|
+
this.commandStatus.hidden = true;
|
|
520
|
+
}
|
|
521
|
+
this.setActiveTab(tab);
|
|
522
|
+
if (tab === 'passes') this.passes.reveal(selection);
|
|
523
|
+
else if (tab === 'resources') this.resources.reveal(selection);
|
|
524
|
+
else if (tab === 'memory') this.memory.reveal(selection);
|
|
525
|
+
else if (tab === 'diagnostics') this.diagnostics.reveal(selection);
|
|
526
|
+
if (this.detailLayout.isDrawer) this.inspector.setOpen(false);
|
|
527
|
+
if (tab === 'graph') this.renderGraph();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
navigate(tab: WorkbenchTab, filter?: 'culled'): void {
|
|
531
|
+
this.setActiveTab(tab);
|
|
532
|
+
if (tab === 'passes' && filter === 'culled') this.passes.showCulled();
|
|
533
|
+
if (this.detailLayout.isDrawer) this.inspector.setOpen(false);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
showNavigationIssue(message: string, selection: Selection): void {
|
|
537
|
+
if (this.detailLayout.isDrawer) this.inspector.setOpen(false);
|
|
538
|
+
this.feedback.hidden = false;
|
|
539
|
+
this.feedback.open = true;
|
|
540
|
+
this.feedback.dataset.tone = 'neutral';
|
|
541
|
+
this.feedbackTitle.textContent = 'Graph location unavailable';
|
|
542
|
+
const action = document.createElement('button');
|
|
543
|
+
action.type = 'button';
|
|
544
|
+
action.className = 'zenfg-inspector-relation-button';
|
|
545
|
+
const tab = selection.kind === 'resource' ? 'resources' : selection.kind === 'allocation' ? 'memory'
|
|
546
|
+
: selection.kind === 'root' || selection.kind === 'segment' ? 'diagnostics' : 'passes';
|
|
547
|
+
action.textContent = `Show in ${tab}`;
|
|
548
|
+
action.addEventListener('click', () => this.callbacks.onReveal?.(selection, tab));
|
|
549
|
+
this.commandStatus.hidden = false;
|
|
550
|
+
this.commandStatus.replaceChildren(document.createTextNode(`${message} `), action);
|
|
551
|
+
action.focus();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
destroy(): void { this.destroyed = true; this.detailLayout.destroy(); }
|
|
555
|
+
|
|
556
|
+
private ensureActiveView(): void {
|
|
557
|
+
const snapshot = this.snapshot;
|
|
558
|
+
if (!snapshot) return;
|
|
559
|
+
if (this.dirtyViews.has(this.activeTab)) {
|
|
560
|
+
switch (this.activeTab) {
|
|
561
|
+
case 'overview': this.renderSummary(snapshot); break;
|
|
562
|
+
case 'passes': this.passes.setSnapshot(snapshot); break;
|
|
563
|
+
case 'resources': this.resources.setSnapshot(snapshot); break;
|
|
564
|
+
case 'memory': this.memory.setSnapshot(snapshot); break;
|
|
565
|
+
case 'diagnostics': this.diagnostics.setSnapshot(snapshot); break;
|
|
566
|
+
}
|
|
567
|
+
this.dirtyViews.delete(this.activeTab);
|
|
568
|
+
}
|
|
569
|
+
this.updateActiveSelection();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private updateActiveSelection(): void {
|
|
573
|
+
switch (this.activeTab) {
|
|
574
|
+
case 'passes': this.passes.setSelection(this.selected); break;
|
|
575
|
+
case 'resources': this.resources.setSelection(this.selected); break;
|
|
576
|
+
case 'memory': this.memory.setSelection(this.selected); break;
|
|
577
|
+
case 'diagnostics': this.diagnostics.setSelection(this.selected); break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private diagnosticCounts(snapshot: FrameGraphDebugViewModel): Record<'error' | 'warning' | 'info', number> {
|
|
582
|
+
const counts = { error: 0, warning: 0, info: 0 };
|
|
583
|
+
for (const entry of snapshot.protocol.diagnostics) counts[entry.severity]++;
|
|
584
|
+
return counts;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private renderDiagnosticBadge(snapshot: FrameGraphDebugViewModel): void {
|
|
588
|
+
const counts = this.diagnosticCounts(snapshot);
|
|
589
|
+
const tab = this.tabButtons.get('diagnostics')!;
|
|
590
|
+
const label = document.createElement('span');
|
|
591
|
+
label.textContent = 'Diagnostics';
|
|
592
|
+
const badge = document.createElement('span');
|
|
593
|
+
badge.className = 'zenfg-inspector-diagnostic-badge';
|
|
594
|
+
badge.textContent = `${counts.error} / ${counts.warning}`;
|
|
595
|
+
badge.title = `${counts.error} errors, ${counts.warning} warnings`;
|
|
596
|
+
badge.setAttribute('aria-hidden', 'true');
|
|
597
|
+
tab.replaceChildren(label, ...(counts.error + counts.warning ? [badge] : []));
|
|
598
|
+
tab.title = badge.title;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
private handleMenuKey(event: KeyboardEvent): void {
|
|
602
|
+
if (this.exportMenu.hidden || event.defaultPrevented) return;
|
|
603
|
+
if (event.key === 'Escape') {
|
|
604
|
+
event.preventDefault(); event.stopPropagation();
|
|
605
|
+
this.setExportMenuOpen(false); this.exportButton.focus();
|
|
606
|
+
} else if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
|
|
607
|
+
event.preventDefault(); event.stopPropagation();
|
|
608
|
+
const items = [this.downloadButton, this.copyButton].filter((button) => !button.disabled);
|
|
609
|
+
const index = items.indexOf(document.activeElement as HTMLButtonElement);
|
|
610
|
+
const next = event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1
|
|
611
|
+
: (index + (event.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length;
|
|
612
|
+
items[next]?.focus();
|
|
613
|
+
}
|
|
465
614
|
}
|
|
466
615
|
}
|
package/src/query.ts
CHANGED
|
@@ -9,14 +9,14 @@ export const ZENFG_INSPECTOR_QUERY_PARAM = 'fg';
|
|
|
9
9
|
* @returns `false` when no URL is available or the supplied URL is invalid.
|
|
10
10
|
*/
|
|
11
11
|
export function isZenFGInspectorRequested(href = globalThis.location?.href): boolean {
|
|
12
|
-
if (!href) {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
return new URL(href).searchParams.has(ZENFG_INSPECTOR_QUERY_PARAM);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
12
|
+
if (!href) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
return new URL(href).searchParams.has(ZENFG_INSPECTOR_QUERY_PARAM);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|