atagen

monolith

public
Star0
Watch0
b03e3b32603ac851a66da1909687f43d6fa81c80
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/render-vk/src/staging.rsRust188 lines5.8 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
}
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) -> Result<&[u8], Error> {
153
        self.allocation
154
            .as_ref()
155
            .and_then(|a| a.mapped_slice())
156
            .ok_or(Error::StagingUnmapped)
157
    }
158
159
    pub fn bytes_mut(&mut self) -> Result<&mut [u8], Error> {
160
        self.allocation
161
            .as_mut()
162
            .and_then(|a| a.mapped_slice_mut())
163
            .ok_or(Error::StagingUnmapped)
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
}