@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.
- package/GUIDE.md +182 -0
- package/README.md +60 -26
- package/THEMING.md +124 -0
- package/dist/FrameGraphInspector.d.ts +24 -8
- package/dist/FrameGraphInspector.d.ts.map +1 -1
- package/dist/FrameGraphInspector.js +151 -65
- 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 +28 -4
- package/dist/debugCaptureModel.d.ts.map +1 -1
- package/dist/debugCaptureModel.js +72 -16
- 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 +18 -3
- package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.js +183 -41
- 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 +167 -58
- 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 +20 -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 +15 -26
- package/dist/panelGraphScene.d.ts.map +1 -1
- package/dist/panelGraphScene.js +191 -208
- 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 +42 -9
- package/dist/panelGraphView.js.map +1 -1
- package/dist/panelGraphVisuals.d.ts +18 -6
- package/dist/panelGraphVisuals.d.ts.map +1 -1
- package/dist/panelGraphVisuals.js +156 -116
- package/dist/panelGraphVisuals.js.map +1 -1
- package/dist/panelInspectorView.d.ts +13 -0
- package/dist/panelInspectorView.d.ts.map +1 -1
- package/dist/panelInspectorView.js +326 -46
- 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 +36 -6
- 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 +18 -4
- package/dist/panelTypes.d.ts.map +1 -1
- package/dist/panelTypes.js.map +1 -1
- package/dist/panelVisualTheme.d.ts +176 -71
- package/dist/panelVisualTheme.d.ts.map +1 -1
- package/dist/panelVisualTheme.js +34 -36
- 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 +63 -2
- 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 +161 -205
- 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 +145 -68
- package/src/capture.ts +4 -0
- package/src/debugCaptureModel.ts +97 -21
- package/src/index.ts +6 -2
- package/src/panelAliasAnalysis.ts +24 -70
- package/src/panelCytoscapeGraphRenderer.ts +177 -40
- package/src/panelDetailLayout.ts +155 -0
- package/src/panelDetailStyles.ts +56 -0
- package/src/panelDiagnosticsView.ts +167 -86
- package/src/panelDomHelpers.ts +4 -5
- package/src/panelGraphLayout.ts +20 -5
- package/src/panelGraphRenderer.ts +7 -0
- package/src/panelGraphScene.ts +204 -292
- package/src/panelGraphSearch.ts +96 -0
- package/src/panelGraphView.ts +41 -9
- package/src/panelGraphVisuals.ts +152 -118
- package/src/panelInspectorView.ts +315 -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 +39 -7
- package/src/panelTheme.ts +85 -0
- package/src/panelTypes.ts +16 -4
- package/src/panelVisualTheme.ts +38 -38
- package/src/panelWorkbenchHelpers.ts +62 -4
- package/src/panelWorkbenchStyles.ts +100 -0
- package/src/panelWorkbenchView.ts +201 -52
- package/src/query.ts +11 -11
- package/src/styles.ts +162 -205
- package/src/theme.ts +24 -0
- package/src/themeDefinitions.ts +103 -0
- package/src/themePalette.ts +44 -0
|
@@ -1,33 +1,51 @@
|
|
|
1
1
|
import type { FrameGraphDebugViewModel } from './debugCaptureModel.ts';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { labelNode, labelResource } from './panelDomHelpers.ts';
|
|
3
|
+
import { resolveNodeSelection } from './panelSelection.ts';
|
|
4
|
+
import type { Selection, WorkbenchTab } from './panelTypes.ts';
|
|
4
5
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
createRelationButton,
|
|
8
|
-
createSelectionCell,
|
|
9
|
-
createTableScroller,
|
|
10
|
-
registerSelectable,
|
|
11
|
-
type WorkbenchCallbacks,
|
|
12
|
-
updateSelectedRows,
|
|
6
|
+
createFilterSelect, createRelationButton, createSearchInput, createViewToolbar,
|
|
7
|
+
registerSelectable, selectionKey, type WorkbenchCallbacks, updateSelectedRows,
|
|
13
8
|
} from './panelWorkbenchHelpers.ts';
|
|
14
9
|
|
|
10
|
+
const severityOrder = { error: 0, warning: 1, info: 2 } as const;
|
|
11
|
+
|
|
15
12
|
export class DiagnosticsView {
|
|
16
13
|
readonly root = document.createElement('section');
|
|
17
14
|
private readonly scroller = document.createElement('div');
|
|
18
15
|
private readonly content = document.createElement('div');
|
|
19
16
|
private readonly rows = new Map<string, HTMLElement[]>();
|
|
17
|
+
private readonly openSections = new Set<string>();
|
|
18
|
+
private readonly count = document.createElement('span');
|
|
19
|
+
private readonly searchInput: HTMLInputElement;
|
|
20
|
+
private readonly severitySelect: HTMLSelectElement;
|
|
20
21
|
private snapshot: FrameGraphDebugViewModel | undefined;
|
|
21
22
|
private selected: Selection | undefined;
|
|
23
|
+
private severity = 'all';
|
|
24
|
+
private search = '';
|
|
22
25
|
|
|
23
26
|
constructor(private readonly callbacks: WorkbenchCallbacks, idPrefix: string) {
|
|
24
27
|
this.root.className = 'zenfg-inspector-view zenfg-inspector-diagnostics-view';
|
|
25
28
|
this.root.id = `${idPrefix}-view-diagnostics`;
|
|
26
29
|
this.root.setAttribute('role', 'tabpanel');
|
|
30
|
+
const toolbar = createViewToolbar('Diagnostic filters');
|
|
31
|
+
this.searchInput = createSearchInput('Search diagnostic code or message', '', (value) => {
|
|
32
|
+
this.search = value.trim().toLocaleLowerCase();
|
|
33
|
+
this.render();
|
|
34
|
+
});
|
|
35
|
+
this.severitySelect = createFilterSelect('Diagnostic severity', this.severity, [
|
|
36
|
+
['all', 'All severities'], ['error', 'Error'], ['warning', 'Warning'], ['info', 'Info'],
|
|
37
|
+
], (value) => { this.severity = value; this.render(); });
|
|
38
|
+
const clear = document.createElement('button');
|
|
39
|
+
clear.type = 'button';
|
|
40
|
+
clear.textContent = 'Clear filters';
|
|
41
|
+
clear.addEventListener('click', () => { this.clearFilters(); this.render(); });
|
|
42
|
+
this.count.className = 'zenfg-inspector-result-count';
|
|
43
|
+
this.count.setAttribute('role', 'status');
|
|
44
|
+
toolbar.append(this.searchInput, this.severitySelect, clear, this.count);
|
|
27
45
|
this.scroller.className = 'zenfg-inspector-diagnostics-scroller';
|
|
28
|
-
this.content.className = 'zenfg-inspector-diagnostics-
|
|
46
|
+
this.content.className = 'zenfg-inspector-diagnostics-sections';
|
|
29
47
|
this.scroller.appendChild(this.content);
|
|
30
|
-
this.root.
|
|
48
|
+
this.root.append(toolbar, this.scroller);
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
setSnapshot(snapshot: FrameGraphDebugViewModel): void {
|
|
@@ -40,23 +58,107 @@ export class DiagnosticsView {
|
|
|
40
58
|
updateSelectedRows(this.rows, selected);
|
|
41
59
|
}
|
|
42
60
|
|
|
61
|
+
reveal(selection: Selection): void {
|
|
62
|
+
this.clearFilters();
|
|
63
|
+
if (selection.kind === 'root') this.openSections.add('roots');
|
|
64
|
+
if (selection.kind === 'culled') this.openSections.add('culled');
|
|
65
|
+
if (selection.kind === 'segment') {
|
|
66
|
+
this.openSections.add('segments');
|
|
67
|
+
this.openSections.add(`segment:${selection.index}`);
|
|
68
|
+
}
|
|
69
|
+
this.render();
|
|
70
|
+
this.rows.get(selectionKey(selection))?.[0]?.scrollIntoView?.({ block: 'nearest' });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private clearFilters(): void {
|
|
74
|
+
this.search = '';
|
|
75
|
+
this.severity = 'all';
|
|
76
|
+
this.searchInput.value = '';
|
|
77
|
+
this.severitySelect.value = 'all';
|
|
78
|
+
}
|
|
79
|
+
|
|
43
80
|
private render(): void {
|
|
44
81
|
const snapshot = this.snapshot;
|
|
45
82
|
if (!snapshot) return;
|
|
46
83
|
this.rows.clear();
|
|
47
84
|
this.content.replaceChildren(
|
|
48
|
-
this.createRoots(snapshot),
|
|
49
|
-
this.createCulled(snapshot),
|
|
50
|
-
this.createSegments(snapshot),
|
|
51
|
-
this.createTiming(snapshot),
|
|
85
|
+
this.createMessages(snapshot), this.createRoots(snapshot), this.createCulled(snapshot), this.createSegments(snapshot),
|
|
52
86
|
);
|
|
53
87
|
updateSelectedRows(this.rows, this.selected);
|
|
54
88
|
}
|
|
55
89
|
|
|
56
|
-
private
|
|
90
|
+
private createMessages(snapshot: FrameGraphDebugViewModel): HTMLElement {
|
|
57
91
|
const section = document.createElement('section');
|
|
58
|
-
section.className = 'zenfg-inspector-diagnostic-
|
|
92
|
+
section.className = 'zenfg-inspector-diagnostic-messages';
|
|
59
93
|
const heading = document.createElement('h2');
|
|
94
|
+
heading.textContent = 'Diagnostics';
|
|
95
|
+
const summary = document.createElement('p');
|
|
96
|
+
const all = snapshot.protocol.diagnostics;
|
|
97
|
+
const errors = all.filter((entry) => entry.severity === 'error').length;
|
|
98
|
+
const warnings = all.filter((entry) => entry.severity === 'warning').length;
|
|
99
|
+
const infos = all.length - errors - warnings;
|
|
100
|
+
summary.textContent = `${errors} errors · ${warnings} warnings · ${infos} info`;
|
|
101
|
+
section.append(heading, summary);
|
|
102
|
+
// Sort a copy. Stable sort preserves capture order within each severity, including repeated codes.
|
|
103
|
+
const messages = all.filter((entry) => (this.severity === 'all' || entry.severity === this.severity)
|
|
104
|
+
&& (!this.search || `${entry.code} ${entry.message}`.toLocaleLowerCase().includes(this.search)))
|
|
105
|
+
.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
106
|
+
this.count.textContent = `${messages.length} / ${all.length} diagnostics`;
|
|
107
|
+
for (const diagnostic of messages) {
|
|
108
|
+
const article = document.createElement('article');
|
|
109
|
+
article.className = 'zenfg-inspector-diagnostic-message';
|
|
110
|
+
article.dataset.severity = diagnostic.severity;
|
|
111
|
+
const title = document.createElement('h3');
|
|
112
|
+
const severity = document.createElement('span');
|
|
113
|
+
severity.className = 'zenfg-inspector-diagnostic-severity';
|
|
114
|
+
severity.textContent = diagnostic.severity;
|
|
115
|
+
const code = document.createElement('code');
|
|
116
|
+
code.textContent = diagnostic.code;
|
|
117
|
+
title.append(severity, code);
|
|
118
|
+
const message = document.createElement('p');
|
|
119
|
+
message.textContent = diagnostic.message;
|
|
120
|
+
article.append(title, message);
|
|
121
|
+
if (diagnostic.nodeId !== undefined) {
|
|
122
|
+
const selection = resolveNodeSelection(snapshot, diagnostic.nodeId);
|
|
123
|
+
const node = snapshot.canonicalNodeById.get(diagnostic.nodeId);
|
|
124
|
+
if (selection && node) article.appendChild(this.createObjectLink(labelNode(node), selection, 'passes'));
|
|
125
|
+
}
|
|
126
|
+
if (diagnostic.resourceId !== undefined) {
|
|
127
|
+
const resource = snapshot.resourceById.get(diagnostic.resourceId);
|
|
128
|
+
if (resource) article.appendChild(this.createObjectLink(labelResource(resource), { kind: 'resource', id: resource.id }, 'resources'));
|
|
129
|
+
}
|
|
130
|
+
section.appendChild(article);
|
|
131
|
+
}
|
|
132
|
+
if (messages.length === 0) {
|
|
133
|
+
const empty = document.createElement('p');
|
|
134
|
+
empty.className = 'zenfg-inspector-muted';
|
|
135
|
+
empty.textContent = all.length === 0 ? 'No diagnostic messages in this snapshot.' : 'No diagnostics match the current filters.';
|
|
136
|
+
section.appendChild(empty);
|
|
137
|
+
}
|
|
138
|
+
return section;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private createObjectLink(label: string, selection: Selection, tab: WorkbenchTab): HTMLElement {
|
|
142
|
+
const links = document.createElement('span');
|
|
143
|
+
links.className = 'zenfg-inspector-diagnostic-links';
|
|
144
|
+
const select = createRelationButton(label, selection, this.callbacks.onSelect);
|
|
145
|
+
registerSelectable(this.rows, select, selection, this.callbacks);
|
|
146
|
+
const reveal = document.createElement('button');
|
|
147
|
+
reveal.type = 'button';
|
|
148
|
+
reveal.className = 'zenfg-inspector-relation-button zenfg-inspector-diagnostic-reveal';
|
|
149
|
+
reveal.textContent = `Show in ${tab === 'passes' ? 'Passes' : 'Resources'}`;
|
|
150
|
+
reveal.setAttribute('aria-label', `${reveal.textContent}: ${label}`);
|
|
151
|
+
reveal.addEventListener('click', () => this.callbacks.onReveal?.(selection, tab));
|
|
152
|
+
links.append(select, reveal);
|
|
153
|
+
return links;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private createSection(key: string, title: string, description?: string): HTMLDetailsElement {
|
|
157
|
+
const section = document.createElement('details');
|
|
158
|
+
section.className = 'zenfg-inspector-diagnostic-section';
|
|
159
|
+
section.dataset.section = key;
|
|
160
|
+
section.open = this.openSections.has(key);
|
|
161
|
+
const heading = document.createElement('summary');
|
|
60
162
|
heading.textContent = title;
|
|
61
163
|
section.appendChild(heading);
|
|
62
164
|
if (description) {
|
|
@@ -64,97 +166,76 @@ export class DiagnosticsView {
|
|
|
64
166
|
text.textContent = description;
|
|
65
167
|
section.appendChild(text);
|
|
66
168
|
}
|
|
169
|
+
section.addEventListener('toggle', () => {
|
|
170
|
+
if (section.open) this.openSections.add(key);
|
|
171
|
+
else this.openSections.delete(key);
|
|
172
|
+
});
|
|
67
173
|
return section;
|
|
68
174
|
}
|
|
69
175
|
|
|
70
176
|
private createRoots(snapshot: FrameGraphDebugViewModel): HTMLElement {
|
|
71
|
-
const section = this.createSection('
|
|
177
|
+
const section = this.createSection('roots', `Retention roots (${snapshot.roots.length})`, 'Roots explain why graph work survived dead-node elimination.');
|
|
72
178
|
const list = document.createElement('div');
|
|
73
179
|
list.className = 'zenfg-inspector-diagnostic-list';
|
|
74
|
-
snapshot.roots
|
|
75
|
-
const selection: Selection = { kind: 'root',
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this.callbacks.onSelect,
|
|
80
|
-
);
|
|
180
|
+
for (const root of snapshot.roots) {
|
|
181
|
+
const selection: Selection = { kind: 'root', key: root.key };
|
|
182
|
+
const node = root.nodeId === undefined ? undefined : snapshot.canonicalNodeById.get(root.nodeId);
|
|
183
|
+
const label = root.resource ? labelResource(root.resource) : node ? labelNode(node) : root.nodeId ?? 'Unknown';
|
|
184
|
+
const button = createRelationButton(`${root.reason} · ${label}`, selection, this.callbacks.onSelect);
|
|
81
185
|
registerSelectable(this.rows, button, selection, this.callbacks);
|
|
82
186
|
list.appendChild(button);
|
|
83
|
-
}
|
|
187
|
+
}
|
|
84
188
|
if (snapshot.roots.length === 0) list.textContent = 'No retention roots.';
|
|
85
189
|
section.appendChild(list);
|
|
86
190
|
return section;
|
|
87
191
|
}
|
|
88
192
|
|
|
89
193
|
private createCulled(snapshot: FrameGraphDebugViewModel): HTMLElement {
|
|
90
|
-
const section = this.createSection('
|
|
91
|
-
const
|
|
92
|
-
for (const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
194
|
+
const section = this.createSection('culled', `Culled passes (${snapshot.culledNodes.length})`, 'Recorded passes removed during compilation. Inspect their reasons and accesses in Passes.');
|
|
195
|
+
const reasons = new Map<string, number>();
|
|
196
|
+
for (const entry of snapshot.culledNodes) reasons.set(entry.reason, (reasons.get(entry.reason) ?? 0) + 1);
|
|
197
|
+
const list = document.createElement('ul');
|
|
198
|
+
for (const [reason, count] of reasons) {
|
|
199
|
+
const item = document.createElement('li');
|
|
200
|
+
item.textContent = `${reason}: ${count}`;
|
|
201
|
+
list.appendChild(item);
|
|
202
|
+
}
|
|
203
|
+
section.appendChild(list);
|
|
204
|
+
if (snapshot.culledNodes.length > 0) {
|
|
205
|
+
const button = document.createElement('button');
|
|
206
|
+
button.type = 'button';
|
|
207
|
+
button.className = 'zenfg-inspector-relation-button';
|
|
208
|
+
button.textContent = 'Show culled passes';
|
|
209
|
+
button.addEventListener('click', () => this.callbacks.onNavigate?.('passes', 'culled'));
|
|
210
|
+
section.appendChild(button);
|
|
102
211
|
}
|
|
103
|
-
if (snapshot.culledNodes.length === 0) table.body.appendChild(createEmptyTableRow(3, 'No culled nodes.'));
|
|
104
|
-
section.appendChild(table.scroller);
|
|
105
212
|
return section;
|
|
106
213
|
}
|
|
107
214
|
|
|
108
215
|
private createSegments(snapshot: FrameGraphDebugViewModel): HTMLElement {
|
|
109
|
-
const section = this.createSection(
|
|
110
|
-
'
|
|
111
|
-
'An opaque interval is a graph boundary around external work; it is not a count or timing of third-party GPU submissions.',
|
|
112
|
-
);
|
|
113
|
-
const table = createTableScroller([{ label: '#', column: 'numeric' }, { label: 'Kind', column: 'kind' }, { label: 'Nodes' }]);
|
|
216
|
+
const section = this.createSection('segments', `Execution segments (${snapshot.executionSegments.length})`,
|
|
217
|
+
'An opaque interval marks external work. Its GPU submissions and duration are not measured here.');
|
|
114
218
|
for (const segment of snapshot.executionSegments) {
|
|
115
219
|
const selection: Selection = { kind: 'segment', index: segment.index };
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
section.appendChild(table.scroller);
|
|
130
|
-
return section;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private createTiming(snapshot: FrameGraphDebugViewModel): HTMLElement {
|
|
134
|
-
const metrics = snapshot.metrics;
|
|
135
|
-
const coverage = metrics.timingEligibleNodeCount === 0
|
|
136
|
-
? 'No eligible render or compute passes'
|
|
137
|
-
: `${metrics.timedNodeCount}/${metrics.timingEligibleNodeCount} eligible passes timed (${((metrics.timedNodeCount / metrics.timingEligibleNodeCount) * 100).toFixed(0)}%)`;
|
|
138
|
-
const section = this.createSection('GPU timing coverage', coverage);
|
|
139
|
-
if (snapshot.profiling.status === 'unavailable') {
|
|
140
|
-
const status = document.createElement('p');
|
|
141
|
-
status.textContent = `Timing unavailable: ${snapshot.profiling.reason}.`;
|
|
142
|
-
section.appendChild(status);
|
|
220
|
+
const members = this.createSection(`segment:${segment.index}`, `${segment.index} · ${segment.kind === 'frame-graph' ? 'FrameGraph command segment' : 'Opaque interval'} · ${segment.nodeIds.length} passes`);
|
|
221
|
+
const button = createRelationButton('Inspect segment', selection, this.callbacks.onSelect);
|
|
222
|
+
registerSelectable(this.rows, button, selection, this.callbacks);
|
|
223
|
+
members.appendChild(button);
|
|
224
|
+
const list = document.createElement('div');
|
|
225
|
+
list.className = 'zenfg-inspector-diagnostic-list';
|
|
226
|
+
for (const id of segment.nodeIds) {
|
|
227
|
+
const node = snapshot.canonicalNodeById.get(id);
|
|
228
|
+
const nodeSelection = resolveNodeSelection(snapshot, id);
|
|
229
|
+
if (node && nodeSelection) list.appendChild(this.createObjectLink(labelNode(node), nodeSelection, 'passes'));
|
|
230
|
+
}
|
|
231
|
+
members.appendChild(list);
|
|
232
|
+
section.appendChild(members);
|
|
143
233
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
registerSelectable(this.rows, row, selection, this.callbacks);
|
|
149
|
-
row.append(
|
|
150
|
-
createSelectionCell(labelNode(node), selection, this.callbacks),
|
|
151
|
-
createKindCell(node.gpuDurationMicros === undefined ? 'not-timed' : 'timed', node.gpuDurationMicros === undefined ? 'Not timed' : 'Timed'),
|
|
152
|
-
createCell(node.gpuDurationMicros === undefined ? '-' : (node.gpuDurationMicros / 1000).toFixed(3), { column: 'numeric' }),
|
|
153
|
-
);
|
|
154
|
-
table.body.appendChild(row);
|
|
234
|
+
if (snapshot.executionSegments.length === 0) {
|
|
235
|
+
const empty = document.createElement('p');
|
|
236
|
+
empty.textContent = 'No execution segments.';
|
|
237
|
+
section.appendChild(empty);
|
|
155
238
|
}
|
|
156
|
-
if (table.body.childElementCount === 0) table.body.appendChild(createEmptyTableRow(3, 'No timing-eligible passes.'));
|
|
157
|
-
section.appendChild(table.scroller);
|
|
158
239
|
return section;
|
|
159
240
|
}
|
|
160
241
|
}
|
package/src/panelDomHelpers.ts
CHANGED
|
@@ -126,9 +126,8 @@ export function formatGpuFrameDuration(snapshot: FrameGraphDebugViewModel): stri
|
|
|
126
126
|
|
|
127
127
|
export function formatPoolHitRate(snapshot: FrameGraphDebugViewModel): string {
|
|
128
128
|
const pool = snapshot.resourcePool;
|
|
129
|
-
if (!pool || pool.status !== 'available'
|
|
130
|
-
|
|
131
|
-
}
|
|
129
|
+
if (!pool || pool.status !== 'available') return 'Unavailable';
|
|
130
|
+
if (pool.acquireCount === 0) return 'Not applicable · no acquisitions';
|
|
132
131
|
return `${((pool.reuseCount / pool.acquireCount) * 100).toFixed(0)}%`;
|
|
133
132
|
}
|
|
134
133
|
|
|
@@ -143,9 +142,9 @@ export function formatBytes(bytes: number): string {
|
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
export function labelNode(node: Pick<FrameGraphDebugNode, 'id' | 'label'>): string {
|
|
146
|
-
return node.label ??
|
|
145
|
+
return node.label ?? node.id;
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
export function labelResource(resource: { readonly id: string; readonly kind: string; readonly label?: string }): string {
|
|
150
|
-
return resource.label ??
|
|
149
|
+
return resource.label ?? resource.id;
|
|
151
150
|
}
|
package/src/panelGraphLayout.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GRAPH_VISUAL_THEME, type GraphVisualTheme } from './panelVisualTheme.ts';
|
|
1
2
|
import type cytoscape from 'cytoscape';
|
|
2
3
|
import type { ELK, ElkExtendedEdge, ElkNode, ElkPort } from 'elkjs/lib/elk-api';
|
|
3
4
|
|
|
@@ -22,8 +23,8 @@ export type CytoscapeEdgeRouteData = {
|
|
|
22
23
|
readonly segmentDistances: string;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
export async function layoutGraphScene(elk: ELK, scene: GraphScene): Promise<GraphLayoutResult> {
|
|
26
|
-
const graph = createElkLayoutGraph(scene);
|
|
26
|
+
export async function layoutGraphScene(elk: ELK, scene: GraphScene, theme: GraphVisualTheme = GRAPH_VISUAL_THEME): Promise<GraphLayoutResult> {
|
|
27
|
+
const graph = createElkLayoutGraph(scene, theme);
|
|
27
28
|
const layout = await elk.layout(graph);
|
|
28
29
|
const positions = new Map<GraphSceneElementId, GraphPosition>();
|
|
29
30
|
collectLayoutPositions(layout, 0, 0, positions);
|
|
@@ -34,20 +35,34 @@ export async function layoutGraphScene(elk: ELK, scene: GraphScene): Promise<Gra
|
|
|
34
35
|
return { positions, routes };
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
function usesCenterPorts(node: GraphSceneNode): boolean {
|
|
39
|
+
return node.kind === 'resource' || node.kind === 'root'
|
|
40
|
+
|| (node.kind === 'pass' && node.passKind === 'external-submission');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createElkLayoutGraph(scene: GraphScene, theme: GraphVisualTheme = GRAPH_VISUAL_THEME): ElkNode {
|
|
38
44
|
const childrenByParent = new Map<GraphSceneElementId | undefined, GraphSceneNode[]>();
|
|
39
45
|
const portsByNode = new Map<GraphSceneElementId, ElkPort[]>();
|
|
46
|
+
const nodesById = new Map(scene.nodes.map((node) => [node.id, node]));
|
|
47
|
+
const boundaryPort = (id: string, source: boolean) => {
|
|
48
|
+
const node = nodesById.get(id)!;
|
|
49
|
+
if (!usesCenterPorts(node)) return {};
|
|
50
|
+
const dimensions = nodeDimensions(node, theme);
|
|
51
|
+
return { x: source ? dimensions.width : 0, y: dimensions.height / 2 };
|
|
52
|
+
};
|
|
40
53
|
const elkEdges = scene.edges.map((edge): ElkExtendedEdge => {
|
|
41
54
|
const sourcePortId = `${edge.id}:source`;
|
|
42
55
|
const targetPortId = `${edge.id}:target`;
|
|
43
56
|
appendPort(portsByNode, edge.from, {
|
|
44
57
|
id: sourcePortId,
|
|
58
|
+
...boundaryPort(edge.from, true),
|
|
45
59
|
width: 1,
|
|
46
60
|
height: 1,
|
|
47
61
|
layoutOptions: { 'elk.port.side': 'EAST' },
|
|
48
62
|
});
|
|
49
63
|
appendPort(portsByNode, edge.to, {
|
|
50
64
|
id: targetPortId,
|
|
65
|
+
...boundaryPort(edge.to, false),
|
|
51
66
|
width: 1,
|
|
52
67
|
height: 1,
|
|
53
68
|
layoutOptions: { 'elk.port.side': 'WEST' },
|
|
@@ -65,7 +80,7 @@ export function createElkLayoutGraph(scene: GraphScene): ElkNode {
|
|
|
65
80
|
}
|
|
66
81
|
const createNode = (node: GraphSceneNode): ElkNode => {
|
|
67
82
|
const children = childrenByParent.get(node.id)?.map(createNode);
|
|
68
|
-
const dimensions = nodeDimensions(node);
|
|
83
|
+
const dimensions = nodeDimensions(node, theme);
|
|
69
84
|
return {
|
|
70
85
|
id: node.id,
|
|
71
86
|
width: dimensions.width,
|
|
@@ -86,7 +101,7 @@ export function createElkLayoutGraph(scene: GraphScene): ElkNode {
|
|
|
86
101
|
'elk.spacing.edgeEdge': '10',
|
|
87
102
|
'elk.layered.spacing.edgeEdgeBetweenLayers': '10',
|
|
88
103
|
} : {
|
|
89
|
-
'elk.portConstraints': 'FIXED_SIDE',
|
|
104
|
+
'elk.portConstraints': usesCenterPorts(node) ? 'FIXED_POS' : 'FIXED_SIDE',
|
|
90
105
|
},
|
|
91
106
|
};
|
|
92
107
|
};
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import type { GraphVisualTheme } from './panelVisualTheme.ts';
|
|
1
2
|
import type { GraphScene, GraphSceneElementId } from './panelGraphScene.ts';
|
|
2
3
|
import type { Selection } from './panelTypes.ts';
|
|
3
4
|
|
|
4
5
|
export type GraphRenderRequest = {
|
|
6
|
+
readonly theme?: GraphVisualTheme;
|
|
7
|
+
readonly onVisible?: () => void;
|
|
5
8
|
readonly scene: GraphScene;
|
|
6
9
|
readonly selected: Selection | undefined;
|
|
7
10
|
readonly hovered: Selection | undefined;
|
|
8
11
|
readonly fit: boolean;
|
|
9
12
|
readonly anchorElementId?: GraphSceneElementId;
|
|
13
|
+
readonly reveal?: { readonly selection: Selection; readonly revision: number };
|
|
14
|
+
readonly captureRevision?: number;
|
|
10
15
|
readonly onSelect: (selection: Selection) => void;
|
|
11
16
|
readonly onHover: (selection: Selection | undefined) => void;
|
|
12
17
|
readonly onToggleGroup: (pathKey: string) => void;
|
|
@@ -14,8 +19,10 @@ export type GraphRenderRequest = {
|
|
|
14
19
|
|
|
15
20
|
export interface GraphRenderer {
|
|
16
21
|
render(request: GraphRenderRequest): void;
|
|
22
|
+
setTheme?(theme: GraphVisualTheme): void;
|
|
17
23
|
resize(): void;
|
|
18
24
|
fit(): void;
|
|
19
25
|
relayout(): void;
|
|
26
|
+
cancelReveal?(): void;
|
|
20
27
|
destroy(): void;
|
|
21
28
|
}
|