| 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 | pub(crate) 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 | /// Granularity of flushes and invalidations of non-coherent memory. |
| 29 | atom: u64, |
| 30 | } |
| 31 | |
| 32 | impl StagingPool { |
| 33 | pub fn new(shared: &Arc<Shared>) -> Arc<Self> { |
| 34 | let limits = unsafe { |
| 35 | shared |
| 36 | .device |
| 37 | .instance |
| 38 | .get_physical_device_properties(shared.device.physical) |
| 39 | } |
| 40 | .limits; |
| 41 | Arc::new(Self { |
| 42 | shared: shared.clone(), |
| 43 | free: Mutex::new(Vec::new()), |
| 44 | atom: limits.non_coherent_atom_size.max(1), |
| 45 | }) |
| 46 | } |
| 47 | |
| 48 | /// Frees pooled buffers that have sat unused for [`POOL_IDLE`]. |
| 49 | pub fn trim(&self) { |
| 50 | let now = Instant::now(); |
| 51 | let expired: Vec<Pooled> = self |
| 52 | .free |
| 53 | .lock() |
| 54 | .unwrap_or_else(|e| e.into_inner()) |
| 55 | .extract_if(.., |p| now - p.returned >= POOL_IDLE) |
| 56 | .collect(); |
| 57 | self.destroy(expired.into_iter().map(|p| (p.buffer, p.allocation))); |
| 58 | } |
| 59 | |
| 60 | fn destroy(&self, buffers: impl IntoIterator<Item = (vk::Buffer, Allocation)>) { |
| 61 | let mut buffers = buffers.into_iter().peekable(); |
| 62 | if buffers.peek().is_none() { |
| 63 | return; |
| 64 | } |
| 65 | let mut allocator = self |
| 66 | .shared |
| 67 | .allocator |
| 68 | .lock() |
| 69 | .unwrap_or_else(|e| e.into_inner()); |
| 70 | for (buffer, allocation) in buffers { |
| 71 | unsafe { self.shared.device.device.destroy_buffer(buffer, None) }; |
| 72 | let _ = allocator.free(allocation); |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | impl Drop for StagingPool { |
| 78 | fn drop(&mut self) { |
| 79 | let free = std::mem::take(self.free.get_mut().unwrap_or_else(|e| e.into_inner())); |
| 80 | self.destroy(free.into_iter().map(|p| (p.buffer, p.allocation))); |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | /// A host-visible buffer for uploads and readbacks. Dropped ones go back to |
| 85 | /// a pool instead of being freed. Allocating and freeing large host-visible |
| 86 | /// memory takes a driver lock on NVIDIA that `vkQueueSubmit` also waits on, |
| 87 | /// so a staging buffer made per upload on the import worker stalled the main |
| 88 | /// thread's submits for 1 to 3 ms. |
| 89 | pub(crate) struct Staging { |
| 90 | pub pool: Arc<StagingPool>, |
| 91 | pub buffer: vk::Buffer, |
| 92 | allocation: Option<Allocation>, |
| 93 | size: usize, |
| 94 | usage: vk::BufferUsageFlags, |
| 95 | location: MemoryLocation, |
| 96 | } |
| 97 | |
| 98 | impl Staging { |
| 99 | pub fn new( |
| 100 | pool: &Arc<StagingPool>, |
| 101 | size: usize, |
| 102 | usage: vk::BufferUsageFlags, |
| 103 | location: MemoryLocation, |
| 104 | ) -> Result<Self, Error> { |
| 105 | let reused = { |
| 106 | let mut free = pool.free.lock().unwrap_or_else(|e| e.into_inner()); |
| 107 | let fits = |p: &Pooled| { |
| 108 | p.usage == usage && p.location == location && p.size >= size && p.size <= size * 2 |
| 109 | }; |
| 110 | free.iter().position(fits).map(|i| free.swap_remove(i)) |
| 111 | }; |
| 112 | if let Some(p) = reused { |
| 113 | return Ok(Self { |
| 114 | pool: pool.clone(), |
| 115 | buffer: p.buffer, |
| 116 | allocation: Some(p.allocation), |
| 117 | size: p.size, |
| 118 | usage, |
| 119 | location, |
| 120 | }); |
| 121 | } |
| 122 | let d = &pool.shared.device.device; |
| 123 | let buffer = unsafe { |
| 124 | d.create_buffer( |
| 125 | &vk::BufferCreateInfo::default() |
| 126 | .size(size as u64) |
| 127 | .usage(usage), |
| 128 | None, |
| 129 | )? |
| 130 | }; |
| 131 | let mut requirements = unsafe { d.get_buffer_memory_requirements(buffer) }; |
| 132 | requirements.size = requirements.size.next_multiple_of(pool.atom); |
| 133 | requirements.alignment = requirements.alignment.max(pool.atom); |
| 134 | let allocation = pool |
| 135 | .shared |
| 136 | .allocator |
| 137 | .lock() |
| 138 | .unwrap_or_else(|e| e.into_inner()) |
| 139 | .allocate(&AllocationCreateDesc { |
| 140 | name: "staging", |
| 141 | requirements, |
| 142 | location, |
| 143 | linear: true, |
| 144 | allocation_scheme: AllocationScheme::GpuAllocatorManaged, |
| 145 | }); |
| 146 | let allocation = match allocation { |
| 147 | Ok(a) => a, |
| 148 | Err(e) => { |
| 149 | unsafe { d.destroy_buffer(buffer, None) }; |
| 150 | return Err(e.into()); |
| 151 | } |
| 152 | }; |
| 153 | unsafe { d.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())? }; |
| 154 | Ok(Self { |
| 155 | pool: pool.clone(), |
| 156 | buffer, |
| 157 | allocation: Some(allocation), |
| 158 | size, |
| 159 | usage, |
| 160 | location, |
| 161 | }) |
| 162 | } |
| 163 | |
| 164 | /// The mapped bytes, with whatever the GPU wrote to them made visible. |
| 165 | pub fn bytes(&self) -> Result<&[u8], Error> { |
| 166 | self.host_sync(false)?; |
| 167 | self.allocation |
| 168 | .as_ref() |
| 169 | .and_then(|a| a.mapped_slice()) |
| 170 | .ok_or(Error::StagingUnmapped) |
| 171 | } |
| 172 | |
| 173 | /// Makes what the host wrote through `bytes_mut` visible to the GPU. |
| 174 | pub fn flush(&self) -> Result<(), Error> { |
| 175 | self.host_sync(true) |
| 176 | } |
| 177 | |
| 178 | /// Flushes or invalidates the mapping. Coherent memory needs neither. |
| 179 | fn host_sync(&self, flush: bool) -> Result<(), Error> { |
| 180 | let Some(allocation) = self.allocation.as_ref() else { |
| 181 | return Ok(()); |
| 182 | }; |
| 183 | if allocation |
| 184 | .memory_properties() |
| 185 | .contains(vk::MemoryPropertyFlags::HOST_COHERENT) |
| 186 | { |
| 187 | return Ok(()); |
| 188 | } |
| 189 | let range = [vk::MappedMemoryRange::default() |
| 190 | .memory(unsafe { allocation.memory() }) |
| 191 | .offset(allocation.offset()) |
| 192 | .size(allocation.size())]; |
| 193 | let d = &self.pool.shared.device.device; |
| 194 | unsafe { |
| 195 | if flush { |
| 196 | d.flush_mapped_memory_ranges(&range)?; |
| 197 | } else { |
| 198 | d.invalidate_mapped_memory_ranges(&range)?; |
| 199 | } |
| 200 | } |
| 201 | Ok(()) |
| 202 | } |
| 203 | |
| 204 | pub fn bytes_mut(&mut self) -> Result<&mut [u8], Error> { |
| 205 | self.allocation |
| 206 | .as_mut() |
| 207 | .and_then(|a| a.mapped_slice_mut()) |
| 208 | .ok_or(Error::StagingUnmapped) |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | impl Drop for Staging { |
| 213 | fn drop(&mut self) { |
| 214 | let Some(allocation) = self.allocation.take() else { |
| 215 | return; |
| 216 | }; |
| 217 | let mut free = self.pool.free.lock().unwrap_or_else(|e| e.into_inner()); |
| 218 | let pooled: usize = free.iter().map(|p| p.size).sum(); |
| 219 | if pooled + self.size <= POOL_BYTES { |
| 220 | free.push(Pooled { |
| 221 | buffer: self.buffer, |
| 222 | allocation, |
| 223 | size: self.size, |
| 224 | usage: self.usage, |
| 225 | location: self.location, |
| 226 | returned: Instant::now(), |
| 227 | }); |
| 228 | return; |
| 229 | } |
| 230 | drop(free); |
| 231 | self.pool.destroy([(self.buffer, allocation)]); |
| 232 | } |
| 233 | } |