use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, PoisonError};
use std::task::Waker;
use super::work::{Count, Counters};
use crate::{Error, Result};
#[derive(Clone)]
pub(crate) struct Readbacks {
free: Arc<Mutex<Vec<wgpu::Buffer>>>,
counters: Option<Arc<Counters>>,
mapping: Arc<AtomicU64>,
}
impl Readbacks {
pub(crate) fn counted(counters: Arc<Counters>) -> Readbacks {
Readbacks { free: Arc::default(), counters: Some(counters), mapping: Arc::default() }
}
pub(crate) fn uncounted() -> Readbacks {
Readbacks { free: Arc::default(), counters: None, mapping: Arc::default() }
}
#[cfg(all(feature = "web", target_arch = "wasm32"))]
pub(crate) fn mapping(&self) -> u64 {
self.mapping.load(Ordering::Acquire)
}
fn take(&self, device: &wgpu::Device, size: u64) -> wgpu::Buffer {
if let Some(counters) = &self.counters {
counters.add(Count::Readbacks, 1);
}
let class = size.max(256).next_power_of_two();
let mut free = self.free.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(i) = free.iter().position(|b| b.size() == class) {
return free.swap_remove(i);
}
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("latkit readback"),
size: class,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Mapping {
Waiting,
Ready,
Failed,
Read,
}
pub(crate) struct Readback {
buffer: Option<wgpu::Buffer>,
size: u64,
pool: Readbacks,
mapping: Arc<Mutex<Mapping>>,
}
impl Readback {
pub(crate) fn new(
pool: &Readbacks,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
source: &wgpu::Buffer,
offset: u64,
size: u64,
waker: &Waker,
) -> Readback {
let buffer = pool.take(device, size);
encoder.copy_buffer_to_buffer(source, offset, &buffer, 0, size);
let mapping = Arc::new(Mutex::new(Mapping::Waiting));
let done = mapping.clone();
let waker = waker.clone();
let pending = pool.mapping.clone();
pending.fetch_add(1, Ordering::AcqRel);
encoder.map_buffer_on_submit(&buffer, wgpu::MapMode::Read, ..size, move |result| {
*done.lock().unwrap_or_else(PoisonError::into_inner) =
if result.is_ok() { Mapping::Ready } else { Mapping::Failed };
pending.fetch_sub(1, Ordering::AcqRel);
waker.wake();
});
Readback { buffer: Some(buffer), size, pool: pool.clone(), mapping }
}
pub(crate) fn take(&self) -> Option<Result<Vec<u8>>> {
let mut mapping = self.mapping.lock().unwrap_or_else(PoisonError::into_inner);
let buffer = self.buffer.as_ref()?;
match *mapping {
Mapping::Waiting | Mapping::Read => None,
Mapping::Failed => Some(Err(Error::DeviceLost)),
Mapping::Ready => {
let bytes = match buffer.get_mapped_range(..self.size) {
Ok(view) => view.to_vec(),
Err(_) => return Some(Err(Error::DeviceLost)),
};
buffer.unmap();
*mapping = Mapping::Read;
Some(Ok(bytes))
}
}
}
}
impl Drop for Readback {
fn drop(&mut self) {
let Some(buffer) = self.buffer.take() else { return };
let mapping = *self.mapping.lock().unwrap_or_else(PoisonError::into_inner);
if mapping == Mapping::Ready {
buffer.unmap();
}
if matches!(mapping, Mapping::Ready | Mapping::Read) {
self.pool.free.lock().unwrap_or_else(PoisonError::into_inner).push(buffer);
}
}
}