1use std::collections::HashMap;
2use std::num::NonZeroU64;
3use std::sync::Mutex;
4
5use crate::Shared;
6use crate::retire::{Retired, Retirement};
7
8pub(crate) const PARAM_SLOT_BYTES: usize = 16;
11
12pub(crate) const fn param_uniform_bytes(count: usize) -> usize {
16 let slots = if count == 0 { 1 } else { count };
17 slots.saturating_mul(PARAM_SLOT_BYTES)
18}
19
20pub(crate) const RUNTIME_UNIFORM_WORDS: usize = 12;
23
24pub(crate) const BATCH_UNIFORM_FLOATS: usize = 12 + 16 * crate::composite::COMPOSITE_BATCH_LAYERS;
28
29pub(crate) const fn float_uniform_bytes(values: usize) -> u64 {
31 (values * size_of::<f32>()) as u64
32}
33
34const MAX_IDLE_UNIFORM_BUFFERS: usize = 64;
37const MAX_IDLE_UNIFORM_BYTES: u64 = 4 * 1024 * 1024;
38
39pub(crate) fn f32_bytes<const N: usize>(values: [f32; N]) -> Vec<u8> {
40 values
41 .into_iter()
42 .flat_map(f32::to_ne_bytes)
43 .collect::<Vec<_>>()
44}
45
46#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
48pub struct UniformBufferPoolStats {
49 pub created: u64,
51 pub reused: u64,
53 pub discarded: u64,
55 pub active: usize,
57 pub active_bytes: u64,
59 pub high_water_bytes: u64,
61 pub available: usize,
63 pub available_bytes: u64,
65}
66
67#[derive(Debug)]
68pub(crate) struct UniformBufferPool {
69 alignment: u64,
71 state: Mutex<UniformBufferPoolState>,
72 retirement: Shared<Retirement>,
73}
74
75#[derive(Debug, Default)]
76struct UniformBufferPoolState {
77 available: Vec<(u64, wgpu::Buffer, Vec<u8>, u64)>,
80 checked_generation: u64,
82 active_by_capacity: HashMap<u64, usize>,
83 peak_active_by_capacity: HashMap<u64, usize>,
84 created: u64,
85 reused: u64,
86 discarded: u64,
87 active: usize,
88 active_bytes: u64,
89 high_water_bytes: u64,
90 available_bytes: u64,
91}
92
93#[derive(Debug)]
94pub(crate) struct UniformArenaLease {
95 pool: Shared<UniformBufferPool>,
96 buffer: Option<wgpu::Buffer>,
97 capacity: u64,
98 alignment: u64,
99 cursor: u64,
100 bytes: Vec<u8>,
101 generation: u64,
102}
103
104#[derive(Copy, Clone, Debug)]
105pub(crate) struct UniformSlice {
106 offset: u64,
107 size: NonZeroU64,
108}
109
110impl UniformBufferPool {
111 pub(crate) fn new(alignment: u64, retirement: Shared<Retirement>) -> Self {
112 Self {
113 alignment: alignment.max(1),
114 state: Mutex::default(),
115 retirement,
116 }
117 }
118
119 pub(crate) fn acquire(
124 self: &Shared<Self>,
125 device: &wgpu::Device,
126 capacity: u64,
127 ) -> UniformArenaLease {
128 let alignment = self.alignment;
129 let capacity = align_up(capacity.max(1), alignment)
130 .checked_next_power_of_two()
131 .unwrap_or(u64::MAX);
132 let reused = {
133 let mut state = self
134 .state
135 .lock()
136 .unwrap_or_else(std::sync::PoisonError::into_inner);
137 let untrusted = self.drop_untrusted(&mut state);
138 let index = state
139 .available
140 .iter()
141 .position(|(available_capacity, _, _, _)| *available_capacity == capacity);
142 let reused = index.map(|index| {
143 state.reused = state.reused.saturating_add(1);
144 state.available_bytes = state.available_bytes.saturating_sub(capacity);
145 let (_, buffer, bytes, generation) = state.available.remove(index);
146 (buffer, bytes, generation)
147 });
148 drop(state);
149 self.retirement.retire(untrusted);
150 reused
151 };
152 let (buffer, bytes, generation) = reused.unwrap_or_else(|| {
153 let mut state = self
154 .state
155 .lock()
156 .unwrap_or_else(std::sync::PoisonError::into_inner);
157 state.created = state.created.saturating_add(1);
158 drop(state);
159 let generation = self.retirement.generation();
160 (
161 device.create_buffer(&wgpu::BufferDescriptor {
162 label: Some("alpha2 uniform arena"),
163 size: capacity,
164 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
165 mapped_at_creation: false,
166 }),
167 vec![0; usize::try_from(capacity).expect("uniform arena fits host indexing")],
168 generation,
169 )
170 });
171 {
172 let mut state = self
173 .state
174 .lock()
175 .unwrap_or_else(std::sync::PoisonError::into_inner);
176 state.active = state.active.saturating_add(1);
177 state.active_bytes = state.active_bytes.saturating_add(capacity);
178 state.high_water_bytes = state.high_water_bytes.max(state.active_bytes);
179 let active = state.active_by_capacity.entry(capacity).or_default();
180 *active = active.saturating_add(1);
181 let active = *active;
182 let peak = state.peak_active_by_capacity.entry(capacity).or_default();
183 *peak = (*peak).max(active);
184 }
185 UniformArenaLease {
186 pool: Shared::clone(self),
187 buffer: Some(buffer),
188 capacity,
189 alignment,
190 cursor: 0,
191 bytes,
192 generation,
193 }
194 }
195
196 fn drop_untrusted(&self, state: &mut UniformBufferPoolState) -> Vec<(Retired, u64)> {
199 let generation = self.retirement.generation();
200 if state.checked_generation == generation {
201 return Vec::new();
202 }
203 state.checked_generation = generation;
204 let (kept, untrusted): (Vec<_>, Vec<_>) = std::mem::take(&mut state.available)
205 .into_iter()
206 .partition(|(_, _, _, created)| *created == generation);
207 state.available = kept;
208 state.available_bytes = state
209 .available
210 .iter()
211 .map(|(capacity, _, _, _)| *capacity)
212 .sum();
213 state.discarded = state
214 .discarded
215 .saturating_add(u64::try_from(untrusted.len()).unwrap_or(u64::MAX));
216 untrusted
217 .into_iter()
218 .map(|(capacity, buffer, _, _)| {
219 prune_capacity(state, capacity);
220 (Retired::Buffer(buffer), capacity)
221 })
222 .collect()
223 }
224
225 pub(crate) fn clear(&self) {
227 let mut state = self
228 .state
229 .lock()
230 .unwrap_or_else(std::sync::PoisonError::into_inner);
231 let discarded = u64::try_from(state.available.len()).unwrap_or(u64::MAX);
232 let idle = std::mem::take(&mut state.available);
233 state.discarded = state.discarded.saturating_add(discarded);
234 state.available_bytes = 0;
235 drop(state);
236 self.retirement.retire(
237 idle.into_iter()
238 .map(|(capacity, buffer, _, _)| (Retired::Buffer(buffer), capacity)),
239 );
240 }
241
242 pub(crate) fn stats(&self) -> UniformBufferPoolStats {
243 let state = self
244 .state
245 .lock()
246 .unwrap_or_else(std::sync::PoisonError::into_inner);
247 UniformBufferPoolStats {
248 created: state.created,
249 reused: state.reused,
250 discarded: state.discarded,
251 active: state.active,
252 active_bytes: state.active_bytes,
253 high_water_bytes: state.high_water_bytes,
254 available: state.available.len(),
255 available_bytes: state.available_bytes,
256 }
257 }
258
259 fn release(&self, capacity: u64, buffer: wgpu::Buffer, bytes: Vec<u8>, generation: u64) {
264 if !self.retirement.trusts(generation) {
265 self.discard(capacity, buffer);
266 return;
267 }
268 let mut retired = Vec::new();
269 self.keep_or_retire(capacity, buffer, bytes, generation, &mut retired);
270 self.retirement.retire(retired);
271 }
272
273 fn keep_or_retire(
274 &self,
275 capacity: u64,
276 buffer: wgpu::Buffer,
277 bytes: Vec<u8>,
278 generation: u64,
279 retired: &mut Vec<(Retired, u64)>,
280 ) {
281 let mut state = self
282 .state
283 .lock()
284 .unwrap_or_else(std::sync::PoisonError::into_inner);
285 finish_active(&mut state, capacity);
286 let retention_target = state
287 .peak_active_by_capacity
288 .get(&capacity)
289 .copied()
290 .unwrap_or(1);
291 let idle_at_capacity = state
292 .available
293 .iter()
294 .filter(|(available, _, _, _)| *available == capacity)
295 .count();
296 if idle_at_capacity >= retention_target || capacity > MAX_IDLE_UNIFORM_BYTES {
297 state.discarded = state.discarded.saturating_add(1);
298 retired.push((Retired::Buffer(buffer), capacity));
299 return;
300 }
301 while state.available.len() >= MAX_IDLE_UNIFORM_BUFFERS
302 || state.available_bytes.saturating_add(capacity) > MAX_IDLE_UNIFORM_BYTES
303 {
304 let (discarded, discarded_buffer, _bytes, _) = state.available.remove(0);
305 state.available_bytes = state.available_bytes.saturating_sub(discarded);
306 state.discarded = state.discarded.saturating_add(1);
307 prune_capacity(&mut state, discarded);
308 retired.push((Retired::Buffer(discarded_buffer), discarded));
309 }
310 state.available.push((capacity, buffer, bytes, generation));
311 state.available_bytes = state.available_bytes.saturating_add(capacity);
312 }
313
314 fn discard(&self, capacity: u64, buffer: wgpu::Buffer) {
316 let mut state = self
317 .state
318 .lock()
319 .unwrap_or_else(std::sync::PoisonError::into_inner);
320 finish_active(&mut state, capacity);
321 state.discarded = state.discarded.saturating_add(1);
322 prune_capacity(&mut state, capacity);
323 drop(state);
324 self.retirement
325 .retire([(Retired::Buffer(buffer), capacity)]);
326 }
327}
328
329impl UniformArenaLease {
330 pub(crate) fn write(&mut self, contents: &[u8]) -> UniformSlice {
331 let offset = align_up(self.cursor, self.alignment);
332 let size = u64::try_from(contents.len()).expect("uniform contents fit u64");
333 let end = offset.checked_add(size).expect("uniform arena offset");
334 assert!(end <= self.capacity, "uniform arena capacity was reserved");
335 let start = usize::try_from(offset).expect("uniform offset fits host indexing");
336 let end = usize::try_from(end).expect("uniform end fits host indexing");
337 self.bytes[start..end].copy_from_slice(contents);
338 self.cursor = u64::try_from(end).expect("uniform cursor fits u64");
339 UniformSlice {
340 offset,
341 size: NonZeroU64::new(size).expect("uniform binding is non-empty"),
342 }
343 }
344
345 pub(crate) fn binding(&self, slice: UniformSlice) -> wgpu::BindingResource<'_> {
346 wgpu::BindingResource::Buffer(wgpu::BufferBinding {
347 buffer: self.buffer.as_ref().expect("live uniform arena"),
348 offset: slice.offset,
349 size: Some(slice.size),
350 })
351 }
352
353 #[cfg(test)]
354 pub(crate) fn buffer(&self) -> &wgpu::Buffer {
355 self.buffer.as_ref().expect("live uniform arena")
356 }
357
358 pub(crate) fn flush(&self, queue: &wgpu::Queue) {
359 if self.cursor > 0 {
360 let used = usize::try_from(self.cursor).expect("uniform cursor fits host indexing");
361 queue.write_buffer(
362 self.buffer.as_ref().expect("live uniform arena"),
363 0,
364 &self.bytes[..used],
365 );
366 }
367 }
368
369 pub(crate) fn recycle(mut self) {
370 let buffer = self.buffer.take().expect("live uniform arena");
371 let bytes = std::mem::take(&mut self.bytes);
372 self.pool
373 .release(self.capacity, buffer, bytes, self.generation);
374 }
375}
376
377impl Drop for UniformArenaLease {
378 fn drop(&mut self) {
379 if let Some(buffer) = self.buffer.take() {
380 self.pool.discard(self.capacity, buffer);
381 }
382 }
383}
384
385fn finish_active(state: &mut UniformBufferPoolState, capacity: u64) {
386 state.active = state.active.saturating_sub(1);
387 state.active_bytes = state.active_bytes.saturating_sub(capacity);
388 if let Some(active) = state.active_by_capacity.get_mut(&capacity) {
389 *active = active.saturating_sub(1);
390 if *active == 0 {
391 state.active_by_capacity.remove(&capacity);
392 }
393 }
394}
395
396fn prune_capacity(state: &mut UniformBufferPoolState, capacity: u64) {
399 if !state.active_by_capacity.contains_key(&capacity)
400 && !state
401 .available
402 .iter()
403 .any(|(available, _, _, _)| *available == capacity)
404 {
405 state.peak_active_by_capacity.remove(&capacity);
406 }
407}
408
409pub(crate) const fn aligned_uniform_end(cursor: u64, size: u64, alignment: u64) -> u64 {
410 align_up(cursor, alignment).saturating_add(size)
411}
412
413const fn align_up(value: u64, alignment: u64) -> u64 {
414 value
415 .saturating_add(alignment.saturating_sub(1))
416 .div_euclid(alignment)
417 .saturating_mul(alignment)
418}