use std::cell::RefCell; use std::collections::HashMap; use std::mem; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::Duration; use smithay::backend::renderer::damage::OutputDamageTracker; use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::{DebugFlags, Renderer}; use smithay::backend::winit::{self, WinitError, WinitEvent, WinitGraphicsBackend}; use smithay::output::{Mode, Output, PhysicalProperties, Scale, Subpixel}; use smithay::reexports::calloop::timer::{TimeoutAction, Timer}; use smithay::reexports::calloop::LoopHandle; use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback; use smithay::reexports::winit::dpi::LogicalSize; use smithay::reexports::winit::window::WindowBuilder; use smithay::utils::Transform; use smithay::wayland::dmabuf::DmabufFeedback; use crate::config::Config; use crate::niri::{OutputRenderElements, RedrawState, State}; use crate::utils::get_monotonic_time; use crate::Niri; pub struct Winit { config: Rc>, output: Output, backend: WinitGraphicsBackend, damage_tracker: OutputDamageTracker, connectors: Arc>>, } impl Winit { pub fn new(config: Rc>, event_loop: LoopHandle) -> Self { let builder = WindowBuilder::new() .with_inner_size(LogicalSize::new(1280.0, 800.0)) // .with_resizable(false) .with_title("niri"); let (backend, mut winit_event_loop) = winit::init_from_builder(builder).unwrap(); let output_config = config .borrow() .outputs .iter() .find(|o| o.name == "winit") .cloned() .unwrap_or_default(); let scale = output_config.scale.clamp(0.1, 10.); let scale = scale.max(1.).round() as i32; let mode = Mode { size: backend.window_size().physical_size, refresh: 60_000, }; let output = Output::new( "winit".to_string(), PhysicalProperties { size: (0, 0).into(), subpixel: Subpixel::Unknown, make: "Smithay".into(), model: "Winit".into(), }, ); output.change_current_state( Some(mode), Some(Transform::Flipped180), Some(Scale::Integer(scale)), None, ); output.set_preferred(mode); let connectors = Arc::new(Mutex::new(HashMap::from([( "winit".to_owned(), output.clone(), )]))); let damage_tracker = OutputDamageTracker::from_output(&output); let timer = Timer::immediate(); event_loop .insert_source(timer, move |_, _, state| { let res = winit_event_loop.dispatch_new_events(|event| match event { WinitEvent::Resized { size, .. } => { let winit = state.backend.winit(); winit.output.change_current_state( Some(Mode { size, refresh: 60_000, }), None, None, None, ); state.niri.output_resized(winit.output.clone()); } WinitEvent::Input(event) => state.process_input_event(event), WinitEvent::Focus(_) => (), WinitEvent::Refresh => state .niri .queue_redraw(state.backend.winit().output.clone()), }); // I want this to stop compiling if more errors are added. #[allow(clippy::single_match)] match res { Err(WinitError::WindowClosed) => { state.niri.stop_signal.stop(); state.niri.remove_output(&state.backend.winit().output); } Ok(()) => (), } TimeoutAction::ToDuration(Duration::from_micros(16667)) }) .unwrap(); Self { config, output, backend, damage_tracker, connectors, } } pub fn init(&mut self, niri: &mut Niri) { // For some reason, binding the display here causes damage tracker artifacts. // // use smithay::backend::renderer::ImportEgl; // // if let Err(err) = self // .backend // .renderer() // .bind_wl_display(&niri.display_handle) // { // warn!("error binding renderer wl_display: {err}"); // } niri.add_output(self.output.clone(), None); } pub fn seat_name(&self) -> String { "winit".to_owned() } pub fn renderer(&mut self) -> &mut GlesRenderer { self.backend.renderer() } pub fn render( &mut self, niri: &mut Niri, output: &Output, elements: &[OutputRenderElements], ) -> Option<&DmabufFeedback> { let _span = tracy_client::span!("Winit::render"); self.backend.bind().unwrap(); let age = self.backend.buffer_age().unwrap(); let res = self .damage_tracker .render_output(self.backend.renderer(), age, elements, [0.1, 0.1, 0.1, 1.0]) .unwrap(); niri.update_primary_scanout_output(output, &res.states); if let Some(damage) = res.damage { if self .config .borrow() .debug .wait_for_frame_completion_before_queueing { let _span = tracy_client::span!("wait for completion"); res.sync.wait(); } self.backend.submit(Some(&damage)).unwrap(); let mut presentation_feedbacks = niri.take_presentation_feedbacks(output, &res.states); let mode = output.current_mode().unwrap(); let refresh = Duration::from_secs_f64(1_000f64 / mode.refresh as f64); presentation_feedbacks.presented::<_, smithay::utils::Monotonic>( get_monotonic_time(), refresh, 0, wp_presentation_feedback::Kind::empty(), ); } let output_state = niri.output_state.get_mut(output).unwrap(); match mem::replace(&mut output_state.redraw_state, RedrawState::Idle) { RedrawState::Idle => unreachable!(), RedrawState::Queued(_) => (), RedrawState::WaitingForVBlank { .. } => unreachable!(), RedrawState::WaitingForEstimatedVBlank(_) => unreachable!(), RedrawState::WaitingForEstimatedVBlankAndQueued(_) => unreachable!(), } if output_state.unfinished_animations_remain { self.backend.window().request_redraw(); } None } pub fn toggle_debug_tint(&mut self) { let renderer = self.backend.renderer(); renderer.set_debug_flags(renderer.debug_flags() ^ DebugFlags::TINT); } pub fn connectors(&self) -> Arc>> { self.connectors.clone() } }