1use std::collections::HashMap;
8
9use api::{ImageFormat, ImageBufferKind};
10use api::units::*;
11use gleam::gl::GlType;
12
13use crate::device::{Device, PBO, DrawTarget, ReadTarget, Texture, TextureFilter};
14use crate::internal_types::RenderTargetInfo;
15use crate::renderer::Renderer;
16use crate::util::round_up_to_multiple;
17
18#[repr(C)]
20#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
21pub struct AsyncScreenshotHandle(usize);
22
23#[repr(C)]
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct RecordedFrameHandle(usize);
27
28struct AsyncScreenshot {
30 pbo: PBO,
32 screenshot_size: DeviceIntSize,
34 buffer_stride: usize,
36 image_format: ImageFormat,
38}
39
40#[derive(Debug, Eq, PartialEq)]
42enum AsyncScreenshotGrabberMode {
43 ProfilerScreenshots,
47
48 CompositionRecorder,
52}
53
54pub(in crate) struct AsyncScreenshotGrabber {
56 scaling_textures: Vec<Texture>,
58 available_pbos: Vec<PBO>,
60 awaiting_readback: HashMap<AsyncScreenshotHandle, AsyncScreenshot>,
62 next_pbo_handle: usize,
64 mode: AsyncScreenshotGrabberMode,
66}
67
68impl Default for AsyncScreenshotGrabber {
69 fn default() -> Self {
70 AsyncScreenshotGrabber {
71 scaling_textures: Vec::new(),
72 available_pbos: Vec::new(),
73 awaiting_readback: HashMap::new(),
74 next_pbo_handle: 1,
75 mode: AsyncScreenshotGrabberMode::ProfilerScreenshots,
76 }
77 }
78}
79
80impl AsyncScreenshotGrabber {
81 pub fn new_composition_recorder() -> Self {
83 let mut recorder = Self::default();
84 recorder.mode = AsyncScreenshotGrabberMode::CompositionRecorder;
85
86 recorder
87 }
88
89 pub fn deinit(self, device: &mut Device) {
91 for texture in self.scaling_textures {
92 device.delete_texture(texture);
93 }
94
95 for pbo in self.available_pbos {
96 device.delete_pbo(pbo);
97 }
98
99 for (_, async_screenshot) in self.awaiting_readback {
100 device.delete_pbo(async_screenshot.pbo);
101 }
102 }
103
104 pub fn get_screenshot(
110 &mut self,
111 device: &mut Device,
112 window_rect: DeviceIntRect,
113 buffer_size: DeviceIntSize,
114 image_format: ImageFormat,
115 ) -> (AsyncScreenshotHandle, DeviceIntSize) {
116 let screenshot_size = match self.mode {
117 AsyncScreenshotGrabberMode::ProfilerScreenshots => {
118 assert_ne!(window_rect.width(), 0);
119 assert_ne!(window_rect.height(), 0);
120
121 let scale = (buffer_size.width as f32 / window_rect.width() as f32)
122 .min(buffer_size.height as f32 / window_rect.height() as f32);
123
124 (window_rect.size().to_f32() * scale).round().to_i32()
125 }
126
127 AsyncScreenshotGrabberMode::CompositionRecorder => {
128 assert_eq!(buffer_size, window_rect.size());
129 buffer_size
130 }
131 };
132
133 assert!(screenshot_size.width <= buffer_size.width);
134 assert!(screenshot_size.height <= buffer_size.height);
135
136 let read_size = match self.mode {
145 AsyncScreenshotGrabberMode::ProfilerScreenshots => {
146 let stride = (screenshot_size.width * image_format.bytes_per_pixel()) as usize;
147 let rounded = round_up_to_multiple(stride, device.required_pbo_stride().num_bytes(image_format));
148 let optimal_width = rounded as i32 / image_format.bytes_per_pixel();
149
150 DeviceIntSize::new(
151 optimal_width,
152 screenshot_size.height,
153 )
154 }
155 AsyncScreenshotGrabberMode::CompositionRecorder => buffer_size,
156 };
157 let required_size = read_size.area() as usize * image_format.bytes_per_pixel() as usize;
158
159 let pbo = {
161 let mut reusable_pbo = None;
162 while let Some(pbo) = self.available_pbos.pop() {
163 if pbo.get_reserved_size() != required_size {
164 device.delete_pbo(pbo);
165 } else {
166 reusable_pbo = Some(pbo);
167 break;
168 }
169 };
170
171 reusable_pbo.unwrap_or_else(|| device.create_pbo_with_size(required_size))
172 };
173 assert_eq!(pbo.get_reserved_size(), required_size);
174
175 let read_target = match self.mode {
176 AsyncScreenshotGrabberMode::ProfilerScreenshots => {
177 self.scale_screenshot(
178 device,
179 ReadTarget::Default,
180 window_rect,
181 buffer_size,
182 read_size,
183 screenshot_size,
184 image_format,
185 0,
186 );
187
188 ReadTarget::from_texture(&self.scaling_textures[0])
189 }
190
191 AsyncScreenshotGrabberMode::CompositionRecorder => ReadTarget::Default,
192 };
193
194 device.read_pixels_into_pbo(
195 read_target,
196 DeviceIntRect::from_size(read_size),
197 image_format,
198 &pbo,
199 );
200
201 let handle = AsyncScreenshotHandle(self.next_pbo_handle);
202 self.next_pbo_handle += 1;
203
204 self.awaiting_readback.insert(
205 handle,
206 AsyncScreenshot {
207 pbo,
208 screenshot_size,
209 buffer_stride: (read_size.width * image_format.bytes_per_pixel()) as usize,
210 image_format,
211 },
212 );
213
214 (handle, screenshot_size)
215 }
216
217 fn scale_screenshot(
230 &mut self,
231 device: &mut Device,
232 read_target: ReadTarget,
233 read_target_rect: DeviceIntRect,
234 buffer_size: DeviceIntSize,
235 min_texture_size: DeviceIntSize,
236 dest_size: DeviceIntSize,
237 image_format: ImageFormat,
238 level: usize,
239 ) {
240 assert_eq!(self.mode, AsyncScreenshotGrabberMode::ProfilerScreenshots);
241
242 let texture_size = {
243 let size = buffer_size * (1 << level);
244 DeviceIntSize::new(
245 size.width.max(min_texture_size.width),
246 size.height.max(min_texture_size.height),
247 )
248 };
249
250 if level == self.scaling_textures.len() || self.scaling_textures[level].get_dimensions() != texture_size {
253 let texture = device.create_texture(
254 ImageBufferKind::Texture2D,
255 image_format,
256 texture_size.width,
257 texture_size.height,
258 TextureFilter::Linear,
259 Some(RenderTargetInfo { has_depth: false }),
260 );
261 if level == self.scaling_textures.len() {
262 self.scaling_textures.push(texture);
263 } else {
264 let old_texture = std::mem::replace(&mut self.scaling_textures[level], texture);
265 device.delete_texture(old_texture);
266 }
267 }
268 assert_eq!(self.scaling_textures[level].get_dimensions(), texture_size);
269
270 let (read_target, read_target_rect) = if read_target_rect.width() > 2 * dest_size.width {
271 self.scale_screenshot(
272 device,
273 read_target,
274 read_target_rect,
275 buffer_size,
276 min_texture_size,
277 dest_size * 2,
278 image_format,
279 level + 1,
280 );
281
282 (
283 ReadTarget::from_texture(&self.scaling_textures[level + 1]),
284 DeviceIntRect::from_size(dest_size * 2),
285 )
286 } else {
287 (read_target, read_target_rect)
288 };
289
290 let draw_target = DrawTarget::from_texture(&self.scaling_textures[level], false);
291
292 let draw_target_rect = draw_target
293 .to_framebuffer_rect(DeviceIntRect::from_size(dest_size));
294
295 let read_target_rect = device_rect_as_framebuffer_rect(&read_target_rect);
296
297 if level == 0 && !device.surface_origin_is_top_left() {
298 device.blit_render_target_invert_y(
299 read_target,
300 read_target_rect,
301 draw_target,
302 draw_target_rect,
303 );
304 } else {
305 device.blit_render_target(
306 read_target,
307 read_target_rect,
308 draw_target,
309 draw_target_rect,
310 TextureFilter::Linear,
311 );
312 }
313 }
314
315 pub fn map_and_recycle_screenshot(
318 &mut self,
319 device: &mut Device,
320 handle: AsyncScreenshotHandle,
321 dst_buffer: &mut [u8],
322 dst_stride: usize,
323 ) -> bool {
324 let AsyncScreenshot {
325 pbo,
326 screenshot_size,
327 buffer_stride,
328 image_format,
329 } = match self.awaiting_readback.remove(&handle) {
330 Some(screenshot) => screenshot,
331 None => return false,
332 };
333
334 let gl_type = device.gl().get_type();
335
336 let success = if let Some(bound_pbo) = device.map_pbo_for_readback(&pbo) {
337 let src_buffer = &bound_pbo.data;
338 let src_stride = buffer_stride;
339 let src_width =
340 screenshot_size.width as usize * image_format.bytes_per_pixel() as usize;
341
342 for (src_slice, dst_slice) in self
343 .iter_src_buffer_chunked(gl_type, src_buffer, src_stride)
344 .zip(dst_buffer.chunks_mut(dst_stride))
345 .take(screenshot_size.height as usize)
346 {
347 dst_slice[.. src_width].copy_from_slice(&src_slice[.. src_width]);
348 }
349
350 true
351 } else {
352 false
353 };
354
355 match self.mode {
356 AsyncScreenshotGrabberMode::ProfilerScreenshots => self.available_pbos.push(pbo),
357 AsyncScreenshotGrabberMode::CompositionRecorder => device.delete_pbo(pbo),
358 }
359
360 success
361 }
362
363 fn iter_src_buffer_chunked<'a>(
364 &self,
365 gl_type: GlType,
366 src_buffer: &'a [u8],
367 src_stride: usize,
368 ) -> Box<dyn Iterator<Item = &'a [u8]> + 'a> {
369 use AsyncScreenshotGrabberMode::*;
370
371 let is_angle = cfg!(windows) && gl_type == GlType::Gles;
372
373 if self.mode == CompositionRecorder && !is_angle {
374 Box::new(src_buffer.chunks(src_stride).rev())
377 } else {
378 Box::new(src_buffer.chunks(src_stride))
382 }
383 }
384}
385
386impl Renderer {
388 pub fn record_frame(
394 &mut self,
395 image_format: ImageFormat,
396 ) -> Option<(RecordedFrameHandle, DeviceIntSize)> {
397 let device_size = self.device_size()?;
398 self.device.begin_frame();
399
400 let (handle, _) = self
401 .async_frame_recorder
402 .get_or_insert_with(AsyncScreenshotGrabber::new_composition_recorder)
403 .get_screenshot(
404 &mut self.device,
405 DeviceIntRect::from_size(device_size),
406 device_size,
407 image_format,
408 );
409
410 self.device.end_frame();
411
412 Some((RecordedFrameHandle(handle.0), device_size))
413 }
414
415 pub fn map_recorded_frame(
417 &mut self,
418 handle: RecordedFrameHandle,
419 dst_buffer: &mut [u8],
420 dst_stride: usize,
421 ) -> bool {
422 if let Some(async_frame_recorder) = self.async_frame_recorder.as_mut() {
423 async_frame_recorder.map_and_recycle_screenshot(
424 &mut self.device,
425 AsyncScreenshotHandle(handle.0),
426 dst_buffer,
427 dst_stride,
428 )
429 } else {
430 false
431 }
432 }
433
434 pub fn release_composition_recorder_structures(&mut self) {
436 if let Some(async_frame_recorder) = self.async_frame_recorder.take() {
437 self.device.begin_frame();
438 async_frame_recorder.deinit(&mut self.device);
439 self.device.end_frame();
440 }
441 }
442
443 pub fn get_screenshot_async(
450 &mut self,
451 window_rect: DeviceIntRect,
452 buffer_size: DeviceIntSize,
453 image_format: ImageFormat,
454 ) -> (AsyncScreenshotHandle, DeviceIntSize) {
455 self.device.begin_frame();
456
457 let handle = self
458 .async_screenshots
459 .get_or_insert_with(AsyncScreenshotGrabber::default)
460 .get_screenshot(&mut self.device, window_rect, buffer_size, image_format);
461
462 self.device.end_frame();
463
464 handle
465 }
466
467 pub fn map_and_recycle_screenshot(
470 &mut self,
471 handle: AsyncScreenshotHandle,
472 dst_buffer: &mut [u8],
473 dst_stride: usize,
474 ) -> bool {
475 if let Some(async_screenshots) = self.async_screenshots.as_mut() {
476 async_screenshots.map_and_recycle_screenshot(
477 &mut self.device,
478 handle,
479 dst_buffer,
480 dst_stride,
481 )
482 } else {
483 false
484 }
485 }
486
487 pub fn release_profiler_structures(&mut self) {
489 if let Some(async_screenshots) = self.async_screenshots.take() {
490 self.device.begin_frame();
491 async_screenshots.deinit(&mut self.device);
492 self.device.end_frame();
493 }
494 }
495}