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"
version = "0.1.0"
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)",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"webrender 0.61.0",

View File

@ -12,3 +12,4 @@ ron = "0.1.7"
serde = {version = "1.0.88", features = ["derive"] }
webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler","no_static_freetype", "leak_checks"]}
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 webrender::api::enumerate_interners;
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)]
pub struct Slice {
pub x: f32,
pub y: f32,
pub transform: Transform3D<f32, PicturePixel, WorldPixel>,
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_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 {
TileNodeKind::Leaf { .. } => {
format!("<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" />\n",
(node.rect.origin.x + x) as i32,
(node.rect.origin.y + y) as i32,
node.rect.size.width, node.rect.size.height)
let rect_world = transform.transform_rect(&node.rect).unwrap();
format!("<rect x=\"{:.2}\" y=\"{:.2}\" width=\"{:.2}\" height=\"{:.2}\" />\n",
rect_world.origin.x,
rect_world.origin.y,
rect_world.size.width,
rect_world.size.height)
},
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,
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 =
@ -105,7 +111,6 @@ fn tile_to_svg(key: TileOffset,
svg = format!(r#"{}<rect x="{}" y="{}" width="{}" height="{}" style="{}" ></rect>"#,
svg,
//TODO --bpe are these in local space or screen space?
tile.rect.origin.x,
tile.rect.origin.y,
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,
//tile_node_to_svg(&tile.root, tile.rect.origin.x, tile.rect.origin.y));
tile_node_to_svg(&tile.root, 0.0, 0.0));
tile_node_to_svg(&tile.root, &slice.transform));
let right = (tile.rect.origin.x + tile.rect.size.width) 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 {
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)
let x = (rect.x + slice.x) as i32;
let y = (rect.y + slice.y) as i32;
// this version is .. interesting: when scrolling, nothing moves in about:config,
// instead the searchbox shifts down.. hmm..
//let x = rect.x as i32;
//let y = rect.y as i32;
let w = rect.w as i32;
let h = rect.h as i32;
// the transform could also be part of the CSS, let the browser do it;
// might be a bit faster and also enable actual 3D transforms.
let rect_pixel = Rect {
origin: PicturePoint::new(rect.x, rect.y),
size: PictureSize::new(rect.w, rect.h),
};
let rect_world = slice.transform.transform_rect(&rect_pixel).unwrap();
let style =
if let Some(prev_tile) = prev_tile {
@ -154,10 +156,13 @@ fn tile_to_svg(key: TileOffset,
"class=\"svg_changed_prim\" "
};
svg = format!(r#"{}<rect x="{}" y="{}" width="{}" height="{}" {}/>"#,
svg,
x, y, w, h,
style);
svg += &format!("<rect x=\"{:.2}\" y=\"{:.2}\" width=\"{:.2}\" height=\"{:.2}\" {}/>",
svg,
rect_world.origin.x,
rect_world.origin.y,
rect_world.size.width,
rect_world.size.height,
style);
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,
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 invalidation_report = String::new();
@ -241,6 +247,7 @@ fn write_html(output_dir: &Path, svg_files: &[String], intern_files: &[String])
<html>\n\
<head>\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\
</head>\n"
.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) {
let mut css = ".tile_svg {\n\
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();
let mut css = String::new();
for ix in 0..max_slice_index + 1 {
let color = ( ix % 7 ) + 1;
@ -370,18 +323,16 @@ fn write_css(output_dir: &Path, max_slice_index: usize) {
let prim_class = format!("tile_slice{}", ix);
css = format!("{}\n\
#{} {{\n\
css += &format!("#{} {{\n\
fill: {};\n\
fill-opacity: 0.03;\n\
stroke-width: 0.8;\n\
stroke: {};\n\
}}\n\n",
css,
prim_class,
//rgb,
"none",
rgb);
}}\n\n",
prim_class,
//rgb,
"none",
rgb);
}
let output_file = output_dir.join("tilecache.css");
@ -392,23 +343,34 @@ fn write_css(output_dir: &Path, max_slice_index: usize) {
macro_rules! updatelist_to_html_macro {
( $( $name:ident: $ty:ty, )+ ) => {
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));
let mut was_insert = true;
for update in &update_lists.$name.1 {
let is_insert = match update.kind {
UpdateKind::Insert => true,
_ => false
html += &format!("<div class=\"intern_header\">{}</div>\n<div class=\"intern_data\">\n",
stringify!($name));
let mut insert_count = 0;
for update in &update_lists.$name.1.updates {
match update.kind {
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);
was_insert = is_insert;
}
html += &"</font><hr/>\n";
html += "</div><br/>\n";
)+
html += "</body> </html>\n";
html
}
}
@ -509,5 +471,8 @@ fn main() {
write_html(output_dir, &svg_files, &intern_files);
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,
/// provided by the interning structure.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct UpdateList<S> {
/// The additions and removals to apply.
pub updates: Vec<Update>,
/// Actual new data to insert.
data: Vec<S>,
pub data: Vec<S>,
}
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::debug_colors;
use euclid::{vec3, Point2D, Scale, Size2D, Vector2D, Rect};
use euclid::{vec3, Point2D, Scale, Size2D, Vector2D, Rect, Transform3D};
use euclid::approxeq::ApproxEq;
use crate::filterdata::SFilterData;
use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
@ -112,8 +112,23 @@ use ron;
#[cfg(feature = "capture")]
use crate::scene_builder_thread::InternerUpdates;
#[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")]
use std::fs::File;
@ -1493,7 +1508,7 @@ impl BackdropInfo {
#[derive(Clone)]
pub struct TileCacheLoggerSlice {
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"))]
@ -1514,7 +1529,7 @@ macro_rules! declare_tile_cache_logger_updatelists {
/// the updates is the list of DataStore updates (avoid UpdateList
/// due to Default() requirements on the Keys) reconstructed at
/// 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 {
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
) {
$(
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(
if self.$name.0.is_empty() {
"[]".to_string()
"( updates: [], data: [] )".to_string()
} else {
self.$name.0.clone()
});
@ -1562,7 +1577,7 @@ macro_rules! declare_tile_cache_logger_updatelists {
#[cfg(feature = "replay")]
pub fn from_ron(&mut self, text: &str) {
let serializer : TileCacheLoggerUpdateListsSerializer =
let serializer : TileCacheLoggerUpdateListsSerializer =
match ron::de::from_str(&text) {
Ok(data) => { data }
Err(e) => {
@ -1646,7 +1661,11 @@ impl TileCacheLogger {
}
#[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() {
return;
}
@ -1709,11 +1728,12 @@ impl TileCacheLogger {
output.write_all(b"// slice data\n").unwrap();
output.write_all(b"[\n").unwrap();
for item in &self.frames[index].slices {
output.write_all(format!("( x: {}, y: {},\n",
item.local_to_world_transform.origin.x,
item.local_to_world_transform.origin.y)
.as_bytes()).unwrap();
output.write_all(b"tile_cache:\n").unwrap();
output.write_all(b"( transform:\n").unwrap();
let transform =
ron::ser::to_string_pretty(
&item.local_to_world_transform, Default::default()).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(b"\n),\n").unwrap();
}
@ -3706,7 +3726,6 @@ impl PicturePrimitive {
}
};
let unclipped =
match self.raster_config {
Some(ref raster_config) => {
let pic_rect = PictureRect::from_untyped(&self.precise_local_rect.to_untyped());
@ -4297,10 +4316,8 @@ impl PicturePrimitive {
root,
);
}
unclipped
}
None => DeviceRect::zero()
None => {}
};
#[cfg(feature = "capture")]
@ -4327,14 +4344,13 @@ impl PicturePrimitive {
});
}
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"))]
{
let _tile_cache_logger = tile_cache_logger; // unused variable fix
let _unclipped = unclipped;
}
let state = PictureState {