pub use wgpu;
pub use miniscreenshot::{Screenshot, ScreenshotProvider};
#[derive(Debug)]
pub enum CaptureError {
UnsupportedFormat(wgpu::TextureFormat),
MapFailed(wgpu::BufferAsyncError),
}
impl std::fmt::Display for CaptureError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnsupportedFormat(fmt) => {
write!(f, "unsupported texture format for screenshot: {fmt:?}")
}
Self::MapFailed(e) => write!(f, "staging buffer map failed: {e}"),
}
}
}
impl std::error::Error for CaptureError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::MapFailed(e) => Some(e),
_ => None,
}
}
}
pub fn capture_texture(
device: &wgpu::Device,
queue: &wgpu::Queue,
texture: &wgpu::Texture,
) -> Result<Screenshot, CaptureError> {
let size = texture.size();
let width = size.width;
let height = size.height;
let format = texture.format();
let is_bgra = match format {
wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Rgba8UnormSrgb => false,
wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb => true,
_ => return Err(CaptureError::UnsupportedFormat(format)),
};
let bytes_per_row = padded_bytes_per_row(width);
let buffer_size = u64::from(bytes_per_row) * u64::from(height);
let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("miniscreenshot_staging_buffer"),
size: buffer_size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("miniscreenshot_encoder"),
});
encoder.copy_texture_to_buffer(
texture.as_image_copy(),
wgpu::ImageCopyBuffer {
buffer: &staging_buffer,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row),
rows_per_image: Some(height),
},
},
size,
);
queue.submit(std::iter::once(encoder.finish()));
let buffer_slice = staging_buffer.slice(..);
let (tx, rx) = std::sync::mpsc::channel();
buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
let _ = tx.send(result);
});
device.poll(wgpu::Maintain::Wait);
rx.recv()
.expect("map_async callback channel closed unexpectedly")
.map_err(CaptureError::MapFailed)?;
let mapped = buffer_slice.get_mapped_range();
let raw: &[u8] = &mapped;
let mut rgba = Vec::with_capacity(width as usize * height as usize * 4);
for row_idx in 0..height as usize {
let row_start = row_idx * bytes_per_row as usize;
let row_end = row_start + width as usize * 4;
let row = &raw[row_start..row_end];
if is_bgra {
for pixel in row.chunks_exact(4) {
rgba.push(pixel[2]); rgba.push(pixel[1]); rgba.push(pixel[0]); rgba.push(pixel[3]); }
} else {
rgba.extend_from_slice(row);
}
}
drop(mapped);
staging_buffer.unmap();
Ok(Screenshot::from_rgba(width, height, rgba))
}
fn padded_bytes_per_row(width: u32) -> u32 {
let unpadded = width * 4;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
unpadded.div_ceil(align) * align
}
#[cfg(test)]
mod tests {
use super::padded_bytes_per_row;
#[test]
fn padding_aligns_to_256() {
assert_eq!(padded_bytes_per_row(1), 256);
assert_eq!(padded_bytes_per_row(64), 256);
assert_eq!(padded_bytes_per_row(65), 512);
assert_eq!(padded_bytes_per_row(128), 512);
}
}