From c87bc697efe2ec0d947a28ba40ee7d71a138c22b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 7 Sep 2024 16:52:03 +0200 Subject: [PATCH] surface: use async uploads for shm buffers --- src/gfx_api.rs | 4 - src/ifs/wl_buffer.rs | 57 ++++---- src/ifs/wl_surface.rs | 53 +++++--- src/ifs/wl_surface/commit_timeline.rs | 187 +++++++++++++++++++++++--- src/rect.rs | 4 +- src/rect/region.rs | 4 - src/state.rs | 3 +- src/utils/double_buffered.rs | 4 - 8 files changed, 243 insertions(+), 73 deletions(-) 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..cbba4d52 100644 --- a/src/ifs/wl_surface/commit_timeline.rs +++ b/src/ifs/wl_surface/commit_timeline.rs @@ -1,6 +1,11 @@ use { crate::{ - ifs::wl_surface::{PendingState, WlSurface, WlSurfaceError}, + clientmem::ClientMemOffset, + gfx_api::{AsyncShmGfxTextureCallback, GfxError, PendingShmUpload}, + ifs::{ + wl_buffer::{WlBuffer, WlBufferStorage}, + wl_surface::{PendingState, WlSurface, WlSurfaceError}, + }, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, @@ -14,13 +19,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 +82,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 +127,9 @@ fn break_loops(list: &LinkedList) { entry.link.take(); if let EntryKind::Commit(c) = &entry.kind { c.wait_handles.take(); + if let Some(shm) = &c.shm_upload { + *shm.borrow_mut() = ShmUploadState::Done; + } } } } @@ -145,7 +156,15 @@ 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 shm_upload = None; + let mut shm_upload_data = None; + if let Some(Some(buffer)) = &pending.buffer { + if let Some(WlBufferStorage::Shm { mem, stride, .. }) = &*buffer.storage.borrow() { + shm_upload = Some(RefCell::new(ShmUploadState::Done)); + shm_upload_data = Some((buffer.clone(), mem.clone(), *stride)); + } + } + if points.is_empty() && self.own_timeline.entries.is_empty() && shm_upload.is_none() { return surface .apply_state(pending) .map_err(CommitTimelineError::ImmediateCommit); @@ -162,23 +181,39 @@ impl CommitTimeline { pending: RefCell::new(mem::take(pending)), sync_obj: NumCell::new(points.len()), wait_handles: Cell::new(Default::default()), + shm_upload, }), ); - 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() || shm_upload_data.is_some() { + 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 let Some((buffer, mem, stride)) = shm_upload_data { + *commit.shm_upload.as_ref().unwrap().borrow_mut() = ShmUploadState::Todo { + node_ref: noderef.clone(), + buf: buffer, + stride, + mem, + }; + needs_flush = true; + } + } + if needs_flush && noderef.prev().is_none() { + flush_from(noderef.clone()).map_err(CommitTimelineError::DelayedCommit)?; } Ok(()) } @@ -194,12 +229,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.shm_upload.as_ref().unwrap().borrow_mut() = ShmUploadState::Done; + flush_commit(&self, commit); } } @@ -216,11 +272,25 @@ enum EntryKind { Gc(CommitTimelineId), } +enum ShmUploadState { + Todo { + node_ref: Rc>, + buf: Rc, + stride: i32, + mem: Rc, + }, + Scheduled { + _pending: PendingShmUpload, + }, + Done, +} + struct Commit { surface: Rc, pending: RefCell>, sync_obj: NumCell, wait_handles: Cell>, + shm_upload: Option>, } fn flush_from(mut point: NodeRef) -> Result<(), WlSurfaceError> { @@ -243,7 +313,35 @@ 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 let Some(state) = &c.shm_upload { + let state = &mut *state.borrow_mut(); + match state { + ShmUploadState::Todo { + node_ref, + buf, + stride, + mem, + } => { + let pending = schedule_async_upload(node_ref, c, buf, mem, *stride)?; + *state = match pending { + None => ShmUploadState::Done, + Some(pending) => { + has_unmet_dependencies = true; + ShmUploadState::Scheduled { _pending: pending } + } + }; + } + ShmUploadState::Scheduled { .. } => { + has_unmet_dependencies = true; + } + ShmUploadState::Done => {} + } + } + if has_unmet_dependencies { return Ok(false); } c.surface.apply_state(c.pending.borrow_mut().deref_mut())?; @@ -266,6 +364,59 @@ impl NodeRef { } } +fn schedule_async_upload( + node_ref: &Rc>, + c: &Commit, + buf: &WlBuffer, + mem: &Rc, + stride: i32, +) -> Result, WlSurfaceError> { + let back = c.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) => { + let pending = &*c.pending.borrow(); + 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 = &c.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]>) { 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()); }