atagen

monolith

public
Star0
Watch0
d9aeb9e28e74d14bc8c35504f26fb3706ba3cf2a
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/render-vk/src/staging.rsRust233 lines7.3 KB
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
}