#[cfg(all(feature = "wgpu-28", feature = "wgpu-29"))]
compile_error!("features `wgpu-28` and `wgpu-29` are mutually exclusive; enable exactly one");
#[cfg(not(any(feature = "wgpu-28", feature = "wgpu-29")))]
compile_error!("one of `wgpu-28` or `wgpu-29` must be enabled for miniscreenshot-wgpu");
#[cfg(feature = "wgpu-28")]
pub use wgpu_28 as wgpu;
#[cfg(feature = "wgpu-29")]
pub use wgpu_29 as wgpu;
pub use miniscreenshot::{Capture, CaptureError, Screenshot};
#[derive(Debug)]
pub enum WgpuCaptureError {
UnsupportedFormat(wgpu::TextureFormat),
MapFailed(wgpu::BufferAsyncError),
PollFailed(wgpu::PollError),
}
impl std::fmt::Display for WgpuCaptureError {
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}"),
Self::PollFailed(e) => write!(f, "device poll failed: {e}"),
}
}
}
impl std::error::Error for WgpuCaptureError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::MapFailed(e) => Some(e),
Self::PollFailed(e) => Some(e),
_ => None,
}
}
}
impl From<WgpuCaptureError> for CaptureError {
fn from(e: WgpuCaptureError) -> Self {
match e {
WgpuCaptureError::UnsupportedFormat(fmt) => CaptureError::new(
miniscreenshot::CaptureErrorKind::Unsupported,
format!("unsupported texture format: {fmt:?}"),
)
.with_source(WgpuCaptureError::UnsupportedFormat(fmt)),
WgpuCaptureError::MapFailed(e) => CaptureError::new(
miniscreenshot::CaptureErrorKind::Backend,
format!("staging buffer map failed: {e}"),
)
.with_source(WgpuCaptureError::MapFailed(e)),
WgpuCaptureError::PollFailed(e) => CaptureError::new(
miniscreenshot::CaptureErrorKind::Backend,
format!("device poll failed: {e}"),
)
.with_source(WgpuCaptureError::PollFailed(e)),
}
}
}
pub struct WgpuCapture<'a> {
device: &'a wgpu::Device,
queue: &'a wgpu::Queue,
texture: &'a wgpu::Texture,
}
impl<'a> WgpuCapture<'a> {
pub fn new(
device: &'a wgpu::Device,
queue: &'a wgpu::Queue,
texture: &'a wgpu::Texture,
) -> Self {
Self {
device,
queue,
texture,
}
}
}
impl Capture for WgpuCapture<'_> {
type Error = CaptureError;
fn capture(&mut self) -> Result<Screenshot, CaptureError> {
capture(self.device, self.queue, self.texture).map_err(CaptureError::from)
}
}
pub fn capture(
device: &wgpu::Device,
queue: &wgpu::Queue,
texture: &wgpu::Texture,
) -> Result<Screenshot, WgpuCaptureError> {
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(WgpuCaptureError::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::TexelCopyBufferInfo {
buffer: &staging_buffer,
layout: wgpu::TexelCopyBufferLayout {
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::PollType::wait_indefinitely())
.map_err(WgpuCaptureError::PollFailed)?;
rx.recv()
.expect("map_async callback channel closed unexpectedly")
.map_err(WgpuCaptureError::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);
}
}