| author | Bert Peers <bpeers@mozilla.com> |
| Sat, 18 Jan 2020 08:22:51 +0000 | |
| changeset 510651 | edd05995832f23464c115ca117146606cae15cde |
| parent 510650 | 947829830dd5a58f90ed78cff03b20da32b19ee9 |
| child 510652 | 2b73803f5428689f18c704ce633ee89f2b7ef52c |
| push id | 37031 |
| push user | nerli@mozilla.com |
| push date | Sun, 19 Jan 2020 09:15:48 +0000 |
| treeherder | mozilla-central@e9a3c8df0fc5 [default view] [failures only] |
| perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
| reviewers | gw |
| bugs | 1605283 |
| milestone | 74.0a1 |
| first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
| last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -585,16 +585,18 @@ static void WebRenderDebugPrefChangeCall wr::DebugFlags::NEW_SCENE_INDICATOR) GFX_WEBRENDER_DEBUG(".show-overdraw", wr::DebugFlags::SHOW_OVERDRAW) GFX_WEBRENDER_DEBUG(".gpu-cache", wr::DebugFlags::GPU_CACHE_DBG) GFX_WEBRENDER_DEBUG(".slow-frame-indicator", wr::DebugFlags::SLOW_FRAME_INDICATOR) GFX_WEBRENDER_DEBUG(".texture-cache.clear-evicted", wr::DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED) GFX_WEBRENDER_DEBUG(".picture-caching", wr::DebugFlags::PICTURE_CACHING_DBG) + GFX_WEBRENDER_DEBUG(".tile-cache-logging", + wr::DebugFlags::TILE_CACHE_LOGGING_DBG) GFX_WEBRENDER_DEBUG(".primitives", wr::DebugFlags::PRIMITIVE_DBG) // Bit 18 is for the zoom display, which requires the mouse position and thus // currently only works in wrench. GFX_WEBRENDER_DEBUG(".small-screen", wr::DebugFlags::SMALL_SCREEN) GFX_WEBRENDER_DEBUG(".disable-opaque-pass", wr::DebugFlags::DISABLE_OPAQUE_PASS) GFX_WEBRENDER_DEBUG(".disable-alpha-pass", wr::DebugFlags::DISABLE_ALPHA_PASS) GFX_WEBRENDER_DEBUG(".disable-clip-masks", wr::DebugFlags::DISABLE_CLIP_MASKS)
--- a/gfx/webrender_bindings/WebRenderAPI.cpp +++ b/gfx/webrender_bindings/WebRenderAPI.cpp @@ -615,17 +615,19 @@ void WebRenderAPI::WaitFlushed() { // implies that all frame data have been processed when the renderer runs this // event. RunOnRenderThread(std::move(event)); task.Wait(); } void WebRenderAPI::Capture() { - uint8_t bits = 3; // TODO: get from JavaScript + // see CaptureBits + // SCENE | FRAME | TILE_CACHE + uint8_t bits = 7; // TODO: get from JavaScript const char* path = "wr-capture"; // TODO: get from JavaScript wr_api_capture(mDocHandle, path, bits); } void WebRenderAPI::SetTransactionLogging(bool aValue) { wr_api_set_transaction_logging(mDocHandle, aValue); }
--- a/gfx/wr/Cargo.lock +++ b/gfx/wr/Cargo.lock @@ -1626,16 +1626,26 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] +name = "tileview" +version = "0.1.0" +dependencies = [ + "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", + "webrender_api 0.61.0", +] + +[[package]] name = "time" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ]
--- a/gfx/wr/Cargo.toml +++ b/gfx/wr/Cargo.toml @@ -1,16 +1,17 @@ [workspace] members = [ "direct-composition", "examples", "webrender", "webrender_api", "wrench", "example-compositor/compositor", + "tileview", ] [profile.release] debug = true panic = "abort" [profile.dev] panic = "abort"
new file mode 100644 --- /dev/null +++ b/gfx/wr/tileview/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tileview" +version = "0.1.0" +authors = ["Bert Peers <bpeers@mozilla.com>"] +license = "MPL-2.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +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"]}
new file mode 100644 --- /dev/null +++ b/gfx/wr/tileview/src/main.rs @@ -0,0 +1,404 @@ +/* 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 webrender::{TileNode, TileNodeKind, InvalidationReason, TileOffset}; +use webrender::{TileSerializer, TileCacheInstanceSerializer}; +use serde::Deserialize; +//use ron::de::from_reader; +use std::fs::File; +use std::io::prelude::*; +use std::path::Path; +use std::ffi::OsString; + +#[derive(Deserialize)] +pub struct Slice { + pub x: f32, + pub y: f32, + pub tile_cache: TileCacheInstanceSerializer +} + +// invalidation reason CSS colors +static CSS_FRACTIONAL_OFFSET: &str = "fill:#4040c0;fill-opacity:0.1;"; +static CSS_BACKGROUND_COLOR: &str = "fill:#10c070;fill-opacity:0.1;"; +static CSS_SURFACE_OPACITY_CHANNEL: &str = "fill:#c040c0;fill-opacity:0.1;"; +static CSS_NO_TEXTURE: &str = "fill:#c04040;fill-opacity:0.1;"; +static CSS_NO_SURFACE: &str = "fill:#40c040;fill-opacity:0.1;"; +static CSS_PRIM_COUNT: &str = "fill:#40f0f0;fill-opacity:0.1;"; +static CSS_CONTENT: &str = "fill:#f04040;fill-opacity:0.1;"; + +fn tile_node_to_svg(node: &TileNode, x: f32, y: f32) -> 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) + }, + TileNodeKind::Node { children } => { + children.iter().fold(String::new(), |acc, child| acc + &tile_node_to_svg(child, x, y) ) + } + } +} + +fn tile_to_svg(key: TileOffset, + tile: &TileSerializer, + slice: &Slice, + prev_tile: Option<&TileSerializer>, + tile_stroke: &str, + prim_class: &str, + 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 tile_fill = + match tile.invalidation_reason { + Some(InvalidationReason::FractionalOffset) => CSS_FRACTIONAL_OFFSET.to_string(), + Some(InvalidationReason::BackgroundColor) => CSS_BACKGROUND_COLOR.to_string(), + Some(InvalidationReason::SurfaceOpacityChanged) => CSS_SURFACE_OPACITY_CHANNEL.to_string(), + Some(InvalidationReason::NoTexture) => CSS_NO_TEXTURE.to_string(), + Some(InvalidationReason::NoSurface) => CSS_NO_SURFACE.to_string(), + Some(InvalidationReason::PrimCount) => CSS_PRIM_COUNT.to_string(), + Some(InvalidationReason::Content { prim_compare_result } ) => { + let _foo = prim_compare_result; + CSS_CONTENT.to_string() //TODO do something with the compare result + } + None => { + let mut background = tile.background_color; + if background.is_none() { + background = slice.tile_cache.background_color + } + match background { + Some(color) => { + let rgb = ( (color.r * 255.0) as u8, + (color.g * 255.0) as u8, + (color.b * 255.0) as u8 ); + format!("fill:rgb({},{},{});fill-opacity:0.3;", rgb.0, rgb.1, rgb.2) + } + None => "fill:none;".to_string() + } + } + }; + + //let tile_style = format!("{}{}", tile_fill, tile_stroke); + let tile_style = format!("{}stroke:none;", tile_fill); + + let title = match tile.invalidation_reason { + Some(_) => format!("<title>slice {} tile ({},{}) - {:?}</title>", + slice.tile_cache.slice, key.x, key.y, + tile.invalidation_reason), + None => String::new() + }; + + if let Some(reason) = tile.invalidation_reason { + invalidation_report.push_str( + &format!("\n<tspan x=\"0\" dy=\"16px\">slice {} tile key ({},{}) invalidated: {:?}</tspan>\n", + slice.tile_cache.slice, key.x, key.y, reason)); + } + + 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, + tile.rect.size.height, + tile_style); + + 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)); + + let right = (tile.rect.origin.x + tile.rect.size.width) as i32; + let bottom = (tile.rect.origin.y + tile.rect.size.height) as i32; + + *svg_width = if right > *svg_width { right } else { *svg_width }; + *svg_height = if bottom > *svg_height { bottom } else { *svg_height }; + + svg += "\n<!-- primitives -->\n"; + + svg = format!("{}<g id=\"{}\">\n\t", + svg, + prim_class); + + 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; + + let style = + if let Some(prev_tile) = prev_tile { + // when this O(n^2) gets too slow, stop brute-forcing and use a set or something + if prev_tile.current_descriptor.prims.iter().find(|&prim| prim.prim_clip_rect == rect).is_some() { + "" + } else { + "class=\"svg_changed_prim\" " + } + } else { + "class=\"svg_changed_prim\" " + }; + + svg = format!(r#"{}<rect x="{}" y="{}" width="{}" height="{}" {}/>"#, + svg, + x, y, w, h, + style); + + svg += "\n\t"; + } + + svg += "\n</g>\n"; + + // nearly invisible, all we want is the toolip really + let style = "style=\"fill-opacity:0.001;"; + svg += &format!("<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" {}{}\" >{}<\u{2f}rect>", + tile.rect.origin.x, + tile.rect.origin.y, + tile.rect.size.width, + tile.rect.size.height, + style, + tile_stroke, + title); + + svg +} + +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 mut svg = String::new(); + let mut invalidation_report = String::new(); + + for slice in slices { + let tile_cache = &slice.tile_cache; + *max_slice_index = if tile_cache.slice > *max_slice_index { tile_cache.slice } else { *max_slice_index }; + + let prim_class = format!("tile_slice{}", tile_cache.slice); + + //println!("slice {}", tile_cache.slice); + svg.push_str(&format!("\n<!-- tile_cache slice {} -->\n", + tile_cache.slice)); + + //let tile_stroke = "stroke:grey;stroke-width:1;".to_string(); + let tile_stroke = "stroke:none;".to_string(); + + let mut prev_slice = None; + if let Some(prev) = &prev_slices { + for prev_search in prev { + if prev_search.tile_cache.slice == tile_cache.slice { + prev_slice = Some(prev_search); + break; + } + } + } + + for (key, tile) in &tile_cache.tiles { + let mut prev_tile = None; + if let Some(prev) = prev_slice { + prev_tile = prev.tile_cache.tiles.get(key); + } + + //println!("fofs {:?}", tile.fract_offset); + //println!("id {:?}", tile.id); + //println!("invr {:?}", tile.invalidation_reason); + svg.push_str(&tile_to_svg(*key, &tile, &slice, prev_tile, &tile_stroke, &prim_class, + &mut invalidation_report, svg_width, svg_height)); + } + } + + svg.push_str(&format!("<text x=\"0\" y=\"-8px\" class=\"svg_invalidated\">{}</text>\n", invalidation_report)); + + format!(r#"{}<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" width="{}" height="{}" >"#, + svg_begin, + svg_width, + svg_height) + + "\n" + + "<rect fill=\"black\" width=\"100%\" height=\"100%\"/>\n" + + &svg + + "\n</svg>\n" +} + +fn write_html(output_dir: &Path, svg_files: &[String]) { + let html_head = "<!DOCTYPE html>\n\ + <html>\n\ + <head>\n\ + <meta charset=\"UTF-8\">\n\ + <link rel=\"stylesheet\" type=\"text/css\" href=\"tilecache.css\"></link>\n\ + </head>\n" + .to_string(); + + let html_body = "<body bgcolor=\"#000000\" onload=\"load()\">\n" + .to_string(); + + + let mut script = "\n<script>\n".to_string(); + + script = format!("{}var svg_files = [\n", script); + for svg_file in svg_files { + script = format!("{} \"{}\",\n", script, svg_file); + } + script = format!("{}];\n</script>\n\n", script); + //TODO this requires copying the js file from somewhere? + script = format!("{}<script src=\"tilecache.js\" type=\"text/javascript\"></script>\n\n", script); + + + let html_end = "</body>\n\ + </html>\n" + .to_string(); + + let html_body = format!( + "{}\n\ + <object id=\"svg_container0\" type=\"image/svg+xml\" data=\"{}\" class=\"tile_svg\" ></object>\n\ + <object id=\"svg_container1\" type=\"image/svg+xml\" data=\"{}\" class=\"tile_svg\" ></object>\n\ + <div id=\"svg_ui_overlay\">\n\ + <div id=\"text_frame_counter\">{}</div>\n\ + <div id=\"text_spacebar\">Spacebar to Play</div>\n\ + <div>Use Left/Right to Step</div>\n\ + <input id=\"frame_slider\" type=\"range\" min=\"0\" max=\"{}\" value=\"0\" class=\"svg_ui_slider\" /> + </div>", + html_body, + svg_files[0], + svg_files[0], + svg_files[0], + svg_files.len() ); + + let html = format!("{}{}{}{}", html_head, html_body, script, html_end); + + let output_file = output_dir.join("index.html"); + let mut html_output = File::create(output_file).unwrap(); + html_output.write_all(html.as_bytes()).unwrap(); +} + +fn write_css(output_dir: &Path, max_slice_index: usize) { + let mut css = ".tile_svg {\n\ + position: fixed;\n\ + }\n\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 { + let color = ( ix % 7 ) + 1; + let rgb = format!("rgb({},{},{})", + if color & 2 != 0 { 205 } else { 90 }, + if color & 4 != 0 { 205 } else { 90 }, + if color & 1 != 0 { 225 } else { 90 }); + + let prim_class = format!("tile_slice{}", ix); + + css = format!("{}\n\ + #{} {{\n\ + fill: {};\n\ + fill-opacity: 0.03;\n\ + stroke-width: 0.8;\n\ + stroke: {};\n\ + }}\n\n", + css, + prim_class, + //rgb, + "none", + rgb); + } + + let output_file = output_dir.join("tilecache.css"); + let mut css_output = File::create(output_file).unwrap(); + css_output.write_all(css.as_bytes()).unwrap(); +} + +fn main() { + let args: Vec<String> = std::env::args().collect(); + + if args.len() != 3 { + println!("Usage: tileview input_dir output_dir"); + println!(" where input_dir is a tile_cache folder inside a wr-capture."); + println!("\nexample: cargo run c:/Users/me/AppData/Local/wr-capture.6/tile_cache/ c:/temp/tilecache/"); + std::process::exit(1); + } + + let input_dir = Path::new(&args[1]); + let output_dir = Path::new(&args[2]); + std::fs::create_dir_all(output_dir).unwrap(); + + let mut svg_width = 100i32; + let mut svg_height = 100i32; + let mut max_slice_index = 0; + + let mut entries: Vec<_> = std::fs::read_dir(input_dir).unwrap() + //.map(|r| r.unwrap()) + .filter_map(|r| r.ok()) + .collect(); + entries.sort_by_key(|dir| dir.path()); + + let mut svg_files: Vec::<String> = Vec::new(); + let mut prev_slices = None; + + for entry in &entries { + if entry.path().is_dir() { + continue; + } + print!("processing {:?}\t", entry.path()); + let f = File::open(entry.path()).unwrap(); + let slices: Vec<Slice> = match ron::de::from_reader(f) { + Ok(data) => { data } + Err(e) => { + println!("ERROR: failed to deserialize {:?}\n{:?}", entry.path(), e); + prev_slices = None; + continue; + } + }; + + let svg = slices_to_svg(&slices, prev_slices, &mut svg_width, &mut svg_height, &mut max_slice_index); + + let mut output_filename = OsString::from(entry.path().file_name().unwrap()); + output_filename.push(".svg"); + svg_files.push(output_filename.to_string_lossy().to_string()); + + output_filename = output_dir.join(output_filename).into_os_string(); + let mut svg_output = File::create(output_filename).unwrap(); + svg_output.write_all(svg.as_bytes()).unwrap(); + + print!("\r"); + prev_slices = Some(slices); + } + + write_html(output_dir, &svg_files); + write_css(output_dir, max_slice_index); + + println!("OK. For now, manually copy tilecache.js to the output folder please. "); +}
new file mode 100644 --- /dev/null +++ b/gfx/wr/tileview/src/tilecache.js @@ -0,0 +1,145 @@ +// current SVG file for scrubbing and playback +var svg_index = 0; + +// double buffered <object>s each holding an SVG file +var backbuffer; +var frontbuffer; + + +// timer for animation +var svg_timer; +var is_playing = false; + +function toggle_play() { + if( is_playing ) { + is_playing = false; + clearInterval(svg_timer); + document.getElementById("text_spacebar").innerHTML = + 'Spacebar to play'; + } else { + is_playing = true; + svg_timer = setInterval(on_tick, 100); + document.getElementById("text_spacebar").innerHTML = + 'Playing (Spacebar to stop)'; + function on_tick() { + if( svg_index + 1 == svg_files.length ) { + toggle_play(); + } else { + go_to_svg(svg_index+1); + } + } + } +} + +function toggle_quadtree() { + var quad_groups = document.getElementsByClassName("svg_quadtree") + var i; + for (i = 0; i < quad_groups.length; i++) { + if( quad_groups[i].style.display == "none" ) + quad_groups[i].style.display = "block"; + else + quad_groups[i].style.display = "none"; + } +} + +// try to block repeated keypressed from causing flickering +// when they land between go_to_svg returning and onload +// firing. +var is_loading = false; + +function go_to_svg(index) { + if( index >= svg_files.length || + index < 0 || + is_loading ) { + return; + } + + is_loading = true; + svg_index = index; + + var slider = document.getElementById('frame_slider'); + // won't recurse thanks to is_loading + slider.value = svg_index; + + backbuffer.onload = function(){ + + document.getElementById("text_frame_counter").innerHTML = + svg_files[svg_index]; + + backbuffer.style.display = ''; + frontbuffer.style.display = 'none'; + + var t = frontbuffer; + frontbuffer = backbuffer; + backbuffer = t; + is_loading = false; + } + backbuffer.setAttribute('data', svg_files[svg_index]); + + // also see https://stackoverflow.com/a/29915275 +} + +function load() { + window.addEventListener('keypress', handle_keyboard_shortcut); + window.addEventListener('keydown', handle_keydown); + + frontbuffer = document.getElementById("svg_container0"); + backbuffer = document.getElementById("svg_container1"); + backbuffer.style.display='none'; + + var slider = document.getElementById('frame_slider'); + slider.oninput = function() { + if( is_playing ) { + toggle_play(); + } + go_to_svg(this.value); + } +} + +function handle_keyboard_shortcut(event) { + switch (event.charCode) { + case 32: // ' ' + toggle_play(); + break; + case 113: // 'q' + toggle_quadtree(); + break; + /* + case 49: // "1" key + document.getElementById("radio1").checked = true; + show_image(1); + break; + case 50: // "2" key + document.getElementById("radio2").checked = true; + show_image(2); + break; + case 100: // "d" key + document.getElementById("differences").click(); + break; + case 112: // "p" key + shift_images(-1); + break; + case 110: // "n" key + shift_images(1); + break; + */ + } +} + +function handle_keydown(event) { + switch (event.keyCode) { + case 37: // left arrow + go_to_svg(svg_index-1); + event.preventDefault(); + break; + case 38: // up arrow + break; + case 39: // right arrow + go_to_svg(svg_index+1); + event.preventDefault(); + break; + case 40: // down arrow + break; + } +} +
--- a/gfx/wr/webrender/src/frame_builder.rs +++ b/gfx/wr/webrender/src/frame_builder.rs @@ -10,16 +10,17 @@ use crate::clip_scroll_tree::{ClipScroll use crate::composite::{CompositorKind, CompositeState}; use crate::debug_render::DebugItem; use crate::gpu_cache::{GpuCache, GpuCacheHandle}; use crate::gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator}; use crate::gpu_types::TransformData; use crate::internal_types::{FastHashMap, PlaneSplitter, SavedTargetIndex}; use crate::picture::{PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex, RecordedDirtyRegion}; use crate::picture::{RetainedTiles, TileCacheInstance, DirtyRegion, SurfaceRenderTasks, SubpixelMode}; +use crate::picture::{TileCacheLogger}; use crate::prim_store::{SpaceMapper, PictureIndex, PrimitiveDebugId, PrimitiveScratchBuffer}; use crate::prim_store::{DeferredResolve, PrimitiveVisibilityMask}; use crate::profiler::{FrameProfileCounters, TextureCacheProfileCounters, ResourceProfileCounters}; use crate::render_backend::{DataStores, FrameStamp, FrameId}; use crate::render_target::{RenderTarget, PictureCacheTarget, TextureCacheRenderTarget}; use crate::render_target::{RenderTargetContext, RenderTargetKind}; use crate::render_task_graph::{RenderTaskId, RenderTaskGraph, RenderTaskGraphCounters}; use crate::render_task_graph::{RenderPassKind, RenderPass}; @@ -235,16 +236,17 @@ impl FrameBuilder { scene_properties: &SceneProperties, transform_palette: &mut TransformPalette, data_stores: &mut DataStores, surfaces: &mut Vec<SurfaceInfo>, scratch: &mut PrimitiveScratchBuffer, debug_flags: DebugFlags, texture_cache_profile: &mut TextureCacheProfileCounters, composite_state: &mut CompositeState, + tile_cache_logger: &mut TileCacheLogger, ) -> Option<RenderTaskId> { profile_scope!("cull"); if scene.prim_store.pictures.is_empty() { return None } scratch.begin_frame(); @@ -406,30 +408,34 @@ impl FrameBuilder { WorldRect::max_rect(), root_spatial_node_index, root_spatial_node_index, ROOT_SURFACE_INDEX, SubpixelMode::Allow, &mut frame_state, &frame_context, scratch, + tile_cache_logger ) .unwrap(); + tile_cache_logger.advance(); + { profile_marker!("PreparePrims"); scene.prim_store.prepare_primitives( &mut prim_list, &pic_context, &mut pic_state, &frame_context, &mut frame_state, data_stores, scratch, + tile_cache_logger, ); } let pic = &mut scene.prim_store.pictures[scene.root_pic_index.0]; pic.restore_context( prim_list, pic_context, pic_state, @@ -460,16 +466,17 @@ impl FrameBuilder { device_origin: DeviceIntPoint, pan: WorldPoint, resource_profile: &mut ResourceProfileCounters, scene_properties: &SceneProperties, data_stores: &mut DataStores, scratch: &mut PrimitiveScratchBuffer, render_task_counters: &mut RenderTaskGraphCounters, debug_flags: DebugFlags, + tile_cache_logger: &mut TileCacheLogger, ) -> Frame { profile_scope!("build"); profile_marker!("BuildFrame"); let mut profile_counters = FrameProfileCounters::new(); profile_counters .total_primitives .set(scene.prim_store.prim_count()); @@ -529,16 +536,17 @@ impl FrameBuilder { scene_properties, &mut transform_palette, data_stores, &mut surfaces, scratch, debug_flags, &mut resource_profile.texture_cache, &mut composite_state, + tile_cache_logger, ); let mut passes; let mut deferred_resolves = vec![]; let mut has_texture_cache_tasks = false; let mut prim_headers = PrimitiveHeaders::new(); {
--- a/gfx/wr/webrender/src/lib.rs +++ b/gfx/wr/webrender/src/lib.rs @@ -216,8 +216,10 @@ pub use crate::renderer::{ GraphicsApiInfo, PipelineInfo, Renderer, RendererError, RendererOptions, RenderResults, RendererStats, SceneBuilderHooks, ThreadListener, ShaderPrecacheFlags, MAX_VERTEX_TEXTURE_WIDTH, }; pub use crate::screen_capture::{AsyncScreenshotHandle, RecordedFrameHandle}; pub use crate::shade::{Shaders, WrShaders}; pub use api as webrender_api; pub use webrender_build::shader::ProgramSourceDigest; +pub use crate::picture::{TileDescriptor, TileId, InvalidationReason, PrimitiveCompareResult}; +pub use crate::picture::{TileNode, TileNodeKind, TileSerializer, TileCacheInstanceSerializer, TileOffset};
--- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -102,16 +102,25 @@ use crate::render_task::{RenderTask, Ren use crate::resource_cache::{ResourceCache, ImageGeneration}; use crate::scene::SceneProperties; use smallvec::SmallVec; use std::{mem, u8, marker, u32}; use std::sync::atomic::{AtomicUsize, Ordering}; use crate::texture_cache::TextureCacheHandle; use crate::util::{TransformedRectKind, MatrixHelpers, MaxRect, scale_factors, VecHelper, RectHelpers}; use crate::filterdata::{FilterDataHandle}; +#[cfg(feature = "capture")] +use ron; + +#[cfg(feature = "capture")] +use std::fs::File; +#[cfg(feature = "capture")] +use std::io::prelude::*; +#[cfg(feature = "capture")] +use std::path::PathBuf; /// Specify whether a surface allows subpixel AA text rendering. #[derive(Debug, Copy, Clone, PartialEq)] pub enum SubpixelMode { /// This surface allows subpixel AA text Allow, /// Subpixel AA text cannot be drawn on this surface Deny, @@ -295,29 +304,31 @@ fn clamp(value: i32, low: i32, high: i32 } fn clampf(value: f32, low: f32, high: f32) -> f32 { value.max(low).min(high) } /// An index into the prims array in a TileDescriptor. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct PrimitiveDependencyIndex(u32); +pub struct PrimitiveDependencyIndex(u32); /// Information about the state of an opacity binding. #[derive(Debug)] pub struct OpacityBindingInfo { /// The current value retrieved from dynamic scene properties. value: f32, /// True if it was changed (or is new) since the last frame build. changed: bool, } /// Information stored in a tile descriptor for an opacity binding. #[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub enum OpacityBinding { Value(f32), Binding(PropertyBindingId), } impl From<PropertyBinding<f32>> for OpacityBinding { fn from(binding: PropertyBinding<f32>) -> OpacityBinding { match binding { @@ -527,35 +538,39 @@ impl TileSurface { } } } /// The result of a primitive dependency comparison. Size is a u8 /// since this is a hot path in the code, and keeping the data small /// is a performance win. #[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] #[repr(u8)] -enum PrimitiveCompareResult { +pub enum PrimitiveCompareResult { /// Primitives match Equal, /// Something in the PrimitiveDescriptor was different Descriptor, /// The clip node content or spatial node changed Clip, /// The value of the transform changed Transform, /// An image dependency was dirty Image, /// The value of an opacity binding changed OpacityBinding, } /// Debugging information about why a tile was invalidated -#[derive(Debug)] -enum InvalidationReason { +#[derive(Debug,Copy,Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum InvalidationReason { /// The fractional offset changed FractionalOffset, /// The background color changed BackgroundColor, /// The opaque state of the backing native surface changed SurfaceOpacityChanged, /// There was no backing texture (evicted or never rendered) NoTexture, @@ -565,16 +580,39 @@ enum InvalidationReason { PrimCount, /// The content of one of the primitives was different Content { /// What changed in the primitive that was different prim_compare_result: PrimitiveCompareResult, }, } +/// A minimal subset of Tile for debug capturing +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TileSerializer { + pub rect: PictureRect, + pub current_descriptor: TileDescriptor, + pub fract_offset: PictureVector2D, + pub id: TileId, + pub root: TileNode, + pub background_color: Option<ColorF>, + pub invalidation_reason: Option<InvalidationReason> +} + +/// A minimal subset of TileCacheInstance for debug capturing +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TileCacheInstanceSerializer { + pub slice: usize, + pub tiles: FastHashMap<TileOffset, TileSerializer>, + pub background_color: Option<ColorF>, + pub fract_offset: PictureVector2D, +} + /// Information about a cached tile. pub struct Tile { /// The current world rect of this tile. pub world_rect: WorldRect, /// The current local rect of this tile. pub rect: PictureRect, /// The local rect of the tile clipped to the overall picture local rect. clipped_rect: PictureRect, @@ -1018,25 +1056,27 @@ impl Tile { self.surface = Some(surface); true } } /// Defines a key that uniquely identifies a primitive instance. #[derive(Debug, Clone)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub struct PrimitiveDescriptor { /// Uniquely identifies the content of the primitive template. prim_uid: ItemUid, /// The origin in world space of this primitive. origin: PointKey, /// The clip rect for this primitive. Included here in /// dependencies since there is no entry in the clip chain /// dependencies for the local clip rect. - prim_clip_rect: RectangleKey, + pub prim_clip_rect: RectangleKey, /// The number of extra dependencies that this primitive has. transform_dep_count: u8, image_dep_count: u8, opacity_binding_dep_count: u8, clip_dep_count: u8, } impl PartialEq for PrimitiveDescriptor { @@ -1148,16 +1188,19 @@ impl<'a, T> CompareHelper<'a, T> where T // Advance the current dependency array by a given amount fn advance_curr(&mut self, count: u8) { self.offset_curr += count as usize; } } /// Uniquely describes the content of this tile, in a way that can be /// (reasonably) efficiently hashed and compared. +#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] pub struct TileDescriptor { /// List of primitive instance unique identifiers. The uid is guaranteed /// to uniquely describe the content of the primitive template, while /// the other parameters describe the clip chain and instance params. pub prims: Vec<PrimitiveDescriptor>, /// List of clip node descriptors. clips: Vec<ItemUid>, @@ -1217,17 +1260,17 @@ impl TileDescriptor { } pt.end_level(); } if !self.images.is_empty() { pt.new_level("images".to_string()); for info in &self.images { pt.new_level(format!("key={:?}", info.key)); - pt.new_level(format!("generation={:?}", info.generation)); + pt.add_item(format!("generation={:?}", info.generation)); pt.end_level(); } pt.end_level(); } if !self.opacity_bindings.is_empty() { pt.new_level("opacity_bindings".to_string()); for opacity_binding in &self.opacity_bindings { @@ -1414,16 +1457,117 @@ impl BackdropInfo { rect: PictureRect::zero(), kind: BackdropKind::Color { color: ColorF::BLACK, }, } } } +#[derive(Clone)] +pub struct TileCacheLoggerSlice { + pub serialized_slice: String, + pub local_to_world_transform: DeviceRect +} + +/// Log tile cache activity whenever anything happens in take_context. +pub struct TileCacheLogger { + /// next write pointer + pub write_index : usize, + /// ron serialization of tile caches; + /// each frame consists of slices, one per take_context call + pub frames: Vec<Vec<TileCacheLoggerSlice>> +} + +impl TileCacheLogger { + pub fn new( + num_frames: usize + ) -> Self { + let mut frames = Vec::with_capacity(num_frames); + let empty_element = Vec::new(); + frames.resize(num_frames, empty_element); + TileCacheLogger { + write_index: 0, + frames + } + } + + pub fn is_enabled(&self) -> bool { + !self.frames.is_empty() + } + + #[cfg(feature = "capture")] + pub fn add(&mut self, serialized_slice: String, local_to_world_transform: DeviceRect) { + if !self.is_enabled() { + return; + } + self.frames[self.write_index].push( + TileCacheLoggerSlice { + serialized_slice, + local_to_world_transform }); + } + + /// see if anything was written in this frame, and if so, + /// advance the write index in a circular way and clear the + /// recorded string. + pub fn advance(&mut self) { + if !self.is_enabled() || self.frames[self.write_index].is_empty() { + return; + } + self.write_index = self.write_index + 1; + if self.write_index >= self.frames.len() { + self.write_index = 0; + } + self.frames[self.write_index] = Vec::new(); + } + + #[cfg(feature = "capture")] + pub fn save_capture( + &self, root: &PathBuf + ) { + if !self.is_enabled() { + return; + } + use std::fs; + + info!("saving tile cache log"); + let path_tile_cache = root.join("tile_cache"); + if !path_tile_cache.is_dir() { + fs::create_dir(&path_tile_cache).unwrap(); + } + + let mut files_written = 0; + for ix in 0..self.frames.len() { + // ...and start with write_index, since that's the oldest entry + // that we're about to overwrite. However when we get to + // save_capture, we've add()ed entries but haven't advance()d yet, + // so the actual oldest entry is write_index + 1 + let index = (self.write_index + 1 + ix) % self.frames.len(); + if self.frames[index].is_empty() { + continue; + } + + let filename = path_tile_cache.join(format!("frame{:05}.ron", files_written)); + files_written = files_written + 1; + let mut output = File::create(filename).unwrap(); + output.write_all(b"[\n").unwrap(); + for item in &self.frames[index] { + 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(item.serialized_slice.as_bytes()).unwrap(); + output.write_all(b"\n),\n").unwrap(); + } + output.write_all(b"]\n").unwrap(); + } + } +} + /// Represents a cache of tiles that make up a picture primitives. pub struct TileCacheInstance { /// Index of the tile cache / slice for this frame builder. It's determined /// by the setup_picture_caching method during flattening, which splits the /// picture tree into multiple slices. It's used as a simple input to the tile /// keys. It does mean we invalidate tiles if a new layer gets inserted / removed /// between display lists - this seems very unlikely to occur on most pages, but /// can be revisited if we ever notice that. @@ -3270,16 +3414,17 @@ impl PicturePrimitive { clipped_prim_bounding_rect: WorldRect, surface_spatial_node_index: SpatialNodeIndex, raster_spatial_node_index: SpatialNodeIndex, parent_surface_index: SurfaceIndex, parent_subpixel_mode: SubpixelMode, frame_state: &mut FrameBuildingState, frame_context: &FrameBuildingContext, scratch: &mut PrimitiveScratchBuffer, + tile_cache_logger: &mut TileCacheLogger, ) -> Option<(PictureContext, PictureState, PrimitiveList)> { if !self.is_visible() { return None; } // Extract the raster and surface spatial nodes from the raster // config, if this picture establishes a surface. Otherwise just // pass in the spatial node indices from the parent context. @@ -3333,16 +3478,17 @@ impl PicturePrimitive { Picture3DContext::In { root_data: Some(_), .. } => { Some(PlaneSplitter::new()) } Picture3DContext::In { root_data: None, .. } => { None } }; + let unclipped = match self.raster_config { Some(ref raster_config) => { let pic_rect = PictureRect::from_untyped(&self.precise_local_rect.to_untyped()); let device_pixel_scale = frame_state .surfaces[raster_config.surface_index.0] .device_pixel_scale; @@ -3921,20 +4067,56 @@ impl PicturePrimitive { port, }); frame_state.render_tasks.add_dependency( frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port, root, ); } + + unclipped } - None => {} + None => DeviceRect::zero() }; + #[cfg(feature = "capture")] + { + if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) { + if let Some(ref tile_cache) = self.tile_cache + { + // extract just the fields that we're interested in + let mut tile_cache_tiny = TileCacheInstanceSerializer { + slice: tile_cache.slice, + tiles: FastHashMap::default(), + background_color: tile_cache.background_color, + fract_offset: tile_cache.fract_offset + }; + for (key, tile) in &tile_cache.tiles { + tile_cache_tiny.tiles.insert(*key, TileSerializer { + rect: tile.rect, + current_descriptor: tile.current_descriptor.clone(), + fract_offset: tile.fract_offset, + id: tile.id, + root: tile.root.clone(), + background_color: tile.background_color, + invalidation_reason: tile.invalidation_reason.clone() + }); + } + let text = ron::ser::to_string_pretty(&tile_cache_tiny, Default::default()).unwrap(); + tile_cache_logger.add(text, unclipped); + } + } + } + #[cfg(not(feature = "capture"))] + { + let _tile_cache_logger = tile_cache_logger; // unused variable fix + let _unclipped = unclipped; + } + let state = PictureState { //TODO: check for MAX_CACHE_SIZE here? map_local_to_pic, map_pic_to_world, map_pic_to_raster, map_raster_to_world, plane_splitter, }; @@ -4677,16 +4859,18 @@ fn get_transform_key( #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] struct PrimitiveComparisonKey { prev_index: PrimitiveDependencyIndex, curr_index: PrimitiveDependencyIndex, } /// Information stored an image dependency #[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] struct ImageDependency { key: ImageKey, generation: ImageGeneration, } /// A helper struct to compare a primitive and all its sub-dependencies. struct PrimitiveComparer<'a> { clip_comparer: CompareHelper<'a, ItemUid>, @@ -4825,46 +5009,56 @@ impl<'a> PrimitiveComparer<'a> { return PrimitiveCompareResult::OpacityBinding; } PrimitiveCompareResult::Equal } } /// Details for a node in a quadtree that tracks dirty rects for a tile. -enum TileNodeKind { +#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum TileNodeKind { Leaf { /// The index buffer of primitives that affected this tile previous frame + #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] prev_indices: Vec<PrimitiveDependencyIndex>, /// The index buffer of primitives that affect this tile on this frame + #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] curr_indices: Vec<PrimitiveDependencyIndex>, /// A bitset of which of the last 64 frames have been dirty for this leaf. + #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] dirty_tracker: u64, /// The number of frames since this node split or merged. + #[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))] frames_since_modified: usize, }, Node { /// The four children of this node children: Vec<TileNode>, }, } /// The kind of modification that a tile wants to do #[derive(Copy, Clone, PartialEq, Debug)] enum TileModification { Split, Merge, } /// A node in the dirty rect tracking quadtree. -struct TileNode { +#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct TileNode { /// Leaf or internal node - kind: TileNodeKind, + pub kind: TileNodeKind, /// Rect of this node in the same space as the tile cache picture - rect: PictureRect, + pub rect: PictureRect, } impl TileNode { /// Construct a new leaf node, with the given primitive dependency index buffer fn new_leaf(curr_indices: Vec<PrimitiveDependencyIndex>) -> Self { TileNode { kind: TileNodeKind::Leaf { prev_indices: Vec::new(),
--- a/gfx/wr/webrender/src/prim_store/mod.rs +++ b/gfx/wr/webrender/src/prim_store/mod.rs @@ -22,17 +22,17 @@ use crate::frame_builder::{FrameBuilding use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState}; use crate::glyph_rasterizer::GlyphKey; use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks}; use crate::gpu_types::{BrushFlags}; use crate::image::{Repetition}; use crate::intern; use crate::internal_types::PlaneSplitAnchor; use malloc_size_of::MallocSizeOf; -use crate::picture::{PictureCompositeMode, PicturePrimitive, ClusterFlags}; +use crate::picture::{PictureCompositeMode, PicturePrimitive, ClusterFlags, TileCacheLogger}; use crate::picture::{PrimitiveList, RecordedDirtyRegion, SurfaceIndex, RetainedTiles, RasterConfig}; use crate::prim_store::backdrop::BackdropDataHandle; use crate::prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle}; use crate::prim_store::gradient::{GRADIENT_FP_STOPS, GradientCacheKey, GradientStopKey}; use crate::prim_store::gradient::{LinearGradientPrimitive, LinearGradientDataHandle, RadialGradientDataHandle}; use crate::prim_store::image::{ImageDataHandle, ImageInstance, VisibleImageTile, YuvImageDataHandle}; use crate::prim_store::line_dec::LineDecorationDataHandle; use crate::prim_store::picture::PictureDataHandle; @@ -2651,16 +2651,17 @@ impl PrimitiveStore { prim_spatial_node_index: SpatialNodeIndex, pic_context: &PictureContext, pic_state: &mut PictureState, frame_context: &FrameBuildingContext, frame_state: &mut FrameBuildingState, plane_split_anchor: PlaneSplitAnchor, data_stores: &mut DataStores, scratch: &mut PrimitiveScratchBuffer, + tile_cache_log: &mut TileCacheLogger, ) -> bool { // If we have dependencies, we need to prepare them first, in order // to know the actual rect of this primitive. // For example, scrolling may affect the location of an item in // local space, which may force us to render this item on a larger // picture target, if being composited. let pic_info = { match prim_instance.kind { @@ -2676,16 +2677,17 @@ impl PrimitiveStore { clipped_prim_bounding_rect, pic_context.surface_spatial_node_index, pic_context.raster_spatial_node_index, pic_context.surface_index, pic_context.subpixel_mode, frame_state, frame_context, scratch, + tile_cache_log, ) { Some(info) => Some(info), None => { if prim_instance.is_chased() { println!("\tculled for carrying an invisible composite filter"); } prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; @@ -2719,16 +2721,17 @@ impl PrimitiveStore { self.prepare_primitives( &mut prim_list, &pic_context_for_children, &mut pic_state_for_children, frame_context, frame_state, data_stores, scratch, + tile_cache_log, ); // Restore the dependencies (borrow check dance) self.pictures[pic_context_for_children.pic_index.0] .restore_context( prim_list, pic_context_for_children, pic_state_for_children, @@ -2784,16 +2787,17 @@ impl PrimitiveStore { &mut self, prim_list: &mut PrimitiveList, pic_context: &PictureContext, pic_state: &mut PictureState, frame_context: &FrameBuildingContext, frame_state: &mut FrameBuildingState, data_stores: &mut DataStores, scratch: &mut PrimitiveScratchBuffer, + tile_cache_log: &mut TileCacheLogger, ) { for (cluster_index, cluster) in prim_list.clusters.iter_mut().enumerate() { pic_state.map_local_to_pic.set_target_spatial_node( cluster.spatial_node_index, frame_context.clip_scroll_tree, ); for (prim_instance_index, prim_instance) in cluster.prim_instances.iter_mut().enumerate() { @@ -2829,16 +2833,17 @@ impl PrimitiveStore { cluster.spatial_node_index, pic_context, pic_state, frame_context, frame_state, plane_split_anchor, data_stores, scratch, + tile_cache_log, ) { frame_state.profile_counters.visible_primitives.inc(); } } } } /// Prepare an interned primitive for rendering, by requesting
--- a/gfx/wr/webrender/src/render_backend.rs +++ b/gfx/wr/webrender/src/render_backend.rs @@ -26,17 +26,17 @@ use crate::composite::{CompositorKind, C use crate::debug_server; use crate::frame_builder::{FrameBuilder, FrameBuilderConfig}; use crate::glyph_rasterizer::{FontInstance}; use crate::gpu_cache::GpuCache; use crate::hit_test::{HitTest, HitTester}; use crate::intern::DataStore; use crate::internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use crate::picture::RetainedTiles; +use crate::picture::{RetainedTiles, TileCacheLogger}; use crate::prim_store::{PrimitiveScratchBuffer, PrimitiveInstance}; use crate::prim_store::{PrimitiveInstanceKind, PrimTemplateCommonData}; use crate::prim_store::interned::*; use crate::profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters}; use crate::record::ApiRecordingReceiver; use crate::record::LogRecorder; use crate::render_task_graph::RenderTaskGraphCounters; use crate::renderer::{AsyncPropertySampler, PipelineInfo}; @@ -535,16 +535,17 @@ impl Document { } fn build_frame( &mut self, resource_cache: &mut ResourceCache, gpu_cache: &mut GpuCache, resource_profile: &mut ResourceProfileCounters, debug_flags: DebugFlags, + tile_cache_logger: &mut TileCacheLogger, ) -> RenderedDocument { let accumulated_scale_factor = self.view.accumulated_scale_factor(); let pan = self.view.pan.to_f32() / accumulated_scale_factor; // Advance to the next frame. self.stamp.advance(); assert!(self.stamp.frame_id() != FrameId::INVALID, @@ -561,16 +562,17 @@ impl Document { self.view.device_rect.origin, pan, resource_profile, &self.dynamic_properties, &mut self.data_stores, &mut self.scratch, &mut self.render_task_counters, debug_flags, + tile_cache_logger, ); self.hit_tester = Some(self.scene.create_hit_tester(&self.data_stores.clip)); frame }; self.frame_is_valid = true; self.hit_tester_is_valid = true; @@ -707,16 +709,17 @@ pub struct RenderBackend { resource_cache: ResourceCache, frame_config: FrameBuilderConfig, documents: FastHashMap<DocumentId, Document>, notifier: Box<dyn RenderNotifier>, recorder: Option<Box<dyn ApiRecordingReceiver>>, logrecorder: Option<Box<LogRecorder>>, + tile_cache_logger: TileCacheLogger, sampler: Option<Box<dyn AsyncPropertySampler + Send>>, size_of_ops: Option<MallocSizeOfOps>, debug_flags: DebugFlags, namespace_alloc_by_client: bool, recycler: Recycler, } @@ -749,16 +752,17 @@ impl RenderBackend { default_device_pixel_ratio, resource_cache, gpu_cache: GpuCache::new(), frame_config, documents: FastHashMap::default(), notifier, recorder, logrecorder: None, + tile_cache_logger: TileCacheLogger::new(500usize), sampler, size_of_ops, debug_flags, namespace_alloc_by_client, recycler: Recycler::new(), } } @@ -1524,16 +1528,17 @@ impl RenderBackend { let _timer = profile_counters.total_time.timer(); let frame_build_start_time = precise_time_ns(); let rendered_document = doc.build_frame( &mut self.resource_cache, &mut self.gpu_cache, &mut profile_counters.resources, self.debug_flags, + &mut self.tile_cache_logger, ); debug!("generated frame for document {:?} with {} passes", document_id, rendered_document.frame.passes.len()); let msg = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); self.result_tx.send(msg).unwrap(); @@ -1701,16 +1706,17 @@ impl RenderBackend { for (&id, doc) in &mut self.documents { debug!("\tdocument {:?}", id); if config.bits.contains(CaptureBits::FRAME) { let rendered_document = doc.build_frame( &mut self.resource_cache, &mut self.gpu_cache, &mut profile_counters.resources, self.debug_flags, + &mut self.tile_cache_logger, ); // After we rendered the frames, there are pending updates to both // GPU cache and resources. Instead of serializing them, we are going to make sure // they are applied on the `Renderer` side. let msg_update_gpu_cache = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); self.result_tx.send(msg_update_gpu_cache).unwrap(); //TODO: write down doc's pipeline info? // it has `pipeline_epoch_map`, @@ -1748,16 +1754,21 @@ impl RenderBackend { } debug!("\tscene builder"); self.scene_tx.send(SceneBuilderRequest::SaveScene(config.clone())).unwrap(); debug!("\tresource cache"); let (resources, deferred) = self.resource_cache.save_capture(&config.root); + if config.bits.contains(CaptureBits::TILE_CACHE) { + debug!("\ttile cache"); + self.tile_cache_logger.save_capture(&config.root); + } + info!("\tbackend"); let backend = PlainRenderBackend { default_device_pixel_ratio: self.default_device_pixel_ratio, frame_config: self.frame_config.clone(), documents: self.documents .iter() .map(|(id, doc)| (*id, doc.view.clone())) .collect(),
--- a/gfx/wr/webrender_api/src/api.rs +++ b/gfx/wr/webrender_api/src/api.rs @@ -905,16 +905,18 @@ 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; + /// + const TILE_CACHE = 0x4; } } bitflags!{ /// Mask for clearing caches in debug commands. #[derive(Deserialize, Serialize)] pub struct ClearCache: u8 { /// @@ -1406,16 +1408,18 @@ bitflags! { /// see when glyphs are re-rasterized. const GLYPH_FLASHING = 1 << 25; /// The profiler only displays information that is out of the ordinary. const SMART_PROFILER = 1 << 26; /// Dynamically control whether picture caching is enabled. const DISABLE_PICTURE_CACHING = 1 << 27; /// If set, dump picture cache invalidation debug to console. const INVALIDATION_DBG = 1 << 28; + /// Log tile cache to memory for later saving as part of wr-capture + const TILE_CACHE_LOGGING_DBG = 1 << 29; } } /// The main entry point to interact with WebRender. pub struct RenderApi { api_sender: MsgSender<ApiMsg>, payload_sender: PayloadSender, namespace_id: IdNamespace,
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -679,16 +679,17 @@ pref("gfx.webrender.debug.epochs", false pref("gfx.webrender.debug.compact-profiler", false); pref("gfx.webrender.debug.smart-profiler", false); pref("gfx.webrender.debug.echo-driver-messages", false); pref("gfx.webrender.debug.new-frame-indicator", false); pref("gfx.webrender.debug.new-scene-indicator", false); pref("gfx.webrender.debug.show-overdraw", false); pref("gfx.webrender.debug.slow-frame-indicator", false); pref("gfx.webrender.debug.picture-caching", false); +pref("gfx.webrender.debug.tile-cache-logging", false); pref("gfx.webrender.debug.primitives", false); pref("gfx.webrender.debug.small-screen", false); pref("gfx.webrender.debug.obscure-images", false); pref("gfx.webrender.debug.glyph-flashing", false); pref("accessibility.warn_on_browsewithcaret", true); pref("accessibility.browsewithcaret_shortcut.enabled", true);