Skip to main content

zenfg_snapshot/
types.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5/// Major/minor version carried by every Snapshot document.
6#[allow(missing_docs)]
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub struct SnapshotVersion {
10    pub major: u32,
11    pub minor: u32,
12}
13
14/// Canonical, strongly typed ZenFG FrameGraph Snapshot 1.2 document.
15///
16/// This structure mirrors the portable JSON wire model. Prefer
17/// [`crate::parse_frame_graph_snapshot`] or [`crate::decode_frame_graph_snapshot`]
18/// over direct Serde deserialization so format/version checks, migrations, and
19/// cross-record validation are applied. Prefer [`crate::to_json`] or
20/// [`crate::to_json_pretty`] for validated encoding.
21#[allow(missing_docs)]
22#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase")]
24pub struct FrameGraphSnapshotV1 {
25    pub format: String,
26    pub version: SnapshotVersion,
27    pub producer: SnapshotProducer,
28    pub capture: SnapshotCapture,
29    pub graph: SnapshotGraph,
30    pub memory: SnapshotMemory,
31    pub timings: SnapshotTimings,
32    pub diagnostics: Vec<SnapshotDiagnostic>,
33    pub extensions: BTreeMap<String, serde_json::Value>,
34}
35
36/// Identity and optional runtime metadata of the library that produced a capture.
37#[allow(missing_docs)]
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase")]
40pub struct SnapshotProducer {
41    pub name: String,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub version: Option<String>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub language: Option<String>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub runtime: Option<SnapshotRuntime>,
48}
49
50/// Optional graphics implementation, API, and native backend facts.
51#[allow(missing_docs)]
52#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct SnapshotRuntime {
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub implementation: Option<String>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub graphics_api: Option<String>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub backend: Option<String>,
61}
62
63/// Frame identity, capture time, and optional migration provenance.
64#[allow(missing_docs)]
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub struct SnapshotCapture {
68    pub frame_index: u64,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub captured_at: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub migration: Option<SnapshotMigration>,
73}
74
75/// Provenance and unavailable facts recorded when converting a historical format.
76#[allow(missing_docs)]
77#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
78#[serde(rename_all = "camelCase")]
79pub struct SnapshotMigration {
80    pub source_format: SnapshotMigrationSourceFormat,
81    pub unavailable_facts: Vec<SnapshotUnavailableFact>,
82}
83
84/// Historical wire format from which a canonical V1 document was migrated.
85#[allow(missing_docs)]
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
87#[serde(rename_all = "kebab-case")]
88pub enum SnapshotMigrationSourceFormat {
89    #[serde(rename = "snapshot-v1.1")]
90    SnapshotV1_1,
91    LegacyV0,
92    LegacyCandidateV1,
93}
94
95/// Canonical graph fact that a historical source format could not represent.
96#[allow(missing_docs)]
97#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
98pub enum SnapshotUnavailableFact {
99    #[serde(rename = "graph.groups")]
100    GraphGroups,
101    #[serde(rename = "graph.textureViews")]
102    GraphTextureViews,
103    #[serde(rename = "graph.nodes.recordingOrder")]
104    GraphNodeRecordingOrder,
105    #[serde(rename = "graph.accesses.regions")]
106    GraphAccessRegions,
107    #[serde(rename = "graph.roots.range")]
108    GraphRootRange,
109    #[serde(rename = "graph.roots.resolution")]
110    GraphRootResolution,
111}
112
113/// Relational graph tables that make up the portable captured frame.
114#[allow(missing_docs)]
115#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct SnapshotGraph {
118    pub groups: Vec<SnapshotGroup>,
119    pub nodes: Vec<SnapshotNode>,
120    pub resources: Vec<SnapshotResource>,
121    pub texture_views: Vec<SnapshotTextureView>,
122    pub accesses: Vec<SnapshotAccess>,
123    pub dependencies: Vec<SnapshotDependency>,
124    pub roots: Vec<SnapshotRoot>,
125    pub segments: Vec<SnapshotSegment>,
126}
127
128/// One recording debug group and its optional parent relationship.
129#[allow(missing_docs)]
130#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct SnapshotGroup {
133    pub id: String,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub parent_id: Option<String>,
136    pub label: String,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub stable_key: Option<String>,
139}
140
141/// One recorded graph node with its original metadata and compile outcome.
142#[allow(missing_docs)]
143#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct SnapshotNode {
146    pub id: String,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub stable_key: Option<String>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub recording_order: Option<u64>,
151    pub kind: SnapshotNodeKind,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub label: Option<String>,
154    pub side_effect: bool,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub group_id: Option<String>,
157    pub compile_state: SnapshotNodeCompileState,
158}
159
160/// Portable kind of work represented by a captured graph node.
161#[allow(missing_docs)]
162#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
163#[serde(rename_all = "kebab-case")]
164pub enum SnapshotNodeKind {
165    Render,
166    Compute,
167    Copy,
168    ClearBuffer,
169    Command,
170    ExternalSubmission,
171}
172
173/// Whether a recorded node was retained, and its order or culling reason.
174#[allow(missing_docs)]
175#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(tag = "status", rename_all = "kebab-case")]
177pub enum SnapshotNodeCompileState {
178    Retained {
179        #[serde(rename = "executionOrder")]
180        execution_order: u64,
181    },
182    Culled {
183        reason: String,
184    },
185}
186
187/// One logical resource with descriptor, usage, lifetime, and allocation facts.
188#[allow(missing_docs)]
189#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct SnapshotResource {
192    pub id: String,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub stable_key: Option<String>,
195    pub kind: SnapshotResourceKind,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub label: Option<String>,
198    pub origin: SnapshotResourceOrigin,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub initial_contents: Option<SnapshotInitialContents>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub group_id: Option<String>,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub lifetime: Option<SnapshotLifetime>,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub allocation_id: Option<String>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub estimated_byte_size: Option<u64>,
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub descriptor: Option<SnapshotResourceDescriptor>,
211    pub usage_flags: Vec<SnapshotUsageFlag>,
212}
213
214/// Portable texture-or-buffer discriminator.
215#[allow(missing_docs)]
216#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
217#[serde(rename_all = "kebab-case")]
218pub enum SnapshotResourceKind {
219    Texture,
220    Buffer,
221}
222
223/// Ownership and allocation origin of a captured logical resource.
224#[allow(missing_docs)]
225#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
226#[serde(rename_all = "kebab-case")]
227pub enum SnapshotResourceOrigin {
228    Transient,
229    Imported,
230    Surface,
231}
232
233/// Whether a resource range is readable at the start of the captured frame.
234#[allow(missing_docs)]
235#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
236#[serde(rename_all = "kebab-case")]
237pub enum SnapshotInitialContents {
238    Defined,
239    Undefined,
240}
241
242/// Inclusive retained execution-order interval for one logical resource.
243#[allow(missing_docs)]
244#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
245#[serde(rename_all = "camelCase")]
246pub struct SnapshotLifetime {
247    pub first_use: u64,
248    pub last_use: u64,
249}
250
251/// Portable physical descriptor for a captured texture or buffer.
252#[allow(missing_docs)]
253#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
254#[serde(tag = "kind", rename_all = "kebab-case")]
255pub enum SnapshotResourceDescriptor {
256    Texture {
257        format: String,
258        size: SnapshotTextureSize,
259        dimension: String,
260        #[serde(rename = "mipLevelCount")]
261        mip_level_count: u64,
262        #[serde(rename = "sampleCount")]
263        sample_count: u64,
264        #[serde(rename = "viewFormats")]
265        view_formats: Vec<String>,
266    },
267    Buffer {
268        size: u64,
269    },
270}
271
272/// Three-dimensional texture extent using JSON-safe integer fields.
273#[allow(missing_docs)]
274#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct SnapshotTextureSize {
277    pub width: u64,
278    pub height: u64,
279    pub depth_or_array_layers: u64,
280}
281
282/// One normalized WebGPU usage flag in protocol-defined ordering.
283#[allow(missing_docs)]
284#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
285#[serde(rename_all = "kebab-case")]
286pub enum SnapshotUsageFlag {
287    MapRead,
288    MapWrite,
289    CopySrc,
290    CopyDst,
291    Index,
292    Vertex,
293    Uniform,
294    Storage,
295    Indirect,
296    QueryResolve,
297    TextureBinding,
298    StorageBinding,
299    RenderAttachment,
300}
301
302/// Fully normalized texture-view descriptor referenced by captured accesses.
303#[allow(missing_docs)]
304#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct SnapshotTextureView {
307    pub id: String,
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub stable_key: Option<String>,
310    pub resource_id: String,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub label: Option<String>,
313    pub format: String,
314    pub dimension: String,
315    pub aspect: String,
316    pub base_mip_level: u64,
317    pub mip_level_count: u64,
318    pub base_array_layer: u64,
319    pub array_layer_count: u64,
320    pub swizzle: String,
321}
322
323/// One declared node-to-resource access and its normalized affected region.
324#[allow(missing_docs)]
325#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
326#[serde(rename_all = "camelCase")]
327pub struct SnapshotAccess {
328    pub id: String,
329    pub node_id: String,
330    pub resource_id: String,
331    pub access: SnapshotAccessKind,
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub texture_view_id: Option<String>,
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub texture_region: Option<SnapshotTextureRegion>,
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub buffer_range: Option<SnapshotBufferRange>,
338    pub mode: SnapshotAccessMode,
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub contents: Option<SnapshotWriteContents>,
341    pub produces_value: bool,
342}
343
344/// Portable pipeline or copy role of one resource access.
345#[allow(missing_docs)]
346#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
347#[serde(rename_all = "kebab-case")]
348pub enum SnapshotAccessKind {
349    TextureSampled,
350    TextureStorageRead,
351    TextureStorageWrite,
352    TextureColorAttachmentWrite,
353    TextureDepthRead,
354    TextureDepthWrite,
355    TextureCopySrc,
356    TextureCopyDst,
357    BufferUniform,
358    BufferStorageRead,
359    BufferStorageWrite,
360    BufferVertex,
361    BufferIndex,
362    BufferIndirect,
363    BufferCopySrc,
364    BufferCopyDst,
365}
366
367/// Whether a captured access reads or writes its resource.
368#[allow(missing_docs)]
369#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
370#[serde(rename_all = "kebab-case")]
371pub enum SnapshotAccessMode {
372    Read,
373    Write,
374}
375
376/// Whether a write overwrites or preserves the prior logical value.
377#[allow(missing_docs)]
378#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
379#[serde(rename_all = "kebab-case")]
380pub enum SnapshotWriteContents {
381    Overwrite,
382    Preserve,
383}
384
385/// Normalized mip, layer/depth-slice, and aspect region for a texture access.
386#[allow(missing_docs)]
387#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub struct SnapshotTextureRegion {
390    pub base_mip_level: u64,
391    pub mip_level_count: u64,
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub base_array_layer: Option<u64>,
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub array_layer_count: Option<u64>,
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub base_depth_slice: Option<u64>,
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub depth_slice_count: Option<u64>,
400    pub aspect: String,
401}
402
403/// Byte range for a captured buffer access; absent size means the remaining buffer.
404#[allow(missing_docs)]
405#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
406#[serde(rename_all = "camelCase")]
407pub struct SnapshotBufferRange {
408    pub offset: u64,
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub size: Option<u64>,
411}
412
413/// One value-carrying or ordering edge between captured graph nodes.
414#[allow(missing_docs)]
415#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
416#[serde(rename_all = "camelCase")]
417pub struct SnapshotDependency {
418    pub from_node_id: String,
419    pub to_node_id: String,
420    pub resource_id: String,
421    pub kind: SnapshotDependencyKind,
422}
423
424/// Whether a dependency carries a logical value or only constrains ordering.
425#[allow(missing_docs)]
426#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
427#[serde(rename_all = "kebab-case")]
428pub enum SnapshotDependencyKind {
429    Value,
430    Ordering,
431}
432
433/// One observable resource/node root and its retention reason.
434#[allow(missing_docs)]
435#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
436#[serde(tag = "reason")]
437pub enum SnapshotRoot {
438    #[serde(rename = "present")]
439    Present(SnapshotResourceRoot),
440    #[serde(rename = "output")]
441    Output(SnapshotResourceRoot),
442    #[serde(rename = "readback")]
443    Readback(SnapshotResourceRoot),
444    #[serde(rename = "side-effect")]
445    SideEffect {
446        #[serde(rename = "nodeId")]
447        node_id: String,
448    },
449    #[serde(rename = "debug-capture")]
450    DebugCapture(SnapshotResourceRoot),
451    #[serde(rename = "persistent-state")]
452    PersistentState(SnapshotResourceRoot),
453}
454
455impl SnapshotRoot {
456    /// Resource selection, absent for side effects.
457    pub fn resource(&self) -> Option<&SnapshotResourceRoot> {
458        match self {
459            Self::SideEffect { .. } => None,
460            Self::Present(root)
461            | Self::Output(root)
462            | Self::Readback(root)
463            | Self::DebugCapture(root)
464            | Self::PersistentState(root) => Some(root),
465        }
466    }
467    /// Selected node, only for side effects.
468    pub fn node_id(&self) -> Option<&str> {
469        match self {
470            Self::SideEffect { node_id } => Some(node_id),
471            _ => None,
472        }
473    }
474    /// Observable retention reason.
475    pub fn reason(&self) -> SnapshotRootReason {
476        match self {
477            Self::SideEffect { .. } => SnapshotRootReason::SideEffect,
478            Self::Present(_) => SnapshotRootReason::Present,
479            Self::Output(_) => SnapshotRootReason::Output,
480            Self::Readback(_) => SnapshotRootReason::Readback,
481            Self::DebugCapture(_) => SnapshotRootReason::DebugCapture,
482            Self::PersistentState(_) => SnapshotRootReason::PersistentState,
483        }
484    }
485}
486
487/// A final resource selection. Missing facts require Legacy provenance.
488#[allow(missing_docs)]
489#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
490#[serde(rename_all = "camelCase")]
491pub struct SnapshotResourceRoot {
492    pub resource_id: String,
493    #[serde(skip_serializing_if = "Option::is_none")]
494    pub range: Option<SnapshotResourceRange>,
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub resolution: Option<SnapshotRootResolution>,
497}
498
499/// Resolved non-empty logical output range.
500#[allow(missing_docs)]
501#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
502#[serde(tag = "kind", rename_all = "camelCase")]
503pub enum SnapshotResourceRange {
504    Buffer { offset: u64, size: u64 },
505    Texture { regions: Vec<SnapshotTextureRegion> },
506}
507
508/// Compiler-provided final content sources.
509#[allow(missing_docs)]
510#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
511#[serde(rename_all = "camelCase")]
512pub struct SnapshotRootResolution {
513    pub producer_node_ids: Vec<String>,
514    pub uses_initial_contents: bool,
515}
516
517/// Portable reason that a node or resource remains observable after compilation.
518#[allow(missing_docs)]
519#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
520#[serde(rename_all = "kebab-case")]
521pub enum SnapshotRootReason {
522    Present,
523    Output,
524    Readback,
525    SideEffect,
526    DebugCapture,
527    PersistentState,
528}
529
530/// Input wire shape recognized by a successful decode.
531#[derive(Clone, Copy, Debug, PartialEq, Eq)]
532pub enum SnapshotDecodeSource {
533    /// Canonical 1.1 explicitly migrated to 1.2.
534    SnapshotV1_1,
535    /// Canonical ZenFG Snapshot 1.2.
536    V1,
537    /// Historical unversioned debug-capture shape.
538    LegacyV0,
539    /// Historical pre-release Legacy Candidate V1 format.
540    LegacyCandidateV1,
541}
542
543/// Canonical snapshot plus provenance and non-fatal migration diagnostics.
544#[derive(Clone, Debug, PartialEq)]
545pub struct SnapshotDecodeResult {
546    /// Validated canonical ZenFG Snapshot 1.2 value.
547    pub snapshot: FrameGraphSnapshotV1,
548    /// Original input format recognized by the decoder.
549    pub source: SnapshotDecodeSource,
550    /// Whether the decoder transformed a historical input.
551    pub migrated: bool,
552    /// Non-fatal warnings, including migration provenance notices.
553    pub issues: Vec<crate::SnapshotIssue>,
554}
555
556/// One ordered frame-graph or external-submission execution segment.
557#[allow(missing_docs)]
558#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
559#[serde(rename_all = "camelCase")]
560pub struct SnapshotSegment {
561    pub id: String,
562    pub order: u64,
563    pub kind: SnapshotSegmentKind,
564    pub node_ids: Vec<String>,
565}
566
567/// Ownership of command submission for one execution segment.
568#[allow(missing_docs)]
569#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
570#[serde(rename_all = "kebab-case")]
571pub enum SnapshotSegmentKind {
572    FrameGraph,
573    ExternalSubmission,
574}
575
576/// Allocation-plan and cross-frame resource-pool facts for the capture.
577#[allow(missing_docs)]
578#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
579#[serde(rename_all = "camelCase")]
580pub struct SnapshotMemory {
581    pub allocation_report: SnapshotAllocationReport,
582    pub pool_report: SnapshotPoolReport,
583}
584
585/// Available physical allocation table or an explicit unavailability reason.
586#[allow(missing_docs)]
587#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
588#[serde(tag = "status", rename_all = "kebab-case")]
589pub enum SnapshotAllocationReport {
590    Available {
591        allocations: Vec<SnapshotAllocation>,
592    },
593    Unavailable {
594        reason: String,
595    },
596}
597
598/// One physical allocation compatibility class and estimated size.
599#[allow(missing_docs)]
600#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
601#[serde(rename_all = "camelCase")]
602pub struct SnapshotAllocation {
603    pub id: String,
604    pub kind: SnapshotResourceKind,
605    pub compatibility_class_id: String,
606    #[serde(skip_serializing_if = "Option::is_none")]
607    pub estimated_byte_size: Option<u64>,
608}
609
610/// Available resource-pool counters or an explicit unavailability reason.
611#[allow(missing_docs)]
612#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
613#[serde(tag = "status", rename_all = "kebab-case")]
614pub enum SnapshotPoolReport {
615    Available {
616        #[serde(rename = "acquireCount")]
617        acquire_count: u64,
618        #[serde(rename = "reuseCount")]
619        reuse_count: u64,
620        #[serde(rename = "createdCount")]
621        created_count: u64,
622        #[serde(rename = "retainedCount")]
623        retained_count: u64,
624        #[serde(
625            rename = "estimatedRetainedBytes",
626            skip_serializing_if = "Option::is_none"
627        )]
628        estimated_retained_bytes: Option<u64>,
629    },
630    Unavailable {
631        reason: String,
632    },
633}
634
635/// Optional timing families captured alongside the graph.
636#[allow(missing_docs)]
637#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
638#[serde(rename_all = "camelCase")]
639pub struct SnapshotTimings {
640    pub cpu: SnapshotCpuTimings,
641    pub gpu: SnapshotGpuTimings,
642}
643
644/// CPU synchronous elapsed timings, independent of GPU completion.
645#[allow(missing_docs)]
646#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
647#[serde(tag = "status", rename_all = "kebab-case")]
648pub enum SnapshotCpuTimings {
649    Available {
650        #[serde(rename = "executionDurationMicros")]
651        execution_duration_micros: f64,
652        nodes: Vec<SnapshotCpuNodeTiming>,
653    },
654    Unavailable {
655        reason: String,
656    },
657}
658
659/// CPU duration in microseconds associated with one retained node of any kind.
660#[allow(missing_docs)]
661#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
662#[serde(rename_all = "camelCase")]
663pub struct SnapshotCpuNodeTiming {
664    pub node_id: String,
665    pub duration_micros: f64,
666}
667
668/// Available GPU pass timings or an explicit unavailability reason.
669#[allow(missing_docs)]
670#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
671#[serde(tag = "status", rename_all = "kebab-case")]
672pub enum SnapshotGpuTimings {
673    Available {
674        #[serde(rename = "frameSpanMicros")]
675        frame_span_micros: f64,
676        nodes: Vec<SnapshotGpuNodeTiming>,
677    },
678    Unavailable {
679        reason: String,
680    },
681}
682
683/// GPU duration, in microseconds, associated with one retained node.
684#[allow(missing_docs)]
685#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
686#[serde(rename_all = "camelCase")]
687pub struct SnapshotGpuNodeTiming {
688    pub node_id: String,
689    pub duration_micros: f64,
690}
691
692/// Structured producer diagnostic with optional graph entity references.
693#[allow(missing_docs)]
694#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
695#[serde(rename_all = "camelCase")]
696pub struct SnapshotDiagnostic {
697    pub severity: SnapshotDiagnosticSeverity,
698    pub code: String,
699    pub message: String,
700    #[serde(skip_serializing_if = "Option::is_none")]
701    pub node_id: Option<String>,
702    #[serde(skip_serializing_if = "Option::is_none")]
703    pub resource_id: Option<String>,
704}
705
706/// Portable severity of a captured producer diagnostic.
707#[allow(missing_docs)]
708#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
709#[serde(rename_all = "kebab-case")]
710pub enum SnapshotDiagnosticSeverity {
711    Info,
712    Warning,
713    Error,
714}