// SPDX-License-Identifier: GPL-3.0-only use crate::{ backend::render, config::OutputConfig, shell::{Devices, SeatExt}, state::{BackendData, Common}, utils::prelude::*, }; use anyhow::{anyhow, Context, Result}; use smithay::{ backend::{ drm::NodeType, egl::EGLDevice, renderer::{ damage::{OutputDamageTracker, RenderOutputResult}, gles::GlesRenderbuffer, glow::GlowRenderer, ImportDma, }, winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice}, }, desktop::layer_map_for_output, output::{Mode, Output, PhysicalProperties, Scale, Subpixel}, reexports::{ calloop::{ping, EventLoop}, wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::DisplayHandle, winit::platform::pump_events::PumpStatus, }, utils::Transform, wayland::{dmabuf::DmabufFeedbackBuilder, presentation::Refresh}, }; use std::{borrow::BorrowMut, cell::RefCell, time::Duration}; use tracing::{error, info, warn}; use super::render::{init_shaders, CursorMode}; #[derive(Debug)] pub struct WinitState { // The winit backend currently has no notion of multiple windows pub backend: WinitGraphicsBackend, output: Output, damage_tracker: OutputDamageTracker, } impl WinitState { #[profiling::function] pub fn render_output(&mut self, state: &mut Common) -> Result<()> { let age = self.backend.buffer_age().unwrap_or(0); let (renderer, mut fb) = self .backend .bind() .with_context(|| "Failed to bind buffer")?; match render::render_output::<_, GlesRenderbuffer>( None, renderer, &mut fb, &mut self.damage_tracker, age, &state.shell, state.clock.now(), &self.output, CursorMode::NotDefault, ) { Ok(RenderOutputResult { damage, states, .. }) => { std::mem::drop(fb); self.backend .submit(damage.map(|x| x.as_slice())) .with_context(|| "Failed to submit buffer for display")?; state.send_frames(&self.output, None); state.update_primary_output(&self.output, &states); state.send_dmabuf_feedback(&self.output, &states, |_| None); if damage.is_some() { let mut output_presentation_feedback = state .shell .read() .unwrap() .take_presentation_feedback(&self.output, &states); output_presentation_feedback.presented( state.clock.now(), self.output .current_mode() .map(|mode| { Refresh::Fixed(Duration::from_secs_f64( 1_000.0 / mode.refresh as f64, )) }) .unwrap_or(Refresh::Unknown), 0, wp_presentation_feedback::Kind::Vsync, ); } } Err(err) => { anyhow::bail!("Rendering failed: {}", err); } }; Ok(()) } pub fn apply_config_for_outputs( &mut self, test_only: bool, ) -> Result, anyhow::Error> { // TODO: if we ever have multiple winit outputs, don't ignore config.enabled // reset size let size = self.backend.window_size(); let mut config = self .output .user_data() .get::>() .unwrap() .borrow_mut(); if dbg!(config.mode.0) != dbg!((size.w, size.h)) { if !test_only { config.mode = ((size.w, size.h), None); } Err(anyhow::anyhow!("Cannot set window size")) } else { Ok(vec![self.output.clone()]) } } } pub fn init_backend( dh: &DisplayHandle, event_loop: &mut EventLoop, state: &mut State, ) -> Result<()> { let (mut backend, mut input): (WinitGraphicsBackend, _) = winit::init().map_err(|e| anyhow!("Failed to initilize winit backend: {e:?}"))?; init_shaders(backend.renderer().borrow_mut()).context("Failed to initialize renderer")?; init_egl_client_side(dh, state, &mut backend)?; let name = format!("WINIT-0"); let size = backend.window_size(); let props = PhysicalProperties { size: (0, 0).into(), subpixel: Subpixel::Unknown, make: "COSMIC".to_string(), model: name.clone(), }; let mode = Mode { size: (size.w, size.h).into(), refresh: 60_000, }; let output = Output::new(name, props); output.add_mode(mode); output.set_preferred(mode); output.change_current_state( Some(mode), Some(Transform::Flipped180), Some(Scale::Integer(1)), Some((0, 0).into()), ); output.user_data().insert_if_missing(|| { RefCell::new(OutputConfig { mode: ((size.w, size.h), None), transform: Transform::Flipped180.into(), ..Default::default() }) }); let (event_ping, event_source) = ping::make_ping().with_context(|| "Failed to init eventloop timer for winit")?; let (render_ping, render_source) = ping::make_ping().with_context(|| "Failed to init eventloop timer for winit")?; let event_ping_handle = event_ping.clone(); let render_ping_handle = render_ping.clone(); let mut token = Some( event_loop .handle() .insert_source(render_source, move |_, _, state| { if let Err(err) = state.backend.winit().render_output(&mut state.common) { error!(?err, "Failed to render frame."); render_ping.ping(); } profiling::finish_frame!(); }) .map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?, ); let event_loop_handle = event_loop.handle(); event_loop .handle() .insert_source(event_source, move |_, _, state| { match input .dispatch_new_events(|event| state.process_winit_event(event, &render_ping_handle)) { PumpStatus::Continue => { event_ping_handle.ping(); render_ping_handle.ping(); } PumpStatus::Exit(_) => { let output = state.backend.winit().output.clone(); state.common.remove_output(&output); if let Some(token) = token.take() { event_loop_handle.remove(token); } } }; }) .map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?; event_ping.ping(); state.backend = BackendData::Winit(WinitState { backend, output: output.clone(), damage_tracker: OutputDamageTracker::from_output(&output), }); state .common .output_configuration_state .add_heads(std::iter::once(&output)); { state.common.add_output(&output); state.common.config.read_outputs( &mut state.common.output_configuration_state, &mut state.backend, &state.common.shell, &state.common.event_loop_handle, &mut state.common.workspace_state.update(), &state.common.xdg_activation_state, state.common.startup_done.clone(), &state.common.clock, ); state.common.refresh(); } state.launch_xwayland(None); Ok(()) } fn init_egl_client_side( dh: &DisplayHandle, state: &mut State, renderer: &mut WinitGraphicsBackend, ) -> Result<()> { let render_node = EGLDevice::device_for_display(renderer.renderer().egl_context().display()) .and_then(|device| device.try_get_render_node()); let dmabuf_formats = renderer.renderer().dmabuf_formats(); match render_node { Ok(Some(node)) => { let feedback = DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone()) .build() .unwrap(); let dmabuf_global = state .common .dmabuf_state .create_global_with_default_feedback::(dh, &feedback); let render_node = render_node.unwrap().unwrap(); let _drm_global_id = state.common.wl_drm_state.create_global::( dh, render_node .dev_path_with_type(NodeType::Render) .or_else(|| render_node.dev_path()) .ok_or(anyhow!( "Could not determine path for gpu node: {}", render_node ))?, dmabuf_formats, &dmabuf_global, ); info!("EGL hardware-acceleration enabled."); } Ok(None) => { warn!("Failed to query render node. Unable to initialize bind display to EGL.") } Err(err) => { warn!( ?err, "Failed to egl device for display. Unable to initialize bind display to EGL." ) } } Ok(()) } impl State { pub fn process_winit_event(&mut self, event: WinitEvent, render_ping: &ping::Ping) { // here we can handle special cases for winit inputs match event { WinitEvent::Focus(true) => { for seat in self.common.shell.read().unwrap().seats.iter() { let devices = seat.user_data().get::().unwrap(); if devices.has_device(&WinitVirtualDevice) { seat.set_active_output(&self.backend.winit().output); break; } } } WinitEvent::Resized { size, .. } => { let winit_state = self.backend.winit(); let output = &winit_state.output; let mode = Mode { size, refresh: 60_000, }; { let mut config = output .user_data() .get::>() .unwrap() .borrow_mut(); config.mode.0 = size.into(); } output.delete_mode(output.current_mode().unwrap()); output.set_preferred(mode); output.change_current_state(Some(mode), None, None, None); layer_map_for_output(output).arrange(); self.common.output_configuration_state.update(); render_ping.ping(); } WinitEvent::Redraw => render_ping.ping(), WinitEvent::Input(event) => self.process_input_event(event), WinitEvent::CloseRequested => { self.common.should_stop = true; } _ => {} }; } }