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