const COPY_ROW_ALIGNMENT: u32 = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
pub struct ReadbackRequest {
buffer: wgpu::Buffer,
height: u32,
padded_row_bytes: u32,
unpadded_row_bytes: u32,
rx: Option<std::sync::mpsc::Receiver<Result<(), wgpu::BufferAsyncError>>>,
}
pub fn request_readback(
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
texture: &wgpu::Texture,
format: wgpu::TextureFormat,
rect: crate::coord::LayerRect,
) -> ReadbackRequest {
let x = rect.x0();
let y = rect.y0();
let w = rect.width;
let h = rect.height;
let bpp = format.block_copy_size(None).unwrap_or(1);
let unpadded_row_bytes = w * bpp;
let padded_row_bytes = unpadded_row_bytes.div_ceil(COPY_ROW_ALIGNMENT) * COPY_ROW_ALIGNMENT;
let buffer_size = padded_row_bytes as u64 * h as u64;
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("readback"),
size: buffer_size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d { x, y, z: 0 },
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded_row_bytes),
rows_per_image: Some(h),
},
},
wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
);
ReadbackRequest {
buffer,
height: h,
padded_row_bytes,
unpadded_row_bytes,
rx: None,
}
}
impl ReadbackRequest {
pub fn from_buffer(
buffer: wgpu::Buffer,
height: u32,
padded_row_bytes: u32,
unpadded_row_bytes: u32,
) -> Self {
ReadbackRequest {
buffer,
height,
padded_row_bytes,
unpadded_row_bytes,
rx: None,
}
}
fn begin_mapping(&mut self) {
let slice = self.buffer.slice(..);
let (tx, rx) = std::sync::mpsc::sync_channel(1);
slice.map_async(wgpu::MapMode::Read, move |result| {
let _ = tx.send(result);
});
self.rx = Some(rx);
}
#[cfg(any(test, feature = "testing"))]
pub fn blocking_read(&self, device: &wgpu::Device) -> Vec<u8> {
let slice = self.buffer.slice(..);
let (tx, rx) = std::sync::mpsc::sync_channel(1);
slice.map_async(wgpu::MapMode::Read, move |result| {
let _ = tx.send(result);
});
let _ = device.poll(wgpu::PollType::Wait {
submission_index: None,
timeout: None,
});
rx.recv()
.expect("map callback never fired")
.expect("buffer mapping failed");
let data = self.extract_pixels(&slice);
self.buffer.unmap();
data
}
fn extract_pixels(&self, slice: &wgpu::BufferSlice) -> Vec<u8> {
let mapped = slice.get_mapped_range();
let unpadded = self.unpadded_row_bytes as usize;
let padded = self.padded_row_bytes as usize;
if unpadded == padded {
return mapped.to_vec();
}
let mut out = Vec::with_capacity(unpadded * self.height as usize);
for row in 0..self.height as usize {
let start = row * padded;
out.extend_from_slice(&mapped[start..start + unpadded]);
}
out
}
}
pub struct ReadbackScheduler<C> {
tasks: Vec<(ReadbackRequest, C)>,
}
impl<C> ReadbackScheduler<C> {
pub fn new() -> Self {
ReadbackScheduler { tasks: Vec::new() }
}
pub fn submit(&mut self, request: ReadbackRequest, context: C) {
self.tasks.push((request, context));
}
pub fn poll(&mut self, device: &wgpu::Device) -> Vec<(C, Vec<u8>)> {
for (req, _) in &mut self.tasks {
if req.rx.is_none() {
req.begin_mapping();
}
}
if !self.tasks.is_empty() {
let _ = device.poll(wgpu::PollType::Poll);
}
let mut completed = Vec::new();
let mut i = 0;
while i < self.tasks.len() {
let ready = self.tasks[i]
.0
.rx
.as_ref()
.and_then(|rx| rx.try_recv().ok());
match ready {
Some(Ok(())) => {
let (req, ctx) = self.tasks.swap_remove(i);
let slice = req.buffer.slice(..);
let pixels = req.extract_pixels(&slice);
req.buffer.unmap();
completed.push((ctx, pixels));
}
Some(Err(e)) => {
log::error!("readback buffer mapping failed: {e}");
self.tasks.swap_remove(i);
}
None => {
i += 1;
}
}
}
completed
}
pub fn has_pending(&self) -> bool {
!self.tasks.is_empty()
}
pub fn any<F: Fn(&C) -> bool>(&self, f: F) -> bool {
self.tasks.iter().any(|(_, ctx)| f(ctx))
}
pub fn cancel<F: Fn(&C) -> bool>(&mut self, f: F) {
self.tasks.retain(|(_, ctx)| !f(ctx));
}
}
impl<C> Default for ReadbackScheduler<C> {
fn default() -> Self {
Self::new()
}
}