зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
5c8de993e0
Коммит
ba01dca1d6
|
@ -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
|
||||||
|
|
Загрузка…
Ссылка в новой задаче