/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Screen capture infrastructure for the Gecko Profiler and Composition Recorder.
use std::collections::HashMap;
use api::{ImageFormat, ImageBufferKind}; use api::units::*; use gleam::gl::GlType;
/// A handle to a screenshot that is being asynchronously captured and scaled. #[repr(C)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pubstruct AsyncScreenshotHandle(usize);
/// A handle to a recorded frame that was captured. #[repr(C)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pubstruct 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, /// The stride of the data in the PBO.
buffer_stride: usize, /// 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(incrate) 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 AsyncScreenshotGrabber { /// Create a new AsyncScreenshotGrabber for the composition recorder. pubfn new_composition_recorder() -> Self { letmut recorder = Self::default();
recorder.mode = AsyncScreenshotGrabberMode::CompositionRecorder;
recorder
}
/// Deinitialize the allocated textures and PBOs. pubfn deinit(self, device: &mut Device) { for texture inself.scaling_textures {
device.delete_texture(texture);
}
for pbo inself.available_pbos {
device.delete_pbo(pbo);
}
for (_, async_screenshot) inself.awaiting_readback {
device.delete_pbo(async_screenshot.pbo);
}
}
/// 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. pubfn get_screenshot(
&mutself,
device: &mut Device,
window_rect: DeviceIntRect,
buffer_size: DeviceIntSize,
image_format: ImageFormat,
) -> (AsyncScreenshotHandle, DeviceIntSize) { let screenshot_size = matchself.mode {
AsyncScreenshotGrabberMode::ProfilerScreenshots => {
assert_ne!(window_rect.width(), 0);
assert_ne!(window_rect.height(), 0);
let scale = (buffer_size.width as f32 / window_rect.width() as f32)
.min(buffer_size.height as f32 / window_rect.height() as f32);
// To ensure that we hit the fast path when reading from a // framebuffer we must ensure that the width of the area we read // is a multiple of the device's optimal pixel-transfer stride. // The read_size should therefore be the screenshot_size with the width // increased to a suitable value. We will also pass this value to // scale_screenshot() as the min_texture_size, to ensure the texture is // large enough to read from. In CompositionRecorder mode we read // directly from the default framebuffer so are unable choose this size. let read_size = matchself.mode {
AsyncScreenshotGrabberMode::ProfilerScreenshots => { let stride = (screenshot_size.width * image_format.bytes_per_pixel()) as usize; let rounded = round_up_to_multiple(stride, device.required_pbo_stride().num_bytes(image_format)); let optimal_width = rounded as i32 / image_format.bytes_per_pixel();
DeviceIntSize::new(
optimal_width,
screenshot_size.height,
)
}
AsyncScreenshotGrabberMode::CompositionRecorder => buffer_size,
}; let required_size = read_size.area() as usize * image_format.bytes_per_pixel() as usize;
// Find an available PBO with the required size, creating a new one if necessary. let pbo = { letmut reusable_pbo = None; whilelet Some(pbo) = self.available_pbos.pop() { if pbo.get_reserved_size() != required_size {
device.delete_pbo(pbo);
} else {
reusable_pbo = Some(pbo); break;
}
};
/// Take the screenshot in the given `ReadTarget` and scale it to `dest_size` recursively. /// /// Each scaling operation scales only by a factor of two to preserve quality. /// /// Textures are scaled such that `scaling_textures[n]` is half the size of /// `scaling_textures[n+1]`. /// /// After the scaling completes, the final screenshot will be in /// `scaling_textures[0]`. /// /// The size of `scaling_textures[0]` will be increased to `min_texture_size` /// so that an optimally-sized area can be read from it. fn scale_screenshot(
&mutself,
device: &mut Device,
read_target: ReadTarget,
read_target_rect: DeviceIntRect,
buffer_size: DeviceIntSize,
min_texture_size: DeviceIntSize,
dest_size: DeviceIntSize,
image_format: ImageFormat,
level: usize,
) {
assert_eq!(self.mode, AsyncScreenshotGrabberMode::ProfilerScreenshots);
let texture_size = { let size = buffer_size * (1 << level);
DeviceIntSize::new(
size.width.max(min_texture_size.width),
size.height.max(min_texture_size.height),
)
};
// If we haven't created a texture for this level, or the existing // texture is the wrong size, then create a new one. if level == self.scaling_textures.len() || self.scaling_textures[level].get_dimensions() != texture_size { let texture = device.create_texture(
ImageBufferKind::Texture2D,
image_format,
texture_size.width,
texture_size.height,
TextureFilter::Linear,
Some(RenderTargetInfo { has_depth: false }),
); if level == self.scaling_textures.len() { self.scaling_textures.push(texture);
} else { let old_texture = std::mem::replace(&mutself.scaling_textures[level], texture);
device.delete_texture(old_texture);
}
}
assert_eq!(self.scaling_textures[level].get_dimensions(), texture_size);
// Stop recursing once the next level's scaling texture would exceed the // device's texture size limit; the device would otherwise clamp it and // fail the assertion above. This level then scales down by more than a // factor of two in a single (lower-quality) blit. let max_texture_size = device.max_texture_size(); let next_texture_size = dest_size * 2; let next_level_fits = next_texture_size.width <= max_texture_size
&& next_texture_size.height <= max_texture_size;
/// Map the contents of the screenshot given by the handle and copy it into /// the given buffer. pubfn map_and_recycle_screenshot(
&mutself,
device: &mut Device,
handle: AsyncScreenshotHandle,
dst_buffer: &mut [u8],
dst_stride: usize,
) -> bool { let AsyncScreenshot {
pbo,
screenshot_size,
buffer_stride,
image_format,
} = matchself.awaiting_readback.remove(&handle) {
Some(screenshot) => screenshot,
None => returnfalse,
};
let gl_type = device.gl().get_type();
let success = iflet Some(bound_pbo) = device.map_pbo_for_readback(&pbo) { let src_buffer = &bound_pbo.data; let src_stride = buffer_stride; let src_width =
screenshot_size.width as usize * image_format.bytes_per_pixel() as usize;
for (src_slice, dst_slice) inself
.iter_src_buffer_chunked(gl_type, src_buffer, src_stride)
.zip(dst_buffer.chunks_mut(dst_stride))
.take(screenshot_size.height as usize)
{
dst_slice[.. src_width].copy_from_slice(&src_slice[.. src_width]);
}
let is_angle = cfg!(windows) && gl_type == GlType::Gles;
ifself.mode == CompositionRecorder && !is_angle { // This is a non-ANGLE configuration. in this case, the recorded frames were captured // upside down, so we have to flip them right side up. Box::new(src_buffer.chunks(src_stride).rev())
} else { // This is either an ANGLE configuration in the `CompositionRecorder` mode or a // non-ANGLE configuration in the `ProfilerScreenshots` mode. In either case, the // captured frames are right-side up. Box::new(src_buffer.chunks(src_stride))
}
}
}
// 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. pubfn record_frame(
&mutself,
image_format: ImageFormat,
) -> Option<(RecordedFrameHandle, DeviceIntSize)> { let device_size = self.device_size()?; self.device.begin_frame();
/// Map a frame captured for the composition recorder into the given buffer. pubfn map_recorded_frame(
&mutself,
handle: RecordedFrameHandle,
dst_buffer: &mut [u8],
dst_stride: usize,
) -> bool { iflet Some(async_frame_recorder) = self.async_frame_recorder.as_mut() {
async_frame_recorder.map_and_recycle_screenshot(
&mutself.device,
AsyncScreenshotHandle(handle.0),
dst_buffer,
dst_stride,
)
} else { false
}
}
/// Free the data structures used by the composition recorder. pubfn release_composition_recorder_structures(&mutself) { iflet Some(async_frame_recorder) = self.async_frame_recorder.take() { self.device.begin_frame();
async_frame_recorder.deinit(&mutself.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. pubfn get_screenshot_async(
&mutself,
window_rect: DeviceIntRect,
buffer_size: DeviceIntSize,
image_format: ImageFormat,
) -> (AsyncScreenshotHandle, DeviceIntSize) { self.device.begin_frame();
let handle = self
.async_screenshots
.get_or_insert_with(AsyncScreenshotGrabber::default)
.get_screenshot(&mutself.device, window_rect, buffer_size, image_format);
self.device.end_frame();
handle
}
/// Map the contents of the screenshot given by the handle and copy it into /// the given buffer. pubfn map_and_recycle_screenshot(
&mutself,
handle: AsyncScreenshotHandle,
dst_buffer: &mut [u8],
dst_stride: usize,
) -> bool { iflet Some(async_screenshots) = self.async_screenshots.as_mut() {
async_screenshots.map_and_recycle_screenshot(
&mutself.device,
handle,
dst_buffer,
dst_stride,
)
} else { false
}
}
/// Release the screenshot grabbing structures that the profiler was using. pubfn release_profiler_structures(&mutself) { iflet Some(async_screenshots) = self.async_screenshots.take() { self.device.begin_frame();
async_screenshots.deinit(&mutself.device); self.device.end_frame();
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.