par_term_render/renderer/
screenshot.rs1use super::{PaneCaptureParams, Renderer};
18
19impl Renderer {
20 pub fn take_screenshot(
32 &mut self,
33 params: PaneCaptureParams<'_>,
34 ) -> Result<image::RgbaImage, crate::error::RenderError> {
35 log::info!(
36 "take_screenshot: Starting screenshot capture ({}x{})",
37 self.size.width,
38 self.size.height
39 );
40
41 let width = self.size.width;
42 let height = self.size.height;
43 let format = self.cell_renderer.surface_format();
45 log::info!("take_screenshot: Using texture format {:?}", format);
46
47 let screenshot_texture =
49 self.cell_renderer
50 .device()
51 .create_texture(&wgpu::TextureDescriptor {
52 label: Some("screenshot texture"),
53 size: wgpu::Extent3d {
54 width,
55 height,
56 depth_or_array_layers: 1,
57 },
58 mip_level_count: 1,
59 sample_count: 1,
60 dimension: wgpu::TextureDimension::D2,
61 format,
62 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
63 view_formats: &[],
64 });
65
66 let screenshot_view =
67 screenshot_texture.create_view(&wgpu::TextureViewDescriptor::default());
68
69 log::info!("take_screenshot: Rendering composited frame...");
71 self.composite_panes_offscreen(params, &screenshot_view)
72 .map_err(|e| {
73 crate::error::RenderError::ScreenshotMap(format!("Render failed: {:#}", e))
74 })?;
75 self.cell_renderer
77 .render_opaque_alpha_to_view(&screenshot_view)
78 .map_err(|e| {
79 crate::error::RenderError::ScreenshotMap(format!("Alpha stamp failed: {:#}", e))
80 })?;
81
82 log::info!("take_screenshot: Render complete");
83
84 let device = self.cell_renderer.device();
86 let queue = self.cell_renderer.queue();
87
88 let bytes_per_pixel = 4u32;
90 let unpadded_bytes_per_row = width * bytes_per_pixel;
91 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
93 let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
94 let buffer_size = (padded_bytes_per_row * height) as u64;
95
96 let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
97 label: Some("screenshot buffer"),
98 size: buffer_size,
99 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
100 mapped_at_creation: false,
101 });
102
103 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
105 label: Some("screenshot encoder"),
106 });
107
108 encoder.copy_texture_to_buffer(
109 wgpu::TexelCopyTextureInfo {
110 texture: &screenshot_texture,
111 mip_level: 0,
112 origin: wgpu::Origin3d::ZERO,
113 aspect: wgpu::TextureAspect::All,
114 },
115 wgpu::TexelCopyBufferInfo {
116 buffer: &output_buffer,
117 layout: wgpu::TexelCopyBufferLayout {
118 offset: 0,
119 bytes_per_row: Some(padded_bytes_per_row),
120 rows_per_image: Some(height),
121 },
122 },
123 wgpu::Extent3d {
124 width,
125 height,
126 depth_or_array_layers: 1,
127 },
128 );
129
130 queue.submit(std::iter::once(encoder.finish()));
131 log::info!("take_screenshot: Texture copy submitted");
132
133 let buffer_slice = output_buffer.slice(..);
135 let (tx, rx) = std::sync::mpsc::channel();
136 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
137 let _ = tx.send(result);
138 });
139
140 let gpu_timeout = std::time::Duration::from_secs(5);
145 log::info!("take_screenshot: Waiting for GPU...");
146 if let Err(e) = device.poll(wgpu::PollType::Wait {
147 submission_index: None,
148 timeout: Some(gpu_timeout),
149 }) {
150 log::warn!("take_screenshot: GPU poll returned error: {:?}", e);
151 }
152 log::info!("take_screenshot: GPU poll complete, waiting for buffer map...");
153 rx.recv_timeout(gpu_timeout)
154 .map_err(|e| {
155 crate::error::RenderError::ScreenshotMap(format!(
156 "Timed out or failed to receive map result: {}",
157 e
158 ))
159 })?
160 .map_err(|e| {
161 crate::error::RenderError::ScreenshotMap(format!("Failed to map buffer: {:?}", e))
162 })?;
163 log::info!("take_screenshot: Buffer mapped successfully");
164
165 let data = buffer_slice.get_mapped_range();
167 let mut pixels = Vec::with_capacity((width * height * 4) as usize);
168
169 let is_bgra = matches!(
171 format,
172 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
173 );
174
175 for y in 0..height {
177 let row_start = (y * padded_bytes_per_row) as usize;
178 let row_end = row_start + (width * bytes_per_pixel) as usize;
179 let row = &data[row_start..row_end];
180
181 if is_bgra {
182 for chunk in row.chunks(4) {
184 pixels.push(chunk[2]); pixels.push(chunk[1]); pixels.push(chunk[0]); pixels.push(chunk[3]); }
189 } else {
190 pixels.extend_from_slice(row);
192 }
193 }
194
195 drop(data);
196 output_buffer.unmap();
197
198 image::RgbaImage::from_raw(width, height, pixels)
200 .ok_or(crate::error::RenderError::ScreenshotImageAssembly)
201 }
202}