use {
super::{
display::{Display, DisplayError},
driver::{Device, Driver, DriverConfigBuilder, DriverError},
frame::FrameContext,
},
log::{debug, info, trace, warn},
std::{
mem::take,
sync::Arc,
time::{Duration, Instant},
},
winit::{
event::{Event, WindowEvent},
event_loop::ControlFlow,
monitor::MonitorHandle,
platform::run_return::EventLoopExtRunReturn,
window::{Fullscreen, Window, WindowBuilder},
},
};
pub fn run<FrameFn>(frame_fn: FrameFn) -> Result<(), DisplayError>
where
FrameFn: FnMut(FrameContext),
{
EventLoop::new().build()?.run(frame_fn)
}
pub enum FullscreenMode {
Borderless,
Exclusive,
}
#[derive(Debug)]
pub struct EventLoop {
pub device: Arc<Device>,
display: Display,
event_loop: winit::event_loop::EventLoop<()>,
pub window: Window,
}
impl EventLoop {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> EventLoopBuilder {
Default::default()
}
pub fn height(&self) -> u32 {
self.window.inner_size().height
}
pub fn run<FrameFn>(mut self, mut frame_fn: FrameFn) -> Result<(), DisplayError>
where
FrameFn: FnMut(FrameContext),
{
let mut events = Vec::new();
let mut will_exit = false;
const STANDARD_REFRESH_RATE: u16 = 60;
let refresh_rate =
self.window
.fullscreen()
.map(|mode| match mode {
Fullscreen::Exclusive(mode) => mode.refresh_rate(),
Fullscreen::Borderless(Some(monitor)) => monitor
.video_modes()
.next()
.map(|mode| mode.refresh_rate())
.unwrap_or(STANDARD_REFRESH_RATE),
_ => STANDARD_REFRESH_RATE,
})
.unwrap_or(STANDARD_REFRESH_RATE)
.clamp(STANDARD_REFRESH_RATE, STANDARD_REFRESH_RATE << 2) as f32;
let mut last_frame = Instant::now();
let mut dt_filtered = 1.0 / refresh_rate;
last_frame -= Duration::from_secs_f32(dt_filtered);
debug!("first frame dt: {}", dt_filtered);
while !will_exit {
trace!("🟥🟩🟦 Event::RedrawRequested");
self.event_loop.run_return(|event, _, control_flow| {
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
*control_flow = ControlFlow::Exit;
will_exit = true;
}
Event::MainEventsCleared => *control_flow = ControlFlow::Exit,
_ => *control_flow = ControlFlow::Poll,
}
events.extend(event.to_static());
});
if !events.is_empty() {
trace!("received {} events", events.len(),);
}
let now = Instant::now();
{
let dt_duration = now - last_frame;
last_frame = now;
let dt_raw = dt_duration.as_secs_f32();
dt_filtered = dt_filtered + (dt_raw - dt_filtered) / 10.0;
};
let (swapchain, mut render_graph) = self.display.acquire_next_image()?;
frame_fn(FrameContext {
device: &self.device,
dt: dt_filtered,
height: self.height(),
render_graph: &mut render_graph,
events: take(&mut events).as_slice(),
swapchain_image: swapchain,
width: self.width(),
window: &self.window,
will_exit: &mut will_exit,
});
let elapsed = Instant::now() - now;
trace!(
"✅✅✅ render graph construction: {} μs ({}% load)",
elapsed.as_micros(),
((elapsed.as_secs_f32() / refresh_rate) * 100.0) as usize,
);
self.display.present_image(render_graph, swapchain)?;
}
Ok(())
}
pub fn width(&self) -> u32 {
self.window.inner_size().width
}
pub fn window(&self) -> &Window {
&self.window
}
}
#[derive(Debug)]
pub struct EventLoopBuilder {
driver_cfg: DriverConfigBuilder,
event_loop: winit::event_loop::EventLoop<()>,
window: WindowBuilder,
}
impl Default for EventLoopBuilder {
fn default() -> Self {
Self {
driver_cfg: DriverConfigBuilder::default(),
event_loop: winit::event_loop::EventLoop::new(),
window: Default::default(),
}
}
}
impl EventLoopBuilder {
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
self.event_loop.available_monitors()
}
pub fn configure<ConfigureFn>(mut self, configure_fn: ConfigureFn) -> Self
where
ConfigureFn: FnOnce(DriverConfigBuilder) -> DriverConfigBuilder,
{
self.driver_cfg = configure_fn(self.driver_cfg);
self
}
pub fn desired_swapchain_image_count(mut self, desired_swapchain_image_count: u32) -> Self {
self.driver_cfg = self
.driver_cfg
.desired_swapchain_image_count(desired_swapchain_image_count);
self
}
pub fn sync_display(mut self, sync_display: bool) -> Self {
self.driver_cfg = self.driver_cfg.sync_display(sync_display);
self
}
pub fn fullscreen_mode(mut self, mode: FullscreenMode) -> Self {
self.window = self.window.with_fullscreen(Some(match mode {
FullscreenMode::Borderless => Fullscreen::Borderless(None),
FullscreenMode::Exclusive => {
if let Some(video_mode) = self
.event_loop
.primary_monitor()
.and_then(|monitor| monitor.video_modes().next())
{
Fullscreen::Exclusive(video_mode)
} else {
Fullscreen::Borderless(None)
}
}
}));
self
}
pub fn debug(mut self, debug: bool) -> Self {
self.driver_cfg = self.driver_cfg.debug(debug);
self
}
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.event_loop.primary_monitor()
}
pub fn ray_tracing(mut self, ray_tracing: bool) -> Self {
self.driver_cfg = self.driver_cfg.ray_tracing(ray_tracing);
self
}
pub fn window<WindowFn>(mut self, window_fn: WindowFn) -> Self
where
WindowFn: FnOnce(WindowBuilder) -> WindowBuilder,
{
self.window = window_fn(self.window);
self
}
pub fn window_mode(mut self) -> Self {
self.window = self.window.with_fullscreen(None);
self
}
}
impl EventLoopBuilder {
pub fn build(self) -> Result<EventLoop, DriverError> {
let cfg = self
.driver_cfg
.build()
.map_err(|_| DriverError::InvalidData)?;
let window = self.window.build(&self.event_loop).map_err(|err| {
warn!("{err}");
DriverError::Unsupported
})?;
let (width, height) = {
let inner_size = window.inner_size();
(inner_size.width, inner_size.height)
};
let driver = Driver::new(&window, cfg, width, height)?;
let display = Display::new(&driver.device, driver.swapchain);
info!(
"display resolution: {}x{} ({}x scale)",
width,
height,
window.scale_factor() as f32,
);
Ok(EventLoop {
device: Arc::clone(&driver.device),
display,
event_loop: self.event_loop,
window,
})
}
}