⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/graphics/
vsync_sw.rs

1//! Software VSync Timer
2//!
3//! Provides a software-based VSync timer using TSC (Time Stamp Counter)
4//! for frame pacing at ~60Hz (16.667ms per frame). Coordinates double-buffer
5//! swap timing for smooth presentation without hardware VSync support.
6//!
7//! All timing uses integer nanoseconds (no FPU required).
8
9#![allow(dead_code)]
10
11use alloc::vec::Vec;
12#[cfg(not(target_arch = "x86_64"))]
13use core::sync::atomic::{AtomicU64, Ordering};
14
15use spin::Mutex;
16
17// ---------------------------------------------------------------------------
18// Constants
19// ---------------------------------------------------------------------------
20
21/// VSync interval in nanoseconds for ~60Hz (1_000_000_000 / 60 =
22/// 16_666_666.67). Rounded to nearest integer.
23pub(crate) const VSYNC_INTERVAL_NS: u64 = 16_666_667;
24
25/// VSync interval in microseconds (for coarser timing).
26const VSYNC_INTERVAL_US: u64 = 16_667;
27
28/// Number of frame times to keep for rolling average.
29const FRAME_HISTORY_SIZE: usize = 60;
30
31/// If a frame takes longer than 2x the VSync interval, it counts as dropped.
32const DROPPED_FRAME_THRESHOLD_NS: u64 = VSYNC_INTERVAL_NS * 2;
33
34// ---------------------------------------------------------------------------
35// SwapState
36// ---------------------------------------------------------------------------
37
38/// Double-buffer swap state machine.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub(crate) enum SwapState {
41    /// No swap in progress; ready to begin rendering.
42    Idle,
43    /// Back buffer is being rendered to.
44    Rendering,
45    /// Rendering complete, waiting for VSync tick to flip.
46    WaitingFlip,
47    /// Buffers have been swapped; new frame is on-screen.
48    Flipped,
49}
50
51// ---------------------------------------------------------------------------
52// FrameStats
53// ---------------------------------------------------------------------------
54
55/// Accumulated frame timing statistics (integer nanoseconds).
56#[derive(Debug, Clone)]
57pub(crate) struct FrameStats {
58    /// Total number of frames presented.
59    pub frame_count: u64,
60    /// Number of dropped frames (took > 2x VSync interval).
61    pub dropped_frames: u64,
62    /// Rolling frame time history (nanoseconds).
63    frame_times: Vec<u64>,
64    /// Index into circular frame_times buffer.
65    write_idx: usize,
66    /// Minimum frame time observed (ns).
67    pub min_frame_time_ns: u64,
68    /// Maximum frame time observed (ns).
69    pub max_frame_time_ns: u64,
70}
71
72impl FrameStats {
73    fn new() -> Self {
74        Self {
75            frame_count: 0,
76            dropped_frames: 0,
77            frame_times: Vec::new(),
78            write_idx: 0,
79            min_frame_time_ns: u64::MAX,
80            max_frame_time_ns: 0,
81        }
82    }
83
84    /// Record a frame's duration.
85    fn record(&mut self, frame_time_ns: u64) {
86        self.frame_count += 1;
87
88        if frame_time_ns > DROPPED_FRAME_THRESHOLD_NS {
89            self.dropped_frames += 1;
90        }
91
92        if frame_time_ns < self.min_frame_time_ns {
93            self.min_frame_time_ns = frame_time_ns;
94        }
95        if frame_time_ns > self.max_frame_time_ns {
96            self.max_frame_time_ns = frame_time_ns;
97        }
98
99        // Circular buffer
100        if self.frame_times.len() < FRAME_HISTORY_SIZE {
101            self.frame_times.push(frame_time_ns);
102        } else {
103            self.frame_times[self.write_idx] = frame_time_ns;
104        }
105        self.write_idx = (self.write_idx + 1) % FRAME_HISTORY_SIZE;
106    }
107
108    /// Average frame time in nanoseconds.
109    pub(crate) fn average_frame_time_ns(&self) -> u64 {
110        if self.frame_times.is_empty() {
111            return 0;
112        }
113        let sum: u64 = self.frame_times.iter().sum();
114        sum / self.frame_times.len() as u64
115    }
116
117    /// Estimated FPS (integer, based on average frame time).
118    pub(crate) fn estimated_fps(&self) -> u32 {
119        let avg = self.average_frame_time_ns();
120        if avg == 0 {
121            return 0;
122        }
123        // fps = 1_000_000_000 / avg
124        (1_000_000_000u64 / avg) as u32
125    }
126
127    /// Drop rate as parts-per-thousand (0 = no drops, 1000 = all dropped).
128    pub(crate) fn drop_rate_permille(&self) -> u32 {
129        if self.frame_count == 0 {
130            return 0;
131        }
132        ((self.dropped_frames * 1000) / self.frame_count) as u32
133    }
134}
135
136// ---------------------------------------------------------------------------
137// SwVsyncState
138// ---------------------------------------------------------------------------
139
140/// Software VSync state tracking frame timing and buffer swaps.
141pub(crate) struct SwVsyncState {
142    /// Monotonic TSC-based timestamp of last VSync tick (nanoseconds).
143    last_vsync_ns: u64,
144    /// Target interval between frames (nanoseconds).
145    interval_ns: u64,
146    /// Current swap state.
147    swap_state: SwapState,
148    /// Frame statistics.
149    stats: FrameStats,
150    /// TSC ticks per nanosecond (calibrated at init), stored as
151    /// fixed-point 32.32 to avoid FP. 0 means uncalibrated (use
152    /// fallback).
153    tsc_per_ns_fp32: u64,
154    /// Whether VSync is enabled.
155    enabled: bool,
156    /// Frame start timestamp (ns) for measuring frame duration.
157    frame_start_ns: u64,
158}
159
160impl SwVsyncState {
161    /// Create a new VSync state (uncalibrated).
162    pub(crate) fn new() -> Self {
163        Self {
164            last_vsync_ns: 0,
165            interval_ns: VSYNC_INTERVAL_NS,
166            swap_state: SwapState::Idle,
167            stats: FrameStats::new(),
168            tsc_per_ns_fp32: 0,
169            enabled: true,
170            frame_start_ns: 0,
171        }
172    }
173
174    /// Create with a custom refresh interval.
175    pub(crate) fn with_interval_ns(interval_ns: u64) -> Self {
176        let mut s = Self::new();
177        s.interval_ns = interval_ns;
178        s
179    }
180
181    /// Set TSC calibration (ticks per nanosecond as 32.32 fixed-point).
182    pub(crate) fn set_tsc_calibration(&mut self, tsc_per_ns_fp32: u64) {
183        self.tsc_per_ns_fp32 = tsc_per_ns_fp32;
184    }
185
186    /// Convert a TSC reading to nanoseconds using calibration.
187    fn tsc_to_ns(&self, tsc: u64) -> u64 {
188        if self.tsc_per_ns_fp32 == 0 {
189            // Fallback: assume ~3 GHz (1 tick = ~0.33 ns)
190            // ns = tsc / 3
191            tsc / 3
192        } else {
193            // ns = tsc / (tsc_per_ns_fp32 >> 32) approximately
194            // More precisely: ns = (tsc << 32) / tsc_per_ns_fp32
195            // but that overflows for large tsc values. Use u128.
196            let numerator = (tsc as u128) << 32;
197            (numerator / self.tsc_per_ns_fp32 as u128) as u64
198        }
199    }
200
201    /// Read the current TSC (x86_64 RDTSC).
202    #[inline]
203    fn read_tsc(&self) -> u64 {
204        #[cfg(target_arch = "x86_64")]
205        {
206            // SAFETY: RDTSC is always available on x86_64.
207            unsafe { core::arch::x86_64::_rdtsc() }
208        }
209        #[cfg(not(target_arch = "x86_64"))]
210        {
211            // Fallback: use a simple counter (not accurate but compiles).
212            static COUNTER: AtomicU64 = AtomicU64::new(0);
213            COUNTER.fetch_add(1, Ordering::Relaxed)
214        }
215    }
216
217    /// Get current monotonic time in nanoseconds.
218    fn now_ns(&self) -> u64 {
219        self.tsc_to_ns(self.read_tsc())
220    }
221
222    // -- VSync control -------------------------------------------------------
223
224    /// Enable or disable VSync pacing.
225    pub(crate) fn set_enabled(&mut self, enabled: bool) {
226        self.enabled = enabled;
227    }
228
229    /// Whether VSync is enabled.
230    pub(crate) fn is_enabled(&self) -> bool {
231        self.enabled
232    }
233
234    /// Set the target refresh rate in mHz (e.g. 60000 for 60 Hz).
235    pub(crate) fn set_refresh_mhz(&mut self, mhz: u32) {
236        if mhz > 0 {
237            // interval_ns = 1_000_000_000_000 / mhz (since mhz is milliHz)
238            self.interval_ns = 1_000_000_000_000u64 / mhz as u64;
239        }
240    }
241
242    // -- Frame timing --------------------------------------------------------
243
244    /// Wait until the next VSync tick. Busy-waits using TSC.
245    ///
246    /// Call this after rendering is complete and before flipping buffers.
247    pub(crate) fn wait(&mut self) {
248        if !self.enabled {
249            return;
250        }
251
252        let now = self.now_ns();
253
254        if self.last_vsync_ns == 0 {
255            // First frame: no wait needed, just record the time.
256            self.last_vsync_ns = now;
257            return;
258        }
259
260        // Calculate next VSync deadline
261        let mut next_vsync = self.last_vsync_ns.saturating_add(self.interval_ns);
262
263        // If we already missed the deadline, skip to the next one
264        if now > next_vsync {
265            let elapsed = now - self.last_vsync_ns;
266            let skipped = elapsed / self.interval_ns;
267            next_vsync = self
268                .last_vsync_ns
269                .saturating_add(self.interval_ns.saturating_mul(skipped + 1));
270        }
271
272        // Busy-wait until deadline
273        while self.now_ns() < next_vsync {
274            core::hint::spin_loop();
275        }
276
277        self.last_vsync_ns = next_vsync;
278    }
279
280    /// Signal that a frame has started rendering.
281    pub(crate) fn begin_frame(&mut self) {
282        self.frame_start_ns = self.now_ns();
283        self.swap_state = SwapState::Rendering;
284    }
285
286    /// Signal that rendering is complete (ready to flip).
287    pub(crate) fn end_render(&mut self) {
288        self.swap_state = SwapState::WaitingFlip;
289    }
290
291    /// Signal that the frame has been presented.
292    pub(crate) fn signal_complete(&mut self) {
293        let now = self.now_ns();
294        if self.frame_start_ns > 0 {
295            let frame_time = now.saturating_sub(self.frame_start_ns);
296            self.stats.record(frame_time);
297        }
298        self.swap_state = SwapState::Flipped;
299    }
300
301    // -- Swap coordination ---------------------------------------------------
302
303    /// Request a buffer swap. Returns true if the swap can proceed.
304    pub(crate) fn request_swap(&mut self) -> bool {
305        match self.swap_state {
306            SwapState::WaitingFlip => {
307                if self.enabled {
308                    self.wait();
309                }
310                self.swap_state = SwapState::Flipped;
311                true
312            }
313            SwapState::Idle => {
314                // No active render, immediate swap OK
315                self.swap_state = SwapState::Flipped;
316                true
317            }
318            _ => false,
319        }
320    }
321
322    /// Mark swap as complete; return to idle for next frame.
323    pub(crate) fn swap_complete(&mut self) {
324        self.signal_complete();
325        self.swap_state = SwapState::Idle;
326    }
327
328    // -- Queries -------------------------------------------------------------
329
330    /// Current swap state.
331    pub(crate) fn swap_state(&self) -> SwapState {
332        self.swap_state
333    }
334
335    /// Frame statistics.
336    pub(crate) fn stats(&self) -> &FrameStats {
337        &self.stats
338    }
339
340    /// Target VSync interval in nanoseconds.
341    pub(crate) fn interval_ns(&self) -> u64 {
342        self.interval_ns
343    }
344}
345
346// ---------------------------------------------------------------------------
347// Global instance
348// ---------------------------------------------------------------------------
349
350static SW_VSYNC: Mutex<Option<SwVsyncState>> = Mutex::new(None);
351
352/// Initialize the software VSync timer.
353pub(crate) fn sw_vsync_init() {
354    let mut guard = SW_VSYNC.lock();
355    if guard.is_none() {
356        *guard = Some(SwVsyncState::new());
357    }
358}
359
360/// Initialize with a specific refresh rate (mHz).
361pub(crate) fn sw_vsync_init_with_refresh(refresh_mhz: u32) {
362    let mut guard = SW_VSYNC.lock();
363    let mut state = SwVsyncState::new();
364    state.set_refresh_mhz(refresh_mhz);
365    *guard = Some(state);
366}
367
368/// Wait for next VSync tick.
369pub(crate) fn sw_vsync_wait() {
370    if let Some(ref mut state) = *SW_VSYNC.lock() {
371        state.wait();
372    }
373}
374
375/// Signal frame complete.
376pub(crate) fn sw_vsync_signal() {
377    if let Some(ref mut state) = *SW_VSYNC.lock() {
378        state.signal_complete();
379    }
380}
381
382/// Access the software VSync state.
383pub(crate) fn with_sw_vsync<R, F: FnOnce(&mut SwVsyncState) -> R>(f: F) -> Option<R> {
384    SW_VSYNC.lock().as_mut().map(f)
385}
386
387// ===========================================================================
388// Tests
389// ===========================================================================
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    #[test]
396    fn test_vsync_interval_ns() {
397        // ~60Hz
398        assert_eq!(VSYNC_INTERVAL_NS, 16_666_667);
399    }
400
401    #[test]
402    fn test_swap_state_transitions() {
403        let mut state = SwVsyncState::new();
404        state.set_enabled(false); // disable wait for test speed
405
406        assert_eq!(state.swap_state(), SwapState::Idle);
407
408        state.begin_frame();
409        assert_eq!(state.swap_state(), SwapState::Rendering);
410
411        state.end_render();
412        assert_eq!(state.swap_state(), SwapState::WaitingFlip);
413
414        assert!(state.request_swap());
415        assert_eq!(state.swap_state(), SwapState::Flipped);
416
417        state.swap_complete();
418        assert_eq!(state.swap_state(), SwapState::Idle);
419    }
420
421    #[test]
422    fn test_swap_request_from_idle() {
423        let mut state = SwVsyncState::new();
424        state.set_enabled(false);
425        assert!(state.request_swap());
426    }
427
428    #[test]
429    fn test_swap_request_during_render() {
430        let mut state = SwVsyncState::new();
431        state.begin_frame();
432        // Cannot swap while rendering
433        assert!(!state.request_swap());
434    }
435
436    #[test]
437    fn test_frame_stats_new() {
438        let stats = FrameStats::new();
439        assert_eq!(stats.frame_count, 0);
440        assert_eq!(stats.dropped_frames, 0);
441        assert_eq!(stats.average_frame_time_ns(), 0);
442        assert_eq!(stats.estimated_fps(), 0);
443    }
444
445    #[test]
446    fn test_frame_stats_record() {
447        let mut stats = FrameStats::new();
448        stats.record(16_666_667); // ~60fps frame
449        assert_eq!(stats.frame_count, 1);
450        assert_eq!(stats.dropped_frames, 0);
451        assert_eq!(stats.min_frame_time_ns, 16_666_667);
452        assert_eq!(stats.max_frame_time_ns, 16_666_667);
453    }
454
455    #[test]
456    fn test_frame_stats_dropped() {
457        let mut stats = FrameStats::new();
458        // A frame that takes 40ms (> 2 * 16.67ms)
459        stats.record(40_000_000);
460        assert_eq!(stats.dropped_frames, 1);
461    }
462
463    #[test]
464    fn test_frame_stats_average() {
465        let mut stats = FrameStats::new();
466        stats.record(10_000_000);
467        stats.record(20_000_000);
468        assert_eq!(stats.average_frame_time_ns(), 15_000_000);
469    }
470
471    #[test]
472    fn test_frame_stats_fps() {
473        let mut stats = FrameStats::new();
474        // Record 60 frames at exactly 16.666ms
475        for _ in 0..60 {
476            stats.record(16_666_667);
477        }
478        let fps = stats.estimated_fps();
479        // Should be ~60
480        assert!(fps >= 59 && fps <= 60, "fps was {}", fps);
481    }
482
483    #[test]
484    fn test_frame_stats_drop_rate() {
485        let mut stats = FrameStats::new();
486        for _ in 0..9 {
487            stats.record(16_000_000); // normal
488        }
489        stats.record(40_000_000); // dropped
490                                  // 1/10 dropped = 100 permille
491        assert_eq!(stats.drop_rate_permille(), 100);
492    }
493
494    #[test]
495    fn test_set_refresh_60hz() {
496        let mut state = SwVsyncState::new();
497        state.set_refresh_mhz(60_000);
498        // 1_000_000_000_000 / 60000 = 16_666_666
499        assert_eq!(state.interval_ns(), 16_666_666);
500    }
501
502    #[test]
503    fn test_set_refresh_144hz() {
504        let mut state = SwVsyncState::new();
505        state.set_refresh_mhz(144_000);
506        // 1_000_000_000_000 / 144000 = 6_944_444
507        assert_eq!(state.interval_ns(), 6_944_444);
508    }
509
510    #[test]
511    fn test_custom_interval() {
512        let state = SwVsyncState::with_interval_ns(8_333_333); // ~120Hz
513        assert_eq!(state.interval_ns(), 8_333_333);
514    }
515
516    #[test]
517    fn test_enable_disable() {
518        let mut state = SwVsyncState::new();
519        assert!(state.is_enabled());
520        state.set_enabled(false);
521        assert!(!state.is_enabled());
522    }
523
524    #[test]
525    fn test_frame_stats_circular_buffer() {
526        let mut stats = FrameStats::new();
527        // Fill beyond FRAME_HISTORY_SIZE
528        for i in 0..FRAME_HISTORY_SIZE + 10 {
529            stats.record((i as u64 + 1) * 1_000_000);
530        }
531        assert_eq!(stats.frame_times.len(), FRAME_HISTORY_SIZE);
532        assert_eq!(stats.frame_count, (FRAME_HISTORY_SIZE + 10) as u64);
533    }
534}