Skip to main content

oximedia_gpu/
sync_primitive.rs

1//! GPU synchronisation primitives (semaphores, fences, barriers).
2#![allow(dead_code)]
3
4/// Type of synchronisation primitive.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum SyncType {
7    /// Binary semaphore: signals when GPU work completes.
8    Semaphore,
9    /// CPU-visible fence: CPU can wait on GPU progress.
10    Fence,
11    /// Pipeline barrier: enforces ordering within a command buffer.
12    Barrier,
13}
14
15impl SyncType {
16    /// Returns the category of wait operation this primitive uses.
17    ///
18    /// - `Semaphore` → `"gpu_wait"` (GPU waits on GPU)
19    /// - `Fence`     → `"cpu_wait"` (CPU waits on GPU)
20    /// - `Barrier`   → `"pipeline_stall"` (in-command serialisation)
21    #[must_use]
22    pub fn wait_type(&self) -> &'static str {
23        match self {
24            Self::Semaphore => "gpu_wait",
25            Self::Fence => "cpu_wait",
26            Self::Barrier => "pipeline_stall",
27        }
28    }
29
30    /// Returns `true` for primitives the CPU can directly observe.
31    #[must_use]
32    pub fn is_cpu_visible(&self) -> bool {
33        matches!(self, Self::Fence)
34    }
35}
36
37/// State of a GPU sync object.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum SyncState {
40    /// Initial / reset state – no work has been queued.
41    Unsignaled,
42    /// Work submitted to the GPU; may or may not be complete.
43    Pending,
44    /// GPU work has completed; primitive is signaled.
45    Signaled,
46}
47
48impl SyncState {
49    /// Returns `true` if the primitive is currently signaled.
50    #[must_use]
51    pub fn is_signaled(&self) -> bool {
52        matches!(self, Self::Signaled)
53    }
54
55    /// Returns `true` if the primitive has in-flight GPU work.
56    #[must_use]
57    pub fn is_pending(&self) -> bool {
58        matches!(self, Self::Pending)
59    }
60}
61
62/// A simulated GPU synchronisation object.
63///
64/// In a real driver backend this would wrap an underlying API object
65/// (`VkSemaphore`, `VkFence`, `MTLEvent`, etc.).  Here we simulate
66/// the state machine for testing and integration purposes.
67pub struct GpuSync {
68    sync_type: SyncType,
69    state: SyncState,
70    label: String,
71    /// How many times this primitive has been signaled in its lifetime.
72    signal_count: u64,
73}
74
75impl GpuSync {
76    /// Create a new sync primitive in the `Unsignaled` state.
77    #[must_use]
78    pub fn new(sync_type: SyncType, label: impl Into<String>) -> Self {
79        Self {
80            sync_type,
81            state: SyncState::Unsignaled,
82            label: label.into(),
83            signal_count: 0,
84        }
85    }
86
87    /// Signal the primitive (simulates GPU work completing).
88    ///
89    /// Transitions `Unsignaled` → `Pending` → `Signaled`, or moves
90    /// directly from `Pending` to `Signaled`.
91    pub fn signal(&mut self) {
92        self.state = SyncState::Signaled;
93        self.signal_count += 1;
94    }
95
96    /// Mark the primitive as having pending GPU work queued.
97    pub fn enqueue(&mut self) {
98        if self.state == SyncState::Unsignaled {
99            self.state = SyncState::Pending;
100        }
101    }
102
103    /// Block (simulate) until the primitive is signaled.
104    ///
105    /// In this simulation, we simply check the current state.  Returns
106    /// `true` if the primitive is (or was already) signaled, `false`
107    /// if it is still `Unsignaled` (nothing was enqueued).
108    #[must_use]
109    pub fn wait(&self) -> bool {
110        self.state == SyncState::Signaled
111    }
112
113    /// Reset the primitive back to `Unsignaled` so it can be re-used.
114    ///
115    /// Returns `false` if the primitive is still `Pending` (cannot safely
116    /// reset GPU-side work that may be in flight).
117    pub fn reset(&mut self) -> bool {
118        if self.state == SyncState::Pending {
119            return false;
120        }
121        self.state = SyncState::Unsignaled;
122        true
123    }
124
125    /// Current state of the primitive.
126    #[must_use]
127    pub fn state(&self) -> SyncState {
128        self.state
129    }
130
131    /// Type of this primitive.
132    #[must_use]
133    pub fn sync_type(&self) -> SyncType {
134        self.sync_type
135    }
136
137    /// Human-readable label.
138    #[must_use]
139    pub fn label(&self) -> &str {
140        &self.label
141    }
142
143    /// Number of times this primitive has been signaled over its lifetime.
144    #[must_use]
145    pub fn signal_count(&self) -> u64 {
146        self.signal_count
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    // --- SyncType tests ---
155
156    #[test]
157    fn test_semaphore_wait_type() {
158        assert_eq!(SyncType::Semaphore.wait_type(), "gpu_wait");
159    }
160
161    #[test]
162    fn test_fence_wait_type() {
163        assert_eq!(SyncType::Fence.wait_type(), "cpu_wait");
164    }
165
166    #[test]
167    fn test_barrier_wait_type() {
168        assert_eq!(SyncType::Barrier.wait_type(), "pipeline_stall");
169    }
170
171    #[test]
172    fn test_fence_is_cpu_visible() {
173        assert!(SyncType::Fence.is_cpu_visible());
174    }
175
176    #[test]
177    fn test_semaphore_not_cpu_visible() {
178        assert!(!SyncType::Semaphore.is_cpu_visible());
179    }
180
181    // --- SyncState tests ---
182
183    #[test]
184    fn test_signaled_is_signaled() {
185        assert!(SyncState::Signaled.is_signaled());
186    }
187
188    #[test]
189    fn test_unsignaled_not_signaled() {
190        assert!(!SyncState::Unsignaled.is_signaled());
191    }
192
193    #[test]
194    fn test_pending_is_pending() {
195        assert!(SyncState::Pending.is_pending());
196    }
197
198    #[test]
199    fn test_signaled_not_pending() {
200        assert!(!SyncState::Signaled.is_pending());
201    }
202
203    // --- GpuSync tests ---
204
205    #[test]
206    fn test_new_sync_is_unsignaled() {
207        let s = GpuSync::new(SyncType::Fence, "f");
208        assert_eq!(s.state(), SyncState::Unsignaled);
209    }
210
211    #[test]
212    fn test_signal_transitions_to_signaled() {
213        let mut s = GpuSync::new(SyncType::Semaphore, "s");
214        s.signal();
215        assert_eq!(s.state(), SyncState::Signaled);
216    }
217
218    #[test]
219    fn test_wait_returns_true_when_signaled() {
220        let mut s = GpuSync::new(SyncType::Fence, "f");
221        s.signal();
222        assert!(s.wait());
223    }
224
225    #[test]
226    fn test_wait_returns_false_when_unsignaled() {
227        let s = GpuSync::new(SyncType::Fence, "f");
228        assert!(!s.wait());
229    }
230
231    #[test]
232    fn test_reset_from_signaled_succeeds() {
233        let mut s = GpuSync::new(SyncType::Fence, "f");
234        s.signal();
235        assert!(s.reset());
236        assert_eq!(s.state(), SyncState::Unsignaled);
237    }
238
239    #[test]
240    fn test_reset_from_pending_fails() {
241        let mut s = GpuSync::new(SyncType::Semaphore, "s");
242        s.enqueue();
243        assert!(!s.reset());
244        assert_eq!(s.state(), SyncState::Pending);
245    }
246
247    #[test]
248    fn test_enqueue_transitions_to_pending() {
249        let mut s = GpuSync::new(SyncType::Barrier, "b");
250        s.enqueue();
251        assert_eq!(s.state(), SyncState::Pending);
252    }
253
254    #[test]
255    fn test_signal_count_increments() {
256        let mut s = GpuSync::new(SyncType::Fence, "f");
257        s.signal();
258        s.reset();
259        s.signal();
260        assert_eq!(s.signal_count(), 2);
261    }
262
263    #[test]
264    fn test_label_stored() {
265        let s = GpuSync::new(SyncType::Fence, "my_fence");
266        assert_eq!(s.label(), "my_fence");
267    }
268
269    #[test]
270    fn test_sync_type_stored() {
271        let s = GpuSync::new(SyncType::Barrier, "b");
272        assert_eq!(s.sync_type(), SyncType::Barrier);
273    }
274
275    #[test]
276    fn test_full_lifecycle() {
277        let mut s = GpuSync::new(SyncType::Fence, "lifecycle");
278        assert_eq!(s.state(), SyncState::Unsignaled);
279        s.enqueue();
280        assert_eq!(s.state(), SyncState::Pending);
281        s.signal();
282        assert!(s.wait());
283        assert!(s.reset());
284        assert_eq!(s.state(), SyncState::Unsignaled);
285    }
286}