Commit 0f11410be91b
tarball5 files changed+264-42
@@ -15,7 +15,7 @@ use smithay::reexports::wayland_server::{Client, Resource};
1515
use smithay::utils::Serial;1616
use smithay::wayland::buffer::BufferHandler;1717
use smithay::wayland::compositor::{18
- BufferAssignment, CompositorClientState, CompositorHandler, CompositorState,18
+ BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, Damage,1919
SurfaceAttributes, add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface,2020
with_states,2121
};@@ -38,7 +38,8 @@ use smithay::wayland::shell::xdg::{
3838
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,3939
};4040
use monolith_protocols::workspace::{Action, WorkspaceHandler, WorkspaceState};41
-use smithay::wayland::shm::{ShmHandler, ShmState};41
+use smithay::utils::{Rectangle, Transform};42
+use smithay::wayland::shm::{ShmHandler, ShmState, with_buffer_contents};4243
4344
impl CompositorHandler for State {4445
fn compositor_state(&mut self) -> &mut CompositorState {@@ -294,6 +295,7 @@ impl DrmSyncobjHandler for State {
294295
/// stalling on the client's GPU work: until its explicit acquire point295296
/// signals, or for implicitly synced buffers until their writes finish.296297
fn hold_until_readable(state: &mut State, surface: &WlSurface) {298
+ stage_shm(state, surface);297299
let (acquire, dmabuf) = with_states(surface, |s| {298300
let acquire = s299301
.cached_state@@ -348,6 +350,45 @@ fn hold_until_readable(state: &mut State, surface: &WlSurface) {
348350
}349351
}350352
353
+/// Copies a committed shm buffer's damage on the worker, holding the commit354
+/// until it is staged. Surface damage equals buffer damage at scale 1 with no355
+/// transform, otherwise the whole buffer is staged so it covers whatever the356
+/// import uploads.357
+fn stage_shm(state: &mut State, surface: &WlSurface) {358
+ let job = with_states(surface, |s| {359
+ let mut cached = s.cached_state.get::<SurfaceAttributes>();360
+ let attrs = cached.pending();361
+ let Some(BufferAssignment::NewBuffer(buffer)) = attrs.buffer.as_ref() else {362
+ return None;363
+ };364
+ if get_dmabuf(buffer).is_ok() {365
+ return None;366
+ }367
+ let plain = attrs.buffer_scale == 1 && attrs.buffer_transform == Transform::Normal.into();368
+ let region = attrs369
+ .damage370
+ .iter()371
+ .map(|d| match d {372
+ Damage::Buffer(r) => *r,373
+ Damage::Surface(r) => {374
+ Rectangle::new((r.loc.x, r.loc.y).into(), (r.size.w, r.size.h).into())375
+ }376
+ })377
+ .reduce(|a, b| a.merge(b))378
+ .filter(|_| plain);379
+ Some((buffer.clone(), region))380
+ });381
+ let (Some((buffer, region)), Some(client)) = (job, surface.client()) else {382
+ return;383
+ };384
+ let Ok(size) = with_buffer_contents(&buffer, |_, _, d| (d.width, d.height)) else {385
+ return;386
+ };387
+ if let Some(blocker) = state.stage_blocker(&buffer, region, size, client) {388
+ add_blocker(surface, blocker);389
+ }390
+}391
+351392
impl XdgDecorationHandler for State {352393
fn new_decoration(&mut self, toplevel: ToplevelSurface) {353394
server_side(&toplevel);@@ -1,14 +1,18 @@
1
-//! Dmabuf imports on a worker thread. A first import takes 15 to 27 ms on2
-//! NVIDIA, which on the main thread drops frames. Buffer creation answers3
-//! once the import is done, and commits of buffers still importing wait on4
-//! a blocker.1
+//! Buffer work on a worker thread. A first dmabuf import takes 15 to 27 ms2
+//! on NVIDIA, and copying a large shm damage takes 1 to 3.5 ms, either of3
+//! which on the main thread makes frames miss. Buffer creation answers once4
+//! an import is done, and commits wait on a blocker until their buffer is5
+//! ready.56
67
use crate::state::State;7
-use monolith_render_vk::{DmabufImporter, VulkanTexture};8
+use monolith_render_vk::{DmabufImporter, ShmStager, StagedShm, VulkanTexture};89
use smithay::backend::allocator::dmabuf::{Dmabuf, WeakDmabuf};910
use smithay::reexports::calloop::LoopHandle;1011
use smithay::reexports::calloop::channel::{Event, channel};11
-use smithay::reexports::wayland_server::Client;12
+use smithay::reexports::wayland_server::backend::ObjectId;13
+use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;14
+use smithay::reexports::wayland_server::{Client, Resource};15
+use smithay::utils::{Buffer, Rectangle};1216
use smithay::wayland::compositor::{Blocker, BlockerState, CompositorHandler};1317
use smithay::wayland::dmabuf::ImportNotifier;1418
use std::collections::HashMap;@@ -16,9 +20,14 @@ use std::sync::Arc;
1620
use std::sync::atomic::{AtomicBool, Ordering};1721
use std::sync::mpsc;1822
23
+/// Below this many damaged bytes a copy is cheaper than the round trip to24
+/// the worker and back.25
+const OFFLOAD_BYTES: i64 = 512 * 1024;26
+1927
pub struct Imports {20
- jobs: mpsc::Sender<Dmabuf>,21
- waiting: HashMap<WeakDmabuf, Waiters>,28
+ jobs: mpsc::Sender<Job>,29
+ dmabufs: HashMap<WeakDmabuf, Waiters>,30
+ shm: HashMap<ObjectId, Waiters>,2231
}2332
2433
#[derive(Default)]@@ -40,42 +49,61 @@ impl Blocker for ImportBlocker {
4049
}4150
}4251
43
-type Done = (Dmabuf, Result<VulkanTexture, String>);52
+enum Job {53
+ Dmabuf(Dmabuf),54
+ Shm(WlBuffer, Option<Rectangle<i32, Buffer>>),55
+}56
+57
+enum Done {58
+ Dmabuf(Dmabuf, Result<VulkanTexture, String>),59
+ Shm(WlBuffer, Result<StagedShm, String>),60
+}4461
4562
impl Imports {4663
pub fn new(4764
handle: &LoopHandle<'static, State>,4865
importer: DmabufImporter,66
+ stager: ShmStager,4967
) -> std::io::Result<Self> {5068
let (done, results) = channel::<Done>();51
- let (jobs, queue) = mpsc::channel::<Dmabuf>();69
+ let (jobs, queue) = mpsc::channel::<Job>();5270
std::thread::Builder::new()53
- .name("dmabuf-import".into())71
+ .name("buffer-import".into())5472
.spawn(move || {55
- for buffer in queue {56
- let result = importer.import(&buffer).map_err(|e| e.to_string());57
- if done.send((buffer, result)).is_err() {73
+ for job in queue {74
+ let result = match job {75
+ Job::Dmabuf(b) => {76
+ let r = importer.import(&b).map_err(|e| e.to_string());77
+ Done::Dmabuf(b, r)78
+ }79
+ Job::Shm(b, region) => {80
+ let r = stager.stage(&b, region).map_err(|e| e.to_string());81
+ Done::Shm(b, r)82
+ }83
+ };84
+ if done.send(result).is_err() {5885
break;5986
}6087
}6188
})?;6289
handle63
- .insert_source(results, |event, _, state| {64
- if let Event::Msg((buffer, result)) = event {65
- state.imported(buffer, result);66
- }90
+ .insert_source(results, |event, _, state| match event {91
+ Event::Msg(Done::Dmabuf(buffer, result)) => state.imported(buffer, result),92
+ Event::Msg(Done::Shm(buffer, result)) => state.staged(buffer, result),93
+ Event::Closed => {}6794
})6895
.map_err(|e| std::io::Error::other(e.to_string()))?;6996
Ok(Self {7097
jobs,71
- waiting: HashMap::new(),98
+ dmabufs: HashMap::new(),99
+ shm: HashMap::new(),72100
})73101
}74102
75103
fn start(&mut self, buffer: &Dmabuf) -> &mut Waiters {76104
let jobs = &self.jobs;77
- self.waiting.entry(buffer.weak()).or_insert_with(|| {78
- let _ = jobs.send(buffer.clone());105
+ self.dmabufs.entry(buffer.weak()).or_insert_with(|| {106
+ let _ = jobs.send(Job::Dmabuf(buffer.clone()));79107
Waiters::default()80108
})81109
}@@ -101,8 +129,33 @@ impl State {
101129
Some(ImportBlocker(waiters.done.clone()))102130
}103131
132
+ /// A blocker for a commit of an shm buffer with enough damage to copy on133
+ /// the worker. `region` is the damage, `None` for all of it.134
+ pub fn stage_blocker(135
+ &mut self,136
+ buffer: &WlBuffer,137
+ region: Option<Rectangle<i32, Buffer>>,138
+ size: (i32, i32),139
+ client: Client,140
+ ) -> Option<ImportBlocker> {141
+ let area = region.map_or(i64::from(size.0) * i64::from(size.1), |r| {142
+ i64::from(r.size.w) * i64::from(r.size.h)143
+ });144
+ if area * 4 < OFFLOAD_BYTES {145
+ return None;146
+ }147
+ let imports = &mut self.services.imports;148
+ let jobs = &imports.jobs;149
+ let waiters = imports.shm.entry(buffer.id()).or_insert_with(|| {150
+ let _ = jobs.send(Job::Shm(buffer.clone(), region));151
+ Waiters::default()152
+ });153
+ waiters.clients.push(client);154
+ Some(ImportBlocker(waiters.done.clone()))155
+ }156
+104157
fn imported(&mut self, buffer: Dmabuf, result: Result<VulkanTexture, String>) {105
- let Some(waiters) = self.services.imports.waiting.remove(&buffer.weak()) else {158
+ let Some(mut waiters) = self.services.imports.dmabufs.remove(&buffer.weak()) else {106159
return;107160
};108161
let result = result.and_then(|t| {@@ -110,19 +163,36 @@ impl State {
110163
.adopt_dmabuf(&buffer, t)111164
.map_err(|e| e.to_string())112165
});166
+ let notifiers = std::mem::take(&mut waiters.notifiers);113167
match result {114168
Ok(()) => {115
- for n in waiters.notifiers {169
+ for n in notifiers {116170
let _ = n.successful::<State>();117171
}118172
}119173
Err(e) => {120174
tracing::warn!("dmabuf import failed: {e}");121
- for n in waiters.notifiers {175
+ for n in notifiers {122176
n.failed();123177
}124178
}125179
}180
+ self.release(waiters);181
+ }182
+183
+ /// A failed copy only costs the import doing it on the main thread.184
+ fn staged(&mut self, buffer: WlBuffer, result: Result<StagedShm, String>) {185
+ let Some(waiters) = self.services.imports.shm.remove(&buffer.id()) else {186
+ return;187
+ };188
+ match result {189
+ Ok(staged) => self.renderer.adopt_staged(&buffer, staged),190
+ Err(e) => tracing::debug!("staging an shm buffer failed: {e}"),191
+ }192
+ self.release(waiters);193
+ }194
+195
+ fn release(&mut self, waiters: Waiters) {126196
waiters.done.store(true, Ordering::Release);127197
let dh = self.core.display.clone();128198
for client in waiters.clients {@@ -142,7 +142,11 @@ impl State {
142142
let mut layout = Layout::default();143143
layout.gaps = gaps(&config);144144
let animation = config.animation.clone();145
- let imports = crate::import::Imports::new(&handle, renderer.dmabuf_importer())?;145
+ let imports = crate::import::Imports::new(146
+ &handle,147
+ renderer.dmabuf_importer(),148
+ renderer.shm_stager(),149
+ )?;146150
let idle = crate::idle::Idle::new(&dh, handle.clone());147151
Ok(Self {148152
protocols: Protocols::new(&dh, &renderer),@@ -18,4 +18,4 @@ pub use dmabuf::VulkanAllocator;
1818
pub use error::Error;1919
pub use frame::{Effect, Transition, VulkanFrame};2020
pub use image::{VulkanImage, VulkanMapping, VulkanTarget, VulkanTexture};21
-pub use renderer::{DmabufImporter, VulkanRenderer};21
+pub use renderer::{DmabufImporter, ShmStager, StagedShm, VulkanRenderer};@@ -19,6 +19,8 @@ use smithay::backend::renderer::{
1919
Bind, ContextId, DebugFlags, ExportMem, ImportDma, ImportDmaWl, ImportMem, Offscreen, Renderer,2020
RendererSuper, Texture, TextureFilter,2121
};22
+use smithay::reexports::wayland_server::Resource;23
+use smithay::reexports::wayland_server::backend::ObjectId;2224
use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;2325
use smithay::utils::{Buffer, Physical, Rectangle, Size, Transform};2426
use smithay::wayland::compositor::SurfaceData;@@ -53,6 +55,8 @@ pub struct VulkanRenderer {
5355
caps: Arc<DmabufCaps>,5456
shared: Arc<Shared>,5557
submissions: Cell<u64>,58
+ /// Shm uploads staged off the main thread, by buffer.59
+ staged: HashMap<ObjectId, (WlBuffer, StagedShm)>,5660
stall: Cell<StallPhase>,5761
}5862
@@ -149,6 +153,7 @@ impl VulkanRenderer {
149153
pool,150154
spare_cmds: Vec::new(),151155
submissions: Cell::new(0),156
+ staged: HashMap::new(),152157
stall: Cell::new(StallPhase {153158
// Measured on an RTX 3060, see research/nvidia.md.154159
phase: shared.device.name.contains("NVIDIA").then_some(1015),@@ -513,21 +518,21 @@ impl VulkanRenderer {
513518
return Err(Error::ForeignTexture);514519
}515520
self.reap();516
- let (w, h) = (region.size.w as usize, region.size.h as usize);517
- if w == 0 || h == 0 {521
+ if region.size.w <= 0 || region.size.h <= 0 {518522
return Ok(());519523
}520
- let line = w * BYTES_PER_PIXEL;521
- let mut staging = Staging::new(522
- &self.shared,523
- line * h,524
- vk::BufferUsageFlags::TRANSFER_SRC,525
- MemoryLocation::CpuToGpu,526
- )?;527
- let dst = staging.bytes_mut();528
- for i in 0..h {529
- dst[i * line..(i + 1) * line].copy_from_slice(row(region.loc.y as usize + i));530
- }524
+ let staging = stage_rows(&self.shared, region, row)?;525
+ self.copy_staged(image, region, staging)526
+ }527
+528
+ /// Copies staged pixels into `region` of `image` on the GPU.529
+ fn copy_staged(530
+ &mut self,531
+ image: &Arc<ImageInner>,532
+ region: Rectangle<i32, Buffer>,533
+ staging: Staging,534
+ ) -> Result<(), Error> {535
+ let (w, h) = (region.size.w as usize, region.size.h as usize);531536
let copy = vk::BufferImageCopy::default()532537
.buffer_row_length(w as u32)533538
.image_subresource(@@ -1050,6 +1055,8 @@ impl ImportMemWl for VulkanRenderer {
10501055
surface: Option<&SurfaceData>,10511056
damage: &[Rectangle<i32, Buffer>],10521057
) -> Result<VulkanTexture, Error> {1058
+ self.staged.retain(|_, (b, _)| b.is_alive());1059
+ let staged = self.staged.remove(&buffer.id()).map(|(_, s)| s);10531060
let cache = surface.map(|s| {10541061
s.data_map10551062
.insert_if_missing_threadsafe(|| ShmCache(Mutex::new(None)));@@ -1093,7 +1100,15 @@ impl ImportMemWl for VulkanRenderer {
10931100
(VulkanTexture(image), Some(full))10941101
}10951102
};1096
- if let Some(region) = region {1103
+ let staged = staged.filter(|s| {1104
+ s.fourcc == fourcc1105
+ && s.size == size1106
+ && region.is_some_and(|r| s.region.contains_rect(r))1107
+ });1108
+ if let Some(s) = staged {1109
+ tracing::trace!(region = ?s.region, "uploading a staged shm copy");1110
+ self.copy_staged(&texture.0, s.region, s.staging)?;1111
+ } else if let Some(region) = region {10971112
let x = region.loc.x as usize * BYTES_PER_PIXEL;10981113
let rw = region.size.w as usize * BYTES_PER_PIXEL;10991114
self.upload_with(&texture.0, region, |y| unsafe {@@ -1111,3 +1126,95 @@ impl ImportMemWl for VulkanRenderer {
11111126
11121127
/// The texture last imported for a surface's shm buffers.11131128
struct ShmCache(Mutex<Option<VulkanTexture>>);1129
+1130
+/// Copies `region` of a full-image `row` source into new staging memory.1131
+fn stage_rows<'a>(1132
+ shared: &Arc<Shared>,1133
+ region: Rectangle<i32, Buffer>,1134
+ row: impl Fn(usize) -> &'a [u8],1135
+) -> Result<Staging, Error> {1136
+ let (w, h) = (region.size.w as usize, region.size.h as usize);1137
+ let line = w * BYTES_PER_PIXEL;1138
+ let mut staging = Staging::new(1139
+ shared,1140
+ line * h,1141
+ vk::BufferUsageFlags::TRANSFER_SRC,1142
+ MemoryLocation::CpuToGpu,1143
+ )?;1144
+ let dst = staging.bytes_mut();1145
+ for i in 0..h {1146
+ dst[i * line..(i + 1) * line].copy_from_slice(row(region.loc.y as usize + i));1147
+ }1148
+ Ok(staging)1149
+}1150
+1151
+/// Shm pixels copied off the main thread, waiting for the import of the1152
+/// commit that shows them.1153
+pub struct StagedShm {1154
+ staging: Staging,1155
+ region: Rectangle<i32, Buffer>,1156
+ fourcc: Fourcc,1157
+ size: Size<i32, Buffer>,1158
+}1159
+1160
+/// Copies shm buffers into staging memory on another thread. The renderer1161
+/// it came from takes the result back with `adopt_staged`.1162
+#[derive(Clone)]1163
+pub struct ShmStager {1164
+ shared: Arc<Shared>,1165
+}1166
+1167
+impl ShmStager {1168
+ /// Stages `region` of `buffer`, all of it when the region is `None`.1169
+ pub fn stage(1170
+ &self,1171
+ buffer: &WlBuffer,1172
+ region: Option<Rectangle<i32, Buffer>>,1173
+ ) -> Result<StagedShm, Error> {1174
+ with_buffer_contents(buffer, |ptr, len, data| {1175
+ let fourcc = shm_format_to_fourcc(data.format).ok_or(Error::ShmAccess)?;1176
+ let (w, h, stride, offset) = (1177
+ data.width as usize,1178
+ data.height as usize,1179
+ data.stride as usize,1180
+ data.offset as usize,1181
+ );1182
+ let line = w * BYTES_PER_PIXEL;1183
+ if w == 0 || h == 0 || stride < line || offset + stride * (h - 1) + line > len {1184
+ return Err(Error::ShmAccess);1185
+ }1186
+ let size = Size::from((data.width, data.height));1187
+ let full = Rectangle::from_size(size);1188
+ let region = region.and_then(|r| r.intersection(full)).unwrap_or(full);1189
+ let x = region.loc.x as usize * BYTES_PER_PIXEL;1190
+ let rw = region.size.w as usize * BYTES_PER_PIXEL;1191
+ // SAFETY: the bounds check above keeps every row inside the1192
+ // `len` bytes smithay mapped at `ptr`.1193
+ let staging = stage_rows(&self.shared, region, |y| unsafe {1194
+ std::slice::from_raw_parts(ptr.add(offset + y * stride + x), rw)1195
+ })?;1196
+ Ok(StagedShm {1197
+ staging,1198
+ region,1199
+ fourcc,1200
+ size,1201
+ })1202
+ })1203
+ .map_err(|_| Error::ShmAccess)?1204
+ }1205
+}1206
+1207
+impl VulkanRenderer {1208
+ pub fn shm_stager(&self) -> ShmStager {1209
+ ShmStager {1210
+ shared: self.shared.clone(),1211
+ }1212
+ }1213
+1214
+ /// Hands back pixels a stager copied, used by the next import of `buffer`.1215
+ pub fn adopt_staged(&mut self, buffer: &WlBuffer, staged: StagedShm) {1216
+ if Arc::ptr_eq(&staged.staging.shared, &self.shared) {1217
+ self.staged.insert(buffer.id(), (buffer.clone(), staged));1218
+ }1219
+ }1220
+}