use super::{PaneCaptureParams, Renderer};
impl Renderer {
pub fn take_screenshot(
&mut self,
params: PaneCaptureParams<'_>,
) -> Result<image::RgbaImage, crate::error::RenderError> {
log::info!(
"take_screenshot: Starting screenshot capture ({}x{})",
self.size.width,
self.size.height
);
let width = self.size.width;
let height = self.size.height;
let format = self.cell_renderer.surface_format();
log::info!("take_screenshot: Using texture format {:?}", format);
let screenshot_texture =
self.cell_renderer
.device()
.create_texture(&wgpu::TextureDescriptor {
label: Some("screenshot texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let screenshot_view =
screenshot_texture.create_view(&wgpu::TextureViewDescriptor::default());
log::info!("take_screenshot: Rendering composited frame...");
self.composite_panes_offscreen(params, &screenshot_view)
.map_err(|e| {
crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
})?;
self.cell_renderer
.render_opaque_alpha_to_view(&screenshot_view)
.map_err(|e| {
crate::error::RenderError::ScreenshotMap(format!("Alpha stamp failed: {:#}", e))
})?;
log::info!("take_screenshot: Render complete");
let device = self.cell_renderer.device();
let queue = self.cell_renderer.queue();
let bytes_per_pixel = 4u32;
let unpadded_bytes_per_row = width * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
let buffer_size = (padded_bytes_per_row * height) as u64;
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("screenshot 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("screenshot encoder"),
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &screenshot_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &output_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded_bytes_per_row),
rows_per_image: Some(height),
},
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
queue.submit(std::iter::once(encoder.finish()));
log::info!("take_screenshot: Texture copy submitted");
let buffer_slice = output_buffer.slice(..);
let (tx, rx) = std::sync::mpsc::channel();
buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
let _ = tx.send(result);
});
let gpu_timeout = std::time::Duration::from_secs(5);
log::info!("take_screenshot: Waiting for GPU...");
if let Err(e) = device.poll(wgpu::PollType::Wait {
submission_index: None,
timeout: Some(gpu_timeout),
}) {
log::warn!("take_screenshot: GPU poll returned error: {:?}", e);
}
log::info!("take_screenshot: GPU poll complete, waiting for buffer map...");
rx.recv_timeout(gpu_timeout)
.map_err(|e| {
crate::error::RenderError::ScreenshotMap(format!(
"Timed out or failed to receive map result: {}",
e
))
})?
.map_err(|e| {
crate::error::RenderError::ScreenshotMap(format!("Failed to map buffer: {:?}", e))
})?;
log::info!("take_screenshot: Buffer mapped successfully");
let data = buffer_slice.get_mapped_range();
let mut pixels = Vec::with_capacity((width * height * 4) as usize);
let is_bgra = matches!(
format,
wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
);
for y in 0..height {
let row_start = (y * padded_bytes_per_row) as usize;
let row_end = row_start + (width * bytes_per_pixel) as usize;
let row = &data[row_start..row_end];
if is_bgra {
for chunk in row.chunks(4) {
pixels.push(chunk[2]); pixels.push(chunk[1]); pixels.push(chunk[0]); pixels.push(chunk[3]); }
} else {
pixels.extend_from_slice(row);
}
}
drop(data);
output_buffer.unmap();
image::RgbaImage::from_raw(width, height, pixels)
.ok_or(crate::error::RenderError::ScreenshotImageAssembly)
}
}