tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

screen_capture.rs (17518B)


      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 
      7 use std::collections::HashMap;
      8 
      9 use api::{ImageFormat, ImageBufferKind};
     10 use api::units::*;
     11 use gleam::gl::GlType;
     12 
     13 use crate::device::{Device, PBO, DrawTarget, ReadTarget, Texture, TextureFilter};
     14 use crate::internal_types::RenderTargetInfo;
     15 use crate::renderer::Renderer;
     16 use 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)]
     21 pub struct AsyncScreenshotHandle(usize);
     22 
     23 /// A handle to a recorded frame that was captured.
     24 #[repr(C)]
     25 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
     26 pub struct RecordedFrameHandle(usize);
     27 
     28 /// An asynchronously captured screenshot bound to a PBO which has not yet been mapped for copying.
     29 struct 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)]
     42 enum 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.
     55 pub(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 
     68 impl 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 
     80 impl 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.
    387 impl 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 }