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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Barret Rennie 2019-05-31 00:30:52 +00:00
Родитель 5c8de993e0
Коммит ba01dca1d6
2 изменённых файлов: 141 добавлений и 21 удалений

Просмотреть файл

@ -1680,6 +1680,7 @@ pub struct Renderer {
pub renderer_errors: Vec<RendererError>, pub renderer_errors: Vec<RendererError>,
pub(in crate) async_frame_recorder: Option<AsyncScreenshotGrabber>,
pub(in crate) async_screenshots: Option<AsyncScreenshotGrabber>, pub(in crate) async_screenshots: Option<AsyncScreenshotGrabber>,
/// List of profile results from previous frames. Can be retrieved /// List of profile results from previous frames. Can be retrieved
@ -2185,6 +2186,7 @@ impl Renderer {
texture_cache_upload_pbo, texture_cache_upload_pbo,
texture_resolver, texture_resolver,
renderer_errors: Vec::new(), renderer_errors: Vec::new(),
async_frame_recorder: None,
async_screenshots: None, async_screenshots: None,
#[cfg(feature = "capture")] #[cfg(feature = "capture")]
read_fbo, read_fbo,

Просмотреть файл

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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 std::collections::HashMap;
@ -18,6 +18,11 @@ use crate::renderer::Renderer;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct AsyncScreenshotHandle(usize); 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. /// An asynchronously captured screenshot bound to a PBO which has not yet been mapped for copying.
struct AsyncScreenshot { struct AsyncScreenshot {
/// The PBO that will contain the screenshot data. /// The PBO that will contain the screenshot data.
@ -28,6 +33,20 @@ struct AsyncScreenshot {
image_format: ImageFormat, 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. /// Renderer infrastructure for capturing screenshots and scaling them asynchronously.
pub(in crate) struct AsyncScreenshotGrabber { pub(in crate) struct AsyncScreenshotGrabber {
/// The textures used to scale screenshots. /// The textures used to scale screenshots.
@ -38,6 +57,8 @@ pub(in crate) struct AsyncScreenshotGrabber {
awaiting_readback: HashMap<AsyncScreenshotHandle, AsyncScreenshot>, awaiting_readback: HashMap<AsyncScreenshotHandle, AsyncScreenshot>,
/// The handle for the net PBO that will be inserted into `in_use_pbos`. /// The handle for the net PBO that will be inserted into `in_use_pbos`.
next_pbo_handle: usize, next_pbo_handle: usize,
/// The mode the grabber operates in.
mode: AsyncScreenshotGrabberMode,
} }
impl Default for AsyncScreenshotGrabber { impl Default for AsyncScreenshotGrabber {
@ -47,11 +68,20 @@ impl Default for AsyncScreenshotGrabber {
available_pbos: Vec::new(), available_pbos: Vec::new(),
awaiting_readback: HashMap::new(), awaiting_readback: HashMap::new(),
next_pbo_handle: 1, next_pbo_handle: 1,
mode: AsyncScreenshotGrabberMode::ProfilerScreenshots,
}; };
} }
} }
impl AsyncScreenshotGrabber { 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. /// Deinitialize the allocated textures and PBOs.
pub fn deinit(self, device: &mut Device) { pub fn deinit(self, device: &mut Device) {
for texture in self.scaling_textures { for texture in self.scaling_textures {
@ -79,35 +109,62 @@ impl AsyncScreenshotGrabber {
buffer_size: DeviceIntSize, buffer_size: DeviceIntSize,
image_format: ImageFormat, image_format: ImageFormat,
) -> (AsyncScreenshotHandle, DeviceIntSize) { ) -> (AsyncScreenshotHandle, DeviceIntSize) {
let scale = (buffer_size.width as f32 / window_rect.size.width as f32) let screenshot_size = match self.mode {
.min(buffer_size.height as f32 / window_rect.size.height as f32); AsyncScreenshotGrabberMode::ProfilerScreenshots => {
let screenshot_size = (window_rect.size.to_f32() * scale).round().to_i32(); 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; 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.width <= buffer_size.width);
assert!(screenshot_size.height <= buffer_size.height); assert!(screenshot_size.height <= buffer_size.height);
let pbo = match self.available_pbos.pop() { let pbo = match self.mode {
Some(pbo) => { AsyncScreenshotGrabberMode::ProfilerScreenshots => match self.available_pbos.pop() {
assert_eq!(pbo.get_reserved_size(), required_size); Some(pbo) => {
pbo assert_eq!(pbo.get_reserved_size(), required_size);
} pbo
}
None => device.create_pbo_with_size(required_size), 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)
}
}; };
self.scale_screenshot( let read_target = match self.mode {
device, AsyncScreenshotGrabberMode::ProfilerScreenshots => {
ReadTarget::Default, self.scale_screenshot(
window_rect, device,
buffer_size, ReadTarget::Default,
screenshot_size, window_rect,
image_format, buffer_size,
0, screenshot_size,
); image_format,
0,
);
ReadTarget::from_texture(&self.scaling_textures[0], 0)
}
AsyncScreenshotGrabberMode::CompositionRecorder => ReadTarget::Default,
};
device.read_pixels_into_pbo( device.read_pixels_into_pbo(
ReadTarget::from_texture(&self.scaling_textures[0], 0), read_target,
DeviceIntRect::new(DeviceIntPoint::new(0, 0), screenshot_size), DeviceIntRect::new(DeviceIntPoint::new(0, 0), screenshot_size),
image_format, image_format,
&pbo, &pbo,
@ -147,6 +204,8 @@ impl AsyncScreenshotGrabber {
image_format: ImageFormat, image_format: ImageFormat,
level: usize, level: usize,
) { ) {
assert_eq!(self.mode, AsyncScreenshotGrabberMode::ProfilerScreenshots);
let texture_size = buffer_size * (1 << level); let texture_size = buffer_size * (1 << level);
if level == self.scaling_textures.len() { if level == self.scaling_textures.len() {
let texture = device.create_texture( let texture = device.create_texture(
@ -245,13 +304,72 @@ impl AsyncScreenshotGrabber {
false false
}; };
self.available_pbos.push(pbo); match self.mode {
AsyncScreenshotGrabberMode::ProfilerScreenshots => self.available_pbos.push(pbo),
AsyncScreenshotGrabberMode::CompositionRecorder => device.delete_pbo(pbo),
}
success success
} }
} }
// Screen-capture specific Renderer impls. // Screen-capture specific Renderer impls.
impl Renderer { 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. /// Take a screenshot and scale it asynchronously.
/// ///
/// The returned handle can be used to access the mapped screenshot data via /// The returned handle can be used to access the mapped screenshot data via