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 UniformSlice {
111 pub(crate) const fn offset(self) -> u64 {
114 self.offset
115 }
116}
117
118impl UniformBufferPool {
119 pub(crate) fn new(alignment: u64, retirement: Shared<Retirement>) -> Self {
120 Self {
121 alignment: alignment.max(1),
122 state: Mutex::default(),
123 retirement,
124 }
125 }
126
127 pub(crate) fn acquire(
132 self: &Shared<Self>,
133 device: &wgpu::Device,
134 capacity: u64,
135 ) -> UniformArenaLease {
136 let alignment = self.alignment;
137 let capacity = align_up(capacity.max(1), alignment)
138 .checked_next_power_of_two()
139 .unwrap_or(u64::MAX);
140 let reused = {
141 let mut state = self
142 .state
143 .lock()
144 .unwrap_or_else(std::sync::PoisonError::into_inner);
145 let untrusted = self.drop_untrusted(&mut state);
146 let index = state
147 .available
148 .iter()
149 .position(|(available_capacity, _, _, _)| *available_capacity == capacity);
150 let reused = index.map(|index| {
151 state.reused = state.reused.saturating_add(1);
152 state.available_bytes = state.available_bytes.saturating_sub(capacity);
153 let (_, buffer, bytes, generation) = state.available.remove(index);
154 (buffer, bytes, generation)
155 });
156 drop(state);
157 self.retirement.retire(untrusted);
158 reused
159 };
160 let (buffer, bytes, generation) = reused.unwrap_or_else(|| {
161 let mut state = self
162 .state
163 .lock()
164 .unwrap_or_else(std::sync::PoisonError::into_inner);
165 state.created = state.created.saturating_add(1);
166 drop(state);
167 let generation = self.retirement.generation();
168 (
169 device.create_buffer(&wgpu::BufferDescriptor {
170 label: Some("alpha2 uniform arena"),
171 size: capacity,
172 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
173 mapped_at_creation: false,
174 }),
175 vec![0; usize::try_from(capacity).expect("uniform arena fits host indexing")],
176 generation,
177 )
178 });
179 {
180 let mut state = self
181 .state
182 .lock()
183 .unwrap_or_else(std::sync::PoisonError::into_inner);
184 state.active = state.active.saturating_add(1);
185 state.active_bytes = state.active_bytes.saturating_add(capacity);
186 state.high_water_bytes = state.high_water_bytes.max(state.active_bytes);
187 let active = state.active_by_capacity.entry(capacity).or_default();
188 *active = active.saturating_add(1);
189 let active = *active;
190 let peak = state.peak_active_by_capacity.entry(capacity).or_default();
191 *peak = (*peak).max(active);
192 }
193 UniformArenaLease {
194 pool: Shared::clone(self),
195 buffer: Some(buffer),
196 capacity,
197 alignment,
198 cursor: 0,
199 bytes,
200 generation,
201 }
202 }
203
204 fn drop_untrusted(&self, state: &mut UniformBufferPoolState) -> Vec<(Retired, u64)> {
207 let generation = self.retirement.generation();
208 if state.checked_generation == generation {
209 return Vec::new();
210 }
211 state.checked_generation = generation;
212 let (kept, untrusted): (Vec<_>, Vec<_>) = std::mem::take(&mut state.available)
213 .into_iter()
214 .partition(|(_, _, _, created)| *created == generation);
215 state.available = kept;
216 state.available_bytes = state
217 .available
218 .iter()
219 .map(|(capacity, _, _, _)| *capacity)
220 .sum();
221 state.discarded = state
222 .discarded
223 .saturating_add(u64::try_from(untrusted.len()).unwrap_or(u64::MAX));
224 untrusted
225 .into_iter()
226 .map(|(capacity, buffer, _, _)| {
227 prune_capacity(state, capacity);
228 (Retired::Buffer(buffer), capacity)
229 })
230 .collect()
231 }
232
233 pub(crate) fn clear(&self) {
235 let mut state = self
236 .state
237 .lock()
238 .unwrap_or_else(std::sync::PoisonError::into_inner);
239 let discarded = u64::try_from(state.available.len()).unwrap_or(u64::MAX);
240 let idle = std::mem::take(&mut state.available);
241 state.discarded = state.discarded.saturating_add(discarded);
242 state.available_bytes = 0;
243 drop(state);
244 self.retirement.retire(
245 idle.into_iter()
246 .map(|(capacity, buffer, _, _)| (Retired::Buffer(buffer), capacity)),
247 );
248 }
249
250 pub(crate) fn stats(&self) -> UniformBufferPoolStats {
251 let state = self
252 .state
253 .lock()
254 .unwrap_or_else(std::sync::PoisonError::into_inner);
255 UniformBufferPoolStats {
256 created: state.created,
257 reused: state.reused,
258 discarded: state.discarded,
259 active: state.active,
260 active_bytes: state.active_bytes,
261 high_water_bytes: state.high_water_bytes,
262 available: state.available.len(),
263 available_bytes: state.available_bytes,
264 }
265 }
266
267 fn release(&self, capacity: u64, buffer: wgpu::Buffer, bytes: Vec<u8>, generation: u64) {
272 if !self.retirement.trusts(generation) {
273 self.discard(capacity, buffer);
274 return;
275 }
276 let mut retired = Vec::new();
277 self.keep_or_retire(capacity, buffer, bytes, generation, &mut retired);
278 self.retirement.retire(retired);
279 }
280
281 fn keep_or_retire(
282 &self,
283 capacity: u64,
284 buffer: wgpu::Buffer,
285 bytes: Vec<u8>,
286 generation: u64,
287 retired: &mut Vec<(Retired, u64)>,
288 ) {
289 let mut state = self
290 .state
291 .lock()
292 .unwrap_or_else(std::sync::PoisonError::into_inner);
293 finish_active(&mut state, capacity);
294 let retention_target = state
295 .peak_active_by_capacity
296 .get(&capacity)
297 .copied()
298 .unwrap_or(1);
299 let idle_at_capacity = state
300 .available
301 .iter()
302 .filter(|(available, _, _, _)| *available == capacity)
303 .count();
304 if idle_at_capacity >= retention_target || capacity > MAX_IDLE_UNIFORM_BYTES {
305 state.discarded = state.discarded.saturating_add(1);
306 retired.push((Retired::Buffer(buffer), capacity));
307 return;
308 }
309 while state.available.len() >= MAX_IDLE_UNIFORM_BUFFERS
310 || state.available_bytes.saturating_add(capacity) > MAX_IDLE_UNIFORM_BYTES
311 {
312 let (discarded, discarded_buffer, _bytes, _) = state.available.remove(0);
313 state.available_bytes = state.available_bytes.saturating_sub(discarded);
314 state.discarded = state.discarded.saturating_add(1);
315 prune_capacity(&mut state, discarded);
316 retired.push((Retired::Buffer(discarded_buffer), discarded));
317 }
318 state.available.push((capacity, buffer, bytes, generation));
319 state.available_bytes = state.available_bytes.saturating_add(capacity);
320 }
321
322 fn discard(&self, capacity: u64, buffer: wgpu::Buffer) {
324 let mut state = self
325 .state
326 .lock()
327 .unwrap_or_else(std::sync::PoisonError::into_inner);
328 finish_active(&mut state, capacity);
329 state.discarded = state.discarded.saturating_add(1);
330 prune_capacity(&mut state, capacity);
331 drop(state);
332 self.retirement
333 .retire([(Retired::Buffer(buffer), capacity)]);
334 }
335}
336
337impl UniformArenaLease {
338 pub(crate) fn write(&mut self, contents: &[u8]) -> UniformSlice {
339 let offset = align_up(self.cursor, self.alignment);
340 let size = u64::try_from(contents.len()).expect("uniform contents fit u64");
341 let end = offset.checked_add(size).expect("uniform arena offset");
342 assert!(end <= self.capacity, "uniform arena capacity was reserved");
343 let start = usize::try_from(offset).expect("uniform offset fits host indexing");
344 let end = usize::try_from(end).expect("uniform end fits host indexing");
345 self.bytes[start..end].copy_from_slice(contents);
346 self.cursor = u64::try_from(end).expect("uniform cursor fits u64");
347 UniformSlice {
348 offset,
349 size: NonZeroU64::new(size).expect("uniform binding is non-empty"),
350 }
351 }
352
353 pub(crate) fn dynamic_binding(&self, size: NonZeroU64) -> wgpu::BindingResource<'_> {
356 wgpu::BindingResource::Buffer(wgpu::BufferBinding {
357 buffer: self.buffer.as_ref().expect("live uniform arena"),
358 offset: 0,
359 size: Some(size),
360 })
361 }
362
363 pub(crate) fn binding(&self, slice: UniformSlice) -> wgpu::BindingResource<'_> {
364 wgpu::BindingResource::Buffer(wgpu::BufferBinding {
365 buffer: self.buffer.as_ref().expect("live uniform arena"),
366 offset: slice.offset,
367 size: Some(slice.size),
368 })
369 }
370
371 #[cfg(test)]
372 pub(crate) fn buffer(&self) -> &wgpu::Buffer {
373 self.buffer.as_ref().expect("live uniform arena")
374 }
375
376 pub(crate) fn flush(&self, queue: &wgpu::Queue) {
377 if self.cursor > 0 {
378 let used = usize::try_from(self.cursor).expect("uniform cursor fits host indexing");
379 queue.write_buffer(
380 self.buffer.as_ref().expect("live uniform arena"),
381 0,
382 &self.bytes[..used],
383 );
384 }
385 }
386
387 pub(crate) fn recycle(mut self) {
388 let buffer = self.buffer.take().expect("live uniform arena");
389 let bytes = std::mem::take(&mut self.bytes);
390 self.pool
391 .release(self.capacity, buffer, bytes, self.generation);
392 }
393}
394
395impl Drop for UniformArenaLease {
396 fn drop(&mut self) {
397 if let Some(buffer) = self.buffer.take() {
398 self.pool.discard(self.capacity, buffer);
399 }
400 }
401}
402
403fn finish_active(state: &mut UniformBufferPoolState, capacity: u64) {
404 state.active = state.active.saturating_sub(1);
405 state.active_bytes = state.active_bytes.saturating_sub(capacity);
406 if let Some(active) = state.active_by_capacity.get_mut(&capacity) {
407 *active = active.saturating_sub(1);
408 if *active == 0 {
409 state.active_by_capacity.remove(&capacity);
410 }
411 }
412}
413
414fn prune_capacity(state: &mut UniformBufferPoolState, capacity: u64) {
417 if !state.active_by_capacity.contains_key(&capacity)
418 && !state
419 .available
420 .iter()
421 .any(|(available, _, _, _)| *available == capacity)
422 {
423 state.peak_active_by_capacity.remove(&capacity);
424 }
425}
426
427pub(crate) const fn aligned_uniform_end(cursor: u64, size: u64, alignment: u64) -> u64 {
428 align_up(cursor, alignment).saturating_add(size)
429}
430
431const fn align_up(value: u64, alignment: u64) -> u64 {
432 value
433 .saturating_add(alignment.saturating_sub(1))
434 .div_euclid(alignment)
435 .saturating_mul(alignment)
436}