@zenfg/inspector usage
@zenfg/inspector is an embeddable, renderer-independent DOM workbench for ZenFG Snapshot data. It depends on @zenfg/snapshot, Cytoscape, and ELK, but has no WebGPU, wgpu, engine, or host-UI dependency.
Use captured graphs, resource facts, and optional timings to understand a frame, then export Snapshot JSON alongside code for human or AI-assisted analysis. See the analyze-and-verify workflow.
The package owns Snapshot validation, migration, visualization, and workbench state. The host owns layout around the workbench, live-capture policy, file retention, and the renderer that produces Snapshot data.
Resource and access statistics describe the declarations captured in the graph. They do not automatically scan all shader bindings or measure total GPU memory; workload-private buffers can be absent. More explicit declarations can be useful for diagnosis. See Choosing resource declaration granularity.
Use @zenfg/inspector to embed a Snapshot workbench, connect live captures, import diagnostics, customize themes, or release an Inspector instance.
Quick start
Run in a modern browser from your existing TypeScript app. Give the host element an explicit size (for example, height: 600px) and supply a valid Snapshot capture. Call mountInspector(host, capture) below; the returned function removes the workbench.
Mount one Inspector into a host element and provide an optional live-capture callback:
import { mountFrameGraphInspector, type FrameGraphCaptureRequest } from '@zenfg/inspector';
import type { FrameGraphSnapshot } from '@zenfg/snapshot';
export function mountInspector(
host: HTMLElement,
capture: (request: FrameGraphCaptureRequest) => FrameGraphSnapshot | Promise<FrameGraphSnapshot>,
): () => void {
const inspector = mountFrameGraphInspector(host, {
captureSnapshot: capture,
branding: 'ZenFG Inspector',
});
return () => inspector.destroy();
}The host must have non-zero width and height. FrameGraphInspector fills that host, so the same workbench can be embedded in a tool panel or mounted as a full-page application.
Both mountFrameGraphInspector(host, options) and new FrameGraphInspector(options) accept FrameGraphInspectorOptions. Graph starts with resource declaration nodes hidden by default, emphasizing pass dependencies and explicit outputs. Set showResourceDeclarations: true to start with declaration nodes visible:
import { FrameGraphInspector } from '@zenfg/inspector';
const inspector = new FrameGraphInspector({
showResourceDeclarations: true,
});This option sets the initial Graph visibility. Users can change it with the Declarations toolbar toggle; the current choice persists across captures and imports in that instance. Locating a resource through Graph search or Show in Graph enables declarations automatically. Resources tables, details, memory, and Snapshot exports always retain the complete resource data.
Themes and style customization
Inspector uses Tokyo Night Storm by default and includes a complete Light preset. Use public --zfgi-* variables on an owned host, or pass a theme object and call setTheme() to update one instance without losing graph interaction state. After changing external CSS dynamically, call refreshTheme() to synchronize the Canvas graph. Static CSS is read automatically at mount.
See Theming for JS presets, the optional CSS preset stylesheet, variable reference, compatibility aliases, and multi-instance behavior.
Common tasks
| Task | Public API |
|---|---|
| Mount into an existing element | mountFrameGraphInspector() |
| Construct without appending | new FrameGraphInspector() and its dom property |
| Request live data from the host | captureSnapshot option |
| Replace the current capture | setSnapshot() |
| Read the current canonical capture | getSnapshot() |
| Import and migrate a file | importSnapshot() |
| Download canonical Snapshot JSON | downloadSnapshot() |
| Copy canonical Snapshot JSON | copySnapshotJson() |
| Show resource declaration nodes initially | showResourceDeclarations option (default false) |
| Apply a preset or custom theme | theme option and setTheme() |
| Synchronize graph after external CSS changes | refreshTheme() |
| Set visible product branding | branding option |
| Limit imported file size | maxImportBytes option |
| Limit automatic graph layout | maxGraphElements option |
| Release DOM, workers, and listeners | destroy() |
Exact options, defaults, return types, and lifecycle behavior are documented by the TSDoc preserved in the packaged source and declarations.
Common mistakes
| Symptom | Fix |
|---|---|
| The Inspector is mounted but invisible | Give the host element a non-zero width and height. |
| Live capture never becomes available | Provide captureSnapshot, or call setSnapshot() with canonical data. |
| A large capture has no graph layout | Raise maxGraphElements deliberately or use the tabular/raw views. |
| A failed import appears to do nothing | Inspect the visible validation feedback; the previous valid capture is intentionally preserved. |
| Old capture results replace new ones in host code | Let the Inspector own capture sequencing instead of applying asynchronous results separately. |
| DOM or worker resources remain after unmount | Call destroy() when the host tool is released. |
Further reading
CPU and GPU capture
The Capture action always requests CPU and GPU together. Providers receive a FrameGraphCaptureRequest and must forward its timing mode to the runtime. Timing is collected only for capture frames; imported data and normal frames are unchanged. CPU columns, sorting, group sums and coverage include all retained node kinds. CPU execute total and GPU span remain separate. Missing, culled and real zero readings are distinct; zero does not guarantee zero CPU cost.
Analyze and verify a frame
- Capture an actual frame and inspect its graph, resource lifetimes, and diagnostics.
- Check available CPU/GPU timings and their coverage. Locate the relevant passes and dependencies before deciding what to change.
- Use the workbench's Snapshot JSON download or clipboard copy with the relevant graph declarations and renderer code for human or AI-assisted analysis. Include the observed problem and constraints; use pass names and resource IDs to make feedback precise. A Snapshot describes declared work, not shader behavior or an entire engine's internals.
- Capture again after modifying the code. Compare the resulting structure and available measurements under comparable workload and device conditions.
This is an analysis workflow, not built-in AI integration or automatic optimization. GPU timing depends on device support and node coverage; absent timing is not zero. External-engine internals remain opaque. Snapshot does not include GPU resource contents or replayable commands. The Inspector itself does not upload imported snapshots; sharing exported data with another tool is a separate user action.
Workbench behavior
The workbench provides Overview, Graph, Passes, Resources, Memory, and Diagnostics views plus a selection Inspector. It supports live capture, direct Snapshot replacement, file import, supported legacy migration, canonical download, clipboard copy, filtering, sorting, group expansion, and graph navigation.
The first capture opens Graph with no selection and the detail pane closed. Subsequent captures preserve the current view, filters, and selection when the same object still exists. Pass selection follows the node ID across retained and culled states; removing the selected object clears selection and closes details. These preferences, group expansion, and detail width belong to the current Inspector instance and are not saved across sessions.
| View | Purpose |
|---|---|
| Overview | Full-width GPU span, CPU execute, physical allocation estimate and retained-pass metrics, with the five slowest measured GPU or CPU passes, work composition, resource/pool summaries and diagnostic counts. Capture metadata and additional Snapshot facts are expandable. |
| Graph | Frame Flow structure, searchable by pass, resource, group, or output, with explicit target location and a collapsible legend. |
| Passes | All, Retained, or Culled passes, filtered by kind and name/ID/group, with execution order, GPU, and CPU sorting. Both timings remain visible in compact rows. The separate Group Hierarchy has its own expansion and path search. |
| Resources | Name/ID/group, type, and Transient/Imported/Surface filters, plus name, estimated size, and first-use sorting. Rows show concise descriptors; full descriptors and allocations remain in details. |
| Memory | Physical allocation and alias savings estimates, a separate cumulative pool summary, and collapsible allocation groups with inclusive execution-slot lifetimes. Search, allocation/size sorting, and All/Aliased/Single/Unallocated filters are available. |
| Diagnostics | Every captured error, warning, and informational message, including repeated codes. Counted severity buttons and code/message search filter messages; Compilation details separately contains expandable retention roots, culling reasons, and execution segments. |
Lists show the matching and total counts, retain continuous scrolling, and offer Clear filters. Passes order retained work by execution order, then culled work by recording order when available, falling back to capture order. Retained means the compiler kept a pass; it does not itself prove GPU execution. Culled work has no execution order, segment, or GPU timing. The Group Hierarchy arrows only change that tree; Show in Graph is a separate action.
Diagnostics preserve capture order within each severity and offer separate node and resource links when a message references both. Culled-node links work the same way as retained-node links. The Diagnostics tab shows error/warning counts. Source, frame, and capture time are available under Capture information in Overview; an absent capture timestamp is shown as unknown. The Legend button stays at the bottom left while its scrollable panel opens above it, preserving the canvas size and viewport. Click the button again, use the panel's close button, press Escape, or click outside to dismiss it. Graph controls also float over the canvas. Search opens the search field; Escape dismisses it. Results show object type, ID, and group path when available; exact names and IDs rank first. Arrow keys move through results and Enter locates the active result. Display contains Declarations, Groups, and Collapse All. Zoom controls show the current percentage, with zoom in/out, 100% for readable scale, Fit selection, and Fit for the full graph. These controls preserve selection and do not expand groups. The selected-group action provides explicit Expand/Collapse controls alongside the existing double-click gesture.
The selection pane has Summary, Relations, and Raw tabs. Summary explains compilation status, timing coverage, accesses, allocation relationships, and attached diagnostics. Relations distinguish access facts, dependencies, output sources, and allocation membership. Raw shows the selected original object and its JSON path in the canonical Snapshot, with field/value search, folding, and copy of the complete object regardless of the current search. Legacy imports explicitly identify their migrated canonical data. IDs can be copied without their presentation labels.
Selecting an object opens its details on object views. Overview uses the full width while retaining selection. The pane defaults to 300px and can be resized from 300px to 480px while leaving at least 520px for the main view. Workspaces narrower than 828px use a modal drawer capped at 340px, with at least a 24px outside strip, a backdrop, keyboard focus containment, and focus restoration when closed. The main content width determines table and information layout; auxiliary table columns move into details when space is limited. Arrow keys and Home/End switch tabs. Escape first dismisses the innermost active menu or detail pane; the Examples host respects handled key events.
Timing and memory interpretation
Timing labels distinguish not collected, partial coverage, complete coverage, not applicable, and a measured zero. Measured pass sum is the sum of available pass timings with its timed/eligible count; GPU span is displayed separately. Opaque external work has no inferred duration. Passes is the comparison table for individual timings; Diagnostics does not repeat it.
Overview ranks measured passes by duration and shows their share of the entire measured pass sum, including passes outside the top five. GPU coverage counts retained render/compute passes; CPU coverage counts all retained passes. A zero sum has no percentage. Click a pass to locate it in Passes, or View all passes to clear pass filters and open the list while preserving its sort. The GPU/CPU choice and expanded Overview disclosures stay with the Inspector instance.
Memory numbers are estimates with different scopes:
| Metric | Meaning |
|---|---|
| Transient estimate | Declared estimated sizes of transient logical resources. |
| Logical capacity | Allocation capacity counted for each assigned logical transient resource. |
| Physical allocation estimate | Estimated sizes of physical allocations in the allocation report, counted once each. |
| Alias savings estimate | Logical capacity minus physical allocation estimate, where both are known. |
| Idle retained estimate | Producer-reported idle pool allocations at sampling time, which may outlive this graph. |
An unavailable report is not zero. Unknown resource/allocation sizes remain unknown, and partial summaries show the known-size coverage. A valid empty report can contain real zero totals. None of these metrics is total GPU memory or a measured peak. Memory summaries always cover the entire Snapshot; filters change the visible rows and matching count only. A lifetime includes both first and last execution slots. Tick positions, grid lines, and resource bars share one stable Snapshot coordinate range; missing lifetimes have no bar.
Lists use full tables at content widths of 1000px or more, merge secondary columns below 1000px, and reflow into compact rows below 600px. GPU/CPU timings and resource estimates remain visible when the selection pane reduces the main content width. Memory lifetimes use the same full execution-slot domain at every width. Allocation folds and estimate information persist across captures; search temporarily expands matching allocations, and explicit location expands the target without changing ordinary selection behavior.
Diagnostics severity counts always cover the complete Snapshot; the matching count reflects search and severity together. Filtering messages does not filter Compilation details. Summary puts object-specific measurements before auxiliary facts and remembers each object's disclosure state. Relations keep object selection separate from the location icon. Raw highlights matching fields and values and always copies the complete canonical object, including hidden fields.
Overview labels pool reuse as cumulative: reuse/acquire counters cover the pool's lifetime, not just the captured frame. Idle pool bytes remain separate from the frame's physical allocation estimate. Logical transient estimates can include resources referenced only by culled passes and are available under Additional snapshot details.
Frame Flow interaction
Reading groups
Enable Declarations to see resource entrances in their declaration groups, and use Groups/expansion controls to browse the captured hierarchy. Cross-group use does not move an entrance; captured boundary relationships contribute to group Inputs/Outputs. Imported describes caller-owned native storage, not borrowing from another diagnostic group. Identical labels remain distinct groups.
Groups describe diagnostic attribution, not retention, allocation isolation or exclusive GPU memory ownership. Shared/aliased allocations can contribute to several group summaries. Read the Frame Flow guide for the complete display and selection behavior.
Graph is a single Frame Flow view: resource declarations → retained pass dependencies → explicit resource outputs. Resource entrances belong to their declaration groups; output roots remain top-level and use compiler-supplied final producers and initial-content contributions. Culled passes remain in lists and details, not in the graph. Collapsed groups aggregate relationships without discarding their underlying semantics. Graph starts with resource declaration entrances hidden, focusing on pass dependencies and outputs. Turn on the Declarations toolbar toggle to show them. When hidden, declaration entrances, their relationships (including initial-content output edges), and resource-only groups leave the layout. Pass dependencies and producer-to-output edges remain. Hosts can set showResourceDeclarations: true in FrameGraphInspectorOptions when constructing or mounting an Inspector to start with declarations visible; the option sets only the initial visibility. Outputs with known initial-content contributions show With initial contents or Initial contents only; unavailable Legacy sources are not inferred. The toggle persists across views, captures, and imports within this Inspector instance. Changing it fits the updated graph while preserving selection and group expansion. Resources tables, details, memory, and Snapshot exports remain complete.
All nodes use single-line borders. Ordinary passes are rounded rectangles, external submissions are cut-corner rectangles, resource entrances are ellipses, and output roots are right-pointing tags. Declarations and outputs each have a role colour independent of Buffer/Texture; pass categories have distinct colours. Resources and Memory retain their resource-type colours. Hover and selection change border emphasis, not shape, text colour, or fill.
Passes show the name first, followed by kind, the zero-based execution slot (#n), and segment index (Sn) when available. Horizontal layout expresses dependencies, not measured execution times or a timeline. Entrances show source/type first and the resource name second; outputs show their purpose first and name second. Long names wrap within bounded labels, with full names available on hover and in details. Output ranges appear only to distinguish different ranges of the same resource and purpose. Exact ranges and final sources remain in hover/details. Semantic zoom hides auxiliary types and range summaries. The grouped legend describes the whole snapshot's Frame Flow, including collapsed objects, respects the Declarations toggle, and remains stable while groups expand/collapse. Culled-only categories and unused resources do not add legend entries.
Category fills are opaque sRGB tints over the canvas: 18% in Storm and 8% in Light. Theme-specific node text targets at least 7:1 contrast. Supporting text targets 4.5:1, and identifying borders/symbols target 3:1 against their adjacent backgrounds. Automated checks cover both official themes and interaction states; custom CSS colour overrides must preserve these contrast relationships.
Solid edges represent declarations, values, and output sources; dashed edges represent ordering-only relationships. Edges never carry labels. Hover provides temporary hints: resource entrances and edges link only to visible entrances and edges with the same logical resource ID, regardless of range. Passes, groups, and output nodes only highlight themselves. These associations do not imply matching ranges or a continuous dataflow path, and never substitute a collapsed group for a hidden resource entrance. Direct and linked hover use the same strong visual style. Selection uses a blue outline without a glow. Hovering a Summary or Relations link previews its target in the graph using the same rules, without changing selection or moving the view; leaving the link or changing/closing the detail pane clears the preview.
Resource Pass accesses entries link only the pass name; access mode, write contents, producesValue, and available ranges remain descriptive text. Pass details use the same access facts. An access is not a graph relationship and is not mapped to an arbitrary dependency edge. Resource Relations also link to individual output roots by reason and range.
Clicking an entrance, edge, or Resources row selects the same logical resource, including all its visible entrances and edges, but no passes, groups, or outputs. An output still selects its Root. Its View resource action and other resource links use the same resource selection. Changing to a different resource opens Summary; selecting the current resource again preserves the active detail tab. Selection does not switch workbench views, change filters, expand groups, or move the viewport. A hidden entrance becomes selected when manually expanded; its visible cross-group edges remain selected while it is hidden. Hover previews are independent, with selection styling taking precedence, and never pin a tooltip. Edge hints show their visible endpoints, resource, distinct relationship types, and underlying relation count, including mixed types in aggregates. Clicking an edge continues to select its logical resource. Focus relations explicitly emphasizes the selected pass's directly recorded relations and their visible endpoints, or a visible group's subtree and direct boundary neighbors. Unrelated work fades; hover and selection remain readable. Focus does not infer a continuous path from a shared resource ID, expand groups, change selection, move the viewport, or filter the Snapshot. Turn it off to restore the full graph. Selecting a different object updates the focus; unsupported selection kinds or an unavailable graph disable it. Double-clicking a group expands or collapses it without replacing selection; single-click group selection waits briefly to distinguish that gesture. Legacy outputs with unavailable resolution remain visible but have no inferred source edges.
Explicit Show in … actions perform navigation: they switch views, clear blocking filters, expand necessary ancestors, and scroll or center the target. They close a narrow-host drawer so the target is visible. Graph search uses the same explicit location behavior. Locating a resource through Graph search or Show in Graph also turns Declarations on; ordinary selection and hover never change the toggle. Failed location restores the previous declaration and group settings. Objects absent from Frame Flow show an explanation and a list-view action; locating an object does not bypass the graph element budget. Ordinary selection and hover retain the behavior above.
Capture, import, and direct replacement share a revision counter so stale async results cannot replace newer data. Failed or oversized imports leave the current valid capture and UI state intact, and operation feedback can be expanded to inspect the full failure reason. Views render their new capture on first activation, and selection updates refresh only the active view and details. Memory grouping and lifetime coordinates are cached per Snapshot; Raw is created only when opened. Graph layout is lazy and is disabled with an explanation when a capture exceeds maxGraphElements; the tabular and raw views remain available.
Snapshot labels, URLs, extensions, and diagnostics are assigned through text DOM APIs and are never executed as markup or code. The standalone Site page in apps/site/inspector mounts this same package without adding duplicate controls.