use std::sync::Arc;
use std::time::{Duration, Instant};
use vulkano::device::Queue;
use vulkano::swapchain::{PresentMode, Surface};
use winit::event_loop::{ActiveEventLoop, ControlFlow};
use winit::window::Window;
use crate::runtime::RenderSettings;
#[must_use]
pub fn select_present_mode(
queue: &Arc<Queue>,
surface: &Arc<Surface>,
vsync: bool,
) -> PresentMode {
let Ok(modes) = queue
.device()
.physical_device()
.surface_present_modes(surface, Default::default())
else {
return PresentMode::Fifo;
};
pick_present_mode(&modes, vsync)
}
#[must_use]
pub fn pick_present_mode(modes: &[PresentMode], vsync: bool) -> PresentMode {
let preferences = if vsync {
[
PresentMode::Fifo,
PresentMode::Mailbox,
PresentMode::Immediate,
]
} else {
[
PresentMode::Immediate,
PresentMode::Mailbox,
PresentMode::Fifo,
]
};
preferences
.into_iter()
.find(|mode| modes.contains(mode))
.unwrap_or(PresentMode::Fifo)
}
pub struct FramePacer {
next_frame: Instant,
}
impl Default for FramePacer {
fn default() -> Self {
Self {
next_frame: Instant::now(),
}
}
}
impl FramePacer {
pub fn request_next_frame(
&mut self,
event_loop: &ActiveEventLoop,
window: &Window,
settings: &RenderSettings,
) {
if !settings.limit_fps {
event_loop.set_control_flow(ControlFlow::Poll);
self.next_frame = Instant::now();
window.request_redraw();
return;
}
let interval =
Duration::from_secs_f64(1.0 / f64::from(settings.max_fps.max(1)));
match self.due(Instant::now(), interval) {
Ok(()) => window.request_redraw(),
Err(deadline) => {
event_loop.set_control_flow(ControlFlow::WaitUntil(deadline));
}
}
}
fn due(&mut self, now: Instant, interval: Duration) -> Result<(), Instant> {
if now < self.next_frame {
return Err(self.next_frame);
}
self.next_frame += interval;
if self.next_frame <= now {
self.next_frame = now + interval;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn present_mode_falls_back_to_what_the_surface_supports() {
let all = [
PresentMode::Fifo,
PresentMode::Mailbox,
PresentMode::Immediate,
];
assert_eq!(pick_present_mode(&all, true), PresentMode::Fifo);
assert_eq!(pick_present_mode(&all, false), PresentMode::Immediate);
let no_immediate = [PresentMode::Fifo, PresentMode::Mailbox];
assert_eq!(
pick_present_mode(&no_immediate, false),
PresentMode::Mailbox
);
assert_eq!(
pick_present_mode(&[PresentMode::Fifo], false),
PresentMode::Fifo
);
assert_eq!(pick_present_mode(&[], false), PresentMode::Fifo);
assert_eq!(
pick_present_mode(&[PresentMode::Mailbox], true),
PresentMode::Mailbox
);
}
#[test]
fn the_limiter_holds_its_rate_through_late_wakes_and_uneven_frames() {
let interval = Duration::from_millis(10);
let start = Instant::now();
let mut pacer = FramePacer { next_frame: start };
let mut now = start;
let mut frames = Vec::new();
for frame in 0..200u64 {
while let Err(deadline) = pacer.due(now, interval) {
now = deadline + Duration::from_micros(500);
}
frames.push(now - start);
now += if frame == 100 {
Duration::from_millis(35)
} else {
Duration::from_micros(1000 + frame * 37 % 8100)
};
}
for (k, at) in frames[1..=100].iter().enumerate() {
let k = k as u32 + 1;
assert_eq!(*at, interval * k + Duration::from_micros(500), "{k}");
}
let gaps: Vec<_> = frames.windows(2).map(|w| w[1] - w[0]).collect();
assert!(gaps.iter().all(|gap| *gap >= interval), "{gaps:?}");
assert_eq!(gaps[100], Duration::from_millis(35));
assert!(gaps[102..].iter().all(|gap| *gap == interval), "{gaps:?}");
}
}