From f4e8d132f3669b69907390c382fc79d04ac2ae4d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 19 Feb 2025 16:30:07 +0100 Subject: [PATCH 1/7] toplevel: remove focus_node --- src/ifs/wl_seat/event_handling.rs | 6 +++--- src/ifs/wl_seat/tablet/tool.rs | 4 ++-- src/ifs/wl_surface.rs | 17 ++--------------- src/ifs/wl_surface/wl_subsurface.rs | 5 +++++ src/ifs/wl_surface/x_surface/xwindow.rs | 4 ++-- src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs | 4 ++-- src/tree/container.rs | 8 -------- src/tree/toplevel.rs | 17 +++-------------- 8 files changed, 19 insertions(+), 46 deletions(-) diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index d5f24ab5..29226d85 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -923,7 +923,7 @@ impl WlSeatGlobal { } pub fn focus_toplevel(self: &Rc, n: Rc) { - let node = match n.tl_focus_child(self.id) { + let node = match n.tl_focus_child() { Some(n) => n, _ => n.tl_into_node(), }; @@ -1153,7 +1153,7 @@ impl WlSeatGlobal { }); self.surface_pointer_frame(surface); if pressed { - if let Some(node) = surface.get_focus_node(self.id) { + if let Some(node) = surface.get_focus_node() { self.focus_node_with_serial(node, serial); } } @@ -1372,7 +1372,7 @@ impl WlSeatGlobal { self.surface_touch_event(Version::ALL, surface, |t| { t.send_down(serial, time, surface.id, id, x, y) }); - if let Some(node) = surface.get_focus_node(self.id) { + if let Some(node) = surface.get_focus_node() { self.focus_node_with_serial(node, serial); } } diff --git a/src/ifs/wl_seat/tablet/tool.rs b/src/ifs/wl_seat/tablet/tool.rs index fcda9cbf..6f6a8326 100644 --- a/src/ifs/wl_seat/tablet/tool.rs +++ b/src/ifs/wl_seat/tablet/tool.rs @@ -212,7 +212,7 @@ impl TabletTool { }); if state == ToolButtonState::Pressed { n.client.focus_stealing_serial.set(Some(serial.get())); - if let Some(node) = n.get_focus_node(self.tablet.seat.id) { + if let Some(node) = n.get_focus_node() { self.tablet.seat.focus_node_with_serial(node, serial.get()); } } @@ -261,7 +261,7 @@ impl TabletTool { if let Some(changes) = changes { if changes.down == Some(true) { n.client.focus_stealing_serial.set(Some(serial.get())); - if let Some(node) = n.get_focus_node(self.tablet.seat.id) { + if let Some(node) = n.get_focus_node() { self.tablet.seat.focus_node_with_serial(node, serial.get()); } } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 8559a0bb..8038ec82 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -777,9 +777,9 @@ impl WlSurface { Ok(cursor) } - pub fn get_focus_node(&self, seat: SeatId) -> Option> { + pub fn get_focus_node(&self) -> Option> { match self.toplevel.get() { - Some(tl) if tl.tl_accepts_keyboard_focus() => tl.tl_focus_child(seat), + Some(tl) if tl.tl_accepts_keyboard_focus() => tl.tl_focus_child(), Some(_) => None, _ => self.ext.get().focus_node(), } @@ -1591,18 +1591,6 @@ impl WlSurface { ss.surface.detach_node(set_invisible); } } - if let Some(tl) = self.toplevel.get() { - let data = tl.tl_data(); - let mut remove = vec![]; - for (seat, s) in data.focus_node.iter() { - if s.node_id() == self.node_id() { - remove.push(seat); - } - } - for seat in remove { - data.focus_node.remove(&seat); - } - } self.seat_state.destroy_node(self); if self.visible.get() && self.toplevel.is_none() { self.client.state.damage(self.buffer_abs_pos.get()); @@ -1802,7 +1790,6 @@ impl Node for WlSurface { fn node_on_focus(self: Rc, seat: &WlSeatGlobal) { if let Some(tl) = self.toplevel.get() { - tl.tl_data().focus_node.insert(seat.id(), self.clone()); tl.tl_on_activate(); } seat.focus_surface(&self); diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index d0baf383..d8dc29da 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -7,6 +7,7 @@ use { }, leaks::Tracker, object::{Object, Version}, + tree::Node, utils::{ clonecell::CloneCell, linkedlist::{LinkedNode, NodeRef}, @@ -375,6 +376,10 @@ impl SurfaceExt for WlSubsurface { Some(self) } + fn focus_node(&self) -> Option> { + self.parent.ext.get().focus_node() + } + fn consume_pending_child( &self, surface: &WlSurface, diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index b558ebe3..182ce5be 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -4,7 +4,7 @@ use { cursor::KnownCursor, fixed::Fixed, ifs::{ - wl_seat::{tablet::TabletTool, NodeSeatState, SeatId, WlSeatGlobal}, + wl_seat::{tablet::TabletTool, NodeSeatState, WlSeatGlobal}, wl_surface::{x_surface::XSurface, WlSurface, WlSurfaceError}, }, rect::Rect, @@ -406,7 +406,7 @@ impl ToplevelNodeBase for Xwindow { .push(XWaylandEvent::Activate(self.data.clone())); } - fn tl_focus_child(&self, _seat: SeatId) -> Option> { + fn tl_focus_child(&self) -> Option> { Some(self.x.surface.clone()) } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 3516d1ba..0a8e5ce9 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -9,7 +9,7 @@ use { fixed::Fixed, ifs::{ ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1, - wl_seat::{tablet::TabletTool, NodeSeatState, SeatId, WlSeatGlobal}, + wl_seat::{tablet::TabletTool, NodeSeatState, WlSeatGlobal}, wl_surface::{ xdg_surface::{ xdg_toplevel::xdg_dialog_v1::XdgDialogV1, XdgSurface, XdgSurfaceError, @@ -582,7 +582,7 @@ impl ToplevelNodeBase for XdgToplevel { } } - fn tl_focus_child(&self, _seat: SeatId) -> Option> { + fn tl_focus_child(&self) -> Option> { Some(self.xdg.surface.clone()) } diff --git a/src/tree/container.rs b/src/tree/container.rs index 2fef939a..6a9492f8 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -2059,14 +2059,6 @@ impl ToplevelNodeBase for ContainerNode { &self.toplevel_data } - fn tl_default_focus_child(&self) -> Option> { - self.focus_history - .last() - .map(|v| v.node.clone()) - .or_else(|| self.children.first().map(|c| c.node.clone())) - .map(|tl| tl.tl_into_node()) - } - fn tl_set_workspace_ext(&self, ws: &Rc) { for child in self.children.iter() { child.node.clone().tl_set_workspace(ws); diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 78c65a1f..1d327a3e 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -7,7 +7,7 @@ use { ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, jay_screencast::JayScreencast, jay_toplevel::JayToplevel, - wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState, SeatId}, + wl_seat::{collect_kb_foci, collect_kb_foci2, NodeSeatState}, wl_surface::WlSurface, }, rect::Rect, @@ -22,7 +22,6 @@ use { copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, numcell::NumCell, - smallmap::SmallMap, threshold_counter::ThresholdCounter, toplevel_identifier::{toplevel_identifier, ToplevelIdentifier}, }, @@ -172,10 +171,6 @@ impl ToplevelNode for T { pub trait ToplevelNodeBase: Node { fn tl_data(&self) -> &ToplevelData; - fn tl_default_focus_child(&self) -> Option> { - None - } - fn tl_accepts_keyboard_focus(&self) -> bool { true } @@ -188,11 +183,8 @@ pub trait ToplevelNodeBase: Node { // nothing } - fn tl_focus_child(&self, seat: SeatId) -> Option> { - self.tl_data() - .focus_node - .get(&seat) - .or_else(|| self.tl_default_focus_child()) + fn tl_focus_child(&self) -> Option> { + None } fn tl_set_workspace_ext(&self, ws: &Rc) { @@ -259,7 +251,6 @@ pub struct ToplevelData { pub client: Option>, pub state: Rc, pub active_surfaces: ThresholdCounter, - pub focus_node: SmallMap, 1>, pub visible: Cell, pub is_floating: Cell, pub float_width: Cell, @@ -300,7 +291,6 @@ impl ToplevelData { client, state: state.clone(), active_surfaces: Default::default(), - focus_node: Default::default(), visible: Cell::new(false), is_floating: Default::default(), float_width: Default::default(), @@ -393,7 +383,6 @@ impl ToplevelData { } self.workspace.take(); self.seat_state.destroy_node(node); - self.focus_node.clear(); } pub fn broadcast(&self, toplevel: Rc) { From 52c9fac23bef41734da6b1846796d79262853733 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 19 Feb 2025 16:39:04 +0100 Subject: [PATCH 2/7] xwayland: improve damage tracking --- src/ifs/wl_surface.rs | 10 +++++-- src/ifs/wl_surface/x_surface.rs | 11 +++++++- src/ifs/wl_surface/x_surface/xwindow.rs | 36 ++++++++++++++----------- src/tree/toplevel.rs | 4 --- src/xwayland/xwm.rs | 1 + 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 8038ec82..605a9fe7 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -1789,8 +1789,14 @@ impl Node for WlSurface { } fn node_on_focus(self: Rc, seat: &WlSeatGlobal) { - if let Some(tl) = self.toplevel.get() { - tl.tl_on_activate(); + if let Some(xsurface) = self.ext.get().into_xsurface() { + if let Some(window) = xsurface.xwindow.get() { + self.client + .state + .xwayland + .queue + .push(XWaylandEvent::Activate(window.data.clone())); + } } seat.focus_surface(&self); } diff --git a/src/ifs/wl_surface/x_surface.rs b/src/ifs/wl_surface/x_surface.rs index eabfbf65..6ec00063 100644 --- a/src/ifs/wl_surface/x_surface.rs +++ b/src/ifs/wl_surface/x_surface.rs @@ -5,7 +5,7 @@ use { SurfaceExt, WlSurface, WlSurfaceError, }, leaks::Tracker, - tree::ToplevelNode, + tree::{Node, ToplevelNode, ToplevelNodeBase}, utils::clonecell::CloneCell, xwayland::XWaylandEvent, }, @@ -58,6 +58,15 @@ impl SurfaceExt for XSurface { } } + fn focus_node(&self) -> Option> { + if let Some(xwindow) = self.xwindow.get() { + if xwindow.tl_accepts_keyboard_focus() { + return Some(xwindow.x.surface.clone()); + } + } + None + } + fn into_xsurface(self: Rc) -> Option> { Some(self) } diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index 182ce5be..d182a543 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -222,7 +222,7 @@ impl Xwindow { } }); slf.x.xwindow.set(Some(slf.clone())); - slf.x.surface.set_toplevel(Some(slf.clone())); + slf.update_toplevel(); Ok(slf) } @@ -296,9 +296,25 @@ impl Xwindow { Change::None => {} } self.data.state.tree_changed(); - if override_redirect { - self.data.state.damage(self.data.info.pending_extents.get()); + self.damage_override_redirect(); + } + + fn damage_override_redirect(&self) { + if !self.data.info.override_redirect.get() { + return; } + let extents = self.x.surface.extents.get(); + let (x, y) = self.x.surface.buffer_abs_pos.get().position(); + let extents = extents.move_(x, y); + self.data.state.damage(extents); + } + + pub fn update_toplevel(self: &Rc) { + let mut toplevel = None; + if !self.data.info.override_redirect.get() { + toplevel = Some(self.clone() as _); + } + self.x.surface.set_toplevel(toplevel); } } @@ -398,14 +414,6 @@ impl ToplevelNodeBase for Xwindow { && self.data.info.input_model.get() != XInputModel::None } - fn tl_on_activate(&self) { - self.data - .state - .xwayland - .queue - .push(XWaylandEvent::Activate(self.data.clone())); - } - fn tl_focus_child(&self) -> Option> { Some(self.x.surface.clone()) } @@ -419,8 +427,6 @@ impl ToplevelNodeBase for Xwindow { let old = self.data.info.extents.replace(*rect); if old != *rect { if self.data.info.override_redirect.get() { - self.data.state.damage(old); - self.data.state.damage(*rect); let (x, y) = rect.center(); let output = self.data.state.find_closest_output(x, y).0; self.x.surface.set_output(&output); @@ -482,9 +488,7 @@ impl StackedNode for Xwindow { stacked_node_impl!(); fn stacked_set_visible(&self, visible: bool) { - let extents = self.x.surface.extents.get(); - let (x, y) = self.x.surface.buffer_abs_pos.get().position(); - self.data.state.damage(extents.move_(x, y)); + self.damage_override_redirect(); self.tl_set_visible(visible); } diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 1d327a3e..eb274852 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -179,10 +179,6 @@ pub trait ToplevelNodeBase: Node { let _ = active; } - fn tl_on_activate(&self) { - // nothing - } - fn tl_focus_child(&self) -> Option> { None } diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 9fb7f510..881b47aa 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -2115,6 +2115,7 @@ impl Wm { // log::info!("xwin {} or {}", data.window_id, or); if let Some(window) = data.window.get() { window.tl_destroy(); + window.update_toplevel(); window.map_status_changed(); } } From 5ae1742075154cdbfc1cfbed9a91bba3ef8e72d9 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 19 Feb 2025 18:09:27 +0100 Subject: [PATCH 3/7] text-input: improve damage tracking --- .../wl_seat/text_input/zwp_input_method_v2.rs | 1 + .../wl_surface/zwp_input_popup_surface_v2.rs | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs b/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs index 7d25398e..52239870 100644 --- a/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs +++ b/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs @@ -174,6 +174,7 @@ impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 { version: self.version, tracker: Default::default(), positioning_scheduled: Cell::new(false), + was_on_screen: Default::default(), }); track!(self.client, popup); self.client.add_client_obj(&popup)?; diff --git a/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs b/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs index 5cdc5eba..4f246b23 100644 --- a/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs +++ b/src/ifs/wl_surface/zwp_input_popup_surface_v2.rs @@ -23,6 +23,7 @@ pub struct ZwpInputPopupSurfaceV2 { pub version: Version, pub tracker: Tracker, pub positioning_scheduled: Cell, + pub was_on_screen: Cell, } impl SurfaceExt for ZwpInputPopupSurfaceV2 { @@ -57,6 +58,7 @@ impl ZwpInputPopupSurfaceV2 { && self.client.state.root_visible(); self.surface.set_visible(is_visible); if was_visible != is_visible { + self.was_on_screen.set(false); if is_visible { self.schedule_positioning(); } else { @@ -107,12 +109,16 @@ impl ZwpInputPopupSurfaceV2 { rect = rect2; } } - self.surface.buffer_abs_pos.set( - self.surface - .buffer_abs_pos - .get() - .at_point(rect.x1() - extents.x1(), rect.y1() - extents.y1()), - ); + let old = self.surface.buffer_abs_pos.get(); + let new = old.at_point(rect.x1() - extents.x1(), rect.y1() - extents.y1()); + if self.was_on_screen.get() && new != old { + self.damage(); + } + self.surface.buffer_abs_pos.set(new); + if !self.was_on_screen.get() || new != old { + self.damage(); + } + self.was_on_screen.set(true); } pub fn install(self: &Rc) -> Result<(), ZwpInputPopupSurfaceV2Error> { From 5932ec14a7b9192f9c914f984cbadd15f7c55775 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 20 Feb 2025 12:22:14 +0100 Subject: [PATCH 4/7] wl_subsurface: improve damage tracking --- src/ifs/wl_surface/wl_subsurface.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ifs/wl_surface/wl_subsurface.rs b/src/ifs/wl_surface/wl_subsurface.rs index d8dc29da..8fe4ce4e 100644 --- a/src/ifs/wl_surface/wl_subsurface.rs +++ b/src/ifs/wl_surface/wl_subsurface.rs @@ -258,6 +258,18 @@ impl WlSubsurface { } Ok(()) } + + fn damage(&self) { + if !self.surface.visible.get() { + return; + } + let (x, y) = self.surface.buffer_abs_pos.get().position(); + let mut rect = self.surface.extents.get().move_(x, y); + if let Some(tl) = self.surface.toplevel.get() { + rect = rect.intersect(tl.node_absolute_position()); + } + self.surface.client.state.damage(rect); + } } impl WlSubsurfaceRequestHandler for WlSubsurface { @@ -353,12 +365,13 @@ impl SurfaceExt for WlSubsurface { if self.had_buffer.replace(has_buffer) != has_buffer { if has_buffer { if self.parent.visible.get() { - let (x, y) = self.surface.buffer_abs_pos.get().position(); - let extents = self.surface.extents.get(); - self.surface.client.state.damage(extents.move_(x, y)); self.surface.set_visible(true); + self.damage(); } } else { + if self.surface.toplevel.is_some() { + self.damage(); + } self.surface.destroy_node(); } } From f80ac20220507789b9db65fd7377f45d6b543fcc Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 19 Feb 2025 19:34:28 +0100 Subject: [PATCH 5/7] render: bound async title textures to their rect --- src/renderer.rs | 12 ++++++++---- src/tree/container.rs | 15 +++------------ src/tree/float.rs | 7 +++++-- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/renderer.rs b/src/renderer.rs index f4f37cd4..4a1a6028 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -253,7 +253,9 @@ impl Renderer<'_> { } if let Some(titles) = rd.titles.get(&self.base.scale) { for title in titles { - let (x, y) = self.base.scale_point(x + title.x, y + title.y); + let rect = title.rect.move_(x, y); + let bounds = self.base.scale_rect(rect); + let (x, y) = self.base.scale_point(rect.x1(), rect.y1()); self.base.render_texture( &title.tex, None, @@ -262,7 +264,7 @@ impl Renderer<'_> { None, None, self.base.scale, - None, + Some(&bounds), None, AcquireSync::None, ReleaseSync::None, @@ -486,7 +488,9 @@ impl Renderer<'_> { self.base.fill_boxes(&title_underline, &uc); if let Some(title) = floating.title_textures.borrow().get(&self.base.scale) { if let Some(texture) = title.texture() { - let (x, y) = self.base.scale_point(x + bw, y + bw); + let rect = floating.title_rect.get().move_(x, y); + let bounds = self.base.scale_rect(rect); + let (x, y) = self.base.scale_point(rect.x1(), rect.y1()); self.base.render_texture( &texture, None, @@ -495,7 +499,7 @@ impl Renderer<'_> { None, None, self.base.scale, - None, + Some(&bounds), None, AcquireSync::None, ReleaseSync::None, diff --git a/src/tree/container.rs b/src/tree/container.rs index 6a9492f8..a60a9651 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -92,8 +92,7 @@ pub enum ContainerFocus { tree_id!(ContainerNodeId); pub struct ContainerTitle { - pub x: i32, - pub y: i32, + pub rect: Rect, pub tex: Rc, } @@ -765,11 +764,7 @@ impl ContainerNode { } if let Some(tex) = tex.texture() { let titles = rd.titles.get_or_default_mut(*scale); - titles.push(ContainerTitle { - x: rect.x1(), - y: rect.y1(), - tex, - }) + titles.push(ContainerTitle { rect, tex }) } } } @@ -840,11 +835,7 @@ impl ContainerNode { for (scale, tex) in tt { if let Some(tex) = tex.texture() { let titles = rd.titles.get_or_default_mut(*scale); - titles.push(ContainerTitle { - x: rect.x1(), - y: rect.y1(), - tex, - }) + titles.push(ContainerTitle { rect, tex }) } } } diff --git a/src/tree/float.rs b/src/tree/float.rs index bf6fc2c9..5d42e1e1 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -47,6 +47,7 @@ pub struct FloatNode { pub seat_state: NodeSeatState, pub layout_scheduled: Cell, pub render_titles_scheduled: Cell, + pub title_rect: Cell, pub title: RefCell, pub title_textures: RefCell>, cursors: RefCell>, @@ -124,6 +125,7 @@ impl FloatNode { seat_state: Default::default(), layout_scheduled: Cell::new(false), render_titles_scheduled: Cell::new(false), + title_rect: Default::default(), title: Default::default(), title_textures: Default::default(), cursors: Default::default(), @@ -174,7 +176,9 @@ impl FloatNode { (pos.height() - 2 * bw - th - 1).max(0), ) .unwrap(); + let tr = Rect::new_sized(bw, bw, (pos.width() - 2 * bw).max(0), th).unwrap(); child.clone().tl_change_extents(&cpos); + self.title_rect.set(tr); self.layout_scheduled.set(false); self.schedule_render_titles(); } @@ -188,7 +192,6 @@ impl FloatNode { fn render_title_phase1(&self) -> Rc { let on_completed = Rc::new(OnDropEvent::default()); let theme = &self.state.theme; - let th = theme.sizes.title_height.get(); let tc = match self.active.get() { true => theme.colors.focused_title_text.get(), false => theme.colors.unfocused_title_text.get(), @@ -205,7 +208,7 @@ impl FloatNode { _ => return on_completed.event(), }; let scales = self.state.scales.lock(); - let tr = Rect::new_sized(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th).unwrap(); + let tr = self.title_rect.get(); let tt = &mut *self.title_textures.borrow_mut(); for (scale, _) in scales.iter() { let tex = From 07fb198eb4d65162cae6e70e4b2cff0ab8d6b760 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 18 Feb 2025 16:43:30 +0100 Subject: [PATCH 6/7] metal: track per-framebuffer damage --- src/backends/metal/present.rs | 60 +++++++--- src/backends/metal/video.rs | 32 ++++- src/compositor.rs | 2 + src/damage.rs | 150 +++++++++++++++++++++++- src/gfx_api.rs | 27 ++++- src/gfx_apis/gl/renderer/framebuffer.rs | 3 +- src/gfx_apis/vulkan/image.rs | 5 +- src/gfx_apis/vulkan/renderer.rs | 2 + src/ifs/wl_output.rs | 47 +++++++- src/ifs/wl_surface.rs | 112 +----------------- src/it/test_gfx_api.rs | 3 +- src/rect.rs | 5 + src/rect/region.rs | 14 ++- src/state.rs | 3 + src/tasks/connector.rs | 2 + src/tree/output.rs | 8 +- src/utils/transform_ext.rs | 10 ++ 17 files changed, 334 insertions(+), 151 deletions(-) diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index 279f44f0..25d9f3dc 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -11,6 +11,7 @@ use { create_render_pass, AcquireSync, BufferResv, GfxApiOpt, GfxRenderPass, GfxTexture, ReleaseSync, SyncFile, }, + rect::Region, theme::Color, time::Time, tracy::FrameName, @@ -30,7 +31,8 @@ use { struct Latched { pass: GfxRenderPass, - damage: u64, + damage_count: u64, + damage: Region, } #[derive(Debug)] @@ -172,24 +174,24 @@ impl MetalConnector { Some(b) => b, _ => return Ok(()), }; + let buffer = &buffers[self.next_buffer.get() % buffers.len()]; if self.has_damage.get() > 0 || self.cursor_damage.get() { node.schedule.commit_cursor(); } self.latch_cursor(&node)?; let cursor_programming = self.compute_cursor_programming(); - let latched = self.latch(&node); + let latched = self.latch(&node, buffer); node.latched(self.try_async_flip()); if cursor_programming.is_none() && latched.is_none() { return Ok(()); } - let buffer = &buffers[self.next_buffer.get() % buffers.len()]; let mut present_fb = None; let mut direct_scanout_id = None; if let Some(latched) = &latched { - let fb = self.prepare_present_fb(buffer, &plane, &latched.pass, true)?; + let fb = self.prepare_present_fb(buffer, &plane, latched, true)?; direct_scanout_id = fb.direct_scanout_data.as_ref().map(|d| d.dma_buf_id); present_fb = Some(fb); } @@ -212,12 +214,8 @@ impl MetalConnector { ); if res.is_err() { if let Some(dsd_id) = direct_scanout_id { - let fb = self.prepare_present_fb( - buffer, - &plane, - &latched.as_ref().unwrap().pass, - false, - )?; + let fb = + self.prepare_present_fb(buffer, &plane, latched.as_ref().unwrap(), false)?; present_fb = Some(fb); self.await_present_fb(present_fb.as_mut()).await; res = self.program_connector( @@ -241,7 +239,14 @@ impl MetalConnector { } } } + let reset_damage = || { + for buffer in &*buffers { + buffer.damage_queue.clear(); + } + buffers[0].damage_full(); + }; if let Err(e) = res { + reset_damage(); if let MetalError::Commit(DrmError::Atomic(OsError(c::EACCES))) = e { log::debug!("Could not perform atomic commit, likely because we're no longer the DRM master"); return Ok(()); @@ -265,7 +270,10 @@ impl MetalConnector { self.presentation_is_zero_copy .set(fb.direct_scanout_data.is_some()); if fb.direct_scanout_data.is_none() { + buffer.damage_queue.clear(); self.next_buffer.fetch_add(1); + } else { + reset_damage(); } self.next_framebuffer.set(Some(fb)); } @@ -275,7 +283,7 @@ impl MetalConnector { } self.can_present.set(false); if let Some(latched) = latched { - self.has_damage.fetch_sub(latched.damage); + self.has_damage.fetch_sub(latched.damage_count); } self.cursor_changed.set(false); Ok(()) @@ -487,12 +495,19 @@ impl MetalConnector { Some(programming) } - fn latch(&self, node: &Rc) -> Option { - let damage = self.has_damage.get(); - if damage == 0 { + fn latch(&self, node: &Rc, buffer: &RenderBuffer) -> Option { + let damage_count = self.has_damage.get(); + if damage_count == 0 { return None; } node.global.connector.damaged.set(false); + let damage = { + node.global.add_visualizer_damage(); + let damage = &mut *node.global.connector.damage.borrow_mut(); + buffer.damage_queue.damage(damage); + damage.clear(); + buffer.damage_queue.get() + }; let render_hw_cursor = !self.cursor_enabled.get(); let mode = node.global.mode.get(); let pass = create_render_pass( @@ -508,7 +523,11 @@ impl MetalConnector { node.global.persistent.transform.get(), Some(&self.state.damage_visualizer), ); - Some(Latched { pass, damage }) + Some(Latched { + pass, + damage_count, + damage, + }) } fn trim_scanout_cache(&self) { @@ -692,7 +711,7 @@ impl MetalConnector { &self, buffer: &RenderBuffer, plane: &Rc, - pass: &GfxRenderPass, + latched: &Latched, try_direct_scanout: bool, ) -> Result { self.trim_scanout_cache(); @@ -706,7 +725,7 @@ impl MetalConnector { && self.dev.is_render_device(); let mut direct_scanout_data = None; if try_direct_scanout { - direct_scanout_data = self.prepare_direct_scanout(&pass, plane); + direct_scanout_data = self.prepare_direct_scanout(&latched.pass, plane); } let direct_scanout_active = direct_scanout_data.is_some(); if self.direct_scanout_active.replace(direct_scanout_active) != direct_scanout_active { @@ -723,7 +742,12 @@ impl MetalConnector { None => { let sf = buffer .render_fb() - .perform_render_pass(AcquireSync::Unnecessary, ReleaseSync::Explicit, pass) + .perform_render_pass( + AcquireSync::Unnecessary, + ReleaseSync::Explicit, + &latched.pass, + &latched.damage, + ) .map_err(MetalError::RenderFrame)?; sync_file = buffer.copy_to_dev(sf)?; fb = buffer.drm.clone(); diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 61657522..a2069903 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -25,6 +25,7 @@ use { wl_output::OutputId, wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY}, }, + rect::{DamageQueue, Rect}, state::State, tree::OutputNode, udev::UdevDevice, @@ -2600,12 +2601,26 @@ impl MetalBackend { ctx: &MetalRenderContext, cursor: bool, ) -> Result<[RenderBuffer; N], MetalError> { - let create = - || self.create_scanout_buffer(dev, format, plane_modifiers, width, height, ctx, cursor); + let mut damage_queue = ArrayVec::from(DamageQueue::new::()); + let mut create = || { + self.create_scanout_buffer( + dev, + format, + plane_modifiers, + width, + height, + ctx, + cursor, + damage_queue.pop().unwrap(), + ) + }; let mut array = ArrayVec::<_, N>::new(); for _ in 0..N { array.push(create()?); } + if let Some(buffer) = array.first() { + buffer.damage_full(); + } Ok(array.into_inner().unwrap()) } @@ -2618,6 +2633,7 @@ impl MetalBackend { height: i32, render_ctx: &MetalRenderContext, cursor: bool, + damage_queue: DamageQueue, ) -> Result { let ctx = dev.ctx.get(); let dev_gfx_formats = ctx.gfx.formats(); @@ -2746,7 +2762,8 @@ impl MetalBackend { }; Ok(RenderBuffer { drm: drm_fb, - _dev_bo: dev_bo, + damage_queue, + dev_bo, _render_bo: render_bo, dev_fb, dev_tex, @@ -2970,7 +2987,8 @@ impl MetalBackend { #[derive(Debug)] pub struct RenderBuffer { pub drm: Rc, - pub _dev_bo: GbmBo, + pub damage_queue: DamageQueue, + pub dev_bo: GbmBo, pub _render_bo: Option, // ctx = dev // buffer location = dev @@ -3010,6 +3028,12 @@ impl RenderBuffer { ) .map_err(MetalError::CopyToOutput) } + + pub fn damage_full(&self) { + let dmabuf = self.dev_bo.dmabuf(); + let rect = Rect::new_sized_unchecked(0, 0, dmabuf.width, dmabuf.height); + self.damage_queue.damage(&[rect]); + } } fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool { diff --git a/src/compositor.rs b/src/compositor.rs index 28a9a905..1467b0c0 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -544,7 +544,9 @@ fn create_dummy_output(state: &Rc) { drm_dev: None, async_event: Default::default(), damaged: Cell::new(false), + damage: Default::default(), needs_vblank_emulation: Cell::new(false), + damage_intersect: Default::default(), }); let schedule = Rc::new(OutputSchedule::new( &state.ring, diff --git a/src/damage.rs b/src/damage.rs index ec54877f..24e4bef2 100644 --- a/src/damage.rs +++ b/src/damage.rs @@ -1,14 +1,19 @@ use { crate::{ async_engine::AsyncEngine, + fixed::Fixed, + ifs::wl_output::WlOutputGlobal, rect::{Rect, Region}, renderer::renderer_base::RendererBase, state::State, theme::Color, time::Time, - utils::{asyncevent::AsyncEvent, errorfmt::ErrorFmt, timer::TimerFd}, + utils::{ + asyncevent::AsyncEvent, errorfmt::ErrorFmt, timer::TimerFd, transform_ext::TransformExt, + }, }, isnt::std_1::primitive::IsntSliceExt, + jay_config::video::Transform, std::{ cell::{Cell, RefCell}, collections::VecDeque, @@ -56,6 +61,9 @@ pub async fn visualize_damage(state: Rc) { fn damage_all(state: &State) { for connector in state.connectors.lock().values() { if connector.connected.get() { + let damage = &mut *connector.damage.borrow_mut(); + damage.clear(); + damage.push(connector.damage_intersect.get()); connector.damage(); } } @@ -126,10 +134,7 @@ impl DamageVisualizer { self.color.set(color); } - pub fn render(&self, cursor_rect: &Rect, renderer: &mut RendererBase<'_>) { - if !self.enabled.get() { - return; - } + fn trim(&self) { let now = self.eng.now(); let entries = &mut *self.entries.borrow_mut(); let decay = self.decay.get(); @@ -140,6 +145,16 @@ impl DamageVisualizer { break; } } + } + + pub fn render(&self, cursor_rect: &Rect, renderer: &mut RendererBase<'_>) { + if !self.enabled.get() { + return; + } + self.trim(); + let now = self.eng.now(); + let entries = &*self.entries.borrow(); + let decay = self.decay.get(); let base_color = self.color.get(); let mut used = Region::empty(); let dx = -cursor_rect.x1(); @@ -156,4 +171,129 @@ impl DamageVisualizer { } } } + + pub fn copy_damage(&self, output: &WlOutputGlobal) { + if !self.enabled.get() { + return; + } + self.trim(); + let entries = &*self.entries.borrow(); + let pos = output.pos.get(); + for entry in entries { + if entry.rect.intersects(&pos) { + output.add_damage_area(&entry.rect); + } + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct DamageMatrix { + transform: Transform, + mx: f64, + my: f64, + dx: f64, + dy: f64, + smear: i32, +} + +impl Default for DamageMatrix { + fn default() -> Self { + Self { + transform: Default::default(), + mx: 1.0, + my: 1.0, + dx: 0.0, + dy: 0.0, + smear: 0, + } + } +} + +impl DamageMatrix { + pub fn apply(&self, dx: i32, dy: i32, rect: Rect) -> Rect { + let x1 = rect.x1() - self.smear; + let x2 = rect.x2() + self.smear; + let y1 = rect.y1() - self.smear; + let y2 = rect.y2() + self.smear; + let [x1, y1, x2, y2] = match self.transform { + Transform::None => [x1, y1, x2, y2], + Transform::Rotate90 => [-y2, x1, -y1, x2], + Transform::Rotate180 => [-x2, -y2, -x1, -y1], + Transform::Rotate270 => [y1, -x2, y2, -x1], + Transform::Flip => [-x2, y1, -x1, y2], + Transform::FlipRotate90 => [y1, x1, y2, x2], + Transform::FlipRotate180 => [x1, -y2, x2, -y1], + Transform::FlipRotate270 => [-y2, -x2, -y1, -x1], + }; + let x1 = (x1 as f64 * self.mx + self.dx).floor() as i32 + dx; + let y1 = (y1 as f64 * self.my + self.dy).floor() as i32 + dy; + let x2 = (x2 as f64 * self.mx + self.dx).ceil() as i32 + dx; + let y2 = (y2 as f64 * self.my + self.dy).ceil() as i32 + dy; + Rect::new(x1, y1, x2, y2).unwrap() + } + + pub fn new( + transform: Transform, + legacy_scale: i32, + buffer_width: i32, + buffer_height: i32, + viewport: Option<[Fixed; 4]>, + dst_width: i32, + dst_height: i32, + ) -> DamageMatrix { + let mut buffer_width = buffer_width as f64; + let mut buffer_height = buffer_height as f64; + let dst_width = dst_width as f64; + let dst_height = dst_height as f64; + + let mut mx = 1.0; + let mut my = 1.0; + if legacy_scale != 1 { + let scale_inv = 1.0 / (legacy_scale as f64); + mx = scale_inv; + my = scale_inv; + buffer_width *= scale_inv; + buffer_height *= scale_inv; + } + let (mut buffer_width, mut buffer_height) = + transform.maybe_swap((buffer_width, buffer_height)); + let (mut dx, mut dy) = match transform { + Transform::None => (0.0, 0.0), + Transform::Rotate90 => (buffer_width, 0.0), + Transform::Rotate180 => (buffer_width, buffer_height), + Transform::Rotate270 => (0.0, buffer_height), + Transform::Flip => (buffer_width, 0.0), + Transform::FlipRotate90 => (0.0, 0.0), + Transform::FlipRotate180 => (0.0, buffer_height), + Transform::FlipRotate270 => (buffer_width, buffer_height), + }; + if let Some([x, y, w, h]) = viewport { + dx -= x.to_f64(); + dy -= y.to_f64(); + buffer_width = w.to_f64(); + buffer_height = h.to_f64(); + } + let mut smear = false; + if dst_width != buffer_width { + let scale = dst_width / buffer_width; + mx *= scale; + dx *= scale; + smear |= dst_width > buffer_width; + } + if dst_height != buffer_height { + let scale = dst_height / buffer_height; + my *= scale; + dy *= scale; + smear |= dst_height > buffer_height; + } + DamageMatrix { + transform, + mx, + my, + dx, + dy, + smear: smear as _, + } + } } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index ded86452..3176a0c5 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -257,12 +257,28 @@ pub enum ResetStatus { pub trait GfxFramebuffer: Debug { fn physical_size(&self) -> (i32, i32); + fn full_region(&self) -> Region { + let (width, height) = self.physical_size(); + Region::new2(Rect::new_sized_unchecked(0, 0, width, height)) + } + fn render( &self, acquire_sync: AcquireSync, release_sync: ReleaseSync, ops: &[GfxApiOpt], clear: Option<&Color>, + ) -> Result, GfxError> { + self.render_with_region(acquire_sync, release_sync, ops, clear, &self.full_region()) + } + + fn render_with_region( + &self, + acquire_sync: AcquireSync, + release_sync: ReleaseSync, + ops: &[GfxApiOpt], + clear: Option<&Color>, + region: &Region, ) -> Result, GfxError>; fn format(&self) -> &'static Format; @@ -395,8 +411,15 @@ impl dyn GfxFramebuffer { acquire_sync: AcquireSync, release_sync: ReleaseSync, pass: &GfxRenderPass, + region: &Region, ) -> Result, GfxError> { - self.render(acquire_sync, release_sync, &pass.ops, pass.clear.as_ref()) + self.render_with_region( + acquire_sync, + release_sync, + &pass.ops, + pass.clear.as_ref(), + region, + ) } pub fn render_output( @@ -451,7 +474,7 @@ impl dyn GfxFramebuffer { transform, None, ); - self.perform_render_pass(acquire_sync, release_sync, &pass) + self.perform_render_pass(acquire_sync, release_sync, &pass, &self.full_region()) } pub fn render_hardware_cursor( diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 190e47e2..810c81b8 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -99,12 +99,13 @@ impl GfxFramebuffer for Framebuffer { (self.gl.width, self.gl.height) } - fn render( + fn render_with_region( &self, acquire_sync: AcquireSync, _release_sync: ReleaseSync, ops: &[GfxApiOpt], clear: Option<&Color>, + _region: &Region, ) -> Result, GfxError> { self.render(acquire_sync, ops, clear).map_err(|e| e.into()) } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 088be323..5fe04966 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -530,15 +530,16 @@ impl GfxFramebuffer for VulkanImage { (self.width as _, self.height as _) } - fn render( + fn render_with_region( &self, acquire_sync: AcquireSync, release_sync: ReleaseSync, ops: &[GfxApiOpt], clear: Option<&Color>, + region: &Region, ) -> Result, GfxError> { self.renderer - .execute(self, acquire_sync, release_sync, ops, clear) + .execute(self, acquire_sync, release_sync, ops, clear, region) .map_err(|e| e.into()) } diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 3b06b10d..d3715426 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -27,6 +27,7 @@ use { VulkanError, }, io_uring::IoUring, + rect::Region, theme::Color, utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack}, video::dmabuf::{dma_buf_export_sync_file, DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE}, @@ -965,6 +966,7 @@ impl VulkanRenderer { fb_release_sync: ReleaseSync, opts: &[GfxApiOpt], clear: Option<&Color>, + _region: &Region, ) -> Result, VulkanError> { zone!("execute"); let res = self.try_execute(fb, fb_acquire_sync, fb_release_sync, opts, clear); diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 82c10f83..ec56fa92 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -4,6 +4,7 @@ use { crate::{ backend, client::{Client, ClientError, ClientId}, + damage::DamageMatrix, format::{Format, XRGB8888}, globals::{Global, GlobalName}, ifs::{wl_surface::WlSurface, zxdg_output_v1::ZxdgOutputV1}, @@ -55,7 +56,7 @@ const MODE_PREFERRED: u32 = 2; pub struct WlOutputGlobal { pub name: GlobalName, - pub _state: Rc, + pub state: Rc, pub connector: Rc, pub pos: Cell, pub output_id: Rc, @@ -71,6 +72,7 @@ pub struct WlOutputGlobal { pub legacy_scale: Cell, pub persistent: Rc, pub opt: Rc, + pub damage_matrix: Cell, } #[derive(Default)] @@ -151,9 +153,9 @@ impl WlOutputGlobal { persistent_state.transform.get(), scale, ); - Self { + let global = Self { name, - _state: state.clone(), + state: state.clone(), connector: connector.clone(), pos: Cell::new(Rect::new_sized(x, y, width, height).unwrap()), output_id: output_id.clone(), @@ -169,7 +171,10 @@ impl WlOutputGlobal { legacy_scale: Cell::new(scale.round_up()), persistent: persistent_state.clone(), opt: Default::default(), - } + damage_matrix: Default::default(), + }; + global.update_damage_matrix(); + global } pub fn position(&self) -> Rect { @@ -253,6 +258,40 @@ impl WlOutputGlobal { .get() .maybe_swap((mode.width, mode.height)) } + + pub fn update_damage_matrix(&self) { + let pos = self.pos.get(); + let mode = self.mode.get(); + let matrix = DamageMatrix::new( + self.persistent.transform.get().inverse(), + 1, + pos.width(), + pos.height(), + None, + mode.width, + mode.height, + ); + self.damage_matrix.set(matrix); + self.connector + .damage_intersect + .set(Rect::new_sized_unchecked(0, 0, mode.width, mode.height)); + } + + pub fn add_damage_area(&self, area: &Rect) { + let pos = self.pos.get(); + let rect = area.move_(-pos.x1(), -pos.y1()); + let mut rect = self.damage_matrix.get().apply(0, 0, rect); + let damage = &mut *self.connector.damage.borrow_mut(); + const MAX_CONNECTOR_DAMAGE: usize = 32; + if damage.len() >= MAX_CONNECTOR_DAMAGE { + rect = rect.union(damage.pop().unwrap()); + } + damage.push(rect.intersect(self.connector.damage_intersect.get())); + } + + pub fn add_visualizer_damage(&self) { + self.state.damage_visualizer.copy_damage(self); + } } global_base!(WlOutputGlobal, WlOutput, WlOutputError); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 605a9fe7..27727807 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -23,6 +23,7 @@ use { backend::KeyState, client::{Client, ClientError}, cursor_user::{CursorUser, CursorUserId}, + damage::DamageMatrix, drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::{ @@ -2027,117 +2028,6 @@ efrom!(WlSurfaceError, XdgSurfaceError); efrom!(WlSurfaceError, ZwlrLayerSurfaceV1Error); efrom!(WlSurfaceError, CommitTimelineError); -#[derive(Copy, Clone, Debug)] -struct DamageMatrix { - transform: Transform, - mx: f64, - my: f64, - dx: f64, - dy: f64, - smear: i32, -} - -impl Default for DamageMatrix { - fn default() -> Self { - Self { - transform: Default::default(), - mx: 1.0, - my: 1.0, - dx: 0.0, - dy: 0.0, - smear: 0, - } - } -} - -impl DamageMatrix { - fn apply(&self, dx: i32, dy: i32, rect: Rect) -> Rect { - let x1 = rect.x1() - self.smear; - let x2 = rect.x2() + self.smear; - let y1 = rect.y1() - self.smear; - let y2 = rect.y2() + self.smear; - let [x1, y1, x2, y2] = match self.transform { - Transform::None => [x1, y1, x2, y2], - Transform::Rotate90 => [-y2, x1, -y1, x2], - Transform::Rotate180 => [-x2, -y2, -x1, -y1], - Transform::Rotate270 => [y1, -x2, y2, -x1], - Transform::Flip => [-x2, y1, -x1, y2], - Transform::FlipRotate90 => [y1, x1, y2, x2], - Transform::FlipRotate180 => [x1, -y2, x2, -y1], - Transform::FlipRotate270 => [-y2, -x2, -y1, -x1], - }; - let x1 = (x1 as f64 * self.mx + self.dx).floor() as i32 + dx; - let y1 = (y1 as f64 * self.my + self.dy).floor() as i32 + dy; - let x2 = (x2 as f64 * self.mx + self.dx).ceil() as i32 + dx; - let y2 = (y2 as f64 * self.my + self.dy).ceil() as i32 + dy; - Rect::new(x1, y1, x2, y2).unwrap() - } - - fn new( - transform: Transform, - legacy_scale: i32, - buffer_width: i32, - buffer_height: i32, - viewport: Option<[Fixed; 4]>, - dst_width: i32, - dst_height: i32, - ) -> DamageMatrix { - let mut buffer_width = buffer_width as f64; - let mut buffer_height = buffer_height as f64; - let dst_width = dst_width as f64; - let dst_height = dst_height as f64; - - let mut mx = 1.0; - let mut my = 1.0; - if legacy_scale != 1 { - let scale_inv = 1.0 / (legacy_scale as f64); - mx = scale_inv; - my = scale_inv; - buffer_width *= scale_inv; - buffer_height *= scale_inv; - } - let (mut buffer_width, mut buffer_height) = - transform.maybe_swap((buffer_width, buffer_height)); - let (mut dx, mut dy) = match transform { - Transform::None => (0.0, 0.0), - Transform::Rotate90 => (buffer_width, 0.0), - Transform::Rotate180 => (buffer_width, buffer_height), - Transform::Rotate270 => (0.0, buffer_height), - Transform::Flip => (buffer_width, 0.0), - Transform::FlipRotate90 => (0.0, 0.0), - Transform::FlipRotate180 => (0.0, buffer_height), - Transform::FlipRotate270 => (buffer_width, buffer_height), - }; - if let Some([x, y, w, h]) = viewport { - dx -= x.to_f64(); - dy -= y.to_f64(); - buffer_width = w.to_f64(); - buffer_height = h.to_f64(); - } - let mut smear = false; - if dst_width != buffer_width { - let scale = dst_width / buffer_width; - mx *= scale; - dx *= scale; - smear |= dst_width > buffer_width; - } - if dst_height != buffer_height { - let scale = dst_height / buffer_height; - my *= scale; - dy *= scale; - smear |= dst_height > buffer_height; - } - DamageMatrix { - transform, - mx, - my, - dx, - dy, - smear: smear as _, - } - } -} - impl VblankListener for WlSurface { fn after_vblank(self: Rc) { if self.visible.get() { diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 36a03040..ce81814b 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -376,12 +376,13 @@ impl GfxFramebuffer for TestGfxFb { } } - fn render( + fn render_with_region( &self, _acquire_sync: AcquireSync, _release_sync: ReleaseSync, ops: &[GfxApiOpt], clear: Option<&Color>, + _region: &Region, ) -> Result, GfxError> { let fb_points = |width: i32, height: i32, rect: &FramebufferRect| { let points = rect.to_points(); diff --git a/src/rect.rs b/src/rect.rs index 03640a01..d9a22cb5 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -164,6 +164,11 @@ impl Rect { self.raw.x1 == self.raw.x2 || self.raw.y1 == self.raw.y2 } + #[expect(dead_code)] + pub fn is_not_empty(&self) -> bool { + !self.is_empty() + } + #[expect(dead_code)] pub fn to_origin(&self) -> Self { Self { diff --git a/src/rect/region.rs b/src/rect/region.rs index 6ea178de..d385c7ad 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -11,7 +11,13 @@ use { RectRaw, }, smallvec::SmallVec, - std::{cell::UnsafeCell, mem, ops::Deref, rc::Rc}, + std::{ + cell::UnsafeCell, + fmt::{Debug, Formatter}, + mem, + ops::Deref, + rc::Rc, + }, }; thread_local! { @@ -196,6 +202,12 @@ pub struct DamageQueue { datas: Rc>>>, } +impl Debug for DamageQueue { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DamageQueue").finish_non_exhaustive() + } +} + impl DamageQueue { pub fn new() -> [DamageQueue; N] { let datas = Rc::new(UnsafeCell::new(vec![vec!(); N])); diff --git a/src/state.rs b/src/state.rs index ac79f8ab..a2354d86 100644 --- a/src/state.rs +++ b/src/state.rs @@ -349,7 +349,9 @@ pub struct ConnectorData { pub drm_dev: Option>, pub async_event: Rc, pub damaged: Cell, + pub damage: RefCell>, pub needs_vblank_emulation: Cell, + pub damage_intersect: Cell, } pub struct OutputData { @@ -831,6 +833,7 @@ impl State { self.damage_visualizer.add(rect); for output in self.root.outputs.lock().values() { if output.global.pos.get().intersects(&rect) { + output.global.add_damage_area(&rect); if cursor && output.schedule.defer_cursor_updates() { output.schedule.software_cursor_changed(); } else { diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 747bbcbd..7c84c0bc 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -35,7 +35,9 @@ pub fn handle(state: &Rc, connector: &Rc) { drm_dev: drm_dev.clone(), async_event: Rc::new(AsyncEvent::default()), damaged: Cell::new(false), + damage: Default::default(), needs_vblank_emulation: Cell::new(false), + damage_intersect: Default::default(), }); if let Some(dev) = drm_dev { dev.connectors.set(id, data.clone()); diff --git a/src/tree/output.rs b/src/tree/output.rs index 5c4630e7..5c16ff7a 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -794,13 +794,17 @@ impl OutputNode { } fn change_extents_(self: &Rc, rect: &Rect) { - if self.node_visible() { + let visible = self.node_visible(); + if visible { let old_pos = self.global.pos.get(); self.state.damage(old_pos); - self.state.damage(*rect); } self.global.persistent.pos.set((rect.x1(), rect.y1())); self.global.pos.set(*rect); + self.global.update_damage_matrix(); + if visible { + self.state.damage(*rect); + } self.state.output_extents_changed(); self.update_rects(); if let Some(ls) = self.lock_surface.get() { diff --git a/src/utils/transform_ext.rs b/src/utils/transform_ext.rs index 63f5a975..f7b965d0 100644 --- a/src/utils/transform_ext.rs +++ b/src/utils/transform_ext.rs @@ -18,6 +18,8 @@ pub trait TransformExt: Sized { fn from_wl(wl: i32) -> Option; fn apply_point(self, width: i32, height: i32, point: (i32, i32)) -> (i32, i32); + + fn inverse(self) -> Self; } impl TransformExt for Transform { @@ -68,4 +70,12 @@ impl TransformExt for Transform { FlipRotate270 => (width - y, height - x), } } + + fn inverse(self) -> Self { + match self { + Rotate90 => Rotate270, + Rotate270 => Rotate90, + _ => self, + } + } } From 993df71c80e6a6aa2a5c5fce6849ce2802a56c8d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 18 Feb 2025 20:49:51 +0100 Subject: [PATCH 7/7] vulkan: draw only in requested regions --- src/gfx_apis/vulkan/renderer.rs | 290 +++++++++++++++++++++++++------- 1 file changed, 225 insertions(+), 65 deletions(-) diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index d3715426..75a7ddb2 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -29,22 +29,25 @@ use { io_uring::IoUring, rect::Region, theme::Color, - utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack}, + utils::{ + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, once::Once, + stack::Stack, + }, video::dmabuf::{dma_buf_export_sync_file, DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE}, }, ahash::AHashMap, ash::{ vk, vk::{ - AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, ClearColorValue, ClearValue, - CommandBuffer, CommandBufferBeginInfo, CommandBufferSubmitInfo, + AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, ClearAttachment, ClearColorValue, + ClearRect, ClearValue, CommandBuffer, CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags, CopyImageInfo2, DependencyInfoKHR, DescriptorBufferBindingInfoEXT, DescriptorImageInfo, DescriptorType, DeviceSize, Extent2D, Extent3D, ImageAspectFlags, ImageCopy2, ImageLayout, ImageMemoryBarrier2, - ImageSubresourceLayers, ImageSubresourceRange, PipelineBindPoint, PipelineStageFlags2, - Rect2D, RenderingAttachmentInfo, RenderingInfo, SemaphoreSubmitInfo, - SemaphoreSubmitInfoKHR, ShaderStageFlags, SubmitInfo2, Viewport, WriteDescriptorSet, - QUEUE_FAMILY_FOREIGN_EXT, + ImageSubresourceLayers, ImageSubresourceRange, Offset2D, Offset3D, PipelineBindPoint, + PipelineStageFlags2, Rect2D, RenderingAttachmentInfo, RenderingInfo, + SemaphoreSubmitInfo, SemaphoreSubmitInfoKHR, ShaderStageFlags, SubmitInfo2, Viewport, + WriteDescriptorSet, QUEUE_FAMILY_FOREIGN_EXT, }, Device, }, @@ -139,6 +142,17 @@ pub(super) struct Memory { release_fence: Option>, release_sync_file: Option, descriptor_buffer: Option, + paint_regions: Vec, + clear_rects: Vec, + image_copy_regions: Vec>, +} + +struct PaintRegion { + rect: Rect2D, + x1: f32, + y1: f32, + x2: f32, + y2: f32, } pub(super) struct PendingFrame { @@ -486,20 +500,31 @@ impl VulkanRenderer { fn begin_rendering(&self, buf: CommandBuffer, fb: &VulkanImage, clear: Option<&Color>) { zone!("begin_rendering"); + let memory = &mut *self.memory.borrow_mut(); + let clear_value = clear.map(|clear| ClearValue { + color: ClearColorValue { + float32: clear.to_array_srgb(), + }, + }); + let load_clear = memory.paint_regions.len() == 1 && { + let rect = &memory.paint_regions[0].rect; + rect.offset.x == 0 + && rect.offset.y == 0 + && rect.extent.width == fb.width + && rect.extent.height == fb.height + }; + let (load_clear, manual_clear) = match load_clear { + false => (None, clear_value), + true => (clear_value, None), + }; let rendering_attachment_info = { let mut rai = RenderingAttachmentInfo::default() .image_view(fb.render_view.unwrap_or(fb.texture_view)) .image_layout(ImageLayout::GENERAL) .load_op(AttachmentLoadOp::LOAD) .store_op(AttachmentStoreOp::STORE); - if let Some(clear) = clear { - rai = rai - .clear_value(ClearValue { - color: ClearColorValue { - float32: clear.to_array_srgb(), - }, - }) - .load_op(AttachmentLoadOp::CLEAR); + if let Some(clear) = load_clear { + rai = rai.clear_value(clear).load_op(AttachmentLoadOp::CLEAR); } rai }; @@ -516,6 +541,27 @@ impl VulkanRenderer { unsafe { self.device.device.cmd_begin_rendering(buf, &rendering_info); } + if let Some(clear) = manual_clear { + let clear_attachment = ClearAttachment::default() + .color_attachment(0) + .clear_value(clear) + .aspect_mask(ImageAspectFlags::COLOR); + memory.clear_rects.clear(); + for region in &memory.paint_regions { + memory.clear_rects.push(ClearRect { + rect: region.rect, + base_array_layer: 0, + layer_count: 1, + }); + } + unsafe { + self.device.device.cmd_clear_attachments( + buf, + &[clear_attachment], + &memory.clear_rects, + ); + } + } } fn set_viewport(&self, buf: CommandBuffer, fb: &VulkanImage) { @@ -552,6 +598,7 @@ impl VulkanRenderer { opts: &[GfxApiOpt], ) -> Result<(), VulkanError> { zone!("record_draws"); + let memory = &*self.memory.borrow(); let pipelines = self.get_or_create_pipelines(fb.format.vk_format)?; let dev = &self.device.device; let mut current_pipeline = None; @@ -567,20 +614,27 @@ impl VulkanRenderer { match opt { GfxApiOpt::Sync => {} GfxApiOpt::FillRect(r) => { - bind(&pipelines.fill); let push = FillPushConstants { pos: r.rect.to_points(), color: r.color.to_array_srgb(), }; - unsafe { - dev.cmd_push_constants( - buf, - pipelines.fill.pipeline_layout, - ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, - 0, - uapi::as_bytes(&push), - ); - dev.cmd_draw(buf, 4, 1, 0, 0); + for region in &memory.paint_regions { + let mut push = push; + let draw = region.constrain(&mut push.pos, None); + if !draw { + continue; + } + bind(&pipelines.fill); + unsafe { + dev.cmd_push_constants( + buf, + pipelines.fill.pipeline_layout, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); + } } } GfxApiOpt::CopyTexture(c) => { @@ -603,7 +657,6 @@ impl VulkanRenderer { false => TexSourceType::Opaque, }; let pipeline = &pipelines.tex[copy_type][source_type]; - bind(pipeline); let push = TexPushConstants { pos: c.target.to_points(), tex_pos: c.source.to_points(), @@ -612,36 +665,47 @@ impl VulkanRenderer { let image_info = DescriptorImageInfo::default() .image_view(tex.texture_view) .image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL); - unsafe { - if let Some(db) = &self.device.descriptor_buffer { - db.cmd_set_descriptor_buffer_offsets( - buf, - PipelineBindPoint::GRAPHICS, - pipeline.pipeline_layout, - 0, - &[0], - &[tex.descriptor_buffer_offset.get()], - ); - } else { - let write_descriptor_set = WriteDescriptorSet::default() - .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) - .image_info(slice::from_ref(&image_info)); - self.device.push_descriptor.cmd_push_descriptor_set( - buf, - PipelineBindPoint::GRAPHICS, - pipeline.pipeline_layout, - 0, - slice::from_ref(&write_descriptor_set), - ); + let init = Once::default(); + for region in &memory.paint_regions { + let mut push = push; + let draw = region.constrain(&mut push.pos, Some(&mut push.tex_pos)); + if !draw { + continue; + } + init.exec(|| unsafe { + bind(pipeline); + if let Some(db) = &self.device.descriptor_buffer { + db.cmd_set_descriptor_buffer_offsets( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + &[0], + &[tex.descriptor_buffer_offset.get()], + ); + } else { + let write_descriptor_set = WriteDescriptorSet::default() + .descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(slice::from_ref(&image_info)); + self.device.push_descriptor.cmd_push_descriptor_set( + buf, + PipelineBindPoint::GRAPHICS, + pipeline.pipeline_layout, + 0, + slice::from_ref(&write_descriptor_set), + ); + } + }); + unsafe { + dev.cmd_push_constants( + buf, + pipeline.pipeline_layout, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + 0, + uapi::as_bytes(&push), + ); + dev.cmd_draw(buf, 4, 1, 0, 0); } - dev.cmd_push_constants( - buf, - pipeline.pipeline_layout, - ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, - 0, - uapi::as_bytes(&push), - ); - dev.cmd_draw(buf, 4, 1, 0, 0); } } } @@ -697,20 +761,33 @@ impl VulkanRenderer { .layer_count(1) .base_array_layer(0) .mip_level(0); - let image_copy = ImageCopy2::default() - .src_subresource(image_subresource_layers) - .dst_subresource(image_subresource_layers) - .extent(Extent3D { - width: fb.width, - height: fb.height, + memory.image_copy_regions.clear(); + for region in &memory.paint_regions { + let offset = Offset3D { + x: region.rect.offset.x, + y: region.rect.offset.y, + z: 0, + }; + let extent = Extent3D { + width: region.rect.extent.width, + height: region.rect.extent.height, depth: 1, - }); + }; + memory.image_copy_regions.push( + ImageCopy2::default() + .src_subresource(image_subresource_layers) + .dst_subresource(image_subresource_layers) + .src_offset(offset) + .dst_offset(offset) + .extent(extent), + ); + } let copy_image_info = CopyImageInfo2::default() .src_image(fb.image) .src_image_layout(ImageLayout::TRANSFER_SRC_OPTIMAL) .dst_image(bridge.dmabuf_image) .dst_image_layout(ImageLayout::TRANSFER_DST_OPTIMAL) - .regions(slice::from_ref(&image_copy)); + .regions(&memory.image_copy_regions); unsafe { self.device.device.cmd_copy_image2(buf, ©_image_info); } @@ -966,10 +1043,10 @@ impl VulkanRenderer { fb_release_sync: ReleaseSync, opts: &[GfxApiOpt], clear: Option<&Color>, - _region: &Region, + region: &Region, ) -> Result, VulkanError> { zone!("execute"); - let res = self.try_execute(fb, fb_acquire_sync, fb_release_sync, opts, clear); + let res = self.try_execute(fb, fb_acquire_sync, fb_release_sync, opts, clear, region); let sync_file = { let mut memory = self.memory.borrow_mut(); memory.textures.clear(); @@ -991,6 +1068,39 @@ impl VulkanRenderer { Ok(semaphore) } + fn create_paint_regions(&self, fb: &VulkanImage, region: &Region) { + let memory = &mut *self.memory.borrow_mut(); + memory.paint_regions.clear(); + for rect in region.rects() { + let x1 = rect.x1().max(0); + let y1 = rect.y1().max(0); + let x2 = rect.x2(); + let y2 = rect.y2(); + if x1 as u32 > fb.width || y1 as u32 > fb.height || x2 <= 0 || y2 <= 0 { + continue; + } + let x2 = x2.min(fb.width as i32); + let y2 = y2.min(fb.height as i32); + let to_fb = |c: i32, max: u32| 2.0 * (c as f32 / max as f32) - 1.0; + memory.paint_regions.push(PaintRegion { + rect: Rect2D { + offset: Offset2D { + x: x1 as _, + y: y1 as _, + }, + extent: Extent2D { + width: (x2 - x1) as u32, + height: (y2 - y1) as u32, + }, + }, + x1: to_fb(x1, fb.width), + x2: to_fb(x2, fb.width), + y1: to_fb(y1, fb.height), + y2: to_fb(y2, fb.height), + }); + } + } + fn try_execute( self: &Rc, fb: &VulkanImage, @@ -998,8 +1108,10 @@ impl VulkanRenderer { fb_release_sync: ReleaseSync, opts: &[GfxApiOpt], clear: Option<&Color>, + region: &Region, ) -> Result<(), VulkanError> { self.check_defunct()?; + self.create_paint_regions(fb, region); let buf = self.gfx_command_buffers.allocate()?; self.collect_memory(opts); self.begin_command_buffer(buf.buffer)?; @@ -1125,3 +1237,51 @@ async fn await_release( } renderer.pending_frames.remove(&frame.point); } + +impl PaintRegion { + fn constrain(&self, pos: &mut [[f32; 2]; 4], tex_pos: Option<&mut [[f32; 2]; 4]>) -> bool { + zone!("constrain"); + let mut npos = *pos; + for [x, y] in &mut npos { + *x = x.clamp(self.x1, self.x2); + *y = y.clamp(self.y1, self.y2); + } + if npos == *pos { + return true; + } + if npos[0] == npos[1] && npos[2] == npos[3] { + return false; + } + if npos[0] == npos[2] && npos[1] == npos[3] { + return false; + } + if let Some(tp) = tex_pos { + let mut ntp = *tp; + for i in 0..4 { + if npos[i] == pos[i] { + continue; + } + macro_rules! sub { + ($l:expr, $r:expr) => { + [$l[0] - $r[0], $l[1] - $r[1]] + }; + } + let dx = sub!(npos[i], pos[i]); + let dy = sub!(pos[(i + 1) & 3], pos[i]); + let dz = sub!(pos[(i + 2) & 3], pos[i]); + let det = 1.0 / (dy[0] * dz[1] - dy[1] * dz[0]); + let alpha = [ + (dx[0] * dz[1] - dx[1] * dz[0]) * det, + (dx[1] * dy[0] - dx[0] * dy[1]) * det, + ]; + let dy = sub!(tp[(i + 1) & 3], tp[i]); + let dz = sub!(tp[(i + 2) & 3], tp[i]); + ntp[i][0] += alpha[0] * dy[0] + alpha[1] * dz[0]; + ntp[i][1] += alpha[0] * dy[1] + alpha[1] * dz[1]; + } + *tp = ntp; + } + *pos = npos; + true + } +}