atagen

monolith

public
Star0
Watch0

Commit e0b486d6097f

tarball

atagen <boss@atagen.co>2026-10-19 07:56 UTC

6 files changed+204-193
render-vk: move the staging pool into its own module

Shared held a pool of image.rs types, so device.rs and image.rs depended on each other. The pool is now a StagingPool that owns an Arc<Shared> and that the renderer and ShmStager hold, and one destroy path replaces the three copies in Shared::drop, trim_staging and Staging::drop.
Modified file: crates/render-vk/src/device.rs−18 deletions
@@ -8,27 +8,10 @@ use std::sync::Mutex;
88 /// Fields drop in declaration order, so the allocator frees its memory before
99 /// the device is destroyed.
1010 pub(crate) struct Shared {
11- /// Staging buffers kept for reuse, see `Staging`.
12- pub staging_pool: Mutex<Vec<crate::image::Pooled>>,
1311 pub allocator: Mutex<Allocator>,
1412 pub device: Device,
1513 }
1614
17-impl Drop for Shared {
18- fn drop(&mut self) {
19- let pool = std::mem::take(
20- self.staging_pool
21- .get_mut()
22- .unwrap_or_else(|e| e.into_inner()),
23- );
24- let allocator = self.allocator.get_mut().unwrap_or_else(|e| e.into_inner());
25- for p in pool {
26- unsafe { self.device.device.destroy_buffer(p.buffer, None) };
27- let _ = allocator.free(p.allocation);
28- }
29- }
30-}
31-
3215 /// Loaders for the extensions that dmabuf import, export and fence export
3316 /// need. Absent when the device lacks any of them.
3417 pub(crate) struct DmabufExt {
@@ -219,7 +202,6 @@ impl Shared {
219202 spare_fences: Mutex::new(Vec::new()),
220203 };
221204 Ok(Self {
222- staging_pool: Mutex::new(Vec::new()),
223205 allocator: Mutex::new(allocator?),
224206 device,
225207 })
Modified file: crates/render-vk/src/image.rs−163 deletions
@@ -9,7 +9,6 @@ use smithay::backend::renderer::{Texture, TextureMapping};
99 use std::fmt;
1010 use std::marker::PhantomData;
1111 use std::sync::{Arc, Mutex};
12-use std::time::{Duration, Instant};
1312
1413 pub(crate) enum Memory {
1514 Allocated(Allocation),
@@ -181,168 +180,6 @@ pub(crate) fn color_range() -> vk::ImageSubresourceRange {
181180 .layer_count(1)
182181 }
183182
184-/// A host-visible buffer for uploads and readbacks. Dropped ones go back to
185-/// a pool on `Shared` instead of being freed. Allocating and freeing large
186-/// host-visible memory takes a driver lock on NVIDIA that `vkQueueSubmit`
187-/// also waits on, so a staging buffer made per upload on the import worker
188-/// stalled the main thread's submits for 1 to 3 ms.
189-pub(crate) struct Staging {
190- pub shared: Arc<Shared>,
191- pub buffer: vk::Buffer,
192- pub allocation: Option<Allocation>,
193- size: usize,
194- usage: vk::BufferUsageFlags,
195- location: MemoryLocation,
196-}
197-
198-pub(crate) struct Pooled {
199- pub buffer: vk::Buffer,
200- pub allocation: Allocation,
201- size: usize,
202- usage: vk::BufferUsageFlags,
203- location: MemoryLocation,
204- returned: Instant,
205-}
206-
207-/// Pooled staging memory kept at most, past which dropped buffers are freed.
208-const POOL_BYTES: usize = 32 * 1024 * 1024;
209-
210-/// How long a pooled buffer may go unused before it is freed.
211-const POOL_IDLE: Duration = Duration::from_secs(2);
212-
213-/// Frees pooled staging buffers that have sat unused for [`POOL_IDLE`].
214-pub(crate) fn trim_staging(shared: &Shared) {
215- let now = Instant::now();
216- let expired: Vec<Pooled> = shared
217- .staging_pool
218- .lock()
219- .unwrap_or_else(|e| e.into_inner())
220- .extract_if(.., |p| now - p.returned >= POOL_IDLE)
221- .collect();
222- if expired.is_empty() {
223- return;
224- }
225- let mut allocator = shared.allocator.lock().unwrap_or_else(|e| e.into_inner());
226- for p in expired {
227- unsafe { shared.device.device.destroy_buffer(p.buffer, None) };
228- let _ = allocator.free(p.allocation);
229- }
230-}
231-
232-impl Staging {
233- pub fn new(
234- shared: &Arc<Shared>,
235- size: usize,
236- usage: vk::BufferUsageFlags,
237- location: MemoryLocation,
238- ) -> Result<Self, Error> {
239- let reused = {
240- let mut pool = shared
241- .staging_pool
242- .lock()
243- .unwrap_or_else(|e| e.into_inner());
244- let fits = |p: &Pooled| {
245- p.usage == usage && p.location == location && p.size >= size && p.size <= size * 2
246- };
247- pool.iter().position(fits).map(|i| pool.swap_remove(i))
248- };
249- if let Some(p) = reused {
250- return Ok(Self {
251- shared: shared.clone(),
252- buffer: p.buffer,
253- allocation: Some(p.allocation),
254- size: p.size,
255- usage,
256- location,
257- });
258- }
259- let d = &shared.device.device;
260- let buffer = unsafe {
261- d.create_buffer(
262- &vk::BufferCreateInfo::default()
263- .size(size as u64)
264- .usage(usage),
265- None,
266- )?
267- };
268- let requirements = unsafe { d.get_buffer_memory_requirements(buffer) };
269- let allocation = shared
270- .allocator
271- .lock()
272- .unwrap_or_else(|e| e.into_inner())
273- .allocate(&AllocationCreateDesc {
274- name: "staging",
275- requirements,
276- location,
277- linear: true,
278- allocation_scheme: AllocationScheme::GpuAllocatorManaged,
279- });
280- let allocation = match allocation {
281- Ok(a) => a,
282- Err(e) => {
283- unsafe { d.destroy_buffer(buffer, None) };
284- return Err(e.into());
285- }
286- };
287- unsafe { d.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())? };
288- Ok(Self {
289- shared: shared.clone(),
290- buffer,
291- allocation: Some(allocation),
292- size,
293- usage,
294- location,
295- })
296- }
297-
298- pub fn bytes(&self) -> &[u8] {
299- self.allocation
300- .as_ref()
301- .and_then(|a| a.mapped_slice())
302- .unwrap_or(&[])
303- }
304-
305- pub fn bytes_mut(&mut self) -> &mut [u8] {
306- self.allocation
307- .as_mut()
308- .and_then(|a| a.mapped_slice_mut())
309- .unwrap_or(&mut [])
310- }
311-}
312-
313-impl Drop for Staging {
314- fn drop(&mut self) {
315- let Some(allocation) = self.allocation.take() else {
316- return;
317- };
318- let mut pool = self
319- .shared
320- .staging_pool
321- .lock()
322- .unwrap_or_else(|e| e.into_inner());
323- let pooled: usize = pool.iter().map(|p| p.size).sum();
324- if pooled + self.size <= POOL_BYTES {
325- pool.push(Pooled {
326- buffer: self.buffer,
327- allocation,
328- size: self.size,
329- usage: self.usage,
330- location: self.location,
331- returned: Instant::now(),
332- });
333- return;
334- }
335- drop(pool);
336- unsafe { self.shared.device.device.destroy_buffer(self.buffer, None) };
337- let _ = self
338- .shared
339- .allocator
340- .lock()
341- .unwrap_or_else(|e| e.into_inner())
342- .free(allocation);
343- }
344-}
345-
346183 /// A sampled texture.
347184 #[derive(Clone, Debug)]
348185 pub struct VulkanTexture(pub(crate) Arc<ImageInner>);
Modified file: crates/render-vk/src/lib.rs+1 additions
@@ -18,6 +18,7 @@ mod frame;
1818 mod image;
1919 mod pipeline;
2020 mod renderer;
21+mod staging;
2122 mod sync;
2223 mod transform;
2324
Modified file: crates/render-vk/src/renderer/mod.rs+7 additions−4 deletions
@@ -10,8 +10,9 @@ use crate::device::Shared;
1010 use crate::dmabuf::{self, DmabufCaps, RENDER_USAGE, SAMPLE_USAGE, VulkanAllocator};
1111 use crate::format::{BYTES_PER_PIXEL, SUPPORTED};
1212 use crate::frame::{Op, VulkanFrame};
13-use crate::image::{ImageInner, Staging, VulkanImage, VulkanMapping, VulkanTarget, VulkanTexture};
13+use crate::image::{ImageInner, VulkanImage, VulkanMapping, VulkanTarget, VulkanTexture};
1414 use crate::pipeline::Pipelines;
15+use crate::staging::{Staging, StagingPool};
1516 use crate::sync::{FenceInner, VkFence};
1617 use ash::vk;
1718 use gpu_allocator::MemoryLocation;
@@ -55,6 +56,7 @@ pub struct VulkanRenderer {
5556 debug: DebugFlags,
5657 context: ContextId<VulkanTexture>,
5758 caps: Arc<DmabufCaps>,
59+ staging_pool: Arc<StagingPool>,
5860 shared: Arc<Shared>,
5961 submissions: Cell<u64>,
6062 /// Shm uploads staged off the main thread, by buffer.
@@ -130,6 +132,7 @@ impl VulkanRenderer {
130132 debug: DebugFlags::empty(),
131133 context: ContextId::new(),
132134 caps: Arc::new(DmabufCaps::query(&shared)),
135+ staging_pool: StagingPool::new(&shared),
133136 shared,
134137 })
135138 }
@@ -187,7 +190,7 @@ impl VulkanRenderer {
187190 self.dmabuf_textures.retain(|k, _| !k.is_gone());
188191 self.dmabuf_targets.retain(|k, _| !k.is_gone());
189192 self.staged.retain(|_, (b, _)| b.is_alive());
190- crate::image::trim_staging(&self.shared);
193+ self.staging_pool.trim();
191194 }
192195
193196 fn begin(&mut self) -> Result<vk::CommandBuffer, Error> {
@@ -467,7 +470,7 @@ impl VulkanRenderer {
467470 if region.size.w <= 0 || region.size.h <= 0 {
468471 return Ok(());
469472 }
470- let staging = stage_rows(&self.shared, region, row)?;
473+ let staging = stage_rows(&self.staging_pool, region, row)?;
471474 self.copy_staged(image, region, staging)
472475 }
473476
@@ -541,7 +544,7 @@ impl VulkanRenderer {
541544 let swap = needs_swizzle(image.fourcc, fourcc)?;
542545 let len = (w as usize * h as usize * BYTES_PER_PIXEL).max(BYTES_PER_PIXEL);
543546 let staging = Staging::new(
544- &self.shared,
547+ &self.staging_pool,
545548 len,
546549 vk::BufferUsageFlags::TRANSFER_DST,
547550 MemoryLocation::GpuToCpu,
Modified file: crates/render-vk/src/renderer/shm.rs+8 additions−8 deletions
@@ -1,8 +1,8 @@
11 use super::VulkanRenderer;
22 use crate::Error;
3-use crate::device::Shared;
43 use crate::format::BYTES_PER_PIXEL;
5-use crate::image::{ImageInner, Staging, VulkanTexture};
4+use crate::image::{ImageInner, VulkanTexture};
5+use crate::staging::{Staging, StagingPool};
66 use ash::vk;
77 use gpu_allocator::MemoryLocation;
88 use smithay::backend::allocator::Fourcc;
@@ -100,14 +100,14 @@ struct ShmCache(Mutex<Option<VulkanTexture>>);
100100
101101 /// Copies `region` of a full-image `row` source into new staging memory.
102102 pub(super) fn stage_rows<'a>(
103- shared: &Arc<Shared>,
103+ pool: &Arc<StagingPool>,
104104 region: Rectangle<i32, Buffer>,
105105 row: impl Fn(usize) -> &'a [u8],
106106 ) -> Result<Staging, Error> {
107107 let (w, h) = (region.size.w as usize, region.size.h as usize);
108108 let line = w * BYTES_PER_PIXEL;
109109 let mut staging = Staging::new(
110- shared,
110+ pool,
111111 line * h,
112112 vk::BufferUsageFlags::TRANSFER_SRC,
113113 MemoryLocation::CpuToGpu,
@@ -132,7 +132,7 @@ pub struct StagedShm {
132132 /// it came from takes the result back with `adopt_staged`.
133133 #[derive(Clone)]
134134 pub struct ShmStager {
135- shared: Arc<Shared>,
135+ staging_pool: Arc<StagingPool>,
136136 }
137137
138138 impl ShmStager {
@@ -161,7 +161,7 @@ impl ShmStager {
161161 let rw = region.size.w as usize * BYTES_PER_PIXEL;
162162 // SAFETY: the bounds check above keeps every row inside the
163163 // `len` bytes smithay mapped at `ptr`.
164- let staging = stage_rows(&self.shared, region, |y| unsafe {
164+ let staging = stage_rows(&self.staging_pool, region, |y| unsafe {
165165 std::slice::from_raw_parts(ptr.add(offset + y * stride + x), rw)
166166 })?;
167167 Ok(StagedShm {
@@ -178,13 +178,13 @@ impl ShmStager {
178178 impl VulkanRenderer {
179179 pub fn shm_stager(&self) -> ShmStager {
180180 ShmStager {
181- shared: self.shared.clone(),
181+ staging_pool: self.staging_pool.clone(),
182182 }
183183 }
184184
185185 /// Hands back pixels a stager copied, used by the next import of `buffer`.
186186 pub fn adopt_staged(&mut self, buffer: &WlBuffer, staged: StagedShm) {
187- if Arc::ptr_eq(&staged.staging.shared, &self.shared) {
187+ if Arc::ptr_eq(&staged.staging.pool, &self.staging_pool) {
188188 self.staged.insert(buffer.id(), (buffer.clone(), staged));
189189 }
190190 }
Added file: crates/render-vk/src/staging.rs+188 additions
@@ -0,0 +1,188 @@
1+use crate::Error;
2+use crate::device::Shared;
3+use ash::vk;
4+use gpu_allocator::MemoryLocation;
5+use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme};
6+use std::sync::{Arc, Mutex};
7+use std::time::{Duration, Instant};
8+
9+/// Pooled staging memory kept at most, past which dropped buffers are freed.
10+const POOL_BYTES: usize = 32 * 1024 * 1024;
11+
12+/// How long a pooled buffer may go unused before it is freed.
13+const POOL_IDLE: Duration = Duration::from_secs(2);
14+
15+struct Pooled {
16+ buffer: vk::Buffer,
17+ allocation: Allocation,
18+ size: usize,
19+ usage: vk::BufferUsageFlags,
20+ location: MemoryLocation,
21+ returned: Instant,
22+}
23+
24+/// Staging buffers kept for reuse, see [`Staging`].
25+pub(crate) struct StagingPool {
26+ shared: Arc<Shared>,
27+ free: Mutex<Vec<Pooled>>,
28+}
29+
30+impl StagingPool {
31+ pub fn new(shared: &Arc<Shared>) -> Arc<Self> {
32+ Arc::new(Self {
33+ shared: shared.clone(),
34+ free: Mutex::new(Vec::new()),
35+ })
36+ }
37+
38+ /// Frees pooled buffers that have sat unused for [`POOL_IDLE`].
39+ pub fn trim(&self) {
40+ let now = Instant::now();
41+ let expired: Vec<Pooled> = self
42+ .free
43+ .lock()
44+ .unwrap_or_else(|e| e.into_inner())
45+ .extract_if(.., |p| now - p.returned >= POOL_IDLE)
46+ .collect();
47+ self.destroy(expired.into_iter().map(|p| (p.buffer, p.allocation)));
48+ }
49+
50+ fn destroy(&self, buffers: impl IntoIterator<Item = (vk::Buffer, Allocation)>) {
51+ let mut buffers = buffers.into_iter().peekable();
52+ if buffers.peek().is_none() {
53+ return;
54+ }
55+ let mut allocator = self
56+ .shared
57+ .allocator
58+ .lock()
59+ .unwrap_or_else(|e| e.into_inner());
60+ for (buffer, allocation) in buffers {
61+ unsafe { self.shared.device.device.destroy_buffer(buffer, None) };
62+ let _ = allocator.free(allocation);
63+ }
64+ }
65+}
66+
67+impl Drop for StagingPool {
68+ fn drop(&mut self) {
69+ let free = std::mem::take(self.free.get_mut().unwrap_or_else(|e| e.into_inner()));
70+ self.destroy(free.into_iter().map(|p| (p.buffer, p.allocation)));
71+ }
72+}
73+
74+/// A host-visible buffer for uploads and readbacks. Dropped ones go back to
75+/// a pool instead of being freed. Allocating and freeing large host-visible
76+/// memory takes a driver lock on NVIDIA that `vkQueueSubmit` also waits on,
77+/// so a staging buffer made per upload on the import worker stalled the main
78+/// thread's submits for 1 to 3 ms.
79+pub(crate) struct Staging {
80+ pub pool: Arc<StagingPool>,
81+ pub buffer: vk::Buffer,
82+ allocation: Option<Allocation>,
83+ size: usize,
84+ usage: vk::BufferUsageFlags,
85+ location: MemoryLocation,
86+}
87+
88+impl Staging {
89+ pub fn new(
90+ pool: &Arc<StagingPool>,
91+ size: usize,
92+ usage: vk::BufferUsageFlags,
93+ location: MemoryLocation,
94+ ) -> Result<Self, Error> {
95+ let reused = {
96+ let mut free = pool.free.lock().unwrap_or_else(|e| e.into_inner());
97+ let fits = |p: &Pooled| {
98+ p.usage == usage && p.location == location && p.size >= size && p.size <= size * 2
99+ };
100+ free.iter().position(fits).map(|i| free.swap_remove(i))
101+ };
102+ if let Some(p) = reused {
103+ return Ok(Self {
104+ pool: pool.clone(),
105+ buffer: p.buffer,
106+ allocation: Some(p.allocation),
107+ size: p.size,
108+ usage,
109+ location,
110+ });
111+ }
112+ let d = &pool.shared.device.device;
113+ let buffer = unsafe {
114+ d.create_buffer(
115+ &vk::BufferCreateInfo::default()
116+ .size(size as u64)
117+ .usage(usage),
118+ None,
119+ )?
120+ };
121+ let requirements = unsafe { d.get_buffer_memory_requirements(buffer) };
122+ let allocation = pool
123+ .shared
124+ .allocator
125+ .lock()
126+ .unwrap_or_else(|e| e.into_inner())
127+ .allocate(&AllocationCreateDesc {
128+ name: "staging",
129+ requirements,
130+ location,
131+ linear: true,
132+ allocation_scheme: AllocationScheme::GpuAllocatorManaged,
133+ });
134+ let allocation = match allocation {
135+ Ok(a) => a,
136+ Err(e) => {
137+ unsafe { d.destroy_buffer(buffer, None) };
138+ return Err(e.into());
139+ }
140+ };
141+ unsafe { d.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())? };
142+ Ok(Self {
143+ pool: pool.clone(),
144+ buffer,
145+ allocation: Some(allocation),
146+ size,
147+ usage,
148+ location,
149+ })
150+ }
151+
152+ pub fn bytes(&self) -> &[u8] {
153+ self.allocation
154+ .as_ref()
155+ .and_then(|a| a.mapped_slice())
156+ .unwrap_or(&[])
157+ }
158+
159+ pub fn bytes_mut(&mut self) -> &mut [u8] {
160+ self.allocation
161+ .as_mut()
162+ .and_then(|a| a.mapped_slice_mut())
163+ .unwrap_or(&mut [])
164+ }
165+}
166+
167+impl Drop for Staging {
168+ fn drop(&mut self) {
169+ let Some(allocation) = self.allocation.take() else {
170+ return;
171+ };
172+ let mut free = self.pool.free.lock().unwrap_or_else(|e| e.into_inner());
173+ let pooled: usize = free.iter().map(|p| p.size).sum();
174+ if pooled + self.size <= POOL_BYTES {
175+ free.push(Pooled {
176+ buffer: self.buffer,
177+ allocation,
178+ size: self.size,
179+ usage: self.usage,
180+ location: self.location,
181+ returned: Instant::now(),
182+ });
183+ return;
184+ }
185+ drop(free);
186+ self.pool.destroy([(self.buffer, allocation)]);
187+ }
188+}