| 1 | use crate::state::{ClientState, State}; |
| 2 | use smithay::backend::allocator::dmabuf::Dmabuf; |
| 3 | use smithay::backend::renderer::utils::{import_surface_tree, on_commit_buffer_handler}; |
| 4 | use smithay::desktop::PopupKind; |
| 5 | use smithay::reexports::calloop::Interest; |
| 6 | use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer; |
| 7 | use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; |
| 8 | use smithay::reexports::wayland_server::{Client, Resource}; |
| 9 | use smithay::utils::{Rectangle, Transform}; |
| 10 | use smithay::wayland::buffer::BufferHandler; |
| 11 | use smithay::wayland::compositor::{ |
| 12 | BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, Damage, |
| 13 | SurfaceAttributes, add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface, |
| 14 | with_states, |
| 15 | }; |
| 16 | use smithay::wayland::dmabuf::{ |
| 17 | DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf, |
| 18 | }; |
| 19 | use smithay::wayland::drm_syncobj::{DrmSyncobjCachedState, DrmSyncobjHandler, DrmSyncobjState}; |
| 20 | use smithay::wayland::shm::{ShmHandler, ShmState, with_buffer_contents}; |
| 21 | |
| 22 | impl CompositorHandler for State { |
| 23 | fn compositor_state(&mut self) -> &mut CompositorState { |
| 24 | &mut self.protocols.compositor_state |
| 25 | } |
| 26 | |
| 27 | fn new_surface(&mut self, surface: &WlSurface) { |
| 28 | add_pre_commit_hook::<Self, _>(surface, |state, _dh, surface| { |
| 29 | hold_until_readable(state, surface); |
| 30 | }); |
| 31 | } |
| 32 | |
| 33 | fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { |
| 34 | if let Some(x) = client.get_data::<smithay::xwayland::XWaylandClientData>() { |
| 35 | return &x.compositor_state; |
| 36 | } |
| 37 | &client |
| 38 | .get_data::<ClientState>() |
| 39 | .expect("every client has ClientState") |
| 40 | .compositor_state |
| 41 | } |
| 42 | |
| 43 | fn commit(&mut self, surface: &WlSurface) { |
| 44 | on_commit_buffer_handler::<Self>(surface); |
| 45 | // Importing at commit keeps first-time dmabuf imports, which take |
| 46 | // milliseconds on NVIDIA, out of the render racing the next vblank. |
| 47 | if let Err(e) = import_surface_tree(&mut self.renderer, surface) { |
| 48 | tracing::debug!("early import failed: {e}"); |
| 49 | } |
| 50 | if !is_sync_subsurface(surface) { |
| 51 | let mut root = surface.clone(); |
| 52 | while let Some(parent) = get_parent(&root) { |
| 53 | root = parent; |
| 54 | } |
| 55 | let mapped = self.wm.id_of(&root).and_then(|id| self.wm.window(id)); |
| 56 | let window = mapped.or_else(|| { |
| 57 | self.wm |
| 58 | .unmapped |
| 59 | .iter() |
| 60 | .find(|w| crate::wm::root(w).as_ref() == Some(&root)) |
| 61 | }); |
| 62 | if let Some(window) = window { |
| 63 | window.on_commit(); |
| 64 | } |
| 65 | self.count_commit(&root); |
| 66 | if let Some(id) = self.window_id(&root) { |
| 67 | self.refresh_identity(id); |
| 68 | if self.services.captures.wants_damage() { |
| 69 | self.services.captures.window_damaged(id); |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | if let Some(i) = self |
| 74 | .wm |
| 75 | .unmapped |
| 76 | .iter() |
| 77 | .position(|w| w.toplevel().is_some_and(|t| t.wl_surface() == surface)) |
| 78 | { |
| 79 | let window = self.wm.unmapped.remove(i); |
| 80 | let toplevel = window.toplevel().cloned(); |
| 81 | self.map_window(window, None); |
| 82 | if let Some(t) = toplevel { |
| 83 | t.send_configure(); |
| 84 | } |
| 85 | } |
| 86 | self.on_buffer(surface); |
| 87 | self.wm.popups.commit(surface); |
| 88 | if let Some(PopupKind::Xdg(popup)) = self.wm.popups.find_popup(surface) |
| 89 | && !popup.is_initial_configure_sent() |
| 90 | { |
| 91 | let _ = popup.send_configure(); |
| 92 | } |
| 93 | self.layer_commit(surface); |
| 94 | self.dirty.redraw = true; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | impl BufferHandler for State { |
| 99 | fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {} |
| 100 | } |
| 101 | |
| 102 | impl ShmHandler for State { |
| 103 | fn shm_state(&self) -> &ShmState { |
| 104 | &self.protocols.shm_state |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | impl DmabufHandler for State { |
| 109 | fn dmabuf_state(&mut self) -> &mut DmabufState { |
| 110 | &mut self.protocols.dmabuf_state |
| 111 | } |
| 112 | |
| 113 | fn dmabuf_imported( |
| 114 | &mut self, |
| 115 | _global: &DmabufGlobal, |
| 116 | dmabuf: Dmabuf, |
| 117 | notifier: ImportNotifier, |
| 118 | ) { |
| 119 | self.import_for(&dmabuf, notifier); |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | impl DrmSyncobjHandler for State { |
| 124 | fn drm_syncobj_state(&mut self) -> Option<&mut DrmSyncobjState> { |
| 125 | self.backend.syncobj_mut() |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | /// Holds a dmabuf commit until the buffer can be read without the renderer |
| 130 | /// stalling on the client's GPU work: until its explicit acquire point |
| 131 | /// signals, or for implicitly synced buffers until their writes finish. |
| 132 | fn hold_until_readable(state: &mut State, surface: &WlSurface) { |
| 133 | stage_shm(state, surface); |
| 134 | let (acquire, dmabuf) = with_states(surface, |s| { |
| 135 | let acquire = s |
| 136 | .cached_state |
| 137 | .get::<DrmSyncobjCachedState>() |
| 138 | .pending() |
| 139 | .acquire_point |
| 140 | .clone(); |
| 141 | let dmabuf = match s |
| 142 | .cached_state |
| 143 | .get::<SurfaceAttributes>() |
| 144 | .pending() |
| 145 | .buffer |
| 146 | .as_ref() |
| 147 | { |
| 148 | Some(BufferAssignment::NewBuffer(buffer)) => get_dmabuf(buffer).cloned().ok(), |
| 149 | _ => None, |
| 150 | }; |
| 151 | (acquire, dmabuf) |
| 152 | }); |
| 153 | let (Some(dmabuf), Some(client)) = (dmabuf, surface.client()) else { |
| 154 | return; |
| 155 | }; |
| 156 | if let Some(blocker) = state.import_blocker(&dmabuf, client.clone()) { |
| 157 | add_blocker(surface, blocker); |
| 158 | } |
| 159 | if let Some(point) = acquire |
| 160 | && let Ok((blocker, source)) = point.generate_blocker() |
| 161 | { |
| 162 | let inserted = state.core.handle.insert_source(source, move |_, _, state| { |
| 163 | let dh = state.core.display.clone(); |
| 164 | state |
| 165 | .client_compositor_state(&client) |
| 166 | .blocker_cleared(state, &dh); |
| 167 | Ok(()) |
| 168 | }); |
| 169 | if inserted.is_ok() { |
| 170 | add_blocker(surface, blocker); |
| 171 | } |
| 172 | return; |
| 173 | } |
| 174 | if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) { |
| 175 | let inserted = state.core.handle.insert_source(source, move |_, _, state| { |
| 176 | let dh = state.core.display.clone(); |
| 177 | state |
| 178 | .client_compositor_state(&client) |
| 179 | .blocker_cleared(state, &dh); |
| 180 | Ok(()) |
| 181 | }); |
| 182 | if inserted.is_ok() { |
| 183 | add_blocker(surface, blocker); |
| 184 | } |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | /// Copies a committed shm buffer's damage on the worker, holding the commit |
| 189 | /// until it is staged. Surface damage equals buffer damage at scale 1 with no |
| 190 | /// transform, otherwise the whole buffer is staged so it covers whatever the |
| 191 | /// import uploads. |
| 192 | fn stage_shm(state: &mut State, surface: &WlSurface) { |
| 193 | let job = with_states(surface, |s| { |
| 194 | let mut cached = s.cached_state.get::<SurfaceAttributes>(); |
| 195 | let attrs = cached.pending(); |
| 196 | let Some(BufferAssignment::NewBuffer(buffer)) = attrs.buffer.as_ref() else { |
| 197 | return None; |
| 198 | }; |
| 199 | if get_dmabuf(buffer).is_ok() { |
| 200 | return None; |
| 201 | } |
| 202 | let plain = attrs.buffer_scale == 1 && attrs.buffer_transform == Transform::Normal.into(); |
| 203 | let region = attrs |
| 204 | .damage |
| 205 | .iter() |
| 206 | .map(|d| match d { |
| 207 | Damage::Buffer(r) => *r, |
| 208 | Damage::Surface(r) => { |
| 209 | Rectangle::new((r.loc.x, r.loc.y).into(), (r.size.w, r.size.h).into()) |
| 210 | } |
| 211 | }) |
| 212 | .reduce(|a, b| a.merge(b)) |
| 213 | .filter(|_| plain); |
| 214 | Some((buffer.clone(), region)) |
| 215 | }); |
| 216 | let (Some((buffer, region)), Some(client)) = (job, surface.client()) else { |
| 217 | return; |
| 218 | }; |
| 219 | let Ok(size) = with_buffer_contents(&buffer, |_, _, d| (d.width, d.height)) else { |
| 220 | return; |
| 221 | }; |
| 222 | if let Some(blocker) = state.stage_blocker(&buffer, region, size, client) { |
| 223 | add_blocker(surface, blocker); |
| 224 | } |
| 225 | } |