feat: manage multiple DRM output pipelines
This commit is contained in:
1 parent
42ad4e1f68
commit
1934cef705
3 files changed
+331
-101
No files matched your search
@@ -1,4 +1,10 @@
|
|||||||
use std::{collections::HashMap, error::Error, io, path::Path, time::Duration};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
error::Error,
|
||||||
|
io,
|
||||||
|
path::Path,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
@@ -24,6 +30,7 @@ use smithay::{
|
|||||||
input::Libinput,
|
input::Libinput,
|
||||||
rustix::fs::OFlags,
|
rustix::fs::OFlags,
|
||||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind,
|
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind,
|
||||||
|
wayland_server::backend::GlobalId,
|
||||||
},
|
},
|
||||||
utils::{DeviceFd, Monotonic, Time, Transform},
|
utils::{DeviceFd, Monotonic, Time, Transform},
|
||||||
wayland::presentation::Refresh,
|
wayland::presentation::Refresh,
|
||||||
@@ -35,7 +42,7 @@ pub struct DirectBackendState {
|
|||||||
pub session: LibSeatSession,
|
pub session: LibSeatSession,
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
devices: HashMap<DrmNode, DirectDevice>,
|
devices: HashMap<DrmNode, DirectDevice>,
|
||||||
presentation: PresentationClock,
|
presentation: HashMap<(DrmNode, crtc::Handle), PresentationClock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -48,18 +55,16 @@ struct PresentationClock {
|
|||||||
|
|
||||||
struct DirectDevice {
|
struct DirectDevice {
|
||||||
drm: DrmDevice,
|
drm: DrmDevice,
|
||||||
#[allow(dead_code)]
|
|
||||||
gbm: GbmDevice<DrmDeviceFd>,
|
gbm: GbmDevice<DrmDeviceFd>,
|
||||||
#[allow(dead_code)]
|
|
||||||
renderer: GlesRenderer,
|
renderer: GlesRenderer,
|
||||||
#[allow(dead_code)]
|
outputs: HashMap<crtc::Handle, DirectOutput>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DirectOutput {
|
||||||
connector: connector::Handle,
|
connector: connector::Handle,
|
||||||
#[allow(dead_code)]
|
|
||||||
crtc: crtc::Handle,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
mode: DrmMode,
|
mode: DrmMode,
|
||||||
#[allow(dead_code)]
|
|
||||||
output: Output,
|
output: Output,
|
||||||
|
global: GlobalId,
|
||||||
surface: GbmBufferedSurface<GbmAllocator<DrmDeviceFd>, OutputPresentationFeedback>,
|
surface: GbmBufferedSurface<GbmAllocator<DrmDeviceFd>, OutputPresentationFeedback>,
|
||||||
damage_tracker: OutputDamageTracker,
|
damage_tracker: OutputDamageTracker,
|
||||||
frame_pending: bool,
|
frame_pending: bool,
|
||||||
@@ -92,7 +97,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
session,
|
session,
|
||||||
active: session_active,
|
active: session_active,
|
||||||
devices: HashMap::new(),
|
devices: HashMap::new(),
|
||||||
presentation: PresentationClock::default(),
|
presentation: HashMap::new(),
|
||||||
});
|
});
|
||||||
open_primary_device(event_loop, state, &primary_path)?;
|
open_primary_device(event_loop, state, &primary_path)?;
|
||||||
|
|
||||||
@@ -107,12 +112,18 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
SessionEvent::PauseSession => {
|
SessionEvent::PauseSession => {
|
||||||
if let Some(backend) = state.direct_backend.as_mut() {
|
if let Some(backend) = state.direct_backend.as_mut() {
|
||||||
backend.active = false;
|
backend.active = false;
|
||||||
backend.presentation.reset_timing();
|
backend
|
||||||
|
.presentation
|
||||||
|
.values_mut()
|
||||||
|
.for_each(PresentationClock::reset_timing);
|
||||||
backend.devices.values_mut().for_each(|device| {
|
backend.devices.values_mut().for_each(|device| {
|
||||||
device.drm.pause();
|
device.drm.pause();
|
||||||
device.surface.reset_buffers();
|
device.outputs.values_mut().for_each(|output| {
|
||||||
device.damage_tracker = OutputDamageTracker::from_output(&device.output);
|
output.surface.reset_buffers();
|
||||||
device.frame_pending = false;
|
output.damage_tracker =
|
||||||
|
OutputDamageTracker::from_output(&output.output);
|
||||||
|
output.frame_pending = false;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
libinput_context.suspend();
|
libinput_context.suspend();
|
||||||
@@ -136,15 +147,23 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
})?;
|
})?;
|
||||||
event_loop
|
event_loop
|
||||||
.handle()
|
.handle()
|
||||||
.insert_source(udev_backend, |event, _, _state| match event {
|
.insert_source(udev_backend, |event, _, state| match event {
|
||||||
UdevEvent::Added { device_id, path } => {
|
UdevEvent::Added { device_id, path } => {
|
||||||
tracing::info!(?device_id, ?path, "DRM device added");
|
if let Some(node) = direct_node_for_device(state, device_id) {
|
||||||
|
rescan_device(state, node);
|
||||||
|
} else {
|
||||||
|
tracing::info!(?device_id, ?path, "additional DRM device discovered");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
UdevEvent::Changed { device_id } => {
|
UdevEvent::Changed { device_id } => {
|
||||||
tracing::debug!(?device_id, "DRM device changed");
|
if let Some(node) = direct_node_for_device(state, device_id) {
|
||||||
|
rescan_device(state, node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
UdevEvent::Removed { device_id } => {
|
UdevEvent::Removed { device_id } => {
|
||||||
tracing::info!(?device_id, "DRM device removed");
|
if let Some(node) = direct_node_for_device(state, device_id) {
|
||||||
|
remove_device(state, node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -182,25 +201,20 @@ fn open_primary_device(
|
|||||||
state
|
state
|
||||||
.dmabuf_state
|
.dmabuf_state
|
||||||
.create_global::<Ferese>(&display_handle, dmabuf_formats);
|
.create_global::<Ferese>(&display_handle, dmabuf_formats);
|
||||||
let (connector, crtc, mode) = select_output(&drm)?;
|
let selections = select_outputs(&drm)?;
|
||||||
let output = create_output(state, &connector, mode);
|
let mut outputs = HashMap::new();
|
||||||
if let Some(backend) = state.direct_backend.as_mut() {
|
for (connector, crtc, mode) in selections {
|
||||||
backend
|
let output = create_direct_output(state, &mut drm, &gbm, &renderer, connector, crtc, mode)?;
|
||||||
|
state
|
||||||
|
.direct_backend
|
||||||
|
.as_mut()
|
||||||
|
.expect("direct backend state remains initialized")
|
||||||
.presentation
|
.presentation
|
||||||
|
.entry((node, crtc))
|
||||||
|
.or_default()
|
||||||
.set_refresh(OutputMode::from(mode).refresh);
|
.set_refresh(OutputMode::from(mode).refresh);
|
||||||
|
outputs.insert(crtc, output);
|
||||||
}
|
}
|
||||||
let drm_surface = drm.create_surface(crtc, mode, &[connector.handle()])?;
|
|
||||||
let allocator = GbmAllocator::new(
|
|
||||||
gbm.clone(),
|
|
||||||
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
|
|
||||||
);
|
|
||||||
let surface = GbmBufferedSurface::new(
|
|
||||||
drm_surface,
|
|
||||||
allocator,
|
|
||||||
&[Fourcc::Argb8888, Fourcc::Abgr8888],
|
|
||||||
renderer.dmabuf_formats(),
|
|
||||||
)?;
|
|
||||||
let damage_tracker = OutputDamageTracker::from_output(&output);
|
|
||||||
|
|
||||||
event_loop
|
event_loop
|
||||||
.handle()
|
.handle()
|
||||||
@@ -211,7 +225,8 @@ fn open_primary_device(
|
|||||||
.direct_backend
|
.direct_backend
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.and_then(|backend| backend.devices.get_mut(&node))
|
.and_then(|backend| backend.devices.get_mut(&node))
|
||||||
.and_then(|device| match device.surface.frame_submitted() {
|
.and_then(|device| device.outputs.get_mut(&crtc))
|
||||||
|
.and_then(|output| match output.surface.frame_submitted() {
|
||||||
Ok(feedback) => feedback,
|
Ok(feedback) => feedback,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!(?node, ?crtc, %error, "failed to retire DRM frame");
|
tracing::error!(?node, ?crtc, %error, "failed to retire DRM frame");
|
||||||
@@ -223,21 +238,30 @@ fn open_primary_device(
|
|||||||
{
|
{
|
||||||
feedback.presented(
|
feedback.presented(
|
||||||
Time::<Monotonic>::from(time),
|
Time::<Monotonic>::from(time),
|
||||||
Refresh::fixed(refresh_duration(state, node)),
|
Refresh::fixed(refresh_duration(state, node, crtc)),
|
||||||
u64::from(metadata.sequence),
|
u64::from(metadata.sequence),
|
||||||
Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some(device) = state
|
let focused = state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|backend| backend.devices.get(&node))
|
||||||
|
.and_then(|device| device.outputs.get(&crtc))
|
||||||
|
.is_some_and(|output| state.is_focused_output(&output.output));
|
||||||
|
if let Some(output) = state
|
||||||
.direct_backend
|
.direct_backend
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.and_then(|backend| backend.devices.get_mut(&node))
|
.and_then(|backend| backend.devices.get_mut(&node))
|
||||||
|
.and_then(|device| device.outputs.get_mut(&crtc))
|
||||||
{
|
{
|
||||||
device.frame_pending = false;
|
output.frame_pending = false;
|
||||||
|
}
|
||||||
|
if focused {
|
||||||
|
state.record_drm_presentation(node, crtc, metadata.time, metadata.sequence);
|
||||||
}
|
}
|
||||||
state.record_drm_presentation(metadata.time, metadata.sequence);
|
|
||||||
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
|
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
|
||||||
render_device(state, node);
|
render_output(state, node, crtc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DrmEvent::Error(error) => {
|
DrmEvent::Error(error) => {
|
||||||
@@ -256,30 +280,30 @@ fn open_primary_device(
|
|||||||
drm,
|
drm,
|
||||||
gbm,
|
gbm,
|
||||||
renderer,
|
renderer,
|
||||||
connector: connector.handle(),
|
outputs,
|
||||||
crtc,
|
|
||||||
mode,
|
|
||||||
output,
|
|
||||||
surface,
|
|
||||||
damage_tracker,
|
|
||||||
frame_pending: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
state.relayout();
|
state.relayout();
|
||||||
render_device(state, node);
|
render_all(state);
|
||||||
tracing::info!(?node, ?path, ?crtc, connector = %connector, "initialized primary DRM/GBM device");
|
tracing::info!(?node, ?path, "initialized DRM/GBM device outputs");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_all(state: &mut Ferese) {
|
pub fn render_all(state: &mut Ferese) {
|
||||||
let nodes = state
|
let outputs = state
|
||||||
.direct_backend
|
.direct_backend
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|backend| backend.devices.keys().copied().collect::<Vec<_>>())
|
.map(|backend| {
|
||||||
|
backend
|
||||||
|
.devices
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(node, device)| device.outputs.keys().map(|crtc| (*node, *crtc)))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
for node in nodes {
|
for (node, crtc) in outputs {
|
||||||
render_device(state, node);
|
render_output(state, node, crtc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +317,7 @@ pub fn switch_vt(state: &mut Ferese, vt: i32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_device(state: &mut Ferese, node: DrmNode) {
|
fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
|
||||||
let Some(mut device) = state
|
let Some(mut device) = state
|
||||||
.direct_backend
|
.direct_backend
|
||||||
.as_mut()
|
.as_mut()
|
||||||
@@ -301,34 +325,39 @@ fn render_device(state: &mut Ferese, node: DrmNode) {
|
|||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !device.drm.is_active() || device.frame_pending {
|
let Some(mut output) = device.outputs.remove(&crtc) else {
|
||||||
|
restore_device(state, node, device);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !device.drm.is_active() || output.frame_pending {
|
||||||
|
device.outputs.insert(crtc, output);
|
||||||
restore_device(state, node, device);
|
restore_device(state, node, device);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rendered = (|| -> Result<bool, Box<dyn Error>> {
|
let rendered = (|| -> Result<bool, Box<dyn Error>> {
|
||||||
state.process_dmabuf_imports(&mut device.renderer);
|
state.process_dmabuf_imports(&mut device.renderer);
|
||||||
let (mut buffer, age) = device.surface.next_buffer()?;
|
let (mut buffer, age) = output.surface.next_buffer()?;
|
||||||
let elements = animated_window_elements(state, &mut device.renderer, &device.output);
|
let elements = animated_window_elements(state, &mut device.renderer, &output.output);
|
||||||
let mut framebuffer = device.renderer.bind(&mut buffer)?;
|
let mut framebuffer = device.renderer.bind(&mut buffer)?;
|
||||||
let result = device.damage_tracker.render_output(
|
let result = output.damage_tracker.render_output(
|
||||||
&mut device.renderer,
|
&mut device.renderer,
|
||||||
&mut framebuffer,
|
&mut framebuffer,
|
||||||
usize::from(age),
|
usize::from(age),
|
||||||
&elements,
|
&elements,
|
||||||
[0.035, 0.04, 0.055, 1.0],
|
[0.035, 0.04, 0.055, 1.0],
|
||||||
)?;
|
)?;
|
||||||
if state.process_screencopies(&mut device.renderer, &framebuffer, &device.output) {
|
if state.process_screencopies(&mut device.renderer, &framebuffer, &output.output) {
|
||||||
let _ = device
|
let _ = device
|
||||||
.renderer
|
.renderer
|
||||||
.render(
|
.render(
|
||||||
&mut framebuffer,
|
&mut framebuffer,
|
||||||
device
|
output
|
||||||
.output
|
.output
|
||||||
.current_mode()
|
.current_mode()
|
||||||
.expect("output has a mode")
|
.expect("output has a mode")
|
||||||
.size,
|
.size,
|
||||||
device.output.current_transform(),
|
output.output.current_transform(),
|
||||||
)?
|
)?
|
||||||
.finish()?;
|
.finish()?;
|
||||||
}
|
}
|
||||||
@@ -336,46 +365,48 @@ fn render_device(state: &mut Ferese, node: DrmNode) {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut presentation = OutputPresentationFeedback::new(&device.output);
|
let mut presentation = OutputPresentationFeedback::new(&output.output);
|
||||||
state.space.elements().for_each(|window| {
|
state.space.elements().for_each(|window| {
|
||||||
window.take_presentation_feedback(
|
window.take_presentation_feedback(
|
||||||
&mut presentation,
|
&mut presentation,
|
||||||
|_, _| Some(device.output.clone()),
|
|_, _| Some(output.output.clone()),
|
||||||
|_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
|_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
let layers = layer_map_for_output(&device.output)
|
let layers = layer_map_for_output(&output.output)
|
||||||
.layers()
|
.layers()
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
layers.iter().for_each(|layer| {
|
layers.iter().for_each(|layer| {
|
||||||
layer.take_presentation_feedback(
|
layer.take_presentation_feedback(
|
||||||
&mut presentation,
|
&mut presentation,
|
||||||
|_, _| Some(device.output.clone()),
|
|_, _| Some(output.output.clone()),
|
||||||
|_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
|_, _| Kind::Vsync | Kind::HwClock | Kind::HwCompletion,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
device
|
output
|
||||||
.surface
|
.surface
|
||||||
.queue_buffer(Some(result.sync), Some(damage), presentation)?;
|
.queue_buffer(Some(result.sync), Some(damage), presentation)?;
|
||||||
device.frame_pending = true;
|
output.frame_pending = true;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
})();
|
})();
|
||||||
|
|
||||||
let queued = match rendered {
|
let queued = match rendered {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
tracing::trace!(?node, "queued DRM frame");
|
tracing::trace!(?node, ?crtc, "queued DRM frame");
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Ok(false) => false,
|
Ok(false) => false,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!(?node, %error, "failed to render DRM frame");
|
tracing::error!(?node, ?crtc, %error, "failed to render DRM frame");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let callback_output = output.output.clone();
|
||||||
|
device.outputs.insert(crtc, output);
|
||||||
restore_device(state, node, device);
|
restore_device(state, node, device);
|
||||||
if queued {
|
if queued {
|
||||||
send_frame_callbacks(state);
|
send_frame_callbacks(state, &callback_output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,58 +416,156 @@ fn restore_device(state: &mut Ferese, node: DrmNode, device: DirectDevice) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_duration(state: &Ferese, node: DrmNode) -> Duration {
|
fn direct_node_for_device(state: &Ferese, device_id: libc::dev_t) -> Option<DrmNode> {
|
||||||
|
state
|
||||||
|
.direct_backend
|
||||||
|
.as_ref()?
|
||||||
|
.devices
|
||||||
|
.keys()
|
||||||
|
.copied()
|
||||||
|
.find(|node| node.dev_id() == device_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rescan_device(state: &mut Ferese, node: DrmNode) {
|
||||||
|
let Some(mut device) = state
|
||||||
|
.direct_backend
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|backend| backend.devices.remove(&node))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let selections = match select_outputs(&device.drm) {
|
||||||
|
Ok(selections) => selections,
|
||||||
|
Err(error) if error.kind() == io::ErrorKind::NotFound => Vec::new(),
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(?node, %error, "failed to scan DRM connectors");
|
||||||
|
restore_device(state, node, device);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut selections = selections
|
||||||
|
.into_iter()
|
||||||
|
.map(|selection @ (_, crtc, _)| (crtc, selection))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
let existing = device.outputs.keys().copied().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for crtc in existing {
|
||||||
|
let unchanged = device.outputs.get(&crtc).is_some_and(|output| {
|
||||||
|
selections.get(&crtc).is_some_and(|(connector, _, mode)| {
|
||||||
|
connector.handle() == output.connector && *mode == output.mode
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if unchanged {
|
||||||
|
selections.remove(&crtc);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(output) = device.outputs.remove(&crtc) {
|
||||||
|
state.display_handle.disable_global::<Ferese>(output.global);
|
||||||
|
state.unregister_output(&output.output);
|
||||||
|
if let Some(backend) = state.direct_backend.as_mut() {
|
||||||
|
backend.presentation.remove(&(node, crtc));
|
||||||
|
}
|
||||||
|
tracing::info!(?node, ?crtc, "removed DRM output");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, (connector, crtc, mode)) in selections {
|
||||||
|
match create_direct_output(
|
||||||
|
state,
|
||||||
|
&mut device.drm,
|
||||||
|
&device.gbm,
|
||||||
|
&device.renderer,
|
||||||
|
connector,
|
||||||
|
crtc,
|
||||||
|
mode,
|
||||||
|
) {
|
||||||
|
Ok(output) => {
|
||||||
|
state
|
||||||
|
.direct_backend
|
||||||
|
.as_mut()
|
||||||
|
.expect("direct backend state remains initialized")
|
||||||
|
.presentation
|
||||||
|
.entry((node, crtc))
|
||||||
|
.or_default()
|
||||||
|
.set_refresh(OutputMode::from(mode).refresh);
|
||||||
|
device.outputs.insert(crtc, output);
|
||||||
|
}
|
||||||
|
Err(error) => tracing::error!(?node, ?crtc, %error, "failed to add DRM output"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_device(state, node, device);
|
||||||
|
state.relayout();
|
||||||
|
render_all(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_device(state: &mut Ferese, node: DrmNode) {
|
||||||
|
let Some(device) = state
|
||||||
|
.direct_backend
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|backend| backend.devices.remove(&node))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (crtc, output) in device.outputs {
|
||||||
|
state.display_handle.disable_global::<Ferese>(output.global);
|
||||||
|
state.unregister_output(&output.output);
|
||||||
|
if let Some(backend) = state.direct_backend.as_mut() {
|
||||||
|
backend.presentation.remove(&(node, crtc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracing::info!(?node, "removed DRM device");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh_duration(state: &Ferese, node: DrmNode, crtc: crtc::Handle) -> Duration {
|
||||||
state
|
state
|
||||||
.direct_backend
|
.direct_backend
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|backend| backend.devices.get(&node))
|
.and_then(|backend| backend.devices.get(&node))
|
||||||
.and_then(|device| device.output.current_mode())
|
.and_then(|device| device.outputs.get(&crtc))
|
||||||
|
.and_then(|output| output.output.current_mode())
|
||||||
.filter(|mode| mode.refresh > 0)
|
.filter(|mode| mode.refresh > 0)
|
||||||
.map(|mode| Duration::from_nanos(1_000_000_000_000_u64 / mode.refresh as u64))
|
.map(|mode| Duration::from_nanos(1_000_000_000_000_u64 / mode.refresh as u64))
|
||||||
.unwrap_or_else(|| Duration::from_millis(16))
|
.unwrap_or_else(|| Duration::from_millis(16))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_frame_callbacks(state: &mut Ferese) {
|
fn send_frame_callbacks(state: &mut Ferese, output: &Output) {
|
||||||
let Some(output) = state
|
|
||||||
.direct_backend
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|backend| backend.devices.values().next())
|
|
||||||
.map(|device| device.output.clone())
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
state.space.elements().for_each(|window| {
|
state.space.elements().for_each(|window| {
|
||||||
window.send_frame(
|
window.send_frame(
|
||||||
&output,
|
output,
|
||||||
state.start_time.elapsed(),
|
state.start_time.elapsed(),
|
||||||
Some(Duration::ZERO),
|
Some(Duration::ZERO),
|
||||||
|_, _| Some(output.clone()),
|
|_, _| Some(output.clone()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
let layers = layer_map_for_output(&output)
|
let layers = layer_map_for_output(output)
|
||||||
.layers()
|
.layers()
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
layers.iter().for_each(|layer| {
|
layers.iter().for_each(|layer| {
|
||||||
layer.send_frame(
|
layer.send_frame(
|
||||||
&output,
|
output,
|
||||||
state.start_time.elapsed(),
|
state.start_time.elapsed(),
|
||||||
Some(Duration::ZERO),
|
Some(Duration::ZERO),
|
||||||
|_, _| Some(output.clone()),
|
|_, _| Some(output.clone()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
state.send_cursor_frame(&output);
|
state.send_cursor_frame(output);
|
||||||
state.space.refresh();
|
state.space.refresh();
|
||||||
state.popups.cleanup();
|
state.popups.cleanup();
|
||||||
layer_map_for_output(&output).cleanup();
|
layer_map_for_output(output).cleanup();
|
||||||
if let Err(error) = state.display_handle.flush_clients() {
|
if let Err(error) = state.display_handle.flush_clients() {
|
||||||
tracing::debug!(%error, "failed to flush clients after DRM frame");
|
tracing::debug!(%error, "failed to flush clients after DRM frame");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_output(drm: &DrmDevice) -> io::Result<(connector::Info, crtc::Handle, DrmMode)> {
|
fn select_outputs(drm: &DrmDevice) -> io::Result<Vec<(connector::Info, crtc::Handle, DrmMode)>> {
|
||||||
let resources = drm.resource_handles()?;
|
let resources = drm.resource_handles()?;
|
||||||
|
let mut selections = Vec::new();
|
||||||
|
let mut used_crtcs = HashSet::new();
|
||||||
|
|
||||||
for handle in resources.connectors() {
|
for handle in resources.connectors() {
|
||||||
let connector = drm.get_connector(*handle, true)?;
|
let connector = drm.get_connector(*handle, true)?;
|
||||||
@@ -456,26 +585,71 @@ fn select_output(drm: &DrmDevice) -> io::Result<(connector::Info, crtc::Handle,
|
|||||||
let current_crtc = connector
|
let current_crtc = connector
|
||||||
.current_encoder()
|
.current_encoder()
|
||||||
.and_then(|handle| drm.get_encoder(handle).ok())
|
.and_then(|handle| drm.get_encoder(handle).ok())
|
||||||
.and_then(|encoder| encoder.crtc());
|
.and_then(|encoder| encoder.crtc())
|
||||||
|
.filter(|crtc| !used_crtcs.contains(crtc));
|
||||||
let compatible_crtc = connector.encoders().iter().find_map(|handle| {
|
let compatible_crtc = connector.encoders().iter().find_map(|handle| {
|
||||||
let encoder = drm.get_encoder(*handle).ok()?;
|
let encoder = drm.get_encoder(*handle).ok()?;
|
||||||
resources
|
resources
|
||||||
.filter_crtcs(encoder.possible_crtcs())
|
.filter_crtcs(encoder.possible_crtcs())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.find(|crtc| !used_crtcs.contains(crtc))
|
||||||
});
|
});
|
||||||
if let Some(crtc) = current_crtc.or(compatible_crtc) {
|
if let Some(crtc) = current_crtc.or(compatible_crtc) {
|
||||||
return Ok((connector, crtc, mode));
|
used_crtcs.insert(crtc);
|
||||||
|
selections.push((connector, crtc, mode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if selections.is_empty() {
|
||||||
Err(io::Error::new(
|
Err(io::Error::new(
|
||||||
io::ErrorKind::NotFound,
|
io::ErrorKind::NotFound,
|
||||||
"no connected desktop DRM connector with a usable CRTC",
|
"no connected desktop DRM connector with a usable CRTC",
|
||||||
))
|
))
|
||||||
|
} else {
|
||||||
|
Ok(selections)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_output(state: &mut Ferese, connector: &connector::Info, mode: DrmMode) -> Output {
|
fn create_direct_output(
|
||||||
|
state: &mut Ferese,
|
||||||
|
drm: &mut DrmDevice,
|
||||||
|
gbm: &GbmDevice<DrmDeviceFd>,
|
||||||
|
renderer: &GlesRenderer,
|
||||||
|
connector: connector::Info,
|
||||||
|
crtc: crtc::Handle,
|
||||||
|
mode: DrmMode,
|
||||||
|
) -> Result<DirectOutput, Box<dyn Error>> {
|
||||||
|
let (output, global) = create_output(state, &connector, mode);
|
||||||
|
let drm_surface = drm.create_surface(crtc, mode, &[connector.handle()])?;
|
||||||
|
let allocator = GbmAllocator::new(
|
||||||
|
gbm.clone(),
|
||||||
|
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
|
||||||
|
);
|
||||||
|
let surface = GbmBufferedSurface::new(
|
||||||
|
drm_surface,
|
||||||
|
allocator,
|
||||||
|
&[Fourcc::Argb8888, Fourcc::Abgr8888],
|
||||||
|
renderer.dmabuf_formats(),
|
||||||
|
)?;
|
||||||
|
let damage_tracker = OutputDamageTracker::from_output(&output);
|
||||||
|
|
||||||
|
tracing::info!(?crtc, connector = %connector, "initialized DRM output");
|
||||||
|
Ok(DirectOutput {
|
||||||
|
connector: connector.handle(),
|
||||||
|
mode,
|
||||||
|
output,
|
||||||
|
global,
|
||||||
|
surface,
|
||||||
|
damage_tracker,
|
||||||
|
frame_pending: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_output(
|
||||||
|
state: &mut Ferese,
|
||||||
|
connector: &connector::Info,
|
||||||
|
mode: DrmMode,
|
||||||
|
) -> (Output, GlobalId) {
|
||||||
let name = connector.to_string();
|
let name = connector.to_string();
|
||||||
let identity = format!("drm:{name}");
|
let identity = format!("drm:{name}");
|
||||||
let physical_size = connector.size().unwrap_or((0, 0));
|
let physical_size = connector.size().unwrap_or((0, 0));
|
||||||
@@ -490,7 +664,7 @@ fn create_output(state: &mut Ferese, connector: &connector::Info, mode: DrmMode)
|
|||||||
);
|
);
|
||||||
let output_mode = OutputMode::from(mode);
|
let output_mode = OutputMode::from(mode);
|
||||||
|
|
||||||
output.create_global::<Ferese>(&state.display_handle);
|
let global = output.create_global::<Ferese>(&state.display_handle);
|
||||||
output.set_preferred(output_mode);
|
output.set_preferred(output_mode);
|
||||||
output.change_current_state(
|
output.change_current_state(
|
||||||
Some(output_mode),
|
Some(output_mode),
|
||||||
@@ -507,12 +681,20 @@ fn create_output(state: &mut Ferese, connector: &connector::Info, mode: DrmMode)
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
state.space.map_output(&output, (x, 0));
|
state.space.map_output(&output, (x, 0));
|
||||||
state.register_output(&output, identity);
|
state.register_output(&output, identity);
|
||||||
output
|
(output, global)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectBackendState {
|
impl DirectBackendState {
|
||||||
pub fn record_presentation(&mut self, time: DrmEventTime, sequence: u32) -> Option<Duration> {
|
pub fn record_presentation(
|
||||||
self.presentation.record(time, sequence)
|
&mut self,
|
||||||
|
node: DrmNode,
|
||||||
|
crtc: crtc::Handle,
|
||||||
|
time: DrmEventTime,
|
||||||
|
sequence: u32,
|
||||||
|
) -> Option<Duration> {
|
||||||
|
self.presentation
|
||||||
|
.get_mut(&(node, crtc))
|
||||||
|
.and_then(|clock| clock.record(time, sequence))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ pub(crate) struct FrameData {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct PendingScreencopy {
|
pub(crate) struct PendingScreencopy {
|
||||||
frame: ZwlrScreencopyFrameV1,
|
pub(crate) frame: ZwlrScreencopyFrameV1,
|
||||||
buffer: WlBuffer,
|
buffer: WlBuffer,
|
||||||
output: Output,
|
pub(crate) output: Output,
|
||||||
region: Rectangle<i32, Buffer>,
|
region: Rectangle<i32, Buffer>,
|
||||||
with_damage: bool,
|
with_damage: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-3
@@ -11,7 +11,11 @@ use ferese_core::{OutputGeometry, OutputId, OutputWorkspaceMap, WindowPlacement,
|
|||||||
use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId};
|
use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{allocator::dmabuf::Dmabuf, drm::DrmEventTime, renderer::ImportDma},
|
backend::{
|
||||||
|
allocator::dmabuf::Dmabuf,
|
||||||
|
drm::{DrmEventTime, DrmNode},
|
||||||
|
renderer::ImportDma,
|
||||||
|
},
|
||||||
desktop::{
|
desktop::{
|
||||||
LayerSurface, PopupManager, Space, Window, WindowSurfaceType, layer_map_for_output,
|
LayerSurface, PopupManager, Space, Window, WindowSurfaceType, layer_map_for_output,
|
||||||
utils::send_frames_surface_tree,
|
utils::send_frames_surface_tree,
|
||||||
@@ -23,6 +27,7 @@ use smithay::{
|
|||||||
output::Output,
|
output::Output,
|
||||||
reexports::{
|
reexports::{
|
||||||
calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction, generic::Generic},
|
calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction, generic::Generic},
|
||||||
|
drm::control::crtc,
|
||||||
wayland_protocols::xdg::{
|
wayland_protocols::xdg::{
|
||||||
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode,
|
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode,
|
||||||
shell::server::xdg_toplevel,
|
shell::server::xdg_toplevel,
|
||||||
@@ -158,6 +163,43 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unregister_output(&mut self, output: &Output) {
|
||||||
|
let Some(output_id) = self.output_ids.remove(output) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pending_screencopies.retain(|capture| {
|
||||||
|
if capture.output == *output {
|
||||||
|
capture.frame.failed();
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.space.unmap_output(output);
|
||||||
|
|
||||||
|
match self.output_workspaces.disconnect(output_id) {
|
||||||
|
Ok(Some(target)) => {
|
||||||
|
if let Some(workspace) = self.output_workspaces.active_workspace(target) {
|
||||||
|
self.activate_output_workspace(target, workspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
self.focused_window = None;
|
||||||
|
self.restore_keyboard_focus();
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::error!(%error, output = %output.name(), "failed to unregister output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.relayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_focused_output(&self, output: &Output) -> bool {
|
||||||
|
self.output_ids.get(output).copied() == self.output_workspaces.focused_output()
|
||||||
|
}
|
||||||
|
|
||||||
fn activate_output_workspace(&mut self, output: OutputId, workspace: ferese_core::WorkspaceId) {
|
fn activate_output_workspace(&mut self, output: OutputId, workspace: ferese_core::WorkspaceId) {
|
||||||
if let Err(error) = self.output_workspaces.focus_output(output) {
|
if let Err(error) = self.output_workspaces.focus_output(output) {
|
||||||
tracing::error!(%error, "failed to focus output");
|
tracing::error!(%error, "failed to focus output");
|
||||||
@@ -672,11 +714,17 @@ impl Ferese {
|
|||||||
self.advance_animations_by(delta)
|
self.advance_animations_by(delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn record_drm_presentation(&mut self, time: DrmEventTime, sequence: u32) {
|
pub fn record_drm_presentation(
|
||||||
|
&mut self,
|
||||||
|
node: DrmNode,
|
||||||
|
crtc: crtc::Handle,
|
||||||
|
time: DrmEventTime,
|
||||||
|
sequence: u32,
|
||||||
|
) {
|
||||||
let delta = self
|
let delta = self
|
||||||
.direct_backend
|
.direct_backend
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.and_then(|backend| backend.record_presentation(time, sequence));
|
.and_then(|backend| backend.record_presentation(node, crtc, time, sequence));
|
||||||
if let Some(delta) = delta {
|
if let Some(delta) = delta {
|
||||||
self.advance_animations_by(delta);
|
self.advance_animations_by(delta);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user