Skip to main content

alpha2_native_gpu/
surface_budget.rs

1//! Count-and-byte budget for decoder surfaces.
2
3use std::error::Error;
4use std::fmt;
5use std::sync::{Arc, Mutex};
6
7/// Current native surface allocation.
8#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
9pub struct SurfaceUsage {
10    /// Number of retained decoder surfaces.
11    pub surfaces: u32,
12    /// Approximate native allocation bytes.
13    pub bytes: u64,
14}
15
16#[derive(Debug)]
17struct State {
18    used: SurfaceUsage,
19}
20
21/// Bounded decoder-surface allocator shared by decode sessions.
22#[derive(Clone, Debug)]
23pub struct SurfaceBudget {
24    max: SurfaceUsage,
25    state: Arc<Mutex<State>>,
26}
27
28/// A reservation released exactly once when its final owner is dropped.
29#[derive(Debug)]
30pub struct SurfaceReservation {
31    usage: SurfaceUsage,
32    state: Arc<Mutex<State>>,
33}
34
35/// A reservation exceeded either the count or byte limit.
36#[derive(Copy, Clone, Debug, Eq, PartialEq)]
37pub struct SurfaceBudgetError;
38
39impl SurfaceBudget {
40    /// Creates a budget with independent count and byte ceilings.
41    #[must_use]
42    pub fn new(max_surfaces: u32, max_bytes: u64) -> Self {
43        Self {
44            max: SurfaceUsage {
45                surfaces: max_surfaces,
46                bytes: max_bytes,
47            },
48            state: Arc::new(Mutex::new(State {
49                used: SurfaceUsage::default(),
50            })),
51        }
52    }
53
54    /// Reserves one surface before the decoder is asked to acquire it.
55    ///
56    /// # Errors
57    /// Returns [`SurfaceBudgetError`] if either configured ceiling would be exceeded.
58    pub fn try_reserve(&self, bytes: u64) -> Result<SurfaceReservation, SurfaceBudgetError> {
59        let mut state = self
60            .state
61            .lock()
62            .unwrap_or_else(std::sync::PoisonError::into_inner);
63        let next = SurfaceUsage {
64            surfaces: state.used.surfaces.saturating_add(1),
65            bytes: state.used.bytes.saturating_add(bytes),
66        };
67        if next.surfaces > self.max.surfaces || next.bytes > self.max.bytes {
68            return Err(SurfaceBudgetError);
69        }
70        state.used = next;
71        Ok(SurfaceReservation {
72            usage: SurfaceUsage { surfaces: 1, bytes },
73            state: Arc::clone(&self.state),
74        })
75    }
76
77    /// Returns the currently retained allocation.
78    #[must_use]
79    pub fn usage(&self) -> SurfaceUsage {
80        self.state
81            .lock()
82            .unwrap_or_else(std::sync::PoisonError::into_inner)
83            .used
84    }
85}
86
87impl Drop for SurfaceReservation {
88    fn drop(&mut self) {
89        let mut state = self
90            .state
91            .lock()
92            .unwrap_or_else(std::sync::PoisonError::into_inner);
93        state.used.surfaces = state.used.surfaces.saturating_sub(self.usage.surfaces);
94        state.used.bytes = state.used.bytes.saturating_sub(self.usage.bytes);
95    }
96}
97
98impl fmt::Display for SurfaceBudgetError {
99    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
100        formatter.write_str("native decoder surface budget exhausted")
101    }
102}
103impl Error for SurfaceBudgetError {}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn reservations_enforce_both_limits_and_release_exactly_once() {
111        let budget = SurfaceBudget::new(2, 30);
112        let first = budget.try_reserve(10).expect("first");
113        let second = budget.try_reserve(20).expect("second");
114        assert_eq!(
115            budget.usage(),
116            SurfaceUsage {
117                surfaces: 2,
118                bytes: 30
119            }
120        );
121        assert_eq!(budget.try_reserve(0).unwrap_err(), SurfaceBudgetError);
122        drop(first);
123        assert_eq!(
124            budget.usage(),
125            SurfaceUsage {
126                surfaces: 1,
127                bytes: 20
128            }
129        );
130        drop(second);
131        assert_eq!(budget.usage(), SurfaceUsage::default());
132    }
133
134    /// The byte ceiling refuses on its own, with surfaces to spare: a 4K
135    /// P010 surface is about 25 MB, so 512 MiB fills before 32 surfaces do.
136    /// The reservation that reaches the ceiling exactly fits.
137    #[test]
138    fn the_byte_ceiling_refuses_a_reservation_the_count_would_allow() {
139        let budget = SurfaceBudget::new(10, 30);
140        let held = budget.try_reserve(20).expect("within both ceilings");
141        assert_eq!(budget.try_reserve(11).unwrap_err(), SurfaceBudgetError);
142        assert_eq!(
143            budget.usage(),
144            SurfaceUsage {
145                surfaces: 1,
146                bytes: 20
147            },
148            "a refused reservation takes nothing"
149        );
150        let exact = budget
151            .try_reserve(10)
152            .expect("reaching the ceiling exactly");
153        assert_eq!(budget.try_reserve(1).unwrap_err(), SurfaceBudgetError);
154        drop((held, exact));
155
156        // One surface larger than the whole ceiling, into an empty budget.
157        assert_eq!(budget.try_reserve(31).unwrap_err(), SurfaceBudgetError);
158        assert_eq!(budget.usage(), SurfaceUsage::default());
159        drop(budget.try_reserve(30).expect("the whole ceiling at once"));
160    }
161}