Skip to main content

molgfx_gpu/residency/
command.rs

1//! Fixed-capacity reusable command storage.
2
3use std::fmt;
4use thiserror::Error;
5
6/// Command scratch counters.
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
8pub struct CommandScratchMetrics {
9    /// Commands accepted over the lifetime of the scratch storage.
10    pub commands_recorded: u64,
11    /// Largest command count in one recording epoch.
12    pub peak_commands: usize,
13    /// Commands rejected because the fixed capacity was exhausted.
14    pub capacity_stalls: u64,
15    /// Host storage allocations performed by this primitive.
16    pub host_allocation_events: u64,
17}
18
19/// A command did not fit in fixed scratch capacity.
20#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
21#[error("command scratch capacity {capacity} is exhausted")]
22pub struct ScratchFull {
23    /// Configured command capacity.
24    pub capacity: usize,
25}
26
27/// Reusable storage for trivially owned command descriptors.
28///
29/// The caller clears the logical length between recording epochs. Capacity
30/// never grows, so `push` is allocation-free after construction.
31pub struct CommandScratch<C: Copy> {
32    commands: Vec<C>,
33    limit: usize,
34    metrics: CommandScratchMetrics,
35}
36
37impl<C: Copy> CommandScratch<C> {
38    /// Allocates storage for at most `capacity` commands.
39    #[must_use]
40    pub fn new(capacity: usize) -> Self {
41        Self {
42            commands: Vec::with_capacity(capacity),
43            limit: capacity,
44            metrics: CommandScratchMetrics {
45                host_allocation_events: u64::from(capacity > 0),
46                ..CommandScratchMetrics::default()
47            },
48        }
49    }
50
51    /// Starts a new recording epoch while retaining allocated storage.
52    pub fn clear(&mut self) {
53        self.commands.clear();
54    }
55
56    /// Appends one command without growing storage.
57    ///
58    /// # Errors
59    ///
60    /// Returns [`ScratchFull`] when fixed capacity is exhausted.
61    pub fn push(&mut self, command: C) -> Result<(), ScratchFull> {
62        if self.commands.len() == self.limit {
63            self.metrics.capacity_stalls = self.metrics.capacity_stalls.saturating_add(1);
64            return Err(ScratchFull {
65                capacity: self.limit,
66            });
67        }
68        self.commands.push(command);
69        self.metrics.commands_recorded = self.metrics.commands_recorded.saturating_add(1);
70        self.metrics.peak_commands = self.metrics.peak_commands.max(self.commands.len());
71        Ok(())
72    }
73
74    /// Commands recorded in the current epoch.
75    #[must_use]
76    pub fn as_slice(&self) -> &[C] {
77        &self.commands
78    }
79
80    /// Mutable command storage for in-place lowering or sorting.
81    #[must_use]
82    pub fn as_mut_slice(&mut self) -> &mut [C] {
83        &mut self.commands
84    }
85
86    /// Fixed command capacity.
87    #[must_use]
88    pub const fn capacity(&self) -> usize {
89        self.limit
90    }
91
92    /// Current cumulative counters.
93    #[must_use]
94    pub const fn metrics(&self) -> CommandScratchMetrics {
95        self.metrics
96    }
97}
98
99impl<C: Copy + fmt::Debug> fmt::Debug for CommandScratch<C> {
100    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
101        formatter
102            .debug_struct("CommandScratch")
103            .field("commands", &self.commands)
104            .field("limit", &self.limit)
105            .field("metrics", &self.metrics)
106            .finish()
107    }
108}
109
110#[cfg(test)]
111#[path = "command_tests.rs"]
112mod tests;