oximedia_gpu/
gpu_fence.rs1#![allow(dead_code)]
7
8use std::time::{Duration, Instant};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum FenceStatus {
13 Pending,
15 Signalled,
17 Reset,
19 TimedOut,
21}
22
23impl FenceStatus {
24 #[must_use]
26 pub fn is_complete(&self) -> bool {
27 matches!(self, Self::Signalled)
28 }
29
30 #[must_use]
32 pub fn is_reusable(&self) -> bool {
33 matches!(self, Self::Reset | Self::TimedOut)
34 }
35}
36
37#[derive(Debug, Clone)]
42pub struct GpuFence {
43 pub id: u64,
45 pub status: FenceStatus,
47 pub label: Option<String>,
49 signal_time: Option<Instant>,
51 simulated_latency: Duration,
53}
54
55impl GpuFence {
56 #[must_use]
58 pub fn new(id: u64) -> Self {
59 Self {
60 id,
61 status: FenceStatus::Pending,
62 label: None,
63 signal_time: None,
64 simulated_latency: Duration::from_millis(1),
65 }
66 }
67
68 #[must_use]
70 pub fn with_label(mut self, label: impl Into<String>) -> Self {
71 self.label = Some(label.into());
72 self
73 }
74
75 pub fn signal(&mut self) {
80 self.signal_time = Some(Instant::now());
81 self.status = FenceStatus::Signalled;
82 }
83
84 #[must_use]
86 pub fn is_signalled(&self) -> bool {
87 self.status.is_complete()
88 }
89
90 pub fn reset(&mut self) {
92 self.status = FenceStatus::Reset;
93 self.signal_time = None;
94 }
95
96 #[allow(clippy::cast_precision_loss)]
102 pub fn wait_timeout_ms(&mut self, timeout_ms: u64) -> bool {
103 match self.status {
104 FenceStatus::Signalled => true,
105 FenceStatus::Pending => {
106 if let Some(t) = self.signal_time {
107 let elapsed = t.elapsed();
108 if elapsed >= self.simulated_latency {
109 self.status = FenceStatus::Signalled;
110 return true;
111 }
112 }
113 let _ = timeout_ms; self.status = FenceStatus::TimedOut;
116 false
117 }
118 FenceStatus::Reset | FenceStatus::TimedOut => false,
119 }
120 }
121
122 #[must_use]
124 pub fn simulated_latency(&self) -> Duration {
125 self.simulated_latency
126 }
127}
128
129#[derive(Debug, Default)]
131pub struct GpuFencePool {
132 next_id: u64,
133 active: Vec<GpuFence>,
134 free_list: Vec<GpuFence>,
135}
136
137impl GpuFencePool {
138 #[must_use]
140 pub fn new() -> Self {
141 Self::default()
142 }
143
144 pub fn create_fence(&mut self) -> GpuFence {
146 if let Some(mut f) = self.free_list.pop() {
147 f.reset();
148 f.status = FenceStatus::Pending;
149 self.active.push(f.clone());
150 return f;
151 }
152 let id = self.next_id;
153 self.next_id += 1;
154 let fence = GpuFence::new(id);
155 self.active.push(fence.clone());
156 fence
157 }
158
159 pub fn return_fence(&mut self, fence: GpuFence) {
161 self.active.retain(|f| f.id != fence.id);
162 self.free_list.push(fence);
163 }
164
165 #[must_use]
167 pub fn active_count(&self) -> usize {
168 self.active.len()
169 }
170
171 #[must_use]
173 pub fn completed_count(&self) -> usize {
174 self.active.iter().filter(|f| f.is_signalled()).count()
175 }
176
177 #[must_use]
179 pub fn total_created(&self) -> u64 {
180 self.next_id
181 }
182
183 #[must_use]
185 pub fn active_fences(&self) -> &[GpuFence] {
186 &self.active
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_fence_status_is_complete_signalled() {
196 assert!(FenceStatus::Signalled.is_complete());
197 }
198
199 #[test]
200 fn test_fence_status_is_complete_pending_false() {
201 assert!(!FenceStatus::Pending.is_complete());
202 }
203
204 #[test]
205 fn test_fence_status_is_reusable_reset() {
206 assert!(FenceStatus::Reset.is_reusable());
207 }
208
209 #[test]
210 fn test_fence_status_is_reusable_signalled_false() {
211 assert!(!FenceStatus::Signalled.is_reusable());
212 }
213
214 #[test]
215 fn test_gpu_fence_new_pending() {
216 let f = GpuFence::new(0);
217 assert_eq!(f.status, FenceStatus::Pending);
218 assert!(!f.is_signalled());
219 }
220
221 #[test]
222 fn test_gpu_fence_signal_sets_status() {
223 let mut f = GpuFence::new(1);
224 f.signal();
225 assert!(f.is_signalled());
226 assert_eq!(f.status, FenceStatus::Signalled);
227 }
228
229 #[test]
230 fn test_gpu_fence_reset_clears_signal() {
231 let mut f = GpuFence::new(2);
232 f.signal();
233 f.reset();
234 assert_eq!(f.status, FenceStatus::Reset);
235 assert!(f.signal_time.is_none());
236 }
237
238 #[test]
239 fn test_gpu_fence_wait_when_signalled_returns_true() {
240 let mut f = GpuFence::new(3);
241 f.signal();
242 assert!(f.wait_timeout_ms(100));
243 }
244
245 #[test]
246 fn test_gpu_fence_wait_timeout_sets_timed_out() {
247 let mut f = GpuFence::new(4);
248 let result = f.wait_timeout_ms(0);
250 assert!(!result);
251 assert_eq!(f.status, FenceStatus::TimedOut);
252 }
253
254 #[test]
255 fn test_gpu_fence_with_label() {
256 let f = GpuFence::new(5).with_label("frame_complete");
257 assert_eq!(f.label.as_deref(), Some("frame_complete"));
258 }
259
260 #[test]
261 fn test_pool_create_fence_pending() {
262 let mut pool = GpuFencePool::new();
263 let f = pool.create_fence();
264 assert_eq!(f.status, FenceStatus::Pending);
265 }
266
267 #[test]
268 fn test_pool_active_count_increments() {
269 let mut pool = GpuFencePool::new();
270 pool.create_fence();
271 pool.create_fence();
272 assert_eq!(pool.active_count(), 2);
273 }
274
275 #[test]
276 fn test_pool_completed_count_after_signal() {
277 let mut pool = GpuFencePool::new();
278 let mut f = pool.create_fence();
279 f.signal();
280 pool.active
284 .iter_mut()
285 .find(|x| x.id == f.id)
286 .expect("operation should succeed in test")
287 .signal();
288 assert_eq!(pool.completed_count(), 1);
289 }
290
291 #[test]
292 fn test_pool_return_fence_moves_to_free_list() {
293 let mut pool = GpuFencePool::new();
294 let f = pool.create_fence();
295 pool.return_fence(f);
296 assert_eq!(pool.active_count(), 0);
297 }
298
299 #[test]
300 fn test_pool_total_created_monotonic() {
301 let mut pool = GpuFencePool::new();
302 pool.create_fence();
303 pool.create_fence();
304 assert_eq!(pool.total_created(), 2);
305 }
306}