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 }