Bug 1551735 - Add a mode to the AsyncScreenshotGrabber to enable composition recording r=kvark
☠☠ backed out by 1b10a1e394f4 ☠ ☠
authorBarret Rennie <barret@brennie.ca>
Wed, 29 May 2019 21:52:44 +0000
changeset 476255 2e6ca6d6c527229f7a8e7d96e1616c8aeda2d0d2
parent 476254 3b2078f907151398e4e55a9276fddef213c48416
child 476256 5891d00fca8535e64c6009860e93132c6ce44a6f
push id36090
push usernbeleuzu@mozilla.com
push dateFri, 31 May 2019 03:59:09 +0000
treeherdermozilla-central@63568b2a8178 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1551735
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1551735 - Add a mode to the AsyncScreenshotGrabber to enable composition recording r=kvark The AsyncScreenshotGrabber now can operate in two modes: * `ProfilerScreenshots`, which does asynchronous scaling of the captured frames for inclusion in profiles by the Gecko Profiler; and * `CompositionRecorder`, which does not do any scaling and is used for visual metrics computations. The latter mode is exposed by on the `Renderer` via the `record_frame`, `map_recorded_frame`, and `release_composition_recorder_structures` methods. A different handle type (`RecordedFrameHandle`) is returned and consumed by these functions, but they translate between `RecordedFrameHandle` and `AsyncScreenshotHandle` when communicating with the underlying `AsyncScreenshotGrabber`. I considered making the `AsyncScreenshotGrabber` generic over its handle type, but the extra cost of monomorphization just to change the handle type did not seem worth it. Differential Revision: https://phabricator.services.mozilla.com/D32232
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/screen_capture.rs
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -1675,16 +1675,17 @@ pub struct Renderer {
     /// heap-allocated pointer.
     size_of_ops: Option<MallocSizeOfOps>,
 
     // Currently allocated FBOs for output frames.
     output_targets: FastHashMap<u32, FrameOutput>,
 
     pub renderer_errors: Vec<RendererError>,
 
+    pub(in crate) async_frame_recorder: Option<AsyncScreenshotGrabber>,
     pub(in crate) async_screenshots: Option<AsyncScreenshotGrabber>,
 
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
     gpu_profiles: VecDeque<GpuProfile>,
 
     /// Notification requests to be fulfilled after rendering.
@@ -2179,16 +2180,17 @@ impl Renderer {
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
             gpu_cache_debug_chunks: Vec::new(),
             gpu_cache_frame_id: FrameId::INVALID,
             gpu_cache_overflow: false,
             texture_cache_upload_pbo,
             texture_resolver,
             renderer_errors: Vec::new(),
+            async_frame_recorder: None,
             async_screenshots: None,
             #[cfg(feature = "capture")]
             read_fbo,
             #[cfg(feature = "replay")]
             owned_external_images: FastHashMap::default(),
             notifications: Vec::new(),
             device_size: None,
             zoom_debug_texture: None,
--- a/gfx/wr/webrender/src/screen_capture.rs
+++ b/gfx/wr/webrender/src/screen_capture.rs
@@ -1,62 +1,92 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-//! Screen capture infrastructure for the Gecko Profiler.
+//! Screen capture infrastructure for the Gecko Profiler and Composition Recorder.
 
 use std::collections::HashMap;
 
 use api::{ImageFormat, TextureTarget};
 use api::units::*;
 
 use crate::device::{Device, PBO, DrawTarget, ReadTarget, Texture, TextureFilter};
 use crate::internal_types::RenderTargetInfo;
 use crate::renderer::Renderer;
 
 /// A handle to a screenshot that is being asynchronously captured and scaled.
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
 pub struct AsyncScreenshotHandle(usize);
 
+/// A handle to a recorded frame that was captured.
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct RecordedFrameHandle(usize);
+
 /// An asynchronously captured screenshot bound to a PBO which has not yet been mapped for copying.
 struct AsyncScreenshot {
     /// The PBO that will contain the screenshot data.
     pbo: PBO,
     /// The size of the screenshot.
     screenshot_size: DeviceIntSize,
     /// Thge image format of the screenshot.
     image_format: ImageFormat,
 }
 
+/// How the `AsyncScreenshotGrabber` captures frames.
+#[derive(Debug, Eq, PartialEq)]
+enum AsyncScreenshotGrabberMode {
+    /// Capture screenshots for the Gecko profiler.
+    ///
+    /// This mode will asynchronously scale the screenshots captured.
+    ProfilerScreenshots,
+
+    /// Capture screenshots for the CompositionRecorder.
+    ///
+    /// This mode does not scale the captured screenshots.
+    CompositionRecorder,
+}
+
 /// Renderer infrastructure for capturing screenshots and scaling them asynchronously.
 pub(in crate) struct AsyncScreenshotGrabber {
     /// The textures used to scale screenshots.
     scaling_textures: Vec<Texture>,
     /// PBOs available to be used for screenshot readback.
     available_pbos: Vec<PBO>,
     /// PBOs containing screenshots that are awaiting readback.
     awaiting_readback: HashMap<AsyncScreenshotHandle, AsyncScreenshot>,
     /// The handle for the net PBO that will be inserted into `in_use_pbos`.
     next_pbo_handle: usize,
+    /// The mode the grabber operates in.
+    mode: AsyncScreenshotGrabberMode,
 }
 
 impl Default for AsyncScreenshotGrabber {
     fn default() -> Self {
         return AsyncScreenshotGrabber {
             scaling_textures: Vec::new(),
             available_pbos: Vec::new(),
             awaiting_readback: HashMap::new(),
             next_pbo_handle: 1,
+            mode: AsyncScreenshotGrabberMode::ProfilerScreenshots,
         };
     }
 }
 
 impl AsyncScreenshotGrabber {
+    /// Create a new AsyncScreenshotGrabber for the composition recorder.
+    pub fn new_composition_recorder() -> Self {
+        let mut recorder = Self::default();
+        recorder.mode = AsyncScreenshotGrabberMode::CompositionRecorder;
+
+        recorder
+    }
+
     /// Deinitialize the allocated textures and PBOs.
     pub fn deinit(self, device: &mut Device) {
         for texture in self.scaling_textures {
             device.delete_texture(texture);
         }
 
         for pbo in self.available_pbos {
             device.delete_pbo(pbo);
@@ -74,45 +104,72 @@ impl AsyncScreenshotGrabber {
     /// The returned size is the size of the screenshot.
     pub fn get_screenshot(
         &mut self,
         device: &mut Device,
         window_rect: DeviceIntRect,
         buffer_size: DeviceIntSize,
         image_format: ImageFormat,
     ) -> (AsyncScreenshotHandle, DeviceIntSize) {
-        let scale = (buffer_size.width as f32 / window_rect.size.width as f32)
-            .min(buffer_size.height as f32 / window_rect.size.height as f32);
-        let screenshot_size = (window_rect.size.to_f32() * scale).round().to_i32();
+        let screenshot_size = match self.mode {
+            AsyncScreenshotGrabberMode::ProfilerScreenshots => {
+                let scale = (buffer_size.width as f32 / window_rect.size.width as f32)
+                    .min(buffer_size.height as f32 / window_rect.size.height as f32);
+
+                (window_rect.size.to_f32() * scale).round().to_i32()
+            }
+
+            AsyncScreenshotGrabberMode::CompositionRecorder => {
+                assert_eq!(buffer_size, window_rect.size);
+                buffer_size
+            }
+        };
+
         let required_size = buffer_size.area() as usize * image_format.bytes_per_pixel() as usize;
 
         assert!(screenshot_size.width <= buffer_size.width);
         assert!(screenshot_size.height <= buffer_size.height);
 
-        let pbo = match self.available_pbos.pop() {
-            Some(pbo) => {
-                assert_eq!(pbo.get_reserved_size(), required_size);
-                pbo
+        let pbo = match self.mode {
+            AsyncScreenshotGrabberMode::ProfilerScreenshots => match self.available_pbos.pop() {
+                Some(pbo) => {
+                    assert_eq!(pbo.get_reserved_size(), required_size);
+                    pbo
+                }
+
+                None => device.create_pbo_with_size(required_size),
+            },
+
+            // When operating in the `CompositionRecorder` mode, PBOs are not mapped for readback
+            // until the recording has completed, so `self.available_pbos` will always be empty.
+            AsyncScreenshotGrabberMode::CompositionRecorder => {
+                device.create_pbo_with_size(required_size)
             }
-
-            None => device.create_pbo_with_size(required_size),
         };
 
-        self.scale_screenshot(
-            device,
-            ReadTarget::Default,
-            window_rect,
-            buffer_size,
-            screenshot_size,
-            image_format,
-            0,
-        );
+        let read_target = match self.mode {
+            AsyncScreenshotGrabberMode::ProfilerScreenshots => {
+                self.scale_screenshot(
+                    device,
+                    ReadTarget::Default,
+                    window_rect,
+                    buffer_size,
+                    screenshot_size,
+                    image_format,
+                    0,
+                );
+
+                ReadTarget::from_texture(&self.scaling_textures[0], 0)
+            }
+
+            AsyncScreenshotGrabberMode::CompositionRecorder => ReadTarget::Default,
+        };
 
         device.read_pixels_into_pbo(
-            ReadTarget::from_texture(&self.scaling_textures[0], 0),
+            read_target,
             DeviceIntRect::new(DeviceIntPoint::new(0, 0), screenshot_size),
             image_format,
             &pbo,
         );
 
         let handle = AsyncScreenshotHandle(self.next_pbo_handle);
         self.next_pbo_handle += 1;
 
@@ -142,16 +199,18 @@ impl AsyncScreenshotGrabber {
         device: &mut Device,
         read_target: ReadTarget,
         read_target_rect: DeviceIntRect,
         buffer_size: DeviceIntSize,
         dest_size: DeviceIntSize,
         image_format: ImageFormat,
         level: usize,
     ) {
+        assert_eq!(self.mode, AsyncScreenshotGrabberMode::ProfilerScreenshots);
+
         let texture_size = buffer_size * (1 << level);
         if level == self.scaling_textures.len() {
             let texture = device.create_texture(
                 TextureTarget::Default,
                 image_format,
                 texture_size.width,
                 texture_size.height,
                 TextureFilter::Linear,
@@ -240,23 +299,82 @@ impl AsyncScreenshotGrabber {
                 dst_slice[.. src_stride].copy_from_slice(src_slice);
             }
 
             true
         } else {
             false
         };
 
-        self.available_pbos.push(pbo);
+        match self.mode {
+            AsyncScreenshotGrabberMode::ProfilerScreenshots => self.available_pbos.push(pbo),
+            AsyncScreenshotGrabberMode::CompositionRecorder => device.delete_pbo(pbo),
+        }
+
         success
     }
 }
 
 // Screen-capture specific Renderer impls.
 impl Renderer {
+    /// Record a frame for the Composition Recorder.
+    ///
+    /// The returned handle can be passed to `map_recorded_frame` to copy it into
+    /// a buffer.
+    /// The returned size is the size of the frame.
+    pub fn record_frame(
+        &mut self,
+        image_format: ImageFormat,
+    ) -> Option<(RecordedFrameHandle, DeviceIntSize)> {
+        let device_size = self.device_size()?;
+        self.device.begin_frame();
+
+        let (handle, _) = self
+            .async_frame_recorder
+            .get_or_insert_with(AsyncScreenshotGrabber::new_composition_recorder)
+            .get_screenshot(
+                &mut self.device,
+                DeviceIntRect::new(DeviceIntPoint::new(0, 0), device_size),
+                device_size,
+                image_format,
+            );
+
+        self.device.end_frame();
+
+        Some((RecordedFrameHandle(handle.0), device_size))
+    }
+
+    /// Map a frame captured for the composition recorder into the given buffer.
+    pub fn map_recorded_frame(
+        &mut self,
+        handle: RecordedFrameHandle,
+        dst_buffer: &mut [u8],
+        dst_stride: usize,
+    ) -> bool {
+        if let Some(async_frame_recorder) = self.async_frame_recorder.as_mut() {
+            async_frame_recorder.map_and_recycle_screenshot(
+                &mut self.device,
+                AsyncScreenshotHandle(handle.0),
+                dst_buffer,
+                dst_stride,
+            )
+        } else {
+            false
+        }
+    }
+
+    /// Free the data structures used by the composition recorder.
+    pub fn release_composition_recorder_structures(&mut self) {
+        if let Some(async_frame_recorder) = self.async_frame_recorder.take() {
+            self.device.begin_frame();
+            async_frame_recorder.deinit(&mut self.device);
+            self.device.end_frame();
+        }
+    }
+
     /// Take a screenshot and scale it asynchronously.
     ///
     /// The returned handle can be used to access the mapped screenshot data via
     /// `map_and_recycle_screenshot`.
     ///
     /// The returned size is the size of the screenshot.
     pub fn get_screenshot_async(
         &mut self,