@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,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
|
|
180
|
+
for (const root of snapshot.roots) {
|
|
75
181
|
const selection: Selection = { kind: 'root', key: root.key };
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this.callbacks.onSelect,
|
|
80
|
-
);
|
|
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);
|
|
@@ -39,14 +40,14 @@ function usesCenterPorts(node: GraphSceneNode): boolean {
|
|
|
39
40
|
|| (node.kind === 'pass' && node.passKind === 'external-submission');
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
export function createElkLayoutGraph(scene: GraphScene): ElkNode {
|
|
43
|
+
export function createElkLayoutGraph(scene: GraphScene, theme: GraphVisualTheme = GRAPH_VISUAL_THEME): ElkNode {
|
|
43
44
|
const childrenByParent = new Map<GraphSceneElementId | undefined, GraphSceneNode[]>();
|
|
44
45
|
const portsByNode = new Map<GraphSceneElementId, ElkPort[]>();
|
|
45
46
|
const nodesById = new Map(scene.nodes.map((node) => [node.id, node]));
|
|
46
47
|
const boundaryPort = (id: string, source: boolean) => {
|
|
47
48
|
const node = nodesById.get(id)!;
|
|
48
49
|
if (!usesCenterPorts(node)) return {};
|
|
49
|
-
const dimensions = nodeDimensions(node);
|
|
50
|
+
const dimensions = nodeDimensions(node, theme);
|
|
50
51
|
return { x: source ? dimensions.width : 0, y: dimensions.height / 2 };
|
|
51
52
|
};
|
|
52
53
|
const elkEdges = scene.edges.map((edge): ElkExtendedEdge => {
|
|
@@ -79,7 +80,7 @@ export function createElkLayoutGraph(scene: GraphScene): ElkNode {
|
|
|
79
80
|
}
|
|
80
81
|
const createNode = (node: GraphSceneNode): ElkNode => {
|
|
81
82
|
const children = childrenByParent.get(node.id)?.map(createNode);
|
|
82
|
-
const dimensions = nodeDimensions(node);
|
|
83
|
+
const dimensions = nodeDimensions(node, theme);
|
|
83
84
|
return {
|
|
84
85
|
id: node.id,
|
|
85
86
|
width: dimensions.width,
|
|
@@ -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
|
}
|
package/src/panelGraphScene.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import { formatGpuDuration, labelNode, labelResource } from './panelDomHelpers.ts';
|
|
10
10
|
import { declarationEntrances } from './debugCaptureModel.ts';
|
|
11
11
|
import type { GraphFlowRelation, Selection } from './panelTypes.ts';
|
|
12
|
+
import { formatMeasuredGpuWork } from './panelWorkbenchHelpers.ts';
|
|
12
13
|
|
|
13
14
|
export type GraphSceneElementId = string;
|
|
14
15
|
|
|
@@ -98,6 +99,7 @@ export type GraphScene = {
|
|
|
98
99
|
|
|
99
100
|
export type CreateGraphSceneOptions = {
|
|
100
101
|
readonly groupsEnabled: boolean;
|
|
102
|
+
readonly showResourceDeclarations?: boolean;
|
|
101
103
|
readonly expandedGroupPaths: ReadonlySet<string>;
|
|
102
104
|
};
|
|
103
105
|
|
|
@@ -113,7 +115,7 @@ export function createGraphScene(
|
|
|
113
115
|
snapshot: FrameGraphDebugViewModel,
|
|
114
116
|
options: CreateGraphSceneOptions,
|
|
115
117
|
): GraphScene {
|
|
116
|
-
return createFrameFlowScene(snapshot, options.groupsEnabled, options.expandedGroupPaths);
|
|
118
|
+
return createFrameFlowScene(snapshot, options.groupsEnabled, options.expandedGroupPaths, options.showResourceDeclarations ?? true);
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
export function graphGroupElementId(pathKey: string): GraphSceneElementId {
|
|
@@ -133,7 +135,7 @@ export function selectionKey(selection: Selection): string {
|
|
|
133
135
|
case 'root':
|
|
134
136
|
return `root:${selection.key}`;
|
|
135
137
|
case 'culled':
|
|
136
|
-
return `culled:${selection.
|
|
138
|
+
return `culled:${selection.id}`;
|
|
137
139
|
case 'segment':
|
|
138
140
|
return `segment:${selection.index}`;
|
|
139
141
|
}
|
|
@@ -143,6 +145,7 @@ function createFrameFlowScene(
|
|
|
143
145
|
snapshot: FrameGraphDebugViewModel,
|
|
144
146
|
groupsEnabled: boolean,
|
|
145
147
|
expandedGroupPaths: ReadonlySet<string>,
|
|
148
|
+
showResourceDeclarations: boolean,
|
|
146
149
|
): GraphScene {
|
|
147
150
|
const interaction = createMutableInteractionIndex();
|
|
148
151
|
const segmentByNodeId = executionSegmentByNodeId(snapshot);
|
|
@@ -157,7 +160,7 @@ function createFrameFlowScene(
|
|
|
157
160
|
...snapshot.accessEdges.filter((access) => retainedIds.has(access.nodeId)).map((access) => access.resource.id),
|
|
158
161
|
...snapshot.roots.flatMap((root) => root.resource ? [root.resource.id] : []),
|
|
159
162
|
]);
|
|
160
|
-
const resources = snapshot.resources.filter((resource) => usedResourceIds.has(resource.id));
|
|
163
|
+
const resources = showResourceDeclarations ? snapshot.resources.filter((resource) => usedResourceIds.has(resource.id)) : [];
|
|
161
164
|
const populatedGroupIds = new Set<string>();
|
|
162
165
|
for (const item of [...snapshot.nodes, ...resources]) {
|
|
163
166
|
for (const id of groupsById.get(item.debugGroupId ?? '')?.ancestorIds ?? []) populatedGroupIds.add(id);
|
|
@@ -202,8 +205,8 @@ function createFrameFlowScene(
|
|
|
202
205
|
groupId: group.id,
|
|
203
206
|
groupPathKey: group.pathKey,
|
|
204
207
|
label: createGroupLabel(group, collapsed),
|
|
205
|
-
overviewLabel: `${collapsed ? '▸' : '▾'} ${group.label}`,
|
|
206
|
-
title: createGroupTitle(group),
|
|
208
|
+
overviewLabel: `${collapsed ? '▸' : '▾'} ${group.label.replace(/\s+/g, ' ').trim()}`,
|
|
209
|
+
title: createGroupTitle(group, snapshot),
|
|
207
210
|
parentId: group.parentId !== undefined && includedGroupIds.has(group.parentId)
|
|
208
211
|
? graphGroupElementId(groupsById.get(group.parentId)!.pathKey)
|
|
209
212
|
: undefined,
|
|
@@ -269,7 +272,7 @@ function createFrameFlowScene(
|
|
|
269
272
|
}
|
|
270
273
|
if (useGroups) {
|
|
271
274
|
for (const group of snapshot.debugGroups) {
|
|
272
|
-
if (includedGroupIds.has(group.id)) continue;
|
|
275
|
+
if (includedGroupIds.has(group.id) || !populatedGroupIds.has(group.id)) continue;
|
|
273
276
|
const collapsedAncestorId = group.ancestorIds.find((id) => !isExpanded(groupsById.get(id)!));
|
|
274
277
|
if (collapsedAncestorId !== undefined) {
|
|
275
278
|
const representative = graphGroupElementId(groupsById.get(collapsedAncestorId)!.pathKey);
|
|
@@ -289,7 +292,7 @@ function createFrameFlowScene(
|
|
|
289
292
|
const node: ResourceSceneNode = {
|
|
290
293
|
id: resourceElementId(resource.id), kind: 'resource', resourceId: resource.id, resourceKind: resource.kind,
|
|
291
294
|
label: origin + ' · ' + (resource.kind === 'buffer' ? 'Buffer' : 'Texture') + '\n' + formatGraphResourceLabel(labelResource(resource)),
|
|
292
|
-
overviewLabel: origin + '\n' + formatGraphResourceLabel(shortGraphLabel(labelResource(resource))),
|
|
295
|
+
overviewLabel: origin + '\n' + formatGraphResourceLabel(shortGraphLabel(labelResource(resource)), 1),
|
|
293
296
|
title: createResourceTitle(resource, snapshot, snapshot.accessesByResourceId.get(resource.id) ?? [])
|
|
294
297
|
+ '\nDeclaration entrance, not a complete initial-content provenance graph.',
|
|
295
298
|
parentId: useGroups && resource.debugGroupId ? graphGroupElementId(groupsById.get(resource.debugGroupId)!.pathKey) : undefined,
|
|
@@ -311,7 +314,7 @@ function createFrameFlowScene(
|
|
|
311
314
|
addRelation(representativeByNodeId.get(dependency.fromNodeId)!, representativeByNodeId.get(dependency.toNodeId)!, dependency.resource.id,
|
|
312
315
|
{ role: dependency.kind, nodeIds: [dependency.fromNodeId, dependency.toNodeId], dependency });
|
|
313
316
|
}
|
|
314
|
-
for (const access of declarationEntrances(snapshot.nodes, snapshot.accessEdges, snapshot.edges)) {
|
|
317
|
+
for (const access of showResourceDeclarations ? declarationEntrances(snapshot.nodes, snapshot.accessEdges, snapshot.edges) : []) {
|
|
315
318
|
addRelation(representativeByResourceId.get(access.resource.id)!, representativeByNodeId.get(access.nodeId)!, access.resource.id,
|
|
316
319
|
{ role: 'declaration', nodeIds: [access.nodeId] });
|
|
317
320
|
}
|
|
@@ -343,11 +346,13 @@ function createFrameFlowScene(
|
|
|
343
346
|
: rangeLabel;
|
|
344
347
|
const reasonLabel = root.reason.split('-').map((word) => word[0]!.toUpperCase() + word.slice(1)).join(' ');
|
|
345
348
|
const disambiguate = rangesByRootFamily.get(JSON.stringify([resource.id, root.reason]))!.size > 1;
|
|
349
|
+
const initialLabel = !showResourceDeclarations && root.resolution?.usesInitialContents
|
|
350
|
+
? '\n' + (root.resolution.producerNodeIds.length ? 'With initial contents' : 'Initial contents only') : '';
|
|
346
351
|
rootNodes.push({
|
|
347
352
|
id, kind: 'root', rootKey: root.key, resourceId: resource.id, resourceKind: resource.kind,
|
|
348
353
|
label: reasonLabel + '\n' + formatGraphResourceLabel(labelResource(resource))
|
|
349
|
-
+ (disambiguate ? '\n' + formatGraphResourceLabel(rangeSummary) : ''),
|
|
350
|
-
overviewLabel: reasonLabel + '\n' + formatGraphResourceLabel(shortGraphLabel(labelResource(resource))),
|
|
354
|
+
+ (disambiguate ? '\n' + formatGraphResourceLabel(rangeSummary, 1) : '') + initialLabel,
|
|
355
|
+
overviewLabel: reasonLabel + '\n' + formatGraphResourceLabel(shortGraphLabel(labelResource(resource)), 1) + initialLabel,
|
|
351
356
|
title: labelResource(resource) + '\n' + root.reason + '\n' + rangeLabel
|
|
352
357
|
+ (root.resolution ? '\nProducers: ' + (root.resolution.producerNodeIds.join(', ') || 'none') + '\nInitial contents: ' + root.resolution.usesInitialContents : '\nOutput sources unavailable in Legacy capture.'),
|
|
353
358
|
});
|
|
@@ -356,7 +361,7 @@ function createFrameFlowScene(
|
|
|
356
361
|
for (const nodeId of root.resolution?.producerNodeIds ?? []) {
|
|
357
362
|
addRelation(representativeByNodeId.get(nodeId)!, id, resource.id, { role: 'output-producer', nodeIds: [nodeId], rootKey: root.key });
|
|
358
363
|
}
|
|
359
|
-
if (root.resolution?.usesInitialContents) addRelation(representativeByResourceId.get(resource.id)!, id, resource.id,
|
|
364
|
+
if (showResourceDeclarations && root.resolution?.usesInitialContents) addRelation(representativeByResourceId.get(resource.id)!, id, resource.id,
|
|
360
365
|
{ role: 'output-initial', nodeIds: [], rootKey: root.key });
|
|
361
366
|
}
|
|
362
367
|
const edges: GraphSceneEdge[] = [...aggregates.entries()].map(([key, entry]) => {
|
|
@@ -479,20 +484,47 @@ function formatGraphNodeLabel(label: string): string {
|
|
|
479
484
|
return formatGraphLabel(label, 20, 3);
|
|
480
485
|
}
|
|
481
486
|
|
|
482
|
-
function formatGraphResourceLabel(label: string): string {
|
|
483
|
-
|
|
487
|
+
function formatGraphResourceLabel(label: string, maxLines = 2): string {
|
|
488
|
+
let remaining = [...label.replace(/\s+/g, ' ').trim()];
|
|
484
489
|
// Reserve conservative monospace cells for CJK/emoji fallback glyphs as well.
|
|
485
|
-
const glyphs = [...singleLine];
|
|
486
490
|
const cells = (glyph: string) => /\p{Mark}/u.test(glyph) ? 0 : glyph.codePointAt(0)! > 255 ? 2 : 1;
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
491
|
+
const take = (limit: number) => {
|
|
492
|
+
let width = 0;
|
|
493
|
+
let count = 0;
|
|
494
|
+
for (const glyph of remaining) {
|
|
495
|
+
if (width + cells(glyph) > limit) break;
|
|
496
|
+
width += cells(glyph);
|
|
497
|
+
count++;
|
|
498
|
+
}
|
|
499
|
+
return count;
|
|
500
|
+
};
|
|
501
|
+
const lines: string[] = [];
|
|
502
|
+
while (remaining.length) {
|
|
503
|
+
let count = take(18);
|
|
504
|
+
if (count === remaining.length) {
|
|
505
|
+
lines.push(remaining.join(''));
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
if (lines.length === maxLines - 1) {
|
|
509
|
+
lines.push(remaining.slice(0, take(17)).join('').trimEnd() + '…');
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
// Prefer a readable namespace/path boundary over splitting a word.
|
|
513
|
+
let boundary = 0;
|
|
514
|
+
for (const separators of [/[./]/u, /[_\-\s]/u]) {
|
|
515
|
+
for (let index = count - 1; index > 0; index--) {
|
|
516
|
+
if (separators.test(remaining[index]!)) {
|
|
517
|
+
boundary = index + 1;
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (boundary) break;
|
|
522
|
+
}
|
|
523
|
+
if (boundary) count = boundary;
|
|
524
|
+
lines.push(remaining.slice(0, count).join('').trimEnd());
|
|
525
|
+
remaining = remaining.slice(count);
|
|
494
526
|
}
|
|
495
|
-
return
|
|
527
|
+
return lines.join('\n');
|
|
496
528
|
}
|
|
497
529
|
|
|
498
530
|
function formatGraphLabel(label: string, maxLineLength: number, maxLines: number): string {
|
|
@@ -574,10 +606,11 @@ function shortGraphLabel(label: string): string {
|
|
|
574
606
|
|
|
575
607
|
function createGroupLabel(group: FrameGraphDebugGroup, collapsed: boolean): string {
|
|
576
608
|
const summary = group.summary;
|
|
577
|
-
const
|
|
578
|
-
? '
|
|
579
|
-
|
|
580
|
-
|
|
609
|
+
const parts = [
|
|
610
|
+
`${collapsed ? '▸' : '▾'} ${group.label.replace(/\s+/g, ' ').trim()}`,
|
|
611
|
+
`${summary.retainedNodeCount} nodes` + (summary.culledNodeCount ? ` · ${summary.culledNodeCount} culled` : ''),
|
|
612
|
+
];
|
|
613
|
+
return parts.join(collapsed ? '\n' : ' · ');
|
|
581
614
|
}
|
|
582
615
|
|
|
583
616
|
function createNodeTitle(
|
|
@@ -585,18 +618,22 @@ function createNodeTitle(
|
|
|
585
618
|
segment: FrameGraphDebugViewModel['executionSegments'][number] | undefined,
|
|
586
619
|
snapshot: FrameGraphDebugViewModel,
|
|
587
620
|
): string {
|
|
621
|
+
const gpu = node.kind === 'external-submission' ? 'Opaque'
|
|
622
|
+
: node.kind !== 'render' && node.kind !== 'compute' ? 'Not applicable'
|
|
623
|
+
: node.gpuDurationMicros === undefined ? 'Not collected' : `${formatGpuDuration(node)} ms`;
|
|
588
624
|
return [
|
|
589
625
|
labelNode(node),
|
|
590
626
|
`kind: ${node.kind}`,
|
|
591
627
|
`group: ${debugGroupPathForId(node.debugGroupId, snapshot)}`,
|
|
592
628
|
`segment: ${segment ? `${segment.index}:${segment.kind}` : '-'}`,
|
|
593
|
-
`
|
|
629
|
+
`CPU duration: ${node.cpuDurationMicros === undefined ? 'Not collected' : (node.cpuDurationMicros / 1000).toFixed(3) + ' ms'}`,
|
|
630
|
+
`GPU duration: ${gpu}`,
|
|
594
631
|
`reads: ${node.reads.map((access) => labelResource(access.resource)).join(', ') || '-'}`,
|
|
595
632
|
`writes: ${node.writes.map((access) => labelResource(access.resource)).join(', ') || '-'}`,
|
|
596
633
|
].join('\n');
|
|
597
634
|
}
|
|
598
635
|
|
|
599
|
-
function createGroupTitle(group: FrameGraphDebugGroup): string {
|
|
636
|
+
function createGroupTitle(group: FrameGraphDebugGroup, snapshot: FrameGraphDebugViewModel): string {
|
|
600
637
|
const summary = group.summary;
|
|
601
638
|
return [
|
|
602
639
|
group.path.join(' / '),
|
|
@@ -605,10 +642,11 @@ function createGroupTitle(group: FrameGraphDebugGroup): string {
|
|
|
605
642
|
`inputs: ${summary.inputResources.map(labelResource).join(', ') || '-'}`,
|
|
606
643
|
`outputs: ${summary.outputResources.map(labelResource).join(', ') || '-'}`,
|
|
607
644
|
`transient registered/accessed: ${summary.registeredTransientResourceCount}/${summary.accessedTransientResourceCount}`,
|
|
608
|
-
`physical allocations: ${summary.physicalAllocationCount}`,
|
|
645
|
+
`physical allocations: ${snapshot.protocol.memory.allocationReport.status === 'available' ? summary.physicalAllocationCount : 'Not collected'}`,
|
|
609
646
|
`segments: ${summary.executionSegmentCount}`,
|
|
610
647
|
`opaque: ${summary.externalSubmissionCount}`,
|
|
611
|
-
|
|
648
|
+
`CPU pass sum: ${summary.cpuTimedNodeCount ? (summary.cpuWorkDurationMicros / 1000).toFixed(3) + ' ms' : 'Not collected'} (${summary.cpuTimedNodeCount}/${summary.retainedNodeCount})`,
|
|
649
|
+
`GPU pass sum: ${formatMeasuredGpuWork(summary.gpuWorkDurationMicros, summary.timedNodeCount, summary.timingEligibleNodeCount)}`,
|
|
612
650
|
].join('\n');
|
|
613
651
|
}
|
|
614
652
|
|