cosmic-comp/src/backend/winit.rs
2023-06-30 15:15:21 +02:00

367 lines
12 KiB
Rust

// SPDX-License-Identifier: GPL-3.0-only
use crate::{
backend::render,
config::OutputConfig,
input::Devices,
state::{BackendData, Common, Data},
utils::prelude::*,
wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession},
};
use anyhow::{anyhow, Context, Result};
use smithay::{
backend::{
egl::EGLDevice,
renderer::{
damage::{OutputDamageTracker, RenderOutputResult},
gles::GlesRenderbuffer,
glow::GlowRenderer,
ImportDma, ImportEgl,
},
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,
},
utils::Transform,
wayland::dmabuf::DmabufFeedbackBuilder,
};
use std::cell::RefCell;
use tracing::{error, info, warn};
#[cfg(feature = "debug")]
use crate::state::Fps;
use super::render::{init_shaders, CursorMode};
pub struct WinitState {
// The winit backend currently has no notion of multiple windows
pub backend: WinitGraphicsBackend<GlowRenderer>,
output: Output,
damage_tracker: OutputDamageTracker,
screencopy: Vec<(ScreencopySession, BufferParams)>,
#[cfg(feature = "debug")]
fps: Fps,
}
impl WinitState {
pub fn render_output(&mut self, state: &mut Common) -> Result<()> {
self.backend
.bind()
.with_context(|| "Failed to bind buffer")?;
let age = self.backend.buffer_age().unwrap_or(0);
let surface = self.backend.egl_surface();
match render::render_output::<_, _, GlesRenderbuffer, _>(
None,
self.backend.renderer(),
surface.clone(),
&mut self.damage_tracker,
age,
state,
&self.output,
CursorMode::NotDefault,
if !self.screencopy.is_empty() {
Some((surface, &self.screencopy))
} else {
None
},
#[cfg(not(feature = "debug"))]
None,
#[cfg(feature = "debug")]
Some(&mut self.fps),
) {
Ok(RenderOutputResult { damage, states, .. }) => {
self.backend
.bind()
.with_context(|| "Failed to bind display")?;
self.backend
.submit(damage.as_deref())
.with_context(|| "Failed to submit buffer for display")?;
self.screencopy.clear();
#[cfg(feature = "debug")]
self.fps.displayed();
state.send_frames(&self.output, &states, |_| None);
if damage.is_some() {
let mut output_presentation_feedback =
state.take_presentation_feedback(&self.output, &states);
output_presentation_feedback.presented(
state.clock.now(),
self.output
.current_mode()
.map(|mode| mode.refresh as u32)
.unwrap_or_default(),
0,
wp_presentation_feedback::Kind::Vsync,
);
}
}
Err(err) => {
for (session, params) in self.screencopy.drain(..) {
state.still_pending(session, params)
}
anyhow::bail!("Rendering failed: {}", err);
}
};
Ok(())
}
pub fn apply_config_for_output(
&mut self,
output: &Output,
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 = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
if dbg!(config.mode.0) != dbg!((size.physical_size.w as i32, size.physical_size.h as i32)) {
if !test_only {
config.mode = (
(size.physical_size.w as i32, size.physical_size.h as i32),
None,
);
}
Err(anyhow::anyhow!("Cannot set window size"))
} else {
Ok(())
}
}
pub fn pending_screencopy(&mut self, new: Option<Vec<(ScreencopySession, BufferParams)>>) {
if let Some(sessions) = new {
self.screencopy.extend(sessions);
}
}
}
pub fn init_backend(
dh: &DisplayHandle,
event_loop: &mut EventLoop<Data>,
state: &mut State,
) -> Result<()> {
let (mut backend, mut input) =
winit::init().map_err(|_| anyhow!("Failed to initilize winit backend"))?;
init_shaders(backend.renderer()).expect("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.physical_size.w as i32, size.physical_size.h as i32).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.physical_size.w as i32, size.physical_size.h as i32),
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 |_, _, data| {
if let Err(err) = data
.state
.backend
.winit()
.render_output(&mut data.state.common)
{
error!(?err, "Failed to render frame.");
render_ping.ping();
}
})
.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 |_, _, data| {
match input.dispatch_new_events(|event| {
data.state.process_winit_event(event, &render_ping_handle)
}) {
Ok(_) => {
event_ping_handle.ping();
render_ping_handle.ping();
}
Err(winit::WinitError::WindowClosed) => {
let output = data.state.backend.winit().output.clone();
let seats = data.state.common.seats().cloned().collect::<Vec<_>>();
data.state
.common
.shell
.remove_output(&output, seats.into_iter());
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();
#[cfg(feature = "debug")]
let fps = Fps::new(backend.renderer());
state.backend = BackendData::Winit(WinitState {
backend,
output: output.clone(),
damage_tracker: OutputDamageTracker::from_output(&output),
screencopy: Vec::new(),
#[cfg(feature = "debug")]
fps,
});
state
.common
.output_configuration_state
.add_heads(std::iter::once(&output));
state.common.shell.add_output(&output);
let seats = state.common.seats().cloned().collect::<Vec<_>>();
state.common.config.read_outputs(
&mut state.common.output_configuration_state,
&mut state.backend,
&mut state.common.shell,
seats.iter().cloned(),
&state.common.event_loop_handle,
);
state.launch_xwayland(None);
Ok(())
}
fn init_egl_client_side(
dh: &DisplayHandle,
state: &mut State,
renderer: &mut WinitGraphicsBackend<GlowRenderer>,
) -> Result<()> {
let bind_result = renderer.renderer().bind_wl_display(dh);
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().collect::<Vec<_>>();
let dmabuf_default_feedback = match render_node {
Ok(Some(node)) => {
let dmabuf_default_feedback =
DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone())
.build()
.unwrap();
Some(dmabuf_default_feedback)
}
Ok(None) => {
warn!("Failed to query render node, dmabuf protocol will only advertise v3");
None
}
Err(err) => {
warn!(
?err,
"Failed to egl device for display, dmabuf protocol will only advertise v3"
);
None
}
};
match dmabuf_default_feedback {
Some(feedback) => {
state
.common
.dmabuf_state
.create_global_with_default_feedback::<State>(dh, &feedback);
info!("EGL hardware-acceleration enabled.");
}
None if bind_result.is_ok() => {
state
.common
.dmabuf_state
.create_global::<State>(dh, dmabuf_formats);
info!("EGL hardware-acceleration enabled.");
}
None => {
let err = bind_result.unwrap_err();
warn!(?err, "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.seats().cloned().collect::<Vec<_>>().iter() {
let devices = seat.user_data().get::<Devices>().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::<RefCell<OutputConfig>>()
.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();
self.common.shell.refresh_outputs();
render_ping.ping();
}
WinitEvent::Refresh => render_ping.ping(),
WinitEvent::Input(event) => self.process_input_event(event),
_ => {}
};
}
}