Bug 1605283 - Improve support for invalidation debugging and testing r=gw a=reland CLOSED TREE

Third iteration:

Fix broken scrolling (and incorrect positioning of quad tree lines) by
serializing the SpaceMapper(-transform) from take_context, and using it
to transform the primitive rects (instead of the previous translation
based on unclipped.origin);
Note: this is done at visualization time and not at export time to
distinguish actually moving elements from merely-scrolling ones.

Serialize the entire UpdateList, so we get the data (Keys) that's being
added; add it to the overview;

Move the static CSS code into tilecache_base.css; add this and the .js
file to the binary, write them as part of output (instead of manual
copy); clean up CSS a bit;

Differential Revision: https://phabricator.services.mozilla.com/D61049

--HG--
extra : source : 535ae1d4818a3f0af64d61846035135751352bd1
extra : histedit_source : bf9a8f830ec7db4c2d1fcb6deaaf72949d6b69ed
This commit is contained in:
Bert Peers 2020-01-28 20:05:38 +00:00
parent 25b1fb82cf
commit 05e00eed63
6 changed files with 205 additions and 126 deletions

1
gfx/wr/Cargo.lock generated
View File

@ -1634,6 +1634,7 @@ dependencies = [
name = "tileview" name = "tileview"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"webrender 0.61.0", "webrender 0.61.0",

View File

@ -12,3 +12,4 @@ ron = "0.1.7"
serde = {version = "1.0.88", features = ["derive"] } serde = {version = "1.0.88", features = ["derive"] }
webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler","no_static_freetype", "leak_checks"]} webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler","no_static_freetype", "leak_checks"]}
webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]} webrender_api = {path = "../webrender_api", features=["serialize","deserialize"]}
euclid = { version = "0.20.0", features = ["serde"] }

View File

@ -12,11 +12,15 @@ use std::path::Path;
use std::ffi::OsString; use std::ffi::OsString;
use webrender::api::enumerate_interners; use webrender::api::enumerate_interners;
use webrender::UpdateKind; use webrender::UpdateKind;
use euclid::{Rect, Transform3D};
use webrender_api::units::{PicturePoint, PictureSize, PicturePixel, WorldPixel};
static RES_JAVASCRIPT: &'static str = include_str!("tilecache.js");
static RES_BASE_CSS: &'static str = include_str!("tilecache_base.css");
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Slice { pub struct Slice {
pub x: f32, pub transform: Transform3D<f32, PicturePixel, WorldPixel>,
pub y: f32,
pub tile_cache: TileCacheInstanceSerializer pub tile_cache: TileCacheInstanceSerializer
} }
@ -30,17 +34,19 @@ static CSS_PRIM_COUNT: &str = "fill:#40f0f0;fill-opacity:0.1;";
static CSS_CONTENT: &str = "fill:#f04040;fill-opacity:0.1;"; static CSS_CONTENT: &str = "fill:#f04040;fill-opacity:0.1;";
static CSS_COMPOSITOR_KIND_CHANGED: &str = "fill:#f0c070;fill-opacity:0.1;"; static CSS_COMPOSITOR_KIND_CHANGED: &str = "fill:#f0c070;fill-opacity:0.1;";
fn tile_node_to_svg(node: &TileNode, x: f32, y: f32) -> String fn tile_node_to_svg(node: &TileNode, transform: &Transform3D<f32, PicturePixel, WorldPixel>) -> String
{ {
match &node.kind { match &node.kind {
TileNodeKind::Leaf { .. } => { TileNodeKind::Leaf { .. } => {
format!("<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" />\n", let rect_world = transform.transform_rect(&node.rect).unwrap();
(node.rect.origin.x + x) as i32, format!("<rect x=\"{:.2}\" y=\"{:.2}\" width=\"{:.2}\" height=\"{:.2}\" />\n",
(node.rect.origin.y + y) as i32, rect_world.origin.x,
node.rect.size.width, node.rect.size.height) rect_world.origin.y,
rect_world.size.width,
rect_world.size.height)
}, },
TileNodeKind::Node { children } => { TileNodeKind::Node { children } => {
children.iter().fold(String::new(), |acc, child| acc + &tile_node_to_svg(child, x, y) ) children.iter().fold(String::new(), |acc, child| acc + &tile_node_to_svg(child, transform) )
} }
} }
} }
@ -54,7 +60,7 @@ fn tile_to_svg(key: TileOffset,
invalidation_report: &mut String, invalidation_report: &mut String,
svg_width: &mut i32, svg_height: &mut i32 ) -> String svg_width: &mut i32, svg_height: &mut i32 ) -> String
{ {
let mut svg = format!("\n<!-- tile key {},{} ; slice x {} y {}-->\n", key.x, key.y, slice.x, slice.y); let mut svg = format!("\n<!-- tile key {},{} ; -->\n", key.x, key.y);
let tile_fill = let tile_fill =
@ -105,7 +111,6 @@ fn tile_to_svg(key: TileOffset,
svg = format!(r#"{}<rect x="{}" y="{}" width="{}" height="{}" style="{}" ></rect>"#, svg = format!(r#"{}<rect x="{}" y="{}" width="{}" height="{}" style="{}" ></rect>"#,
svg, svg,
//TODO --bpe are these in local space or screen space?
tile.rect.origin.x, tile.rect.origin.x,
tile.rect.origin.y, tile.rect.origin.y,
tile.rect.size.width, tile.rect.size.width,
@ -114,8 +119,7 @@ fn tile_to_svg(key: TileOffset,
svg = format!("{}\n\n<g class=\"svg_quadtree\">\n{}</g>\n", svg = format!("{}\n\n<g class=\"svg_quadtree\">\n{}</g>\n",
svg, svg,
//tile_node_to_svg(&tile.root, tile.rect.origin.x, tile.rect.origin.y)); tile_node_to_svg(&tile.root, &slice.transform));
tile_node_to_svg(&tile.root, 0.0, 0.0));
let right = (tile.rect.origin.x + tile.rect.size.width) as i32; let right = (tile.rect.origin.x + tile.rect.size.width) as i32;
let bottom = (tile.rect.origin.y + tile.rect.size.height) as i32; let bottom = (tile.rect.origin.y + tile.rect.size.height) as i32;
@ -131,16 +135,14 @@ fn tile_to_svg(key: TileOffset,
for prim in &tile.current_descriptor.prims { for prim in &tile.current_descriptor.prims {
let rect = prim.prim_clip_rect; let rect = prim.prim_clip_rect;
//TODO proper positioning of prims, especially when scrolling
// this version seems closest, but introduces gaps (eg in about:config) // the transform could also be part of the CSS, let the browser do it;
let x = (rect.x + slice.x) as i32; // might be a bit faster and also enable actual 3D transforms.
let y = (rect.y + slice.y) as i32; let rect_pixel = Rect {
// this version is .. interesting: when scrolling, nothing moves in about:config, origin: PicturePoint::new(rect.x, rect.y),
// instead the searchbox shifts down.. hmm.. size: PictureSize::new(rect.w, rect.h),
//let x = rect.x as i32; };
//let y = rect.y as i32; let rect_world = slice.transform.transform_rect(&rect_pixel).unwrap();
let w = rect.w as i32;
let h = rect.h as i32;
let style = let style =
if let Some(prev_tile) = prev_tile { if let Some(prev_tile) = prev_tile {
@ -154,9 +156,12 @@ fn tile_to_svg(key: TileOffset,
"class=\"svg_changed_prim\" " "class=\"svg_changed_prim\" "
}; };
svg = format!(r#"{}<rect x="{}" y="{}" width="{}" height="{}" {}/>"#, svg += &format!("<rect x=\"{:.2}\" y=\"{:.2}\" width=\"{:.2}\" height=\"{:.2}\" {}/>",
svg, svg,
x, y, w, h, rect_world.origin.x,
rect_world.origin.y,
rect_world.size.width,
rect_world.size.height,
style); style);
svg += "\n\t"; svg += "\n\t";
@ -182,7 +187,8 @@ fn slices_to_svg(slices: &[Slice], prev_slices: Option<Vec<Slice>>,
svg_width: &mut i32, svg_height: &mut i32, svg_width: &mut i32, svg_height: &mut i32,
max_slice_index: &mut usize) -> String max_slice_index: &mut usize) -> String
{ {
let svg_begin = "<?xml\u{2d}stylesheet type\u{3d}\"text/css\" href\u{3d}\"tilecache.css\" ?>\n"; let svg_begin = "<?xml\u{2d}stylesheet type\u{3d}\"text/css\" href\u{3d}\"tilecache_base.css\" ?>\n\
<?xml\u{2d}stylesheet type\u{3d}\"text/css\" href\u{3d}\"tilecache.css\" ?>\n";
let mut svg = String::new(); let mut svg = String::new();
let mut invalidation_report = String::new(); let mut invalidation_report = String::new();
@ -241,6 +247,7 @@ fn write_html(output_dir: &Path, svg_files: &[String], intern_files: &[String])
<html>\n\ <html>\n\
<head>\n\ <head>\n\
<meta charset=\"UTF-8\">\n\ <meta charset=\"UTF-8\">\n\
<link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache_base.css\"></link>\n\
<link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache.css\"></link>\n\ <link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache.css\"></link>\n\
</head>\n" </head>\n"
.to_string(); .to_string();
@ -305,61 +312,7 @@ fn write_html(output_dir: &Path, svg_files: &[String], intern_files: &[String])
} }
fn write_css(output_dir: &Path, max_slice_index: usize) { fn write_css(output_dir: &Path, max_slice_index: usize) {
let mut css = ".tile_svg {\n\ let mut css = String::new();
float: left;\n\
}\n\
\n\
.split {\n\
position: fixed;\n\
z-index: 1;\n\
top: 0;\n\
padding-top: 20px;\n\
}\n\
\n\
.left {\n\
left: 0;\n\
}\n\
\n\
.right {\n\
right: 0;\n\
width: 20%;\n\
height: 100%;\n\
opacity: 90%;\n\
}\n\
\n\
#intern {\n\
position:relative;\n\
top:60px;\n\
width: 100%;\n\
height: 100%;\n\
color: orange;\n\
background-color:white;\n\
}\n\
.svg_invalidated {\n\
fill: white;\n\
font-family:monospace;\n\
}\n\n\n\
#svg_ui_overlay {\n\
position:absolute;\n\
right:0; \n\
top:0; \n\
z-index:70; \n\
color: rgb(255,255,100);\n\
font-family:monospace;\n\
background-color: #404040a0;\n\
}\n\n\n\
.svg_quadtree {\n\
fill: none;\n\
stroke-width: 1;\n\
stroke: orange;\n\
}\n\n\n\
.svg_changed_prim {\n\
stroke: red;\n\
stroke-width: 2.0;\n\
}\n\n\n\
#svg_ui_slider {\n\
width:90%;\n\
}\n\n".to_string();
for ix in 0..max_slice_index + 1 { for ix in 0..max_slice_index + 1 {
let color = ( ix % 7 ) + 1; let color = ( ix % 7 ) + 1;
@ -370,14 +323,12 @@ fn write_css(output_dir: &Path, max_slice_index: usize) {
let prim_class = format!("tile_slice{}", ix); let prim_class = format!("tile_slice{}", ix);
css = format!("{}\n\ css += &format!("#{} {{\n\
#{} {{\n\
fill: {};\n\ fill: {};\n\
fill-opacity: 0.03;\n\ fill-opacity: 0.03;\n\
stroke-width: 0.8;\n\ stroke-width: 0.8;\n\
stroke: {};\n\ stroke: {};\n\
}}\n\n", }}\n\n",
css,
prim_class, prim_class,
//rgb, //rgb,
"none", "none",
@ -392,23 +343,34 @@ fn write_css(output_dir: &Path, max_slice_index: usize) {
macro_rules! updatelist_to_html_macro { macro_rules! updatelist_to_html_macro {
( $( $name:ident: $ty:ty, )+ ) => { ( $( $name:ident: $ty:ty, )+ ) => {
fn updatelist_to_html(update_lists: &TileCacheLoggerUpdateLists) -> String { fn updatelist_to_html(update_lists: &TileCacheLoggerUpdateLists) -> String {
let mut html = String::new(); let mut html = "\
<!DOCTYPE html>\n\
<html> <head> <meta charset=\"UTF-8\">\n\
<link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache_base.css\"></link>\n\
<link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache.css\"></link>\n\
</head> <body>\n".to_string();
$( $(
html += &format!("<h4 style=\"margin:5px;\">{}</h4>\n<font color=\"green\">\n", stringify!($name)); html += &format!("<div class=\"intern_header\">{}</div>\n<div class=\"intern_data\">\n",
let mut was_insert = true; stringify!($name));
for update in &update_lists.$name.1 { let mut insert_count = 0;
let is_insert = match update.kind { for update in &update_lists.$name.1.updates {
UpdateKind::Insert => true, match update.kind {
_ => false UpdateKind::Insert => {
html += &format!("<div class=\"insert\">{} {}</div>\n",
update.index,
format!("({:?})", update_lists.$name.1.data[insert_count]));
insert_count = insert_count + 1;
}
_ => {
html += &format!("<div class=\"remove\">{}</div>\n",
update.index);
}
}; };
if was_insert != is_insert {
html += &format!("</font><font color=\"{}\">", if is_insert { "green" } else { "red" });
} }
html += &format!("{}, \n", update.index); html += "</div><br/>\n";
was_insert = is_insert;
}
html += &"</font><hr/>\n";
)+ )+
html += "</body> </html>\n";
html html
} }
} }
@ -509,5 +471,8 @@ fn main() {
write_html(output_dir, &svg_files, &intern_files); write_html(output_dir, &svg_files, &intern_files);
write_css(output_dir, max_slice_index); write_css(output_dir, max_slice_index);
println!("OK. For now, manually copy tilecache.js to the output folder please. "); std::fs::write(output_dir.join("tilecache.js"), RES_JAVASCRIPT).unwrap();
std::fs::write(output_dir.join("tilecache_base.css"), RES_BASE_CSS).unwrap();
println!("\n");
} }

View File

@ -0,0 +1,94 @@
.tile_svg {
float: left;
}
#intern {
position:relative;
top:60px;
width: 100%;
height: 100%;
color: orange;
border: 0px;
overflow: auto;
background: white;
}
.intern_header {
color: blue;
font-family: Arial;
font-weight: bold;
line-height: 200%;
background-color: lightgrey;
margin-top: 5px;
margin-bottom: 5px;
}
.intern_data {
font-family: monospace;
font-size: small;
}
.intern_data .insert:nth-child(even) {
background: #FFFFFF;
}
.intern_data .insert:nth-child(odd) {
background: #EFEFEF;
}
.intern_data .insert {
color: #008000;
}
.intern_data .remove {
color: #800000;
}
.split {
position: fixed;
z-index: 1;
top: 0;
padding-top: 14px;
}
.left {
left: 0;
}
.right {
right: 0;
width: 25%;
height: 90%;
}
#svg_ui_overlay {
position:absolute;
right:0;
top:0;
z-index:70;
color: rgb(255,255,100);
font-family:monospace;
background-color: #404040a0;
}
#svg_ui_slider {
width:90%;
}
.svg_invalidated {
fill: white;
font-family:monospace;
}
.svg_quadtree {
fill: none;
stroke-width: 1;
stroke: orange;
}
.svg_changed_prim {
stroke: red;
stroke-width: 2.0;
}

View File

@ -50,11 +50,13 @@ struct Epoch(u64);
/// A list of updates to be applied to the data store, /// A list of updates to be applied to the data store,
/// provided by the interning structure. /// provided by the interning structure.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct UpdateList<S> { pub struct UpdateList<S> {
/// The additions and removals to apply. /// The additions and removals to apply.
pub updates: Vec<Update>, pub updates: Vec<Update>,
/// Actual new data to insert. /// Actual new data to insert.
data: Vec<S>, pub data: Vec<S>,
} }
lazy_static! { lazy_static! {

View File

@ -80,7 +80,7 @@ use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX,
}; };
use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId, NativeTileId}; use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId, NativeTileId};
use crate::debug_colors; use crate::debug_colors;
use euclid::{vec3, Point2D, Scale, Size2D, Vector2D, Rect}; use euclid::{vec3, Point2D, Scale, Size2D, Vector2D, Rect, Transform3D};
use euclid::approxeq::ApproxEq; use euclid::approxeq::ApproxEq;
use crate::filterdata::SFilterData; use crate::filterdata::SFilterData;
use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState}; use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
@ -112,8 +112,23 @@ use ron;
#[cfg(feature = "capture")] #[cfg(feature = "capture")]
use crate::scene_builder_thread::InternerUpdates; use crate::scene_builder_thread::InternerUpdates;
#[cfg(any(feature = "capture", feature = "replay"))] #[cfg(any(feature = "capture", feature = "replay"))]
use crate::intern::Update; use crate::intern::{Internable, UpdateList};
#[cfg(any(feature = "capture", feature = "replay"))]
use api::{ClipIntern, FilterDataIntern, PrimitiveKeyKind};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::backdrop::Backdrop;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::gradient::{LinearGradient, RadialGradient};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::image::{Image, YuvImage};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::line_dec::LineDecoration;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::picture::Picture;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::text_run::TextRun;
#[cfg(feature = "capture")] #[cfg(feature = "capture")]
use std::fs::File; use std::fs::File;
@ -1493,7 +1508,7 @@ impl BackdropInfo {
#[derive(Clone)] #[derive(Clone)]
pub struct TileCacheLoggerSlice { pub struct TileCacheLoggerSlice {
pub serialized_slice: String, pub serialized_slice: String,
pub local_to_world_transform: DeviceRect pub local_to_world_transform: Transform3D<f32, PicturePixel, WorldPixel>,
} }
#[cfg(any(feature = "capture", feature = "replay"))] #[cfg(any(feature = "capture", feature = "replay"))]
@ -1514,7 +1529,7 @@ macro_rules! declare_tile_cache_logger_updatelists {
/// the updates is the list of DataStore updates (avoid UpdateList /// the updates is the list of DataStore updates (avoid UpdateList
/// due to Default() requirements on the Keys) reconstructed at /// due to Default() requirements on the Keys) reconstructed at
/// load time. /// load time.
pub $name: (String, Vec<Update>), pub $name: (String, UpdateList<<$ty as Internable>::Key>),
)+ )+
} }
@ -1522,7 +1537,7 @@ macro_rules! declare_tile_cache_logger_updatelists {
pub fn new() -> Self { pub fn new() -> Self {
TileCacheLoggerUpdateLists { TileCacheLoggerUpdateLists {
$( $(
$name : ( String::new(), Vec::<Update>::new() ), $name : ( String::new(), UpdateList{ updates: Vec::new(), data: Vec::new()} ),
)+ )+
} }
} }
@ -1534,7 +1549,7 @@ macro_rules! declare_tile_cache_logger_updatelists {
updates: &InternerUpdates updates: &InternerUpdates
) { ) {
$( $(
self.$name.0 = ron::ser::to_string_pretty(&updates.$name.updates, Default::default()).unwrap(); self.$name.0 = ron::ser::to_string_pretty(&updates.$name, Default::default()).unwrap();
)+ )+
} }
@ -1552,7 +1567,7 @@ macro_rules! declare_tile_cache_logger_updatelists {
$( $(
serializer.ron_string.push( serializer.ron_string.push(
if self.$name.0.is_empty() { if self.$name.0.is_empty() {
"[]".to_string() "( updates: [], data: [] )".to_string()
} else { } else {
self.$name.0.clone() self.$name.0.clone()
}); });
@ -1646,7 +1661,11 @@ impl TileCacheLogger {
} }
#[cfg(feature = "capture")] #[cfg(feature = "capture")]
pub fn add(&mut self, serialized_slice: String, local_to_world_transform: DeviceRect) { pub fn add(
&mut self,
serialized_slice: String,
local_to_world_transform: Transform3D<f32, PicturePixel, WorldPixel>
) {
if !self.is_enabled() { if !self.is_enabled() {
return; return;
} }
@ -1709,11 +1728,12 @@ impl TileCacheLogger {
output.write_all(b"// slice data\n").unwrap(); output.write_all(b"// slice data\n").unwrap();
output.write_all(b"[\n").unwrap(); output.write_all(b"[\n").unwrap();
for item in &self.frames[index].slices { for item in &self.frames[index].slices {
output.write_all(format!("( x: {}, y: {},\n", output.write_all(b"( transform:\n").unwrap();
item.local_to_world_transform.origin.x, let transform =
item.local_to_world_transform.origin.y) ron::ser::to_string_pretty(
.as_bytes()).unwrap(); &item.local_to_world_transform, Default::default()).unwrap();
output.write_all(b"tile_cache:\n").unwrap(); output.write_all(transform.as_bytes()).unwrap();
output.write_all(b",\n tile_cache:\n").unwrap();
output.write_all(item.serialized_slice.as_bytes()).unwrap(); output.write_all(item.serialized_slice.as_bytes()).unwrap();
output.write_all(b"\n),\n").unwrap(); output.write_all(b"\n),\n").unwrap();
} }
@ -3706,7 +3726,6 @@ impl PicturePrimitive {
} }
}; };
let unclipped =
match self.raster_config { match self.raster_config {
Some(ref raster_config) => { Some(ref raster_config) => {
let pic_rect = PictureRect::from_untyped(&self.precise_local_rect.to_untyped()); let pic_rect = PictureRect::from_untyped(&self.precise_local_rect.to_untyped());
@ -4297,10 +4316,8 @@ impl PicturePrimitive {
root, root,
); );
} }
unclipped
} }
None => DeviceRect::zero() None => {}
}; };
#[cfg(feature = "capture")] #[cfg(feature = "capture")]
@ -4327,14 +4344,13 @@ impl PicturePrimitive {
}); });
} }
let text = ron::ser::to_string_pretty(&tile_cache_tiny, Default::default()).unwrap(); let text = ron::ser::to_string_pretty(&tile_cache_tiny, Default::default()).unwrap();
tile_cache_logger.add(text, unclipped); tile_cache_logger.add(text, map_pic_to_world.get_transform());
} }
} }
} }
#[cfg(not(feature = "capture"))] #[cfg(not(feature = "capture"))]
{ {
let _tile_cache_logger = tile_cache_logger; // unused variable fix let _tile_cache_logger = tile_cache_logger; // unused variable fix
let _unclipped = unclipped;
} }
let state = PictureState { let state = PictureState {