webrender/
screen_capture.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! Screen capture infrastructure for the Gecko Profiler and Composition Recorder.
6
7use 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/// A handle to a screenshot that is being asynchronously captured and scaled.
19#[repr(C)]
20#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
21pub struct AsyncScreenshotHandle(usize);
22
23/// A handle to a recorded frame that was captured.
24#[repr(C)]
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct RecordedFrameHandle(usize);
27
28/// An asynchronously captured screenshot bound to a PBO which has not yet been mapped for copying.
29struct AsyncScreenshot {
30    /// The PBO that will contain the screenshot data.
31    pbo: PBO,
32    /// The size of the screenshot.
33    screenshot_size: DeviceIntSize,
34    /// The stride of the data in the PBO.
35    buffer_stride: usize,
36    /// Thge image format of the screenshot.
37    image_format: ImageFormat,
38}
39
40/// How the `AsyncScreenshotGrabber` captures frames.
41#[derive(Debug, Eq, PartialEq)]
42enum AsyncScreenshotGrabberMode {
43    /// Capture screenshots for the Gecko profiler.
44    ///
45    /// This mode will asynchronously scale the screenshots captured.
46    ProfilerScreenshots,
47
48    /// Capture screenshots for the CompositionRecorder.
49    ///
50    /// This mode does not scale the captured screenshots.
51    CompositionRecorder,
52}
53
54/// Renderer infrastructure for capturing screenshots and scaling them asynchronously.
55pub(in crate) struct AsyncScreenshotGrabber {
56    /// The textures used to scale screenshots.
57    scaling_textures: Vec<Texture>,
58    /// PBOs available to be used for screenshot readback.
59    available_pbos: Vec<PBO>,
60    /// PBOs containing screenshots that are awaiting readback.
61    awaiting_readback: HashMap<AsyncScreenshotHandle, AsyncScreenshot>,
62    /// The handle for the net PBO that will be inserted into `in_use_pbos`.
63    next_pbo_handle: usize,
64    /// The mode the grabber operates in.
65    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    /// Create a new AsyncScreenshotGrabber for the composition recorder.
82    pub fn new_composition_recorder() -> Self {
83        let mut recorder = Self::default();
84        recorder.mode = AsyncScreenshotGrabberMode::CompositionRecorder;
85
86        recorder
87    }
88
89    /// Deinitialize the allocated textures and PBOs.
90    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    /// Take a screenshot and scale it asynchronously.
105    ///
106    /// The returned handle can be used to access the mapped screenshot data via
107    /// `map_and_recycle_screenshot`.
108    /// The returned size is the size of the screenshot.
109    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        // To ensure that we hit the fast path when reading from a
137        // framebuffer we must ensure that the width of the area we read
138        // is a multiple of the device's optimal pixel-transfer stride.
139        // The read_size should therefore be the screenshot_size with the width
140        // increased to a suitable value. We will also pass this value to
141        // scale_screenshot() as the min_texture_size, to ensure the texture is
142        // large enough to read from. In CompositionRecorder mode we read
143        // directly from the default framebuffer so are unable choose this size.
144        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        // Find an available PBO with the required size, creating a new one if necessary.
160        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    /// Take the screenshot in the given `ReadTarget` and scale it to `dest_size` recursively.
218    ///
219    /// Each scaling operation scales only by a factor of two to preserve quality.
220    ///
221    /// Textures are scaled such that `scaling_textures[n]` is half the size of
222    /// `scaling_textures[n+1]`.
223    ///
224    /// After the scaling completes, the final screenshot will be in
225    /// `scaling_textures[0]`.
226    ///
227    /// The size of `scaling_textures[0]` will be increased to `min_texture_size`
228    /// so that an optimally-sized area can be read from it.
229    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 we haven't created a texture for this level, or the existing
251        // texture is the wrong size, then create a new one.
252        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    /// Map the contents of the screenshot given by the handle and copy it into
316    /// the given buffer.
317    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            // This is a non-ANGLE configuration. in this case, the recorded frames were captured
375            // upside down, so we have to flip them right side up.
376            Box::new(src_buffer.chunks(src_stride).rev())
377        } else {
378            // This is either an ANGLE configuration in the `CompositionRecorder` mode or a
379            // non-ANGLE configuration in the `ProfilerScreenshots` mode. In either case, the
380            // captured frames are right-side up.
381            Box::new(src_buffer.chunks(src_stride))
382        }
383    }
384}
385
386// Screen-capture specific Renderer impls.
387impl Renderer {
388    /// Record a frame for the Composition Recorder.
389    ///
390    /// The returned handle can be passed to `map_recorded_frame` to copy it into
391    /// a buffer.
392    /// The returned size is the size of the frame.
393    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    /// Map a frame captured for the composition recorder into the given buffer.
416    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    /// Free the data structures used by the composition recorder.
435    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    /// Take a screenshot and scale it asynchronously.
444    ///
445    /// The returned handle can be used to access the mapped screenshot data via
446    /// `map_and_recycle_screenshot`.
447    ///
448    /// The returned size is the size of the screenshot.
449    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    /// Map the contents of the screenshot given by the handle and copy it into
468    /// the given buffer.
469    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    /// Release the screenshot grabbing structures that the profiler was using.
488    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}