mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
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:
parent
e2e9432702
commit
9008946909
7
gfx/wr/Cargo.lock
generated
7
gfx/wr/Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user