1use crate::{DrawCommand, WgpuGfxCtx};
15
16impl WgpuGfxCtx {
17 pub(crate) fn capture_screenshot_impl(&mut self) -> bool {
22 let Some(src) = self.surface_texture.clone() else {
23 return false;
24 };
25 let src_size = src.size();
26 let (sw, sh) = (src_size.width, src_size.height);
27 if sw == 0 || sh == 0 {
28 return false;
29 }
30
31 let need_alloc = match &self.capture_texture {
32 Some((_, _, w, h)) => *w != sw || *h != sh,
33 None => true,
34 };
35 if need_alloc {
36 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
37 label: Some("screenshot_capture"),
38 size: wgpu::Extent3d {
39 width: sw,
40 height: sh,
41 depth_or_array_layers: 1,
42 },
43 mip_level_count: 1,
44 sample_count: 1,
45 dimension: wgpu::TextureDimension::D2,
46 format: self.surface_format,
47 usage: wgpu::TextureUsages::TEXTURE_BINDING
51 | wgpu::TextureUsages::COPY_DST
52 | wgpu::TextureUsages::COPY_SRC,
53 view_formats: &[],
54 });
55 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
56 self.capture_texture = Some((std::sync::Arc::new(tex), view, sw, sh));
57 }
58
59 let dst_tex = std::sync::Arc::clone(&self.capture_texture.as_ref().unwrap().0);
60 let mut encoder = self
61 .device
62 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
63 label: Some("screenshot_capture_copy"),
64 });
65 encoder.copy_texture_to_texture(
66 wgpu::TexelCopyTextureInfo {
67 texture: &src,
68 mip_level: 0,
69 origin: wgpu::Origin3d::ZERO,
70 aspect: wgpu::TextureAspect::All,
71 },
72 wgpu::TexelCopyTextureInfo {
73 texture: &dst_tex,
74 mip_level: 0,
75 origin: wgpu::Origin3d::ZERO,
76 aspect: wgpu::TextureAspect::All,
77 },
78 wgpu::Extent3d {
79 width: sw,
80 height: sh,
81 depth_or_array_layers: 1,
82 },
83 );
84 self.queue.submit(std::iter::once(encoder.finish()));
85 true
86 }
87
88 pub(crate) fn draw_captured_screenshot_impl(
100 &mut self,
101 dst_x: f64,
102 dst_y: f64,
103 dst_w: f64,
104 dst_h: f64,
105 ) -> bool {
106 let Some((texture, view, _, _)) = self.capture_texture.as_ref() else {
107 return false;
108 };
109 let texture = std::sync::Arc::clone(texture);
110 let view = view.clone();
111
112 let bl = self.transform_pt(dst_x, dst_y);
113 let br = self.transform_pt(dst_x + dst_w, dst_y);
114 let tr = self.transform_pt(dst_x + dst_w, dst_y + dst_h);
115 let tl = self.transform_pt(dst_x, dst_y + dst_h);
116 let verts: [f32; 24] = [
119 bl[0], bl[1], 0.0, 1.0, br[0], br[1], 1.0, 1.0, tr[0], tr[1], 1.0, 0.0, bl[0], bl[1],
120 0.0, 1.0, tr[0], tr[1], 1.0, 0.0, tl[0], tl[1], 0.0, 0.0,
121 ];
122 let clip = self.current_clip();
123 let alpha = self.global_alpha as f32;
124 self.commands.push(DrawCommand::Textured {
125 verts,
126 texture,
127 view,
128 nearest: false,
129 tint: [1.0, 1.0, 1.0, alpha],
130 clip,
131 });
132 true
133 }
134
135 pub(crate) fn read_captured_screenshot_impl(&mut self) -> (Vec<u8>, u32, u32) {
142 let Some((texture, _, w, h)) = self.capture_texture.as_ref() else {
143 return (Vec::new(), 0, 0);
144 };
145 let (w, h) = (*w, *h);
146 if w == 0 || h == 0 {
147 return (Vec::new(), 0, 0);
148 }
149 let texture = std::sync::Arc::clone(texture);
150
151 const ALIGN: u32 = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
152 let unpadded_bpr = w * 4;
153 let padded_bpr = unpadded_bpr.div_ceil(ALIGN) * ALIGN;
154 let buffer_size = (padded_bpr as u64) * (h as u64);
155
156 let staging = self.device.create_buffer(&wgpu::BufferDescriptor {
157 label: Some("capture_readback"),
158 size: buffer_size,
159 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
160 mapped_at_creation: false,
161 });
162 let mut encoder = self
163 .device
164 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
165 label: Some("capture_readback_copy"),
166 });
167 encoder.copy_texture_to_buffer(
168 wgpu::TexelCopyTextureInfo {
169 texture: &texture,
170 mip_level: 0,
171 origin: wgpu::Origin3d::ZERO,
172 aspect: wgpu::TextureAspect::All,
173 },
174 wgpu::TexelCopyBufferInfo {
175 buffer: &staging,
176 layout: wgpu::TexelCopyBufferLayout {
177 offset: 0,
178 bytes_per_row: Some(padded_bpr),
179 rows_per_image: Some(h),
180 },
181 },
182 wgpu::Extent3d {
183 width: w,
184 height: h,
185 depth_or_array_layers: 1,
186 },
187 );
188 self.queue.submit(std::iter::once(encoder.finish()));
189
190 let slice = staging.slice(..);
191 let (sender, receiver) = std::sync::mpsc::channel();
192 slice.map_async(wgpu::MapMode::Read, move |res| {
193 let _ = sender.send(res);
194 });
195 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
196 if receiver.recv().ok().and_then(|r| r.ok()).is_none() {
197 return (Vec::new(), 0, 0);
198 }
199
200 let bgra = matches!(
201 self.surface_format,
202 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
203 );
204 let mut out = Vec::with_capacity((w as usize) * (h as usize) * 4);
205 {
206 let view = slice.get_mapped_range();
207 for row in 0..h as usize {
208 let start = row * padded_bpr as usize;
209 let end = start + unpadded_bpr as usize;
210 let src = &view[start..end];
211 if bgra {
212 for px in src.chunks_exact(4) {
213 out.extend_from_slice(&[px[2], px[1], px[0], px[3]]);
214 }
215 } else {
216 out.extend_from_slice(src);
217 }
218 }
219 }
220 staging.unmap();
221 (out, w, h)
222 }
223}
224
225impl WgpuGfxCtx {
232 pub fn has_pending_readback(&self) -> bool {
234 self.pending_readback.is_some()
235 }
236
237 pub fn begin_capture_readback(&mut self) -> bool {
241 if self.pending_readback.is_some() {
242 return false;
243 }
244 let Some((texture, _, w, h)) = self.capture_texture.as_ref() else {
245 return false;
246 };
247 let (w, h) = (*w, *h);
248 if w == 0 || h == 0 {
249 return false;
250 }
251 let texture = std::sync::Arc::clone(texture);
252
253 const ALIGN: u32 = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
254 let padded_bpr = (w * 4).div_ceil(ALIGN) * ALIGN;
255 let staging = self.device.create_buffer(&wgpu::BufferDescriptor {
256 label: Some("capture_readback_async"),
257 size: (padded_bpr as u64) * (h as u64),
258 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
259 mapped_at_creation: false,
260 });
261 let mut encoder = self
262 .device
263 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
264 label: Some("capture_readback_async_copy"),
265 });
266 encoder.copy_texture_to_buffer(
267 wgpu::TexelCopyTextureInfo {
268 texture: &texture,
269 mip_level: 0,
270 origin: wgpu::Origin3d::ZERO,
271 aspect: wgpu::TextureAspect::All,
272 },
273 wgpu::TexelCopyBufferInfo {
274 buffer: &staging,
275 layout: wgpu::TexelCopyBufferLayout {
276 offset: 0,
277 bytes_per_row: Some(padded_bpr),
278 rows_per_image: Some(h),
279 },
280 },
281 wgpu::Extent3d {
282 width: w,
283 height: h,
284 depth_or_array_layers: 1,
285 },
286 );
287 self.queue.submit(std::iter::once(encoder.finish()));
288
289 let (tx, rx) = std::sync::mpsc::channel();
290 staging
291 .slice(..)
292 .map_async(wgpu::MapMode::Read, move |res| {
293 let _ = tx.send(res);
294 });
295 self.pending_readback = Some(crate::PendingReadback {
296 staging,
297 w,
298 h,
299 padded_bpr,
300 done: rx,
301 });
302 true
303 }
304
305 pub fn poll_capture_readback(&mut self) -> Option<(Vec<u8>, u32, u32)> {
309 let _ = self.device.poll(wgpu::PollType::Poll);
310 match self.pending_readback.as_ref()?.done.try_recv() {
311 Ok(Ok(())) => {}
312 Err(std::sync::mpsc::TryRecvError::Empty) => return None, Ok(Err(_)) | Err(std::sync::mpsc::TryRecvError::Disconnected) => {
314 self.pending_readback = None; return None;
316 }
317 }
318
319 let crate::PendingReadback {
320 staging,
321 w,
322 h,
323 padded_bpr,
324 ..
325 } = self.pending_readback.take().unwrap();
326
327 let bgra = matches!(
328 self.surface_format,
329 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
330 );
331 let unpadded_bpr = (w * 4) as usize;
332 let mut out = Vec::with_capacity((w as usize) * (h as usize) * 4);
333 {
334 let view = staging.slice(..).get_mapped_range();
335 for row in 0..h as usize {
336 let start = row * padded_bpr as usize;
337 let src = &view[start..start + unpadded_bpr];
338 if bgra {
339 for px in src.chunks_exact(4) {
340 out.extend_from_slice(&[px[2], px[1], px[0], px[3]]);
341 }
342 } else {
343 out.extend_from_slice(src);
344 }
345 }
346 }
347 staging.unmap();
348 Some((out, w, h))
349 }
350}