Bug 1547290 - Dump the render graph in SVG format in frame captures. r=kvark

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

--HG--
extra : source : b4afebeb22461c276463a919880efdbe0126695d
This commit is contained in:
Nicolas Silva 2019-04-29 16:11:12 +02:00
parent e2e9432702
commit 9008946909
7 changed files with 235 additions and 4 deletions

7
gfx/wr/Cargo.lock generated
View File

@ -1392,6 +1392,11 @@ name = "strsim"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "svg_fmt"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.15.30"
@ -1661,6 +1666,7 @@ dependencies = [
"serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"svg_fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"webrender_api 0.60.0",
@ -2037,6 +2043,7 @@ dependencies = [
"checksum smithay-client-toolkit 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "428d6c019bb92753be9670367e3f483e4fcef396180a9b59e813b69b20014881"
"checksum stable_deref_trait 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffbc596e092fe5f598b12ef46cc03754085ac2f4d8c739ad61c4ae266cc3b3fa"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum svg_fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c666f0fed8e1e20e057af770af9077d72f3d5a33157b8537c1475dd8ffd6d32b"
"checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2"
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
"checksum tempfile 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "47776f63b85777d984a50ce49d6b9e58826b6a3766a449fc95bc66cd5663c15b"

View File

@ -50,6 +50,7 @@ webrender_api = { version = "0.60.0", path = "../webrender_api" }
webrender_build = { version = "0.0.1", path = "../webrender_build" }
wr_malloc_size_of = { version = "0.0.1", path = "../wr_malloc_size_of" }
ws = { optional = true, version = "0.7.3" }
svg_fmt = "0.4"
[dependencies.pathfinder_font_renderer]
git = "https://github.com/pcwalton/pathfinder"

View File

@ -39,6 +39,11 @@ impl CaptureConfig {
}
}
pub fn file_path<P>(&self, name: P, ext: &str) -> PathBuf
where P: AsRef<Path> {
self.root.join(name).with_extension(ext)
}
#[cfg(feature = "capture")]
pub fn serialize<T, P>(&self, data: &T, name: P)
where
@ -49,9 +54,7 @@ impl CaptureConfig {
let ron = ron::ser::to_string_pretty(data, self.pretty.clone())
.unwrap();
let path = self.root
.join(name)
.with_extension("ron");
let path = self.file_path(name, "ron");
let mut file = File::create(path)
.unwrap();
write!(file, "{}\n", ron)

View File

@ -70,6 +70,7 @@ extern crate serde;
extern crate thread_profiler;
extern crate wr_malloc_size_of;
extern crate svg_fmt;
use wr_malloc_size_of as malloc_size_of;
#[macro_use]

View File

@ -1644,6 +1644,7 @@ impl RenderBackend {
) -> DebugOutput {
use std::fs;
use capture::CaptureConfig;
use render_task::dump_render_tasks_as_svg;
debug!("capture: saving {:?}", root);
if !root.is_dir() {
@ -1680,6 +1681,14 @@ impl RenderBackend {
config.serialize_tree(&doc.clip_scroll_tree, file_name);
let file_name = format!("builder-{}-{}", id.namespace_id.0, id.id);
config.serialize(doc.frame_builder.as_ref().unwrap(), file_name);
let file_name = format!("render-tasks-{}-{}.svg", id.namespace_id.0, id.id);
let mut svg_file = fs::File::create(&config.file_path(file_name, "svg"))
.expect("Failed to open the SVG file.");
dump_render_tasks_as_svg(
&rendered_document.frame.render_tasks,
&rendered_document.frame.passes,
&mut svg_file
).unwrap();
}
let data_stores_name = format!("data-stores-{}-{}", id.namespace_id.0, id.id);

View File

@ -34,6 +34,7 @@ use std::{ops, mem, usize, f32, i32, u32};
use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
use tiling::{RenderPass, RenderTargetIndex};
use tiling::{RenderTargetKind};
use std::io;
const RENDER_TASK_SIZE_SANITY_CHECK: i32 = 16000;
@ -260,6 +261,14 @@ impl RenderTaskLocation {
_ => false,
}
}
pub fn size(&self) -> DeviceIntSize {
match self {
RenderTaskLocation::Fixed(rect) => rect.size,
RenderTaskLocation::Dynamic(_, size) => *size,
RenderTaskLocation::TextureCache { rect, .. } => rect.size,
}
}
}
#[derive(Debug)]
@ -425,6 +434,25 @@ pub enum RenderTaskKind {
Gradient(GradientTask),
}
impl RenderTaskKind {
pub fn as_str(&self) -> &'static str {
match *self {
RenderTaskKind::Picture(..) => "Picture",
RenderTaskKind::CacheMask(..) => "CacheMask",
RenderTaskKind::ClipRegion(..) => "ClipRegion",
RenderTaskKind::VerticalBlur(..) => "VerticalBlur",
RenderTaskKind::HorizontalBlur(..) => "HorizontalBlur",
RenderTaskKind::Glyph(..) => "Glyph",
RenderTaskKind::Readback(..) => "Readback",
RenderTaskKind::Scaling(..) => "Scaling",
RenderTaskKind::Blit(..) => "Blit",
RenderTaskKind::Border(..) => "Border",
RenderTaskKind::LineDecoration(..) => "LineDecoration",
RenderTaskKind::Gradient(..) => "Gradient",
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
@ -1470,3 +1498,185 @@ pub fn to_cache_size(size: DeviceSize) -> DeviceIntSize {
1.max(size.height.round() as i32),
)
}
// Dump an SVG visualization of the render graph for debugging purposes
#[allow(dead_code)]
pub fn dump_render_tasks_as_svg(
render_tasks: &RenderTaskTree,
passes: &[RenderPass],
output: &mut dyn io::Write,
) -> io::Result<()> {
use svg_fmt::*;
let node_width = 80.0;
let node_height = 30.0;
let vertical_spacing = 8.0;
let horizontal_spacing = 20.0;
let margin = 10.0;
let text_size = 10.0;
let mut pass_rects = Vec::new();
let mut nodes = vec![None; render_tasks.tasks.len()];
let mut x = margin;
let mut max_y: f32 = 0.0;
#[derive(Clone)]
struct Node {
rect: Rectangle,
label: Text,
size: Text,
}
for pass in passes {
let mut layout = VerticalLayout::new(x, margin, node_width);
for task_id in &pass.tasks {
let task_index = task_id.index as usize;
let task = &render_tasks.tasks[task_index];
let rect = layout.push_rectangle(node_height);
let tx = rect.x + rect.w / 2.0;
let ty = rect.y + 10.0;
let label = text(tx, ty, task.kind.as_str());
let size = text(tx, ty + 12.0, format!("{}", task.location.size()));
nodes[task_index] = Some(Node { rect, label, size });
layout.advance(vertical_spacing);
}
pass_rects.push(layout.total_rectangle());
x += node_width + horizontal_spacing;
max_y = max_y.max(layout.y + margin);
}
let mut links = Vec::new();
for node_index in 0..nodes.len() {
if nodes[node_index].is_none() {
continue;
}
let task = &render_tasks.tasks[node_index];
for dep in &task.children {
let dep_index = dep.index as usize;
if let (&Some(ref node), &Some(ref dep_node)) = (&nodes[node_index], &nodes[dep_index]) {
links.push((
dep_node.rect.x + dep_node.rect.w,
dep_node.rect.y + dep_node.rect.h / 2.0,
node.rect.x,
node.rect.y + node.rect.h / 2.0,
));
}
}
}
let svg_w = x + margin;
let svg_h = max_y + margin;
writeln!(output, "{}", BeginSvg { w: svg_w, h: svg_h })?;
// Background.
writeln!(output,
" {}",
rectangle(0.0, 0.0, svg_w, svg_h)
.inflate(1.0, 1.0)
.fill(rgb(50, 50, 50))
)?;
// Passes.
for rect in pass_rects {
writeln!(output,
" {}",
rect.inflate(3.0, 3.0)
.border_radius(4.0)
.opacity(0.4)
.fill(black())
)?;
}
// Links.
for (x1, y1, x2, y2) in links {
dump_task_dependency_link(output, x1, y1, x2, y2);
}
// Tasks.
for node in &nodes {
if let Some(node) = node {
writeln!(output,
" {}",
node.rect
.clone()
.fill(black())
.border_radius(3.0)
.opacity(0.5)
.offset(0.0, 2.0)
)?;
writeln!(output,
" {}",
node.rect
.clone()
.fill(rgb(200, 200, 200))
.border_radius(3.0)
.opacity(0.8)
)?;
writeln!(output,
" {}",
node.label
.clone()
.size(text_size)
.align(Align::Center)
.color(rgb(50, 50, 50))
)?;
writeln!(output,
" {}",
node.size
.clone()
.size(text_size * 0.7)
.align(Align::Center)
.color(rgb(50, 50, 50))
)?;
}
}
writeln!(output, "{}", EndSvg)
}
#[allow(dead_code)]
fn dump_task_dependency_link(
output: &mut io::Write,
x1: f32, y1: f32,
x2: f32, y2: f32,
) {
use svg_fmt::*;
// If the link is a straight horizontal line and spans over multiple passes, it
// is likely to go straight though unrelated nodes in a way that makes it look like
// they are connected, so we bend the line upward a bit to avoid that.
let simple_path = (y1 - y2).abs() > 1.0 || (x2 - x1) < 45.0;
let mid_x = (x1 + x2) / 2.0;
if simple_path {
write!(output, " {}",
path().move_to(x1, y1)
.cubic_bezier_to(mid_x, y1, mid_x, y2, x2, y2)
.fill(Fill::None)
.stroke(Stroke::Color(rgb(100, 100, 100), 3.0))
).unwrap();
} else {
let ctrl1_x = (mid_x + x1) / 2.0;
let ctrl2_x = (mid_x + x2) / 2.0;
let ctrl_y = y1 - 25.0;
write!(output, " {}",
path().move_to(x1, y1)
.cubic_bezier_to(ctrl1_x, y1, ctrl1_x, ctrl_y, mid_x, ctrl_y)
.cubic_bezier_to(ctrl2_x, ctrl_y, ctrl2_x, y2, x2, y2)
.fill(Fill::None)
.stroke(Stroke::Color(rgb(100, 100, 100), 3.0))
).unwrap();
}
}

View File

@ -912,7 +912,7 @@ pub struct RenderPass {
pub kind: RenderPassKind,
/// The set of tasks to be performed in this pass, as indices into the
/// `RenderTaskTree`.
tasks: Vec<RenderTaskId>,
pub tasks: Vec<RenderTaskId>,
}
impl RenderPass {