Skip to main content

emblema_hal/
sync.rs

1//! GPU completion, and handing it to something outside this process.
2
3use crate::error::Result;
4use std::time::Duration;
5
6/// A signal that GPU work has completed.
7///
8/// This is the second of the two requirements that exist in the HAL from day
9/// one for the sake of the DRM path. Explicit sync is the design center of the
10/// frame loop: on the DRM path the render-done primitive is exported as a
11/// `sync_file` fd and attached to the atomic commit as `IN_FENCE_FD`, so the
12/// kernel latches the flip when the fence signals. Neither `glFinish` nor
13/// `vkDeviceWaitIdle` belongs in the frame loop on any path.
14///
15/// Backing primitives differ per backend — Vulkan fences, `GLsync` objects,
16/// sync_file fds — and the fence waiter thread retires all of them uniformly.
17pub trait HalFence: Send + Sync + 'static {
18    /// Whether the fence has signaled, without blocking.
19    fn is_signaled(&self) -> Result<bool>;
20
21    /// Block until the fence signals or the timeout elapses.
22    ///
23    /// Returns `Ok(true)` on signal and `Ok(false)` on timeout, so an expected
24    /// timeout is not an error. A caller that treats timeout as fatal checks
25    /// the returned value.
26    fn wait(&self, timeout: Duration) -> Result<bool>;
27
28    /// Export as a `sync_file` fd for a consumer outside this process.
29    ///
30    /// Returns [`crate::Error::Unsupported`] where the driver cannot export.
31    /// Callers check [`crate::SyncSupport::export_sync_file`] first and fall
32    /// back to a CPU-side wait before commit — correct, slower, and worth
33    /// logging loudly, since on Tier-1 hardware it is a driver bug to chase
34    /// rather than a state to settle into.
35    #[cfg(unix)]
36    fn export_sync_file(&self) -> Result<std::os::fd::OwnedFd> {
37        Err(crate::Error::Unsupported("sync_file export"))
38    }
39}
40
41/// A fence for a backend that cannot submit without waiting.
42///
43/// Uninhabited, so every method is unreachable: no value can exist to call one
44/// on. That is a stronger statement than a type whose methods return errors,
45/// which would be constructible and could reach a caller expecting a real
46/// fence.
47impl HalFence for std::convert::Infallible {
48    fn is_signaled(&self) -> Result<bool> {
49        match *self {}
50    }
51
52    fn wait(&self, _timeout: Duration) -> Result<bool> {
53        match *self {}
54    }
55}
56
57/// How long a frame-loop wait should tolerate before it is considered a hang.
58///
59/// Generous relative to any real frame: a wait reaching this has hit a lost
60/// device or a fence that will never signal, not a slow frame.
61pub const FRAME_WAIT_TIMEOUT: Duration = Duration::from_secs(5);
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::Error;
67    use std::sync::atomic::{AtomicBool, Ordering};
68
69    struct TestFence {
70        signaled: AtomicBool,
71    }
72
73    impl HalFence for TestFence {
74        fn is_signaled(&self) -> Result<bool> {
75            Ok(self.signaled.load(Ordering::Acquire))
76        }
77
78        fn wait(&self, _timeout: Duration) -> Result<bool> {
79            self.is_signaled()
80        }
81    }
82
83    #[test]
84    fn fence_export_defaults_to_unsupported_rather_than_panicking() {
85        let fence = TestFence {
86            signaled: AtomicBool::new(false),
87        };
88        // A backend that has not implemented export must degrade to the
89        // CPU-wait fallback, so the default has to be a recoverable error.
90        #[cfg(unix)]
91        assert!(matches!(
92            fence.export_sync_file(),
93            Err(Error::Unsupported(_))
94        ));
95        assert!(!fence.is_signaled().unwrap());
96    }
97
98    #[test]
99    fn timeout_is_reported_as_a_value_not_an_error() {
100        let fence = TestFence {
101            signaled: AtomicBool::new(false),
102        };
103        // Waiting out a frame slot that is still in flight is ordinary, so it
104        // must not surface as Err.
105        assert!(!fence.wait(Duration::from_millis(0)).unwrap());
106        fence.signaled.store(true, Ordering::Release);
107        assert!(fence.wait(Duration::from_millis(0)).unwrap());
108    }
109}