mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 16:55:40 +00:00
Bug 1577766 - Rename tiling.rs into render_task_graph.rs and move some code around. r=gw
Differential Revision: https://phabricator.services.mozilla.com/D44165 --HG-- rename : gfx/wr/webrender/src/tiling.rs => gfx/wr/webrender/src/render_task_graph.rs extra : moz-landing-system : lando
This commit is contained in:
parent
2e3f0733db
commit
87c3918219
@ -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
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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};
|
||||
|
@ -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<K, V> = HashMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;
|
||||
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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};
|
||||
|
@ -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() {
|
||||
|
@ -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<RenderTask>,
|
||||
pub task_data: Vec<RenderTaskData>,
|
||||
/// 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<RenderTaskId>,
|
||||
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<RenderTaskId>,
|
||||
screen_size: DeviceIntSize,
|
||||
gpu_supports_fast_clears: bool,
|
||||
) -> Vec<RenderPass> {
|
||||
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<RenderPass>,
|
||||
) {
|
||||
// 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<RenderTaskId> 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<RenderTaskId> 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]);
|
||||
}
|
||||
|
@ -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<RenderTask>,
|
||||
pub task_data: Vec<RenderTaskData>,
|
||||
/// 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<RenderTaskId>,
|
||||
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<RenderTaskId>,
|
||||
screen_size: DeviceIntSize,
|
||||
gpu_supports_fast_clears: bool,
|
||||
) -> Vec<RenderPass> {
|
||||
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<RenderPass>,
|
||||
) {
|
||||
// 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<RenderTaskId> 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<RenderTaskId> 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,14 +1774,12 @@ impl Frame {
|
||||
}
|
||||
}
|
||||
|
||||
impl BlurTask {
|
||||
fn add_instances(
|
||||
&self,
|
||||
fn add_blur_instances(
|
||||
instances: &mut Vec<BlurInstance>,
|
||||
blur_direction: BlurDirection,
|
||||
task_address: RenderTaskAddress,
|
||||
src_task_address: RenderTaskAddress,
|
||||
) {
|
||||
) {
|
||||
let instance = BlurInstance {
|
||||
task_address,
|
||||
src_task_address,
|
||||
@ -1394,26 +1787,24 @@ impl BlurTask {
|
||||
};
|
||||
|
||||
instances.push(instance);
|
||||
}
|
||||
}
|
||||
|
||||
impl ScalingTask {
|
||||
fn add_instances(
|
||||
&self,
|
||||
fn add_scaling_instances(
|
||||
task: &ScalingTask,
|
||||
instances: &mut FastHashMap<TextureSource, Vec<ScalingInstance>>,
|
||||
target_task: &RenderTask,
|
||||
source_task: Option<&RenderTask>,
|
||||
resource_cache: &ResourceCache,
|
||||
gpu_cache: &mut GpuCache,
|
||||
deferred_resolves: &mut Vec<DeferredResolve>,
|
||||
) {
|
||||
) {
|
||||
let target_rect = target_task
|
||||
.get_target_rect()
|
||||
.0
|
||||
.inner_rect(self.padding)
|
||||
.inner_rect(task.padding)
|
||||
.to_f32();
|
||||
|
||||
let (source, (source_rect, source_layer)) = match self.image {
|
||||
let (source, (source_rect, source_layer)) = match task.image {
|
||||
Some(key) => {
|
||||
assert!(source_task.is_none());
|
||||
|
||||
@ -1444,7 +1835,7 @@ impl ScalingTask {
|
||||
}
|
||||
None => {
|
||||
(
|
||||
match self.target_kind {
|
||||
match task.target_kind {
|
||||
RenderTargetKind::Color => TextureSource::PrevPassColor,
|
||||
RenderTargetKind::Alpha => TextureSource::PrevPassAlpha,
|
||||
},
|
||||
@ -1461,12 +1852,9 @@ impl ScalingTask {
|
||||
source_rect,
|
||||
source_layer: source_layer as i32,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl SvgFilterTask {
|
||||
fn add_instances(
|
||||
&self,
|
||||
fn add_svg_filter_instances(
|
||||
instances: &mut Vec<(BatchTextures, Vec<SvgFilterInstance>)>,
|
||||
render_tasks: &RenderTaskGraph,
|
||||
filter: &SvgFilterInfo,
|
||||
@ -1474,7 +1862,7 @@ impl SvgFilterTask {
|
||||
input_1_task: Option<RenderTaskId>,
|
||||
input_2_task: Option<RenderTaskId>,
|
||||
extra_data_address: Option<GpuCacheAddress>,
|
||||
) {
|
||||
) {
|
||||
let mut textures = BatchTextures::no_texture();
|
||||
|
||||
if let Some(saved_index) = input_1_task.map(|id| &render_tasks[id].saved_index) {
|
||||
@ -1561,5 +1949,358 @@ impl SvgFilterTask {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 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]);
|
||||
}
|
@ -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;
|
||||
|
@ -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};
|
||||
|
Loading…
Reference in New Issue
Block a user