atagen

monolith

public
Star0
Watch0
fb4bcf5b1d2997b77772b46418128ee2b11bc045
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/render-vk/src/staging.rsRust232 lines7.2 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
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 requirements = unsafe { d.get_buffer_memory_requirements(buffer) };
132
        let allocation = pool
133
            .shared
134
            .allocator
135
            .lock()
136
            .unwrap_or_else(|e| e.into_inner())
137
            .allocate(&AllocationCreateDesc {
138
                name: "staging",
139
                requirements,
140
                location,
141
                linear: true,
142
                allocation_scheme: AllocationScheme::GpuAllocatorManaged,
143
            });
144
        let allocation = match allocation {
145
            Ok(a) => a,
146
            Err(e) => {
147
                unsafe { d.destroy_buffer(buffer, None) };
148
                return Err(e.into());
149
            }
150
        };
151
        unsafe { d.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())? };
152
        Ok(Self {
153
            pool: pool.clone(),
154
            buffer,
155
            allocation: Some(allocation),
156
            size,
157
            usage,
158
            location,
159
        })
160
    }
161
162
    /// The mapped bytes, with whatever the GPU wrote to them made visible.
163
    pub fn bytes(&self) -> Result<&[u8], Error> {
164
        self.host_sync(false)?;
165
        self.allocation
166
            .as_ref()
167
            .and_then(|a| a.mapped_slice())
168
            .ok_or(Error::StagingUnmapped)
169
    }
170
171
    /// Makes what the host wrote through `bytes_mut` visible to the GPU.
172
    pub fn flush(&self) -> Result<(), Error> {
173
        self.host_sync(true)
174
    }
175
176
    /// Flushes or invalidates the mapping. Coherent memory needs neither.
177
    fn host_sync(&self, flush: bool) -> Result<(), Error> {
178
        let Some(allocation) = self.allocation.as_ref() else {
179
            return Ok(());
180
        };
181
        if allocation
182
            .memory_properties()
183
            .contains(vk::MemoryPropertyFlags::HOST_COHERENT)
184
        {
185
            return Ok(());
186
        }
187
        let atom = self.pool.atom;
188
        let range = [vk::MappedMemoryRange::default()
189
            .memory(unsafe { allocation.memory() })
190
            .offset(allocation.offset() / atom * atom)
191
            .size(vk::WHOLE_SIZE)];
192
        let d = &self.pool.shared.device.device;
193
        unsafe {
194
            if flush {
195
                d.flush_mapped_memory_ranges(&range)?;
196
            } else {
197
                d.invalidate_mapped_memory_ranges(&range)?;
198
            }
199
        }
200
        Ok(())
201
    }
202
203
    pub fn bytes_mut(&mut self) -> Result<&mut [u8], Error> {
204
        self.allocation
205
            .as_mut()
206
            .and_then(|a| a.mapped_slice_mut())
207
            .ok_or(Error::StagingUnmapped)
208
    }
209
}
210
211
impl Drop for Staging {
212
    fn drop(&mut self) {
213
        let Some(allocation) = self.allocation.take() else {
214
            return;
215
        };
216
        let mut free = self.pool.free.lock().unwrap_or_else(|e| e.into_inner());
217
        let pooled: usize = free.iter().map(|p| p.size).sum();
218
        if pooled + self.size <= POOL_BYTES {
219
            free.push(Pooled {
220
                buffer: self.buffer,
221
                allocation,
222
                size: self.size,
223
                usage: self.usage,
224
                location: self.location,
225
                returned: Instant::now(),
226
            });
227
            return;
228
        }
229
        drop(free);
230
        self.pool.destroy([(self.buffer, allocation)]);
231
    }
232
}