alpha2_native_gpu/
surface_budget.rs1use std::error::Error;
4use std::fmt;
5use std::sync::{Arc, Mutex};
6
7#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
9pub struct SurfaceUsage {
10 pub surfaces: u32,
12 pub bytes: u64,
14}
15
16#[derive(Debug)]
17struct State {
18 used: SurfaceUsage,
19}
20
21#[derive(Clone, Debug)]
23pub struct SurfaceBudget {
24 max: SurfaceUsage,
25 state: Arc<Mutex<State>>,
26}
27
28#[derive(Debug)]
30pub struct SurfaceReservation {
31 usage: SurfaceUsage,
32 state: Arc<Mutex<State>>,
33}
34
35#[derive(Copy, Clone, Debug, Eq, PartialEq)]
37pub struct SurfaceBudgetError;
38
39impl SurfaceBudget {
40 #[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 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 #[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 #[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 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}