diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 23ac5bab..9e7882ec 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -551,7 +551,6 @@ pub struct PendingShmUpload { } pub trait AsyncShmGfxTexture: GfxTexture { - #[expect(dead_code)] fn async_upload( self: Rc, callback: Rc, @@ -561,7 +560,6 @@ pub trait AsyncShmGfxTexture: GfxTexture { fn sync_upload(self: Rc, shm: &[Cell], damage: Region) -> Result<(), GfxError>; - #[expect(dead_code)] fn compatible_with( &self, format: &'static Format, @@ -570,7 +568,6 @@ pub trait AsyncShmGfxTexture: GfxTexture { stride: i32, ) -> bool; - #[expect(dead_code)] fn into_texture(self: Rc) -> Rc; } @@ -598,7 +595,6 @@ pub trait GfxContext: Debug { damage: Option<&[Rect]>, ) -> Result, GfxError>; - #[expect(dead_code)] fn async_shmem_texture( self: Rc, format: &'static Format, diff --git a/src/ifs/wl_buffer.rs b/src/ifs/wl_buffer.rs index 268f72a8..3068d572 100644 --- a/src/ifs/wl_buffer.rs +++ b/src/ifs/wl_buffer.rs @@ -7,7 +7,7 @@ use { ifs::wl_surface::WlSurface, leaks::Tracker, object::{Object, Version}, - rect::Rect, + rect::{Rect, Region}, theme::Color, utils::errorfmt::ErrorFmt, video::dmabuf::DmaBuf, @@ -22,7 +22,7 @@ use { pub enum WlBufferStorage { Shm { - mem: ClientMemOffset, + mem: Rc, stride: i32, }, Dmabuf { @@ -41,6 +41,7 @@ pub struct WlBuffer { pub dmabuf: Option, render_ctx_version: Cell, pub storage: RefCell>, + shm: bool, pub color: Option, width: i32, height: i32, @@ -52,6 +53,10 @@ impl WlBuffer { self.destroyed.get() } + pub fn is_shm(&self) -> bool { + self.shm + } + pub fn new_dmabuf( id: WlBufferId, client: &Rc, @@ -76,6 +81,7 @@ impl WlBuffer { tex: None, fb: None, })), + shm: false, tracker: Default::default(), color: None, } @@ -100,7 +106,7 @@ impl WlBuffer { if required > mem.len() as u64 { return Err(WlBufferError::OutOfBounds); } - let mem = mem.offset(offset); + let mem = Rc::new(mem.offset(offset)); let min_row_size = width as u64 * shm_info.bpp as u64; if (stride as u64) < min_row_size { return Err(WlBufferError::StrideTooSmall); @@ -114,6 +120,7 @@ impl WlBuffer { dmabuf: None, render_ctx_version: Cell::new(client.state.render_ctx_version.get()), storage: RefCell::new(Some(WlBufferStorage::Shm { mem, stride })), + shm: true, width, height, tracker: Default::default(), @@ -138,6 +145,7 @@ impl WlBuffer { dmabuf: None, render_ctx_version: Cell::new(client.state.render_ctx_version.get()), storage: RefCell::new(None), + shm: false, width: 1, height: 1, tracker: Default::default(), @@ -153,7 +161,7 @@ impl WlBuffer { let had_texture = self.reset_gfx_objects(surface); if had_texture { if let Some(surface) = surface { - self.update_texture_or_log(surface, None); + self.update_texture_or_log(surface, true); } } } @@ -166,7 +174,10 @@ impl WlBuffer { let had_texture = match s { WlBufferStorage::Shm { .. } => { return match surface { - Some(s) => s.shm_texture.take().is_some(), + Some(s) => { + s.shm_textures.back().tex.take(); + s.shm_textures.front().tex.take().is_some() + } None => false, }; } @@ -201,24 +212,24 @@ impl WlBuffer { match &*self.storage.borrow() { None => None, Some(s) => match s { - WlBufferStorage::Shm { .. } => surface.shm_texture.get().map(|t| t.into_texture()), + WlBufferStorage::Shm { .. } => surface + .shm_textures + .front() + .tex + .get() + .map(|t| t.into_texture()), WlBufferStorage::Dmabuf { tex, .. } => tex.clone(), }, } } - pub fn update_texture_or_log(&self, surface: &WlSurface, damage: Option<&[Rect]>) { - if let Err(e) = self.update_texture(surface, damage) { + pub fn update_texture_or_log(&self, surface: &WlSurface, sync_shm: bool) { + if let Err(e) = self.update_texture(surface, sync_shm) { log::warn!("Could not update texture: {}", ErrorFmt(e)); } } - fn update_texture( - &self, - surface: &WlSurface, - damage: Option<&[Rect]>, - ) -> Result<(), WlBufferError> { - let old_shm_texture = surface.shm_texture.take(); + fn update_texture(&self, surface: &WlSurface, sync_shm: bool) -> Result<(), WlBufferError> { let storage = &mut *self.storage.borrow_mut(); let storage = match storage { Some(s) => s, @@ -226,19 +237,19 @@ impl WlBuffer { }; match storage { WlBufferStorage::Shm { mem, stride } => { - if let Some(ctx) = self.client.state.render_ctx.get() { - let tex = mem.access(|mem| { - ctx.shmem_texture( - old_shm_texture, - mem, + if sync_shm { + if let Some(ctx) = self.client.state.render_ctx.get() { + let tex = ctx.async_shmem_texture( self.format, self.width, self.height, *stride, - damage, - ) - })??; - surface.shm_texture.set(Some(tex)); + &self.client.state.cpu_worker, + )?; + mem.access(|mem| tex.clone().sync_upload(mem, Region::new2(self.rect)))??; + surface.shm_textures.front().tex.set(Some(tex)); + surface.shm_textures.front().damage.clear(); + } } } WlBufferStorage::Dmabuf { img, tex, .. } => { diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 48422742..63b9101b 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -23,8 +23,8 @@ use { drm_feedback::DrmFeedback, fixed::Fixed, gfx_api::{ - AcquireSync, BufferResv, BufferResvUser, ReleaseSync, SampleRect, ShmGfxTexture, - SyncFile, + AcquireSync, AsyncShmGfxTexture, BufferResv, BufferResvUser, GfxError, ReleaseSync, + SampleRect, SyncFile, }, ifs::{ wl_buffer::WlBuffer, @@ -60,16 +60,16 @@ use { }, leaks::Tracker, object::{Object, Version}, - rect::{Rect, Region}, + rect::{DamageQueue, Rect, Region}, renderer::Renderer, tree::{ ContainerNode, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, NodeVisitorBase, OutputNode, OutputNodeId, PlaceholderNode, ToplevelNode, }, utils::{ - cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - linkedlist::LinkedList, numcell::NumCell, smallmap::SmallMap, - transform_ext::TransformExt, + cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, + double_buffered::DoubleBuffered, errorfmt::ErrorFmt, linkedlist::LinkedList, + numcell::NumCell, smallmap::SmallMap, transform_ext::TransformExt, }, video::{ dmabuf::DMA_BUF_SYNC_READ, @@ -250,6 +250,11 @@ impl BufferResv for SurfaceBuffer { } } +pub struct SurfaceShmTexture { + pub tex: CloneCell>>, + pub damage: DamageQueue, +} + pub struct WlSurface { pub id: WlSurfaceId, pub node_id: SurfaceNodeId, @@ -272,7 +277,7 @@ pub struct WlSurface { pub buffer: CloneCell>>, buffer_presented: Cell, buffer_had_frame_request: Cell, - pub shm_texture: CloneCell>>, + pub shm_textures: DoubleBuffered, pub buf_x: NumCell, pub buf_y: NumCell, pub children: RefCell>>, @@ -585,7 +590,10 @@ impl WlSurface { buffer: Default::default(), buffer_presented: Default::default(), buffer_had_frame_request: Default::default(), - shm_texture: Default::default(), + shm_textures: DoubleBuffered::new(DamageQueue::new().map(|damage| SurfaceShmTexture { + tex: Default::default(), + damage, + })), buf_x: Default::default(), buf_y: Default::default(), children: Default::default(), @@ -910,7 +918,7 @@ impl WlSurfaceRequestHandler for WlSurface { *children = None; } self.buffer.set(None); - self.shm_texture.take(); + self.reset_shm_textures(); if let Some(xwayland_serial) = self.xwayland_serial.get() { self.client .surfaces_by_xwayland_serial @@ -1078,11 +1086,13 @@ impl WlSurface { old_raw_size = Some(buffer.buffer.rect); } if let Some(buffer) = buffer_change { - let damage = match pending.damage_full || pending.surface_damage.is_not_empty() { - true => None, - false => Some(&pending.buffer_damage[..]), - }; - buffer.update_texture_or_log(self, damage); + if buffer.is_shm() { + self.shm_textures.flip(); + self.shm_textures.front().damage.clear(); + } else { + self.reset_shm_textures(); + } + buffer.update_texture_or_log(self, false); let (sync, release_sync) = match pending.explicit_sync { false => (AcquireSync::Implicit, ReleaseSync::Implicit), true => (AcquireSync::Unnecessary, ReleaseSync::Explicit), @@ -1104,7 +1114,7 @@ impl WlSurface { self.buffer_had_frame_request.set(false); } } else { - self.shm_texture.take(); + self.reset_shm_textures(); self.buf_x.set(0); self.buf_y.set(0); for (_, cursor) in &self.cursors { @@ -1322,6 +1332,13 @@ impl WlSurface { Ok(()) } + pub fn reset_shm_textures(&self) { + for tex in &*self.shm_textures { + tex.tex.take(); + tex.damage.clear(); + } + } + fn apply_damage(&self, pending: &PendingState) { let bounds = self.toplevel.get().map(|tl| tl.node_absolute_position()); let pos = self.buffer_abs_pos.get(); @@ -1916,6 +1933,12 @@ pub enum WlSurfaceError { UnexpectedSyncPoints, #[error("The supplied region is invalid")] InvalidRect, + #[error("There is no render context")] + NoRenderContext, + #[error("Could not create a shm texture")] + CreateAsyncShmTexture(#[source] GfxError), + #[error("Could not prepare upload to a shm texture")] + PrepareAsyncUpload(#[source] GfxError), } efrom!(WlSurfaceError, ClientError); efrom!(WlSurfaceError, XdgSurfaceError); diff --git a/src/ifs/wl_surface/commit_timeline.rs b/src/ifs/wl_surface/commit_timeline.rs index a150b6fa..8ec7a1b0 100644 --- a/src/ifs/wl_surface/commit_timeline.rs +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -1,6 +1,10 @@ use { crate::{ - ifs::wl_surface::{PendingState, WlSurface, WlSurfaceError}, + gfx_api::{AsyncShmGfxTextureCallback, GfxError, PendingShmUpload}, + ifs::{ + wl_buffer::WlBufferStorage, + wl_surface::{PendingState, WlSurface, WlSurfaceError}, + }, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, @@ -14,13 +18,14 @@ use { DrmError, }, }, - isnt::std_1::primitive::IsntSliceExt, + isnt::std_1::{primitive::IsntSliceExt, vec::IsntVecExt}, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, mem, - ops::{Deref, DerefMut}, + ops::DerefMut, rc::Rc, + slice, }, thiserror::Error, }; @@ -76,6 +81,8 @@ pub enum CommitTimelineError { Wait(#[source] DrmError), #[error("The client has too many pending commits")] Depth, + #[error("Could not upload a shm texture")] + ShmUpload(#[source] GfxError), } impl CommitTimelines { @@ -119,6 +126,7 @@ fn break_loops(list: &LinkedList) { entry.link.take(); if let EntryKind::Commit(c) = &entry.kind { c.wait_handles.take(); + *c.shm_upload.borrow_mut() = ShmUploadState::None; } } } @@ -145,7 +153,9 @@ impl CommitTimeline { ) -> Result<(), CommitTimelineError> { let mut points = SmallVec::new(); consume_acquire_points(pending, &mut points); - if points.is_empty() && self.own_timeline.entries.is_empty() { + let mut pending_uploads = 0; + count_shm_uploads(pending, &mut pending_uploads); + if points.is_empty() && pending_uploads == 0 && self.own_timeline.entries.is_empty() { return surface .apply_state(pending) .map_err(CommitTimelineError::ImmediateCommit); @@ -162,23 +172,35 @@ impl CommitTimeline { pending: RefCell::new(mem::take(pending)), sync_obj: NumCell::new(points.len()), wait_handles: Cell::new(Default::default()), + pending_uploads: NumCell::new(pending_uploads), + shm_upload: RefCell::new(ShmUploadState::None), }), ); - if points.is_not_empty() { - let mut wait_handles = SmallVec::new(); - let noderef = Rc::new(noderef); - for (sync_obj, point) in points { - let handle = self - .shared - .wfs - .wait(&sync_obj, point, true, noderef.clone()) - .map_err(CommitTimelineError::RegisterWait)?; - wait_handles.push(handle); - } + let mut needs_flush = false; + if points.is_not_empty() || pending_uploads > 0 { + let noderef = Rc::new(noderef.clone()); let EntryKind::Commit(commit) = &noderef.kind else { unreachable!(); }; - commit.wait_handles.set(wait_handles); + if points.is_not_empty() { + let mut wait_handles = SmallVec::new(); + for (sync_obj, point) in points { + let handle = self + .shared + .wfs + .wait(&sync_obj, point, true, noderef.clone()) + .map_err(CommitTimelineError::RegisterWait)?; + wait_handles.push(handle); + } + commit.wait_handles.set(wait_handles); + } + if pending_uploads > 0 { + *commit.shm_upload.borrow_mut() = ShmUploadState::Todo(noderef.clone()); + needs_flush = true; + } + } + if needs_flush && noderef.prev().is_none() { + flush_from(noderef.clone()).map_err(CommitTimelineError::DelayedCommit)?; } Ok(()) } @@ -194,12 +216,33 @@ impl SyncObjWaiter for NodeRef { return; } commit.sync_obj.fetch_sub(1); - if let Err(e) = flush_from(self.deref().clone()) { + flush_commit(&self, commit); + } +} + +fn flush_commit(node_ref: &NodeRef, commit: &Commit) { + if let Err(e) = flush_from(node_ref.clone()) { + commit + .surface + .client + .error(CommitTimelineError::DelayedCommit(e)); + } +} + +impl AsyncShmGfxTextureCallback for NodeRef { + fn completed(self: Rc, res: Result<(), GfxError>) { + let EntryKind::Commit(commit) = &self.kind else { + unreachable!(); + }; + if let Err(e) = res { commit .surface .client - .error(CommitTimelineError::DelayedCommit(e)); + .error(CommitTimelineError::ShmUpload(e)); + return; } + commit.pending_uploads.fetch_sub(1); + flush_commit(&self, commit); } } @@ -216,11 +259,19 @@ enum EntryKind { Gc(CommitTimelineId), } +enum ShmUploadState { + None, + Todo(Rc>), + Scheduled(#[expect(dead_code)] SmallVec<[PendingShmUpload; 1]>), +} + struct Commit { surface: Rc, pending: RefCell>, sync_obj: NumCell, wait_handles: Cell>, + pending_uploads: NumCell, + shm_upload: RefCell, } fn flush_from(mut point: NodeRef) -> Result<(), WlSurfaceError> { @@ -243,7 +294,17 @@ impl NodeRef { } match &self.kind { EntryKind::Commit(c) => { + let mut has_unmet_dependencies = false; if c.sync_obj.get() > 0 { + has_unmet_dependencies = true; + } + if c.pending_uploads.get() > 0 { + check_shm_uploads(c)?; + if c.pending_uploads.get() > 0 { + has_unmet_dependencies = true; + } + } + if has_unmet_dependencies { return Ok(false); } c.surface.apply_state(c.pending.borrow_mut().deref_mut())?; @@ -266,6 +327,90 @@ impl NodeRef { } } +fn check_shm_uploads(c: &Commit) -> Result<(), WlSurfaceError> { + let state = &mut *c.shm_upload.borrow_mut(); + if let ShmUploadState::Todo(node_ref) = state { + let mut pending = SmallVec::new(); + schedule_async_uploads(node_ref, &c.surface, &c.pending.borrow(), &mut pending)?; + c.pending_uploads.set(pending.len()); + *state = ShmUploadState::Scheduled(pending); + } + Ok(()) +} + +fn schedule_async_uploads( + node_ref: &Rc>, + surface: &WlSurface, + pending: &PendingState, + uploads: &mut SmallVec<[PendingShmUpload; 1]>, +) -> Result<(), WlSurfaceError> { + if let Some(pending) = schedule_async_upload(node_ref, surface, pending)? { + uploads.push(pending); + } + for ss in pending.subsurfaces.values() { + if let Some(state) = &ss.pending.state { + schedule_async_uploads(node_ref, &ss.subsurface.surface, state, uploads)?; + } + } + Ok(()) +} + +fn schedule_async_upload( + node_ref: &Rc>, + surface: &WlSurface, + pending: &PendingState, +) -> Result, WlSurfaceError> { + let Some(Some(buf)) = &pending.buffer else { + return Ok(None); + }; + let Some(WlBufferStorage::Shm { mem, stride, .. }) = &*buf.storage.borrow() else { + return Ok(None); + }; + let back = surface.shm_textures.back(); + let mut back_tex_opt = back.tex.get(); + if let Some(back_tex) = &back_tex_opt { + if !back_tex.compatible_with(buf.format, buf.rect.width(), buf.rect.height(), *stride) { + back_tex_opt = None; + } + } + let damage_full = || { + back.damage.clear(); + back.damage.damage(slice::from_ref(&buf.rect)); + }; + let back_tex = match back_tex_opt { + Some(b) => { + if pending.damage_full || pending.surface_damage.is_not_empty() { + damage_full(); + } else { + back.damage.damage(&pending.buffer_damage); + } + b + } + None => { + damage_full(); + let state = &surface.client.state; + let ctx = state + .render_ctx + .get() + .ok_or(WlSurfaceError::NoRenderContext)?; + let back_tex = ctx + .async_shmem_texture( + buf.format, + buf.rect.width(), + buf.rect.height(), + *stride, + &state.cpu_worker, + ) + .map_err(WlSurfaceError::CreateAsyncShmTexture)?; + back.tex.set(Some(back_tex.clone())); + back_tex + } + }; + back_tex + .async_upload(node_ref.clone(), mem, back.damage.get()) + .map_err(WlSurfaceError::PrepareAsyncUpload) +} + type Point = (Rc, SyncObjPoint); fn consume_acquire_points(pending: &mut PendingState, points: &mut SmallVec<[Point; 1]>) { @@ -301,3 +446,16 @@ fn set_effective_timeline( } } } + +fn count_shm_uploads(pending: &PendingState, count: &mut usize) { + if let Some(Some(buffer)) = &pending.buffer { + if buffer.is_shm() { + *count += 1; + } + } + for ss in pending.subsurfaces.values() { + if let Some(state) = &ss.pending.state { + count_shm_uploads(state, count); + } + } +} diff --git a/src/rect.rs b/src/rect.rs index a5cd711c..a8bd1230 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -3,9 +3,7 @@ mod region; #[cfg(test)] mod tests; -#[expect(unused_imports)] -pub use region::DamageQueue; -pub use region::RegionBuilder; +pub use region::{DamageQueue, RegionBuilder}; use { jay_algorithms::rect::RectRaw, smallvec::SmallVec, diff --git a/src/rect/region.rs b/src/rect/region.rs index ade706c1..6ea178de 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -197,7 +197,6 @@ pub struct DamageQueue { } impl DamageQueue { - #[expect(dead_code)] pub fn new() -> [DamageQueue; N] { let datas = Rc::new(UnsafeCell::new(vec![vec!(); N])); array::from_fn(|this| DamageQueue { @@ -206,7 +205,6 @@ impl DamageQueue { }) } - #[expect(dead_code)] pub fn damage(&self, rects: &[Rect]) { let datas = unsafe { self.datas.get().deref_mut() }; for data in datas { @@ -214,13 +212,11 @@ impl DamageQueue { } } - #[expect(dead_code)] pub fn clear(&self) { let data = unsafe { &mut self.datas.get().deref_mut()[self.this] }; data.clear(); } - #[expect(dead_code)] pub fn get(&self) -> Region { let data = unsafe { &self.datas.get().deref()[self.this] }; Region::from_rects2(data) diff --git a/src/state.rs b/src/state.rs index fb44f9f6..f3f2f5b0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -215,7 +215,6 @@ pub struct State { pub enable_ei_acceptor: Cell, pub ei_clients: EiClients, pub slow_ei_clients: AsyncQueue>, - #[expect(dead_code)] pub cpu_worker: Rc, } @@ -485,7 +484,7 @@ impl State { updated_buffers.insert(buffer.buffer.id); buffer.buffer.handle_gfx_context_change(Some(surface)); } else { - surface.shm_texture.take(); + surface.reset_shm_textures(); } } for buffer in client.data.objects.buffers.lock().values() { diff --git a/src/utils/double_buffered.rs b/src/utils/double_buffered.rs index 6a60f783..9844377a 100644 --- a/src/utils/double_buffered.rs +++ b/src/utils/double_buffered.rs @@ -7,7 +7,6 @@ pub struct DoubleBuffered { } impl DoubleBuffered { - #[expect(dead_code)] pub fn new(bufs: [T; 2]) -> Self { Self { bufs, @@ -15,17 +14,14 @@ impl DoubleBuffered { } } - #[expect(dead_code)] pub fn front(&self) -> &T { unsafe { self.bufs.get_unchecked(self.front.get()) } } - #[expect(dead_code)] pub fn back(&self) -> &T { unsafe { self.bufs.get_unchecked(1 - self.front.get()) } } - #[expect(dead_code)] pub fn flip(&self) { self.front.set(1 - self.front.get()); }