diff --git a/.gitignore b/.gitignore index 0b43b20312..a89b0cd9c3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ target/ *# # WR internals -webrender/captures +captures wrench/json_frames wrench/ron_frames diff --git a/Cargo.lock b/Cargo.lock index 6c8538699c..8cfb52b789 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,7 +105,7 @@ name = "cgl" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gleam 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -359,12 +359,22 @@ dependencies = [ "xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gl_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "khronos_api 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gleam" -version = "0.4.15" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gl_generator 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gl_generator 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1011,7 +1021,7 @@ dependencies = [ "euclid 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "freetype 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1074,7 +1084,7 @@ dependencies = [ "env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "font-loader 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gleam 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)", + "gleam 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1192,7 +1202,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" "checksum gif 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f" "checksum gl_generator 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "75d69f914b49d9ff32fdf394cbd798f8c716d74fd19f9cc29da3e99797b2a78d" -"checksum gleam 0.4.15 (registry+https://github.com/rust-lang/crates.io-index)" = "dff613336334932baaa2759d001f14e06ea1a08a247c05962d1423aa0e89ee99" +"checksum gl_generator 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f5c19cde55637681450c92f7a05ea16c78e2b6d0587e601ec1ebdab6960854b" +"checksum gleam 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "4f756699879522bc654ecc44ad42ad14c59803c2dacfa5a67a7fc27257a8b4e9" "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" "checksum image 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d1576ffa01849c91b484b95c01d54dddc242b4d50923eaa2d4d74a58c4b9e8fd" diff --git a/webrender/Cargo.toml b/webrender/Cargo.toml index e3e36bdf94..d28c607710 100644 --- a/webrender/Cargo.toml +++ b/webrender/Cargo.toml @@ -19,7 +19,7 @@ bincode = "0.9" byteorder = "1.0" euclid = "0.16" fxhash = "0.2.1" -gleam = "0.4.15" +gleam = "0.4.19" lazy_static = "1" log = "0.3" num-traits = "0.1.32" diff --git a/webrender/examples/animation.rs b/webrender/examples/animation.rs index c78dbc4431..05298216e6 100644 --- a/webrender/examples/animation.rs +++ b/webrender/examples/animation.rs @@ -66,7 +66,7 @@ impl Example for App { ); // Fill it with a white rect - builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0)); + builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0)); builder.pop_stacking_context(); } diff --git a/webrender/examples/common/boilerplate.rs b/webrender/examples/common/boilerplate.rs index c0edce9f6f..4b88fc2137 100644 --- a/webrender/examples/common/boilerplate.rs +++ b/webrender/examples/common/boilerplate.rs @@ -280,12 +280,11 @@ pub fn main_wrapper( _, Some(glutin::VirtualKeyCode::C), ) => { - let path: PathBuf = "captures/example".into(); - if path.is_dir() { - api.load_capture(path); - } else { - api.save_capture(path); - } + let path: PathBuf = "../captures/example".into(); + //TODO: switch between SCENE/FRAME capture types + // based on "shift" modifier, when `glutin` is updated. + let bits = CaptureBits::all(); + api.save_capture(path, bits); } _ => if example.on_event(event, &api, document_id) { let mut builder = DisplayListBuilder::new(pipeline_id, layout_size); diff --git a/webrender/examples/frame_output.rs b/webrender/examples/frame_output.rs index 317ca1c77c..13f021aee2 100644 --- a/webrender/examples/frame_output.rs +++ b/webrender/examples/frame_output.rs @@ -77,7 +77,7 @@ impl App { ImageData::External(ExternalImageData { id: ExternalImageId(0), channel_index: 0, - image_type: ExternalImageType::Texture2DHandle + image_type: ExternalImageType::Texture2DHandle, }), None, ); @@ -126,7 +126,7 @@ impl App { true, resources, ); - + api.generate_frame(document.id, None); self.output_document = Some(document); } @@ -174,7 +174,7 @@ impl Example for App { fn get_image_handlers( &mut self, gl: &gl::Gl, - ) -> (Option>, + ) -> (Option>, Option>) { let texture_id = gl.gen_textures(1)[0]; @@ -212,8 +212,8 @@ impl Example for App { ); gl.bind_texture(gl::TEXTURE_2D, 0); - ( - Some(Box::new(ExternalHandler { texture_id })), + ( + Some(Box::new(ExternalHandler { texture_id })), Some(Box::new(OutputHandler { texture_id })) ) } diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index be7c10dbe3..342f1d655b 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -35,6 +35,7 @@ use util::{MatrixHelpers, TransformedRectKind}; const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(i32::MAX as u32); #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum TransformBatchKind { TextRun(GlyphFormat), Image(ImageBufferKind), @@ -47,6 +48,7 @@ pub enum TransformBatchKind { } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum BrushImageSourceKind { Alpha, Color, @@ -63,6 +65,7 @@ impl BrushImageSourceKind { } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum BrushBatchKind { Image(BrushImageSourceKind), Solid, @@ -70,6 +73,7 @@ pub enum BrushBatchKind { } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum BatchKind { Composite { task_id: RenderTaskId, @@ -86,6 +90,7 @@ pub enum BatchKind { /// Optional textures that can be used as a source in the shaders. /// Textures that are not used by the batch are equal to TextureId::invalid(). #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct BatchTextures { pub colors: [SourceTexture; 3], } @@ -115,6 +120,7 @@ impl BatchTextures { } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct AlphaPrimitiveBatch { pub key: BatchKey, pub instances: Vec, @@ -132,6 +138,7 @@ impl AlphaPrimitiveBatch { } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct OpaquePrimitiveBatch { pub key: BatchKey, pub instances: Vec, @@ -147,6 +154,7 @@ impl OpaquePrimitiveBatch { } #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct BatchKey { pub kind: BatchKind, pub blend_mode: BlendMode, @@ -175,6 +183,7 @@ fn textures_compatible(t1: SourceTexture, t2: SourceTexture) -> bool { t1 == SourceTexture::Invalid || t2 == SourceTexture::Invalid || t1 == t2 } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct AlphaBatchList { pub batches: Vec, } @@ -252,6 +261,7 @@ impl AlphaBatchList { } } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct OpaqueBatchList { pub pixel_area_threshold_for_new_batch: i32, pub batches: Vec, @@ -317,6 +327,7 @@ impl OpaqueBatchList { } } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct BatchList { pub alpha_batch_list: AlphaBatchList, pub opaque_batch_list: OpaqueBatchList, @@ -363,6 +374,7 @@ impl BatchList { } /// Encapsulates the logic of building batches for items that are blended. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct AlphaBatcher { pub batch_list: BatchList, pub text_run_cache_prims: FastHashMap>, @@ -1435,6 +1447,7 @@ fn make_polygon( /// Batcher managing draw calls into the clip mask (in the RT cache). #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct ClipBatcher { /// Rectangle draws fill up the rectangles with rounded corners. pub rectangles: Vec, diff --git a/webrender/src/box_shadow.rs b/webrender/src/box_shadow.rs index 4462d85c41..f9552f13a0 100644 --- a/webrender/src/box_shadow.rs +++ b/webrender/src/box_shadow.rs @@ -29,6 +29,7 @@ pub const MAX_BLUR_RADIUS : f32 = 300.; pub const MASK_CORNER_PADDING: f32 = 4.0; #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct BoxShadowCacheKey { pub width: Au, pub height: Au, diff --git a/webrender/src/capture.rs b/webrender/src/capture.rs new file mode 100644 index 0000000000..cedbe6b040 --- /dev/null +++ b/webrender/src/capture.rs @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +use std::fs::File; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + +use api::{CaptureBits, ExternalImageData, ImageDescriptor}; +use ron::{de, ser}; +use serde::{Deserialize, Serialize}; + + +pub struct CaptureConfig { + pub root: PathBuf, + pub bits: CaptureBits, + pretty: ser::PrettyConfig, +} + +impl CaptureConfig { + pub fn new(root: PathBuf, bits: CaptureBits) -> Self { + CaptureConfig { + root, + bits, + pretty: ser::PrettyConfig::default(), + } + } + + pub fn serialize(&self, data: &T, name: P) + where + T: Serialize, + P: AsRef, + { + let ron = ser::to_string_pretty(data, self.pretty.clone()) + .unwrap(); + let path = self.root + .join(name) + .with_extension("ron"); + let mut file = File::create(path) + .unwrap(); + write!(file, "{}\n", ron) + .unwrap(); + } + + pub fn deserialize(root: &PathBuf, name: P) -> Option + where + T: for<'a> Deserialize<'a>, + P: AsRef, + { + let mut string = String::new(); + let path = root + .join(name) + .with_extension("ron"); + File::open(path) + .ok()? + .read_to_string(&mut string) + .unwrap(); + Some(de::from_str(&string) + .unwrap()) + } +} + +#[derive(Deserialize, Serialize)] +pub struct ExternalCaptureImage { + pub short_path: String, + pub descriptor: ImageDescriptor, + pub external: ExternalImageData, +} diff --git a/webrender/src/clip_scroll_tree.rs b/webrender/src/clip_scroll_tree.rs index 058dfc5a91..f736fa6d1a 100644 --- a/webrender/src/clip_scroll_tree.rs +++ b/webrender/src/clip_scroll_tree.rs @@ -23,6 +23,7 @@ pub type ScrollStates = FastHashMap; /// system are the same or are in the same axis-aligned space. This allows /// for optimizing mask generation. #[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct CoordinateSystemId(pub u32); impl CoordinateSystemId { diff --git a/webrender/src/device.rs b/webrender/src/device.rs index 4cc28c121c..963981994f 100644 --- a/webrender/src/device.rs +++ b/webrender/src/device.rs @@ -26,6 +26,7 @@ use std::thread; const WORK_AROUND_TEX_IMAGE: bool = cfg!(windows); #[derive(Debug, Copy, Clone, PartialEq, Ord, Eq, PartialOrd)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct FrameId(usize); impl FrameId { @@ -85,6 +86,7 @@ impl TextureTarget { } #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum TextureFilter { Nearest, Linear, @@ -126,6 +128,14 @@ pub enum UploadMethod { PixelBuffer(VertexUsageHint), } +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ReadPixelsFormat { + R8, + Rgba8, + Bgra8, + Rgba32F, +} + pub fn get_gl_format_bgra(gl: &gl::Gl) -> gl::GLuint { match gl.get_type() { gl::GlType::Gl => GL_FORMAT_BGRA_GL, @@ -409,13 +419,14 @@ impl Drop for VBO { } } +#[cfg_attr(feature = "capture", derive(Clone))] pub struct ExternalTexture { id: gl::GLuint, target: gl::GLuint, } impl ExternalTexture { - pub fn new(id: u32, target: TextureTarget) -> ExternalTexture { + pub fn new(id: u32, target: TextureTarget) -> Self { ExternalTexture { id, target: target.to_gl_target(), @@ -453,6 +464,14 @@ impl Texture { self.format } + pub fn get_filter(&self) -> TextureFilter { + self.filter + } + + pub fn get_render_target(&self) -> Option { + self.render_target.clone() + } + pub fn get_bpp(&self) -> u32 { match self.format { ImageFormat::R8 => 1, @@ -470,6 +489,16 @@ impl Texture { pub fn get_rt_info(&self) -> Option<&RenderTargetInfo> { self.render_target.as_ref() } + + #[cfg(feature = "capture")] + pub fn into_external(mut self) -> ExternalTexture { + let ext = ExternalTexture { + id: self.id, + target: self.target, + }; + self.id = 0; // don't complain, moved out + ext + } } impl Drop for Texture { @@ -829,7 +858,7 @@ impl Device { self.bind_texture_impl(sampler.into(), external_texture.id, external_texture.target); } - fn bind_read_target_impl(&mut self, fbo_id: FBOId) { + pub fn bind_read_target_impl(&mut self, fbo_id: FBOId) { debug_assert!(self.inside_frame); if self.bound_read_fbo != fbo_id { @@ -878,7 +907,7 @@ impl Device { pub fn create_fbo_for_external_texture(&mut self, texture_id: u32) -> FBOId { let fbo = FBOId(self.gl.gen_framebuffers(1)[0]); - self.bind_external_draw_target(fbo); + fbo.bind(self.gl(), FBOTarget::Draw); self.gl.framebuffer_texture_2d( gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0, @@ -886,6 +915,7 @@ impl Device { texture_id, 0, ); + self.bound_draw_fbo.bind(self.gl(), FBOTarget::Draw); fbo } @@ -964,7 +994,7 @@ impl Device { self.bind_texture(DEFAULT_TEXTURE, texture); self.set_texture_parameters(texture.target, texture.filter); - self.update_texture_storage(texture, &rt_info, true, false); + self.update_target_storage(texture, &rt_info, true, false, None); let rect = DeviceIntRect::new(DeviceIntPoint::zero(), old_size.to_i32()); for (read_fbo, &draw_fbo) in old_fbos.into_iter().zip(&texture.fbo_ids) { @@ -1006,65 +1036,31 @@ impl Device { match render_target { Some(info) => { - assert!(pixels.is_none()); - self.update_texture_storage(texture, &info, is_resized, is_format_changed); + self.update_target_storage(texture, &info, is_resized, is_format_changed, pixels); } None => { - let (internal_format, gl_format) = gl_texture_formats_for_image_format(self.gl(), format); - let type_ = gl_type_for_texture_format(format); - - match texture.target { - gl::TEXTURE_2D_ARRAY => { - self.gl.tex_image_3d( - gl::TEXTURE_2D_ARRAY, - 0, - internal_format as gl::GLint, - width as gl::GLint, - height as gl::GLint, - layer_count, - 0, - gl_format, - type_, - pixels, - ); - } - gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => { - self.gl.tex_image_2d( - texture.target, - 0, - internal_format as gl::GLint, - width as gl::GLint, - height as gl::GLint, - 0, - gl_format, - type_, - pixels, - ); - } - _ => panic!("BUG: Unexpected texture target!"), - } + self.update_texture_storage(texture, pixels); } } } - /// Updates the texture storage for the texture, creating FBOs as required. - fn update_texture_storage( + /// Updates the render target storage for the texture, creating FBOs as required. + fn update_target_storage( &mut self, texture: &mut Texture, rt_info: &RenderTargetInfo, is_resized: bool, is_format_changed: bool, + pixels: Option<&[u8]>, ) { assert!(texture.layer_count > 0); let needed_layer_count = texture.layer_count - texture.fbo_ids.len() as i32; - let allocate_color = needed_layer_count != 0 || is_resized || is_format_changed; + let allocate_color = needed_layer_count != 0 || + is_resized || is_format_changed || pixels.is_some(); if allocate_color { - let (internal_format, gl_format) = - gl_texture_formats_for_image_format(&*self.gl, texture.format); - let type_ = gl_type_for_texture_format(texture.format); - + let desc = gl_describe_format(self.gl(), texture.format); match texture.target { gl::TEXTURE_2D_ARRAY => { if WORK_AROUND_TEX_IMAGE { @@ -1083,14 +1079,14 @@ impl Device { self.gl.tex_image_3d( texture.target, 0, - internal_format as _, + desc.internal, texture.width as _, texture.height as _, texture.layer_count, 0, - gl_format, - type_, - None, + desc.external, + desc.pixel_type, + pixels, ) } _ => { @@ -1098,13 +1094,13 @@ impl Device { self.gl.tex_image_2d( texture.target, 0, - internal_format as _, + desc.internal, texture.width as _, texture.height as _, 0, - gl_format, - type_, - None, + desc.external, + desc.pixel_type, + pixels, ) } } @@ -1187,6 +1183,40 @@ impl Device { } } + fn update_texture_storage(&mut self, texture: &Texture, pixels: Option<&[u8]>) { + let desc = gl_describe_format(self.gl(), texture.format); + match texture.target { + gl::TEXTURE_2D_ARRAY => { + self.gl.tex_image_3d( + gl::TEXTURE_2D_ARRAY, + 0, + desc.internal, + texture.width as _, + texture.height as _, + texture.layer_count, + 0, + desc.external, + desc.pixel_type, + pixels, + ); + } + gl::TEXTURE_2D | gl::TEXTURE_RECTANGLE | gl::TEXTURE_EXTERNAL_OES => { + self.gl.tex_image_2d( + texture.target, + 0, + desc.internal, + texture.width as _, + texture.height as _, + 0, + desc.external, + desc.pixel_type, + pixels, + ); + } + _ => panic!("BUG: Unexpected texture target!"), + } + } + pub fn blit_render_target(&mut self, src_rect: DeviceIntRect, dest_rect: DeviceIntRect) { debug_assert!(self.inside_frame); @@ -1204,48 +1234,49 @@ impl Device { ); } - pub fn free_texture_storage(&mut self, texture: &mut Texture) { - debug_assert!(self.inside_frame); - - if texture.format == ImageFormat::Invalid { - return; - } - - self.bind_texture(DEFAULT_TEXTURE, texture); - - let (internal_format, gl_format) = - gl_texture_formats_for_image_format(&*self.gl, texture.format); - let type_ = gl_type_for_texture_format(texture.format); - - match texture.target { + fn free_texture_storage_impl(&mut self, target: gl::GLenum, desc: FormatDesc) { + match target { gl::TEXTURE_2D_ARRAY => { self.gl.tex_image_3d( gl::TEXTURE_2D_ARRAY, 0, - internal_format as gl::GLint, + desc.internal, 0, 0, 0, 0, - gl_format, - type_, + desc.external, + desc.pixel_type, None, ); } _ => { self.gl.tex_image_2d( - texture.target, + target, 0, - internal_format, + desc.internal, 0, 0, 0, - gl_format, - type_, + desc.external, + desc.pixel_type, None, ); } } + } + + pub fn free_texture_storage(&mut self, texture: &mut Texture) { + debug_assert!(self.inside_frame); + + if texture.format == ImageFormat::Invalid { + return; + } + + self.bind_texture(DEFAULT_TEXTURE, texture); + let desc = gl_describe_format(self.gl(), texture.format); + + self.free_texture_storage_impl(texture.target, desc); if let Some(RBOId(depth_rb)) = texture.depth_rb.take() { self.gl.delete_renderbuffers(&[depth_rb]); @@ -1272,6 +1303,19 @@ impl Device { texture.id = 0; } + #[cfg(feature = "capture")] + pub fn delete_external_texture(&mut self, mut external: ExternalTexture) { + self.bind_external_texture(DEFAULT_TEXTURE, &external); + //Note: the format descriptor here doesn't really matter + self.free_texture_storage_impl(external.target, FormatDesc { + internal: gl::R8 as _, + external: gl::RED, + pixel_type: gl::UNSIGNED_BYTE, + }); + self.gl.delete_textures(&[external.id]); + external.id = 0; + } + pub fn delete_program(&mut self, mut program: Program) { self.gl.delete_program(program.id); program.id = 0; @@ -1490,19 +1534,82 @@ impl Device { } } - pub fn read_pixels(&mut self, desc: &ImageDescriptor) -> Vec { - let (_, gl_format) = gl_texture_formats_for_image_format(self.gl(), desc.format); - let type_ = gl_type_for_texture_format(desc.format); - + pub fn read_pixels(&mut self, img_desc: &ImageDescriptor) -> Vec { + let desc = gl_describe_format(self.gl(), img_desc.format); self.gl.read_pixels( 0, 0, - desc.width as i32, - desc.height as i32, - gl_format, - type_, + img_desc.width as i32, + img_desc.height as i32, + desc.external, + desc.pixel_type, ) } + /// Read rectangle of RGBA8 or BGRA8 pixels into the specified output slice. + pub fn read_pixels_into( + &mut self, + rect: DeviceUintRect, + format: ReadPixelsFormat, + output: &mut [u8], + ) { + let (gl_format, gl_type, pixel_size) = match format { + ReadPixelsFormat::R8 => (gl::RED, gl::UNSIGNED_BYTE, 1), + ReadPixelsFormat::Rgba8 => (gl::RGBA, gl::UNSIGNED_BYTE, 4), + ReadPixelsFormat::Bgra8 => (get_gl_format_bgra(self.gl()), gl::UNSIGNED_BYTE, 4), + ReadPixelsFormat::Rgba32F => (gl::RGBA, gl::FLOAT, 16), + }; + let size_in_bytes = (pixel_size * rect.size.width * rect.size.height) as usize; + assert_eq!(output.len(), size_in_bytes); + + self.gl.flush(); + self.gl.read_pixels_into_buffer( + rect.origin.x as _, + rect.origin.y as _, + rect.size.width as _, + rect.size.height as _, + gl_format, + gl_type, + output, + ); + } + + /// Attaches the provided texture to the current Read FBO binding. + fn attach_read_texture_raw( + &mut self, texture_id: gl::GLuint, target: gl::GLuint, layer_id: i32 + ) { + match target { + gl::TEXTURE_2D_ARRAY => { + self.gl.framebuffer_texture_layer( + gl::READ_FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + texture_id, + 0, + layer_id, + ) + } + _ => { + assert_eq!(layer_id, 0); + self.gl.framebuffer_texture_2d( + gl::READ_FRAMEBUFFER, + gl::COLOR_ATTACHMENT0, + target, + texture_id, + 0, + ) + } + } + } + + pub fn attach_read_texture_external( + &mut self, texture_id: gl::GLuint, target: TextureTarget, layer_id: i32 + ) { + self.attach_read_texture_raw(texture_id, target.to_gl_target(), layer_id) + } + + pub fn attach_read_texture(&mut self, texture: &Texture, layer_id: i32) { + self.attach_read_texture_raw(texture.id, texture.target, layer_id) + } + fn bind_vao_impl(&mut self, id: gl::GLuint) { debug_assert!(self.inside_frame); @@ -1925,31 +2032,44 @@ impl Device { } } -/// return (gl_internal_format, gl_format) -fn gl_texture_formats_for_image_format( - gl: &gl::Gl, - format: ImageFormat, -) -> (gl::GLint, gl::GLuint) { - match format { - ImageFormat::R8 => (gl::RED as gl::GLint, gl::RED), - ImageFormat::BGRA8 => match gl.get_type() { - gl::GlType::Gl => (gl::RGBA as gl::GLint, get_gl_format_bgra(gl)), - gl::GlType::Gles => (get_gl_format_bgra(gl) as gl::GLint, get_gl_format_bgra(gl)), - }, - ImageFormat::RGBAF32 => (gl::RGBA32F as gl::GLint, gl::RGBA), - ImageFormat::RG8 => (gl::RG8 as gl::GLint, gl::RG), - ImageFormat::Invalid => unreachable!(), - } +struct FormatDesc { + internal: gl::GLint, + external: gl::GLuint, + pixel_type: gl::GLuint, } -fn gl_type_for_texture_format(format: ImageFormat) -> gl::GLuint { +fn gl_describe_format(gl: &gl::Gl, format: ImageFormat) -> FormatDesc { match format { - ImageFormat::RGBAF32 => gl::FLOAT, - _ => gl::UNSIGNED_BYTE, + ImageFormat::R8 => FormatDesc { + internal: gl::RED as _, + external: gl::RED, + pixel_type: gl::UNSIGNED_BYTE, + }, + ImageFormat::BGRA8 => { + let external = get_gl_format_bgra(gl); + FormatDesc { + internal: match gl.get_type() { + gl::GlType::Gl => gl::RGBA as _, + gl::GlType::Gles => external as _, + }, + external, + pixel_type: gl::UNSIGNED_BYTE, + } + }, + ImageFormat::RGBAF32 => FormatDesc { + internal: gl::RGBA32F as _, + external: gl::RGBA, + pixel_type: gl::FLOAT, + }, + ImageFormat::RG8 => FormatDesc { + internal: gl::RG8 as _, + external: gl::RG, + pixel_type: gl::UNSIGNED_BYTE, + }, + ImageFormat::Invalid => unreachable!(), } } - struct UploadChunk { rect: DeviceUintRect, layer_index: i32, diff --git a/webrender/src/frame.rs b/webrender/src/frame.rs index ab45111db9..481f54a9b6 100644 --- a/webrender/src/frame.rs +++ b/webrender/src/frame.rs @@ -21,9 +21,10 @@ use internal_types::{FastHashMap, FastHashSet, RenderedDocument}; use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters}; use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap}; use scene::{Scene, StackingContextHelpers, ScenePipeline, SceneProperties}; -use tiling::CompositeOps; +use tiling::{CompositeOps, Frame}; #[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct FrameId(pub u32); static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { @@ -1095,6 +1096,11 @@ impl FrameContext { self.pipeline_epoch_map.insert(pipeline_id, epoch); } + pub fn make_rendered_document(&self, frame: Frame) -> RenderedDocument { + let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back(); + RenderedDocument::new(self.pipeline_epoch_map.clone(), nodes_bouncing_back, frame) + } + pub fn build_rendered_document( &mut self, frame_builder: &mut FrameBuilder, @@ -1122,8 +1128,6 @@ impl FrameContext { gpu_cache_profile, scene_properties, ); - - let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back(); - RenderedDocument::new(self.pipeline_epoch_map.clone(), nodes_bouncing_back, frame) + self.make_rendered_document(frame) } } diff --git a/webrender/src/freelist.rs b/webrender/src/freelist.rs index 17e3b07fa7..f7c0c35acc 100644 --- a/webrender/src/freelist.rs +++ b/webrender/src/freelist.rs @@ -10,9 +10,11 @@ use util::recycle_vec; // retain() style functionality. #[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct Epoch(u32); #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct FreeListHandle { index: u32, epoch: Epoch, @@ -30,7 +32,7 @@ impl FreeListHandle { } impl Clone for WeakFreeListHandle { - fn clone(&self) -> WeakFreeListHandle { + fn clone(&self) -> Self { WeakFreeListHandle { index: self.index, epoch: self.epoch, @@ -40,18 +42,21 @@ impl Clone for WeakFreeListHandle { } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct WeakFreeListHandle { index: u32, epoch: Epoch, _marker: PhantomData, } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct Slot { next: Option, epoch: Epoch, value: Option, } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct FreeList { slots: Vec>, free_list_head: Option, @@ -63,7 +68,7 @@ pub enum UpsertResult { } impl FreeList { - pub fn new() -> FreeList { + pub fn new() -> Self { FreeList { slots: Vec::new(), free_list_head: None, diff --git a/webrender/src/glyph_cache.rs b/webrender/src/glyph_cache.rs index bf0b45eac3..c28fc34e47 100644 --- a/webrender/src/glyph_cache.rs +++ b/webrender/src/glyph_cache.rs @@ -9,23 +9,34 @@ use resource_cache::ResourceClassCache; use std::sync::Arc; use texture_cache::TextureCacheHandle; -pub struct CachedGlyphInfo { +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] +pub struct GenericCachedGlyphInfo { pub texture_cache_handle: TextureCacheHandle, - pub glyph_bytes: Arc>, + pub glyph_bytes: D, pub size: DeviceUintSize, pub offset: DevicePoint, pub scale: f32, pub format: GlyphFormat, } +pub type CachedGlyphInfo = GenericCachedGlyphInfo>>; pub type GlyphKeyCache = ResourceClassCache>; +#[cfg(feature = "capture")] +pub type PlainCachedGlyphInfo = GenericCachedGlyphInfo; +#[cfg(feature = "capture")] +pub type PlainGlyphKeyCache = ResourceClassCache>; +#[cfg(feature = "capture")] +pub type PlainGlyphCacheRef<'a> = FastHashMap<&'a FontInstance, PlainGlyphKeyCache>; +#[cfg(feature = "capture")] +pub type PlainGlyphCacheOwn = FastHashMap; + pub struct GlyphCache { pub glyph_key_caches: FastHashMap, } impl GlyphCache { - pub fn new() -> GlyphCache { + pub fn new() -> Self { GlyphCache { glyph_key_caches: FastHashMap::default(), } diff --git a/webrender/src/glyph_rasterizer.rs b/webrender/src/glyph_rasterizer.rs index 2d34698cb9..65b2b1cfee 100644 --- a/webrender/src/glyph_rasterizer.rs +++ b/webrender/src/glyph_rasterizer.rs @@ -219,6 +219,7 @@ impl FontInstance { } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] #[allow(dead_code)] pub enum GlyphFormat { Alpha, @@ -596,6 +597,7 @@ impl FontContext { } #[derive(Clone, Hash, PartialEq, Eq, Debug, Ord, PartialOrd)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct GlyphRequest { pub key: GlyphKey, pub font: FontInstance, diff --git a/webrender/src/gpu_cache.rs b/webrender/src/gpu_cache.rs index 35af83c0d0..d59487d597 100644 --- a/webrender/src/gpu_cache.rs +++ b/webrender/src/gpu_cache.rs @@ -38,6 +38,7 @@ const FRAMES_BEFORE_EVICTION: usize = 10; const NEW_ROWS_PER_RESIZE: u32 = 512; #[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct Epoch(u32); impl Epoch { @@ -47,6 +48,7 @@ impl Epoch { } #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct CacheLocation { block_index: BlockIndex, epoch: Epoch, @@ -54,12 +56,13 @@ struct CacheLocation { /// A single texel in RGBAF32 texture - 16 bytes. #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct GpuBlockData { pub data: [f32; 4], } impl GpuBlockData { - pub fn empty() -> GpuBlockData { + pub fn empty() -> Self { GpuBlockData { data: [0.0; 4] } } } @@ -109,6 +112,7 @@ pub trait ToGpuBlocks { // A handle to a GPU resource. #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct GpuCacheHandle { location: Option, } @@ -123,20 +127,21 @@ impl GpuCacheHandle { // as part of the primitive instances, to allow the vertex // shader to fetch the specific data. #[derive(Copy, Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct GpuCacheAddress { pub u: u16, pub v: u16, } impl GpuCacheAddress { - fn new(u: usize, v: usize) -> GpuCacheAddress { + fn new(u: usize, v: usize) -> Self { GpuCacheAddress { u: u as u16, v: v as u16, } } - pub fn invalid() -> GpuCacheAddress { + pub fn invalid() -> Self { GpuCacheAddress { u: u16::MAX, v: u16::MAX, @@ -157,6 +162,7 @@ impl Add for GpuCacheAddress { // An entry in a free-list of blocks in the GPU cache. #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct Block { // The location in the cache of this block. address: GpuCacheAddress, @@ -182,9 +188,11 @@ impl Block { } #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct BlockIndex(usize); // A row in the cache texture. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct Row { // The fixed size of blocks that this row supports. // Each row becomes a slab allocator for a fixed block size. @@ -205,6 +213,7 @@ impl Row { // this frame. The list of updates is created by the render backend // during frame construction. It's passed to the render thread // where GL commands can be applied. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum GpuCacheUpdate { Copy { block_index: usize, @@ -226,6 +235,7 @@ pub struct GpuCacheUpdateList { // Holds the free lists of fixed size blocks. Mostly // just serves to work around the borrow checker. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct FreeBlockLists { free_list_1: Option, free_list_2: Option, @@ -239,7 +249,7 @@ struct FreeBlockLists { } impl FreeBlockLists { - fn new() -> FreeBlockLists { + fn new() -> Self { FreeBlockLists { free_list_1: None, free_list_2: None, @@ -276,6 +286,7 @@ impl FreeBlockLists { } // CPU-side representation of the GPU resource cache texture. +#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))] struct Texture { // Current texture height height: u32, @@ -490,6 +501,7 @@ impl<'a> Drop for GpuDataRequest<'a> { /// The main LRU cache interface. +#[cfg_attr(feature = "capture", derive(Serialize, Deserialize))] pub struct GpuCache { /// Current frame ID. frame_id: FrameId, diff --git a/webrender/src/gpu_types.rs b/webrender/src/gpu_types.rs index b7cd97f024..a736ffbf26 100644 --- a/webrender/src/gpu_types.rs +++ b/webrender/src/gpu_types.rs @@ -11,6 +11,7 @@ use render_task::RenderTaskAddress; #[repr(i32)] #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum BlurDirection { Horizontal = 0, Vertical, @@ -18,6 +19,7 @@ pub enum BlurDirection { #[derive(Debug)] #[repr(C)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct BlurInstance { pub task_address: RenderTaskAddress, pub src_task_address: RenderTaskAddress, @@ -28,6 +30,7 @@ pub struct BlurInstance { /// Could be an image or a rectangle, which defines the /// way `address` is treated. #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] #[repr(C)] pub struct ClipMaskInstance { pub render_task_address: RenderTaskAddress, @@ -39,6 +42,7 @@ pub struct ClipMaskInstance { // 32 bytes per instance should be enough for anyone! #[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct PrimitiveInstance { data: [i32; 8], } @@ -60,7 +64,7 @@ impl SimplePrimitiveInstance { clip_chain_rect_index: ClipChainRectIndex, scroll_id: ClipScrollNodeIndex, z_sort_index: i32, - ) -> SimplePrimitiveInstance { + ) -> Self { SimplePrimitiveInstance { specific_prim_address, task_address, @@ -108,7 +112,7 @@ impl CompositePrimitiveInstance { z: i32, data2: i32, data3: i32, - ) -> CompositePrimitiveInstance { + ) -> Self { CompositePrimitiveInstance { task_address, src_task_address, @@ -123,7 +127,7 @@ impl CompositePrimitiveInstance { } impl From for PrimitiveInstance { - fn from(instance: CompositePrimitiveInstance) -> PrimitiveInstance { + fn from(instance: CompositePrimitiveInstance) -> Self { PrimitiveInstance { data: [ instance.task_address.0 as i32, @@ -161,7 +165,7 @@ pub struct BrushInstance { } impl From for PrimitiveInstance { - fn from(instance: BrushInstance) -> PrimitiveInstance { + fn from(instance: BrushInstance) -> Self { PrimitiveInstance { data: [ instance.picture_address.0 as i32, @@ -189,10 +193,12 @@ pub enum BrushImageKind { } #[derive(Copy, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] #[repr(C)] pub struct ClipScrollNodeIndex(pub u32); #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] #[repr(C)] pub struct ClipScrollNodeData { pub transform: LayerToWorldTransform, @@ -201,7 +207,7 @@ pub struct ClipScrollNodeData { } impl ClipScrollNodeData { - pub fn invalid() -> ClipScrollNodeData { + pub fn invalid() -> Self { ClipScrollNodeData { transform: LayerToWorldTransform::identity(), transform_kind: 0.0, @@ -215,6 +221,7 @@ impl ClipScrollNodeData { pub struct ClipChainRectIndex(pub usize); #[derive(Copy, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] #[repr(C)] pub enum PictureType { Image = 1, diff --git a/webrender/src/internal_types.rs b/webrender/src/internal_types.rs index 3fe6498dac..e203f72cb3 100644 --- a/webrender/src/internal_types.rs +++ b/webrender/src/internal_types.rs @@ -5,8 +5,6 @@ use api::{ClipId, DevicePoint, DeviceUintRect, DocumentId, Epoch}; use api::{ExternalImageData, ExternalImageId}; use api::{ImageFormat, PipelineId}; -#[cfg(feature = "capture")] -use api::ImageDescriptor; use api::DebugCommand; use device::TextureFilter; use fxhash::FxHasher; @@ -17,6 +15,9 @@ use std::f32; use std::hash::BuildHasherDefault; use std::path::PathBuf; use std::sync::Arc; + +#[cfg(feature = "capture")] +use capture::{CaptureConfig, ExternalCaptureImage}; use tiling; pub type FastHashMap = HashMap>; @@ -32,9 +33,11 @@ pub type FastHashSet = HashSet>; // map from cache texture ID to native texture. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct CacheTextureId(pub usize); #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderPassIndex(pub usize); // Represents the source for a texture. @@ -44,6 +47,7 @@ pub struct RenderPassIndex(pub usize); // native texture ID. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum SourceTexture { Invalid, TextureCache(CacheTextureId), @@ -60,6 +64,7 @@ pub const ORTHO_NEAR_PLANE: f32 = -1000000.0; pub const ORTHO_FAR_PLANE: f32 = 1000000.0; #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderTargetInfo { pub has_depth: bool, } @@ -99,12 +104,13 @@ pub struct TextureUpdate { pub op: TextureUpdateOp, } +#[derive(Default)] pub struct TextureUpdateList { pub updates: Vec, } impl TextureUpdateList { - pub fn new() -> TextureUpdateList { + pub fn new() -> Self { TextureUpdateList { updates: Vec::new(), } @@ -142,20 +148,13 @@ impl RenderedDocument { } } -#[cfg(feature = "capture")] -pub struct ExternalCaptureImage { - pub short_path: String, - pub descriptor: ImageDescriptor, - pub external: ExternalImageData, -} - pub enum DebugOutput { FetchDocuments(String), FetchClipScrollTree(String), #[cfg(feature = "capture")] - SaveCapture(PathBuf, Vec), + SaveCapture(CaptureConfig, Vec), #[cfg(feature = "capture")] - LoadCapture, + LoadCapture(PathBuf), } pub enum ResultMsg { diff --git a/webrender/src/lib.rs b/webrender/src/lib.rs index 0ade76834a..c5decf1879 100644 --- a/webrender/src/lib.rs +++ b/webrender/src/lib.rs @@ -55,6 +55,8 @@ extern crate serde; mod batch; mod border; mod box_shadow; +#[cfg(feature = "capture")] +mod capture; mod clip; mod clip_scroll_node; mod clip_scroll_tree; @@ -160,10 +162,10 @@ extern crate base64; pub extern crate webrender_api; #[doc(hidden)] -pub use device::{build_shader_strings, ProgramCache, UploadMethod, VertexUsageHint}; +pub use device::{build_shader_strings, ProgramCache, ReadPixelsFormat, UploadMethod, VertexUsageHint}; pub use renderer::{CpuProfile, DebugFlags, GpuProfile, OutputImageHandler, RendererKind}; pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource}; -pub use renderer::{GraphicsApi, GraphicsApiInfo, ReadPixelsFormat, Renderer, RendererOptions}; +pub use renderer::{GraphicsApi, GraphicsApiInfo, Renderer, RendererOptions}; pub use renderer::{RendererStats, ThreadListener}; pub use renderer::MAX_VERTEX_TEXTURE_WIDTH; pub use webrender_api as api; diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 099fc6af41..7bcd090e74 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -43,6 +43,7 @@ pub enum PictureCompositeMode { /// Configure whether the content to be drawn by a picture /// in local space rasterization or the screen space. #[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum ContentOrigin { Local(LayerPoint), Screen(DeviceIntPoint), diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 60879c37e9..0567b03743 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -140,6 +140,7 @@ pub struct DeferredResolve { pub struct SpecificPrimitiveIndex(pub usize); #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct PrimitiveIndex(pub usize); #[derive(Debug, Copy, Clone, Eq, PartialEq)] diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index baa1dac1dc..1d5589747a 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -10,23 +10,25 @@ use api::{DocumentId, DocumentLayer, DocumentMsg}; use api::{IdNamespace, PipelineId, RenderNotifier}; use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods}; use api::channel::{PayloadSender, PayloadSenderHelperMethods}; +#[cfg(feature = "capture")] +use api::CapturedDocument; +#[cfg(feature = "capture")] +use capture::{CaptureConfig, ExternalCaptureImage}; #[cfg(feature = "debugger")] use debug_server; use frame::FrameContext; use frame_builder::{FrameBuilder, FrameBuilderConfig}; use gpu_cache::GpuCache; use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg}; -#[cfg(feature = "capture")] -use internal_types::ExternalCaptureImage; use profiler::{BackendProfileCounters, ResourceProfileCounters}; use rayon::ThreadPool; use record::ApiRecordingReceiver; use resource_cache::ResourceCache; #[cfg(feature = "capture")] -use resource_cache::PlainResources; +use resource_cache::{PlainCacheOwn, PlainResources}; use scene::Scene; #[cfg(feature = "serialize")] -use serde::{Serialize, Serializer}; +use serde::{Serialize, Deserialize}; #[cfg(feature = "debugger")] use serde_json; #[cfg(feature = "capture")] @@ -587,16 +589,31 @@ impl RenderBackend { ResultMsg::DebugOutput(DebugOutput::FetchClipScrollTree(json)) } #[cfg(feature = "capture")] - DebugCommand::SaveCapture(root) => { - let deferred = self.save_capture(&root); - ResultMsg::DebugOutput(DebugOutput::SaveCapture(root, deferred)) + DebugCommand::SaveCapture(root, bits) => { + let config = CaptureConfig::new(root, bits); + let deferred = self.save_capture(&config, &mut profile_counters); + ResultMsg::DebugOutput(DebugOutput::SaveCapture(config, deferred)) }, #[cfg(feature = "capture")] - DebugCommand::LoadCapture(root) => { + DebugCommand::LoadCapture(root, tx) => { NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed); frame_counter += 1; + let msg = ResultMsg::DebugOutput( + DebugOutput::LoadCapture(root.clone()) + ); + self.result_tx.send(msg).unwrap(); self.load_capture(&root, &mut profile_counters); - ResultMsg::DebugOutput(DebugOutput::LoadCapture) + + for (id, doc) in &self.documents { + let captured = CapturedDocument { + document_id: *id, + root_pipeline_id: doc.scene.root_pipeline_id, + }; + tx.send(captured).unwrap(); + } + // Note: we can't pass `LoadCapture` here since it needs to arrive + // before the `PublishDocument` messages sent by `load_capture`. + continue }, DebugCommand::EnableDualSourceBlending(enable) => { // Set in the config used for any future documents @@ -671,6 +688,9 @@ impl RenderBackend { &mut profile_counters.resources, ); + info!("generated frame for document {:?} with {} passes", + document_id, rendered_document.frame.passes.len()); + // Publish the frame let pending_update = self.resource_cache.pending_updates(); let msg = ResultMsg::PublishDocument( @@ -812,30 +832,41 @@ impl ToDebugString for SpecificDisplayItem { } } - #[cfg(feature = "capture")] impl RenderBackend { // Note: the mutable `self` is only needed here for resolving blob images - fn save_capture(&mut self, root: &PathBuf) -> Vec { - use ron::ser::{to_string_pretty, PrettyConfig}; - use std::fs; - use std::io::Write; + fn save_capture( + &mut self, + config: &CaptureConfig, + profile_counters: &mut BackendProfileCounters, + ) -> Vec { + use api::CaptureBits; - info!("capture: saving {}", root.to_string_lossy()); - let ron_config = PrettyConfig::default(); - let (resources, deferred) = self.resource_cache.save_capture(root); + info!("capture: saving {:?}", config.root); + let (resources, deferred) = self.resource_cache.save_capture(&config.root); - for (&id, doc) in &self.documents { + for (&id, doc) in &mut self.documents { info!("\tdocument {:?}", id); - let ron = to_string_pretty(&doc.scene, ron_config.clone()).unwrap(); - let file_name = format!("scene-{}-{}.ron", (id.0).0, id.1); - let ron_path = root.clone().join(file_name); - let mut file = fs::File::create(ron_path).unwrap(); - write!(file, "{}\n", ron).unwrap(); + if config.bits.contains(CaptureBits::SCENE) { + let file_name = format!("scene-{}-{}", (id.0).0, id.1); + config.serialize(&doc.scene, file_name); + } + if config.bits.contains(CaptureBits::FRAME) { + let rendered_document = doc.render( + &mut self.resource_cache, + &mut self.gpu_cache, + &mut profile_counters.resources, + ); + //TODO: write down full `RenderedDocument`? + // it has `pipeline_epoch_map` and `layers_bouncing_back`, + // which may capture necessary details for some cases. + let file_name = format!("frame-{}-{}", (id.0).0, id.1); + config.serialize(&rendered_document.frame, file_name); + } } info!("\tbackend"); - let serial = PlainRenderBackend { + let backend = PlainRenderBackend { default_device_pixel_ratio: self.default_device_pixel_ratio, enable_render_on_scroll: self.enable_render_on_scroll, frame_config: self.frame_config.clone(), @@ -846,10 +877,15 @@ impl RenderBackend { resources, }; - let ron = to_string_pretty(&serial, ron_config).unwrap(); - let ron_path = root.clone().join("backend.ron"); - let mut file = fs::File::create(ron_path).unwrap(); - write!(file, "{}\n", ron).unwrap(); + config.serialize(&backend, "backend"); + + if config.bits.contains(CaptureBits::FRAME) { + info!("\tresource cache"); + let caches = self.resource_cache.save_caches(&config.root); + config.serialize(&caches, "resource_cache"); + info!("\tgpu cache"); + config.serialize(&self.gpu_cache, "gpu_cache"); + } deferred } @@ -859,26 +895,24 @@ impl RenderBackend { root: &PathBuf, profile_counters: &mut BackendProfileCounters, ) { - use ron::de; - use std::fs::File; - use std::io::Read; + use tiling::Frame; - let mut string = String::new(); - info!("capture: loading {}", root.to_string_lossy()); - - File::open(root.join("backend.ron")) - .unwrap() - .read_to_string(&mut string) - .unwrap(); - let backend: PlainRenderBackend = de::from_str(&string) - .unwrap(); + info!("capture: loading {:?}", root); + let backend = CaptureConfig::deserialize::(root, "backend") + .expect("Unable to open backend.ron"); + let caches_maybe = CaptureConfig::deserialize::(root, "resource_cache"); // Note: it would be great to have RenderBackend to be split // rather explicitly on what's used before and after scene building // so that, for example, we never miss anything in the code below: - self.resource_cache.load_capture(backend.resources, root); - self.gpu_cache = GpuCache::new(); + self.resource_cache.load_capture(backend.resources, caches_maybe,root); + + self.gpu_cache = match CaptureConfig::deserialize::(root, "gpu_cache") { + Some(gpu_cache) => gpu_cache, + None => GpuCache::new(), + }; + self.documents.clear(); self.default_device_pixel_ratio = backend.default_device_pixel_ratio; self.frame_config = backend.frame_config; @@ -886,14 +920,9 @@ impl RenderBackend { for (id, view) in backend.documents { info!("\tdocument {:?}", id); - string.clear(); - let file_name = format!("scene-{}-{}.ron", (id.0).0, id.1); - File::open(root.join(file_name)) - .expect(&format!("Unable to open scene {:?}", id)) - .read_to_string(&mut string) - .unwrap(); - let scene: Scene = de::from_str(&string) - .unwrap(); + let scene_name = format!("scene-{}-{}", (id.0).0, id.1); + let scene = CaptureConfig::deserialize::(root, &scene_name) + .expect(&format!("Unable to open {}.ron", scene_name)); let mut doc = Document { scene, @@ -904,25 +933,32 @@ impl RenderBackend { render_on_scroll: None, }; - doc.build_scene(&mut self.resource_cache); - let render_doc = doc.render( - &mut self.resource_cache, - &mut self.gpu_cache, - &mut profile_counters.resources, - ); + let frame_name = format!("frame-{}-{}", (id.0).0, id.1); + let render_doc = match CaptureConfig::deserialize::(root, frame_name) { + Some(frame) => { + info!("\tloaded a built frame with {} passes", frame.passes.len()); + doc.frame_ctx.make_rendered_document(frame) + } + None => { + doc.build_scene(&mut self.resource_cache); + doc.render( + &mut self.resource_cache, + &mut self.gpu_cache, + &mut profile_counters.resources, + ) + } + }; - let pending_update = self.resource_cache.pending_updates(); let msg = ResultMsg::PublishDocument( id, render_doc, - pending_update, - profile_counters.clone() + self.resource_cache.pending_updates(), + profile_counters.clone(), ); self.result_tx.send(msg).unwrap(); profile_counters.reset(); self.notifier.new_document_ready(id, false, true); - self.documents.insert(id, doc); } } diff --git a/webrender/src/render_task.rs b/webrender/src/render_task.rs index 32a4322e6a..b5481df1da 100644 --- a/webrender/src/render_task.rs +++ b/webrender/src/render_task.rs @@ -27,13 +27,16 @@ pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0; pub const MIN_DOWNSCALING_RT_SIZE: i32 = 128; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderTaskId(pub u32); // TODO(gw): Make private when using GPU cache! #[derive(Debug, Copy, Clone)] #[repr(C)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderTaskAddress(pub u32); #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderTaskTree { pub tasks: Vec, pub task_data: Vec, @@ -209,6 +212,7 @@ impl ops::IndexMut for RenderTaskTree { } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum RenderTaskLocation { Fixed, Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize), @@ -216,6 +220,7 @@ pub enum RenderTaskLocation { } #[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct ClipWorkItem { pub scroll_node_data_index: ClipScrollNodeIndex, pub clip_sources: ClipSourcesWeakHandle, @@ -223,6 +228,7 @@ pub struct ClipWorkItem { } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct CacheMaskTask { actual_rect: DeviceIntRect, pub clips: Vec, @@ -230,6 +236,7 @@ pub struct CacheMaskTask { } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct PictureTask { pub prim_index: PrimitiveIndex, pub target_kind: RenderTargetKind, @@ -239,6 +246,7 @@ pub struct PictureTask { } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct BlurTask { pub blur_std_deviation: f32, pub target_kind: RenderTargetKind, @@ -256,11 +264,13 @@ impl BlurTask { } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderTaskData { pub data: [f32; FLOATS_PER_RENDER_TASK_INFO], } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum RenderTaskKind { Picture(PictureTask), CacheMask(CacheMaskTask), @@ -271,6 +281,7 @@ pub enum RenderTaskKind { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum ClearMode { // Applicable to color and alpha targets. Zero, @@ -281,6 +292,7 @@ pub enum ClearMode { } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderTask { pub location: RenderTaskLocation, pub children: Vec, diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index 834c7e770b..ae4a1ad6d0 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -20,17 +20,18 @@ use api::DebugCommand; use api::channel::MsgSender; use batch::{BatchKey, BatchKind, BatchTextures, BrushBatchKind}; use batch::{BrushImageSourceKind, TransformBatchKind}; +#[cfg(feature = "capture")] +use capture::{CaptureConfig, ExternalCaptureImage}; use debug_colors; use debug_render::DebugRenderer; #[cfg(feature = "debugger")] use debug_server::{self, DebugServer}; use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, VertexDescriptor, PBO}; -use device::{get_gl_format_bgra, ExternalTexture, FBOId, TextureSlot, VertexAttribute, - VertexAttributeKind}; +use device::{ExternalTexture, FBOId, TextureSlot, VertexAttribute, VertexAttributeKind}; use device::{FileWatcherHandler, ShaderError, TextureFilter, TextureTarget, VertexUsageHint, VAO, VBO, CustomVAO}; -use device::ProgramCache; +use device::{ProgramCache, ReadPixelsFormat}; use euclid::{rect, Transform3D}; use frame_builder::FrameBuilderConfig; use gleam::gl; @@ -41,6 +42,7 @@ use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE}; use internal_types::{CacheTextureId, FastHashMap, RenderedDocument, ResultMsg, TextureUpdateOp}; use internal_types::{DebugOutput, RenderPassIndex, RenderTargetInfo, TextureUpdateList, TextureUpdateSource}; use picture::ContentOrigin; +use prim_store::DeferredResolve; use profiler::{BackendProfileCounters, Profiler}; use profiler::{GpuProfileTag, RendererProfileCounters, RendererProfileTimers}; use query::{GpuProfiler, GpuTimer}; @@ -480,6 +482,7 @@ pub struct GraphicsApiInfo { } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum ImageBufferKind { Texture2D = 0, TextureRect = 1, @@ -698,7 +701,7 @@ impl SourceTextureResolver { SourceTexture::External(external_image) => { let texture = self.external_images .get(&(external_image.id, external_image.channel_index)) - .expect("BUG: External image should be resolved by now!"); + .expect(&format!("BUG: External image should be resolved by now: {:?}", external_image)); device.bind_external_texture(sampler, texture); } SourceTexture::TextureCache(index) => { @@ -757,6 +760,7 @@ impl SourceTextureResolver { } #[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] #[allow(dead_code)] // SubpixelVariableTextColor is not used at the moment. pub enum BlendMode { None, @@ -775,7 +779,7 @@ struct CacheRow { } impl CacheRow { - fn new() -> CacheRow { + fn new() -> Self { CacheRow { is_dirty: false } } } @@ -1256,7 +1260,7 @@ impl BrushShader { device: &mut Device, features: &[&'static str], precache: bool, - ) -> Result { + ) -> Result { let opaque = try!{ LazilyCompiledShader::new(ShaderKind::Brush, name, @@ -1320,7 +1324,7 @@ impl PrimitiveShader { device: &mut Device, features: &[&'static str], precache: bool, - ) -> Result { + ) -> Result { let simple = try!{ LazilyCompiledShader::new(ShaderKind::Primitive, name, @@ -1379,7 +1383,7 @@ impl TextShader { device: &mut Device, features: &[&'static str], precache: bool, - ) -> Result { + ) -> Result { let simple = try!{ LazilyCompiledShader::new(ShaderKind::Text, name, @@ -1525,6 +1529,16 @@ fn create_clip_shader(name: &'static str, device: &mut Device) -> Result Option { + Some(match ext_type { + ExternalImageType::Texture2DHandle => TextureTarget::Default, + ExternalImageType::Texture2DArrayHandle => TextureTarget::Array, + ExternalImageType::TextureRectHandle => TextureTarget::Rect, + ExternalImageType::TextureExternalHandle => TextureTarget::External, + ExternalImageType::ExternalBuffer => return None, + }) +} + struct FileWatcher { notifier: Box, result_tx: Sender, @@ -1537,12 +1551,6 @@ impl FileWatcherHandler for FileWatcher { } } -#[derive(Clone, Debug, PartialEq)] -pub enum ReadPixelsFormat { - Rgba8, - Bgra8, -} - struct FrameOutput { last_access: FrameId, fbo_id: FBOId, @@ -1661,6 +1669,11 @@ pub struct Renderer { /// via get_frame_profiles(). cpu_profiles: VecDeque, gpu_profiles: VecDeque, + + #[cfg(feature = "capture")] + capture_read_fbo: FBOId, + #[cfg(feature = "capture")] + owned_external_images: FastHashMap<(ExternalImageId, u8), ExternalTexture>, } #[derive(Debug)] @@ -2218,6 +2231,9 @@ impl Renderer { }) }; + #[cfg(feature = "capture")] + let capture_read_fbo = device.create_fbo_for_external_texture(0); + let gpu_profile = GpuProfiler::new(Rc::clone(device.rc_gl())); let mut renderer = Renderer { @@ -2282,6 +2298,10 @@ impl Renderer { texture_cache_upload_pbo, texture_resolver, renderer_errors: Vec::new(), + #[cfg(feature = "capture")] + capture_read_fbo, + #[cfg(feature = "capture")] + owned_external_images: FastHashMap::default(), }; renderer.set_debug_flags(options.debug_flags); @@ -2384,47 +2404,14 @@ impl Renderer { self.debug_server.send(string); } #[cfg(feature = "capture")] - DebugOutput::SaveCapture(path, deferred)=> { - use std::fs::File; - use std::io::Write; - use api::ExternalImageData; - - if deferred.is_empty() { - continue - } - - info!("saving external images"); - let handler = self.external_image_handler - .as_mut() - .expect("Unable to lock the external image handler!"); - for def in deferred { - let ExternalImageData { id, channel_index, .. } = def.external; - let data = match handler.lock(id, channel_index).source { - ExternalImageSource::RawData(data) => data.to_vec(), - ExternalImageSource::NativeTexture(_gl_id) => { - //TODO: make a read FBO with this GL texture - //self.device.read_pixels(&def.descriptor); - unimplemented!() - } - ExternalImageSource::Invalid => { - // Create a dummy buffer... - let stride = def.descriptor.compute_stride(); - let total_size = def.descriptor.height * stride; - vec![0xFF; total_size as usize] - } - }; - handler.unlock(id, channel_index); - - let full_path = format!("{}/{}", - path.to_string_lossy(), def.short_path); - File::create(full_path) - .expect(&format!("Unable to create {}", def.short_path)) - .write_all(&data) - .unwrap(); - } + DebugOutput::SaveCapture(config, deferred) => { + self.save_capture(config, deferred); } #[cfg(feature = "capture")] - DebugOutput::LoadCapture => {} + DebugOutput::LoadCapture(root) => { + self.active_documents.clear(); + self.load_capture(root); + } }, ResultMsg::DebugCommand(command) => { self.handle_debug_command(command); @@ -2670,9 +2657,9 @@ impl Renderer { let json = self.get_screenshot_for_debugger(); self.debug_server.send(json); } - DebugCommand::SaveCapture(_) | - DebugCommand::LoadCapture(_) => { - panic!("Capture commands are not welcome here!") + DebugCommand::SaveCapture(..) | + DebugCommand::LoadCapture(..) => { + panic!("Capture commands are not welcome here! Did you build with 'capture' feature?") } DebugCommand::EnableDualSourceBlending(_) => { panic!("Should be handled by render backend"); @@ -2814,6 +2801,11 @@ impl Renderer { self.prepare_tile_frame(&mut doc_with_id.1.frame); } + #[cfg(feature = "capture")] + self.texture_resolver.external_images.extend( + self.owned_external_images.iter().map(|(key, value)| (*key, value.clone())) + ); + for &mut (_, RenderedDocument { ref mut frame, .. }) in &mut active_documents { self.prepare_gpu_cache(frame); @@ -2893,7 +2885,7 @@ impl Renderer { fn prepare_gpu_cache(&mut self, frame: &Frame) { let _gm = self.gpu_profile.start_marker("gpu cache update"); - let deferred_update_list = self.update_deferred_resolves(frame); + let deferred_update_list = self.update_deferred_resolves(&frame.deferred_resolves); self.pending_gpu_cache_updates.extend(deferred_update_list); // For an artificial stress test of GPU cache resizing, @@ -3736,10 +3728,9 @@ impl Renderer { .as_mut() .expect("Found output image, but no handler set!"); if let Some((texture_id, output_size)) = handler.lock(output.pipeline_id) { - let device = &mut self.device; let fbo_id = match self.output_targets.entry(texture_id) { Entry::Vacant(entry) => { - let fbo_id = device.create_fbo_for_external_texture(texture_id); + let fbo_id = self.device.create_fbo_for_external_texture(texture_id); entry.insert(FrameOutput { fbo_id, last_access: frame_id, @@ -3754,9 +3745,9 @@ impl Renderer { }; let (src_rect, _) = render_tasks[output.task_id].get_target_rect(); let dest_rect = DeviceIntRect::new(DeviceIntPoint::zero(), output_size); - device.bind_read_target(render_target); - device.bind_external_draw_target(fbo_id); - device.blit_render_target(src_rect, dest_rect); + self.device.bind_read_target(render_target); + self.device.bind_external_draw_target(fbo_id); + self.device.blit_render_target(src_rect, dest_rect); handler.unlock(output.pipeline_id); } } @@ -3995,12 +3986,12 @@ impl Renderer { } } - fn update_deferred_resolves(&mut self, frame: &Frame) -> Option { + fn update_deferred_resolves(&mut self, deferred_resolves: &[DeferredResolve]) -> Option { // The first thing we do is run through any pending deferred // resolves, and use a callback to get the UV rect for this // custom item. Then we patch the resource_rects structure // here before it's uploaded to the GPU. - if frame.deferred_resolves.is_empty() { + if deferred_resolves.is_empty() { return None; } @@ -4014,25 +4005,15 @@ impl Renderer { updates: Vec::new(), }; - for deferred_resolve in &frame.deferred_resolves { + for deferred_resolve in deferred_resolves { self.gpu_profile.place_marker("deferred resolve"); let props = &deferred_resolve.image_properties; let ext_image = props .external_image .expect("BUG: Deferred resolves must be external images!"); let image = handler.lock(ext_image.id, ext_image.channel_index); - let texture_target = match ext_image.image_type { - ExternalImageType::Texture2DHandle => TextureTarget::Default, - ExternalImageType::Texture2DArrayHandle => TextureTarget::Array, - ExternalImageType::TextureRectHandle => TextureTarget::Rect, - ExternalImageType::TextureExternalHandle => TextureTarget::External, - ExternalImageType::ExternalBuffer => { - panic!( - "{:?} is not a suitable image type in update_deferred_resolves().", - ext_image.image_type - ); - } - }; + let texture_target = get_external_image_target(ext_image.image_type) + .expect(&format!("{:?} is not a suitable image type in update_deferred_resolves()", ext_image.image_type)); // In order to produce the handle, the external image handler may call into // the GL context and change some states. @@ -4511,20 +4492,25 @@ impl Renderer { ); } - pub fn read_pixels_rgba8(&self, rect: DeviceUintRect) -> Vec { - let mut pixels = vec![0u8; (4 * rect.size.width * rect.size.height) as usize]; - self.read_pixels_into(rect, ReadPixelsFormat::Rgba8, &mut pixels); + /// Pass-through to `Device::read_pixels_into`, used by Gecko's WR bindings. + pub fn read_pixels_into(&mut self, rect: DeviceUintRect, format: ReadPixelsFormat, output: &mut [u8]) { + self.device.read_pixels_into(rect, format, output); + } + + pub fn read_pixels_rgba8(&mut self, rect: DeviceUintRect) -> Vec { + let mut pixels = vec![0; (rect.size.width * rect.size.height * 4) as usize]; + self.device.read_pixels_into(rect, ReadPixelsFormat::Rgba8, &mut pixels); pixels } pub fn read_gpu_cache(&mut self) -> (DeviceUintSize, Vec) { let size = self.gpu_cache_texture.texture.get_dimensions(); - let mut texels = vec![0u8; 4 * (size.width * size.height) as usize]; + let mut texels = vec![0; (size.width * size.height * 16) as usize]; self.device.begin_frame(); self.device.bind_read_target(Some((&self.gpu_cache_texture.texture, 0))); - self.read_pixels_into( + self.device.read_pixels_into( DeviceUintRect::new(DeviceUintPoint::zero(), size), - ReadPixelsFormat::Rgba8, + ReadPixelsFormat::Rgba32F, &mut texels, ); self.device.bind_read_target(None); @@ -4532,32 +4518,6 @@ impl Renderer { (size, texels) } - pub fn read_pixels_into( - &self, - rect: DeviceUintRect, - format: ReadPixelsFormat, - output: &mut [u8], - ) { - let (gl_format, gl_type, size) = match format { - ReadPixelsFormat::Rgba8 => (gl::RGBA, gl::UNSIGNED_BYTE, 4), - ReadPixelsFormat::Bgra8 => (get_gl_format_bgra(self.device.gl()), gl::UNSIGNED_BYTE, 4), - }; - assert_eq!( - output.len(), - (size * rect.size.width * rect.size.height) as usize - ); - self.device.gl().flush(); - self.device.gl().read_pixels_into_buffer( - rect.origin.x as gl::GLint, - rect.origin.y as gl::GLint, - rect.size.width as gl::GLsizei, - rect.size.height as gl::GLsizei, - gl_format, - gl_type, - output, - ); - } - // De-initialize the Renderer safely, assuming the GL is still alive and active. pub fn deinit(mut self) { //Note: this is a fake frame, only needed because texture deletion is require to happen inside a frame @@ -4612,6 +4572,12 @@ impl Renderer { self.ps_hw_composite.deinit(&mut self.device); self.ps_split_composite.deinit(&mut self.device); self.ps_composite.deinit(&mut self.device); + #[cfg(feature = "capture")] + self.device.delete_fbo(self.capture_read_fbo); + #[cfg(feature = "capture")] + for (_, ext) in self.owned_external_images { + self.device.delete_external_texture(ext); + } self.device.end_frame(); } } @@ -4698,7 +4664,7 @@ pub struct RendererOptions { } impl Default for RendererOptions { - fn default() -> RendererOptions { + fn default() -> Self { RendererOptions { device_pixel_ratio: 1.0, resource_override_path: None, @@ -4736,7 +4702,7 @@ pub struct DebugServer; #[cfg(not(feature = "debugger"))] impl DebugServer { - pub fn new(_: MsgSender) -> DebugServer { + pub fn new(_: MsgSender) -> Self { DebugServer } @@ -4754,7 +4720,7 @@ pub struct RendererStats { } impl RendererStats { - pub fn empty() -> RendererStats { + pub fn empty() -> Self { RendererStats { total_draw_calls: 0, alpha_target_count: 0, @@ -4762,3 +4728,249 @@ impl RendererStats { } } } + + +#[cfg(feature = "capture")] +#[derive(Deserialize, Serialize)] +struct PlainTexture { + data: String, + size: (u32, u32, i32), + format: ImageFormat, + filter: TextureFilter, + render_target: Option, +} + +#[cfg(feature = "capture")] +#[derive(Deserialize, Serialize)] +struct PlainRenderer { + gpu_cache: PlainTexture, + textures: Vec, + external_images: Vec +} + +#[cfg(feature = "capture")] +struct DummyExternalImageHandler { + data: FastHashMap<(ExternalImageId, u8), Vec>, +} + +#[cfg(feature = "capture")] +impl ExternalImageHandler for DummyExternalImageHandler { + fn lock(&mut self, key: ExternalImageId, channel_index: u8) -> ExternalImage { + let slice = &self.data[&(key, channel_index)]; + ExternalImage { + u0: 0.0, + v0: 0.0, + u1: 1.0, + v1: 1.0, + source: ExternalImageSource::RawData(slice), + } + } + fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {} +} + +#[cfg(feature = "capture")] +impl OutputImageHandler for () { + fn lock(&mut self, _: PipelineId) -> Option<(u32, DeviceIntSize)> { + None + } + fn unlock(&mut self, _: PipelineId) { + unreachable!() + } +} + +#[cfg(feature = "capture")] +impl Renderer { + fn save_texture( + texture: &Texture, name: &str, root: &PathBuf, device: &mut Device + ) -> PlainTexture { + use std::fs; + use std::io::Write; + + let short_path = format!("textures/{}.raw", name); + + let (read_format, bytes_per_pixel) = match texture.get_format() { + ImageFormat::R8 => (ReadPixelsFormat::R8, 1u32), + ImageFormat::BGRA8 => (ReadPixelsFormat::Bgra8, 4u32), + ImageFormat::RGBAF32 => (ReadPixelsFormat::Rgba32F, 16u32), + _ => unimplemented!() + }; + let rect = DeviceUintRect::new( + DeviceUintPoint::zero(), + texture.get_dimensions(), + ); + + let mut file = fs::File::create(root.join(&short_path)) + .expect(&format!("Unable to create {}", short_path)); + let bytes_per_layer = (rect.size.width * rect.size.height * bytes_per_pixel) as usize; + let mut data = vec![0; bytes_per_layer]; + + for layer_id in 0 .. texture.get_layer_count() { + device.attach_read_texture(texture, layer_id); + device.read_pixels_into(rect, read_format, &mut data); + file.write_all(&data) + .unwrap(); + } + + PlainTexture { + data: short_path, + size: (rect.size.width, rect.size.height, texture.get_layer_count()), + format: texture.get_format(), + filter: texture.get_filter(), + render_target: texture.get_render_target(), + } + } + + fn load_texture(texture: &mut Texture, plain: &PlainTexture, root: &PathBuf, device: &mut Device) -> Vec { + use std::fs::File; + use std::io::Read; + + let mut texels = Vec::new(); + File::open(root.join(&plain.data)) + .unwrap() + .read_to_end(&mut texels) + .unwrap(); + + device.init_texture( + texture, plain.size.0, plain.size.1, + plain.format, plain.filter, plain.render_target, + plain.size.2, Some(texels.as_slice()), + ); + + texels + } + + fn save_capture(&mut self, config: CaptureConfig, deferred_images: Vec) { + use std::fs; + use std::io::Write; + use api::{CaptureBits, ExternalImageData}; + + self.device.begin_frame(); + self.device.bind_read_target_impl(self.capture_read_fbo); + + if !deferred_images.is_empty() { + info!("saving external images"); + let handler = self.external_image_handler + .as_mut() + .expect("Unable to lock the external image handler!"); + for def in &deferred_images { + let ExternalImageData { id, channel_index, image_type } = def.external; + let data = match handler.lock(id, channel_index).source { + ExternalImageSource::RawData(data) => data.to_vec(), + ExternalImageSource::NativeTexture(gl_id) => { + let target = get_external_image_target(image_type).unwrap(); + self.device.attach_read_texture_external(gl_id, target, 0); + self.device.read_pixels(&def.descriptor) + } + ExternalImageSource::Invalid => { + // Create a dummy buffer... + let stride = def.descriptor.compute_stride(); + let total_size = def.descriptor.height * stride; + vec![0xFF; total_size as usize] + } + }; + handler.unlock(id, channel_index); + + fs::File::create(config.root.join(&def.short_path)) + .expect(&format!("Unable to create {}", def.short_path)) + .write_all(&data) + .unwrap(); + } + } + + if config.bits.contains(CaptureBits::FRAME) { + let path_textures = config.root.join("textures"); + if !path_textures.is_dir() { + fs::create_dir(&path_textures).unwrap(); + } + + info!("saving GPU cache"); + let mut plain_self = PlainRenderer { + gpu_cache: Self::save_texture( + &self.gpu_cache_texture.texture, + "gpu", &config.root, &mut self.device, + ), + textures: Vec::new(), + external_images: deferred_images, + }; + + info!("saving cached textures"); + for texture in &self.texture_resolver.cache_texture_map { + let file_name = format!("cache-{}", plain_self.textures.len() + 1); + info!("\t{}", file_name); + let plain = Self::save_texture(texture, &file_name, &config.root, &mut self.device); + plain_self.textures.push(plain); + } + + config.serialize(&plain_self, "renderer"); + } + + self.device.bind_read_target(None); + self.device.end_frame(); + info!("done."); + } + + fn load_capture(&mut self, root: PathBuf) { + let renderer = match CaptureConfig::deserialize::(&root, "renderer") { + Some(r) => r, + None => return, + }; + + self.device.begin_frame(); + info!("loading cached textures"); + + for texture in self.texture_resolver.cache_texture_map.drain(..) { + self.device.delete_texture(texture); + } + for texture in renderer.textures { + info!("\t{}", texture.data); + let mut t = self.device.create_texture(TextureTarget::Array); + Self::load_texture(&mut t, &texture, &root, &mut self.device); + self.texture_resolver.cache_texture_map.push(t); + } + + info!("loading gpu cache"); + Self::load_texture( + &mut self.gpu_cache_texture.texture, + &renderer.gpu_cache, + &root, + &mut self.device, + ); + match self.gpu_cache_texture.bus { + CacheBus::PixelBuffer { ref mut rows, ref mut cpu_blocks, .. } => { + rows.clear(); + cpu_blocks.clear(); + } + CacheBus::Scatter { .. } => {} + } + + info!("loading external images"); + assert!(self.texture_resolver.external_images.is_empty()); + let mut image_handler = DummyExternalImageHandler { + data: FastHashMap::default(), + }; + + for ExternalCaptureImage { short_path, external, descriptor } in renderer.external_images { + let target = get_external_image_target(external.image_type).unwrap(); + //TODO: provide a way to query both the layer count and the filter from external images + let (layer_count, filter) = (1, TextureFilter::Linear); + let plain = PlainTexture { + data: short_path, + size: (descriptor.width, descriptor.height, layer_count), + format: descriptor.format, + filter, + render_target: None, + }; + + let mut t = self.device.create_texture(target); + let data = Self::load_texture(&mut t, &plain, &root, &mut self.device); + let key = (external.id, external.channel_index); + self.owned_external_images.insert(key, t.into_external()); + image_handler.data.insert(key, data); + } + + self.device.end_frame(); + self.external_image_handler = Some(Box::new(image_handler) as Box<_>); + self.output_image_handler = Some(Box::new(()) as Box<_>); + info!("done."); + } +} diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index a9cd0ab451..fcd957c04b 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -14,14 +14,16 @@ use api::{TileOffset, TileSize}; #[cfg(feature = "capture")] use api::{NativeFontHandle}; use app_units::Au; +#[cfg(feature = "capture")] +use capture::{ExternalCaptureImage}; use device::TextureFilter; use frame::FrameId; use glyph_cache::GlyphCache; +#[cfg(feature = "capture")] +use glyph_cache::{CachedGlyphInfo, PlainGlyphCacheOwn, PlainGlyphCacheRef, PlainCachedGlyphInfo}; use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest}; use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle}; use internal_types::{FastHashMap, FastHashSet, SourceTexture, TextureUpdateList}; -#[cfg(feature = "capture")] -use internal_types::ExternalCaptureImage; use profiler::{ResourceProfileCounters, TextureCacheProfileCounters}; use rayon::ThreadPool; use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId, RenderTaskTree}; @@ -38,6 +40,7 @@ use texture_cache::{TextureCache, TextureCacheHandle}; const DEFAULT_TILE_SIZE: TileSize = 512; +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct GlyphFetchResult { pub index_in_text_run: i32, pub uv_rect_address: GpuCacheAddress, @@ -117,19 +120,22 @@ impl ImageTemplates { } } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct CachedImageInfo { texture_cache_handle: TextureCacheHandle, epoch: Epoch, } #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Clone, Deserialize, Serialize))] pub enum ResourceClassCacheError { OverLimitSize, } pub type ResourceCacheResult = Result; -pub struct ResourceClassCache { +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] +pub struct ResourceClassCache { resources: FastHashMap>, } @@ -182,6 +188,7 @@ where #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct ImageRequest { key: ImageKey, rendering: ImageRendering, @@ -217,6 +224,8 @@ impl BlobImageResources for Resources { } } +pub type GlyphDimensionsCache = FastHashMap>; + pub struct ResourceCache { cached_glyphs: GlyphCache, cached_images: ImageCache, @@ -229,7 +238,7 @@ pub struct ResourceCache { texture_cache: TextureCache, // TODO(gw): We should expire (parts of) this cache semi-regularly! - cached_glyph_dimensions: FastHashMap>, + cached_glyph_dimensions: GlyphDimensionsCache, glyph_rasterizer: GlyphRasterizer, // The set of images that aren't present or valid in the texture cache, @@ -992,6 +1001,26 @@ pub struct PlainResources { image_templates: FastHashMap, } +#[cfg(feature = "capture")] +#[derive(Serialize)] +pub struct PlainCacheRef<'a> { + current_frame_id: FrameId, + glyphs: PlainGlyphCacheRef<'a>, + glyph_dimensions: &'a GlyphDimensionsCache, + images: &'a ImageCache, + textures: &'a TextureCache, +} + +#[cfg(feature = "capture")] +#[derive(Deserialize)] +pub struct PlainCacheOwn { + current_frame_id: FrameId, + glyphs: PlainGlyphCacheOwn, + glyph_dimensions: GlyphDimensionsCache, + images: ImageCache, + textures: TextureCache, +} + #[cfg(feature = "capture")] impl ResourceCache { pub fn save_capture( @@ -1003,38 +1032,36 @@ impl ResourceCache { info!("saving resource cache"); let res = &self.resources; if !root.is_dir() { - fs::create_dir_all(&root).unwrap() + fs::create_dir_all(root).unwrap() } - let path_fonts = root.clone().join("fonts"); + let path_fonts = root.join("fonts"); if !path_fonts.is_dir() { fs::create_dir(&path_fonts).unwrap(); } - let path_images = root.clone().join("images"); + let path_images = root.join("images"); if !path_images.is_dir() { fs::create_dir(&path_images).unwrap(); } - let path_blobs = root.clone().join("blobs"); + let path_blobs = root.join("blobs"); if !path_blobs.is_dir() { fs::create_dir(&path_blobs).unwrap(); } info!("\tfont templates"); let mut font_paths = FastHashMap::default(); - let mut num_fonts = 0; for template in res.font_templates.values() { let data: &[u8] = match *template { FontTemplate::Raw(ref arc, _) => arc, FontTemplate::Native(_) => continue, }; + let font_id = res.font_templates.len() + 1; let entry = match font_paths.entry(data.as_ptr()) { Entry::Occupied(_) => continue, Entry::Vacant(e) => e, }; - num_fonts += 1; - let file_name = format!("{}.raw", num_fonts); + let file_name = format!("{}.raw", font_id); let short_path = format!("fonts/{}", file_name); - let full_path = path_fonts.clone().join(&file_name); - fs::File::create(full_path) + fs::File::create(path_fonts.join(file_name)) .expect(&format!("Unable to create {}", short_path)) .write_all(data) .unwrap(); @@ -1044,23 +1071,22 @@ impl ResourceCache { info!("\timage templates"); let mut image_paths = FastHashMap::default(); let mut other_paths = FastHashMap::default(); - let mut num_images = 0; let mut external_images = Vec::new(); for (&key, template) in res.image_templates.images.iter() { let desc = &template.descriptor; match template.data { ImageData::Raw(ref arc) => { + let image_id = image_paths.len() + 1; let entry = match image_paths.entry(arc.as_ptr()) { Entry::Occupied(_) => continue, Entry::Vacant(e) => e, }; - //TODO: option to save as PNG - num_images += 1; - let file_name = format!("{}.raw", num_images); + //TODO: option to save as PNG: + // https://github.com/servo/webrender/issues/2234 + let file_name = format!("{}.raw", image_id); let short_path = format!("images/{}", file_name); - let full_path = path_images.clone().join(&file_name); - fs::File::create(full_path) + fs::File::create(path_images.join(file_name)) .expect(&format!("Unable to create {}", short_path)) .write_all(&*arc) .unwrap(); @@ -1070,7 +1096,9 @@ impl ResourceCache { assert_eq!(template.tiling, None); let request = BlobImageRequest { key, - tile: None, //TODO: tiled blob images + //TODO: support tiled blob images + // https://github.com/servo/webrender/issues/2236 + tile: None, }; let renderer = self.blob_image_renderer.as_mut().unwrap(); @@ -1146,22 +1174,142 @@ impl ResourceCache { (resources, external_images) } - pub fn load_capture(&mut self, resources: PlainResources, root: &PathBuf) { + pub fn save_caches(&self, root: &PathBuf) -> PlainCacheRef { + use std::io::Write; + use std::fs; + + let path_glyphs = root.join("glyphs"); + if !path_glyphs.is_dir() { + fs::create_dir(&path_glyphs).unwrap(); + } + + info!("\tcached glyphs"); + let mut glyph_paths = FastHashMap::default(); + for cache in self.cached_glyphs.glyph_key_caches.values() { + for result in cache.resources.values() { + let arc = match *result { + Ok(Some(ref info)) => &info.glyph_bytes, + Ok(None) | Err(_) => continue, + }; + let glyph_id = glyph_paths.len() + 1; + let entry = match glyph_paths.entry(arc.as_ptr()) { + Entry::Occupied(_) => continue, + Entry::Vacant(e) => e, + }; + + let file_name = format!("{}.raw", glyph_id); + let short_path = format!("glyphs/{}", file_name); + fs::File::create(path_glyphs.join(&file_name)) + .expect(&format!("Unable to create {}", short_path)) + .write_all(&*arc) + .unwrap(); + entry.insert(short_path); + } + } + + PlainCacheRef { + current_frame_id: self.current_frame_id, + glyphs: self.cached_glyphs.glyph_key_caches + .iter() + .map(|(font_instance, cache)| { + let resources = cache.resources + .iter() + .map(|(key, result)| { + (key.clone(), match *result { + Ok(Some(ref info)) => Ok(Some(PlainCachedGlyphInfo { + texture_cache_handle: info.texture_cache_handle.clone(), + glyph_bytes: glyph_paths[&info.glyph_bytes.as_ptr()].clone(), + size: info.size, + offset: info.offset, + scale: info.scale, + format: info.format, + })), + Ok(None) => Ok(None), + Err(ref e) => Err(e.clone()), + }) + }) + .collect(); + (font_instance, ResourceClassCache { resources }) + }) + .collect(), + glyph_dimensions: &self.cached_glyph_dimensions, + images: &self.cached_images, + textures: &self.texture_cache, + } + } + + pub fn load_capture( + &mut self, + resources: PlainResources, + caches: Option, + root: &PathBuf, + ) { use std::fs::File; use std::io::Read; info!("loading resource cache"); - self.cached_glyphs.clear(); - self.cached_images.clear(); - self.cached_render_tasks.clear(); - - self.state = State::Idle; - self.current_frame_id = FrameId(0); + //TODO: instead of filling the local path to Arc map as we process + // each of the resource types, we could go through all of the local paths + // and fill out the map as the first step. + let mut raw_map = FastHashMap::>>::default(); - let max_texture_size = self.texture_cache.max_texture_size(); - self.texture_cache = TextureCache::new(max_texture_size); + match caches { + Some(cached) => { + let glyph_key_caches = cached.glyphs + .into_iter() + .map(|(font_instance, rcc)| { + let resources = rcc.resources + .into_iter() + .map(|(key, result)| { + (key, match result { + Ok(Some(info)) => { + let glyph_bytes = match raw_map.entry(info.glyph_bytes) { + Entry::Occupied(e) => { + e.get().clone() + } + Entry::Vacant(e) => { + let mut buffer = Vec::new(); + File::open(root.join(e.key())) + .expect(&format!("Unable to open {}", e.key())) + .read_to_end(&mut buffer) + .unwrap(); + e.insert(Arc::new(buffer)) + .clone() + } + }; + Ok(Some(CachedGlyphInfo { + texture_cache_handle: info.texture_cache_handle, + glyph_bytes, + size: info.size, + offset: info.offset, + scale: info.scale, + format: info.format, + })) + }, + Ok(None) => Ok(None), + Err(e) => Err(e), + }) + }) + .collect(); + (font_instance, ResourceClassCache { resources }) + }) + .collect(); + self.current_frame_id = cached.current_frame_id; + self.cached_glyphs = GlyphCache { glyph_key_caches }; + self.cached_glyph_dimensions = cached.glyph_dimensions; + self.texture_cache = cached.textures; + } + None => { + self.current_frame_id = FrameId(0); + self.cached_glyphs.clear(); + self.cached_glyph_dimensions.clear(); + self.cached_images.clear(); + let max_texture_size = self.texture_cache.max_texture_size(); + self.texture_cache = TextureCache::new(max_texture_size); + } + } - self.cached_glyph_dimensions.clear(); + self.state = State::Idle; self.glyph_rasterizer.reset(); self.pending_image_requests.clear(); @@ -1169,7 +1317,6 @@ impl ResourceCache { res.font_templates.clear(); *res.font_instances.write().unwrap() = resources.font_instances; res.image_templates.images.clear(); - let mut raw_map = FastHashMap::>>::default(); info!("\tfont templates..."); for (key, plain_template) in resources.font_templates { @@ -1180,9 +1327,8 @@ impl ResourceCache { e.get().clone() } Entry::Vacant(e) => { - let path = format!("{}/{}", root.to_string_lossy(), e.key()); let mut buffer = Vec::new(); - File::open(path) + File::open(root.join(e.key())) .expect(&format!("Unable to open {}", e.key())) .read_to_end(&mut buffer) .unwrap(); @@ -1208,10 +1354,8 @@ impl ResourceCache { e.get().clone() } Entry::Vacant(e) => { - //TODO: consider merging the code path with font loading - let path = format!("{}/{}", root.to_string_lossy(), e.key()); let mut buffer = Vec::new(); - File::open(path) + File::open(root.join(e.key())) .expect(&format!("Unable to open {}", e.key())) .read_to_end(&mut buffer) .unwrap(); diff --git a/webrender/src/texture_allocator.rs b/webrender/src/texture_allocator.rs index 771ab33376..e5dd2d022a 100644 --- a/webrender/src/texture_allocator.rs +++ b/webrender/src/texture_allocator.rs @@ -22,6 +22,7 @@ const MINIMUM_LARGE_RECT_SIZE: u32 = 32; /// /// This approach was chosen because of its simplicity, good performance, and easy support for /// dynamic texture deallocation. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct GuillotineAllocator { texture_size: DeviceUintSize, free_list: FreeRectList, @@ -170,6 +171,7 @@ impl GuillotineAllocator { /// A binning free list. Binning is important to avoid sifting through lots of small strips when /// allocating many texture items. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct FreeRectList { small: Vec, medium: Vec, @@ -177,7 +179,7 @@ struct FreeRectList { } impl FreeRectList { - fn new() -> FreeRectList { + fn new() -> Self { FreeRectList { small: vec![], medium: vec![], diff --git a/webrender/src/texture_cache.rs b/webrender/src/texture_cache.rs index cc40e7e9d0..9a98d9f8d0 100644 --- a/webrender/src/texture_cache.rs +++ b/webrender/src/texture_cache.rs @@ -29,13 +29,14 @@ const TEXTURE_REGION_DIMENSIONS: u32 = 512; // Maintains a simple freelist of texture IDs that are mapped // to real API-specific texture IDs in the renderer. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct CacheTextureIdList { free_list: Vec, next_id: usize, } impl CacheTextureIdList { - fn new() -> CacheTextureIdList { + fn new() -> Self { CacheTextureIdList { next_id: 0, free_list: Vec::new(), @@ -63,6 +64,7 @@ impl CacheTextureIdList { // Items in the texture cache can either be standalone textures, // or a sub-rect inside the shared cache. #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] enum EntryKind { Standalone, Cache { @@ -79,6 +81,7 @@ enum EntryKind { // cache. This is stored for each item whether it's in the shared // cache or a standalone texture. #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct CacheEntry { // Size the requested item, in device pixels. size: DeviceUintSize, @@ -153,16 +156,18 @@ type WeakCacheEntryHandle = WeakFreeListHandle; // In this case, the cache handle needs to re-upload this item // to the texture cache (see request() below). #[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Clone, Deserialize, Serialize))] pub struct TextureCacheHandle { entry: Option, } impl TextureCacheHandle { - pub fn new() -> TextureCacheHandle { + pub fn new() -> Self { TextureCacheHandle { entry: None } } } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct TextureCache { // A lazily allocated, fixed size, texture array for // each format the texture cache supports. @@ -181,6 +186,7 @@ pub struct TextureCache { // A list of updates that need to be applied to the // texture cache in the rendering thread this frame. + #[cfg_attr(feature = "capture", serde(skip))] pending_updates: TextureUpdateList, // The current frame ID. Used for cache eviction policies. @@ -218,7 +224,7 @@ impl TextureCache { array_rgba8_nearest: TextureArray::new( ImageFormat::BGRA8, TextureFilter::Nearest, - TEXTURE_ARRAY_LAYERS_NEAREST + TEXTURE_ARRAY_LAYERS_NEAREST, ), cache_textures: CacheTextureIdList::new(), pending_updates: TextureUpdateList::new(), @@ -801,23 +807,19 @@ impl SlabSize { } // The x/y location within a texture region of an allocation. -struct TextureLocation { - x: u8, - y: u8, -} +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] +struct TextureLocation(u8, u8); impl TextureLocation { - fn new(x: u32, y: u32) -> TextureLocation { - debug_assert!(x < 256 && y < 256); - TextureLocation { - x: x as u8, - y: y as u8, - } + fn new(x: u32, y: u32) -> Self { + debug_assert!(x < 0x100 && y < 0x100); + TextureLocation(x as u8, y as u8) } } // A region is a sub-rect of a texture array layer. // All allocations within a region are of the same size. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct TextureRegion { layer_index: i32, region_size: u32, @@ -829,7 +831,7 @@ struct TextureRegion { } impl TextureRegion { - fn new(region_size: u32, layer_index: i32, origin: DeviceUintPoint) -> TextureRegion { + fn new(region_size: u32, layer_index: i32, origin: DeviceUintPoint) -> Self { TextureRegion { layer_index, region_size, @@ -876,8 +878,8 @@ impl TextureRegion { fn alloc(&mut self) -> Option { self.free_slots.pop().map(|location| { DeviceUintPoint::new( - self.origin.x + self.slab_size * location.x as u32, - self.origin.y + self.slab_size * location.y as u32, + self.origin.x + self.slab_size * location.0 as u32, + self.origin.y + self.slab_size * location.1 as u32, ) }) } @@ -900,6 +902,7 @@ impl TextureRegion { // A texture array contains a number of texture layers, where // each layer contains one or more regions that can act // as slab allocators. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct TextureArray { filter: TextureFilter, layer_count: usize, @@ -914,7 +917,7 @@ impl TextureArray { format: ImageFormat, filter: TextureFilter, layer_count: usize - ) -> TextureArray { + ) -> Self { TextureArray { format, filter, diff --git a/webrender/src/tiling.rs b/webrender/src/tiling.rs index b07760d538..b09d1d3f3e 100644 --- a/webrender/src/tiling.rs +++ b/webrender/src/tiling.rs @@ -35,6 +35,7 @@ pub struct ScrollbarPrimitive { } #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderTargetIndex(pub usize); pub struct RenderTargetContext<'a> { @@ -46,6 +47,7 @@ pub struct RenderTargetContext<'a> { pub use_dual_source_blending: bool, } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] struct TextureAllocator { // TODO(gw): Replace this with a simpler allocator for // render target allocation - this use case doesn't need @@ -111,16 +113,19 @@ pub trait RenderTarget { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum RenderTargetKind { Color, // RGBA32 Alpha, // R8 } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderTargetList { screen_size: DeviceIntSize, pub format: ImageFormat, pub max_size: DeviceUintSize, pub targets: Vec, + #[cfg_attr(feature = "capture", serde(skip))] pub texture: Option, } @@ -216,17 +221,20 @@ impl RenderTargetList { /// Storing the task ID allows the renderer to find /// the target rect within the render target that this /// pipeline exists at. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct FrameOutput { pub task_id: RenderTaskId, pub pipeline_id: PipelineId, } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct ScalingInfo { pub src_task_id: RenderTaskId, pub dest_task_id: RenderTaskId, } /// A render target represents a number of rendering operations on a surface. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct ColorRenderTarget { pub alpha_batcher: AlphaBatcher, // List of blur operations to apply for this render target. @@ -361,6 +369,7 @@ impl RenderTarget for ColorRenderTarget { } } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct AlphaRenderTarget { pub clip_batcher: ClipBatcher, pub brush_mask_corners: Vec, @@ -530,6 +539,7 @@ impl RenderTarget for AlphaRenderTarget { } } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct TextureCacheRenderTarget { pub horizontal_blurs: Vec, } @@ -572,6 +582,7 @@ impl TextureCacheRenderTarget { } } +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum RenderPassKind { MainFramebuffer(ColorRenderTarget), OffScreen { @@ -586,6 +597,7 @@ pub enum RenderPassKind { /// /// A render pass can have several render targets if there wasn't enough space in one /// target to do all of the rendering for that pass. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct RenderPass { pub kind: RenderPassKind, tasks: Vec, @@ -731,6 +743,7 @@ impl CompositeOps { /// A rendering-oriented representation of frame::Frame built by the render backend /// and presented to the renderer. +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub struct Frame { pub window_size: DeviceUintSize, pub inner_rect: DeviceUintRect, @@ -738,6 +751,7 @@ pub struct Frame { pub layer: DocumentLayer, pub device_pixel_ratio: f32, pub passes: Vec, + #[cfg_attr(feature = "capture", serde(default = "FrameProfileCounters::new", skip))] pub profile_counters: FrameProfileCounters, pub node_data: Vec, @@ -746,12 +760,14 @@ pub struct Frame { // List of updates that need to be pushed to the // gpu resource cache. + #[cfg_attr(feature = "capture", serde(skip))] pub gpu_cache_updates: Option, // List of textures that we don't know about yet // from the backend thread. The render thread // will use a callback to resolve these and // patch the data structures. + #[cfg_attr(feature = "capture", serde(skip))] pub deferred_resolves: Vec, } diff --git a/webrender/src/util.rs b/webrender/src/util.rs index 758291256d..b21bd05edf 100644 --- a/webrender/src/util.rs +++ b/webrender/src/util.rs @@ -214,8 +214,9 @@ pub fn _subtract_rect( } } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] #[repr(u32)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Deserialize, Serialize))] pub enum TransformedRectKind { AxisAligned = 0, Complex = 1, diff --git a/webrender_api/src/api.rs b/webrender_api/src/api.rs index 0471032013..4e9a9f11b6 100644 --- a/webrender_api/src/api.rs +++ b/webrender_api/src/api.rs @@ -419,7 +419,25 @@ impl fmt::Debug for DocumentMsg { } } -#[derive(Debug, Clone, Deserialize, Serialize)] +bitflags!{ + /// Bit flags for WR stages to store in a capture. + // Note: capturing `FRAME` without `SCENE` is not currently supported. + #[derive(Deserialize, Serialize)] + pub struct CaptureBits: u8 { + const SCENE = 0x1; + const FRAME = 0x2; + } +} + +/// Information about a loaded capture of each document +/// that is returned by `RenderBackend`. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CapturedDocument { + pub document_id: DocumentId, + pub root_pipeline_id: Option, +} + +#[derive(Clone, Deserialize, Serialize)] pub enum DebugCommand { /// Display the frame profiler on screen. EnableProfiler(bool), @@ -444,9 +462,9 @@ pub enum DebugCommand { /// Fetch screenshot. FetchScreenshot, /// Save a capture of all the documents state. - SaveCapture(PathBuf), + SaveCapture(PathBuf, CaptureBits), /// Load a capture of all the documents state. - LoadCapture(PathBuf), + LoadCapture(PathBuf, MsgSender), /// Configure if dual-source blending is used, if available. EnableDualSourceBlending(bool), } @@ -964,15 +982,22 @@ impl RenderApi { } /// Save a capture of the current frame state for debugging. - pub fn save_capture(&self, path: PathBuf) { - let msg = ApiMsg::DebugCommand(DebugCommand::SaveCapture(path)); + pub fn save_capture(&self, path: PathBuf, bits: CaptureBits) { + let msg = ApiMsg::DebugCommand(DebugCommand::SaveCapture(path, bits)); self.send_message(msg); } /// Load a capture of the current frame state for debugging. - pub fn load_capture(&self, path: PathBuf) { - let msg = ApiMsg::DebugCommand(DebugCommand::LoadCapture(path)); + pub fn load_capture(&self, path: PathBuf) -> Vec { + let (tx, rx) = channel::msg_channel().unwrap(); + let msg = ApiMsg::DebugCommand(DebugCommand::LoadCapture(path, tx)); self.send_message(msg); + + let mut documents = Vec::new(); + while let Ok(captured_doc) = rx.recv() { + documents.push(captured_doc); + } + documents } pub fn send_debug_cmd(&self, cmd: DebugCommand) { diff --git a/webrender_api/src/display_list.rs b/webrender_api/src/display_list.rs index 06ec03c1be..b907300163 100644 --- a/webrender_api/src/display_list.rs +++ b/webrender_api/src/display_list.rs @@ -491,17 +491,6 @@ impl<'de> Deserialize<'de> for BuiltDisplayList { use display_item::CompletelySpecificDisplayItem::*; use display_item::{CompletelySpecificDisplayItem, GenericDisplayItem}; - // Push a vector of things into the DL according to the - // convention used by `skip_iter` and `push_iter` - fn push_vec(data: &mut Vec, vec: Vec) { - let vec_len = vec.len(); - let byte_size = mem::size_of::() * vec_len; - serialize_fast(data, &byte_size); - serialize_fast(data, &vec_len); - let count = serialize_iter_fast(data, vec.into_iter()); - assert_eq!(count, vec_len); - } - let list = Vec::> ::deserialize(deserializer)?; @@ -511,15 +500,15 @@ impl<'de> Deserialize<'de> for BuiltDisplayList { let item = DisplayItem { item: match complete.item { Clip(specific_item, complex_clips) => { - push_vec(&mut temp, complex_clips); + DisplayListBuilder::push_iter_impl(&mut temp, complex_clips); SpecificDisplayItem::Clip(specific_item) }, ClipChain(specific_item, clip_chain_ids) => { - push_vec(&mut temp, clip_chain_ids); + DisplayListBuilder::push_iter_impl(&mut temp, clip_chain_ids); SpecificDisplayItem::ClipChain(specific_item) } ScrollFrame(specific_item, complex_clips) => { - push_vec(&mut temp, complex_clips); + DisplayListBuilder::push_iter_impl(&mut temp, complex_clips); SpecificDisplayItem::ScrollFrame(specific_item) }, StickyFrame(specific_item) => SpecificDisplayItem::StickyFrame(specific_item), @@ -527,7 +516,7 @@ impl<'de> Deserialize<'de> for BuiltDisplayList { ClearRectangle => SpecificDisplayItem::ClearRectangle, Line(specific_item) => SpecificDisplayItem::Line(specific_item), Text(specific_item, glyphs) => { - push_vec(&mut temp, glyphs); + DisplayListBuilder::push_iter_impl(&mut temp, glyphs); SpecificDisplayItem::Text(specific_item) }, Image(specific_item) => SpecificDisplayItem::Image(specific_item), @@ -539,12 +528,12 @@ impl<'de> Deserialize<'de> for BuiltDisplayList { SpecificDisplayItem::RadialGradient(specific_item), Iframe(specific_item) => SpecificDisplayItem::Iframe(specific_item), PushStackingContext(specific_item, filters) => { - push_vec(&mut temp, filters); + DisplayListBuilder::push_iter_impl(&mut temp, filters); SpecificDisplayItem::PushStackingContext(specific_item) }, PopStackingContext => SpecificDisplayItem::PopStackingContext, SetGradientStops(stops) => { - push_vec(&mut temp, stops); + DisplayListBuilder::push_iter_impl(&mut temp, stops); SpecificDisplayItem::SetGradientStops }, PushShadow(specific_item) => SpecificDisplayItem::PushShadow(specific_item), @@ -927,38 +916,48 @@ impl DisplayListBuilder { ) } - fn push_iter(&mut self, iter: I) + fn push_iter_impl(data: &mut Vec, iter_source: I) where I: IntoIterator, I::IntoIter: ExactSizeIterator + Clone, I::Item: Serialize, { - let iter = iter.into_iter(); + let iter = iter_source.into_iter(); let len = iter.len(); - // Format: // payload_byte_size: usize, item_count: usize, [I; item_count] // We write a dummy value so there's room for later - let byte_size_offset = self.data.len(); - serialize_fast(&mut self.data, &0usize); - serialize_fast(&mut self.data, &len); - let payload_offset = self.data.len(); + let byte_size_offset = data.len(); + serialize_fast(data, &0usize); + serialize_fast(data, &len); + let payload_offset = data.len(); - let count = serialize_iter_fast(&mut self.data, iter.into_iter()); + let count = serialize_iter_fast(data, iter); // Now write the actual byte_size - let final_offset = self.data.len(); + let final_offset = data.len(); let byte_size = final_offset - payload_offset; // Note we don't use serialize_fast because we don't want to change the Vec's len - bincode::serialize_into(&mut &mut self.data[byte_size_offset..], - &byte_size, - bincode::Infinite).unwrap(); + bincode::serialize_into( + &mut &mut data[byte_size_offset..], + &byte_size, + bincode::Infinite, + ).unwrap(); debug_assert_eq!(len, count); } + fn push_iter(&mut self, iter: I) + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator + Clone, + I::Item: Serialize, + { + Self::push_iter_impl(&mut self.data, iter); + } + pub fn push_rect(&mut self, info: &LayoutPrimitiveInfo, color: ColorF) { let item = SpecificDisplayItem::Rectangle(RectangleDisplayItem { color }); self.push_item(item, info); @@ -1254,7 +1253,7 @@ impl DisplayListBuilder { } /// Pushes a linear gradient to be displayed. - /// + /// /// The gradient itself is described in the /// `gradient` parameter. It is drawn on /// a "tile" with the dimensions from `tile_size`. @@ -1262,9 +1261,9 @@ impl DisplayListBuilder { /// to the bottom infinitly. If `tile_spacing` /// is not zero spacers with the given dimensions /// are inserted between the tiles as seams. - /// + /// /// The origin of the tiles is given in `info.rect.origin`. - /// If the gradient should only be displayed once limit + /// If the gradient should only be displayed once limit /// the `info.rect.size` to a single tile. /// The gradient is only visible within the local clip. pub fn push_gradient( @@ -1284,7 +1283,7 @@ impl DisplayListBuilder { } /// Pushes a radial gradient to be displayed. - /// + /// /// See [`push_gradient`](#method.push_gradient) for explanation. pub fn push_radial_gradient( &mut self, diff --git a/wrench/src/args.yaml b/wrench/src/args.yaml index 9023ad5e26..11810fb888 100644 --- a/wrench/src/args.yaml +++ b/wrench/src/args.yaml @@ -149,3 +149,11 @@ subcommands: help: second benchmark file to compare required: true index: 2 + - load: + about: load a capture + args: + - path: + help: directory containing the capture + takes_value: true + required: false + index: 1 diff --git a/wrench/src/main.rs b/wrench/src/main.rs index c4f7205a8b..ce82010c2d 100644 --- a/wrench/src/main.rs +++ b/wrench/src/main.rs @@ -419,6 +419,13 @@ fn main() { let second_filename = subargs.value_of("second_filename").unwrap(); perf::compare(first_filename, second_filename); return; + } else if let Some(subargs) = args.subcommand_matches("load") { + let dir = subargs.value_of("path").unwrap_or("../captures/example"); + let mut documents = wrench.api.load_capture(PathBuf::from(dir)); + println!("loaded {:?}", documents.iter().map(|cd| cd.document_id).collect::>()); + let captured = documents.swap_remove(0); + wrench.document_id = captured.document_id; + Box::new(captured) as Box } else { panic!("Should never have gotten here! {:?}", args); }; @@ -551,15 +558,8 @@ fn main() { } if is_headless { - let pixels = window.gl().read_pixels( - 0, - 0, - size.width as gl::GLsizei, - size.height as gl::GLsizei, - gl::RGBA, - gl::UNSIGNED_BYTE, - ); - + let rect = DeviceUintRect::new(DeviceUintPoint::zero(), size); + let pixels = wrench.renderer.read_pixels_rgba8(rect); save_flipped("screenshot.png", pixels, size); } diff --git a/wrench/src/rawtest.rs b/wrench/src/rawtest.rs index f61d441928..738702e1f3 100644 --- a/wrench/src/rawtest.rs +++ b/wrench/src/rawtest.rs @@ -76,6 +76,7 @@ impl<'a> RawtestHarness<'a> { } fn test_tile_decomposition(&mut self) { + println!("\ttile decomposition..."); // This exposes a crash in tile decomposition let layout_size = LayoutSize::new(800., 800.); let mut resources = ResourceUpdates::new(); @@ -117,6 +118,7 @@ impl<'a> RawtestHarness<'a> { } fn test_retained_blob_images_test(&mut self) { + println!("\tretained blob images test..."); let blob_img; let window_size = self.window.get_inner_size_pixels(); let window_size = DeviceUintSize::new(window_size.0, window_size.1); @@ -197,6 +199,7 @@ impl<'a> RawtestHarness<'a> { } fn test_blob_update_epoch_test(&mut self) { + println!("\tblob update epoch test..."); let (blob_img, blob_img2); let window_size = self.window.get_inner_size_pixels(); let window_size = DeviceUintSize::new(window_size.0, window_size.1); @@ -318,6 +321,7 @@ impl<'a> RawtestHarness<'a> { } fn test_blob_update_test(&mut self) { + println!("\tblob update test..."); let window_size = self.window.get_inner_size_pixels(); let window_size = DeviceUintSize::new(window_size.0, window_size.1); @@ -413,6 +417,7 @@ impl<'a> RawtestHarness<'a> { // Ensures that content doing a save-restore produces the same results as not fn test_save_restore(&mut self) { + println!("\tsave/restore..."); let window_size = self.window.get_inner_size_pixels(); let window_size = DeviceUintSize::new(window_size.0, window_size.1); @@ -478,6 +483,8 @@ impl<'a> RawtestHarness<'a> { } fn test_capture(&mut self) { + println!("\tcapture..."); + let path = "../captures/test"; let layout_size = LayoutSize::new(400., 400.); let (_, windows_height) = self.window.get_inner_size_pixels(); let window_rect = DeviceUintRect::new( @@ -522,7 +529,7 @@ impl<'a> RawtestHarness<'a> { // 2. capture it - self.wrench.api.save_capture("capture".into()); + self.wrench.api.save_capture(path.into(), CaptureBits::all()); self.rx.recv().unwrap(); // 3. set a different scene @@ -541,13 +548,20 @@ impl<'a> RawtestHarness<'a> { // 4. load the first one - self.wrench.api.load_capture("capture".into()); - self.rx.recv().unwrap(); - - // 5. render and compare + let mut documents = self.wrench.api.load_capture(path.into()); + let captured = documents.swap_remove(0); - self.wrench.api.generate_frame(self.wrench.document_id, None); + // 5. render the built frame and compare let pixels1 = self.render_and_get_pixels(window_rect); assert!(pixels0 == pixels1); + + // 6. rebuild the scene and compare again + self.wrench.api.set_root_pipeline( + captured.document_id, + captured.root_pipeline_id.unwrap() + ); + self.wrench.api.generate_frame(captured.document_id, None); + let pixels2 = self.render_and_get_pixels(window_rect); + assert!(pixels0 == pixels2); } } diff --git a/wrench/src/wrench.rs b/wrench/src/wrench.rs index 7714e3d7fc..58975cd238 100644 --- a/wrench/src/wrench.rs +++ b/wrench/src/wrench.rs @@ -121,6 +121,23 @@ pub trait WrenchThing { } } +impl WrenchThing for CapturedDocument { + fn next_frame(&mut self) {} + fn prev_frame(&mut self) {} + fn do_frame(&mut self, wrench: &mut Wrench) -> u32 { + match self.root_pipeline_id.take() { + Some(root_pipeline_id) => { + // skip the first frame - to not overwrite the loaded one + wrench.api.set_root_pipeline(self.document_id, root_pipeline_id); + } + None => { + wrench.refresh(); + } + } + 0 + } +} + pub struct Wrench { window_size: DeviceUintSize, device_pixel_ratio: f32,