332 lines
11 KiB
Rust
332 lines
11 KiB
Rust
// 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<GlowRenderer>,
|
|
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<Vec<Output>, 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::<RefCell<OutputConfig>>()
|
|
.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>,
|
|
state: &mut State,
|
|
) -> Result<()> {
|
|
let (mut backend, mut input): (WinitGraphicsBackend<GlowRenderer>, _) =
|
|
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<GlowRenderer>,
|
|
) -> 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::<State>(dh, &feedback);
|
|
|
|
let render_node = render_node.unwrap().unwrap();
|
|
let _drm_global_id = state.common.wl_drm_state.create_global::<State>(
|
|
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::<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();
|
|
render_ping.ping();
|
|
}
|
|
WinitEvent::Redraw => render_ping.ping(),
|
|
WinitEvent::Input(event) => self.process_input_event(event),
|
|
WinitEvent::CloseRequested => {
|
|
self.common.should_stop = true;
|
|
}
|
|
_ => {}
|
|
};
|
|
}
|
|
}
|