Source code

Revision control

Other Tools

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, TextureTarget};
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
let scale = (buffer_size.width as f32 / window_rect.size.width as f32)
119
.min(buffer_size.height as f32 / window_rect.size.height as f32);
120
121
(window_rect.size.to_f32() * scale).round().to_i32()
122
}
123
124
AsyncScreenshotGrabberMode::CompositionRecorder => {
125
assert_eq!(buffer_size, window_rect.size);
126
buffer_size
127
}
128
};
129
130
assert!(screenshot_size.width <= buffer_size.width);
131
assert!(screenshot_size.height <= buffer_size.height);
132
133
// To ensure that we hit the fast path when reading from a
134
// framebuffer we must ensure that the width of the area we read
135
// is a multiple of the device's optimal pixel-transfer stride.
136
// The read_size should therefore be the screenshot_size with the width
137
// increased to a suitable value. We will also pass this value to
138
// scale_screenshot() as the min_texture_size, to ensure the texture is
139
// large enough to read from. In CompositionRecorder mode we read
140
// directly from the default framebuffer so are unable choose this size.
141
let read_size = match self.mode {
142
AsyncScreenshotGrabberMode::ProfilerScreenshots => {
143
let stride = (screenshot_size.width * image_format.bytes_per_pixel()) as usize;
144
let rounded = round_up_to_multiple(stride, device.optimal_pbo_stride());
145
let optimal_width = rounded as i32 / image_format.bytes_per_pixel();
146
147
DeviceIntSize::new(
148
optimal_width,
149
screenshot_size.height,
150
)
151
}
152
AsyncScreenshotGrabberMode::CompositionRecorder => buffer_size,
153
};
154
let required_size = read_size.area() as usize * image_format.bytes_per_pixel() as usize;
155
156
// Find an available PBO with the required size, creating a new one if necessary.
157
let pbo = {
158
let mut reusable_pbo = None;
159
while let Some(pbo) = self.available_pbos.pop() {
160
if pbo.get_reserved_size() != required_size {
161
device.delete_pbo(pbo);
162
} else {
163
reusable_pbo = Some(pbo);
164
break;
165
}
166
};
167
168
reusable_pbo.unwrap_or_else(|| device.create_pbo_with_size(required_size))
169
};
170
assert_eq!(pbo.get_reserved_size(), required_size);
171
172
let read_target = match self.mode {
173
AsyncScreenshotGrabberMode::ProfilerScreenshots => {
174
self.scale_screenshot(
175
device,
176
ReadTarget::Default,
177
window_rect,
178
buffer_size,
179
read_size,
180
screenshot_size,
181
image_format,
182
0,
183
);
184
185
ReadTarget::from_texture(&self.scaling_textures[0], 0)
186
}
187
188
AsyncScreenshotGrabberMode::CompositionRecorder => ReadTarget::Default,
189
};
190
191
device.read_pixels_into_pbo(
192
read_target,
193
DeviceIntRect::new(DeviceIntPoint::new(0, 0), read_size),
194
image_format,
195
&pbo,
196
);
197
198
let handle = AsyncScreenshotHandle(self.next_pbo_handle);
199
self.next_pbo_handle += 1;
200
201
self.awaiting_readback.insert(
202
handle,
203
AsyncScreenshot {
204
pbo,
205
screenshot_size,
206
buffer_stride: (read_size.width * image_format.bytes_per_pixel()) as usize,
207
image_format,
208
},
209
);
210
211
(handle, screenshot_size)
212
}
213
214
/// Take the screenshot in the given `ReadTarget` and scale it to `dest_size` recursively.
215
///
216
/// Each scaling operation scales only by a factor of two to preserve quality.
217
///
218
/// Textures are scaled such that `scaling_textures[n]` is half the size of
219
/// `scaling_textures[n+1]`.
220
///
221
/// After the scaling completes, the final screenshot will be in
222
/// `scaling_textures[0]`.
223
///
224
/// The size of `scaling_textures[0]` will be increased to `min_texture_size`
225
/// so that an optimally-sized area can be read from it.
226
fn scale_screenshot(
227
&mut self,
228
device: &mut Device,
229
read_target: ReadTarget,
230
read_target_rect: DeviceIntRect,
231
buffer_size: DeviceIntSize,
232
min_texture_size: DeviceIntSize,
233
dest_size: DeviceIntSize,
234
image_format: ImageFormat,
235
level: usize,
236
) {
237
assert_eq!(self.mode, AsyncScreenshotGrabberMode::ProfilerScreenshots);
238
239
let texture_size = {
240
let size = buffer_size * (1 << level);
241
DeviceIntSize::new(
242
size.width.max(min_texture_size.width),
243
size.height.max(min_texture_size.height),
244
)
245
};
246
247
// If we haven't created a texture for this level, or the existing
248
// texture is the wrong size, then create a new one.
249
if level == self.scaling_textures.len() || self.scaling_textures[level].get_dimensions() != texture_size {
250
let texture = device.create_texture(
251
TextureTarget::Default,
252
image_format,
253
texture_size.width,
254
texture_size.height,
255
TextureFilter::Linear,
256
Some(RenderTargetInfo { has_depth: false }),
257
1,
258
);
259
if level == self.scaling_textures.len() {
260
self.scaling_textures.push(texture);
261
} else {
262
let old_texture = std::mem::replace(&mut self.scaling_textures[level], texture);
263
device.delete_texture(old_texture);
264
}
265
}
266
assert_eq!(self.scaling_textures[level].get_dimensions(), texture_size);
267
268
let (read_target, read_target_rect) = if read_target_rect.size.width > 2 * dest_size.width {
269
self.scale_screenshot(
270
device,
271
read_target,
272
read_target_rect,
273
buffer_size,
274
min_texture_size,
275
dest_size * 2,
276
image_format,
277
level + 1,
278
);
279
280
(
281
ReadTarget::from_texture(&self.scaling_textures[level + 1], 0),
282
DeviceIntRect::new(DeviceIntPoint::new(0, 0), dest_size * 2),
283
)
284
} else {
285
(read_target, read_target_rect)
286
};
287
288
let draw_target = DrawTarget::from_texture(&self.scaling_textures[level], 0 as _, false);
289
290
let draw_target_rect = draw_target
291
.to_framebuffer_rect(DeviceIntRect::new(DeviceIntPoint::new(0, 0), dest_size));
292
293
let read_target_rect = device_rect_as_framebuffer_rect(&read_target_rect);
294
295
if level == 0 && !device.surface_origin_is_top_left() {
296
device.blit_render_target_invert_y(
297
read_target,
298
read_target_rect,
299
draw_target,
300
draw_target_rect,
301
);
302
} else {
303
device.blit_render_target(
304
read_target,
305
read_target_rect,
306
draw_target,
307
draw_target_rect,
308
TextureFilter::Linear,
309
);
310
}
311
}
312
313
/// Map the contents of the screenshot given by the handle and copy it into
314
/// the given buffer.
315
pub fn map_and_recycle_screenshot(
316
&mut self,
317
device: &mut Device,
318
handle: AsyncScreenshotHandle,
319
dst_buffer: &mut [u8],
320
dst_stride: usize,
321
) -> bool {
322
let AsyncScreenshot {
323
pbo,
324
screenshot_size,
325
buffer_stride,
326
image_format,
327
} = match self.awaiting_readback.remove(&handle) {
328
Some(screenshot) => screenshot,
329
None => return false,
330
};
331
332
let gl_type = device.gl().get_type();
333
334
let success = if let Some(bound_pbo) = device.map_pbo_for_readback(&pbo) {
335
let src_buffer = &bound_pbo.data;
336
let src_stride = buffer_stride;
337
let src_width =
338
screenshot_size.width as usize * image_format.bytes_per_pixel() as usize;
339
340
for (src_slice, dst_slice) in self
341
.iter_src_buffer_chunked(gl_type, src_buffer, src_stride)
342
.zip(dst_buffer.chunks_mut(dst_stride))
343
.take(screenshot_size.height as usize)
344
{
345
dst_slice[.. src_width].copy_from_slice(&src_slice[.. src_width]);
346
}
347
348
true
349
} else {
350
false
351
};
352
353
match self.mode {
354
AsyncScreenshotGrabberMode::ProfilerScreenshots => self.available_pbos.push(pbo),
355
AsyncScreenshotGrabberMode::CompositionRecorder => device.delete_pbo(pbo),
356
}
357
358
success
359
}
360
361
fn iter_src_buffer_chunked<'a>(
362
&self,
363
gl_type: GlType,
364
src_buffer: &'a [u8],
365
src_stride: usize,
366
) -> Box<dyn Iterator<Item = &'a [u8]> + 'a> {
367
use AsyncScreenshotGrabberMode::*;
368
369
let is_angle = cfg!(windows) && gl_type == GlType::Gles;
370
371
if self.mode == CompositionRecorder && !is_angle {
372
// This is a non-ANGLE configuration. in this case, the recorded frames were captured
373
// upside down, so we have to flip them right side up.
374
Box::new(src_buffer.chunks(src_stride).rev())
375
} else {
376
// This is either an ANGLE configuration in the `CompositionRecorder` mode or a
377
// non-ANGLE configuration in the `ProfilerScreenshots` mode. In either case, the
378
// captured frames are right-side up.
379
Box::new(src_buffer.chunks(src_stride))
380
}
381
}
382
}
383
384
// Screen-capture specific Renderer impls.
385
impl Renderer {
386
/// Record a frame for the Composition Recorder.
387
///
388
/// The returned handle can be passed to `map_recorded_frame` to copy it into
389
/// a buffer.
390
/// The returned size is the size of the frame.
391
pub fn record_frame(
392
&mut self,
393
image_format: ImageFormat,
394
) -> Option<(RecordedFrameHandle, DeviceIntSize)> {
395
let device_size = self.device_size()?;
396
self.device.begin_frame();
397
398
let (handle, _) = self
399
.async_frame_recorder
400
.get_or_insert_with(AsyncScreenshotGrabber::new_composition_recorder)
401
.get_screenshot(
402
&mut self.device,
403
DeviceIntRect::new(DeviceIntPoint::new(0, 0), device_size),
404
device_size,
405
image_format,
406
);
407
408
self.device.end_frame();
409
410
Some((RecordedFrameHandle(handle.0), device_size))
411
}
412
413
/// Map a frame captured for the composition recorder into the given buffer.
414
pub fn map_recorded_frame(
415
&mut self,
416
handle: RecordedFrameHandle,
417
dst_buffer: &mut [u8],
418
dst_stride: usize,
419
) -> bool {
420
if let Some(async_frame_recorder) = self.async_frame_recorder.as_mut() {
421
async_frame_recorder.map_and_recycle_screenshot(
422
&mut self.device,
423
AsyncScreenshotHandle(handle.0),
424
dst_buffer,
425
dst_stride,
426
)
427
} else {
428
false
429
}
430
}
431
432
/// Free the data structures used by the composition recorder.
433
pub fn release_composition_recorder_structures(&mut self) {
434
if let Some(async_frame_recorder) = self.async_frame_recorder.take() {
435
self.device.begin_frame();
436
async_frame_recorder.deinit(&mut self.device);
437
self.device.end_frame();
438
}
439
}
440
441
/// Take a screenshot and scale it asynchronously.
442
///
443
/// The returned handle can be used to access the mapped screenshot data via
444
/// `map_and_recycle_screenshot`.
445
///
446
/// The returned size is the size of the screenshot.
447
pub fn get_screenshot_async(
448
&mut self,
449
window_rect: DeviceIntRect,
450
buffer_size: DeviceIntSize,
451
image_format: ImageFormat,
452
) -> (AsyncScreenshotHandle, DeviceIntSize) {
453
self.device.begin_frame();
454
455
let handle = self
456
.async_screenshots
457
.get_or_insert_with(AsyncScreenshotGrabber::default)
458
.get_screenshot(&mut self.device, window_rect, buffer_size, image_format);
459
460
self.device.end_frame();
461
462
handle
463
}
464
465
/// Map the contents of the screenshot given by the handle and copy it into
466
/// the given buffer.
467
pub fn map_and_recycle_screenshot(
468
&mut self,
469
handle: AsyncScreenshotHandle,
470
dst_buffer: &mut [u8],
471
dst_stride: usize,
472
) -> bool {
473
if let Some(async_screenshots) = self.async_screenshots.as_mut() {
474
async_screenshots.map_and_recycle_screenshot(
475
&mut self.device,
476
handle,
477
dst_buffer,
478
dst_stride,
479
)
480
} else {
481
false
482
}
483
}
484
485
/// Release the screenshot grabbing structures that the profiler was using.
486
pub fn release_profiler_structures(&mut self) {
487
if let Some(async_screenshots) = self.async_screenshots.take() {
488
self.device.begin_frame();
489
async_screenshots.deinit(&mut self.device);
490
self.device.end_frame();
491
}
492
}
493
}