diff --git a/gfx/wr/webrender/src/batch.rs b/gfx/wr/webrender/src/batch.rs index e8f8f5dd86ce..90672ed787d7 100644 --- a/gfx/wr/webrender/src/batch.rs +++ b/gfx/wr/webrender/src/batch.rs @@ -21,13 +21,13 @@ use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, VECS_PER_SEGM use crate::prim_store::{recompute_snap_offsets}; use crate::prim_store::image::ImageSource; use crate::render_backend::DataStores; -use crate::render_task::{RenderTaskAddress, RenderTaskId, RenderTaskGraph}; +use crate::render_task_graph::{RenderTaskId, RenderTaskGraph, RenderTargetContext}; +use crate::render_task::RenderTaskAddress; use crate::renderer::{BlendMode, ImageBufferKind, ShaderColorMode}; use crate::renderer::{BLOCKS_PER_UV_RECT, MAX_VERTEX_TEXTURE_WIDTH}; use crate::resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties}; use smallvec::SmallVec; use std::{f32, i32, usize}; -use crate::tiling::{RenderTargetContext}; use crate::util::{project_rect, TransformedRectKind}; // Special sentinel value recognized by the shader. It is considered to be diff --git a/gfx/wr/webrender/src/display_list_flattener.rs b/gfx/wr/webrender/src/display_list_flattener.rs index a708d4057382..d503d150a6c9 100644 --- a/gfx/wr/webrender/src/display_list_flattener.rs +++ b/gfx/wr/webrender/src/display_list_flattener.rs @@ -42,7 +42,7 @@ use crate::spatial_node::{StickyFrameInfo, ScrollFrameKind}; use std::{f32, mem, usize, ops}; use std::collections::vec_deque::VecDeque; use std::sync::Arc; -use crate::tiling::{CompositeOps}; +use crate::render_task_graph::{CompositeOps}; use crate::util::{MaxRect, VecHelper}; use crate::filterdata::{SFilterDataComponent, SFilterData, SFilterDataKey}; diff --git a/gfx/wr/webrender/src/frame_builder.rs b/gfx/wr/webrender/src/frame_builder.rs index 98a82bd074b7..c8385067cbef 100644 --- a/gfx/wr/webrender/src/frame_builder.rs +++ b/gfx/wr/webrender/src/frame_builder.rs @@ -21,14 +21,15 @@ use crate::prim_store::{PrimitiveStore, SpaceMapper, PictureIndex, PrimitiveDebu use crate::prim_store::{PrimitiveStoreStats}; use crate::profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters}; use crate::render_backend::{DataStores, FrameStamp}; -use crate::render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskGraph, RenderTaskGraphCounters}; +use crate::render_task_graph::{RenderTaskId, RenderTaskGraph, RenderTaskGraphCounters}; +use crate::render_task::{RenderTask, RenderTaskLocation}; use crate::resource_cache::{ResourceCache}; use crate::scene::{ScenePipeline, SceneProperties}; use crate::scene_builder::DocumentStats; use crate::segment::SegmentBuilder; use std::{f32, mem}; use std::sync::Arc; -use crate::tiling::{Frame, RenderPassKind, RenderTargetContext}; +use crate::render_task_graph::{Frame, RenderPassKind, RenderTargetContext}; use crate::util::MaxRect; @@ -99,7 +100,7 @@ impl FrameGlobalResources { } } -/// A builder structure for `tiling::Frame` +/// A builder structure for `render_task_graph::Frame` #[cfg_attr(feature = "capture", derive(Serialize))] pub struct FrameBuilder { output_rect: DeviceIntRect, diff --git a/gfx/wr/webrender/src/glyph_rasterizer/mod.rs b/gfx/wr/webrender/src/glyph_rasterizer/mod.rs index d2524f7857bf..864f36f974f4 100644 --- a/gfx/wr/webrender/src/glyph_rasterizer/mod.rs +++ b/gfx/wr/webrender/src/glyph_rasterizer/mod.rs @@ -15,7 +15,8 @@ use crate::glyph_cache::{GlyphCache, CachedGlyphInfo, GlyphCacheEntry}; use crate::resource_cache::CachedImageData; use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction}; use crate::gpu_cache::GpuCache; -use crate::render_task::{RenderTaskGraph, RenderTaskCache}; +use crate::render_task_graph::RenderTaskGraph; +use crate::render_task::RenderTaskCache; use crate::profiler::TextureCacheProfileCounters; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use rayon::ThreadPool; @@ -1018,7 +1019,8 @@ mod test_glyph_rasterizer { use crate::texture_cache::TextureCache; use crate::glyph_cache::GlyphCache; use crate::gpu_cache::GpuCache; - use crate::render_task::{RenderTaskCache, RenderTaskGraph, RenderTaskGraphCounters}; + use crate::render_task::RenderTaskCache; + use crate::render_task_graph::{RenderTaskGraph, RenderTaskGraphCounters}; use crate::profiler::TextureCacheProfileCounters; use api::{FontKey, FontInstanceKey, FontTemplate, FontRenderMode, IdNamespace, ColorU}; diff --git a/gfx/wr/webrender/src/internal_types.rs b/gfx/wr/webrender/src/internal_types.rs index 16edf0f39743..18b81c9ffda6 100644 --- a/gfx/wr/webrender/src/internal_types.rs +++ b/gfx/wr/webrender/src/internal_types.rs @@ -24,7 +24,7 @@ use std::sync::Arc; use crate::capture::{CaptureConfig, ExternalCaptureImage}; #[cfg(feature = "replay")] use crate::capture::PlainExternalImage; -use crate::tiling; +use crate::render_task_graph; pub type FastHashMap = HashMap>; pub type FastHashSet = HashSet>; @@ -473,9 +473,9 @@ impl TextureUpdateList { } } -/// Wraps a tiling::Frame, but conceptually could hold more information +/// Wraps a render_task_graph::Frame, but conceptually could hold more information pub struct RenderedDocument { - pub frame: tiling::Frame, + pub frame: render_task_graph::Frame, pub is_new_scene: bool, } diff --git a/gfx/wr/webrender/src/lib.rs b/gfx/wr/webrender/src/lib.rs index c87754c5ae05..74694da3c39e 100644 --- a/gfx/wr/webrender/src/lib.rs +++ b/gfx/wr/webrender/src/lib.rs @@ -108,6 +108,7 @@ mod prim_store; mod print_tree; mod record; mod render_backend; +mod render_task_graph; mod render_task; mod renderer; mod resource_cache; @@ -120,7 +121,6 @@ mod spatial_node; mod storage; mod texture_allocator; mod texture_cache; -mod tiling; mod util; mod shader_source { diff --git a/gfx/wr/webrender/src/picture.rs b/gfx/wr/webrender/src/picture.rs index 71932b44842b..3eb2333a2e84 100644 --- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -28,8 +28,9 @@ use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer, RectangleKey}; use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex}; use crate::print_tree::PrintTreePrinter; use crate::render_backend::DataStores; +use crate::render_task_graph::RenderTaskId; use crate::render_task::{ClearMode, RenderTargetKind, RenderTask}; -use crate::render_task::{RenderTaskId, RenderTaskLocation, BlurTaskCache}; +use crate::render_task::{RenderTaskLocation, BlurTaskCache}; use crate::resource_cache::ResourceCache; use crate::scene::SceneProperties; use crate::scene_builder::Interners; diff --git a/gfx/wr/webrender/src/prim_store/mod.rs b/gfx/wr/webrender/src/prim_store/mod.rs index 0f5d45a773b5..dd4258bfd3f0 100644 --- a/gfx/wr/webrender/src/prim_store/mod.rs +++ b/gfx/wr/webrender/src/prim_store/mod.rs @@ -39,8 +39,9 @@ use crate::prim_store::text_run::{TextRunDataHandle, TextRunPrimitive}; #[cfg(debug_assertions)] use crate::render_backend::{FrameId}; use crate::render_backend::DataStores; +use crate::render_task_graph::RenderTaskId; use crate::render_task::{RenderTask, RenderTaskCacheKey, to_cache_size}; -use crate::render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle}; +use crate::render_task::{RenderTaskCacheKeyKind, RenderTaskCacheEntryHandle}; use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH}; use crate::resource_cache::{ImageProperties, ImageRequest}; use crate::scene::SceneProperties; diff --git a/gfx/wr/webrender/src/prim_store/text_run.rs b/gfx/wr/webrender/src/prim_store/text_run.rs index 9b211640d892..9eb0100233bd 100644 --- a/gfx/wr/webrender/src/prim_store/text_run.rs +++ b/gfx/wr/webrender/src/prim_store/text_run.rs @@ -13,7 +13,7 @@ use crate::internal_types::LayoutPrimitiveInfo; use crate::picture::{SubpixelMode, SurfaceInfo}; use crate::prim_store::{PrimitiveOpacity, PrimitiveSceneData, PrimitiveScratchBuffer}; use crate::prim_store::{PrimitiveStore, PrimKeyCommonData, PrimTemplateCommonData}; -use crate::render_task::{RenderTaskGraph}; +use crate::render_task_graph::RenderTaskGraph; use crate::renderer::{MAX_VERTEX_TEXTURE_WIDTH}; use crate::resource_cache::{ResourceCache}; use crate::util::{MatrixHelpers}; diff --git a/gfx/wr/webrender/src/render_backend.rs b/gfx/wr/webrender/src/render_backend.rs index 1f401029ac17..9c647d9da3e8 100644 --- a/gfx/wr/webrender/src/render_backend.rs +++ b/gfx/wr/webrender/src/render_backend.rs @@ -38,7 +38,7 @@ use crate::prim_store::{PrimitiveInstanceKind, PrimTemplateCommonData}; use crate::prim_store::interned::*; use crate::profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters}; use crate::record::ApiRecordingReceiver; -use crate::render_task::RenderTaskGraphCounters; +use crate::render_task_graph::RenderTaskGraphCounters; use crate::renderer::{AsyncPropertySampler, PipelineInfo}; use crate::resource_cache::ResourceCache; #[cfg(feature = "replay")] @@ -59,7 +59,7 @@ use std::sync::mpsc::{channel, Sender, Receiver}; use std::time::{UNIX_EPOCH, SystemTime}; use std::u32; #[cfg(feature = "replay")] -use crate::tiling::Frame; +use crate::render_task_graph::Frame; use time::precise_time_ns; use crate::util::{Recycler, VecHelper, drain_filter}; @@ -1707,7 +1707,7 @@ impl RenderBackend { ) -> DebugOutput { use std::fs; use crate::capture::CaptureConfig; - use crate::render_task::dump_render_tasks_as_svg; + use crate::render_task_graph::dump_render_tasks_as_svg; debug!("capture: saving {:?}", root); if !root.is_dir() { diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs index 7a5eef5e87d9..18770aee7585 100644 --- a/gfx/wr/webrender/src/render_task.rs +++ b/gfx/wr/webrender/src/render_task.rs @@ -22,12 +22,10 @@ use crate::prim_store::gradient::{GRADIENT_FP_STOPS, GradientCacheKey, GradientS use crate::prim_store::line_dec::LineDecorationCacheKey; #[cfg(feature = "debugger")] use crate::print_tree::{PrintTreePrinter}; -use crate::render_backend::FrameId; use crate::resource_cache::{CacheItem, ResourceCache}; -use std::{ops, mem, usize, f32, i32, u32}; +use std::{mem, usize, f32, i32, u32}; use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction}; -use crate::tiling::{RenderPass, RenderTargetIndex}; -use std::io; +use crate::render_task_graph::{RenderTaskGraph, RenderTargetIndex, RenderTaskId}; const RENDER_TASK_SIZE_SANITY_CHECK: i32 = 16000; @@ -52,401 +50,12 @@ pub enum RenderTargetKind { Alpha, // R8 } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -#[cfg_attr(feature = "capture", derive(Serialize))] -#[cfg_attr(feature = "replay", derive(Deserialize))] -pub struct RenderTaskId { - pub index: u32, - - #[cfg(debug_assertions)] - #[cfg_attr(feature = "replay", serde(default = "FrameId::first"))] - frame_id: FrameId, -} - -impl RenderTaskId { - pub const INVALID: RenderTaskId = RenderTaskId { - index: u32::MAX, - #[cfg(debug_assertions)] - frame_id: FrameId::INVALID, - }; -} - #[derive(Debug, Copy, Clone, PartialEq)] #[repr(C)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct RenderTaskAddress(pub u16); -#[cfg_attr(feature = "capture", derive(Serialize))] -#[cfg_attr(feature = "replay", derive(Deserialize))] -pub struct RenderTaskGraph { - pub tasks: Vec, - pub task_data: Vec, - /// Tasks that don't have dependencies, and that may be shared between - /// picture tasks. - /// - /// We render these unconditionally before-rendering the rest of the tree. - pub cacheable_render_tasks: Vec, - next_saved: SavedTargetIndex, - frame_id: FrameId, -} - -#[derive(Debug)] -pub struct RenderTaskGraphCounters { - tasks_len: usize, - task_data_len: usize, - cacheable_render_tasks_len: usize, -} - -impl RenderTaskGraphCounters { - pub fn new() -> Self { - RenderTaskGraphCounters { - tasks_len: 0, - task_data_len: 0, - cacheable_render_tasks_len: 0, - } - } -} - -impl RenderTaskGraph { - pub fn new(frame_id: FrameId, counters: &RenderTaskGraphCounters) -> Self { - // Preallocate a little more than what we needed in the previous frame so that small variations - // in the number of items don't cause us to constantly reallocate. - let extra_items = 8; - RenderTaskGraph { - tasks: Vec::with_capacity(counters.tasks_len + extra_items), - task_data: Vec::with_capacity(counters.task_data_len + extra_items), - cacheable_render_tasks: Vec::with_capacity(counters.cacheable_render_tasks_len + extra_items), - next_saved: SavedTargetIndex(0), - frame_id, - } - } - - pub fn counters(&self) -> RenderTaskGraphCounters { - RenderTaskGraphCounters { - tasks_len: self.tasks.len(), - task_data_len: self.task_data.len(), - cacheable_render_tasks_len: self.cacheable_render_tasks.len(), - } - } - - pub fn add(&mut self, task: RenderTask) -> RenderTaskId { - let index = self.tasks.len() as _; - self.tasks.push(task); - RenderTaskId { - index, - #[cfg(debug_assertions)] - frame_id: self.frame_id, - } - } - - /// Express a render task dependency between a parent and child task. - /// This is used to assign tasks to render passes. - pub fn add_dependency( - &mut self, - parent_id: RenderTaskId, - child_id: RenderTaskId, - ) { - let parent = &mut self[parent_id]; - parent.children.push(child_id); - } - - /// Assign this frame's render tasks to render passes ordered so that passes appear - /// earlier than the ones that depend on them. - pub fn generate_passes( - &mut self, - main_render_task: Option, - screen_size: DeviceIntSize, - gpu_supports_fast_clears: bool, - ) -> Vec { - let mut passes = Vec::new(); - - if !self.cacheable_render_tasks.is_empty() { - self.generate_passes_impl( - &self.cacheable_render_tasks[..], - screen_size, - gpu_supports_fast_clears, - false, - &mut passes, - ); - } - - if let Some(main_task) = main_render_task { - self.generate_passes_impl( - &[main_task], - screen_size, - gpu_supports_fast_clears, - true, - &mut passes, - ); - } - - - self.resolve_target_conflicts(&mut passes); - - passes - } - - /// Assign the render tasks from the tree rooted at root_task to render passes and - /// append them to the `passes` vector so that the passes that we depend on end up - /// _earlier_ in the pass list. - fn generate_passes_impl( - &self, - root_tasks: &[RenderTaskId], - screen_size: DeviceIntSize, - gpu_supports_fast_clears: bool, - for_main_framebuffer: bool, - passes: &mut Vec, - ) { - // We recursively visit tasks from the roots (main and cached render tasks), to figure out - // which ones affect the frame and which passes they should be assigned to. - // - // We track the maximum depth of each task (how far it is from the roots) as well as the total - // maximum depth of the graph to determine each tasks' pass index. In a nutshell, depth 0 is - // for the last render pass (for example the main framebuffer), while the highest depth - // corresponds to the first pass. - - fn assign_task_depth( - tasks: &[RenderTask], - task_id: RenderTaskId, - task_depth: i32, - task_max_depths: &mut [i32], - max_depth: &mut i32, - ) { - *max_depth = std::cmp::max(*max_depth, task_depth); - - let task_max_depth = &mut task_max_depths[task_id.index as usize]; - if task_depth > *task_max_depth { - *task_max_depth = task_depth; - } else { - // If this task has already been processed at a larger depth, - // there is no need to process it again. - return; - } - - let task = &tasks[task_id.index as usize]; - for child in &task.children { - assign_task_depth( - tasks, - *child, - task_depth + 1, - task_max_depths, - max_depth, - ); - } - } - - // The maximum depth of each task. Values that are still equal to -1 after recursively visiting - // the nodes correspond to tasks that don't contribute to the frame. - let mut task_max_depths = vec![-1; self.tasks.len()]; - let mut max_depth = 0; - - for root_task in root_tasks { - assign_task_depth( - &self.tasks, - *root_task, - 0, - &mut task_max_depths, - &mut max_depth, - ); - } - - let offset = passes.len(); - - passes.reserve(max_depth as usize + 1); - for _ in 0..max_depth { - passes.push(RenderPass::new_off_screen(screen_size, gpu_supports_fast_clears)); - } - - if for_main_framebuffer { - passes.push(RenderPass::new_main_framebuffer(screen_size, gpu_supports_fast_clears)); - } else { - passes.push(RenderPass::new_off_screen(screen_size, gpu_supports_fast_clears)); - } - - // Assign tasks to their render passes. - for task_index in 0..self.tasks.len() { - if task_max_depths[task_index] < 0 { - // The task wasn't visited, it means it doesn't contribute to this frame. - continue; - } - let pass_index = offset + (max_depth - task_max_depths[task_index]) as usize; - let task_id = RenderTaskId { - index: task_index as u32, - #[cfg(debug_assertions)] - frame_id: self.frame_id, - }; - let task = &self.tasks[task_index]; - passes[pass_index as usize].add_render_task( - task_id, - task.get_dynamic_size(), - task.target_kind(), - &task.location, - ); - } - } - - /// Resolve conflicts between the generated passes and the limitiations of our target - /// allocation scheme. - /// - /// The render task graph operates with a ping-pong target allocation scheme where - /// a set of targets is written to by even passes and a different set of targets is - /// written to by odd passes. - /// Since tasks cannot read and write the same target, we can run into issues if a - /// task pass in N + 2 reads the result of a task in pass N. - /// To avoid such cases have to insert blit tasks to copy the content of the task - /// into pass N + 1 which is readable by pass N + 2. - /// - /// In addition, allocated rects of pass N are currently not tracked and can be - /// overwritten by allocations in later passes on the same target, unless the task - /// has been marked for saving, which perserves the allocated rect until the end of - /// the frame. This is a big hammer, hopefully we won't need to mark many passes - /// for saving. A better solution would be to track allocations through the entire - /// graph, there is a prototype of that in https://github.com/nical/toy-render-graph/ - fn resolve_target_conflicts(&mut self, passes: &mut [RenderPass]) { - // Keep track of blit tasks we inserted to avoid adding several blits for the same - // task. - let mut task_redirects = vec![None; self.tasks.len()]; - - let mut task_passes = vec![-1; self.tasks.len()]; - for pass_index in 0..passes.len() { - for task in &passes[pass_index].tasks { - task_passes[task.index as usize] = pass_index as i32; - } - } - - for task_index in 0..self.tasks.len() { - if task_passes[task_index] < 0 { - // The task doesn't contribute to this frame. - continue; - } - - let pass_index = task_passes[task_index]; - - // Go through each dependency and check whether they belong - // to a pass that uses the same targets and/or are more than - // one pass behind. - for nth_child in 0..self.tasks[task_index].children.len() { - let child_task_index = self.tasks[task_index].children[nth_child].index as usize; - let child_pass_index = task_passes[child_task_index]; - - if child_pass_index == pass_index - 1 { - // This should be the most common case. - continue; - } - - // TODO: Picture tasks don't support having their dependency tasks redirected. - // Pictures store their respective render task(s) on their SurfaceInfo. - // We cannot blit the picture task here because we would need to update the - // surface's render tasks, but we don't have access to that info here. - // Also a surface may be expecting a picture task and not a blit task, so - // even if we could update the surface's render task(s), it might cause other issues. - // For now we mark the task to be saved rather than trying to redirect to a blit task. - let task_is_picture = if let RenderTaskKind::Picture(..) = self.tasks[task_index].kind { - true - } else { - false - }; - - if child_pass_index % 2 != pass_index % 2 || task_is_picture { - // The tasks and its dependency aren't on the same targets, - // but the dependency needs to be kept alive. - self.tasks[child_task_index].mark_for_saving(); - continue; - } - - if let Some(blit_id) = task_redirects[child_task_index] { - // We already resolved a similar conflict with a blit task, - // reuse the same blit instead of creating a new one. - self.tasks[task_index].children[nth_child] = blit_id; - - // Mark for saving if the blit is more than pass appart from - // our task. - if child_pass_index < pass_index - 2 { - self.tasks[blit_id.index as usize].mark_for_saving(); - } - - continue; - } - - // Our dependency is an even number of passes behind, need - // to insert a blit to ensure we don't read and write from - // the same target. - - let child_task_id = RenderTaskId { - index: child_task_index as u32, - #[cfg(debug_assertions)] - frame_id: self.frame_id, - }; - - let mut blit = RenderTask::new_blit( - self.tasks[child_task_index].location.size(), - BlitSource::RenderTask { task_id: child_task_id }, - ); - - // Mark for saving if the blit is more than pass appart from - // our task. - if child_pass_index < pass_index - 2 { - blit.mark_for_saving(); - } - - let blit_id = RenderTaskId { - index: self.tasks.len() as u32, - #[cfg(debug_assertions)] - frame_id: self.frame_id, - }; - - self.tasks.push(blit); - - passes[child_pass_index as usize + 1].tasks.push(blit_id); - - self.tasks[task_index].children[nth_child] = blit_id; - task_redirects[child_task_index] = Some(blit_id); - } - } - } - - pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress { - #[cfg(all(debug_assertions, not(feature = "replay")))] - debug_assert_eq!(self.frame_id, id.frame_id); - RenderTaskAddress(id.index as u16) - } - - pub fn write_task_data(&mut self) { - for task in &self.tasks { - self.task_data.push(task.write_task_data()); - } - } - - pub fn save_target(&mut self) -> SavedTargetIndex { - let id = self.next_saved; - self.next_saved.0 += 1; - id - } - - #[cfg(debug_assertions)] - pub fn frame_id(&self) -> FrameId { - self.frame_id - } -} - -impl ops::Index for RenderTaskGraph { - type Output = RenderTask; - fn index(&self, id: RenderTaskId) -> &RenderTask { - #[cfg(all(debug_assertions, not(feature = "replay")))] - debug_assert_eq!(self.frame_id, id.frame_id); - &self.tasks[id.index as usize] - } -} - -impl ops::IndexMut for RenderTaskGraph { - fn index_mut(&mut self, id: RenderTaskId) -> &mut RenderTask { - #[cfg(all(debug_assertions, not(feature = "replay")))] - debug_assert_eq!(self.frame_id, id.frame_id); - &mut self.tasks[id.index as usize] - } -} - /// Identifies the output buffer location for a given `RenderTask`. #[derive(Clone, Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] @@ -2210,358 +1819,3 @@ 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: &RenderTaskGraph, - 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 saved = if task.saved_index.is_some() { " (Saved)" } else { "" }; - let label = text(tx, ty, format!("{}{}", task.kind.as_str(), saved)); - 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 dyn 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(); - } -} - -#[cfg(test)] -use euclid::{size2, rect}; - -#[cfg(test)] -fn dyn_location(w: i32, h: i32) -> RenderTaskLocation { - RenderTaskLocation::Dynamic(None, size2(w, h)) -} - - -#[test] -fn diamond_task_graph() { - // A simple diamon shaped task graph. - // - // [b1] - // / \ - // [a] [main_pic] - // \ / - // [b2] - - let color = RenderTargetKind::Color; - - let counters = RenderTaskGraphCounters::new(); - let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters); - - let a = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new())); - let b1 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![a])); - let b2 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![a])); - - let main_pic = tasks.add(RenderTask::new_test( - color, - RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)), - vec![b1, b2], - )); - - let initial_number_of_tasks = tasks.tasks.len(); - - let passes = tasks.generate_passes(Some(main_pic), size2(3200, 1800), true); - - // We should not have added any blits. - assert_eq!(tasks.tasks.len(), initial_number_of_tasks); - - assert_eq!(passes.len(), 3); - assert_eq!(passes[0].tasks, vec![a]); - - assert_eq!(passes[1].tasks.len(), 2); - assert!(passes[1].tasks.contains(&b1)); - assert!(passes[1].tasks.contains(&b2)); - - assert_eq!(passes[2].tasks, vec![main_pic]); -} - -#[test] -fn blur_task_graph() { - // This test simulates a complicated shadow stack effect with target allocation - // conflicts to resolve. - - let color = RenderTargetKind::Color; - - let counters = RenderTaskGraphCounters::new(); - let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters); - - let pic = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new())); - let scale1 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![pic])); - let scale2 = tasks.add(RenderTask::new_test(color, dyn_location(160, 160), vec![scale1])); - let scale3 = tasks.add(RenderTask::new_test(color, dyn_location(80, 80), vec![scale2])); - let scale4 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![scale3])); - - let vblur1 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![scale4])); - let hblur1 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![vblur1])); - - let vblur2 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![scale4])); - let hblur2 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![vblur2])); - - // Insert a task that is an even number of passes away from its dependency. - // This means the source and destination are on the same target and we have to resolve - // this conflict by automatically inserting a blit task. - let vblur3 = tasks.add(RenderTask::new_test(color, dyn_location(80, 80), vec![scale3])); - let hblur3 = tasks.add(RenderTask::new_test(color, dyn_location(80, 80), vec![vblur3])); - - // Insert a task that is an odd number > 1 of passes away from its dependency. - // This should force us to mark the dependency "for saving" to keep its content valid - // until the task can access it. - let vblur4 = tasks.add(RenderTask::new_test(color, dyn_location(160, 160), vec![scale2])); - let hblur4 = tasks.add(RenderTask::new_test(color, dyn_location(160, 160), vec![vblur4])); - - let main_pic = tasks.add(RenderTask::new_test( - color, - RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)), - vec![hblur1, hblur2, hblur3, hblur4], - )); - - let initial_number_of_tasks = tasks.tasks.len(); - - let passes = tasks.generate_passes(Some(main_pic), size2(3200, 1800), true); - - // We should have added a single blit task. - assert_eq!(tasks.tasks.len(), initial_number_of_tasks + 1); - - // vblur3's dependency to scale3 should be replaced by a blit. - let blit = tasks[vblur3].children[0]; - assert!(blit != scale3); - - match tasks[blit].kind { - RenderTaskKind::Blit(..) => {} - _ => { panic!("This should be a blit task."); } - } - - assert_eq!(passes.len(), 8); - - assert_eq!(passes[0].tasks, vec![pic]); - assert_eq!(passes[1].tasks, vec![scale1]); - assert_eq!(passes[2].tasks, vec![scale2]); - assert_eq!(passes[3].tasks, vec![scale3]); - - assert_eq!(passes[4].tasks.len(), 2); - assert!(passes[4].tasks.contains(&scale4)); - assert!(passes[4].tasks.contains(&blit)); - - assert_eq!(passes[5].tasks.len(), 4); - assert!(passes[5].tasks.contains(&vblur1)); - assert!(passes[5].tasks.contains(&vblur2)); - assert!(passes[5].tasks.contains(&vblur3)); - assert!(passes[5].tasks.contains(&vblur4)); - - assert_eq!(passes[6].tasks.len(), 4); - assert!(passes[6].tasks.contains(&hblur1)); - assert!(passes[6].tasks.contains(&hblur2)); - assert!(passes[6].tasks.contains(&hblur3)); - assert!(passes[6].tasks.contains(&hblur4)); - - assert_eq!(passes[7].tasks, vec![main_pic]); - - // See vblur4's comment above. - assert!(tasks[scale2].saved_index.is_some()); -} - -#[test] -fn culled_tasks() { - // This test checks that tasks that do not contribute to the frame don't appear in the - // generated passes. - - let color = RenderTargetKind::Color; - - let counters = RenderTaskGraphCounters::new(); - let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters); - - let a1 = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new())); - let _a2 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![a1])); - - let b1 = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new())); - let b2 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![b1])); - let _b3 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![b2])); - - let main_pic = tasks.add(RenderTask::new_test( - color, - RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)), - vec![b2], - )); - - let initial_number_of_tasks = tasks.tasks.len(); - - let passes = tasks.generate_passes(Some(main_pic), size2(3200, 1800), true); - - // We should not have added any blits. - assert_eq!(tasks.tasks.len(), initial_number_of_tasks); - - assert_eq!(passes.len(), 3); - assert_eq!(passes[0].tasks, vec![b1]); - assert_eq!(passes[1].tasks, vec![b2]); - assert_eq!(passes[2].tasks, vec![main_pic]); -} diff --git a/gfx/wr/webrender/src/tiling.rs b/gfx/wr/webrender/src/render_task_graph.rs similarity index 62% rename from gfx/wr/webrender/src/tiling.rs rename to gfx/wr/webrender/src/render_task_graph.rs index ef8562705529..a2c4534ad44c 100644 --- a/gfx/wr/webrender/src/tiling.rs +++ b/gfx/wr/webrender/src/render_task_graph.rs @@ -2,6 +2,11 @@ * 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/. */ +//! This module contains the render task graph and it's output (the Frame). +//! +//! Code associated with creating specific render tasks is in the render_task +//! module. + use api::{ColorF, BorderStyle, FilterPrimitive, MixBlendMode, PipelineId, PremultipliedColorF}; use api::{DocumentLayer, FilterData, ImageFormat, LineOrientation}; use api::units::*; @@ -20,12 +25,11 @@ use crate::prim_store::gradient::GRADIENT_FP_STOPS; use crate::prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer, PrimitiveVisibilityMask}; use crate::profiler::FrameProfileCounters; use crate::render_backend::{DataStores, FrameId}; -use crate::render_task::{BlitSource, RenderTargetKind, RenderTaskAddress, RenderTask, RenderTaskId, RenderTaskKind}; -use crate::render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskGraph, ScalingTask, SvgFilterTask, SvgFilterInfo}; +use crate::render_task::{BlitSource, RenderTargetKind, RenderTask, RenderTaskKind, RenderTaskAddress, RenderTaskData}; +use crate::render_task::{ClearMode, RenderTaskLocation, ScalingTask, SvgFilterInfo}; use crate::resource_cache::ResourceCache; -use std::{cmp, usize, f32, i32, mem}; use crate::texture_allocator::{ArrayAllocationTracker, FreeRectSlice}; - +use std::{cmp, usize, f32, i32, u32, mem}; const STYLE_SOLID: i32 = ((BorderStyle::Solid as i32) << 8) | ((BorderStyle::Solid as i32) << 16); const STYLE_MASK: i32 = 0x00FF_FF00; @@ -39,6 +43,395 @@ const IDEAL_MAX_TEXTURE_DIMENSION: i32 = 2048; /// reasonable number in order to have a bit of leeway in placing things inside. const TEXTURE_DIMENSION_MASK: i32 = 0xFF; +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTaskGraph { + pub tasks: Vec, + pub task_data: Vec, + /// Tasks that don't have dependencies, and that may be shared between + /// picture tasks. + /// + /// We render these unconditionally before-rendering the rest of the tree. + pub cacheable_render_tasks: Vec, + next_saved: SavedTargetIndex, + frame_id: FrameId, +} + +impl RenderTaskGraph { + pub fn new(frame_id: FrameId, counters: &RenderTaskGraphCounters) -> Self { + // Preallocate a little more than what we needed in the previous frame so that small variations + // in the number of items don't cause us to constantly reallocate. + let extra_items = 8; + RenderTaskGraph { + tasks: Vec::with_capacity(counters.tasks_len + extra_items), + task_data: Vec::with_capacity(counters.task_data_len + extra_items), + cacheable_render_tasks: Vec::with_capacity(counters.cacheable_render_tasks_len + extra_items), + next_saved: SavedTargetIndex(0), + frame_id, + } + } + + pub fn counters(&self) -> RenderTaskGraphCounters { + RenderTaskGraphCounters { + tasks_len: self.tasks.len(), + task_data_len: self.task_data.len(), + cacheable_render_tasks_len: self.cacheable_render_tasks.len(), + } + } + + pub fn add(&mut self, task: RenderTask) -> RenderTaskId { + let index = self.tasks.len() as _; + self.tasks.push(task); + RenderTaskId { + index, + #[cfg(debug_assertions)] + frame_id: self.frame_id, + } + } + + /// Express a render task dependency between a parent and child task. + /// This is used to assign tasks to render passes. + pub fn add_dependency( + &mut self, + parent_id: RenderTaskId, + child_id: RenderTaskId, + ) { + let parent = &mut self[parent_id]; + parent.children.push(child_id); + } + + /// Assign this frame's render tasks to render passes ordered so that passes appear + /// earlier than the ones that depend on them. + pub fn generate_passes( + &mut self, + main_render_task: Option, + screen_size: DeviceIntSize, + gpu_supports_fast_clears: bool, + ) -> Vec { + let mut passes = Vec::new(); + + if !self.cacheable_render_tasks.is_empty() { + self.generate_passes_impl( + &self.cacheable_render_tasks[..], + screen_size, + gpu_supports_fast_clears, + false, + &mut passes, + ); + } + + if let Some(main_task) = main_render_task { + self.generate_passes_impl( + &[main_task], + screen_size, + gpu_supports_fast_clears, + true, + &mut passes, + ); + } + + + self.resolve_target_conflicts(&mut passes); + + passes + } + + /// Assign the render tasks from the tree rooted at root_task to render passes and + /// append them to the `passes` vector so that the passes that we depend on end up + /// _earlier_ in the pass list. + fn generate_passes_impl( + &self, + root_tasks: &[RenderTaskId], + screen_size: DeviceIntSize, + gpu_supports_fast_clears: bool, + for_main_framebuffer: bool, + passes: &mut Vec, + ) { + // We recursively visit tasks from the roots (main and cached render tasks), to figure out + // which ones affect the frame and which passes they should be assigned to. + // + // We track the maximum depth of each task (how far it is from the roots) as well as the total + // maximum depth of the graph to determine each tasks' pass index. In a nutshell, depth 0 is + // for the last render pass (for example the main framebuffer), while the highest depth + // corresponds to the first pass. + + fn assign_task_depth( + tasks: &[RenderTask], + task_id: RenderTaskId, + task_depth: i32, + task_max_depths: &mut [i32], + max_depth: &mut i32, + ) { + *max_depth = std::cmp::max(*max_depth, task_depth); + + let task_max_depth = &mut task_max_depths[task_id.index as usize]; + if task_depth > *task_max_depth { + *task_max_depth = task_depth; + } else { + // If this task has already been processed at a larger depth, + // there is no need to process it again. + return; + } + + let task = &tasks[task_id.index as usize]; + for child in &task.children { + assign_task_depth( + tasks, + *child, + task_depth + 1, + task_max_depths, + max_depth, + ); + } + } + + // The maximum depth of each task. Values that are still equal to -1 after recursively visiting + // the nodes correspond to tasks that don't contribute to the frame. + let mut task_max_depths = vec![-1; self.tasks.len()]; + let mut max_depth = 0; + + for root_task in root_tasks { + assign_task_depth( + &self.tasks, + *root_task, + 0, + &mut task_max_depths, + &mut max_depth, + ); + } + + let offset = passes.len(); + + passes.reserve(max_depth as usize + 1); + for _ in 0..max_depth { + passes.push(RenderPass::new_off_screen(screen_size, gpu_supports_fast_clears)); + } + + if for_main_framebuffer { + passes.push(RenderPass::new_main_framebuffer(screen_size, gpu_supports_fast_clears)); + } else { + passes.push(RenderPass::new_off_screen(screen_size, gpu_supports_fast_clears)); + } + + // Assign tasks to their render passes. + for task_index in 0..self.tasks.len() { + if task_max_depths[task_index] < 0 { + // The task wasn't visited, it means it doesn't contribute to this frame. + continue; + } + let pass_index = offset + (max_depth - task_max_depths[task_index]) as usize; + let task_id = RenderTaskId { + index: task_index as u32, + #[cfg(debug_assertions)] + frame_id: self.frame_id, + }; + let task = &self.tasks[task_index]; + passes[pass_index as usize].add_render_task( + task_id, + task.get_dynamic_size(), + task.target_kind(), + &task.location, + ); + } + } + + /// Resolve conflicts between the generated passes and the limitiations of our target + /// allocation scheme. + /// + /// The render task graph operates with a ping-pong target allocation scheme where + /// a set of targets is written to by even passes and a different set of targets is + /// written to by odd passes. + /// Since tasks cannot read and write the same target, we can run into issues if a + /// task pass in N + 2 reads the result of a task in pass N. + /// To avoid such cases have to insert blit tasks to copy the content of the task + /// into pass N + 1 which is readable by pass N + 2. + /// + /// In addition, allocated rects of pass N are currently not tracked and can be + /// overwritten by allocations in later passes on the same target, unless the task + /// has been marked for saving, which perserves the allocated rect until the end of + /// the frame. This is a big hammer, hopefully we won't need to mark many passes + /// for saving. A better solution would be to track allocations through the entire + /// graph, there is a prototype of that in https://github.com/nical/toy-render-graph/ + fn resolve_target_conflicts(&mut self, passes: &mut [RenderPass]) { + // Keep track of blit tasks we inserted to avoid adding several blits for the same + // task. + let mut task_redirects = vec![None; self.tasks.len()]; + + let mut task_passes = vec![-1; self.tasks.len()]; + for pass_index in 0..passes.len() { + for task in &passes[pass_index].tasks { + task_passes[task.index as usize] = pass_index as i32; + } + } + + for task_index in 0..self.tasks.len() { + if task_passes[task_index] < 0 { + // The task doesn't contribute to this frame. + continue; + } + + let pass_index = task_passes[task_index]; + + // Go through each dependency and check whether they belong + // to a pass that uses the same targets and/or are more than + // one pass behind. + for nth_child in 0..self.tasks[task_index].children.len() { + let child_task_index = self.tasks[task_index].children[nth_child].index as usize; + let child_pass_index = task_passes[child_task_index]; + + if child_pass_index == pass_index - 1 { + // This should be the most common case. + continue; + } + + // TODO: Picture tasks don't support having their dependency tasks redirected. + // Pictures store their respective render task(s) on their SurfaceInfo. + // We cannot blit the picture task here because we would need to update the + // surface's render tasks, but we don't have access to that info here. + // Also a surface may be expecting a picture task and not a blit task, so + // even if we could update the surface's render task(s), it might cause other issues. + // For now we mark the task to be saved rather than trying to redirect to a blit task. + let task_is_picture = if let RenderTaskKind::Picture(..) = self.tasks[task_index].kind { + true + } else { + false + }; + + if child_pass_index % 2 != pass_index % 2 || task_is_picture { + // The tasks and its dependency aren't on the same targets, + // but the dependency needs to be kept alive. + self.tasks[child_task_index].mark_for_saving(); + continue; + } + + if let Some(blit_id) = task_redirects[child_task_index] { + // We already resolved a similar conflict with a blit task, + // reuse the same blit instead of creating a new one. + self.tasks[task_index].children[nth_child] = blit_id; + + // Mark for saving if the blit is more than pass appart from + // our task. + if child_pass_index < pass_index - 2 { + self.tasks[blit_id.index as usize].mark_for_saving(); + } + + continue; + } + + // Our dependency is an even number of passes behind, need + // to insert a blit to ensure we don't read and write from + // the same target. + + let child_task_id = RenderTaskId { + index: child_task_index as u32, + #[cfg(debug_assertions)] + frame_id: self.frame_id, + }; + + let mut blit = RenderTask::new_blit( + self.tasks[child_task_index].location.size(), + BlitSource::RenderTask { task_id: child_task_id }, + ); + + // Mark for saving if the blit is more than pass appart from + // our task. + if child_pass_index < pass_index - 2 { + blit.mark_for_saving(); + } + + let blit_id = RenderTaskId { + index: self.tasks.len() as u32, + #[cfg(debug_assertions)] + frame_id: self.frame_id, + }; + + self.tasks.push(blit); + + passes[child_pass_index as usize + 1].tasks.push(blit_id); + + self.tasks[task_index].children[nth_child] = blit_id; + task_redirects[child_task_index] = Some(blit_id); + } + } + } + + pub fn get_task_address(&self, id: RenderTaskId) -> RenderTaskAddress { + #[cfg(all(debug_assertions, not(feature = "replay")))] + debug_assert_eq!(self.frame_id, id.frame_id); + RenderTaskAddress(id.index as u16) + } + + pub fn write_task_data(&mut self) { + for task in &self.tasks { + self.task_data.push(task.write_task_data()); + } + } + + pub fn save_target(&mut self) -> SavedTargetIndex { + let id = self.next_saved; + self.next_saved.0 += 1; + id + } + + #[cfg(debug_assertions)] + pub fn frame_id(&self) -> FrameId { + self.frame_id + } +} + +impl std::ops::Index for RenderTaskGraph { + type Output = RenderTask; + fn index(&self, id: RenderTaskId) -> &RenderTask { + #[cfg(all(debug_assertions, not(feature = "replay")))] + debug_assert_eq!(self.frame_id, id.frame_id); + &self.tasks[id.index as usize] + } +} + +impl std::ops::IndexMut for RenderTaskGraph { + fn index_mut(&mut self, id: RenderTaskId) -> &mut RenderTask { + #[cfg(all(debug_assertions, not(feature = "replay")))] + debug_assert_eq!(self.frame_id, id.frame_id); + &mut self.tasks[id.index as usize] + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct RenderTaskId { + pub index: u32, + + #[cfg(debug_assertions)] + #[cfg_attr(feature = "replay", serde(default = "FrameId::first"))] + frame_id: FrameId, +} + +#[derive(Debug)] +pub struct RenderTaskGraphCounters { + tasks_len: usize, + task_data_len: usize, + cacheable_render_tasks_len: usize, +} + +impl RenderTaskGraphCounters { + pub fn new() -> Self { + RenderTaskGraphCounters { + tasks_len: 0, + task_data_len: 0, + cacheable_render_tasks_len: 0, + } + } +} + +impl RenderTaskId { + pub const INVALID: RenderTaskId = RenderTaskId { + index: u32::MAX, + #[cfg(debug_assertions)] + frame_id: FrameId::INVALID, + }; +} + /// Identifies a given `RenderTarget` in a `RenderTargetList`. #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "capture", derive(Serialize))] @@ -474,16 +867,16 @@ impl RenderTarget for ColorRenderTarget { let task = &render_tasks[task_id]; match task.kind { - RenderTaskKind::VerticalBlur(ref info) => { - info.add_instances( + RenderTaskKind::VerticalBlur(..) => { + add_blur_instances( &mut self.vertical_blurs, BlurDirection::Vertical, render_tasks.get_task_address(task_id), render_tasks.get_task_address(task.children[0]), ); } - RenderTaskKind::HorizontalBlur(ref info) => { - info.add_instances( + RenderTaskKind::HorizontalBlur(..) => { + add_blur_instances( &mut self.horizontal_blurs, BlurDirection::Horizontal, render_tasks.get_task_address(task_id), @@ -504,7 +897,7 @@ impl RenderTarget for ColorRenderTarget { } } RenderTaskKind::SvgFilter(ref task_info) => { - task_info.add_instances( + add_svg_filter_instances( &mut self.svg_filters, render_tasks, &task_info.info, @@ -525,7 +918,8 @@ impl RenderTarget for ColorRenderTarget { self.readbacks.push(device_rect); } RenderTaskKind::Scaling(ref info) => { - info.add_instances( + add_scaling_instances( + info, &mut self.scalings, task, task.children.first().map(|&child| &render_tasks[child]), @@ -671,16 +1065,16 @@ impl RenderTarget for AlphaRenderTarget { RenderTaskKind::SvgFilter(..) => { panic!("BUG: should not be added to alpha target!"); } - RenderTaskKind::VerticalBlur(ref info) => { - info.add_instances( + RenderTaskKind::VerticalBlur(..) => { + add_blur_instances( &mut self.vertical_blurs, BlurDirection::Vertical, render_tasks.get_task_address(task_id), render_tasks.get_task_address(task.children[0]), ); } - RenderTaskKind::HorizontalBlur(ref info) => { - info.add_instances( + RenderTaskKind::HorizontalBlur(..) => { + add_blur_instances( &mut self.horizontal_blurs, BlurDirection::Horizontal, render_tasks.get_task_address(task_id), @@ -720,7 +1114,8 @@ impl RenderTarget for AlphaRenderTarget { ); } RenderTaskKind::Scaling(ref info) => { - info.add_instances( + add_scaling_instances( + info, &mut self.scalings, task, task.children.first().map(|&child| &render_tasks[child]), @@ -808,8 +1203,8 @@ impl TextureCacheRenderTarget { wavy_line_thickness: info.wavy_line_thickness, }); } - RenderTaskKind::HorizontalBlur(ref info) => { - info.add_instances( + RenderTaskKind::HorizontalBlur(..) => { + add_blur_instances( &mut self.horizontal_blurs, BlurDirection::Horizontal, task_address, @@ -1379,187 +1774,533 @@ impl Frame { } } -impl BlurTask { - fn add_instances( - &self, - instances: &mut Vec, - blur_direction: BlurDirection, - task_address: RenderTaskAddress, - src_task_address: RenderTaskAddress, - ) { - let instance = BlurInstance { - task_address, - src_task_address, - blur_direction, - }; +fn add_blur_instances( + instances: &mut Vec, + blur_direction: BlurDirection, + task_address: RenderTaskAddress, + src_task_address: RenderTaskAddress, +) { + let instance = BlurInstance { + task_address, + src_task_address, + blur_direction, + }; - instances.push(instance); - } + instances.push(instance); } -impl ScalingTask { - fn add_instances( - &self, - instances: &mut FastHashMap>, - target_task: &RenderTask, - source_task: Option<&RenderTask>, - resource_cache: &ResourceCache, - gpu_cache: &mut GpuCache, - deferred_resolves: &mut Vec, - ) { - let target_rect = target_task - .get_target_rect() - .0 - .inner_rect(self.padding) - .to_f32(); +fn add_scaling_instances( + task: &ScalingTask, + instances: &mut FastHashMap>, + target_task: &RenderTask, + source_task: Option<&RenderTask>, + resource_cache: &ResourceCache, + gpu_cache: &mut GpuCache, + deferred_resolves: &mut Vec, +) { + let target_rect = target_task + .get_target_rect() + .0 + .inner_rect(task.padding) + .to_f32(); - let (source, (source_rect, source_layer)) = match self.image { - Some(key) => { - assert!(source_task.is_none()); + let (source, (source_rect, source_layer)) = match task.image { + Some(key) => { + assert!(source_task.is_none()); - // Get the cache item for the source texture. - let cache_item = resolve_image( - key.request, - resource_cache, - gpu_cache, - deferred_resolves, - ); + // Get the cache item for the source texture. + let cache_item = resolve_image( + key.request, + resource_cache, + gpu_cache, + deferred_resolves, + ); - // Work out a source rect to copy from the texture, depending on whether - // a sub-rect is present or not. - let source_rect = key.texel_rect.map_or(cache_item.uv_rect, |sub_rect| { - DeviceIntRect::new( - DeviceIntPoint::new( - cache_item.uv_rect.origin.x + sub_rect.origin.x, - cache_item.uv_rect.origin.y + sub_rect.origin.y, - ), - sub_rect.size, - ) - }); - - ( - cache_item.texture_id, - (source_rect, cache_item.texture_layer as LayerIndex), + // Work out a source rect to copy from the texture, depending on whether + // a sub-rect is present or not. + let source_rect = key.texel_rect.map_or(cache_item.uv_rect, |sub_rect| { + DeviceIntRect::new( + DeviceIntPoint::new( + cache_item.uv_rect.origin.x + sub_rect.origin.x, + cache_item.uv_rect.origin.y + sub_rect.origin.y, + ), + sub_rect.size, ) - } - None => { - ( - match self.target_kind { - RenderTargetKind::Color => TextureSource::PrevPassColor, - RenderTargetKind::Alpha => TextureSource::PrevPassAlpha, - }, - source_task.unwrap().location.to_source_rect(), - ) - } - }; - - instances - .entry(source) - .or_insert(Vec::new()) - .push(ScalingInstance { - target_rect, - source_rect, - source_layer: source_layer as i32, }); - } + + ( + cache_item.texture_id, + (source_rect, cache_item.texture_layer as LayerIndex), + ) + } + None => { + ( + match task.target_kind { + RenderTargetKind::Color => TextureSource::PrevPassColor, + RenderTargetKind::Alpha => TextureSource::PrevPassAlpha, + }, + source_task.unwrap().location.to_source_rect(), + ) + } + }; + + instances + .entry(source) + .or_insert(Vec::new()) + .push(ScalingInstance { + target_rect, + source_rect, + source_layer: source_layer as i32, + }); } -impl SvgFilterTask { - fn add_instances( - &self, - instances: &mut Vec<(BatchTextures, Vec)>, - render_tasks: &RenderTaskGraph, - filter: &SvgFilterInfo, - task_id: RenderTaskId, - input_1_task: Option, - input_2_task: Option, - extra_data_address: Option, - ) { - let mut textures = BatchTextures::no_texture(); +fn add_svg_filter_instances( + instances: &mut Vec<(BatchTextures, Vec)>, + render_tasks: &RenderTaskGraph, + filter: &SvgFilterInfo, + task_id: RenderTaskId, + input_1_task: Option, + input_2_task: Option, + extra_data_address: Option, +) { + let mut textures = BatchTextures::no_texture(); - if let Some(saved_index) = input_1_task.map(|id| &render_tasks[id].saved_index) { - textures.colors[0] = match saved_index { - Some(saved_index) => TextureSource::RenderTaskCache(*saved_index, Swizzle::default()), - None => TextureSource::PrevPassColor, - }; + if let Some(saved_index) = input_1_task.map(|id| &render_tasks[id].saved_index) { + textures.colors[0] = match saved_index { + Some(saved_index) => TextureSource::RenderTaskCache(*saved_index, Swizzle::default()), + None => TextureSource::PrevPassColor, + }; + } + + if let Some(saved_index) = input_2_task.map(|id| &render_tasks[id].saved_index) { + textures.colors[1] = match saved_index { + Some(saved_index) => TextureSource::RenderTaskCache(*saved_index, Swizzle::default()), + None => TextureSource::PrevPassColor, + }; + } + + let kind = match filter { + SvgFilterInfo::Blend(..) => 0, + SvgFilterInfo::Flood(..) => 1, + SvgFilterInfo::LinearToSrgb => 2, + SvgFilterInfo::SrgbToLinear => 3, + SvgFilterInfo::Opacity(..) => 4, + SvgFilterInfo::ColorMatrix(..) => 5, + SvgFilterInfo::DropShadow(..) => 6, + SvgFilterInfo::Offset(..) => 7, + SvgFilterInfo::ComponentTransfer(..) => 8, + SvgFilterInfo::Identity => 9, + SvgFilterInfo::Composite(..) => 10, + }; + + let input_count = match filter { + SvgFilterInfo::Flood(..) => 0, + + SvgFilterInfo::LinearToSrgb | + SvgFilterInfo::SrgbToLinear | + SvgFilterInfo::Opacity(..) | + SvgFilterInfo::ColorMatrix(..) | + SvgFilterInfo::Offset(..) | + SvgFilterInfo::ComponentTransfer(..) | + SvgFilterInfo::Identity => 1, + + // Not techincally a 2 input filter, but we have 2 inputs here: original content & blurred content. + SvgFilterInfo::DropShadow(..) | + SvgFilterInfo::Blend(..) | + SvgFilterInfo::Composite(..) => 2, + }; + + let generic_int = match filter { + SvgFilterInfo::Blend(mode) => *mode as u16, + SvgFilterInfo::ComponentTransfer(data) => + ((data.r_func.to_int() << 12 | + data.g_func.to_int() << 8 | + data.b_func.to_int() << 4 | + data.a_func.to_int()) as u16), + SvgFilterInfo::Composite(operator) => + operator.as_int() as u16, + SvgFilterInfo::LinearToSrgb | + SvgFilterInfo::SrgbToLinear | + SvgFilterInfo::Flood(..) | + SvgFilterInfo::Opacity(..) | + SvgFilterInfo::ColorMatrix(..) | + SvgFilterInfo::DropShadow(..) | + SvgFilterInfo::Offset(..) | + SvgFilterInfo::Identity => 0, + }; + + let instance = SvgFilterInstance { + task_address: render_tasks.get_task_address(task_id), + input_1_task_address: input_1_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)), + input_2_task_address: input_2_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)), + kind, + input_count, + generic_int, + extra_data_address: extra_data_address.unwrap_or(GpuCacheAddress::INVALID), + }; + + for (ref mut batch_textures, ref mut batch) in instances.iter_mut() { + if let Some(combined_textures) = batch_textures.combine_textures(textures) { + batch.push(instance); + // Update the batch textures to the newly combined batch textures + *batch_textures = combined_textures; + return; + } + } + + instances.push((textures, vec![instance])); +} + +// Dump an SVG visualization of the render graph for debugging purposes +#[allow(dead_code)] +pub fn dump_render_tasks_as_svg( + render_tasks: &RenderTaskGraph, + passes: &[RenderPass], + output: &mut dyn std::io::Write, +) -> std::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 saved = if task.saved_index.is_some() { " (Saved)" } else { "" }; + let label = text(tx, ty, format!("{}{}", task.kind.as_str(), saved)); + let size = text(tx, ty + 12.0, format!("{}", task.location.size())); + + nodes[task_index] = Some(Node { rect, label, size }); + + layout.advance(vertical_spacing); } - if let Some(saved_index) = input_2_task.map(|id| &render_tasks[id].saved_index) { - textures.colors[1] = match saved_index { - Some(saved_index) => TextureSource::RenderTaskCache(*saved_index, Swizzle::default()), - None => TextureSource::PrevPassColor, - }; + 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 kind = match filter { - SvgFilterInfo::Blend(..) => 0, - SvgFilterInfo::Flood(..) => 1, - SvgFilterInfo::LinearToSrgb => 2, - SvgFilterInfo::SrgbToLinear => 3, - SvgFilterInfo::Opacity(..) => 4, - SvgFilterInfo::ColorMatrix(..) => 5, - SvgFilterInfo::DropShadow(..) => 6, - SvgFilterInfo::Offset(..) => 7, - SvgFilterInfo::ComponentTransfer(..) => 8, - SvgFilterInfo::Identity => 9, - SvgFilterInfo::Composite(..) => 10, - }; + let task = &render_tasks.tasks[node_index]; + for dep in &task.children { + let dep_index = dep.index as usize; - let input_count = match filter { - SvgFilterInfo::Flood(..) => 0, - - SvgFilterInfo::LinearToSrgb | - SvgFilterInfo::SrgbToLinear | - SvgFilterInfo::Opacity(..) | - SvgFilterInfo::ColorMatrix(..) | - SvgFilterInfo::Offset(..) | - SvgFilterInfo::ComponentTransfer(..) | - SvgFilterInfo::Identity => 1, - - // Not techincally a 2 input filter, but we have 2 inputs here: original content & blurred content. - SvgFilterInfo::DropShadow(..) | - SvgFilterInfo::Blend(..) | - SvgFilterInfo::Composite(..) => 2, - }; - - let generic_int = match filter { - SvgFilterInfo::Blend(mode) => *mode as u16, - SvgFilterInfo::ComponentTransfer(data) => - ((data.r_func.to_int() << 12 | - data.g_func.to_int() << 8 | - data.b_func.to_int() << 4 | - data.a_func.to_int()) as u16), - SvgFilterInfo::Composite(operator) => - operator.as_int() as u16, - SvgFilterInfo::LinearToSrgb | - SvgFilterInfo::SrgbToLinear | - SvgFilterInfo::Flood(..) | - SvgFilterInfo::Opacity(..) | - SvgFilterInfo::ColorMatrix(..) | - SvgFilterInfo::DropShadow(..) | - SvgFilterInfo::Offset(..) | - SvgFilterInfo::Identity => 0, - }; - - let instance = SvgFilterInstance { - task_address: render_tasks.get_task_address(task_id), - input_1_task_address: input_1_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)), - input_2_task_address: input_2_task.map(|id| render_tasks.get_task_address(id)).unwrap_or(RenderTaskAddress(0)), - kind, - input_count, - generic_int, - extra_data_address: extra_data_address.unwrap_or(GpuCacheAddress::INVALID), - }; - - for (ref mut batch_textures, ref mut batch) in instances.iter_mut() { - if let Some(combined_textures) = batch_textures.combine_textures(textures) { - batch.push(instance); - // Update the batch textures to the newly combined batch textures - *batch_textures = combined_textures; - return; + 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, + )); } } + } - instances.push((textures, vec![instance])); + 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 dyn std::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(); } } + +#[cfg(test)] +use euclid::{size2, rect}; + +#[cfg(test)] +fn dyn_location(w: i32, h: i32) -> RenderTaskLocation { + RenderTaskLocation::Dynamic(None, size2(w, h)) +} + +#[test] +fn diamond_task_graph() { + // A simple diamon shaped task graph. + // + // [b1] + // / \ + // [a] [main_pic] + // \ / + // [b2] + + let color = RenderTargetKind::Color; + + let counters = RenderTaskGraphCounters::new(); + let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters); + + let a = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new())); + let b1 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![a])); + let b2 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![a])); + + let main_pic = tasks.add(RenderTask::new_test( + color, + RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)), + vec![b1, b2], + )); + + let initial_number_of_tasks = tasks.tasks.len(); + + let passes = tasks.generate_passes(Some(main_pic), size2(3200, 1800), true); + + // We should not have added any blits. + assert_eq!(tasks.tasks.len(), initial_number_of_tasks); + + assert_eq!(passes.len(), 3); + assert_eq!(passes[0].tasks, vec![a]); + + assert_eq!(passes[1].tasks.len(), 2); + assert!(passes[1].tasks.contains(&b1)); + assert!(passes[1].tasks.contains(&b2)); + + assert_eq!(passes[2].tasks, vec![main_pic]); +} + +#[test] +fn blur_task_graph() { + // This test simulates a complicated shadow stack effect with target allocation + // conflicts to resolve. + + let color = RenderTargetKind::Color; + + let counters = RenderTaskGraphCounters::new(); + let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters); + + let pic = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new())); + let scale1 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![pic])); + let scale2 = tasks.add(RenderTask::new_test(color, dyn_location(160, 160), vec![scale1])); + let scale3 = tasks.add(RenderTask::new_test(color, dyn_location(80, 80), vec![scale2])); + let scale4 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![scale3])); + + let vblur1 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![scale4])); + let hblur1 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![vblur1])); + + let vblur2 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![scale4])); + let hblur2 = tasks.add(RenderTask::new_test(color, dyn_location(40, 40), vec![vblur2])); + + // Insert a task that is an even number of passes away from its dependency. + // This means the source and destination are on the same target and we have to resolve + // this conflict by automatically inserting a blit task. + let vblur3 = tasks.add(RenderTask::new_test(color, dyn_location(80, 80), vec![scale3])); + let hblur3 = tasks.add(RenderTask::new_test(color, dyn_location(80, 80), vec![vblur3])); + + // Insert a task that is an odd number > 1 of passes away from its dependency. + // This should force us to mark the dependency "for saving" to keep its content valid + // until the task can access it. + let vblur4 = tasks.add(RenderTask::new_test(color, dyn_location(160, 160), vec![scale2])); + let hblur4 = tasks.add(RenderTask::new_test(color, dyn_location(160, 160), vec![vblur4])); + + let main_pic = tasks.add(RenderTask::new_test( + color, + RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)), + vec![hblur1, hblur2, hblur3, hblur4], + )); + + let initial_number_of_tasks = tasks.tasks.len(); + + let passes = tasks.generate_passes(Some(main_pic), size2(3200, 1800), true); + + // We should have added a single blit task. + assert_eq!(tasks.tasks.len(), initial_number_of_tasks + 1); + + // vblur3's dependency to scale3 should be replaced by a blit. + let blit = tasks[vblur3].children[0]; + assert!(blit != scale3); + + match tasks[blit].kind { + RenderTaskKind::Blit(..) => {} + _ => { panic!("This should be a blit task."); } + } + + assert_eq!(passes.len(), 8); + + assert_eq!(passes[0].tasks, vec![pic]); + assert_eq!(passes[1].tasks, vec![scale1]); + assert_eq!(passes[2].tasks, vec![scale2]); + assert_eq!(passes[3].tasks, vec![scale3]); + + assert_eq!(passes[4].tasks.len(), 2); + assert!(passes[4].tasks.contains(&scale4)); + assert!(passes[4].tasks.contains(&blit)); + + assert_eq!(passes[5].tasks.len(), 4); + assert!(passes[5].tasks.contains(&vblur1)); + assert!(passes[5].tasks.contains(&vblur2)); + assert!(passes[5].tasks.contains(&vblur3)); + assert!(passes[5].tasks.contains(&vblur4)); + + assert_eq!(passes[6].tasks.len(), 4); + assert!(passes[6].tasks.contains(&hblur1)); + assert!(passes[6].tasks.contains(&hblur2)); + assert!(passes[6].tasks.contains(&hblur3)); + assert!(passes[6].tasks.contains(&hblur4)); + + assert_eq!(passes[7].tasks, vec![main_pic]); + + // See vblur4's comment above. + assert!(tasks[scale2].saved_index.is_some()); +} + +#[test] +fn culled_tasks() { + // This test checks that tasks that do not contribute to the frame don't appear in the + // generated passes. + + let color = RenderTargetKind::Color; + + let counters = RenderTaskGraphCounters::new(); + let mut tasks = RenderTaskGraph::new(FrameId::first(), &counters); + + let a1 = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new())); + let _a2 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![a1])); + + let b1 = tasks.add(RenderTask::new_test(color, dyn_location(640, 640), Vec::new())); + let b2 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![b1])); + let _b3 = tasks.add(RenderTask::new_test(color, dyn_location(320, 320), vec![b2])); + + let main_pic = tasks.add(RenderTask::new_test( + color, + RenderTaskLocation::Fixed(rect(0, 0, 3200, 1800)), + vec![b2], + )); + + let initial_number_of_tasks = tasks.tasks.len(); + + let passes = tasks.generate_passes(Some(main_pic), size2(3200, 1800), true); + + // We should not have added any blits. + assert_eq!(tasks.tasks.len(), initial_number_of_tasks); + + assert_eq!(passes.len(), 3); + assert_eq!(passes[0].tasks, vec![b1]); + assert_eq!(passes[1].tasks, vec![b2]); + assert_eq!(passes[2].tasks, vec![main_pic]); +} diff --git a/gfx/wr/webrender/src/renderer.rs b/gfx/wr/webrender/src/renderer.rs index 022af40b379d..89355a0c4266 100644 --- a/gfx/wr/webrender/src/renderer.rs +++ b/gfx/wr/webrender/src/renderer.rs @@ -77,16 +77,17 @@ use crate::device::query::{GpuProfiler, GpuDebugMethod}; use rayon::{ThreadPool, ThreadPoolBuilder}; use crate::record::ApiRecordingReceiver; use crate::render_backend::{FrameId, RenderBackend}; -use crate::render_task::{RenderTargetKind, RenderTask, RenderTaskData, RenderTaskKind, RenderTaskGraph}; +use crate::render_task_graph::RenderTaskGraph; +use crate::render_task::{RenderTargetKind, RenderTask, RenderTaskData, RenderTaskKind}; use crate::resource_cache::ResourceCache; use crate::scene_builder::{SceneBuilder, LowPrioritySceneBuilder}; use crate::screen_capture::AsyncScreenshotGrabber; use crate::shade::{Shaders, WrShaders}; use smallvec::SmallVec; use crate::texture_cache::TextureCache; -use crate::tiling::{AlphaRenderTarget, ColorRenderTarget, PictureCacheTarget}; -use crate::tiling::{BlitJob, BlitJobSource, RenderPassKind, RenderTargetList}; -use crate::tiling::{Frame, RenderTarget, TextureCacheRenderTarget}; +use crate::render_task_graph::{AlphaRenderTarget, ColorRenderTarget, PictureCacheTarget}; +use crate::render_task_graph::{BlitJob, BlitJobSource, RenderPassKind, RenderTargetList}; +use crate::render_task_graph::{Frame, RenderTarget, TextureCacheRenderTarget}; use crate::util::drain_filter; use std; diff --git a/gfx/wr/webrender/src/resource_cache.rs b/gfx/wr/webrender/src/resource_cache.rs index db8a5deb8bb8..309c3df15866 100644 --- a/gfx/wr/webrender/src/resource_cache.rs +++ b/gfx/wr/webrender/src/resource_cache.rs @@ -28,8 +28,9 @@ use crate::image::{compute_tile_size, compute_tile_range, for_each_tile_in_range use crate::internal_types::{FastHashMap, FastHashSet, TextureSource, TextureUpdateList}; use crate::profiler::{ResourceProfileCounters, TextureCacheProfileCounters}; use crate::render_backend::{FrameId, FrameStamp}; -use crate::render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId}; -use crate::render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskGraph}; +use crate::render_task_graph::{RenderTaskGraph, RenderTaskId}; +use crate::render_task::{RenderTaskCache, RenderTaskCacheKey}; +use crate::render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle}; use smallvec::SmallVec; use std::collections::hash_map::Entry::{self, Occupied, Vacant}; use std::collections::hash_map::{Iter, IterMut};